zheng 17 órája
szülő
commit
e942e55f16
41 módosított fájl, 1175 hozzáadás és 0 törlés
  1. 23 0
      19.React/高阶/project5/.gitignore
  2. 70 0
      19.React/高阶/project5/README.md
  3. 45 0
      19.React/高阶/project5/package.json
  4. BIN
      19.React/高阶/project5/public/favicon.ico
  5. 43 0
      19.React/高阶/project5/public/index.html
  6. BIN
      19.React/高阶/project5/public/logo192.png
  7. BIN
      19.React/高阶/project5/public/logo512.png
  8. 25 0
      19.React/高阶/project5/public/manifest.json
  9. 38 0
      19.React/高阶/project5/public/products.json
  10. 3 0
      19.React/高阶/project5/public/robots.txt
  11. 38 0
      19.React/高阶/project5/src/App.css
  12. 25 0
      19.React/高阶/project5/src/App.js
  13. 8 0
      19.React/高阶/project5/src/App.test.js
  14. 2 0
      19.React/高阶/project5/src/api/product.js
  15. 0 0
      19.React/高阶/project5/src/assets/logo.svg
  16. 14 0
      19.React/高阶/project5/src/hooks/useAuth.js
  17. 13 0
      19.React/高阶/project5/src/index.css
  18. 19 0
      19.React/高阶/project5/src/index.js
  19. 80 0
      19.React/高阶/project5/src/layout/index.css
  20. 133 0
      19.React/高阶/project5/src/layout/index.jsx
  21. 0 0
      19.React/高阶/project5/src/logo.svg
  22. 7 0
      19.React/高阶/project5/src/pages/Home.jsx
  23. 47 0
      19.React/高阶/project5/src/pages/Login.jsx
  24. 71 0
      19.React/高阶/project5/src/pages/Product.jsx
  25. 158 0
      19.React/高阶/project5/src/pages/Setting.jsx
  26. 84 0
      19.React/高阶/project5/src/pages/Shop.jsx
  27. 21 0
      19.React/高阶/project5/src/pages/login.css
  28. 13 0
      19.React/高阶/project5/src/reportWebVitals.js
  29. 47 0
      19.React/高阶/project5/src/router/index.js
  30. 5 0
      19.React/高阶/project5/src/setupTests.js
  31. 16 0
      19.React/高阶/project5/src/store/index.js
  32. 32 0
      19.React/高阶/project5/src/store/slice/product.js
  33. 40 0
      19.React/高阶/project5/src/store/slice/system.js
  34. 1 0
      20.webpack/dist/a.js
  35. 1 0
      20.webpack/dist/b.js
  36. 1 0
      20.webpack/dist/main.js
  37. 10 0
      20.webpack/package.json
  38. 15 0
      20.webpack/src/hello.js
  39. 1 0
      20.webpack/src/index.js
  40. 8 0
      20.webpack/webpack.config.js
  41. 18 0
      20.webpack/初始.md

+ 23 - 0
19.React/高阶/project5/.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*

+ 70 - 0
19.React/高阶/project5/README.md

@@ -0,0 +1,70 @@
+# 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 your browser.
+
+The page will reload when you make changes.\
+You may 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/).
+
+### Code Splitting
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
+
+### Analyzing the Bundle Size
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
+
+### Making a Progressive Web App
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
+
+### Advanced Configuration
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
+
+### Deployment
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
+
+### `npm run build` fails to minify
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

+ 45 - 0
19.React/高阶/project5/package.json

@@ -0,0 +1,45 @@
+{
+  "name": "project5",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@ant-design/icons": "^6.1.0",
+    "@reduxjs/toolkit": "1.9",
+    "@testing-library/dom": "^10.4.1",
+    "@testing-library/jest-dom": "^6.9.1",
+    "@testing-library/react": "^16.3.2",
+    "@testing-library/user-event": "^13.5.0",
+    "antd": "^6.2.2",
+    "axios": "^1.13.4",
+    "react": "^18.3.1",
+    "react-dom": "^18.3.1",
+    "react-redux": "^8.0.0",
+    "react-router-dom": "^6.30.3",
+    "react-scripts": "5.0.1",
+    "web-vitals": "^2.1.4"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts 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"
+    ]
+  }
+}

