Browse Source

Initial commit

main
maochaoying 2 years ago
commit
0a031ffb40
  1. 4
      .env
  2. 4
      .env.dev
  3. 4
      .env.prod
  4. 20
      .eslintrc.cjs
  5. 24
      .gitignore
  6. 11
      .prettierrc
  7. 17
      README.md
  8. 13
      index.html
  9. 1
      jsconfig.json
  10. 34
      package.json
  11. 1
      public/vite.svg
  12. 10
      src/App.jsx
  13. 110
      src/assets/css/reset.css
  14. BIN
      src/assets/img/login/bg.gif
  15. 5
      src/components/Add.jsx
  16. 0
      src/index.css
  17. 115
      src/layouts/Base.jsx
  18. 20
      src/main.jsx
  19. 9
      src/request/clientError.js
  20. 115
      src/request/index.js
  21. 13
      src/router/index.jsx
  22. 18
      src/store/CountStore.js
  23. 24
      src/store/index.jsx
  24. 13
      src/views/Login/index.jsx
  25. 22
      src/views/Login/index.module.less
  26. 13
      vite.config.js
  27. 2926
      yarn.lock

4
.env

@ -0,0 +1,4 @@
VITE_APP_TITLE=Hello
VITE_APP_PROXY_URL=https://next.bspapp.com/server

4
.env.dev

@ -0,0 +1,4 @@
VITE_APP_ENV = development
VITE_APP_PROXY_URL=https://next.development.com/server

4
.env.prod

@ -0,0 +1,4 @@
VITE_APP_ENV = production
VITE_APP_PROXY_URL=https://next.production.com/server

20
.eslintrc.cjs

@ -0,0 +1,20 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
.gitignore

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

11
.prettierrc

@ -0,0 +1,11 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 80,
"trailingComma": "all",
"arrowParens": "avoid",
"tabWidth": 2,
"endOfLine": "lf",
"jsxSingleQuote": true,
"jsxBracketSameLine": false
}

17
README.md

@ -0,0 +1,17 @@
# React + Vite 前端项目模版
## 使用全新的技术栈开发
React18
AntDesign 5.0
react-router 6
Vite4
## 包管理
本项目采用yarn进行包管理
yarn.lock:锁定安装时的版本号,并且需要上传到git,以保证其他人再yarn install 时大家的依赖能保证一致
## UI库 AntDesign
```javascript
yarn add antd
```

13
index.html

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

1
jsconfig.json

@ -0,0 +1 @@
{}

34
package.json

@ -0,0 +1,34 @@
{
"name": "react-vite-template",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --mode dev",
"prod": "vite --mode prod",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"antd": "^5.8.4",
"axios": "^1.4.0",
"less": "^4.2.0",
"mobx": "^6.10.0",
"mobx-react": "^9.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.15.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"prettier": "^3.0.2",
"vite": "^4.4.5"
}
}

1
public/vite.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

10
src/App.jsx

@ -0,0 +1,10 @@
import React, { useState } from 'react'
import BaseLayout from '@/layouts/Base'
import Login from '@/views/Login'
const App = () => {
const [isLogin, setIsLogin] = useState(false)
return !isLogin ? <Login /> : <BaseLayout />
}
export default App

110
src/assets/css/reset.css

@ -0,0 +1,110 @@
/* http://meyerweb.com/eric/tools/css/reset/ */
/* v1.0 | 20080212 */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
font,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
vertical-align: baseline;
background: transparent;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
/* remember to define focus styles! */
:focus {
outline: 0;
}
/* remember to highlight inserts somehow! */
ins {
text-decoration: none;
}
del {
text-decoration: line-through;
}
/* tables still need 'cellspacing="0"' in the markup */
table {
border-collapse: collapse;
border-spacing: 0;
}

BIN
src/assets/img/login/bg.gif

After

Width: 700  |  Height: 701  |  Size: 2.8 MiB

5
src/components/Add.jsx

@ -0,0 +1,5 @@
function Add() {
return <div>123</div>
}
export default Add

