diff --git a/package.json b/package.json index a77aa79..8a434dc 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "antd": "^5.11.1", "antd-img-crop": "^4.17.0", "axios": "^1.6.1", + "cropperjs": "^1.6.1", "dayjs": "^1.11.10", "detect-browser": "^5.3.0", "downloadjs": "^1.4.7", @@ -27,6 +28,7 @@ "pinyin-pro": "^3.17.0", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", + "react-cropper": "^2.3.3", "react-dom": "^18.2.0", "react-router": "^6.18.0", "react-router-dom": "^6.18.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 976598a..dd7aecd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ dependencies: axios: specifier: ^1.6.1 version: 1.6.1 + cropperjs: + specifier: ^1.6.1 + version: 1.6.1 dayjs: specifier: ^1.11.10 version: 1.11.10 @@ -56,6 +59,9 @@ dependencies: react-copy-to-clipboard: specifier: ^5.1.0 version: 5.1.0(react@18.2.0) + react-cropper: + specifier: ^2.3.3 + version: 2.3.3(react@18.2.0) react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) @@ -3148,6 +3154,7 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -3157,6 +3164,7 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -3166,6 +3174,7 @@ packages: engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -3175,6 +3184,7 @@ packages: engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -4152,6 +4162,10 @@ packages: yaml: 1.10.2 dev: false + /cropperjs@1.6.1: + resolution: {integrity: sha512-F4wsi+XkDHCOMrHMYjrTEE4QBOrsHHN5/2VsVAaRq8P7E5z7xQpT75S+f/9WikmBEailas3+yo+6zPIomW+NOA==} + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -6221,6 +6235,15 @@ packages: react: 18.2.0 dev: false + /react-cropper@2.3.3(react@18.2.0): + resolution: {integrity: sha512-zghiEYkUb41kqtu+2jpX2Ntigf+Jj1dF9ew4lAobPzI2adaPE31z0p+5TcWngK6TvmWQUwK3lj4G+NDh1PDQ1w==} + peerDependencies: + react: '>=17.0.2' + dependencies: + cropperjs: 1.6.1 + react: 18.2.0 + dev: false + /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: diff --git a/src/pages/index.tsx b/src/pages/index.tsx index e8fd4bd..f9503c3 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -89,7 +89,7 @@ export default function index() { <div p-4 cursor-pointer rounded-xl decoration-none relative overflow-hidden className='z-0 card hover:bg-zinc-50 transition' key={key} onClick={()=>{jumpBefore(item.url)}}> <div className='z-20'> <div flex='~ items-start justify-between'> - <div className='w-25 h-10 bg-contain bg-cover bg-left bg-no-repeat' text='center' style={{backgroundImage: `url(${item.logo})`}} /> + <div className='w-25 h-10 bg-contain bg-contain bg-left bg-no-repeat' text='center' style={{backgroundImage: `url(${item.logo})`}} /> {item.tag? <div className='px-1 py-0.5 text-xs rounded absolute right-2 top-2' style={{backgroundColor: item.tag_color[0], color: item.tag_color[1]}}>{item.tag}</div> :'' @@ -110,7 +110,7 @@ export default function index() { {list.todo.map((item, key)=>( <div p-4 className='card hover:bg-zinc-50 transition' rounded-xl decoration-none key={key} > <div flex='~ items-start justify-between'> - <div className='w-25 h-10 bg-contain bg-cover bg-left bg-no-repeat' text='center' style={{backgroundImage: `url(${item.logo})`}} /> + <div className='w-25 h-10 bg-contain bg-contain bg-left bg-no-repeat' text='center' style={{backgroundImage: `url(${item.logo})`}} /> </div> <div className='mt-6' text='xl zinc-700'>{item.name[0]}</div> <div text='sm zinc-500'>{item.name[1]}</div> diff --git a/src/pages/tools/instagram.tsx b/src/pages/tools/instagram.tsx index fc77b79..810215a 100644 --- a/src/pages/tools/instagram.tsx +++ b/src/pages/tools/instagram.tsx @@ -83,7 +83,7 @@ export default function Instagram() { <tbody> <tr> <td className='h-full w-full relative overflow-hidden'> - <div className='h-full w-full bg-center bg-contain bg-cover bg-no-repeat overflow-hidden' style={{backgroundImage: `url(${image})`, transform: `translate(${imageX}%,${imageY}%) scale(${imageZoom-30}%)`}}> + <div className='h-full w-full bg-center bg-contain bg-cover! bg-no-repeat overflow-hidden' style={{backgroundImage: `url(${image})`, transform: `translate(${imageX}%,${imageY}%) scale(${imageZoom-30}%)`}}> </div> <div className='h-35 w-45 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2' style={{border:'20px solid #00000050'}}></div> <div className='text-[0.7rem] text-white absolute top-[1px] left-1/2 transform -translate-x-1/2'>裁剪区</div> @@ -143,7 +143,7 @@ export default function Instagram() { <div className='bg-gradient-to-rt from-[#FCCE10] via-[#FA0C65] to-[#D009B9] p-[1px] rounded-full'> <div className='bg-white rounded-full p-[1px]'> {head? - <div className='w-4.5 h-4.5 rounded-full bg-center bg-cover bg-no-repeat ' style={{backgroundImage: `url(${head})`}} />: + <div className='w-4.5 h-4.5 rounded-full bg-center bg-cover! bg-no-repeat ' style={{backgroundImage: `url(${head})`}} />: <div className='w-4.5 h-4.5 rounded-full bg-zinc-50' /> } </div> @@ -155,7 +155,7 @@ export default function Instagram() { </div> {image? <div className='h-full w-full overflow-hidden'> - <div className='h-full w-full bg-center bg-contain bg-cover bg-no-repeat overflow-hidden' style={{backgroundImage: `url(${image})`, transform: `translate(${imageX}%,${imageY}%) scale(${imageZoom}%)`}}></div> + <div className='h-full w-full bg-center bg-contain bg-cover! bg-no-repeat overflow-hidden' style={{backgroundImage: `url(${image})`, transform: `translate(${imageX}%,${imageY}%) scale(${imageZoom}%)`}}></div> </div>: <div className='w-full h-full bg-zinc-50 flex items-center justify-center text-zinc-500'>请上传图片</div> } diff --git a/src/pages/tools/rseg.tsx b/src/pages/tools/rseg.tsx index 65fbb75..1ce020f 100644 --- a/src/pages/tools/rseg.tsx +++ b/src/pages/tools/rseg.tsx @@ -1,66 +1,165 @@ -import { Button, Slider, SliderFilledTrack, SliderThumb, SliderTrack, Switch, FormLabel, FormControl } from '@chakra-ui/react' +import { + Button, + Switch, + FormLabel, + FormControl, + Drawer, + DrawerBody, + DrawerFooter, + DrawerHeader, + DrawerOverlay, + DrawerContent, + DrawerCloseButton, + useDisclosure, +} from '@chakra-ui/react' +import Cropper, { ReactCropperElement } from "react-cropper"; import HighText from "../../components/HighText"; import { useOutletContext } from "react-router-dom"; -import { ChangeEvent, RefObject, useState } from "react"; +import { ChangeEvent, RefObject, useState, useRef } from "react"; import BaseCard from "../../components/BaseCard"; -export default function Instagram() { +import "cropperjs/dist/cropper.css"; + +export default function Rseg() { + const cropperRef1 = useRef<ReactCropperElement>(null) + const cropperRef2 = useRef<ReactCropperElement>(null) + const { isOpen: isOpen1, onOpen: onOpen1, onClose: onClose1 } = useDisclosure() + const { isOpen: isOpen2, onOpen: onOpen2, onClose: onClose2 } = useDisclosure() + const [highLight, ref]:[highLight: boolean, ref: RefObject<HTMLDivElement>] = useOutletContext(); - const [image, setImage] = useState(''); - const [head, setHead] = useState(''); + const [image1, setImage1] = useState(''); + const [image2, setImage2] = useState(''); + const [image1Crop, setImage1Crop] = useState(''); + const [image2Crop, setImage2Crop] = useState(''); const [bleedingLine, setBleedingLine] = useState(false); - const [imageX , setImageX] = useState(0) - const [imageY , setImageY] = useState(0) - const [imageZoom , setImageZoom] = useState(100) - - const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => { + const handleImage1Change = (e: ChangeEvent<HTMLInputElement>) => { const file = e.target.files && e.target.files[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => { - setImage(reader.result as string); + setImage1(reader.result as string); }; reader.readAsDataURL(file); } - }; - - const handleHeadChange = (e: ChangeEvent<HTMLInputElement>) => { + onOpen1() + } + const handleImage2Change = (e: ChangeEvent<HTMLInputElement>) => { const file = e.target.files && e.target.files[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => { - setHead(reader.result as string); + setImage2(reader.result as string); }; reader.readAsDataURL(file); } - }; + onOpen2() + } + + const getCropData1 = () => { + if (typeof cropperRef1.current?.cropper !== "undefined") { + setImage1Crop(cropperRef1.current?.cropper.getCroppedCanvas().toDataURL()); + } + } + const getCropData2 = () => { + if (typeof cropperRef2.current?.cropper !== "undefined") { + setImage2Crop(cropperRef2.current?.cropper.getCroppedCanvas().toDataURL()); + } + } return ( <div> + <Drawer + isOpen={isOpen1} + placement='bottom' + onClose={onClose1} + size='md' + > + <DrawerOverlay /> + <DrawerContent> + <DrawerCloseButton /> + <DrawerHeader>裁剪图片</DrawerHeader> + + <DrawerBody className='mx-auto'> + <Cropper + src={image1} + style={{ maxHeight: '50vh', width: "80vw" }} + initialAspectRatio={1} + aspectRatio={1} + guides={true} + autoCropArea={1} + ref={cropperRef1} + /> + </DrawerBody> + + <DrawerFooter> + <Button variant='outline' mr={3} onClick={onClose1}> + 取消 + </Button> + <Button colorScheme='messenger' onClick={()=>{ + getCropData1() + onClose1() + }}>裁剪</Button> + </DrawerFooter> + </DrawerContent> + </Drawer> + <Drawer + isOpen={isOpen2} + placement='bottom' + onClose={onClose2} + size='md' + > + <DrawerOverlay /> + <DrawerContent> + <DrawerCloseButton /> + <DrawerHeader>裁剪图片</DrawerHeader> + + <DrawerBody className='mx-auto'> + <Cropper + src={image2} + style={{ maxHeight: '50vh', width: "80vw" }} + initialAspectRatio={1} + aspectRatio={1} + guides={true} + autoCropArea={1} + ref={cropperRef2} + /> + </DrawerBody> + + <DrawerFooter> + <Button variant='outline' mr={3} onClick={onClose2}> + 取消 + </Button> + <Button colorScheme='messenger' onClick={()=>{ + getCropData2() + onClose2() + }}>裁剪</Button> + </DrawerFooter> + </DrawerContent> + </Drawer> <div className='flex gap-2 mb-4'> <label className='flex-1'> - <input type="file" onChange={handleHeadChange} className='hidden'> + <input type="file" onChange={handleImage1Change} className='hidden'> </input> <Button as='span' variant='outline' colorScheme='messenger' className='w-full' - leftIcon={<div className="i-ri-account-circle-fill text-xl" />} + leftIcon={<div className="i-ri-image-add-fill text-xl" />} > - 上传头像 + 上传图片(左) </Button> </label> <label className='flex-1'> - <input type="file" onChange={handleImageChange} className='hidden'> + <input type="file" onChange={handleImage2Change} className='hidden'> </input> <Button as='span' @@ -69,59 +168,11 @@ export default function Instagram() { className='w-full' leftIcon={<div className="i-ri-image-add-fill text-xl" />} > - 上传图片 + 上传图片(右) </Button> </label> </div> <div className='mb-4'> - <div className='mb-2 text-sm text-orange-500 flex items-center justify-center'> - <div className="i-ri-information-line mr-1" /> - 请到「预览模式」调整图片以获得准确的比例 - </div> - <div className='flex items-center gap-4'> - <table className='w-48 h-40 mx-auto'> - <tbody> - <tr> - <td className='h-full w-full relative overflow-hidden'> - <div className='h-full w-full bg-center bg-contain bg-cover bg-no-repeat overflow-hidden' style={{backgroundImage: `url(${image})`, transform: `translate(${imageX}%,${imageY}%) scale(${imageZoom-30}%)`}}> - </div> - <div className='h-35 w-45 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2' style={{border:'20px solid #00000050'}}></div> - <div className='text-[0.7rem] text-white absolute top-[1px] left-1/2 transform -translate-x-1/2'>裁剪区</div> - </td> - <td className='h-full w-full'> - <Slider aria-label='slider-ex-1' isReversed className='rotate-180=' defaultValue={0} min={-100} max={100} onChange={(v)=>{setImageY(v)}} orientation='vertical' minH='100px'> - <SliderTrack> - <SliderFilledTrack /> - </SliderTrack> - <SliderThumb /> - </Slider> - </td> - </tr> - <tr> - <td className='h-full w-full'> - <Slider aria-label='slider-ex-1' defaultValue={0} min={-100} max={100} onChange={(v)=>{setImageX(v)}}> - <SliderTrack> - <SliderFilledTrack /> - </SliderTrack> - <SliderThumb /> - </Slider> - </td> - <td></td> - </tr> - </tbody> - </table> - <div className='flex-1 mb-4'> - <div className='text-xs text-center op50 mb1'>图片缩放</div> - <Slider aria-label='slider-ex-1' defaultValue={100} min={0} max={200} onChange={(v)=>{setImageZoom(v)}}> - <SliderTrack> - <SliderFilledTrack /> - </SliderTrack> - <SliderThumb /> - </Slider> - <div className='text-xs text-center op50 mb1 mt-4'>详细参数</div> - <div className='text-xs text-center op80'>X:{imageX}% Y:{imageY}% Zoom:{imageZoom}%</div> - </div> - </div> <FormControl display='flex' alignItems='center' className='justify-center text-zinc-500'> <FormLabel htmlFor='line' mb='0' fontSize='sm'> 3mm 出血线(印刷需打开) @@ -130,7 +181,7 @@ export default function Instagram() { </FormControl> </div> <BaseCard ref={ref} className='overflow-hidden'> - <div className={`min-w-[345px] max-w-[345px] z-0 relative min-h-[219px] ${highLight?'h-auto':'max-h-[219px] h-[219px]'} origin-top flex flex-col`} style={bleedingLine?{border:'12.18px dashed #FF000020'}:{}}> + <div className={`min-w-[345px] max-w-[345px] z-0 relative min-h-[219px] bg-white ${highLight?'':'max-h-[219px] h-[219px]'} origin-top flex flex-col`} style={bleedingLine?{border:'12.18px dashed #FF000020'}:{}}> <div className={`p-3 ${bleedingLine?'scale-95':'scale-100'} absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2`} style={{fontFamily: 'exo'}}> <table className='mx-auto'> <tbody> @@ -138,10 +189,10 @@ export default function Instagram() { <td></td> <td className='flex justify-between text-xs mb-1'> <div> - <HighText show={highLight} text='左侧文字' eg='TAKE YOUR MEMORY' inputed /> + <HighText show={highLight} value='TAKE YOUR MEMORY' /> </div> <div> - <HighText show={highLight} text='右侧文字' eg='PHOTOMATIC' inputed /> + <HighText show={highLight} value='PHOTOMATIC' /> </div> </td> <td></td> @@ -156,8 +207,8 @@ export default function Instagram() { </div> </td> <td className='flex gap-3'> - <div className='h-33 w-33 bg-zinc-100'></div> - <div className='h-33 w-33 bg-zinc-100'></div> + <div className='h-33 w-33 bg-center bg-contain bg-cover! bg-no-repeat overflow-hidden' style={{backgroundImage: `url(${image1Crop})`}}></div> + <div className='h-33 w-33 bg-center bg-contain bg-cover! bg-no-repeat overflow-hidden' style={{backgroundImage: `url(${image2Crop})`}}></div> </td> <td className='align-end '> <div className='justify-end align-bottom rotate-90 origin-bottom mb-15 flex items-center relative'> @@ -169,63 +220,13 @@ export default function Instagram() { </td> </tr> <tr> - {/* <td>1</td> - <td>2</td> */} <td colSpan={3} className='text-center text-2xl'> - <HighText show={highLight} text='底部文字' eg='Photomatic' inputed /> + <HighText show={highLight} value='Photomatic' /> </td> </tr> </tbody> </table> </div> - {/* <div className='flex justify-between px-2 py-1.5 bg-white'> - <img src="/images/instagram/logo.webp" alt="logo" className='w-15 h-auto' /> - <div className='flex gap-4'> - <img src="/images/instagram/like.svg" alt="" className='w-3.5' /> - <img src="/images/instagram/messenger.svg" alt="" className='w-3.5' /> - </div> - </div> - <div className='h-[1px] bg-#E8E8E8 w-full'></div> - <div className='flex px-2 py-1.5 items-center bg-white'> - <div className='bg-gradient-to-rt from-[#FCCE10] via-[#FA0C65] to-[#D009B9] p-[1px] rounded-full'> - <div className='bg-white rounded-full p-[1px]'> - {head? - <div className='w-4.5 h-4.5 rounded-full bg-center bg-cover bg-no-repeat ' style={{backgroundImage: `url(${head})`}} />: - <div className='w-4.5 h-4.5 rounded-full bg-zinc-50' /> - } - </div> - </div> - <div className='text-[0.6rem] ml-1.5 flex-1'> - <HighText show={highLight} text='昵称' eg='YuzeTT' /> - </div> - <img src="/images/instagram/more.svg" alt="more" className='w-4' /> - </div> - {image? - <div className='h-full w-full overflow-hidden'> - <div className='h-full w-full bg-center bg-contain bg-cover bg-no-repeat overflow-hidden' style={{backgroundImage: `url(${image})`, transform: `translate(${imageX}%,${imageY}%) scale(${imageZoom}%)`}}></div> - </div>: - <div className='w-full h-full bg-zinc-50 flex items-center justify-center text-zinc-500'>请上传图片</div> - } - - <div className='flex items-center px-2 py-1.5 gap-3 bg-white'> - <img src="/images/instagram/liked.svg" alt="liked" className='w-3.5' /> - <img src="/images/instagram/message.svg" alt="message" className='w-3.5' /> - <img src="/images/instagram/send.svg" alt="send" className='w-3.5' /> - <div className='flex-1'></div> - <img src="/images/instagram/collect.svg" alt="collect" className='w-3.5' /> - </div> - <div className='px-2 pb-1.5 text-[0.6rem] flex-1 bg-white'> - <HighText show={highLight} text='文案' eg='点我看群主女装' /> - </div> - <div className='w-full h-[1px] bg-#E8E8E8'></div> - <div className='flex items-center justify-between py-2 px-4 bg-white'> - <img src="/images/instagram/home.svg" alt="home" className='w-3.5' /> - <img src="/images/instagram/search.svg" alt="search" className='w-4' /> - <img src="/images/instagram/add.svg" alt="add" className='w-3.5' /> - <img src="/images/instagram/video.svg" alt="video" className='w-3.5' /> - <img src="/images/instagram/user.svg" alt="user" className='w-3.5' /> - - </div> */} </div> </BaseCard> </div>