BIN
19.React/高阶/project5/public/favicon.ico


+ 43 - 0
19.React/高阶/project5/public/index.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+  <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/
+    -->
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <!--
+      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>

BIN
19.React/高阶/project5/public/logo192.png


BIN
19.React/高阶/project5/public/logo512.png


+ 25 - 0
19.React/高阶/project5/public/manifest.json

@@ -0,0 +1,25 @@
+{
+  "short_name": "React App",
+  "name": "Create React App Sample",
+  "icons": [
+    {
+      "src": "favicon.ico",
+      "sizes": "64x64 32x32 24x24 16x16",
+      "type": "image/x-icon"
+    },
+    {
+      "src": "logo192.png",
+      "type": "image/png",
+      "sizes": "192x192"
+    },
+    {
+      "src": "logo512.png",
+      "type": "image/png",
+      "sizes": "512x512"
+    }
+  ],
+  "start_url": ".",
+  "display": "standalone",
+  "theme_color": "#000000",
+  "background_color": "#ffffff"
+}

+ 38 - 0
19.React/高阶/project5/public/products.json

@@ -0,0 +1,38 @@
+[
+    {
+        "category": "Sporting Goods",
+        "price": "$49.99",
+        "stocked": true,
+        "name": "Football"
+    },
+    {
+        "category": "Sporting Goods",
+        "price": "$9.99",
+        "stocked": true,
+        "name": "Baseball"
+    },
+    {
+        "category": "Sporting Goods",
+        "price": "$29.99",
+        "stocked": false,
+        "name": "Basketball"
+    },
+    {
+        "category": "Electronics",
+        "price": "$99.99",
+        "stocked": true,
+        "name": "iPod Touch"
+    },
+    {
+        "category": "Electronics",
+        "price": "$399.99",
+        "stocked": false,
+        "name": "iPhone 5"
+    },
+    {
+        "category": "Electronics",
+        "price": "$199.99",
+        "stocked": true,
+        "name": "Nexus 7"
+    }
+]

+ 3 - 0
19.React/高阶/project5/public/robots.txt

@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:

+ 38 - 0
19.React/高阶/project5/src/App.css

@@ -0,0 +1,38 @@
+.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;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  font-size: calc(10px + 2vmin);
+  color: white;
+}
+
+.App-link {
+  color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}

+ 25 - 0
19.React/高阶/project5/src/App.js

@@ -0,0 +1,25 @@
+import logo from './logo.svg';
+import './App.css';
+
+function App() {
+  return (
+    <div className="App">
+      <header className="App-header">
+        <img src={logo} className="App-logo" alt="logo" />
+        <p>
+          Edit <code>src/App.js</code> and save to reload.
+        </p>
+        <a
+          className="App-link"
+          href="https://reactjs.org"
+          target="_blank"
+          rel="noopener noreferrer"
+        >
+          Learn React
+        </a>
+      </header>
+    </div>
+  );
+}
+
+export default App;

+ 8 - 0
19.React/高阶/project5/src/App.test.js

@@ -0,0 +1,8 @@
+import { render, screen } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+  render(<App />);
+  const linkElement = screen.getByText(/learn react/i);
+  expect(linkElement).toBeInTheDocument();
+});

+ 2 - 0
19.React/高阶/project5/src/api/product.js

@@ -0,0 +1,2 @@
+import axios from 'axios';
+export const getAllProducts = () => axios.get('/products.json');

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
19.React/高阶/project5/src/assets/logo.svg


+ 14 - 0
19.React/高阶/project5/src/hooks/useAuth.js

@@ -0,0 +1,14 @@
+import { useEffect } from "react";
+import { useLocation, useNavigate } from "react-router-dom";
+
+export function useAuth() {
+    const whiteList = ['/login'];
+    const location = useLocation();
+    const navigate = useNavigate();
+    let token = localStorage.getItem("token");
+    useEffect(() => {
+        if (!token && !whiteList.includes(location.pathname)) {
+            navigate('/login')
+        }
+    }, [location]);
+}

+ 13 - 0
19.React/高阶/project5/src/index.css

