shaogen1995 2 gadi atpakaļ
vecāks
revīzija
819a3623cf

+ 23 - 0
pc/.gitignore

@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*

+ 46 - 0
pc/README.md

@@ -0,0 +1,46 @@
+# Getting Started with Create React App
+
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+
+## Available Scripts
+
+In the project directory, you can run:
+
+### `npm start`
+
+Runs the app in the development mode.\
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
+
+The page will reload if you make edits.\
+You will also see any lint errors in the console.
+
+### `npm test`
+
+Launches the test runner in the interactive watch mode.\
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+
+### `npm run build`
+
+Builds the app for production to the `build` folder.\
+It correctly bundles React in production mode and optimizes the build for the best performance.
+
+The build is minified and the filenames include the hashes.\
+Your app is ready to be deployed!
+
+See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+
+### `npm run eject`
+
+**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
+
+If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+
+Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
+
+You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
+
+## Learn More
+
+You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
+
+To learn React, check out the [React documentation](https://reactjs.org/).

+ 10 - 0
pc/config-overrides.js

@@ -0,0 +1,10 @@
+const path = require('path')
+const { override, addWebpackAlias } = require('customize-cra')
+
+// 添加 @ 别名
+const webpackAlias = addWebpackAlias({
+  '@': path.resolve(__dirname, 'src'),
+})
+
+// 导出要进行覆盖的 webpack 配置
+module.exports = override(webpackAlias)

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 28144 - 0
pc/package-lock.json


+ 57 - 0
pc/package.json

@@ -0,0 +1,57 @@
+{
+  "name": "demo",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@testing-library/jest-dom": "^5.16.5",
+    "@testing-library/react": "^13.4.0",
+    "@testing-library/user-event": "^13.5.0",
+    "@types/jest": "^27.5.2",
+    "@types/node": "^16.18.3",
+    "@types/react": "^18.0.24",
+    "@types/react-dom": "^18.0.8",
+    "axios": "^1.1.3",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "react-redux": "^8.0.4",
+    "react-router-dom": "5.3",
+    "react-scripts": "5.0.1",
+    "redux": "^4.2.0",
+    "redux-devtools-extension": "^2.13.9",
+    "redux-thunk": "^2.4.1",
+    "sass": "^1.55.0",
+    "typescript": "^4.8.4",
+    "web-vitals": "^2.1.4"
+  },
+  "scripts": {
+    "dev": "react-app-rewired start",
+    "build": "react-app-rewired build",
+    "test": "react-app-rewired test",
+    "eject": "react-scripts eject"
+  },
+  "eslintConfig": {
+    "extends": [
+      "react-app",
+      "react-app/jest"
+    ]
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  },
+  "devDependencies": {
+    "@types/history": "^5.0.0",
+    "@types/react-router-dom": "^5.3.3",
+    "customize-cra": "^1.0.0",
+    "react-app-rewired": "^2.2.1"
+  },
+  "homepage": "."
+}

+ 8 - 0
pc/path.tsconfig.json

@@ -0,0 +1,8 @@
+{
+    "compilerOptions": {
+      "baseUrl": "./",
+      "paths": {
+        "@/*": ["src/*"]
+      }
+    }
+  }

BIN
pc/public/favicon.ico


+ 43 - 0
pc/public/index.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="zh">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+    <!--
+      manifest.json provides metadata used when your web app is installed on a
+      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
+    -->
+
+    <!--
+      Notice the use of %PUBLIC_URL% in the tags above.
+      It will be replaced with the URL of the `public` folder during the build.
+      Only files inside the `public` folder can be referenced from the HTML.
+
+      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+      work correctly both with client-side routing and a non-root public URL.
+      Learn how to configure a non-root public URL by running `npm run build`.
+    -->
+    <title>React App</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+  </body>
+</html>

+ 95 - 0
pc/src/App.tsx

