MarkLabel.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { PromptIcon } from '@/components/bs-icons/prompt';
  2. import { Button } from '@/components/bs-ui/button';
  3. import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog";
  4. import { useTranslation } from 'react-i18next';
  5. import { CrossCircledIcon } from '@radix-ui/react-icons';
  6. import { cname } from '@/components/bs-ui/utils';
  7. import { useEffect, useState } from 'react';
  8. import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
  9. import { updateHomeLabelApi, getAllLabelsApi } from "@/controllers/API/label";
  10. import { captureAndAlertRequestErrorHoc } from '@/controllers/request';
  11. import { useToast } from '@/components/bs-ui/toast/use-toast';
  12. function DragItem({className = '', data, children, onCancel}) {
  13. return <div className={cname('h-7 w-32 relative rounded-xl border flex place-items-center border-[#666] text-[#999]', className)}>
  14. <CrossCircledIcon onClick={(e) => {e.stopPropagation(); onCancel(data.id)}}
  15. className='text-[#999] absolute top-[-6px] right-[-6px] cursor-pointer'/>
  16. <div className='bg-[#999] rounded-xl w-[26px] h-full text-center ml-[-1px]'>
  17. <span className='text-slate-50 font-bold text-sm text-[#262626]'>{data.index}</span>
  18. </div>
  19. <div className='ml-2 truncate'>
  20. {children}
  21. </div>
  22. </div>
  23. }
  24. export default function MarkLabel({open, home, onClose}) {
  25. const { t } = useTranslation()
  26. const [labels, setLabels] = useState([])
  27. const [selected, setSelected] = useState([])
  28. const { message } = useToast()
  29. useEffect(() => {
  30. async function init() {
  31. const all = await getAllLabelsApi()
  32. const newData = all.data.map(d => {
  33. const res = home.find(h => h.value === d.id)
  34. return res ? {label:d.name, value:d.id, selected:true} : {label:d.name, value:d.id, selected:false}
  35. })
  36. setLabels(newData)
  37. setSelected(home)
  38. }
  39. init()
  40. }, [home])
  41. const handleCancel = () => {
  42. onClose(false)
  43. }
  44. const handleConfirm = async () => {
  45. await captureAndAlertRequestErrorHoc(updateHomeLabelApi(selected.map(s => s.value)))
  46. onClose(false)
  47. }
  48. const handleSelect = (id) => {
  49. setLabels(pre => {
  50. const newData = pre.map(l => l.value === id ? {...l, selected:!l.selected} : l)
  51. if(newData.filter(d => d.selected).length > 10) {
  52. message({
  53. title: t('prompt'),
  54. variant: 'warning',
  55. description: '最多选择10个标签'
  56. })
  57. return pre
  58. }
  59. const select = newData.find(d => d.value === id && d.selected)
  60. setSelected(select ? [...selected, select] : pre => pre.filter(d => d.value !== id))
  61. return newData
  62. })
  63. }
  64. const handleDelete = (id) => {
  65. setSelected(pre => pre.filter(d => d.value !== id))
  66. setLabels(pre => pre.map(d => d.value === id ? {...d, selected:!d.selected} : d))
  67. }
  68. const handleDragEnd = (result) => {
  69. if(!result.destination) return
  70. const newData = selected
  71. const [moveItem] = newData.splice(result.source.index, 1)
  72. newData.splice(result.destination.index, 0, moveItem)
  73. setSelected(newData)
  74. setFlag(false)
  75. }
  76. const [flag, setFlag] = useState(false) // 解决拖拽映射位置错位
  77. return <Dialog open={open} onOpenChange={onClose}>
  78. <DialogContent className=' max-w-[70%]'>
  79. <DialogHeader>
  80. <DialogTitle className='flex items-center space-x-2'>
  81. <PromptIcon/>
  82. <span className='text-sm text-[#999]'>{t('chat.operationTips')}</span>
  83. </DialogTitle>
  84. </DialogHeader>
  85. <div className='h-[650px] w-full grid grid-cols-[70%_30%]'>
  86. <div className='ml-[10px]'>
  87. <div className='flex w-[760px] flex-wrap'>
  88. {/* <div className='w-full relative top-[30px] transform -translate-y-[50%] flex'> */}
  89. {
  90. labels.map(l =>
  91. <Button onClick={() => handleSelect(l.value)}
  92. size='sm'
  93. className={`ml-[14px] mb-[10px] w-[108px] h-[22px] flex justify-center items-center cursor-pointer ${!l.selected ? 'text-[#999999] bg-[#333333] hover:bg-[#333333]' : 'text-[#333333] bg-[#FFD54C] hover:bg-[#FFD54C]'} w-[120px]`}>
  94. <span className='truncate'>{l.label}</span>
  95. </Button>
  96. // <div onClick={() => handleSelect(l.value)} style={{borderRadius:"4px"}} className={`ml-[14px] w-[108px] h-[22px] flex justify-center items-center cursor-pointer ${!l.selected ? 'text-[#999999] bg-[#333333]' : 'text-[#333333] bg-[#FFD54C]'} w-[120px]`}>
  97. // {l.label}
  98. // </div>
  99. )
  100. }
  101. </div>
  102. </div>
  103. <div className='border-l border-[#666]'>
  104. <div className='ml-4'>
  105. <span className='text-md font-bold text-[#999]'>{t('chat.selected')}:{selected.length}/10</span>
  106. <DragDropContext onDragEnd={handleDragEnd} onDragStart={() => setFlag(true)} onDragUpdate={() => setFlag(true)}>
  107. <Droppable droppableId={'list'}>
  108. {(provided) => (
  109. <div {...provided.droppableProps} ref={provided.innerRef}>
  110. {selected.map((b,index) => (
  111. <Draggable key={'drag' + b.value} draggableId={'drag' + b.value} index={index}>
  112. {(provided) => (
  113. <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}
  114. style={flag ? { ...provided.draggableProps.style, position:'relative', left:0, top:0 } : {...provided.draggableProps.style}}>
  115. <DragItem onCancel={handleDelete} data={{index:index + 1, id:b.value}} className='mt-4 w-[170px]'>
  116. <span className='font-bold text-sm'>{b.label}</span>
  117. </DragItem>
  118. </div>
  119. )}
  120. </Draggable>
  121. ))}
  122. {provided.placeholder}
  123. </div>
  124. )}
  125. </Droppable>
  126. </DragDropContext>
  127. </div>
  128. </div>
  129. </div>
  130. <DialogFooter className='absolute bottom-6 right-6'>
  131. <Button variant="outline" className="h-10 w-[120px] px-16" onClick={handleCancel}>{t('cancel')}</Button>
  132. <Button className="px-16 h-10 w-[120px] bg-[#FFD54C] hover:bg-[#FFD54C]" onClick={handleConfirm}>{t('save')}</Button>
  133. </DialogFooter>
  134. </DialogContent>
  135. </Dialog>
  136. }