Front-end development tips

Front-end development tips

  1. Regarding the difference between sharp angles (^) and tildes (~) in package.json

    In package.json, we can use sharp angles (^) and tildes (~) to represent different package versions. These symbols are often used to define the minimum version required for a package or the range of versions within which updates are allowed.

    A sharp angle ( ) is typically used to specify that the major version number remains unchanged, allowing new minor and patch versions to be installed. For example, if we specify the version of a package in the "dependencies" object in package.json as " 1.2.3", then npm will install the latest version that is greater than or equal to 1.2.3 and less than 2.0.0.

    A tilde ( ) is typically used to specify that the patch version of a package remains unchanged, allowing new minor and major versions to be installed. For example, if we specify the version of a package in the "dependencies" object in package.json as " 1.2.3", then npm will install the latest version that is greater than or equal to 1.2.3 and less than 1.3.0.

    In general, the use of sharp angles and tildes is to ensure that dependent packages are compatible and updated in a timely manner. It is recommended to choose an appropriate version control method when defining package.json to make the project's dependencies more stable.

  2. Version conflict occurs when executing npm install command
    // 执行npm install 命令发现报错:D:\StudySoft\VsCode\code\CODE_Projects\new-cms>npm install
    npm ERR! code ERESOLVE
    npm ERR! ERESOLVE unable to resolve dependency tree
    npm ERR!
    npm ERR! While resolving: panda@1.0.0
    npm ERR! Found: react@17.0.2
    npm ERR! node_modules/react
    npm ERR!   react@"^17.0.2" from the root project
    npm ERR!   peer react@">=16.9.0" from antd@4.24.8
    npm ERR!   node_modules/antd
    npm ERR!     antd@"^4.21.2" from the root project
    npm ERR!     peer antd@"4.x" from @ant-design/pro-card@1.0.6
    npm ERR!     node_modules/@ant-design/pro-card
    npm ERR!       @ant-design/pro-card@"1.0.6" from the root project
    npm ERR!   1 more (react-dom)
    

    This error is due to a problem with the dependency tree, which may be caused by version conflicts of some dependencies.

    You can try the following methods to solve this problem:

    1. Clear the node_modules and package-lock.json files, and re-execute the npm install command.
    2. Use the npm install --legacy-peer-deps command instead of the npm install command. This command will ignore the version restrictions of peerDependencies.
    3. Update the dependency version numbers in package.json to comply with the SemVer specification.
    4. Change the package manager to yarn or pnpm and try to execute the installation command again.

    If the above method does not solve the problem, it is recommended to check whether the dependencies in package.json in the project are correct and check whether the network connection status is normal.

  3. Steps to quickly locate the location corresponding to a component
    1. After the project is running, find the component keyword in the address bar:

    Insert image description here

    1. Copy the relative path to the project routing folder:

    Insert image description here

    1. Combining the component keyword and the relative path of the route, you can quickly locate the route of the modified component:

    Insert image description here

    1. You can clearly see the location of the component definition in the routing file:

    Insert image description here

  4. About CSS modularity

    In CSS, modularity can be achieved in a variety of ways. Here are a few common methods:

    1. Naming convention: Identify which module a style rule belongs to by using a specific naming prefix or suffix in the style rule. For example, if your website contains a module called "header", you can use the "-header" suffix to identify all style rules related to that module.
    2. BEM (Block, Element, Modifier) ​​method: This is a widely used CSS naming convention based on the idea of ​​componentized design. Using BEM, each module is treated as an independent block, which contains multiple elements and can have zero or more modifiers. For example, a module named "head" could define a block element "head__logo", and a block element "head--transparent" with the modifier. Disadvantages: This method and the naming convention method are relatively traditional solutions, but as the scale of the application increases, the problems of naming conflicts and code duplication become more and more obvious, increasing the complexity of development and maintenance. Difficulty.
    3. CSS Modules: It is an official CSS modular solution. It uses packaging tools such as Webpack and Vite to archive CSS style sheets as modules and automatically manage the scope and naming of CSS class names. This makes CSS code easier to maintain and extend, and avoids global pollution and naming conflicts. In the CSS scope of the Vue framework, the local scope (Local Scope) method in CSS Modules is adopted. index.module.less is also a CSS modularization method based on CSS Modules. It can be used in React projects, but it may cause inflexible problems. For example, if you want to declare a selector to be effective globally, you can only use pseudo Class: global. Disadvantages: Using CSS Modules requires the use of packaging tools, and the class name of each component needs to be unique, otherwise it will affect the correctness of the style. In addition, the learning cost of CSS Modules is relatively high compared to other methods, and it requires understanding some additional syntax and configuration.
    4. CSS-in-JS: This is a method of embedding CSS styles into components as JavaScript objects. Using CSS-in-JS, you can define styles for different modules in the same file or within the same component and apply them dynamically based on component state or other conditions. Common CSS-in-JS libraries include Styled Components, Emotion, etc. Disadvantages: Although CSS-in-JS can achieve componentized style definition and better utilize the programming capabilities of JavaScript, it requires the introduction of additional libraries and plug-ins into the project, which increases the complexity of the code and the cost of learning.

    The above are several common CSS modularization methods. Each method has its advantages, disadvantages and applicable scenarios. Choosing the right approach can make your code more scalable, maintainable, and reusable, improve development efficiency, and reduce errors.

  5. Regarding naming conventions when defining types

    Specification: It starts with a capital I, and the first letter of each word is capitalized. If the type is an array, add Item after it, such as:

    export interface IOperateInfoItem {
      action: string
      name: string
      createTime: string
      type: string
      docnumber: number
    }
    
  6. git clone to the local project and execute the npm install command to report an error

    Insert image description here

    Cause: permission issue

    Solution: https://blog.csdn.net/qq_34488939/article/details/121146658

    (The main thing is to add permissions to the node_global folder)

    If the installation still fails and an error is reported after that, if you look carefully, you will find that it is not the above error, but an error when installing certain packages. Because there are pre-dependencies, you can execute npm i -f to force the installation:

    Insert image description here

  7. Tips on writing comments

    When writing comments with double slashes:

    Insert image description here

    As a result, if this variable is used elsewhere, there will be no comment prompt when the mouse is placed on it:

    Insert image description here

    But if it is commented in this way /** */:

    Insert image description here

    If this variable is used elsewhere, a comment will appear when the mouse is placed on it:

    Insert image description here

  8. Application of generics in interface type definition

    For some requests, the data returned by the interface always has the same fields. For example, the data returned by paging for the following request will always have several fields such as current, page, records, searchCount, size, total, etc., but the fields in records may be different. Specific definitions for specific situations. Therefore, for this situation, you can use generics, define data as PagesuccessResponse, and the records inside are generic arrays, and then you can define the specific situation:

    Insert image description here
    Insert image description here
    Insert image description here
    Insert image description here

  9. About the automated deployment process of enterprise projects

    Use GitLab's Webhook feature to monitor changes in the code base and automatically trigger the deployment process. The specific implementation steps are as follows:

    1. Add a new webhook in the Webhooks option in GitLab project settings. Specify the webhook URL address as a script on the deployment server that receives requests.

    2. Write a script on the deployment server. When receiving a request from GitLab Webhook, parse the data in the request and trigger the corresponding automated deployment process based on the parsing results. The deployment process can include multiple steps such as testing, building, and deploying, and can be implemented using automated deployment tools such as Jenkins or Ansible.

    3. After completing the above steps, whenever a change occurs in the GitLab code base, the deployment server will automatically receive the Webhook request and trigger the automated deployment process. In this way, the purpose of automated deployment can be achieved and development efficiency and deployment quality can be improved.

  10. Encountered permission problems and solutions when git clone warehouse project

    As shown below, the first time I git clone a warehouse, I encounter permission problems:

    Insert image description here

    Solution: Generate git password locally and add it to the repository:

    To generate a Git key locally, follow these steps:

    1. Open a command line or terminal window.
    2. Enter the following command: ssh-keygen -t rsa -b 4096 -C "[email protected]".
    3. Press Enter and the key will be generated using the default file name and location. If you wish to use a different file name or location, change it as needed.
    4. You will then be prompted to enter a password to protect your key. If you don't want to add a password, you can just press Enter.
    5. Finally, two files will be generated at the specified location: public key (id_rsa.pub) and private key (id_rsa).

    Insert image description here

    Just paste the public key content inside:

    Insert image description here

    At this point, you can successfully git clone the project.

  11. Things to note when using the a tag

    When using the a tag, generally in addition to setting the href attribute, you also need to set the two attributes target="_blank" and rel="noopener noreferrer".

    target="_blank" is used to open the link in a new window or new tab instead of opening the link on the current page.

    rel="noopener noreferrer" is a security attribute mainly used to protect user privacy. Among them, noreferrer instructs the browser not to send the Referer header (that is, the information that tells the target site which website it comes from) when navigating to the target resource, thereby protecting the user's browser information from being leaked. Noopener instructs the browser to cancel the reference to the original page in the next new page, preventing malicious pages from accessing the permissions of the original page through window.opener, thereby preventing cross-window scripting attacks.

    The combined use of these two attributes can effectively prevent some potential security issues, and it is recommended to develop a habit of using them during the development process.

  12. About automatic conversion between px and rem (using postcss-pxtorem)

    Install dependencies:

    pnpm install postcss-pxtorem
    

    Create a new postcss.config.js file:

    export default {
          
          
      plugins: {
          
          
        'postcss-pxtorem': {
          
          
          // 基准屏幕宽度
          rootValue: 192,
          // rem的小数点后位数
          unitPrecision: 2,
          propList: ['*'],
          exclude: function (file) {
          
          
            // console.log('postcss-pxtorem', file)
            // if (file.indexOf('node_modules') > -1 || file.indexOf('cms.module') > -1) {
          
          
            //   console.log('postcss-pxtorem', file)
            // }
            return file.indexOf('node_modules') > -1;
          },
        },
      },
    };
    

    Introduced in the root node file:

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import App from '@/App';
    import '@/assets/global.less';
    const onResize = () => {
      let width = document.documentElement.clientWidth;
      if (width > 1920) {
        width = 1920;
      }
      document.documentElement.style.fontSize = width / 10 + 'px';
    };
    // 初始化时,即页面挂载前就要执行一次,防止页面第一次加载时产生抖动
    onResize();
    window.addEventListener('resize', onResize);
    ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
      <React.Fragment>
        <App />
      </React.Fragment>,
    );
    

    In App.tsx:

    import HomePage from '@/pages/homePage';
    import styles from './app.module.less';
    function App() {
      return (
        <div className={styles.content}>
          <HomePage></HomePage>
        </div>
      );
    }
    
    export default App;
    

    In src\app.module.less:

    .content {
          
          
      margin: 0 auto;
      max-width: 1920px;
    }
    

    The outermost container of each component separated:

    Insert image description here

    After that, you can directly write the px unit size of the design draft. The maximum width is set to 1920px. If it exceeds this width, it will be centered. If it is less than this width, it will be reduced.

    postcss-pxtorem is a PostCSS plug-in that converts pixel units (px) in CSS to rem units to achieve responsive layout. The principle of this plug-in is to traverse each rule in the CSS style file, detect and convert the pixel units that appear, and convert them into rem units according to the agreed conversion ratio. Normally, the plug-in uses the viewport width as a reference to achieve consistent UI display on different devices.

    For example, under the default settings, when the style rule of font-size: 16px; appears in CSS, the plug-in will automatically convert it to font-size: 1rem;, calculated according to the default conversion ratio (1px = 1/16rem) And get. This ensures that the size and spacing of UI elements can be adaptively adjusted under different screen sizes and resolutions, improving the accessibility and user experience of the website or application.

  13. Tips on debugging and modifying the style of antd design components

    When we use the antd design component, we need to change the default style. If we want to change the style of a component, we first need to find the class name of a component label. Generally, we can find it through mouse selection in the console. For some things that need to be triggered, There are two situations for displayed elements: hover triggering or the component itself has something like open: true/false (similar to the Dropdown component, expansion or collapse is triggered by the open attribute)

    Insert image description here

    If you want to make global changes at this time, you need to add code similar to the following in the style file:

    :global {
      .ant-dropdown .ant-dropdown-menu {
        box-shadow: 0px 7.41667px 22.25px rgba(54, 88, 255, 0.15);
        border-radius: 14.8333px;
        padding: 20px 10px 20px 10px;
        display: flex;
        flex-direction: column;
        justify-content: center;
      }
    }
    

    At this time, if you want to change only the component in a certain place, you need to add rootClassName to the component and then change the style:

    Insert image description here

    At this point you can see that the label has been mounted with the class name: (rootClassName + generated hash (used for style isolation, the principle is similar to Vue’s scoped method)

    Insert image description here

    Now you can modify the style:

    .dropdown {
      :global {
        .ant-dropdown-menu {
          box-shadow: 0px 7.41667px 22.25px rgba(54, 88, 255, 0.15);
          border-radius: 14.8333px;
          padding: 20px 10px 20px 10px;
          display: flex;
          flex-direction: column;
          justify-content: center;
          li {
            padding: 4.8px 36px 4.8px 36px !important;
          }
        }
      }
    }
    
  14. Regarding screen adaptation and zoom operation adaptation issues when making web pages based on design drafts
    14-1 Do not use design drafts to determine positioning

    When we restore the design draft, for the outermost container of each separated component, we do not need to set a fixed height and width for it. Just set max-width to width: 100%; max-width: 1920px; and the rest. Just expand it by child elements:

    Insert image description here

    When setting the child elements in the container, remember not to use the absolute positioning of the design draft, because the positioning is determined based on the entire web page, which may cause the page layout to collapse. Just tile the container with the container as the parent box.

    14-2 About the inexplicable scroll bar (involving the default width of the element)

    If no width is set, the element's default width is 100%. This means that the element fills the entire width of its parent element. ( Some elements (such as ) have their own default width ), like this:

    Insert image description hereInsert image description here

    When the element is offset (the left value or the right value is not 0), it will cause the box to overflow the parent box, causing scroll bars to appear on the entire page:
    Insert image description here

    Insert image description here

    At this time, you can use calc() to calculate and determine the width of the box to prevent the above situation from happening:
    Insert image description here
    Insert image description here

    If it is not the default width of the element that causes the scroll bar to appear inexplicably, then the troubleshooting method is generally to delete it in the root component one by one to see which component the problem occurs in. After determining which component the problem occurs, delete the element in the component to see where the problem occurs. element. (usually caused by elements with a fixed width that is too wide)

    14-3 About the 12px limit of browsers

    For some divs with text inside, if you set a fixed width and height for these divs, when the page is reduced, due to the 12px limit of the browser font, the text may overflow the div box. In this case, two solutions can be adopted:

    1. Do not set the width and height of the div, but set padding so that the text inside stretches the div to prevent overflow:
      Insert image description here

    2. Use media queries to force scaling of text: (When using this method, remember to add an extra layer of boxes to the text, because scaling scales the entire element together):

    Insert image description here

    14-4 Regarding processing when shrinking the screen (involving meta viewport)

    It is a meta element that describes the viewport of a web page.

    On mobile devices, web pages often need to adapt to different screen sizes and resolutions. So, how should the web page behave in this case? The viewport element is here to solve this problem.

    Specifically, width=device-width means that the width of the web page should be equal to the width of the device, while initial-scale=1.0 means that the initial scaling of the web page is 100%. This setting is important to ensure that web pages displayed on mobile devices respond correctly to user gestures.

    In addition to the two attributes mentioned above, the viewport element has some other commonly used attributes, such as:

    • height: Set the height of the viewport;
    • user-scalable: Set whether the user is allowed to zoom the web page;
    • minimum-scale and maximum-scale: Set the minimum and maximum values ​​that the user can zoom.

    To sum up, the viewport element is a very important web page meta-information that can help web pages be displayed correctly on mobile devices and provide a more friendly user experience.

    If it is removed <meta name="viewport" content="width=device-width, initial-scale=1.0"> , the webpage will automatically zoom when you open it on a mobile device, causing the elements in the webpage to become very small. When there is no design draft for the mobile terminal, it can be regarded as a way to prevent the layout style from collapsing on the mobile terminal.

    If no width is set, the element's default width is 100%. This means that the element fills the entire width of its parent element. Some elements (such as <button>) have their own default width), like this:

    Insert image description here

    When the element is offset (the left value or the right value is not 0), it will cause the box to overflow the parent box, causing scroll bars to appear on the entire page:Insert image description here

    Insert image description here

    At this time, you can use calc() to calculate and determine the width of the box to prevent the above situation from happening:

    Insert image description here

    Insert image description here

  15. A case of using grid layout
    <div className={
          
          styles.innerface}>
        <div className={
          
          styles.imageList}>
            {
          
          fourthImgs.innerfaceImgs.map((imgSrc, index) => (
               <div className={
          
          styles.item} key={
          
          index}>
                  <img src={
          
          imgSrc} alt="" />
                </div>
             ))}
         </div>
     </div>
    
    .innerface {
          
          
          width: 1920px;
          height: 1024px;
          position: absolute;
          top: 3750px;
          left: 50%;
          transform: translate(-50%, 0);
          display: flex;
          justify-content: center;
          align-items: center;
          .imageList {
          
          
            display: grid;
            grid-template-columns: repeat(8, 1fr);
            grid-template-rows: repeat(3, auto);
            gap: 10px;
            width: 100%;
            height: 100%;
            opacity: 0.15;
            .item:nth-child(8n + 1),
            .item:nth-child(8n) {
          
          
              img {
          
          
                width: calc(50%);
                height: calc((100%) - 10px);
              }
            }
            .item:nth-child(8n) {
          
          
              text-align: right;
            }
            .item:not(:nth-child(8n + 1)):not(:nth-child(8n)) {
          
          
              position: relative;
              img {
          
          
                position: absolute;
                width: calc((100%));
                height: calc((100%) - 10px);
              }
            }
            .item:nth-child(8n + 2) {
          
          
              img {
          
          
                left: -75px;
              }
            }
            .item:nth-child(8n + 3) {
          
          
              img {
          
          
                left: -45px;
              }
            }
            .item:nth-child(8n + 4) {
          
          
              img {
          
          
                left: -15px;
              }
            }
            .item:nth-child(8n + 5) {
          
          
              img {
          
          
                right: -15px;
              }
            }
            .item:nth-child(8n + 6) {
          
          
              img {
          
          
                right: -45px;
              }
            }
            .item:nth-child(8n + 7) {
          
          
              img {
          
          
                right: -75px;
              }
            }
          }
        }
    

    The effect is as follows:

    Insert image description here

    Core: Remember to wrap another layer of boxes outside the img box, and then use positioning to slowly adjust the position.

  16. About multi-language switching in projects

    Multi-language switching is used in many scenarios, especially scenarios like this on the official website:

    Insert image description here

    Proceed as follows:

    1. Encapsulates a Storage class and some related types and methods to facilitate our operation and processing of sessionStorage .
      export const localStorageKey = 'com.drpanda.chatgpt.';
      
      interface ISessionStorage<T> {
              
              
        key: string;
        defaultValue: T;
      }
      // 重新封装的sessionStorage
      export class Storage<T> implements ISessionStorage<T> {
              
              
        key: string;
      
        defaultValue: T;
      
        constructor(key: string, defaultValue: T) {
              
              
          this.key = localStorageKey + key;
          this.defaultValue = defaultValue;
        }
      
        setItem(value: T) {
              
              
          sessionStorage.setItem(this.key, JSON.stringify(value));
        }
      
        getItem(): T {
              
              
          const value = sessionStorage[this.key] && sessionStorage.getItem(this.key);
          if (value === undefined) return this.defaultValue;
          try {
              
              
            return value && value !== 'null' && value !== 'undefined' ? (JSON.parse(value) as T) : this.defaultValue;
          } catch (error) {
              
              
            return value && value !== 'null' && value !== 'undefined' ? (value as unknown as T) : this.defaultValue;
          }
        }
      
        removeItem() {
              
              
          sessionStorage.removeItem(this.key);
        }
      }
      
      /** 管理token */
      export const tokenStorage = new Storage<string>('authToken', '');
      
      /** 只清除当前项目所属的本地存储 */
      export const clearSessionStorage = () => {
              
              
        for (const key in sessionStorage) {
              
              
          if (key.includes(localStorageKey)) {
              
              
            sessionStorage.removeItem(key);
          }
        }
      };
      
    2. Use React Context to implement a state management library so that all components can easily obtain the current state (i.e. language type) and re-render when state changes are detected:
      import React, { createContext, useContext, ComponentType, ComponentProps } from 'react';
      
      /** 创建context组合useState状态Store */
      function createStore<T extends object>(store: () => T) {
        // eslint-disable-next-line
        const ModelContext: any = {};
      
        /** 使用model */
        function useModel<K extends keyof T>(key: K) {
          return useContext(ModelContext[key]) as T[K];
        }
      
        /** 当前的状态 */
        let currentStore: T;
        /** 上一次的状态 */
        let prevStore: T;
      
        /** 创建状态注入组件 */
        function StoreProvider(props: { children: React.ReactNode }) {
          currentStore = store();
          /** 如果有上次的context状态,做一下浅对比,
           * 如果状态没变,就复用上一次context的value指针,避免context重新渲染
           */
          if (prevStore) {
            for (const key in prevStore) {
              if (Shallow(prevStore[key], currentStore[key])) {
                currentStore[key] = prevStore[key];
              }
            }
          }
          prevStore = currentStore;
          // eslint-disable-next-line
          let keys: any[] = Object.keys(currentStore);
          let i = 0;
          const length = keys.length;
          /** 遍历状态,递归形成多层级嵌套Context */
          function getContext<V, K extends keyof V>(key: K, val: V, children: React.ReactNode): JSX.Element {
            const Context = ModelContext[key] || (ModelContext[key] = createContext(val[key]));
            const currentIndex = ++i;
            /** 返回嵌套的Context */
            return React.createElement(
              Context.Provider,
              {
                value: val[key],
              },
              currentIndex < length ? getContext(keys[currentIndex], val, children) : children,
            );
          }
          return getContext(keys[i], currentStore, props.children);
        }
      
        /** 获取当前状态, 方便在组件外部使用,也不会引起页面更新 */
        function getModel<K extends keyof T>(key: K): T[K] {
          return currentStore[key];
        }
      
        /** 连接Model注入到组件中 */
        function connectModel<Selected, K extends keyof T>(key: K, selector: (state: T[K]) => Selected) {
          // eslint-disable-next-line func-names
          return function <P, C extends ComponentType>(
            WarpComponent: C,
          ): ComponentType<Omit<ComponentProps<C>, keyof Selected>> {
            const Connect = (props: P) => {
              const val = useModel(key);
              const state = selector(val);
              // eslint-disable-next-line @typescript-eslint/ban-ts-comment
              // @ts-ignore
              return React.createElement(WarpComponent, {
                ...props,
                ...state,
              });
            };
            return Connect as unknown as ComponentType<Omit<ComponentProps<C>, keyof Selected>>;
          };
        }
      
        return {
          useModel,
          connectModel,
          StoreProvider,
          getModel,
        };
      }
      
      export default createStore;
      
      /** 浅对比对象 */
      function Shallow<T>(obj1: T, obj2: T) {
        if (obj1 === obj2) return true;
        for (const key in obj1) {
          if (obj1[key] !== obj2[key]) return false;
        }
        return true;
      }
      

      The above code is a state management library implemented using React Context. It provides the createStore method to create a state Store, obtains the value of the corresponding state through the useModel method, uses the connectModel method in the component to connect the corresponding Model and component, and uses the StoreProvider Components inject state into the entire application. The change of state avoids meaningless re-rendering by judging whether the two states are the same before and after, and uses a shallow comparison method to judge whether the state is the same.

    3. Set the default language according to the browser API, create sessionStorage, change the value stored in sessionStorage if you switch the language, and also be responsible for introducing the language file to work with the state manager:
      import enUS from '@/locales/en-US';
      import esES from '@/locales/es-ES';
      import { Storage } from '@/common/storage';
      import { useMemo, useState } from 'react';
      import { useMemoizedFn } from 'tools';
      
      // 根据浏览器api获取当前语言
      const getBrowserLanguage = () => {
        // 获取浏览器语言字符串
        const languageString = navigator.language || navigator.languages[0];
        // 将语言字符串拆分成语言和地区
        const [language, region] = languageString.split('-');
        // 返回语言
        return language;
      };
      
      const localesMap = { enUS, esES, default: getBrowserLanguage() === 'es' ? esES : enUS };
      
      type ILocale = 'enUS' | 'esES' | 'default';
      /** 管理user */
      export const localeStorage = new Storage<ILocale>('locale', undefined as unknown as ILocale);
      
      export default () => {
        const [locale, _setLocale] = useState<ILocale>(localeStorage.getItem() || 'default');
      
        const locales = useMemo(() => (locale ? localesMap[locale] : localesMap.default), [locale]);
      
        const setLocale = useMemoizedFn((value: ILocale | ((value: ILocale) => ILocale)) => {
          if (typeof value === 'function') {
            value = value(locale!);
          }
          localeStorage.setItem(value);
          _setLocale(value);
        });
        return {
          ...locales,
          locale,
          setLocale,
        };
      };
      

      Insert image description here

      In the custom Hook exported by default above, first use useState to define a state variable named locale, which is used to store the language type currently selected by the user. The default value is localeStorage.getItem() or 'default'. Then use the useMemo function to obtain the corresponding translation text from the language package localesMap according to the current language type. If the current language type is a falsy value, the translated text for the default language 'default' is used. Finally, use the useMemoizedFn function to define a setLocale method to modify the current language type. If a function is passed in, the function is first executed according to the current language type to obtain the new language type to be modified, and then the language type is stored in local storage and the current language type variable is modified. Finally, locales, locale and setLocale are packaged into an object and returned.

      The language files are as follows:

      import {
              
               ILocales } from '../types';
      import home from './home';
      import second from './second';
      import third from './third';
      import forth from './forth';
      import fifth from './fifth';
      import contact from './contact';
      
      const enUS: ILocales = {
              
              
        home,
        second,
        third,
        forth,
        fifth,
        contact,
      };
      
      export default enUS;
      
    4. Create a global state management library for managing language status according to steps 2 and 3, and export relevant methods for external use:
      import createStore from './createStore';
      import locales from './modules/locales';
      
      const store = () => ({
              
              
        locales: locales(),
      });
      
      const contextResult = createStore(store);
      
      export const {
              
               useModel, StoreProvider, getModel, connectModel } = contextResult;
      
    5. Implement language switching in the component and use the language package of the corresponding state:

      Insert image description here

  17. About the request method based on fetch encapsulation (including adding interceptors)
    export interface IRequestOptions {
      method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
      headers?: { [key: string]: string };
      body?: BodyInit;
    }
    // 添加泛型
    export async function request<T>(url: string, options: IRequestOptions = {}): Promise<T> {
      const response = await fetch(url, {
        method: options.method || 'GET',
        headers: options.headers || {
          'Content-Type': 'application/json',
        },
        body: options.body,
      });
    
      if (!response.ok) {
        throw new Error(`Request failed with status code ${response.status}`);
      }
    
      const data = (await response.json()) as T;
      return data;
    }
    

    Now it can be used elsewhere:

    import {
          
           paramsType, resType } from './type';
    import {
          
           request } from '@/utils/request';
    
    export async function feedbackSubmit(params: paramsType): Promise<resType> {
          
          
      const data: resType = await request('https://api.example.com/data', {
          
          
        method: 'POST',
        body: JSON.stringify(params),
      });
      return data;
    }
    

    Notice:

    The feedbackSubmit request method above is an asynchronous request. If it is like this:

    setLoading(true);
    try {
          
          
      feedbackSubmit(contactMsg).then((res) => {
          
          
        if (res.code === 0) {
          
          
          message.success(contact.status.success);
        } else if (res.code === 101) {
          
          
          message.error(contact.status.throttle);
        } else {
          
          
          message.error(contact.status.fail);
        }
        setLoading(false);
      });
    } catch {
          
          
      message.error(contact.status.fail);
      setLoading(false);
      return;
    } 
    

    If the interface reports an error, it should be that the feedbackSubmit() method threw an error and it was not handled. In this case, try catch cannot catch this error because it can only handle synchronous exceptions. The feedbackSubmit() method is an asynchronous method, so you need to handle exceptions in the callback function. You can pass in the callback function in the second parameter of then to handle errors reported by the interface. For example:

    setLoading(true);
    feedbackSubmit(contactMsg)
       .then((res) => {
          
          
         if (res.code === 0) {
          
          
           message.success(contact.status.success);
         } else if (res.code === 101) {
          
          
           message.error(contact.status.throttle);
         } else {
          
          
           message.error(contact.status.fail);
         }
         setLoading(false);
       })
       .catch(() => {
          
          
         message.error(contact.status.fail);
         setLoading(false);
       });
    

    Attachment: Code to add interceptor:

    export interface IRequestOptions {
          
          
        method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
        headers?: {
          
           [key: string]: string };
        body?: BodyInit;
    }
    
    // 定义拦截器的接口
    interface Interceptor<T> {
          
          
        onFulfilled?: (value: T) => T | Promise<T>;
        onRejected?: (error: any) => any;
    }
    
    // 定义拦截器管理类--用于管理多个拦截器,可以通过use()方法向拦截器数组中添加一个拦截器,可以通过forEach()方法对所有的拦截器进行遍历和执行。
    class InterceptorManager<T> {
          
          
        private interceptors: Array<Interceptor<T>>;
    
        constructor() {
          
          
            this.interceptors = [];
        }
    
        use(interceptor: Interceptor<T>) {
          
          
            this.interceptors.push(interceptor);
        }
    
        forEach(fn: (interceptor: Interceptor<T>) => void) {
          
          
            this.interceptors.forEach((interceptor) => {
          
          
            if (interceptor) {
          
          
                fn(interceptor);
            }
            });
        }
    }
    
    // 添加拦截器的 request 函数
    export async function request<T>(url: string, options: IRequestOptions = {
          
          }): Promise<T> {
          
          
        const requestInterceptors = new InterceptorManager<IRequestOptions>();
        const responseInterceptors = new InterceptorManager<any>();
        
        // 添加请求拦截器
        requestInterceptors.use({
          
          
            onFulfilled: (options) => {
          
          
            // 处理请求
            console.log('请求拦截器:处理请求');
            return options;
            },
            onRejected: (error) => {
          
          
            console.log('请求拦截器:处理错误', error);
            return error;
            },
        });
    
        // 添加响应拦截器
        responseInterceptors.use({
          
          
            onFulfilled: (response) => {
          
          
            // 处理响应
            console.log('响应拦截器:处理响应');
            return response.json();
            },
            onRejected: (error) => {
          
          
            console.log('响应拦截器:处理错误', error);
            return error;
            },
        });
    
        // 处理请求拦截器--遍历所有的请求拦截器,并执行onFulfilled()方法,将返回值赋值给options
        requestInterceptors.forEach(async (interceptor) => {
          
          
            options = await interceptor.onFulfilled?.(options) ?? options;
        });
    
        let response = await fetch(url, {
          
          
            method: options.method || 'GET',
            headers: options.headers || {
          
          
            'Content-Type': 'application/json',
            },
            body: options.body,
        });
    
        if (!response.ok) {
          
          
            throw new Error(`Request failed with status code ${
            
            response.status}`);
        }
    
        // 处理响应拦截器--遍历所有的响应拦截器,并执行onFulfilled()方法,将返回值赋值给response
        responseInterceptors.forEach((interceptor) => {
          
          
            response = interceptor.onFulfilled?.(response) ?? response;
        });
    
        return response.json() as Promise<T>;
    }
    

    This code is a fetch request function that encapsulates an interceptor. By calling the request function, you can send a request and intercept and process the request and response.

    Specifically, an IRequestOptions interface is defined to represent the request parameters, and the request method and request header parameters are specified; an Interceptor type is defined to represent the interceptor, which includes onFulfilled and onRejected methods, which respectively represent the request success and request failure. The processing function; an InterceptorManager class is defined to manage the interceptor array, including the use method to add an interceptor and the forEach method to traverse the interceptor.

    In the request function, the request interceptor and response interceptor are first created, the interceptor is added using the use method, the request is processed in the request interceptor, and the response is processed in the response interceptor. Finally, the processed response data is returned.

  18. About agency service
    18-1 How to configure a proxy in vite to solve cross-domain access (for local cross-domain access)

    For the interface address of the production environment, we generally need to configure a proxy when making requests to solve cross-domain problems:

    When making a request locally:

    server: {
          
          
        open: true,
        proxy: {
          
          
          '/uis': {
          
          
            target: 'http://subs-global.xiongmaoboshi.com',
            changeOrigin: true,
            rewrite: (path) => path.replace(/^/api/, ''),
            // 由于网站部署在后端的OSS(云服务器)上,不经过前端的node服务,前端无法通过nginx配置代理实现跨域访问
            // 所以对于线上的生产环境,需要后端开启访问白名单,允许前端的域名访问
            // 但是本地开发环境,由于没有后端,所以需要通过vite的代理配置来实现跨域访问
            // 但是这里有个问题,就是代理配置的headers中的Origin,必须和请求的Origin一致,否则会报错(403Forbidden)
            // 虽然我们在这里设置了代理的headers,但是打开控制台会看到请求的headers中,Origin并没有被设置仍然是本地http://127.0.0.1:5173
            // 但实质上,vite代理服务器帮我们转发请求的时候,Origin已经被设置为了http://subs-global.xiongmaoboshi.com了,只是控制台没有显示出来
            headers: {
          
          
              Origin: 'http://subs-global.xiongmaoboshi.com',
            },
          },
        },
        // // 放在这里是设置全局的了,没必要,我们只需要设置代理的时候,才需要设置
        // headers: {
          
          
        //   Origin: 'http://subs-global.xiongmaoboshi.com',
        // },
      },
    

    After configuring the proxy, you can request the address locally:

    import {
          
           paramsType, resType } from './type';
    import {
          
           request } from '@/utils/request';
    
    export async function feedbackSubmit(params: paramsType): Promise<resType> {
          
          
      const data: resType = await request('/uis/xxx/xxx', {
          
          
        method: 'POST',
        body: JSON.stringify(params),
      });
      return data;
    }
    

    Insert image description here

    18-2 The principle of configuring proxy in vite to solve cross-domain issues

    principle:

    Utilizing Vite's internally integrated development server and Connect middleware framework, the request is forwarded to another server by setting a proxy server on the development server. The proxy server is not a browser and is not restricted by the same-origin policy, so it can initiate requests to interfaces under any domain name. Specifically, the development server receives requests from the browser through the listening port. When it receives a request that matches the proxy rules, it forwards the request to the target server and returns the response to the browser. While forwarding requests, the proxy server can modify information such as request headers, request bodies, and target URLs, thereby helping developers solve problems such as cross-domain, request redirection, and unified interface prefixes.

    In this example, the http-proxy-middleware library is used, which encapsulates the proxy function of the Connect middleware, and performs path rewriting before processing the request, replacing the prefix /uis in the request path with /api, so that Send the request to the correct interface of the target server.

    18-3 nginx proxy solves cross-domain issues (for deployment on your own server, otherwise the backend needs to enable access whitelisting)

    Configuring a proxy in vite solves cross-domain problems and is generally used for local access. If you need cross-domain access after going online, you can use nginx as a reverse proxy to achieve cross-domain requests. The configuration is as follows:

    server {
          
          
      server_name book-waves
      gzip on;
      location / {
          
          
        root /web-project/bookwaves-web;
        index  index.html index.htm;
        try_files $uri $uri /index.html;
      }
      location /uis {
          
          
        proxy_pass http://subs-global.xiongmaoboshi.com;                 
      }
    }
    
    18-4 Set environment variables to determine whether it is a local development environment or an online production environment

    In the above description, we know that cross-domain access is achieved locally by enabling Vite's proxy server, and cross-domain access is achieved online by setting an access whitelist on the backend. We must set an environment variable to determine whether it is a local development environment or an online production environment, because their request interfaces are different:

    import {
          
           paramsType, resType } from './type';
    import {
          
           request } from '@/utils/request';
    
    export async function feedbackSubmit(params: paramsType): Promise<resType> {
          
          
      // 本地时,由于有vite的代理服务,我们只需要在请求时,把这里的请求路径改为'/uis/ns/sendEmail'即可,因为会被代理服务转发到线上的地址
      // 但是线上时,由于没有代理服务,所以我们需要在请求时,把这里的请求路径改为'http://subs-global.xiongmaoboshi.com/uis/ns/sendEmail',因为没有代理服务,所以不会被转发到线上的地址
      let url = '';
      if (process.env.NODE_ENV === 'development') {
          
          
        url = '/uis/ns/sendEmail';
      } else {
          
          
        // 项目上线后申请了https证书,所以这里的地址需要改为https,否则会报错
        url = 'https://subs-global.xiongmaoboshi.com/uis/ns/sendEmail';
      }
      const data: resType = await request(url, {
          
          
        method: 'POST',
        body: JSON.stringify(params),
      });
      return data;
    }
    
    18-5 Situations where proxy configuration is required

    The browser's same-origin policy restricts front-end pages from initiating requests to interfaces with different domain names, which results in the need to use a proxy server to forward requests in some cases. Generally speaking, this situation includes the following:

    1. Using third-party API or services: For example, using a third-party map API service requires making a request to the interface under the domain name of the API service provider, which is different from the domain name where the front-end page is located.
    2. The development environment is different from the production environment: in the development environment, the front-end page usually runs on the local development server, while the back-end service runs on the remote server. In this case, since the development server has a different domain name than the backend server, a proxy server needs to be used to forward the request to the correct backend service endpoint.
    3. Some interfaces require login authentication: In some cases, the server needs to control access to the interface, and the user needs to first perform login authentication on the page. At this time, the front-end page needs to first initiate a request to the login interface under its own domain name for authentication. After obtaining the authentication information, it then uses a proxy server to forward the request containing the authentication information to the corresponding interface.
    18-6 Security problems caused by agents and their solutions

    Proxies may pose security issues (anyone can request the interface). Therefore, in some cases, the server needs to control access to the interface and requires the user to first perform login authentication on the page (such as logging in with user name and password, two-step verification code). At this time, the front-end page needs to first initiate a request to the login interface under its own domain name for authentication. After obtaining the authentication information, it then uses a proxy server to forward the request containing the authentication information to the corresponding interface. (Use token for authentication):

    For this type of interface, usually after the user successfully logs in, the backend will generate a token and return it to the frontend. The frontend saves the token on the client and carries the token in subsequent requests so that the server can authenticate the request. After the server receives a request with a token, it will verify whether the token is legal and decide whether to allow the request to access the corresponding resource. ****

    The advantage of this approach is that the server does not need to save a separate cookie-session for each access request. The stateless feature of the entire process also makes it easier for the server to expand horizontally to support high concurrency.

    18-7 About carrying and setting up tokens

    Token is usually carried in the Authorization field of the request header, and its format is Bearer <token> , where <token>is the token generated by back-end authentication. This method is called the Bearer Token authentication protocol, and its implementation is as follows:

    Authorization: Bearer <token>
    

    Bearer is the authentication protocol type, similar to Basic and Digest, and other types of authentication methods can be specified. <token>It is an authentication token generated by the backend, usually a random string, which can be in various forms such as JSON Web Token (JWT), OAuth Token, etc.

    When the front end sends a request, it needs to set the Authorization field to the corresponding token value so that the back end can parse the token from the request header and perform authentication. For example, in JavaScript you can use the fetch API or the axios library to set request headers:

    // 使用 fetch API
    const token = 'your_token_here'
    fetch('/api/some-resource', {
          
          
      headers: {
          
          
        Authorization: 'Bearer ' + token
      }
    })
    
    // 使用 axios
    const token = 'your_token_here'
    axios.get('/api/some-resource', {
          
          
      headers: {
          
          
        Authorization: 'Bearer ' + token
      }
    })
    
  19. About environment variables
    19-1 The concept of environment variables

    System environment variables refer to global variables set in the operating system. They are variables that specify some parameters and paths required by the operating system and other applications when running.

    Common environment variables include:

    • PATH: Specify the path where the executable file is located. When the user enters a command, the system will search for the executable file in the path specified in PATH.
    • HOME: Specify the current user's home directory path.
    • TEMP / TMP: Specify the storage path of temporary files.
    • LANG/LC_ALL: Specifies the locale of the system.

    Users can also create custom environment variables to store some parameters and configuration information they need. In the Windows operating system, environment variables can be set through "system variables" and "user variables". In Linux or Unix systems, you can use the "export" command to set environment variables.

    Using environment variables can improve application portability and flexibility because different operating systems and applications can use environment variables to adapt to different configurations and needs.

    19-2 The role of environment variables in front-end code writing

    The interface written on the back end may have different URLs in the development environment and the production environment. As the front end, when we call the interface, we must determine whether it is the development environment or the production environment to choose to call different interfaces. Like this:

    import {
          
           paramsType, resType } from './type';
    import {
          
           request } from '@/utils/request';
    
    export async function feedbackSubmit(params: paramsType): Promise<resType> {
          
          
      // 本地时,由于有vite的代理服务,我们只需要在请求时,把这里的请求路径改为'/uis/ns/sendEmail'即可,因为会被代理服务转发到线上的地址
      // 但是线上时,由于没有代理服务,所以我们需要在请求时,把这里的请求路径改为'http://subs-global.xiongmaoboshi.com/uis/ns/sendEmail',因为没有代理服务,所以不会被转发到线上的地址
      let url = '';
      if (process.env.NODE_ENV === 'development') {
          
          
        url = '/uis/ns/sendEmail';
      } else {
          
          
        // 项目上线后申请了https证书,所以这里的地址需要改为https,否则会报错
        url = 'https://subs-global.xiongmaoboshi.com/uis/ns/sendEmail';
      }
      const data: resType = await request(url, {
          
          
        method: 'POST',
        body: JSON.stringify(params),
      });
      return data;
    }
    
    19-3 Regarding the difference between node environment and browser environment in accessing environment variables

    Let me start with the conclusion: the browser itself does not directly support access to system environment variables, but Node.js can access environment variables.

    A browser is an application that runs on the user's operating system. It communicates with the system hardware through APIs and drivers provided by the operating system.

    System environment variables are system-level configuration information. They are variables that specify some parameters and paths required by the operating system and other applications when they are running. Since environment variables may involve system-level security issues, browsers cannot directly access them to avoid security holes.

    In addition, the names and values ​​of environment variables used by different operating systems may also be different. Therefore, the browser cannot directly access the operating system's environment variables like Node.js.

    As an alternative, browsers provide some local storage mechanisms (such as localStorage and sessionStorage), as well as some browser extension APIs (such as Chrome's chrome.storage and Firefox's browser.storage), which developers can use to store and read Get browser-level configuration information and user settings to achieve similar functions. .

    Insert image description here

    Node.js is a server-side development platform based on JavaScript. Since it runs on the server side rather than in the browser, it can directly use the API provided by the underlying operating system to access system environment variables.

    In Node.js, environment variables are managed using the process.env property. The process object is a global object of the Node.js built-in object. It provides information about the current process and methods to control process operations. The process.env attribute is a collection of key-value pairs representing the current operating system environment variables.

    Insert image description here

    However, for web applications built using Vite, the console input console.log(process.env) can print out things:

    Insert image description here

    In the Vite development environment, it does not run directly in the browser. Instead, the code is preprocessed through Node.js and converted into a JavaScript file executable by the browser. Therefore, in the Vite development environment, you can Access system environment variables through the process object provided by Node.js.

    Many front-end frameworks (such as React and Vue.js) integrate packaging tools such as Vite and Webpack in the development environment. These packaging tools can inject environment variables into the application when compiling the code, thereby using the environment in the application. variable. These front-end frameworks generally provide their own ways to obtain environment variables, usually by reading the variables in the process.env object in the code. In the development environment, you can also print out the process.env object in the console, but this does not directly access the environment variables of the operating system, but prints out the environment variables injected in the current application. In a production environment, it is generally not recommended to expose environment variable information in the console for security reasons.

    19-4 Manually set environment variables with cross-env

    In Vite, it comes with [ Environment Variables and Mode ] configuration to help us manually set some environment variables, but these configurations are not very useful, so we can use the cross-env package to elegantly and flexibly set the environment manually. variable.

    Install dependencies:

    pnpm install cross-env
    

    At this point we can set our environment variables in package.json:

    Insert image description here

    At this time, the console prints the value of the environment variable, and you can see that the environment variable has been injected:

    Insert image description here

  20. Use vite-plugin-html to inject content into html templates

    Github address: https://github.com/vbenjs/vite-plugin-html

    Sometimes, our web pages need to make some SEO configurations, such as title, description, keywords, etc. If we want to customize these contents in the background, we need to use the vite-plugin-html plug-in to call the relevant interface to obtain the content and transfer it to the HTML file. injection. Proceed as follows:

    1. Install dependencies:

    Insert image description here

    1. At the same time, because the fetch function is defined globally in the browser environment, it can be used directly in the browser environment. However, when using the fetch function in vite.config.ts, it may not have been loaded into the browser environment, so special processing is required before it can be used in vite.config.ts. You need to use the third-party module node-fetch to make the fetch function compatible with the node.js environment, so that you can use the fetch function directly in vite.config.ts:

    Insert image description here

    1. Add the following code in the vite.config.ts file to make the fetch function compatible with the node.js environment:
    import fetch from 'node-fetch'
    (global as any).fetch = fetch
    
    1. Write an interface in vite.config.ts to call the function to get the content:
    // 接口返回数据的类型
    interface IHtmlHeadContent {
          
          
      seo: {
          
          
        title: string;
        description: string;
        keywords: string;
      };
    }
    async function getHtmlHeadContent(): Promise<IHtmlHeadContent> {
          
          
      let url = '';
      // 判断是否是生产环境
      if (process.env.NODE_ENV === 'development') {
          
          
        url = 'https://www.book-waves.com/dev/home/data.json';
      } else {
          
          
        url = 'https://www.book-waves.com/home/data.json';
      }
      const response = await fetch(url);
      const data = await response.json();
      return data as IHtmlHeadContent;
    }
    
    1. Inject into html file:
    plugins: [
      react(),
      createHtmlPlugin({
          
          
        minify: true,
        /**
         * 需要注入 index.html ejs 模版的数据
         */
        inject: {
          
          
          data: {
          
          
            title: (await getHtmlHeadContent()).seo.title,
            description: (await getHtmlHeadContent()).seo.description,
            keywords: (await getHtmlHeadContent()).seo.keywords,
          },
        },
      }),
    ],
    
    1. Get the injected content in the html file:
    <title><%- title %></title>
    <meta name="description" content="<%= description %>" />
    <meta name="keywords" content="<%= keywords %>" />
    
  21. About antd design's Form acquisition instance to set form echo

    If you now want to implement an echo requirement and set the initial values ​​of the Input tag and TextArea tag wrapped by the Form, it is not feasible to obtain the tag instance through ref and then set it as follows:

    const emailTitleRef = useRef<InputRef>(null)
    const emailMsgRef = useRef<HTMLDivElement>(null)
    
    
    <Form>
      <Form.Item label='邮件主题' name='emailTitle' rules={[{ required: true }]}>
        <Input
          placeholder='请输入邮件主题 - 注意长度和语言'
          onChange={e => setEmailTitle(e.target.value)}
          ref={emailTitleRef}
        />
      </Form.Item>
      <Form.Item label='邮件正文' name='emailContent'>
        <TextArea
          placeholder='请输入邮件正文 - 仅支持「文本」或「图片」'
          disabled={!!emailImageList[0]}
          onChange={e => setEmailContent(e.target.value)}
          ref={emailMsgRef}
        />
      </Form.Item>
      <Form.Item label=' ' name='loadImage'>
        <>
          <Button
                      icon={<UploadOutlined />}
                      type='primary'
                      disabled={!!emailContent}
                      onClick={handleUploadImage}
                    >
                      上传图片
          </Button>
          {emailImageList[0] && (
            <div className={styles.upLoad}>
              <PaperClipOutlined />
              {emailImageList[0]}
              <DeleteOutlined onClick={handleRemoveImage} />
            </div>
          )}
        </>
      </Form.Item>
    </Form>
    
    
    // 不起作用
    // emailTitleRef.current.input.defaultValue = cnTitle || enTitle
    // emailMsgRef.current.input.defaultValue = cnMsg || enMsg
    

    This is because after the Form is wrapped, the components inside become controlled components. You can only obtain the instance of the entire form through the method Form.useForm provided by Form, and then use this instance to set the value of the subitem:

    const emailFillingInstance = Form.useForm(null)[0]
    
    
    <Form form={emailFillingInstance}>
      <Form.Item label='邮件主题' name='emailTitle' rules={[{ required: true }]}>
        <Input
          placeholder='请输入邮件主题 - 注意长度和语言'
          onChange={e => setEmailTitle(e.target.value)}
        />
      </Form.Item>
      <Form.Item label='邮件正文' name='emailContent'>
        <TextArea
          placeholder='请输入邮件正文 - 仅支持「文本」或「图片」'
          disabled={!!emailImageList[0]}
          onChange={e => setEmailContent(e.target.value)}
        />
      </Form.Item>
      <Form.Item label=' ' name='loadImage'>
        <>
          <Button
            icon={<UploadOutlined />}
            type='primary'
            disabled={!!emailContent}
            onClick={handleUploadImage}
          >
            上传图片
          </Button>
          {emailImageList[0] && (
            <div className={styles.upLoad}>
              <PaperClipOutlined />
              {emailImageList[0]}
              <DeleteOutlined onClick={handleRemoveImage} />
            </div>
          )}
        </>
      </Form.Item>
    </Form>
    
    
    
    // 设置值--起作用了
    emailFillingInstance?.setFieldsValue({
       emailTitle: cnTitle || enTitle,
       emailContent: cnMsg || enMsg,
    })
    

    You can also pass generics to instances:

    const [emailFillingInstance] = Form.useForm<{ emailTitle: string; emailContent: string }>()
    
    
    <Form form={emailFillingInstance}>
      <Form.Item label='邮件主题' name='emailTitle' rules={[{ required: true }]}>
        <Input
          placeholder='请输入邮件主题 - 注意长度和语言'
          onChange={e => setEmailTitle(e.target.value)}
        />
      </Form.Item>
      <Form.Item label='邮件正文' name='emailContent'>
        <TextArea
          placeholder='请输入邮件正文 - 仅支持「文本」或「图片」'
          disabled={!!emailImageList[0]}
          onChange={e => setEmailContent(e.target.value)}
        />
      </Form.Item>
      <Form.Item label=' ' name='loadImage'>
        <>
          <Button
            icon={<UploadOutlined />}
            type='primary'
            disabled={!!emailContent}
            onClick={handleUploadImage}
          >
            上传图片
          </Button>
          {emailImageList[0] && (
            <div className={styles.upLoad}>
              <PaperClipOutlined />
              {emailImageList[0]}
              <DeleteOutlined onClick={handleRemoveImage} />
            </div>
          )}
        </>
      </Form.Item>
    </Form>
    
    
    
    // 设置值--起作用了
    emailFillingInstance?.setFieldsValue({
       emailTitle: cnTitle || enTitle,
       emailContent: cnMsg || enMsg,
    })
    

    For the situation where form items can be dynamically increased or decreased, the incoming value can be obtained through the getFieldValue method:

    const [welfareTypeInstance] = Form.useForm<{ welfareType: string[] }>()
    
    
    <Form disabled={!isWelfare || componentType === 1} form={welfareTypeInstance}>
      <Form.List name='welfareType'>
        {(fields, { add, remove }) => {
         // 获取传过来的值
          const welfareType = welfareTypeInstance.getFieldValue('welfareType')
          return (
            <>
              <Form.Item label='福利类型' name='welfareIdCheck'>
                <Checkbox
                  onChange={e => setIsWelfareId(e.target.checked)}
                  checked={isWelfareId}
                >
                  福利ID
                </Checkbox>
              </Form.Item>
              {fields.map((field, index) => (
                <Form.Item key={field.key} name={[field.name]}>
                  <>
                    <Input
                      value={welfareType[index]}
                      disabled={!isWelfareId || componentType === 1}
                      onChange={event => handleGetWelfareId(index, event.target.value)}
                    />
                    {!isWelfare || componentType === 1 ? (
                      <DeleteOutlined />
                    ) : (
                      <DeleteOutlined
                        onClick={() => {
                          remove(field.name)
                          const welfareIdList = welfareIds
                          welfareIdList.splice(index, 1)
                          setWelfareIds(welfareIdList)
                        }}
                      />
                    )}
                  </>
                </Form.Item>
              ))}
              <Form.Item name='welfareIdAdd'>
                <Button
                  onClick={() => {
                    add()
                    setWelfareIdNum(welfareIdNum + 1)
                  }}
                  disabled={!isWelfareId || componentType === 1}
                >
                  <PlusOutlined />
                </Button>
              </Form.Item>
            </>
          )
        }}
      </Form.List>
    </Form>
    
    
    // 给Form.List传值
    welfareTypeInstance?.setFieldsValue({
      welfareType: welfareIdList,
    })
    
    

Guess you like

Origin blog.csdn.net/dfc_dfc/article/details/134915937