@@ -0,0 +1,13 @@
+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;
+}

+ 19 - 0
19.React/高阶/project5/src/index.js

@@ -0,0 +1,19 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import router from './router';
+import { RouterProvider } from 'react-router-dom';
+import reportWebVitals from './reportWebVitals';
+import store from './store';
+import {Provider} from 'react-redux';
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+    <Provider store={store}>
+      <RouterProvider router={router}></RouterProvider>
+    </Provider>
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();

+ 80 - 0
19.React/高阶/project5/src/layout/index.css

@@ -0,0 +1,80 @@
+/* .layout {
+    display: flex;
+
+    aside {
+        width: 200px;
+        height: 100vh;
+
+        .ant-menu {
+            border-inline-end: none !important;
+        }
+    }
+
+    main {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        background: #f5f5f5;
+
+        header {
+            height: 64px;
+            background: #fff;
+            .content {
+                margin: 12px 16px;
+                display: flex;
+                justify-content: space-between;
+            }
+        }
+
+        .box {
+            flex: 1;
+            .content {
+                padding: 16px;
+                border-radius: 8px;
+                height: 90%;
+                margin: 24px 16px;
+                background: #fff;
+            }
+        }
+
+        footer {
+            height: 64px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+    }
+
+} */
+
+.btn {
+    width: 96%;
+    height: 50px;
+    margin: 5px 0px 5px 3px;
+}
+
+.ant-layout-sider {
+    background: #fff;
+}
+
+.ant-layout-header {
+    background: #fff !important;
+    display: flex;
+    justify-content: space-between;
+    
+}
+
+.ant-layout {
+    height: 100vh;
+}
+
+.box {
+    min-height: 95%;
+    padding: 24px;
+    background: rgb(255, 255, 255);
+    border-radius: 8px;
+}
+
+.info {
+    margin-right: 16px;
+}

+ 133 - 0
19.React/高阶/project5/src/layout/index.jsx