0
src/index.css

115
src/layouts/Base.jsx

@ -0,0 +1,115 @@
import React, { useState } from 'react'
import {
DesktopOutlined,
FileOutlined,
PieChartOutlined,
TeamOutlined,
UserOutlined,
} from '@ant-design/icons'
import { Breadcrumb, Layout, Menu, theme } from 'antd'
import { useRoutes } from 'react-router-dom'
import routes from '@/router/index'
import { useRootStore } from '@/store'
const { Header, Content, Footer, Sider } = Layout
function getItem(label, key, icon, children) {
return {
key,
icon,
children,
label,
}
}
const items = [
getItem('Option 1', '1', <PieChartOutlined />),
getItem('Option 2', '2', <DesktopOutlined />),
getItem('User', 'sub1', <UserOutlined />, [
getItem('Tom', '3'),
getItem('Bill', '4'),
getItem('Alex', '5'),
]),
getItem('Team', 'sub2', <TeamOutlined />, [
getItem('Team 1', '6'),
getItem('Team 2', '8'),
]),
getItem('Files', '9', <FileOutlined />),
]
function BaseLayout() {
const { countStore } = useRootStore()
const { count } = countStore
const {
token: { colorBgContainer },
} = theme.useToken()
const [collapsed, setCollapsed] = useState(false)
return (
<Layout
style={{
minHeight: '100vh',
}}
>
<Sider
collapsible
collapsed={collapsed}
onCollapse={value => setCollapsed(value)}
>
<div
style={{
textAlign: 'center',
padding: '20px 0',
color: '#fff',
fontSize: '18px',
}}
>
{count}
</div>
<Menu
theme='dark'
defaultSelectedKeys={['1']}
mode='inline'
items={items}
/>
</Sider>
<Layout>
<Header
style={{
padding: 0,
background: colorBgContainer,
}}
/>
<Content
style={{
margin: '0 16px',
}}
>
<Breadcrumb
style={{
margin: '16px 0',
}}
>
<Breadcrumb.Item>User</Breadcrumb.Item>
<Breadcrumb.Item>Bill</Breadcrumb.Item>
</Breadcrumb>
<div
style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
}}
>
{useRoutes(routes)}
</div>
</Content>
<Footer
style={{
textAlign: 'center',
}}
>
Ant Design ©2023 Created by Ant UED
</Footer>
</Layout>
</Layout>
)
}
export default BaseLayout

20
src/main.jsx

@ -0,0 +1,20 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App.jsx'
import { RootStoreProvider } from '@/store'
import '@/assets/css/reset.css'
import './index.css'
import { Suspense } from 'react'
ReactDOM.createRoot(document.getElementById('root')).render(
<RootStoreProvider>
<BrowserRouter>
<React.StrictMode>
<Suspense fallback={<div>加载中</div>}>
<App />
</Suspense>
</React.StrictMode>
</BrowserRouter>
</RootStoreProvider>,
)

9
src/request/clientError.js

@ -0,0 +1,9 @@
class ClientError extends Error {
constructor(code, message) {
super(message);
this.message = message;
this.code = code;
}
}
export default ClientError;

115
src/request/index.js

