next.js 如何使用Material-UI菜单组件处理多个菜单状态?

pgx2nnw8  于 2022-11-23  发布在  其他
关注(0)|答案(3)|浏览(183)

我有一组动态生成的下拉菜单和可折叠菜单,它们填充在呈现客户端(从数据库验证用户购买)。
我遇到了一个错误,我确信是因为我的菜单anchorEl不知道使用anchorEl打开“哪个”菜单。MUI文档并没有真正涉及多个动态菜单,所以我不确定如何管理打开的菜单
下面是一张图片,展示了我的使用案例:

正如你所看到的,被锚定的菜单实际上是最后呈现的元素。每个下载按钮都显示最后呈现的菜单。我做了研究,我想我已经把它削减到锚定元素和打开属性。
这是我的代码。请记住,数据结构按预期工作,所以我省略了它,以保持简短,因为它来自firebase,我必须在这里完全重新创建它(我认为这是多余的)。
组件:

import { useAuth } from '../contexts/AuthContext'
import { Accordion, AccordionSummary, AccordionDetails, Button, ButtonGroup, CircularProgress, ClickAwayListener, Grid, Menu, MenuItem, Typography } from '@material-ui/core'
import { ExpandMore as ExpandMoreIcon } from '@material-ui/icons'
import LoginForm from '../components/LoginForm'
import { motion } from 'framer-motion'
import { useEffect, useState } from 'react'
import { db, functions } from '../firebase'
import styles from '../styles/Account.module.scss'