@@ -0,0 +1,95 @@
+// 导入初始化样式
+import "@/assets/styles/base.css";
+
+// 导入 修改仓库的 dispatch,和获取仓库数据的 useSelector
+import { useDispatch, useSelector } from "react-redux";
+
+// 导入自己定义的 RootState 声明类型
+import { RootState } from "./store";
+
+// 导入自己定义的异步addNunAction
+import { addNunAction } from "./store/action/login";
+
+// 关于路由
+import React from "react";
+import { Router, Route, Switch, Redirect } from "react-router-dom";
+
+// 导入自己封装的 history
+import history from "./utils/history";
+
+// 导入自己封装的 鉴权路由
+import AuthRoute from "./components/AuthRoute";
+
+// 使用 React.lazy 懒加载页面
+const Layout = React.lazy(() => import("./pages/Layout"));
+const Login = React.lazy(() => import("./pages/Login"));
+const Home = React.lazy(() => import("./pages/Home"));
+
+function App() {
+  const dispatch = useDispatch(); 
+  return (
+    <div>
+      <h1>App总组件</h1>
+      {/* 点击这个按钮 控制  dispatch 来异步 修改仓库的 num 变量*/}
+      <button onClick={() => dispatch(addNunAction())}>点击+1</button>
+      <br />
+      <div onClick={() => history.push("/Login")}>去登录页</div>
+      <br />
+      <div onClick={() => history.push("/Layout")}>去Layout页面</div>
+      <br />
+      <div onClick={() => history.push("/Home")}>去Home页面</div>
+      <hr />
+      <Son1 />
+      <hr />
+      <Son2 />
+
+      {/* 关于路由 */}
+      {/* 这里需要把自己封装的 history 给第一级总路由器,不然项目中的路由信息会链接不上*/}
+      <Router history={history}>
+        {/* 使用了 React.lazy 需要定义一个加载中组件,这里我简单写一个。后面根据需求自己修改更好看的 加载中 组件*/}
+        <React.Suspense fallback={<div>加载中...</div>}>
+          {/* 使用 Switch 包裹路由,匹配到一个之后就不会往下匹配 */}
+          <Switch>
+            {/* 使用路由重定向,回到登录页 */}
+            <Redirect exact path="/" to="Login" />
+            {/* 普通路由,没有鉴权功能 */}
+            <Route path="/Login" component={Login} />
+            {/* 普通路由,没有鉴权功能 */}
+            <Route path="/Layout" component={Layout} />
+            {/* 鉴权路由,没有token,访问改路由,会重定向到 登录页面 */}
+            <AuthRoute path="/Home" component={Home} />
+            {/* 当所有路由匹配不到的时候显示 自己定义的 404 页面,这里我暂时没有定义 */}
+            {/* <Route path='*' component={NotFound} /> */}
+          </Switch>
+        </React.Suspense>
+      </Router>
+    </div>
+  );
+}
+
+const Son1 = function Son1() {
+  // 从仓库中获取响应式数据 num
+  const { num } = useSelector((state: RootState) => state.loginStore);
+  return (
+    <>
+      <h2>子组件1</h2>
+      <p>存在仓库的数字:{num}</p>
+    </>
+  );
+};
+
+const Son2 = function Son2() {
+  // 从仓库中获取响应式数据 num
+  const { num } = useSelector((state: RootState) => state.loginStore);
+
+  return (
+    <>
+      <h2>子组件2</h2>
+      <p>存在仓库的数字:{num}</p>
+    </>
+  );
+};
+
+const MemoApp = React.memo(App);
+
+export default MemoApp;

+ 49 - 0
pc/src/assets/styles/base.css

@@ -0,0 +1,49 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+html {
+  height: 100%;
+  font-size: 14px;
+  user-select: none;
+}
+body {
+  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
+  height: 100%;
+  color: #333;
+}
+a {
+  text-decoration: none;
+  color: #333;
+  outline: none;
+}
+i {
+  font-style: normal;
+}
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+  object-fit: cover;
+}
+ul {
+  list-style: none;
+}
+body {
+  overflow-y: overlay;
+}
+/* 文本域取消下拉 */
+textarea {
+  resize: none !important;
+}
+/* 主题色 */
+:root {
+  --themeColor: #9F1927;
+}
+a {
+  color: var(--themeColor);
+}
+[hidden] {
+  display: none !important;
+}

+ 60 - 0
pc/src/assets/styles/base.less

@@ -0,0 +1,60 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+html {
+  height: 100%;
+  font-size: 14px;
+  user-select: none;
+}
+
+body {
+  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
+  height: 100%;
+  color: #333;
+}
+
+a {
+  text-decoration: none;
+  color: #333;
+  outline: none;
+}
+
+i {
+  font-style: normal;
+}
+
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+  object-fit: cover;
+}
+
+ul {
+  list-style: none;
+}
+
+body {
+  overflow-y: overlay;
+}
+
+/* 文本域取消下拉 */
+textarea {
+  resize: none !important;
+}
+
+/* 主题色 */
+:root {
+  --themeColor: #9F1927;
+}
+
+a {
+  color: var(--themeColor);
+}
+
+[hidden] {
+  display: none !important;
+}