@ -0,0 +1,115 @@
/* eslint-disable no-unused-expressions */
import axios from 'axios'
import React from 'react'
import { message } from 'antd'
import ClientError from './clientError'
/**
* 0:成功
* 1:系统内部错误
* 2:session过期跳转登陆界面
*/
axios.defaults.headers['Cache-Control'] =
'no-store,no-cache,no-transform,must-revalidate,max-age=0'
class BaseHttpClient {
constructor() {}
baseUrl = ''
loged = 1
instance = axios.create({
baseURL: this.baseUrl,
timeout: 20000,
withCredentials: true,
})
getCommon(method, url, params = {}, headers = {}) {
this.url = url
return this.instance[method](url, { params, ...headers })
.then(this.handleSuccessResponse.bind(this))
.catch(this.handleErrorResponse.bind(this))
.finally(() => {})
}
postCommon(method, url, data = {}, header) {
return this.instance[method](url, data, header)
.then(this.handleSuccessResponse.bind(this))
.catch(this.handleErrorResponse.bind(this))
.finally(() => {})
}
get(url, params = {}, headers = {}) {
return this.getCommon('get', url, params, headers)
}
delete(url, params = {}, header) {
return this.getCommon('delete', url, params, header)
}
post(url, data = {}, header) {
return this.postCommon('post', url, data, header)
}
put(url, data = {}, header) {
return this.postCommon('put', url, data, header)
}
handleSuccessResponse(res) {
if (res) {
if (res && res.data.result_code === 0) {
return res.data
}
}
return null
}
handleErrorResponse(err) {
// console.log(err.response, 92)
if (err.toString().indexOf('Network Error') >= 0) {
message.destroy()
message.error('网络连接错误')
return
}
if ([101002, 101003].includes(err?.response?.data?.result_code)) {
message.destroy()
message.error('用户登录已过期')
// window.location.href = '/login';
return
}
if (err?.response?.data?.result_code && err?.response?.data?.message) {
message.destroy()
message.error(err.response.data.message)
} else if (err) {
throw err
} else {
throw new ClientError(999, '未知错误')
}
}
}
// 带有拦截器的HttpClient
class HttpClientWithInterceptors extends BaseHttpClient {
constructor() {
super()
this.instance.interceptors.response.use(
response =>
// 在这里添加响应的拦截
response,
error => {
const originalRequest = error.config
if (
error.code === 'ECONNABORTED' &&
error.message.indexOf('timeout') !== -1 &&
!originalRequest._retry
) {
message.error('查询超时,请稍后重试')
}
},
)
}
}
class HttpClientClass extends HttpClientWithInterceptors {}
export const HttpClient = new HttpClientClass()

13
src/router/index.jsx

@ -0,0 +1,13 @@
import { lazy } from 'react'
const Add = lazy(() => import('../components/Add'))
//
const routes = [
{
path: '/',
element: <Add />,
},
]
export default routes

18
src/store/CountStore.js

@ -0,0 +1,18 @@
import { action, makeObservable, observable } from 'mobx'
/**
* 状态被标记为observable
* 更改状态的方法标记为action.bound
* 组件使用observer方法包裹
*/
export default class CountStore {
constructor() {
this.count = 0
makeObservable(this, {
count: observable,
increment: action.bound,
})
}
increment() {
this.count++
}
}

24
src/store/index.jsx

@ -0,0 +1,24 @@
import CountStore from './CountStore'
import { createContext, useContext } from 'react'
class RootStore {
constructor() {
this.countStore = new CountStore()
}
}
const rootStore = new RootStore()
const RootStoreContext = createContext()
export const RootStoreProvider = ({ children }) => {
return (
<RootStoreContext.Provider value={rootStore}>
{children}
</RootStoreContext.Provider>
)
}
export const useRootStore = () => {
return useContext(RootStoreContext)
}

13
src/views/Login/index.jsx

@ -0,0 +1,13 @@
import styles from './index.module.less'
import Background from '@/assets/img/login/bg.gif'
const Login = () => {
return (
<div className={styles.login_container}>
<img src={Background} className={styles.back} />
<div className={styles.form_wrap}></div>
</div>
)
}
export default Login

22
src/views/Login/index.module.less

@ -0,0 +1,22 @@
.login_container {
width: 100vw;
height: 100vh;
background: #f6f5f8;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8vw;
box-sizing: border-box;
overflow: hidden;
.back{
width: 40vw;
height: 40vw;
margin-top: 8vw;
}
.form_wrap{
width: 40vw;
height: 40vw;
border-radius: 38px;
background: #FFFFFF;
}
}

13
vite.config.js

@ -0,0 +1,13 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
});

2926
yarn.lock
File diff suppressed because it is too large
View File

Loading…
Cancel
Save