export default function Account() {
  const { currentUser } = useAuth()
  const [userPurchases, setUserPurchases] = useState([])
  const [anchorEl, setAnchorEl] = useState(null)
  const [generatingURL, setGeneratingURL] = useState(false)

  function openDownloads(e) {
    setAnchorEl(prevState => (e.currentTarget))
  }

  function handleClose(e) {
    setAnchorEl(prevState => null)
  }

  function generateLink(prefix, variationChoice, pack) {
    console.log("pack from generate func", pack)
    setGeneratingURL(true)
    const variation = variationChoice ? `${variationChoice}/` : ''
    console.log('link: ', `edit-elements/${prefix}/${variation}${pack}.zip`)
    setGeneratingURL(false)
    return
    if (pack.downloads_remaining === 0) {
      console.error("No more Downloads remaining")
      setGeneratingURL(false)
      handleClose()
      return
    }
    handleClose()
    const genLink = functions.httpsCallable('generatePresignedURL')
    genLink({
      fileName: pack,
      variation: variation,
      prefix: prefix
    })
    .then(res => {
      console.log(JSON.stringify(res))
      setGeneratingURL(false)
    })
    .catch(err => {
      console.log(JSON.stringify(err))
      setGeneratingURL(false)
    })
  }

  useEffect(() => {
    if (currentUser !== null) {
      const fetchData = async () => {
      // Grab user products_owned from customers collection for user UID
      const results = await db.collection('customers').doc(currentUser.uid).get()
      .then((response) => {
        return response.data().products_owned
      })
      .catch(err => console.log(err))

        Object.entries(results).map(([product, fields]) => {
          // Grabbing each product document to get meta (title, prefix, image location, etc [so it's always current])
          const productDoc = db.collection('products').doc(product).get()
          .then(doc => {
            const data = doc.data()
            const productMeta = {
              uid: product,
              title: data.title,
              main_image: data.main_image,
              product_prefix: data.product_prefix,
              variations: data.variations
            }
            // This is where we merge the meta with the customer purchase data for each product
            setUserPurchases({
              ...userPurchases,
              [product]: {
                ...fields,
                ...productMeta
              }
            })
          })
          .catch(err => {
            console.error('Error retrieving purchases. Please refresh page to try again. Full error: ', JSON.stringify(err))  
          })
        })
      }
    return fetchData()
    }
  }, [currentUser])

  if (userPurchases.length === 0) {
    return (
      <CircularProgress />
    )
  }

  return(
    currentUser !== null && userPurchases !== null ? 
      <>
        <p>Welcome, { currentUser.displayName || currentUser.email }!</p>
        <Typography variant="h3" style={{marginBottom: '1em'}}>Purchased Products:</Typography>
        { userPurchases && Object.values(userPurchases).map((product) => {
          const purchase_date = new Date(product.purchase_date.seconds * 1000).toLocaleDateString()
          return (
            <motion.div key={product.uid}>
              <Accordion style={{backgroundColor: '#efefef'}}>
                <AccordionSummary expandIcon={<ExpandMoreIcon style={{fontSize: "calc(2vw + 10px)"}}/>} aria-controls={`${product.title} accordion panel`}>
                  <Grid container direction="row" alignItems="center">
                    <Grid item xs={3}><img src={product.main_image} style={{ height: '100%', maxHeight: "200px", width: '100%', maxWidth: '150px' }}/></Grid>
                    <Grid item xs={6}><Typography variant="h6">{product.title}</Typography></Grid>
                    <Grid item xs={3}><Typography variant="body2"><b>Purchase Date:</b><br />{purchase_date}</Typography></Grid>
                  </Grid>
                </AccordionSummary>
                <AccordionDetails style={{backgroundColor: "#e5e5e5", borderTop: 'solid 6px #5e5e5e', padding: '0px'}}>
                  <Grid container direction="column" className={styles[`product-grid`]}>
                    {Object.entries(product.packs).map(([pack, downloads]) => {
                      // The pack object right now
                      return (
                        <Grid key={ `${pack}-container` } container direction="row" alignItems="center" justify="space-between" style={{padding: '2em 1em'}}>
                          <Grid item xs={4} style={{ textTransform: 'uppercase', backgroundColor: 'transparent' }}><Typography align="left" variant="subtitle2" style={{fontSize: 'calc(.5vw + 10px)'}}>{pack}</Typography></Grid>
                          <Grid item xs={4} style={{ backgroundColor: 'transparent' }}><Typography variant="subtitle2" style={{fontSize: "calc(.4vw + 10px)"}}>{`Remaining: ${downloads.downloads_remaining}`}</Typography></Grid>
                          <Grid item xs={4} style={{ backgroundColor: 'transparent' }}>
                            <ButtonGroup variant="contained" fullWidth >
                              <Button id={`${pack}-btn`} disabled={generatingURL} onClick={openDownloads} color='primary'>
                                <Typography variant="button" style={{fontSize: "calc(.4vw + 10px)"}} >{!generatingURL ? 'Downloads' : 'Processing'}</Typography>
                              </Button>
                            </ButtonGroup>
                            <ClickAwayListener key={`${product.product_prefix}-${pack}`} mouseEvent='onMouseDown' onClickAway={handleClose}>
                              <Menu anchorOrigin={{ vertical: 'top', horizontal: 'right' }} transformOrigin={{ vertical: 'top', horizontal: 'right' }} id={`${product}-variations`} open={Boolean(anchorEl)} anchorEl={anchorEl}>
                                {product.variations && <MenuItem onClick={() => generateLink(product.product_prefix, null, pack) }>{`Pack - ${pack}`}</MenuItem>}
                                {product.variations && Object.entries(product.variations).map(([variation, link]) => {
                                  return (
                                    <MenuItem key={`${product.product_prefix}-${variation}-${pack}`} onClick={() => generateLink(product.product_prefix, link, pack)}>{ variation }</MenuItem>
                                  )
                                })}
                              </Menu>
                            </ClickAwayListener>
                          </Grid>
                        </Grid>
                      )}
                    )}
                  </Grid>
                </AccordionDetails>
              </Accordion>
            </motion.div>
          )
        }) 
      }
    </>
    :
    <>
      <p>No user Signed in</p>
      <LoginForm />
    </>
  )
}

