|
| 1 | +# react-context-menu |
| 2 | + |
| 3 | +[](https://npmjs.org/package/react-use-contextmenu) |
| 4 | +[](https://npmjs.org/package/react-use-contextmenu) |
| 5 | + |
| 6 | +## Install |
| 7 | + |
| 8 | +```bash |
| 9 | +$ pnpm install react-use-contextmenu |
| 10 | +``` |
| 11 | + |
| 12 | +## run | build |
| 13 | + |
| 14 | +```bash |
| 15 | +$ npm run dev |
| 16 | +$ npm run build |
| 17 | +``` |
| 18 | + |
| 19 | + |
| 20 | + |
| 21 | +## 为什么会有这个组件而不是用社区的 |
| 22 | + |
| 23 | +看了几款react生态下的相关插件,无法适配当前业务的复杂性。 |
| 24 | + |
| 25 | +例如: |
| 26 | + |
| 27 | + |
| 28 | +- 当菜单超出屏幕时需要做UI适配,会导致顶部和底部列表的选项无法全部展示 |
| 29 | +- 当菜单处于局部滚动条内时,当用户滚动时,菜单也应该对应的滚动,否则无法继续操作 - 未处理 |
| 30 | +- 需要适配多级菜单和动态展开,展开式也需要对UI做判断,需要适配屏幕(目前只适配了两级,多级情况太复杂了,也不知道交互要怎么搞,几乎不会出现这种情况) |
| 31 | +- 菜单禁用状态适配 |
| 32 | +- 动态生成菜单 |
| 33 | + |
| 34 | + |
| 35 | +其它社区优秀插件: [React ContextMenu](https://github.com/vkbansal/react-contextmenu/issues/339) 2020年已不维护 |
| 36 | + |
| 37 | + |
| 38 | +# ContextMenu 组件描述 |
| 39 | + |
| 40 | +用于需要对该项进行多个选项操作时。 |
| 41 | + |
| 42 | +## 基础 |
| 43 | + |
| 44 | +右键菜单分为多个部分,下面是一些相关概念。你也可以基于此实现其它相关菜单的功能 |
| 45 | + |
| 46 | + |
| 47 | +### MenuItem 数据结构 |
| 48 | + |
| 49 | +<API src="./index.tsx" hideTitle exports='["ContextMenuItem"]'></API> |
| 50 | + |
| 51 | +### DEMO |
| 52 | + |
| 53 | +```jsx |
| 54 | +import useContextMenu from 'react-use-contextmenu'; |
| 55 | +import React from 'react' |
| 56 | + |
| 57 | + |
| 58 | +export default function MyContextMenu () { |
| 59 | + const { Trigger, ContextMenu } = useContextMenu() |
| 60 | + return <div> |
| 61 | + <Trigger data={{id: '1j24iej1h2r23'}}>我是触发器,单击我会把我的data传给右键菜单的处理函数</Trigger> |
| 62 | + <ContextMenu menus={[{ label: '操作1', value: '1' }]} onClick={(e, data, menu) => { |
| 63 | + alert(`data.id: ${data.id} menu.value: ${menu.value}`) |
| 64 | + }}/> |
| 65 | + </div> |
| 66 | +} |
| 67 | + |
| 68 | +``` |
| 69 | + |
| 70 | +### useContextMenu |
| 71 | + |
| 72 | +逻辑解耦, 只负责处理处理右键和定位逻辑, 返回的Trigger和ContextMenu是包含菜单功能逻辑的菜单和触发器,用的时候只要加一些配置即可,可配置唤醒方式,单击,右键等事件,默认右键唤醒 |
| 73 | + |
| 74 | +<API src="./useContextMenu.tsx" hideTitle exports='["HooksProps"]'></API> |
| 75 | + |
| 76 | + |
| 77 | +### Trigger 触发器 |
| 78 | + |
| 79 | +用来根据hooks初始化时配置的操作事件唤醒菜单和传递参数。 |
| 80 | + |
| 81 | +`注:为了无缝接入其它组件库,所有在Trigger上的参数都会被渲染到子元素的props上`[接入Antd Table](/components/context-menu#table列表右键菜单) |
| 82 | + |
| 83 | +<API src="./useContextMenu.tsx" hideTitle exports='["TriggerProps"]'></API> |
| 84 | + |
| 85 | +### ContextMenu |
| 86 | + |
| 87 | +展示菜单UI的组件和对应的菜单处理函数。 |
| 88 | + |
| 89 | +<API src="./useContextMenu.tsx" hideTitle exports='["ContextMenuProps"]'></API> |
| 90 | + |
| 91 | +## 触发方式 |
| 92 | + |
| 93 | +可通过初始化hooks时配置触发方式来适配其它激活操作。 |
| 94 | + |
| 95 | +```tsx |
| 96 | +import useContextMenu from 'react-use-contextmenu'; |
| 97 | +import React from 'react' |
| 98 | + |
| 99 | + |
| 100 | +export default function MyContextMenu () { |
| 101 | + const { Trigger, ContextMenu } = useContextMenu({ event: 'click' }) |
| 102 | + const { Trigger: Trigger2, ContextMenu: ContextMenu2 } = useContextMenu({ event: 'mousemove' }) |
| 103 | + |
| 104 | + return <div> |
| 105 | + <Trigger data={{id: 'click'}}>单击触发</Trigger> |
| 106 | + <ContextMenu menus={[{ label: '操作1', value: '1' }]} onClick={(e, data, menu) => { |
| 107 | + alert(`data.id: ${data.id} menu.value: ${menu.value}`) |
| 108 | + }}/> |
| 109 | + <Trigger2 data={{id: 'mousemove'}} tag="span">鼠标移动触发</Trigger2> |
| 110 | + <ContextMenu2 menus={[{ label: '操作1', value: '1' }]} onClick={(e, data, menu) => { |
| 111 | + alert(`data.id: ${data.id} menu.value: ${menu.value}`) |
| 112 | + }}/> |
| 113 | + </div> |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +## 多级菜单 & 异步动态加载 |
| 118 | + |
| 119 | +```tsx |
| 120 | +import useContextMenu from 'react-use-contextmenu'; |
| 121 | +import React from 'react' |
| 122 | +import { ReloadOutlined } from '@ant-design/icons'; |
| 123 | + |
| 124 | + |
| 125 | +export default function MyContextMenu () { |
| 126 | + const { Trigger, ContextMenu } = useContextMenu() |
| 127 | + |
| 128 | + |
| 129 | + return <div> |
| 130 | + <Trigger data={{id: '123'}}>右键我</Trigger> |
| 131 | + <ContextMenu menus={[{ label: '操作1', value: '1' }, |
| 132 | + { |
| 133 | + label: '动态异步菜单', |
| 134 | + value: 'key2', |
| 135 | + icon: <ReloadOutlined />, |
| 136 | + children: () => { |
| 137 | + return new Promise((resolve, reject) => { |
| 138 | + setTimeout(() => { |
| 139 | + resolve([ |
| 140 | + { label: '操作2-1', |
| 141 | + value: 'key2-1' |
| 142 | + }, |
| 143 | + { label: '操作2-2', |
| 144 | + value: 'key2-2' |
| 145 | + }, |
| 146 | + { label: '操作2-3', |
| 147 | + value: 'key2-3' |
| 148 | + }, |
| 149 | + { label: '操作2-4', |
| 150 | + value: 'key2-4' |
| 151 | + } |
| 152 | + ]) |
| 153 | + }, 1000) |
| 154 | + }) |
| 155 | + } |
| 156 | +}, |
| 157 | + ]} onClick={(e, data, menu) => { |
| 158 | + alert(`data.id: ${data.id} menu.value: ${menu.value}`) |
| 159 | + }}/> |
| 160 | + </div> |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +## Table列表右键菜单 |
| 165 | + |
| 166 | +无缝接入ant design [Table](https://4x.ant.design/components/table-cn/#components-table-demo-edit-row)组件, 为每项加入菜单, 重写render row方法,把所有属性同步到`Trigger`即可 |
| 167 | + |
| 168 | + |
| 169 | + |
| 170 | +```tsx |
| 171 | +import React, { useState } from 'react'; |
| 172 | +import useContextMenu from 'react-use-contextmenu'; |
| 173 | +import { Button, Table } from 'antd' |
| 174 | +import { ReloadOutlined } from '@ant-design/icons'; |
| 175 | + |
| 176 | + |
| 177 | +const menus = [{ |
| 178 | + label: '操作1', |
| 179 | + value: 'key1', |
| 180 | + children: [ |
| 181 | + { label: '操作1-1', |
| 182 | + value: 'key1-1', |
| 183 | + }, |
| 184 | + { label: '操作1-2', |
| 185 | + value: 'key1-2' |
| 186 | + } |
| 187 | + ], |
| 188 | + icon: <ReloadOutlined />, |
| 189 | +}, |
| 190 | +{ |
| 191 | + label: '操作3', |
| 192 | + value: 'key3', |
| 193 | +}, |
| 194 | +{ |
| 195 | + label: '操作被禁用', |
| 196 | + value: 'key4', |
| 197 | + disabled: true, |
| 198 | +}] |
| 199 | + |
| 200 | +export default () => { |
| 201 | + const { Trigger, ContextMenu } = useContextMenu() |
| 202 | + return <div > |
| 203 | + <ContextMenu menus={menus} onClick={(e, item, menu)=> { |
| 204 | + console.log(item, menu, '----') |
| 205 | + }}/> |
| 206 | + <Table |
| 207 | + dataSource={[ |
| 208 | + { |
| 209 | + key: '1', |
| 210 | + name: '胡彦斌', |
| 211 | + age: 32, |
| 212 | + address: '西湖区湖底公园1号', |
| 213 | + }, |
| 214 | + { |
| 215 | + key: '2', |
| 216 | + name: '胡彦祖', |
| 217 | + age: 42, |
| 218 | + address: '西湖区湖底公园1号', |
| 219 | + }, |
| 220 | + ]} |
| 221 | + columns={ [ |
| 222 | + { |
| 223 | + title: '姓名', |
| 224 | + dataIndex: 'name', |
| 225 | + key: 'name', |
| 226 | + }, |
| 227 | + { |
| 228 | + title: '年龄', |
| 229 | + dataIndex: 'age', |
| 230 | + key: 'age', |
| 231 | + }, |
| 232 | + { |
| 233 | + title: '住址', |
| 234 | + dataIndex: 'address', |
| 235 | + key: 'address', |
| 236 | + }, |
| 237 | + ]} |
| 238 | + components={{ |
| 239 | + body: { |
| 240 | + row: (item) => { |
| 241 | + const { index, |
| 242 | + moveRow, |
| 243 | + className, |
| 244 | + style, |
| 245 | + ...restProps} = item; |
| 246 | + return <Trigger data={item} tag="tr" className={`${className}`} |
| 247 | + style={style} |
| 248 | + {...restProps} |
| 249 | + > |
| 250 | + </Trigger> |
| 251 | + } |
| 252 | + } |
| 253 | + }} |
| 254 | + /> |
| 255 | + </div> |
| 256 | +} |
| 257 | +``` |
| 258 | + |
| 259 | + |
| 260 | +## 自定义封装 |
| 261 | + |
| 262 | +<Alert type="error"> |
| 263 | + <span style="color: red"> |
| 264 | + 注:以下API适用于个人封装,与hooks用法无关,可不了解 |
| 265 | + </span> |
| 266 | +</Alert> |
| 267 | +<API src="./index.tsx"></API> |
| 268 | + |
0 commit comments