@@ -0,0 +1,133 @@
+import { Menu, Dropdown, Button, Avatar, Breadcrumb, Layout, theme, Space } from 'antd';
+import { useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { useAuth } from '../hooks/useAuth';
+import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom';
+import {
+    HomeOutlined, 
+    DownOutlined,
+    MenuFoldOutlined,
+    MenuUnfoldOutlined,
+    UploadOutlined,
+    UserOutlined,
+    VideoCameraOutlined,
+} from '@ant-design/icons';
+import './index.css';
+import defaultPic from '../assets/logo.svg';
+const { Header, Sider, Content, Footer } = Layout;
+function Layouts() {
+    const [collapsed, setCollapsed] = useState(false);
+    const [selectedMenu, setSelectedMenu] = useState("");
+    const navigate = useNavigate();
+    const location = useLocation();
+    const systemMenu = useSelector(({ systemReducer }) => systemReducer);
+    const [user, setUser] = useState("");
+    useAuth();
+    useEffect(() => {
+        setUser(JSON.parse(localStorage.getItem("token")).username);
+        commonTitle(location.pathname);
+    })
+    function handleClick({ key }) {
+        commonTitle(key)
+        navigate(key)
+    }
+    function commonTitle(val) {
+        systemMenu.menus.filter((item) => {
+            if (item.key === val) {
+                setSelectedMenu(item.label)
+            }
+        })
+    }
+    function handleOutLlogin() {
+        localStorage.clear();
+        navigate("/login");
+    }
+    const items = [
+        {
+            label: (
+                <Link to={"/setting"}>设置</Link>
+            ),
+            key: '0',
+        },
+        {
+            type: 'divider',
+        },
+        {
+            label: (
+                <span onClick={handleOutLlogin}>退出登录</span>
+            ),
+            key: '1',
+        },
+    ];
+    return (
+
+        <Layout>
+            <Sider trigger={null} collapsible collapsed={collapsed}>
+                <div className="demo-logo-vertical" />
+                <Button type="primary" ghost className='btn'>
+                    导航菜单
+                </Button>
+                <Menu
+                    defaultSelectedKeys={[location.pathname]}
+                    mode="inline"
+                    theme="light"
+                    inlineCollapsed={collapsed}
+                    items={systemMenu.menus}
+                    onClick={handleClick}
+                />
+            </Sider>
+            <Layout>
+                <Header style={{ padding: 0 }}>
+                    <Space>
+
+                        <Button
+                            type="text"
+                            icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
+                            onClick={() => setCollapsed(!collapsed)}
+                            style={{
+                                fontSize: '16px',
+                                width: 64,
+                                height: 64,
+                            }}
+                        />
+                        <Breadcrumb
+                            items={[
+                                {
+                                    href: '/',
+                                    title: <HomeOutlined />,
+                                },
+                                {
+                                    title: (
+                                        <>
+                                            <span>{selectedMenu}</span>
+                                        </>
+                                    ),
+                                }
+                            ]}
+                        />
+                    </Space>
+                    <Space size={1} wrap className='info'>
+                        <Avatar size={45} src={defaultPic}></Avatar>  <Dropdown menu={{ items }}>
+                            <a onClick={e => e.preventDefault()}>
+                                <Space>
+                                    {user}
+                                    <DownOutlined />
+                                </Space>
+                            </a>
+                        </Dropdown>
+                    </Space>
+                </Header>
+                <Content style={{ margin: '24px 16px 0' }}>
+                    <div className='box'>
+                        <Outlet></Outlet>
+                    </div>
+                </Content>
+                <Footer style={{ textAlign: 'center' }}>
+                    copyLoveCoding React案例
+                </Footer>
+            </Layout>
+        </Layout>
+    )
+}
+
+export default Layouts;

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
19.React/高阶/project5/src/logo.svg


+ 7 - 0
19.React/高阶/project5/src/pages/Home.jsx

@@ -0,0 +1,7 @@
+function Home() {
+    return <div>
+        <h1>首页</h1>
+    </div>
+}
+
+export default Home;

+ 47 - 0
19.React/高阶/project5/src/pages/Login.jsx

@@ -0,0 +1,47 @@
+import { Button, Checkbox, Form, Input } from 'antd';
+import { UserOutlined,LockOutlined } from '@ant-design/icons';
+import './login.css';
+import { useNavigate } from 'react-router-dom';
+function Login() {
+    const navigate = useNavigate();
+    const onFinish = values => {
+        console.log('Success:', values);
+        if(values) {
+            localStorage.setItem('token',JSON.stringify(values));
+            navigate('/');
+        }
+    };
+    return <div className='login'>
+        <div className="main">
+            <h1>欢迎登录</h1>
+            <Form
+                name="basic"
+                labelCol={{ span: 8 }}
+                wrapperCol={{ span: 16 }}
+                style={{ maxWidth: 600 }}
+                onFinish={onFinish}
+            >
+                <Form.Item
+                    name="username"
+                    rules={[{ required: true, message: '请输入用户名' }]}
+                >
+                    <Input placeholder='请输入用户名'  prefix={<UserOutlined />}  />
+                </Form.Item>
+
+                <Form.Item
+                    name="password"
+                    rules={[{ required: true, message: '请输入密码' }]}
+                >
+                    <Input.Password placeholder='请输入密码' prefix={<LockOutlined />} />
+                </Form.Item>
+                <Form.Item label={null}>
+                    <Button type="primary" htmlType="submit">
+                        Submit
+                    </Button>
+                </Form.Item>
+            </Form>
+        </div>
+    </div>
+}
+
+export default Login;

+ 71 - 0
19.React/高阶/project5/src/pages/Product.jsx

@@ -0,0 +1,71 @@
+import { useDispatch, useSelector } from "react-redux";
+import { fetchProducts,setFilterName, setShowStocked } from '../store/slice/product';
+import { Fragment, useEffect } from "react";
+function Product() {
+    const dispatch = useDispatch();
+    useEffect(() => {
+        dispatch(fetchProducts());
+    }, [])
+    let prevTitle = '';
+    let products = useSelector(({ productReducer }) => productReducer.products);
+    let filterName = useSelector(({ productReducer }) => productReducer.filterName);
+    let showStocked = useSelector(({ productReducer }) => productReducer.showStocked);
+    const renderProduct = () => {
+        let newProducts = products.filter((item) => item.name.includes(filterName) && (showStocked ? item.stocked : true))
+        return newProducts.map((product,index) => {
+            let categoryName = null;
+            if (prevTitle !== product.category) {
+                categoryName = (
+                    <tr>
+                        <td colSpan={2}>{product.category}</td>
+                    </tr>
+                )
+            }
+            prevTitle = product.category;
+
+            let productElement = <span>{product.name}</span>;
+            if (!product.stocked) {
+                productElement = (
+                    <span style={{ color: 'red' }}>{product.name}</span>
+                )
+            }
+            return (
+                <Fragment key={index}>
+                    {categoryName}
+                    <tr>
+                        <td>{productElement}</td>
+                        <td>{product.price}</td>
+                    </tr>
+                </Fragment>
+            )
+        })
+    }
+    const handleName = (val) => {
+        dispatch(setFilterName(val))
+    }
+    const handleStocket = (val) => {
+        dispatch(setShowStocked(val))
+    }
+    return <div>
+        <div className="header">
+            <input type="text" placeholder="请输入商品名称" value={filterName} onChange={(e) => handleName(e.target.value)} />
+            <label />
+            <input type="checkbox" value={showStocked} onChange={(e) => handleStocket(e.target.checked)} />仅显示有库存的商品
+        </div>
+        <div className="content">
+            <table>
+                <thead>
+                    <tr>
+                        <th>商品名称</th>
+                        <th>商品价格</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    {renderProduct()}
+                </tbody>
+            </table>
+        </div>
+    </div>
+}
+
+export default Product;

+ 158 - 0
19.React/高阶/project5/src/pages/Setting.jsx

@@ -0,0 +1,158 @@
+import { useState, useEffect } from 'react';
+
+// 宠物图标(可替换为你自己的图片)
+const pets = [
+  '🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼',
+  '🐨', '🐯', '🦁', '🐮', '🐷', '🐸', '🐵', '🐔'
+];
+
+// 生成随机配对卡片
+const generateCards = () => {
+  // 取前5对(10张),可调整数量
+  const selected = pets.slice(0, 5);
+  const pairs = [...selected, ...selected]; // 复制一份形成配对
+  return pairs.sort(() => Math.random() - 0.5); // 打乱顺序
+};
+
+export default function Setting() {
+  const [cards, setCards] = useState([]);
+  const [flipped, setFlipped] = useState([]); // 已翻开的卡片索引
+  const [matched, setMatched] = useState([]); // 已配对成功的索引
+  const [moves, setMoves] = useState(0); // 移动次数
+  const [remaining, setRemaining] = useState(0); // 剩余配对数
+
+  // 初始化游戏
+  useEffect(() => {
+    const newCards = generateCards();
+    setCards(newCards);
+    setFlipped([]);
+    setMatched([]);
+    setMoves(0);
+    setRemaining(newCards.length / 2); // 总配对数
+  }, []);
+
+  // 处理卡片点击
+  const handleClick = (index) => {
+    // 已翻开或已配对,不处理
+    if (flipped.includes(index) || matched.includes(index)) return;
+
+    let newFlipped = [...flipped, index];
+    setFlipped(newFlipped);
+    setMoves(moves + 1);
+
+    // 翻开两张时判断是否配对
+    if (newFlipped.length === 2) {
+      const [first, second] = newFlipped;
+      if (cards[first] === cards[second]) {
+        // 配对成功
+        setMatched([...matched, first, second]);
+        setRemaining(remaining - 1);
+      }
+      // 1秒后翻回
+      setTimeout(() => setFlipped([]), 1000);
+    }
+  };
+
+  // 重新开始
+  const restart = () => {
+    const newCards = generateCards();
+    setCards(newCards);
+    setFlipped([]);
+    setMatched([]);
+    setMoves(0);
+    setRemaining(newCards.length / 2);
+  };
+
+  // 计算准确率
+  const accuracy = moves === 0
+    ? 0
+    : Math.round((matched.length / 2 / moves) * 100);
+
+  return (
+    <div style={{
+      width: '100%',
+      minHeight: '100vh',
+      padding: '20px',
+      boxSizing: 'border-box'
+    }}>
+      {/* 顶部导航 */}
+      <div style={{
+        display: 'flex',
+        justifyContent: 'space-between',
+        fontSize: '20px',
+        fontWeight: 'bold',
+        marginBottom: '30px'
+      }}>
+        <span></span>
+        <span style={{ color: '#a0f' }}></span>
+        <span></span>
+      </div>
+
+      {/* 游戏标题 */}
+      <h1 style={{ textAlign: 'center', margin: '20px 0' }}>
+        宠物配对
+      </h1>
+
+      {/* 统计信息 */}
+      <div style={{
+        textAlign: 'center',
+        fontSize: '16px',
+        marginBottom: '20px'
+      }}>
+        移动次数: {moves} | 剩余配对: {remaining} | 准确率: {accuracy}%
+      </div>
+
+      {/* 卡片网格 */}
+      <div style={{
+        display: 'grid',
+        gridTemplateColumns: 'repeat(6, 1fr)',
+        gap: '10px',
+        maxWidth: '600px',
+        margin: '0 auto'
+      }}>
+        {cards.map((pet, index) => (
+          <div
+            key={index}
+            onClick={() => handleClick(index)}
+            style={{
+              width: '80px',
+              height: '80px',
+              backgroundColor: '#09f',
+              color: '#fff',
+              display: 'flex',
+              alignItems: 'center',
+              justifyContent: 'center',
+              fontSize: '30px',
+              borderRadius: '8px',
+              cursor: 'pointer',
+              userSelect: 'none'
+            }}
+          >
+            {/* 已配对或已翻开 → 显示宠物;否则显示 ? */}
+            {matched.includes(index) || flipped.includes(index)
+              ? pet
+              : '?'}
+          </div>
+        ))}
+      </div>
+
+      {/* 重新开始按钮 */}
+      <div style={{ textAlign: 'center', marginTop: '30px' }}>
+        <button
+          onClick={restart}
+          style={{
+            padding: '10px 20px',
+            fontSize: '16px',
+            backgroundColor: '#0c6',
+            color: '#fff',
+            border: 'none',
+            borderRadius: '8px',
+            cursor: 'pointer'
+          }}
+        >
+          重新开始
+        </button>
+      </div>
+    </div>
+  );
+}

+ 84 - 0
19.React/高阶/project5/src/pages/Shop.jsx

@@ -0,0 +1,84 @@
+import { useMemo, useState } from "react";
+
+const initList = [
+    { id: 1, name: '算法导论', publish: '2006-9', price: 85, count: 1 },
+    { id: 2, name: 'UNIX编程艺术', publish: '2016-2', price: 39, count: 1 },
+    { id: 3, name: '编程珠玑', publish: '2020-4', price: 189, count: 1 },
+    { id: 4, name: '代码大全', publish: '2013-8', price: 299, count: 1 },
+];
+function Shop() {
+    const [books, setBooks] = useState(initList);
+    let total = useMemo(() => {
+        // let sum = 0;
+        // books.forEach((item) => (sum += item.count * item.price));
+        // return sum;
+        return books.reduce((sum,item) => sum + item.count * item.price,0);
+    },[books])
+    const renderList = () => {
+        return books.map((item, index) => (
+            <tr key={index}>
+                <td>{item.id}</td>
+                <td>{item.name}</td>
+                <td>{item.publish}</td>
+                <td>{item.price}</td>
+                <td>
+                    <input type="button" value="-"
+                        disabled={item.count === 1 ? true : false}
+                        onClick={() => handleReduce(item.id)} />
+                    <span style={
+                        {
+                            padding: '4px'
+                        }
+                    }>
+                        {item.count}
+                    </span>
+                    <input type="button" value="+" onClick={() => handleAdd(item.id)} />
+                </td>
+                <td>
+                    <button onClick={() =>handleRemove(item.id)}>移除</button>
+                </td>
+            </tr>
+        ))
+    }
+    const filterData = (val) => {
+        for (let i = 0; i < books.length; i++) {
+            if (books[i].id === val) {
+                return books[i];
+            }
+        }
+    }
+    const handleAdd = (val) => {
+        let currentBook = filterData(val)
+        currentBook.count++;
+        setBooks([...books]);
+    }
+    const handleReduce = (val) => {
+        let currentBook = filterData(val)
+        currentBook.count--;
+        setBooks([...books]);
+    }
+    function handleRemove(val) {
+        if(!window.confirm("确定从购物车中移除当前书籍么")) return;
+        setBooks(books.filter(item => item.id !== val))
+    }
+    return <div>
+        <table border={1} cellPadding={25}>
+            <thead>
+                <tr>
+                    <td>#</td>
+                    <td>书籍名称</td>
+                    <td>出版日期</td>
+                    <td>价格</td>
+                    <td>购买数量</td>
+                    <td>操作</td>
+                </tr>
+            </thead>
+            <tbody>
+                {renderList()}
+            </tbody>
+        </table>
+        <h1>总价格:{total}</h1>
+    </div>
+}
+
+export default Shop;

+ 21 - 0
19.React/高阶/project5/src/pages/login.css

@@ -0,0 +1,21 @@
+.login {
+    width: 100vw;
+    height: 100vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+.main {
+    width: 400px;
+    height: 300px;
+    h1 {
+        text-align: center;
+    }
+    .ant-form {
+        width: 100vw;
+    }
+    .ant-btn {
+        width: 400px;
+        margin-left: -200px;
+    }
+}

+ 13 - 0
19.React/高阶/project5/src/reportWebVitals.js

@@ -0,0 +1,13 @@
+const reportWebVitals = onPerfEntry => {
+  if (onPerfEntry && onPerfEntry instanceof Function) {
+    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+      getCLS(onPerfEntry);
+      getFID(onPerfEntry);
+      getFCP(onPerfEntry);
+      getLCP(onPerfEntry);
+      getTTFB(onPerfEntry);
+    });
+  }
+};
+
+export default reportWebVitals;