+ 48 - 0
pc/src/components/AuthRoute/index.tsx

@@ -0,0 +1,48 @@
+//自己封装的 history 路由跳转,在tsx和ts中都能使用。使用 react-router-dom 里面的 useHistory Hooks 只能在tsx函数组件中使用,不能在ts文件中使用,不太方便,所以这里自己封装。
+import history from "@/utils/history";
+
+// 自己封装的获取token的函数
+import { hasToken } from "@//utils/storage";
+
+import React from "react";
+
+import { Redirect, Route } from "react-router-dom";
+
+type AtahType = {
+  path: string; //传入的路由路径,必传
+  component: React.FC; //引入react中以及封装好的 组件 的类型声明,必传
+};
+
+function AuthRoute({ path, component: Com }: AtahType) {
+  return (
+    //引入 react-router-dom 中的 Route 组件
+    <Route
+      // 路由路径
+      path={path}
+      render={() => {
+        // 需要token的页面使用本组件,检查到有token,就正常访问
+        if (hasToken()) return <Com />;
+        else {
+          // 没有token,提示一下。这里随便写的alret,可自行修改其他方式提示(比如antd的轻提示)
+          alert("登录失效");
+          return (
+            // 路由重定向到 登录页面
+            <Redirect
+              to={{
+                pathname: "/Login",
+                // 附带 回跳 信息,有些业务需求 登录之后回到 上一个页面的时候可以使用(比如:在个人详情页,由于某些原因token失效,或者本地删除了token,那就会回到登录页,登录成功之后会回跳到 个人详情 页)
+                state: { from: history.location.pathname },
+              }}
+            />
+          );
+        }
+      }}
+    />
+  );
+}
+
+// 使用 React.memo 来优化组件,避免组件的无效更新,类似 类组件里面的PureComponent
+const MemoAuthRoute = React.memo(AuthRoute);
+
+// 默认导出
+export default MemoAuthRoute;

+ 14 - 0
pc/src/index.tsx

@@ -0,0 +1,14 @@
+import App from "./App";
+import store from "./store/index";
+import { Provider } from "react-redux";
+import { createRoot } from "react-dom/client";
+
+const container = document.getElementById("root") as HTMLElement;
+
+const root = createRoot(container);
+
+root.render(
+  <Provider store={store}>
+    <App />
+  </Provider>
+);

+ 11 - 0
pc/src/pages/Home/index.tsx

@@ -0,0 +1,11 @@
+import React from "react";
+
+function Home() {
+  return <div>Home</div>;
+}
+
+// 使用 React.memo 来优化组件,避免组件的无效更新,类似 类组件里面的PureComponent
+const MemoHome = React.memo(Home);
+
+// 默认导出
+export default MemoHome;

+ 15 - 0
pc/src/pages/Layout/index.tsx

@@ -0,0 +1,15 @@
+import React from "react";
+
+function Layout() {
+  return (
+    <div>
+      <h1>Layout页面</h1>
+    </div>
+  );
+}
+
+// 使用 React.memo 来优化组件,避免组件的无效更新,类似 类组件里面的PureComponent
+const MemoLayout = React.memo(Layout);
+
+// 默认导出
+export default MemoLayout;

+ 13 - 0
pc/src/pages/Login/index.module.scss

@@ -0,0 +1,13 @@
+.Login {
+  width: 500px;
+  height: 500px;
+  background-color: red;
+
+  // 使用 global 来声明 除了第一个盒子 会模块化自动随机生成 calss类名,global里面的类名和元素不会变更。这样在控制台调试样式的时候不会混乱
+  :global {
+    h1 {
+      background-color: aquamarine;
+      font-size: 40px;
+    }
+  }
+}

+ 23 - 0
pc/src/pages/Login/index.tsx

@@ -0,0 +1,23 @@
+import React from "react";
+
+// 自己封装的 axios 初始基地址,会根据开发环境或者打包环境来自动改变
+import { baseURL } from "@/utils/http";
+
+// 导入模块化的css
+import styles from "./index.module.scss";
+
+function Login() {
+  return (
+    // 定义模块化css类名
+    <div className={styles.Login}>
+      <h1>登录页</h1>
+      <h2>全局变量:{baseURL}</h2>
+    </div>
+  );
+}
+
+// 使用 React.memo 来优化组件,避免组件的无效更新,类似 类组件里面的PureComponent
+const MemoLogin = React.memo(Login);
+
+// 默认导出
+export default MemoLogin;

