zheng hai 1 día
pai
achega
cac6beb09f

+ 2 - 1
19.React/高阶/project4/src/App.js

@@ -1,4 +1,5 @@
-
+import logo from './logo.svg';
+import './App.css';
 import Demo1 from './components/Demo1';
 import Demo1 from './components/Demo1';
 function App() {
 function App() {
   return (
   return (

+ 57 - 0
19.React/高阶/project4/src/components/Count.jsx

@@ -0,0 +1,57 @@
+import { useDispatch, useSelector } from 'react-redux';
+import styles from './counter.module.css'
+import {reduceCount,addCount,addAmount,asyncAddCount} from '../store/slices/counter';
+import { useState } from 'react';
+function Counter() {
+    const counter = useSelector(({counterReducer})=> counterReducer)
+    const dispatch = useDispatch();
+    let [numbers,setNumbers] = useState("");
+    return (
+        <div>
+            <h3>计数器</h3>
+            <div className={styles.row}>
+                <button
+                    className={styles.button}
+                    onClick={() => {
+                        dispatch(reduceCount())
+                    }}
+                >
+                    -
+                </button>
+                <span className={styles.value}>{counter.num}</span>
+                <button
+                    className={styles.button}
+                    onClick={() => {
+                        dispatch(addCount())
+                    }}
+                >
+                    +
+                </button>
+            </div>
+            <div className={styles.row}>
+                <input
+                    className={styles.textbox}
+                    onChange={(e) => setNumbers(Number(e.target.value))}
+                />
+                <button
+                    className={styles.button}
+                    onClick={()=>{
+                        dispatch(addAmount(numbers))
+                    }}
+                >
+                    Add Amount
+                </button>
+                <button
+                    className={styles.asyncButton}
+                    onClick={()=>{
+                        dispatch(asyncAddCount(numbers))
+                    }}
+                >
+                    Add Async
+                </button>
+            </div>
+        </div>
+    );
+}
+
+export default Counter;

+ 76 - 0
19.React/高阶/project4/src/components/counter.module.css

@@ -0,0 +1,76 @@
+.row {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.row:not(:last-child) {
+  margin-bottom: 16px;
+}
+
+.value {
+  font-size: 78px;
+  padding-left: 16px;
+  padding-right: 16px;
+  margin-top: 2px;
+  font-family: 'Courier New', Courier, monospace;
+}
+
+.button {
+  appearance: none;
+  border: none;
+  background: none;
+  font-size: 32px;
+  padding-left: 12px;
+  padding-right: 12px;
+  outline: none;
+  border: 2px solid transparent;
+  color: rgb(112, 76, 182);
+  padding-bottom: 4px;
+  cursor: pointer;
+  background-color: rgba(112, 76, 182, 0.1);
+  border-radius: 2px;
+  transition: all 0.15s;
+}
+
+.textbox {
+  font-size: 32px;
+  padding: 2px;
+  width: 64px;
+  text-align: center;
+  margin-right: 8px;
+}
+
+.button:hover,
+.button:focus {
+  border: 2px solid rgba(112, 76, 182, 0.4);
+}
+
+.button:active {
+  background-color: rgba(112, 76, 182, 0.2);
+}
+
+.asyncButton {
+  composes: button;
+  position: relative;
+  margin-left: 8px;
+}
+
+.asyncButton:after {
+  content: '';
+  background-color: rgba(112, 76, 182, 0.15);
+  display: block;
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  left: 0;
+  top: 0;
+  opacity: 0;
+  transition: width 1s linear, opacity 0.5s ease 1s;
+}
+
+.asyncButton:active:after {
+  width: 0%;
+  opacity: 1;
+  transition: 0s;
+}

+ 10 - 1
19.React/高阶/project4/src/store/index.js

@@ -1,5 +1,9 @@
 import {configureStore} from '@reduxjs/toolkit'; 
 import {configureStore} from '@reduxjs/toolkit'; 
 import userReducer from './slices/user';
 import userReducer from './slices/user';
+import counterReducer from './slices/counter';
+import themeReducer from './themeSlice';
+import userReducer from './userSlice';
+import logReducer from './logSlice';
 // const reducer = (state,action) => {
 // const reducer = (state,action) => {
 //     return {
 //     return {
 //         user:"图图"
 //         user:"图图"
@@ -7,7 +11,12 @@ import userReducer from './slices/user';
 // }
 // }
 const store = configureStore({
 const store = configureStore({
     reducer:{
     reducer:{
-        users: userReducer
+        users: userReducer,
+        counterReducer,
+        theme: themeReducer, // 主题模块
+    user: userReducer,   // 用户信息模块
+    log: logReducer      // 操作日志模块
+        
     }
     }
 })
 })
 // Redux 遵循 单向数据流
 // Redux 遵循 单向数据流

+ 28 - 0
19.React/高阶/project4/src/store/slices/counter.js

@@ -0,0 +1,28 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+export const counterSlice = createSlice({
+    name: "counter1",
+    initialState: {
+        num: 0
+    },
+    reducers: {
+        addCount(state) {
+            state.num++;
+        },
+        reduceCount(state) {
+            state.num--;
+        },
+        addAmount(state,{payload}) {
+            state.num += payload;
+        }
+    }
+})
+
+export default counterSlice.reducer;
+export const { addCount, reduceCount,addAmount } = counterSlice.actions;
+
+export const asyncAddCount = (val) => (dispatch,getState) => {
+    setTimeout(()=>{
+        dispatch(addAmount(val))
+    },1000)
+}

+ 34 - 0
19.React/高阶/project4/src/store/slices/logSlice.js

@@ -0,0 +1,34 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+// 初始状态:空日志列表
+const initialState = {
+  logs: []
+};
+
+const logSlice = createSlice({
+  name: 'log',
+  initialState,
+  reducers: {
+    // 添加操作日志 action
+    addLog: (state, action) => {
+      // action.payload 传入日志内容
+      const newLog = {
+        id: Date.now(), // 用时间戳作为唯一 ID
+        content: action.payload,
+        time: new Date().toLocaleString() // 格式化当前时间(年月日 时分秒)
+      };
+      // 往日志列表头部添加(最新日志在最前面)
+      state.logs.unshift(newLog);
+    },
+    // 清空操作日志 action
+    clearLogs: (state) => {
+      state.logs = [];
+    }
+  }
+});
+
+// 提取 action 创建函数
+export const { addLog, clearLogs } = logSlice.actions;
+
+// 导出 reducer
+export default logSlice.reducer;

+ 24 - 0
19.React/高阶/project4/src/store/slices/themeSlice.js

@@ -0,0 +1,24 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+// 初始状态:默认亮色主题
+const initialState = {
+  currentTheme: 'light-theme' // 对应 global.css 中的 class 名:light-theme/dark-theme/blue-theme
+};
+
+const themeSlice = createSlice({
+  name: 'theme',
+  initialState,
+  reducers: {
+    // 切换主题 action
+    changeTheme: (state, action) => {
+      // action.payload 传入主题名称(如 'dark-theme')
+      state.currentTheme = action.payload;
+    }
+  }
+});
+
+// 提取 action 创建函数
+export const { changeTheme } = themeSlice.actions;
+
+// 导出 reducer
+export default themeSlice.reducer;

+ 31 - 0
19.React/高阶/project4/src/store/slices/userSlice.js

@@ -0,0 +1,31 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+// 初始用户信息
+const initialState = {
+  name: '张三',
+  age: 28,
+  profession: '前端开发工程师',
+  avatar: 'https://picsum.photos/200/200?random=1' // 随机头像图片
+};
+
+const userSlice = createSlice({
+  name: 'user',
+  initialState,
+  reducers: {
+    // 修改用户信息 action(payload 传入新的用户信息对象)
+    updateUserInfo: (state, action) => {
+      // 合并新信息到原有状态(Object.assign 或展开运算符均可)
+      return { ...state, ...action.payload };
+    },
+    // 重置用户信息 action(恢复初始状态)
+    resetUserInfo: () => {
+      return initialState;
+    }
+  }
+});
+
+// 提取 action 创建函数
+export const { updateUserInfo, resetUserInfo } = userSlice.actions;
+
+// 导出 reducer
+export default userSlice.reducer;

+ 110 - 0
19.React/高阶/project4/src/styles/global.css

@@ -0,0 +1,110 @@
+/* 全局样式重置 */
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: "Microsoft YaHei", sans-serif;
+}
+
+body {
+  min-height: 100vh;
+  transition: all 0.3s ease;
+}
+
+/* 根元素 - 通用样式 */
+#root {
+  padding: 2rem;
+  max-width: 1200px;
+  margin: 0 auto;
+}
+
+/* 主题样式定义 - 亮色主题(默认) */
+.light-theme {
+  --bg-color: #ffffff;
+  --text-color: #333333;
+  --card-bg: #f8f9fa;
+  --button-bg: #007bff;
+  --button-text: #ffffff;
+  --border-color: #dee2e6;
+}
+
+/* 暗色主题 */
+.dark-theme {
+  --bg-color: #1a1a1a;
+  --text-color: #f8f9fa;
+  --card-bg: #2d2d2d;
+  --button-bg: #6c757d;
+  --button-text: #ffffff;
+  --border-color: #444444;
+}
+
+/* 蓝色主题 */
+.blue-theme {
+  --bg-color: #e3f2fd;
+  --text-color: #0d47a1;
+  --card-bg: #bbdefb;
+  --button-bg: #2196f3;
+  --button-text: #ffffff;
+  --border-color: #90caf9;
+}
+
+/* 通用组件样式 */
+.card {
+  background-color: var(--card-bg);
+  border: 1px solid var(--border-color);
+  border-radius: 8px;
+  padding: 1.5rem;
+  margin-bottom: 1.5rem;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+h1, h2, h3 {
+  color: var(--text-color);
+  margin-bottom: 1rem;
+}
+
+p, label, span {
+  color: var(--text-color);
+}
+
+button {
+  background-color: var(--button-bg);
+  color: var(--button-text);
+  border: none;
+  border-radius: 4px;
+  padding: 0.5rem 1rem;
+  cursor: pointer;
+  margin: 0 0.5rem 0.5rem 0;
+  transition: background-color 0.3s ease;
+}
+
+button:hover {
+  opacity: 0.9;
+}
+
+input {
+  padding: 0.5rem;
+  border: 1px solid var(--border-color);
+  border-radius: 4px;
+  background-color: var(--bg-color);
+  color: var(--text-color);
+  margin: 0.5rem 0;
+  width: 250px;
+}
+
+.form-item {
+  margin-bottom: 1rem;
+}
+
+.log-list {
+  list-style: none;
+  margin-top: 1rem;
+  max-height: 200px;
+  overflow-y: auto;
+}
+
+.log-item {
+  padding: 0.5rem;
+  border-bottom: 1px solid var(--border-color);
+  font-size: 0.9rem;
+}