+ 47 - 0
19.React/高阶/project5/src/router/index.js

@@ -0,0 +1,47 @@
+import { createBrowserRouter } from 'react-router-dom';
+import { lazy,Suspense } from 'react';
+import Layout from '../layout';
+import Login from '../pages/Login';
+const Home = lazy(() => import("../pages/Home"));
+const Shop = lazy(() => import("../pages/Shop"));
+const Setting = lazy(() => import("../pages/Setting"));
+const Product = lazy(() => import("../pages/Product"));
+const fallbackWord = '加载中...';
+const withSuspense = (Component) => (
+    <Suspense fallback={fallbackWord}>
+        <Component/>
+    </Suspense>
+)
+
+const routes = [
+    {
+        path:'/',
+        element: <Layout></Layout>,
+        children: [
+            {
+               index:true,
+                element:withSuspense(Home)
+            },
+            {
+                path:'shop',
+                element:withSuspense(Shop)
+            },
+            {
+                path:'setting',
+                element:withSuspense(Setting)
+            },
+            {
+                path:'product',
+                element:withSuspense(Product)
+            },
+        ]
+    },
+    {
+        path: '/login',
+        element: <Login></Login>
+    }
+]
+
+const router = createBrowserRouter(routes);
+
+export default router;

+ 5 - 0
19.React/高阶/project5/src/setupTests.js

@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom';

+ 16 - 0
19.React/高阶/project5/src/store/index.js

@@ -0,0 +1,16 @@
+import { configureStore } from '@reduxjs/toolkit';
+import systemReducer from './slice/system';
+import productReducer from './slice/product';
+const store = configureStore({
+    reducer: {
+        systemReducer,
+        productReducer
+    },
+    middleware: (getDefaultMiddleware) =>
+        getDefaultMiddleware({
+            serializableCheck: false
+        })
+
+})
+
+export default store;

+ 32 - 0
19.React/高阶/project5/src/store/slice/product.js

@@ -0,0 +1,32 @@
+import { createSlice } from "@reduxjs/toolkit";
+import { getAllProducts } from "../../api/product";
+
+export const productSlice = createSlice({
+    name: 'product',
+    initialState: {
+        products: [],
+        filterName: "",
+        showStocked: false
+    },
+    reducers: {
+        setProducts(state, { payload }) {
+            state.products = payload;
+        },
+        setFilterName(state, { payload }) {
+            state.filterName = payload;
+
+        },
+        setShowStocked(state, { payload }) {
+            state.showStocked = payload;
+
+        },
+    }
+})
+
+export default productSlice.reducer;
+export const { setProducts, setFilterName, setShowStocked } = productSlice.actions;
+
+export const fetchProducts = () => async (dispatch) => {
+    let results = await getAllProducts();
+    dispatch(setProducts(results.data));
+}