+ 21 - 0
pc/src/store/action/login.ts

@@ -0,0 +1,21 @@
+// 导入总仓库和自己声明的 AppDispatch
+import store, { AppDispatch } from ".."
+// import http from "@/utils/http"
+
+export const addNunAction = () => {
+
+  // 返回一个函数,用来处理异步操作
+  return async (dispatch: AppDispatch) => {
+    // 可以异步发送请求,这里我简单模拟一个异步操作
+    // const res = await http.get(``)
+    // console.log('----', res);
+    window.setTimeout(() => {
+      // 从仓库中获取之前的 num 值
+      const { num } = store.getState().loginStore
+      // 设置新的 num +1
+      dispatch({ type: 'login/addNum', payload: num + 1 })
+    }, 100);
+  }
+}
+
+

+ 20 - 0
pc/src/store/index.ts

@@ -0,0 +1,20 @@
+// 导入 redux
+import { applyMiddleware, legacy_createStore as createStore } from 'redux'
+// 导入自己封装的  rootReducer 
+import rootReducer from './reducer'
+// 导入调试工具和 异步的 redux(用来发送异步请求)
+// 调试工具需要下载谷歌 扩展程序 我用的是 Redux DevTools 3.0.17
+import { composeWithDevTools } from 'redux-devtools-extension'
+import thunk from 'redux-thunk'
+
+// 创建仓库实例
+const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)))
+
+// 声明 RootState,在使用仓库的时候用来使用
+export type RootState = ReturnType<typeof store.getState>
+
+// 声明 AppDispatch,在异步请求的时候来使用
+export type AppDispatch = typeof store.dispatch
+
+// 导出仓库实例
+export default store

+ 13 - 0
pc/src/store/reducer/index.ts

@@ -0,0 +1,13 @@
+// 导入合并reducer的依赖
+import { combineReducers } from 'redux'
+
+// 导入 登录 模块的 reducer
+import loginReducer from './login'
+
+// 合并 reducer
+const rootReducer = combineReducers({
+  loginStore: loginReducer,
+})
+
+// 默认导出
+export default rootReducer

+ 22 - 0
pc/src/store/reducer/login.ts

@@ -0,0 +1,22 @@
+// 导入自己封装的 关于 LoginReducer 的ts声明文件
+import { LoginType } from "@/types"
+
+// 初始化状态
+const initState: LoginType = {
+  num: 0,
+  active: 0,
+}
+
+// 定义 action 类型
+type LoginActionType =
+  { type: 'login/addNum', payload: number }
+
+// 频道 reducer
+export default function loginReducer(state = initState, action: LoginActionType) {
+  switch (action.type) {
+    case 'login/addNum':
+      return { ...state, num: action.payload }
+    default:
+      return state
+  }
+}

+ 2 - 0
pc/src/types/declaration.d.ts

@@ -0,0 +1,2 @@
+declare module 'history'
+declare module '*.scss';

+ 2 - 0
pc/src/types/index.d.ts

@@ -0,0 +1,2 @@
+export * from './store/login'
+

+ 4 - 0
pc/src/types/store/login.d.ts

@@ -0,0 +1,4 @@
+export type LoginType = {
+  num: number;
+  active: number;
+}

+ 6 - 0
pc/src/utils/history.ts

@@ -0,0 +1,6 @@
+// createBrowserHistory 和 createHashHistory。
+// 这里我们简单说一下 createBrowserHistory地址栏不带#; createHashHistory 带#
+// 根据公司要求自己选择,createBrowserHistory在项目上线的时候需要服务器映射等处理。
+import { createHashHistory  } from 'history'
+const history = createHashHistory()
+export default history

+ 90 - 0
pc/src/utils/http.ts

