Commit c7301e8d by 于方蒙

update

parent c187d65e
# 部署服务器的静态文件路径
REACT_APP_PUBLIC_URL="/login-record-build"
# 构建文件名字
REACT_APP_BUILD_PATH="login-record-build"
# 是否生成线上sourcemap文件
GENERATE_SOURCEMAP=false
# 请求的基础路径
REACT_APP_BASE_URL="/ssw_test"
\ No newline at end of file
......@@ -22,5 +22,6 @@ module.exports = {
rules: {
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
'react-hooks/exhaustive-deps': 'warn', //
},
}
......@@ -10,6 +10,8 @@
# production
/build
/dist
/login_record_static
# misc
.DS_Store
......
......@@ -124,6 +124,20 @@ module.exports = function (webpackEnv) {
config: false,
plugins: !useTailwind
? [
[
'postcss-px-to-viewport-8-plugin',
{
viewportWidth: 750 / 2, // (Number) The width of the viewport.
viewportHeight: 1334 / 2, // (Number) The height of the viewport.
unitPrecision: 3, // (Number) The decimal numbers to allow the REM units to grow to.
viewportUnit: 'vw', // (String) Expected units.
selectorBlackList: ['.ignore', '.hairlines'], // (Array) The selectors to ignore and leave as px.
exclude: [/node_modules/],
include: [/src/],
minPixelValue: 1, // (Number) Set the minimum pixel value to replace.
mediaQuery: false, // (Boolean) Allow px to be converted in media queries.
},
],
'postcss-flexbugs-fixes',
[
'postcss-preset-env',
......@@ -298,6 +312,7 @@ module.exports = function (webpackEnv) {
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
'@': path.join(__dirname, '../src/'),
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
......
......@@ -13,6 +13,17 @@ const sockPort = process.env.WDS_SOCKET_PORT
module.exports = function (proxy, allowedHost) {
const disableFirewall = !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true'
proxy = {
'/ssw_test': {
target: 'http://test.ssw-htzn.com/ssw_test',
secure: false,
changeOrigin: true,
pathRewrite: {
'^/ssw_test': '/',
},
},
...proxy,
}
return {
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding:
......@@ -97,7 +108,17 @@ module.exports = function (proxy, allowedHost) {
index: paths.publicUrlOrPath,
},
// `proxy` is run between `before` and `after` `webpack-dev-server` hooks
proxy,
proxy: {
'/ssw_test': {
target: 'http://test.ssw-htzn.com/ssw_test',
secure: false,
changeOrigin: true,
pathRewrite: {
'^/ssw_test': '/',
},
},
...proxy,
},
onBeforeSetupMiddleware(devServer) {
// Keep `evalSourceMapMiddleware`
// middlewares before `redirectServedPath` otherwise will not have any effect
......
......@@ -2,6 +2,7 @@
"name": "login-record-ts",
"version": "0.1.0",
"private": true,
"homepage": ".",
"dependencies": {
"@babel/core": "^7.16.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
......@@ -9,6 +10,8 @@
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"antd-mobile": "^5.28.0",
"axios": "^1.3.3",
"babel-loader": "^8.2.3",
"babel-plugin-named-asset-import": "^0.3.8",
"babel-preset-react-app": "^10.0.1",
......@@ -41,6 +44,7 @@
"react-refresh": "^0.11.0",
"resolve": "^1.20.0",
"resolve-url-loader": "^4.0.0",
"sass": "^1.57.1",
"sass-loader": "^12.3.0",
"semver": "^7.3.5",
"source-map-loader": "^3.0.0",
......@@ -59,8 +63,8 @@
"build": "node scripts/build.js",
"lint": "eslint --ext .js --ext .jsx src",
"lint:fix": "eslint -c ./.eslintrc.js --ext .js,.jsx,.ts,.tsx src/ --fix",
"prettier": "npx prettier src test --check",
"prettier:fix": "npm run prettier --write"
"prettier": "yarn prettier src --check",
"prettier:fix": "yarn prettier --write"
},
"browserslist": {
"production": [
......@@ -78,8 +82,8 @@
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"postcss-px-to-viewport-8-plugin": "^1.2.0",
"prettier": "^2.8.3",
"prettier-eslint": "^15.0.1",
"sass": "^1.57.1"
"prettier-eslint": "^15.0.1"
}
}
// 不知道为啥不生效
module.exports = {
loader: 'postcss-loader',
plugins: {
'postcss-px-to-viewport-8-plugin': {
viewportWidth: 750 / 2, // (Number) The width of the viewport.
viewportHeight: 1334 / 2, // (Number) The height of the viewport.
unitPrecision: 3, // (Number) The decimal numbers to allow the REM units to grow to.
viewportUnit: 'vw', // (String) Expected units.
selectorBlackList: ['.ignore', '.hairlines'], // (Array) The selectors to ignore and leave as px.
exclude: [/node_modules/],
include: [/src/],
minPixelValue: 1, // (Number) Set the minimum pixel value to replace.
mediaQuery: false, // (Boolean) Allow px to be converted in media queries.
},
},
}
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'test'
process.env.NODE_ENV = 'test'
process.env.PUBLIC_URL = ''
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', (err) => {
throw err
})
// Ensure environment variables are read.
require('../config/env')
const jest = require('jest')
const execSync = require('child_process').execSync
let argv = process.argv.slice(2)
function isInGitRepository() {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
return true
} catch (e) {
return false
}
}
function isInMercurialRepository() {
try {
execSync('hg --cwd . root', { stdio: 'ignore' })
return true
} catch (e) {
return false
}
}
// Watch unless on CI or explicitly running all tests
if (
!process.env.CI &&
argv.indexOf('--watchAll') === -1 &&
argv.indexOf('--watchAll=false') === -1
) {
// https://github.com/facebook/create-react-app/issues/5210
const hasSourceControl = isInGitRepository() || isInMercurialRepository()
argv.push(hasSourceControl ? '--watch' : '--watchAll')
}
jest.run(argv)
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
#loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.75);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
.add {
}
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
z-index: 9999;
font-size: 20px;
}
\ No newline at end of file
import React from 'react'
import logo from './logo.svg'
import { LoginList } from './components/login-list'
import './App.scss'
function App(props: {
children?:
| string
| number
| boolean
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
| React.ReactFragment
| React.ReactPortal
| null
| undefined
}) {
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<Ceshi></Ceshi>
{props.children}
</header>
<LoginList></LoginList>
</div>
)
}
function Ceshi() {
return <div>123123</div>
}
export default App
export { Ceshi }
import axiosInstance from '../https/useRequest'
type paramsobj = {
page?: number
page_size?: number
keyword?: string
}
export type agentListResponse = {
user_name: string
last_login_time: string
phone: string
login_count: number
}
export type dataResponese = {
list: agentListResponse[] | []
page: number
total_page: number
total_count: number
}
export const AgentList = (params: paramsobj): Promise<dataResponese> => {
return axiosInstance.post('/index/list', params)
}
.anchor-dot {
position: relative;
.dot-postion {
position: fixed;
top: 50%;
right: 0;
transform: translateY(-50%);
z-index: 10;
}
}
.flex-space {
display: flex;
justify-content: space-between;
align-items: center;
line-height: 2;
}
.font-normal {
font-size: 16px;
padding: 10px;
font-weight: 600;
}
.fixed-header {
width: 100%;
position: fixed;
left: 0;
top:0;
z-index: 1;
background-color: #fff;
}
.ceshi {
font-size: 14px;
}
\ No newline at end of file
import React, { useState, useRef } from 'react'
import { InfiniteScroll, List, PullToRefresh, PageIndicator, Tag, SearchBar } from 'antd-mobile'
import { AgentList, agentListResponse, dataResponese } from '../../api/agent'
import { PullStatus } from 'antd-mobile/es/components/pull-to-refresh'
import { SearchBarRef } from 'antd-mobile/es/components/search-bar'
import './index.scss'
export function LoginList() {
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
const [data, setData] = useState<agentListResponse[]>([])
const [resposeData, setResponseData] = useState<dataResponese>({
total_page: 0,
page: 0,
list: [],
total_count: 0,
})
const [searchValue, setSearchValue] = useState('')
const searchRef = useRef<SearchBarRef>(null)
async function loadMore() {
const res: dataResponese = await AgentList({ page, page_size: 20, keyword: searchValue })
setData((val) => [...val, ...(res?.list as agentListResponse[])])
setHasMore((res?.list.length as number) > 0)
setResponseData(res)
setPage((v) => ++v)
}
async function onRefresh() {
const res = await AgentList({ page, page_size: 20, keyword: searchValue })
setData([...res.list])
setPage(1)
}
const statusRecord: Record<PullStatus, string> = {
pulling: '用力拉',
canRelease: '松开吧',
refreshing: '玩命加载中...',
complete: '好啦',
}
function checkCount(count: number): string {
const countColor: [number, number, string][] = [
[1, 3, 'primary'],
[4, 10, 'warning'],
[11, 99, 'danger'],
]
function checkValue() {
for (let i of countColor) {
if (count > countColor[countColor.length - 1][1]) {
return 'danger'
}
if (count >= i[0] && count <= i[1]) {
return i[2]
}
}
return ''
}
return checkValue()
}
return (
<div className="anchor-dot">
<div className="dot-postion">
<PageIndicator
total={resposeData!.total_page}
current={resposeData!.page}
direction="vertical"
style={{
'--dot-color': 'rgba(0, 0, 0, 0.4)',
'--active-dot-color': '#ffc0cb',
'--dot-size': '10px',
'--active-dot-size': '30px',
'--dot-border-radius': '50%',
'--active-dot-border-radius': '15px',
'--dot-spacing': '8px',
}}
/>
</div>
<PullToRefresh
onRefresh={onRefresh}
renderText={(status) => {
return <div>{statusRecord[status]}</div>
}}
>
<div className="fixed-header">
<div className="font-normal flex-space">
<span>用户名</span>
<span className="offset3">上次登录时间</span>
<span className="offset4">登录手机号</span>
<span className="offset2">登录次数</span>
</div>
<div
style={{
padding: '0 3%',
}}
>
<SearchBar
ref={searchRef}
placeholder="请输入内容"
showCancelButton
onSearch={async (val) => {
const res = await AgentList({ page: 1, page_size: 20, keyword: val })
setData(res.list)
setHasMore(res.list.length > 0)
setSearchValue(val)
// setPage(1)
}}
></SearchBar>
</div>
</div>
<div style={{ marginTop: '21%' }}>
{data.map((item, index) => (
<List.Item key={index}>
<div className="flex-space">
<span className="ceshi">{item.user_name}</span>
<span className="ceshi">{item.last_login_time}</span>
<span className="ceshi">{item.phone}</span>
<Tag color={checkCount(item.login_count)}>{item.login_count}</Tag>
</div>
</List.Item>
))}
</div>
</PullToRefresh>
<InfiniteScroll loadMore={loadMore} hasMore={hasMore}></InfiniteScroll>
</div>
)
}
export const showMessage = (status: number | string): string => {
let message: string = ''
switch (status) {
case 400:
message = '请求错误(400)'
break
case 401:
message = '未授权,请重新登录(401)'
break
case 403:
message = '拒绝访问(403)'
break
case 404:
message = '请求出错(404)'
break
case 408:
message = '请求超时(408)'
break
case 500:
message = '服务器错误(500)'
break
case 501:
message = '服务未实现(501)'
break
case 502:
message = '网络错误(502)'
break
case 503:
message = '服务不可用(503)'
break
case 504:
message = '网络超时(504)'
break
case 505:
message = 'HTTP版本不受支持(505)'
break
default:
message = `连接出错(${status})!`
}
return `${message},请检查网络或联系管理员!`
}
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
import { showMessage } from './status'
import { Toast } from 'antd-mobile'
import type { ToastHandler } from 'antd-mobile/es/components/toast'
const axiosInstance: AxiosInstance = axios.create({
baseURL: process.env.REACT_APP_BASE_URL,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
let showToast: ToastHandler | null = null
// axios实例拦截响应
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
showToast && showToast.close()
if (response.status === 200) {
if (response.data.data) return response.data.data
return response.data
} else {
console.log(showMessage(response.status))
return response
}
},
// 请求失败
(error: any) => {
const { response } = error
if (response) {
// 请求已发出,但是不在2xx的范围
Toast.show({
icon: 'fail',
content: showMessage(response.status),
})
return Promise.reject(response.data)
} else {
Toast.show({
icon: 'fail',
content: '网络连接异常,请稍后再试!',
})
}
}
)
// axios实例拦截请求
axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem('app_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
showToast = Toast.show({
icon: 'loading',
content: '加载中…',
duration: 0,
})
return config
},
(error: any) => {
return Promise.reject(error)
}
)
export default axiosInstance
/* 300ms 延迟解决方案*/
html {
touch-action: manipulation;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment