ResourcesCard.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import MyEmpty from '@/new-components/common/MyEmpty';
  2. import { IResource } from '@/types/app';
  3. import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
  4. import { Button, Popconfirm, Select, Typography } from 'antd';
  5. import classNames from 'classnames';
  6. import { concat } from 'lodash';
  7. import React, { useEffect, useRef, useState } from 'react';
  8. import { useTranslation } from 'react-i18next';
  9. import { v4 as uuid } from 'uuid';
  10. import { resourceTypeIcon } from '../../config';
  11. import ResourceContent from './ResourceContent';
  12. interface ResourceTabProps extends IResource {
  13. uid?: string;
  14. icon?: string;
  15. initVal?: any;
  16. name?: string;
  17. }
  18. const ResourcesCard: React.FC<{
  19. name: string;
  20. updateData: (data: any) => void;
  21. initValue?: any;
  22. resourceTypeOptions: Record<string, any>[];
  23. }> = ({ name, updateData, resourceTypeOptions, initValue }) => {
  24. const { t } = useTranslation();
  25. const resources = useRef<ResourceTabProps[]>(initValue || []);
  26. const [curIcon, setCurIcon] = useState<{ uid: string; icon: string }>({
  27. uid: '',
  28. icon: '',
  29. });
  30. const [resourcesTabs, setResourcesTabs] = useState<ResourceTabProps[]>(
  31. initValue?.map((item: any) => {
  32. return {
  33. ...item,
  34. icon: item.type,
  35. initVal: item,
  36. };
  37. }) || [],
  38. );
  39. const [filterResourcesTabs, setFilterResourcesTabs] = useState<ResourceTabProps[]>([...resourcesTabs]);
  40. const [activeKey, setActiveKey] = useState<string>(resourcesTabs?.[0]?.uid || '');
  41. const [hoverKey, setHoverKey] = useState<string>('');
  42. // 删除资源
  43. const remove = (e: any, item: any) => {
  44. e?.stopPropagation();
  45. const findActiveIndex = resources.current?.findIndex(i => i.uid === activeKey);
  46. const filteredResources = resourcesTabs?.filter(i => i.uid !== item.uid);
  47. resources.current = resources.current.filter(i => i.uid !== item.uid) || [];
  48. updateData([name, resources.current]);
  49. setResourcesTabs(filteredResources);
  50. if (findActiveIndex === resourcesTabs?.length - 1 && findActiveIndex !== 0) {
  51. setTimeout(() => {
  52. setActiveKey(filteredResources?.[filteredResources.length - 1]?.uid || '');
  53. }, 0);
  54. }
  55. setActiveKey(filteredResources?.[findActiveIndex]?.uid || '');
  56. };
  57. // 添加资源
  58. const addSource = () => {
  59. const uid = uuid();
  60. resources.current = concat(
  61. resources.current,
  62. [
  63. {
  64. is_dynamic: false,
  65. type: resourceTypeOptions?.filter(item => item.value !== 'all')?.[0].value,
  66. value: '',
  67. uid,
  68. name: t('resource') + ` ${resources.current.length + 1}`,
  69. },
  70. ].filter(Boolean),
  71. );
  72. updateData([name, resources.current]);
  73. setResourcesTabs((prev: any) => {
  74. return [
  75. ...prev,
  76. {
  77. icon: resourceTypeOptions?.filter(item => item.value !== 'all')?.[0]?.value || '',
  78. uid,
  79. initVal: {
  80. is_dynamic: false,
  81. type: resourceTypeOptions?.filter(item => item.value !== 'all')?.[0].value,
  82. value: '',
  83. uid,
  84. name: t('resource') + ` ${prev.length + 1}`,
  85. },
  86. name: t('resource') + ` ${prev.length + 1}`,
  87. },
  88. ];
  89. });
  90. setActiveKey(uid);
  91. setCurIcon({
  92. uid,
  93. icon: resourceTypeOptions?.filter(item => item.value !== 'all')?.[0].value,
  94. });
  95. };
  96. useEffect(() => {
  97. setFilterResourcesTabs([...resourcesTabs]);
  98. }, [resourcesTabs]);
  99. // 资源切换图标同步切换
  100. useEffect(() => {
  101. setResourcesTabs(
  102. resourcesTabs.map(item => {
  103. if (curIcon?.uid === item.uid) {
  104. return {
  105. ...item,
  106. icon: curIcon.icon,
  107. };
  108. }
  109. return item;
  110. }),
  111. );
  112. // eslint-disable-next-line react-hooks/exhaustive-deps
  113. }, [curIcon]);
  114. return (
  115. <div className='flex flex-1 h-64 px-3 py-4 border border-[#d6d8da] rounded-md'>
  116. <div className='flex flex-col w-40 h-full'>
  117. <Select
  118. options={resourceTypeOptions}
  119. className='w-full h-8'
  120. variant='borderless'
  121. defaultValue='all'
  122. onChange={(value: any) => {
  123. if (value === 'all') {
  124. setFilterResourcesTabs(resourcesTabs);
  125. setActiveKey(resourcesTabs?.[0]?.uid || '');
  126. } else {
  127. const newSourcesTabs = resourcesTabs?.filter(item => item?.icon === value);
  128. setActiveKey(newSourcesTabs?.[0]?.uid || '');
  129. setFilterResourcesTabs(newSourcesTabs as any);
  130. }
  131. }}
  132. />
  133. <div className='flex flex-1 flex-col gap-1 overflow-y-auto'>
  134. {filterResourcesTabs?.map(item => (
  135. <div
  136. className={classNames(
  137. 'flex h-8 items-center px-3 pl-[0.6rem] rounded-md hover:bg-[#f5faff] hover:dark:bg-[#606264] cursor-pointer relative',
  138. {
  139. 'bg-[#f5faff] dark:bg-[#606264]': item.uid === activeKey,
  140. },
  141. )}
  142. key={item.uid}
  143. onClick={() => {
  144. setActiveKey(item.uid || '');
  145. }}
  146. onMouseEnter={() => {
  147. setHoverKey(item.uid || '');
  148. }}
  149. onMouseLeave={() => {
  150. setHoverKey('');
  151. }}
  152. >
  153. {resourceTypeIcon[item.icon || '']}
  154. <Typography.Text
  155. className={classNames('flex flex-1 items-center text-sm p-0 m-0 mx-2 line-clamp-1', {
  156. 'text-[#0c75fc]': item.uid === activeKey,
  157. })}
  158. editable={{
  159. autoSize: {
  160. maxRows: 1,
  161. },
  162. onChange: v => {
  163. setResourcesTabs(
  164. resourcesTabs.map(i => {
  165. if (i.uid === item.uid) {
  166. return {
  167. ...i,
  168. name: v,
  169. };
  170. }
  171. return i;
  172. }),
  173. );
  174. resources.current = resources.current.map(i => {
  175. if (i.uid === item.uid) {
  176. return {
  177. ...i,
  178. name: v,
  179. };
  180. }
  181. return i;
  182. });
  183. updateData([name, resources.current]);
  184. },
  185. }}
  186. ellipsis={{
  187. tooltip: true,
  188. }}
  189. >
  190. {item.name}
  191. </Typography.Text>
  192. <Popconfirm
  193. title={t('want_delete')}
  194. onConfirm={e => {
  195. remove(e, item);
  196. }}
  197. onCancel={e => e?.stopPropagation()}
  198. >
  199. <DeleteOutlined
  200. className={`text-sm cursor-pointer absolute right-2 ${hoverKey === item.uid ? 'opacity-100' : 'opacity-0'}`}
  201. style={{ top: '50%', transform: 'translateY(-50%)' }}
  202. onClick={e => e.stopPropagation()}
  203. />
  204. </Popconfirm>
  205. </div>
  206. ))}
  207. </div>
  208. <Button className='w-full h-8' type='dashed' block icon={<PlusOutlined />} onClick={addSource}>
  209. {t('add_resource')}
  210. </Button>
  211. </div>
  212. <div className='flex flex-1 ml-6 '>
  213. {filterResourcesTabs && filterResourcesTabs?.length > 0 ? (
  214. <div className='flex flex-1'>
  215. {filterResourcesTabs?.map(item => (
  216. <ResourceContent
  217. key={item.uid}
  218. classNames={item.uid === activeKey ? 'block' : 'hidden'}
  219. resourceTypeOptions={resourceTypeOptions}
  220. initValue={item.initVal}
  221. setCurIcon={setCurIcon}
  222. updateData={(data: any) => {
  223. resources.current = resources.current?.map(i => {
  224. if (i?.uid === data?.uid) {
  225. return {
  226. ...i,
  227. ...data,
  228. };
  229. }
  230. return i;
  231. });
  232. updateData([name, resources.current]);
  233. }}
  234. uid={item.uid || ''}
  235. />
  236. ))}
  237. </div>
  238. ) : (
  239. <MyEmpty className='w-40 h-40' />
  240. )}
  241. </div>
  242. </div>
  243. );
  244. };
  245. export default ResourcesCard;