@@ -0,0 +1,90 @@
+// 导入 axios
+import axios from "axios";
+// 导入自己封装的 history 用来跳转。useHistory 在单纯的ts文件里面无法使用
+import history from "./history";
+// 导入自己封装的获取token信息的函数,和删除本地存储token信息的函数
+import { getTokenInfo, removeTokenInfo } from "./storage";
+// 请求基地址,根据开发环境和打包环境来自己设置。一般打包环境都为空
+export const baseURL = process.env.NODE_ENV === "development" ? "开发环境" : "";
+
+// 创建 axios 实例
+const http = axios.create({
+  baseURL,
+  // 请求超过5m没有回来即停止请求,并且抛出异常
+  timeout: 5000,
+});
+
+// 设置一个请求状态开关,判断 请求正在发送 和 所有请求都发送完毕
+let axajInd = 0;
+
+// 请求拦截器
+http.interceptors.request.use(
+  function (config: any) {
+    // 开关数量+1,表示现在有一个请求在发送。
+    axajInd++;
+    //可以在这里添加业务代码,比如:显示 loding状态
+
+    // 检查到有token,为所有请求添加请求体。Bearer字段为 我这边后端的需求,各位看自己 后端需求来修改
+    const { token } = getTokenInfo();
+    if (token) config.headers.Authorization = `Bearer ${token}`;
+    return config;
+  },
+  function (err) {
+    return Promise.reject(err);
+  }
+);
+
+// 设置一个定时器id,防止多个请求同时发送,并且token失效的情况,多次执行 业务 代码
+let timeId = -1;
+
+// 响应拦截器
+http.interceptors.response.use(
+  function (response) {
+    // 一个请求发送完毕
+    axajInd--;
+    if (axajInd === 0) {
+      // 所有请求发送完毕,可以在这里添加业务代码,比如:隐藏 loding状态
+    }
+
+    // token过期,这里看后台的返回值设置的为多少
+    if (response.data.code === 401) {
+      // 先清理定时器,在执行。防止多次执行。即页面防抖原理
+      clearTimeout(timeId);
+
+      timeId = window.setTimeout(() => {
+        // 删除本地token信息
+        removeTokenInfo();
+        // 这里的提示统一简单用的alert,自己根据需求更改。比如:antd的message
+        alert("登录失效!");
+        // 回到登录页面
+        history.push("/Login");
+      }, 200);
+    } else if (response.data.code === 0) {
+      // 请求成功,状态也成功(根据后端定义的状态值自己修改)
+      // 这里暂时不做处理,我一般会根据具体需求在组件中自己定义 message
+    } else {
+      //这里一般是请求成功,但是状态码有问题,直接返回后端的提示信息。这里的msg是我这边后端的字段,自己根据后端字段来修改
+      alert(response.data.msg);
+     }
+
+    return response.data;
+  },
+  async function (err) {
+    // 请求错误的时候,直接把 axajInd 归零,防止页面一直处于 loding 状态
+    axajInd = 0;
+    // 如果在上面定义了发送请求前显示 loding状态,这里需要隐藏 loding
+
+    // 如果因为网络原因,response没有,给提示消息
+    if (!err.response) {
+      alert("网络繁忙,请稍后重试");
+    } else {
+      // 网络没问题,后台返回了有数据
+      alert("错误");
+    }
+
+    return Promise.reject(err);
+  }
+);
+
+// 导出 axios 实例
+export default http;

+ 34 - 0
pc/src/utils/storage.ts

@@ -0,0 +1,34 @@
+// ------------------------------------token的本地存储------------------------------------
+
+// 用户 Token 的本地缓存键名,自己定义
+const TOKEN_KEY = 'XXXXX'
+
+/**
+ * 从本地缓存中获取 Token 信息
+ */
+export const getTokenInfo = (): any => {
+  return JSON.parse(localStorage.getItem(TOKEN_KEY) || '{}')
+}
+
+/**
+ * 将 Token 信息存入缓存
+ * @param {Object} tokenInfo 从后端获取到的 Token 信息
+ */
+export const setTokenInfo = (tokenInfo: any): void => {
+  localStorage.setItem(TOKEN_KEY, JSON.stringify(tokenInfo))
+}
+
+/**
+ * 删除本地缓存中的 Token 信息
+ */
+export const removeTokenInfo = (): void => {
+  localStorage.removeItem(TOKEN_KEY)
+}
+
+/**
+ * 判断本地缓存中是否存在 Token 信息
+ */
+export const hasToken = (): boolean => {
+  return Boolean(getTokenInfo().token)
+}
+

+ 27 - 0
pc/tsconfig.json

@@ -0,0 +1,27 @@
+{
+  "extends": "./path.tsconfig.json",
+  "compilerOptions": {
+    "target": "es5",
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "esnext"
+    ],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true,
+    "allowSyntheticDefaultImports": true,
+    "strict": true,
+    "forceConsistentCasingInFileNames": true,
+    "noFallthroughCasesInSwitch": true,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react-jsx"
+  },
+  "include": [
+    "src"
+  ]
+}