+ 40 - 0
19.React/高阶/project5/src/store/slice/system.js

@@ -0,0 +1,40 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+import {
+    HomeOutlined, ShopOutlined, SettingOutlined,ShoppingCartOutlined
+} from '@ant-design/icons';
+export const systemSlice = createSlice({
+    name: "system1",
+    initialState: {
+        menus: [
+            {
+                id:1,
+                key: "/",
+                label: "首页",
+                icon: <HomeOutlined />
+            },
+            {
+
+                id:2,
+                key: "/product",
+                label: "商品列表",
+                icon: <ShopOutlined />
+            },
+            {
+
+                id:3,
+                key: "/setting",
+                label: "设置",
+                icon: <SettingOutlined />
+            },
+            {
+                id:4,
+                key: "/shop",
+                label: "购物车",
+                icon:<ShoppingCartOutlined />
+            }
+        ]
+    }
+})
+
+export default systemSlice.reducer;

+ 1 - 0
20.webpack/dist/a.js

@@ -0,0 +1 @@
+(()=>{for(var r=0;r<5;r++){for(var e=4;e>r;e--)document.write("&nbsp;");for(var o=0;o<=r;o++)document.write("*&nbsp;");document.write("<br>")}})();

+ 1 - 0
20.webpack/dist/b.js

