我正在为一家商店构建一个管理面板,并使用 HeadlessUI 使用
<Tab>
而不是页面来构建它。通过这种方法,选项卡可以正常工作,但后来我尝试为管理员添加角色,因此我使用 allowedTabs: 创建了一个对象
export const allowedTabs = {
admin: {
shop: [EShopTab.ORDERS, EShopTab.PRODUCTS, EShopTab.STATISTICS],
system: [ESystemTab.USERS, ESystemTab.ADMINS, ESystemTab.SETTINGS],
},
moderator: {
shop: [EShopTab.ORDERS, EShopTab.PRODUCTS],
system: [ESystemTab.SETTINGS],
},
accountant: {
shop: [EShopTab.STATISTICS],
system: [ESystemTab.SETTINGS],
},
analyst: {
shop: [EShopTab.STATISTICS],
system: [ESystemTab.SETTINGS],
},
};
之后我在
tabs
和 tab.panels
中映射它们:
侧边栏选项卡.tsx:
export const SidebarTabs = () => {
return (
<Tab.List className={s.tabs}>
{Object.keys(tabs).map((key, index) => (
<SidebarSection sectionKey={key as ESidebarSectionKey} key={index} />
))}
</Tab.List>
);
};
SidebarSection.tsx:
export const SidebarSection = ({ sectionKey }: TProps) => {
const { admin } = useSelector((state: RootState) => state.admins);
const t = useTranslations('Sidebar');
return (
<section className={s.sidebarSection}>
<h1 className={s.title}>{t(sectionKey)}</h1>
<section className={s.sectionLinks}>
{allowedTabs &&
allowedTabs[admin.role] &&
Object.values(allowedTabs[admin.role][sectionKey]).map((key, index) => (
<SidebarTab key={index} label={key} />
))}
</section>
</section>
);
};
侧边栏Tab.tsx:
export const SidebarTab = ({ label }: TProps) => {
const t = useTranslations('Sidebar.Tabs');
return (
<Tab as={Fragment}>
{({ selected }) => (
<button
className={cn(s.tab, {
[s.active]: selected,
})}
>
{t(label)}
</button>
)}
</Tab>
);
};
有了选项卡后,我将在另一个组件中渲染它们的面板。由于我不能只渲染 4 个选项卡和 8 个面板(因为第一个选项卡将与错误的面板相关),我还通过 allowedTabs:
进行映射工作区.tsx:
export const Workspace = () => {
const { admin } = useSelector((state: RootState) => state.admins);
return (
<section className={s.workspace}>
<Tab.Panels>
{Object.values(allowedTabs[admin.role].shop).map((key, index) => (
<Tab.Panel key={index}>{panelByAllowedTab[key]}</Tab.Panel>
))}
{Object.values(allowedTabs[admin.role].system).map((key, index) => (
<Tab.Panel key={index}>{panelByAllowedTab[key]}</Tab.Panel>
))}
</Tab.Panels>
</section>
);
};
渲染面板(考虑到@Konrad评论将所有面板更改为
OrdersTab
,问题仍然存在):
tabs.ts:
export const panelByAllowedTab = {
[EShopTab.ORDERS]: OrdersTab,
[EShopTab.PRODUCTS]: OrdersTab,
[EShopTab.STATISTICS]: OrdersTab,
[ESystemTab.USERS]: OrdersTab,
[ESystemTab.ADMINS]: OrdersTab,
[ESystemTab.SETTINGS]: OrdersTab,
};
添加订单选项卡:
export const OrdersTab = () => {
const dispatch = useDispatch();
const { orders } = useSelector((state: RootState) => state.orders);
const { products } = useSelector((state: RootState) => state.products);
const t = useTranslations('Headers.Orders');
useEffect(() => {
dispatch(fetchProducts());
dispatch(fetchOrders());
}, []);
return (
<TabLayout title="Orders">
<section className={s.content}>
<div className={s.header}>
<p className={s.orderNumber}>{t('orderNumber')}</p>
<p className={s.orderDate}>{t('date')}</p>
<p className={s.status}>{t('status')}</p>
<p className={s.orderTotal}>{t('total')}</p>
<p className={s.orderDelivery}>{t('delivery')}</p>
<p className={s.orderAddress}>{t('address')}</p>
<p className={s.orderPayment}>{t('payment')}</p>
</div>
<Button
onClick={() => {
dispatch(
createOrder({
status: EStatus.NEW,
totalprice: 0,
createdat: new Date().toISOString(),
deliverytype: EDelivery.PICKUP,
address: 'St',
paymenttype: EPayment.CARD,
contactphone: '37721732',
paymentstatus: EPaymentStatus.NOT_PAID,
filters: [
{
id: 'd8171941-f4f0-40b7-8642-19bc206d89ec',
filters: [
{
label: 'Weight',
value: '1',
extraprice: 0,
},
{
label: 'Color',
value: '#381232',
extraprice: 0,
},
],
},
],
ordernumber: `${orders.length + 1}`,
name: 'Name',
products: ['d8171941-f4f0-40b7-8642-19bc206d89ec'].map(id => {
return products.find(
product => product.id === id,
) as TProductDTO;
}),
id: crypto.randomUUID() as UUID,
}),
);
}}
>
Add order
</Button>
{orders &&
orders.length > 0 &&
orders.map(order => <OrdersCard key={order.id} id={order.id} />)}
</section>
</TabLayout>
);
};
和选项卡布局:
export const TabLayout = ({ children, title }: TProps) => {
return (
<section className={s.tab}>
<h1 className={s.title}>{title}</h1>
<Divider />
{children}
</section>
);
};
当我尝试单击我收到的另一个面板时,会呈现此选项卡和面板:
Unhandled Runtime Error
Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
这里发生了一些事情:
我不太确定,但看起来
Tab.Panel
可以接受一个函数作为子函数(Tabs
可以肯定阅读更多)
OrdersTab
,则将
<OrdersTab />
视为函数
因此
OrdersTab
可能作为函数被调用不同次数,导致 useDispatch
被调用
将
[EShopTab.ORDERS]: OrdersTab
替换为 [EShopTab.ORDERS]: <OrdersTab />
或者像这样定义
Workspace
组件:
export const Workspace = () => {
const { admin } = useSelector((state: RootState) => state.admins);
// any name, but must be capitalized
const Component = panelByAllowedTab[key]
return (
<section className={s.workspace}>
<Tab.Panels>
{Object.values(allowedTabs[admin.role].shop).map((key, index) => (
<Tab.Panel key={index}><Component /></Tab.Panel>
))}
{Object.values(allowedTabs[admin.role].system).map((key, index) => (
<Tab.Panel key={index}><Component /></Tab.Panel>
))}
</Tab.Panels>
</section>
);
};