我想这也值得一提的是,我 * 做 * 检查了呈现的HTML,正确的列表是有顺序的-它只是最后一个假设的状态。提前感谢,请让我知道,如果我错过了什么,或者如果我可以以任何方式澄清。:)

biswetbf

biswetbf1#

我无法管理动态菜单,而是使用了Collapse Panel示例,并在数组的每一项上使用属性isOpen进行操作。
Check Cards Collapse Example在setIsOpen方法上,您可以更改此布尔属性:

const setIsOpen = (argNodeId: string) => {
    const founded = tree.find(item => item.nodeId === argNodeId);
    const items = [...tree];

    if (founded) {
      const index = tree.indexOf(founded);
      founded.isOpen = !founded.isOpen;
      items[index]=founded;
      setTree(items); 
    }
 
  };

 <IconButton className={clsx(classes.expand, {
              [classes.expandOpen]: node.isOpen,
            })}
            onClick={()=>setIsOpen(node.nodeId)}
            aria-expanded={node.isOpen}
            aria-label="show more"
          >
            <MoreVertIcon />
          </IconButton>
        </CardActions>
        <Collapse in={node.isOpen} timeout="auto" unmountOnExit>
          <CardContent>
            <MenuItem onClick={handleClose}>{t("print")}</MenuItem>
            <MenuItem onClick={handleClose}>{t("commodities_management.linkContainers")}</MenuItem>
            <MenuItem onClick={handleClose}>{t("commodities_management.linkDetails")}</MenuItem>
          </CardContent>
        </Collapse>

r7xajy2e

r7xajy2e2#

我认为这是正确的解决方案:https://stackoverflow.com/a/59531513,为您呈现的每个Menu元素更改anchorEl。例如:D

qacovj5a

qacovj5a3#

这段代码属于TS react,如果你使用的是普通JS,那么就删除该类型。

import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import { useState } from 'react';
import { month } from '../../helper/Utilities';
function Company() {
  const [anchorEl, setAnchorEl] = useState<HTMLElement[]>([]);
  const handleClose = (event: any, idx: number) => {
    let array = [...anchorEl];
    array.splice(idx, 1);
    setAnchorEl(array);
  };
  <div>
    {month &&
      month.map((val: any, ind: number) => {
        return (
          <div
            key={val.id + 'w9348w344ndf allBankAndCardAccountOfClient'}
            style={{ borderColor: ind === 0 ? '#007B55' : '#919EAB52' }}
          >
            <Menu
              id='demo-positioned-menu'
              aria-labelledby='demo-positioned-button'
              anchorEl={anchorEl[ind]}
              open={anchorEl[ind] ? true : false}
              key={val.id + 'w9348w344ndf allBankAndCardAccountOfClient' + ind}
              onClick={(event) => handleClose(event, ind)}
              anchorOrigin={{
                vertical: 'top',
                horizontal: 'left',
              }}
              transformOrigin={{
                vertical: 'top',
                horizontal: 'left',
              }}
            >
              <MenuItem
                key={val.id + 'w9348w344ndf allBankAndCardAccountOfClient' + ind}
                onClick={(event) => handleClose(event, ind)}
                style={{
                  display: ind === 0 ? 'none' : 'inline-block',
                }}
              >
                <span
                  style={{
                    marginLeft: '.5em',
                    color: 'black',
                    background: 'inherit',
                  }}
                >
                  Make Primary
                </span>
              </MenuItem>

              <MenuItem onClick={(event) => handleClose(event, ind)}>
                <span style={{ marginLeft: '.5em', color: 'black' }}>Edit</span>
              </MenuItem>
              <MenuItem
                onClick={(event) => handleClose(event, ind)}
                style={{
                  display: ind === 0 ? 'none' : 'inline-block',
                }}
              >
                <span style={{ marginLeft: '.5em', color: 'red' }}>Delete</span>
              </MenuItem>
            </Menu>
          </div>
        );
      })}
  </div>;
}

export default Company;

相关问题