@@ -0,0 +1 @@
+console.log("你好");

+ 1 - 0
20.webpack/dist/main.js

@@ -0,0 +1 @@
+(()=>{for(var r=0;r<5;r++){for(var o=4;o>r;o--)document.write("&nbsp;");for(var e=0;e<=r;e++)document.write("*&nbsp;");document.write("<br>")}})(),console.log("你好");

+ 10 - 0
20.webpack/package.json

@@ -0,0 +1,10 @@
+{
+  "name": "20.webpack",
+  "version": "1.0.0",
+  "main": "index.js",
+  "license": "MIT",
+  "devDependencies": {
+    "webpack": "^5.104.1",
+    "webpack-cli": "^6.0.1"
+  }
+}

+ 15 - 0
20.webpack/src/hello.js

@@ -0,0 +1,15 @@
+// 第二题星号图形
+// 控制输出的行数
+for (var i = 0; i < 5; i++) {
+    // &nbsp; 这个实体字符实现空格
+    // 控制星号前面的空格
+    for (var k = 4; k > i; k--) {
+        document.write("&nbsp;");
+    }
+    // 控制每一行输出的内容 *
+    for (var j = 0; j <= i; j++) {
+        document.write("*&nbsp;");
+    }
+    // 换行
+    document.write("<br>");
+}

+ 1 - 0
20.webpack/src/index.js

@@ -0,0 +1 @@
+console.log("你好");

+ 8 - 0
20.webpack/webpack.config.js

@@ -0,0 +1,8 @@
+module.exports = {
+    // 配置打包入口
+    // entry:'./src/hello.js'
+    entry: {
+        a:'./src/hello.js',
+        b:'./src/index.js'
+    }
+}

+ 18 - 0
20.webpack/初始.md

@@ -0,0 +1,18 @@
+1. 安装yarn
+npm install yarn -g
+2. 初始化文件
+yarn init -y
+3. 安装webpack
+yarn add -D webpack webpack-cli
+4. 创建src文件夹 创建index.js文件
+5. 配置webpack
+创建webpack.config.js
+6. 打包命令
+yarn webpack
+7. 入口文件
+entry: 'xxx'
+entry: ['./src/hello.js','./src/index.js']
+entry: {
+    a:'./src/hello.js',
+    b:'./src/index.js'
+}

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott