You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
8.0 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. import Glass from '@/components/glass'
  2. import { useTranslation } from '@/i18n.ts'
  3. import { PlusOutlined } from '@ant-design/icons'
  4. import { PageContainer, ProCard } from '@ant-design/pro-components'
  5. import { Button, Form, Input, Radio, TreeSelect, InputNumber, notification, Alert, InputRef, Divider } from 'antd'
  6. import { useAtom, useAtomValue } from 'jotai'
  7. import { defaultMenu, menuDataAtom, saveOrUpdateMenuAtom, selectedMenuAtom } from '@/store/menu.ts'
  8. import IconPicker from '@/components/icon/picker'
  9. import ButtonTable from './components/ButtonTable.tsx'
  10. import { Flexbox } from 'react-layout-kit'
  11. import { DraggablePanel } from '@/components/draggable-panel'
  12. import { useStyle } from './style.ts'
  13. import { MenuItem } from '@/global'
  14. import MenuTree from './components/MenuTree.tsx'
  15. import BatchButton from '@/pages/system/menus/components/BatchButton.tsx'
  16. import { useEffect, useRef } from 'react'
  17. const Menus = () => {
  18. const { styles, cx } = useStyle()
  19. const { t } = useTranslation()
  20. const [ form ] = Form.useForm()
  21. const { mutate, isPending, error, isError } = useAtomValue(saveOrUpdateMenuAtom)
  22. const { data = [] } = useAtomValue(menuDataAtom)
  23. const [ currentMenu, setMenuData ] = useAtom<MenuItem>(selectedMenuAtom) ?? {}
  24. const menuInputRef = useRef<InputRef | undefined>(undefined)
  25. useEffect(() => {
  26. if (isError) {
  27. notification.error({
  28. message: t('message.error', '错误'),
  29. description: (error as any).message ?? t('message.saveFail', '保存失败'),
  30. })
  31. }
  32. }, [ isError ])
  33. useEffect(() => {
  34. if (currentMenu.id === 0 && menuInputRef.current) {
  35. menuInputRef.current.focus()
  36. }
  37. }, [ currentMenu ])
  38. return (
  39. <PageContainer breadcrumbRender={false} title={false} className={styles.container}>
  40. <Flexbox horizontal>
  41. <DraggablePanel expandable={false}
  42. placement="left"
  43. defaultSize={{ width: 300 }}
  44. maxWidth={500}
  45. style={{ position: 'relative' }}
  46. >
  47. <ProCard title={t('system.menus.title', '菜单')}
  48. extra={
  49. <>
  50. <BatchButton/>
  51. </>
  52. }
  53. >
  54. <MenuTree form={form}/>
  55. </ProCard>
  56. <div className={styles.treeActions}>
  57. <Divider style={{ flex: 1, margin: '8px 0' }}/>
  58. <Button style={{ flex: 1 }} size={'small'}
  59. block={true} type={'dashed'}
  60. icon={<PlusOutlined/>}
  61. onClick={() => {
  62. const menu = {
  63. ...defaultMenu,
  64. parent_id: currentMenu.id ?? 0,
  65. }
  66. setMenuData(menu)
  67. form.setFieldsValue(menu)
  68. }}
  69. >{t('actions.news')}</Button>
  70. </div>
  71. </DraggablePanel>
  72. <Flexbox className={styles.box}>
  73. <Glass
  74. enabled={currentMenu.id === undefined}
  75. description={<>
  76. <Alert
  77. message={t('message.infoTitle', '提示')}
  78. description={t('system.menus.form.empty', '请从左侧选择一行数据操作')}
  79. type="info"
  80. />
  81. </>}
  82. >
  83. <Form form={form}
  84. initialValues={currentMenu!}
  85. labelCol={{ flex: '110px' }}
  86. labelAlign="left"
  87. labelWrap
  88. wrapperCol={{ flex: 1 }}
  89. colon={false}
  90. className={cx(styles.form, {
  91. [styles.emptyForm]: currentMenu.id === undefined
  92. })}
  93. >
  94. <ProCard title={t('system.menus.setting', '配置')}
  95. className={styles.formSetting}
  96. >
  97. <Form.Item hidden={true} label={'id'} name={'id'}>
  98. <Input disabled={true}/>
  99. </Form.Item>
  100. <Form.Item
  101. rules={[
  102. { required: true, message: t('rules.required') }
  103. ]}
  104. label={t('system.menus.form.title', '菜单名称')} name={'title'}>
  105. <Input ref={menuInputRef as any} placeholder={t('system.menus.form.title', '菜单名称')}/>
  106. </Form.Item>
  107. <Form.Item label={t('system.menus.form.parent', '上级菜单')} name={'parent_id'}>
  108. <TreeSelect
  109. treeData={[
  110. { id: 0, title: '顶级菜单', children: data as any },
  111. ]}
  112. treeDefaultExpandAll={true}
  113. fieldNames={{
  114. label: 'title',
  115. value: 'id'
  116. }}/>
  117. </Form.Item>
  118. <Form.Item label={t('system.menus.form.type', '类型')} name={'type'}>
  119. <Radio.Group
  120. options={[
  121. {
  122. label: t('system.menus.form.typeOptions.menu', '菜单'),
  123. value: 'menu'
  124. },
  125. {
  126. label: t('system.menus.form.typeOptions.iframe', 'iframe'),
  127. value: 'iframe'
  128. },
  129. {
  130. label: t('system.menus.form.typeOptions.link', '外链'),
  131. value: 'link'
  132. },
  133. {
  134. label: t('system.menus.form.typeOptions.button', '按钮'),
  135. value: 'button'
  136. },
  137. ]}
  138. optionType="button"
  139. buttonStyle="solid"
  140. />
  141. </Form.Item>
  142. <Form.Item
  143. rules={[
  144. { required: true, message: t('rules.required') }
  145. ]}
  146. label={t('system.menus.form.name', '别名')} name={'name'}>
  147. <Input placeholder={t('system.menus.form.name', '别名')}/>
  148. </Form.Item>
  149. <Form.Item label={t('system.menus.form.icon', '图标')} name={'icon'}>
  150. <IconPicker placement={'left'}/>
  151. </Form.Item>
  152. <Form.Item label={t('system.menus.form.sort', '排序')} name={'sort'}>
  153. <InputNumber/>
  154. </Form.Item>
  155. <Form.Item label={t('system.menus.form.path', '路由')} name={'path'}>
  156. <Input/>
  157. </Form.Item>
  158. <Form.Item label={t('system.menus.form.component', '视图')}
  159. name={'component'}
  160. help={t('system.menus.form.componentHelp', '视图路径,相对于src/pages')}
  161. >
  162. <Input addonBefore={'pages/'}/>
  163. </Form.Item>
  164. <Form.Item label={' '}>
  165. <Button type="primary"
  166. htmlType={'submit'}
  167. loading={isPending}
  168. onClick={() => {
  169. form.validateFields().then((values) => {
  170. mutate(values)
  171. })
  172. }}
  173. >
  174. {t('system.menus.form.save', '保存')}
  175. </Button>
  176. </Form.Item>
  177. </ProCard>
  178. <ProCard title={t('system.menus.button', '按钮')}
  179. className={styles.formButtons}
  180. colSpan={8}>
  181. <Form.Item noStyle={true} name={'button'}
  182. shouldUpdate={(prevValues: MenuItem, curValues) => {
  183. return prevValues.id !== curValues.id
  184. }}>
  185. <ButtonTable form={form} key={(currentMenu as any).id}/>
  186. </Form.Item>
  187. </ProCard>
  188. </Form>
  189. </Glass>
  190. </Flexbox>
  191. </Flexbox>
  192. </PageContainer>
  193. )
  194. }
  195. export default Menus