Bladeren bron

修改裁剪

bill 2 jaren geleden
bovenliggende
commit
3e61cc22ad
4 gewijzigde bestanden met toevoegingen van 382 en 78 verwijderingen
  1. 3 0
      package.json
  2. 16 14
      pnpm-lock.yaml
  3. 268 0
      src/views/draw-file/crop.js
  4. 95 64
      src/views/draw-file/modal.tsx

+ 3 - 0
package.json

@@ -22,6 +22,7 @@
     "axios": "^0.27.2",
     "canvas-nest.js": "^2.0.4",
     "classnames": "^2.3.1",
+    "compare-versions": "6.0.0-rc.1",
     "craco-less": "^2.0.0",
     "dom-to-image": "^2.6.0",
     "icons": "link:@types/@ant-design/icons",
@@ -31,6 +32,7 @@
     "mitt": "^3.0.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "react-easy-crop": "^4.7.4",
     "react-edit-table": "0.1.1-experimental.4",
     "react-grid-layout": "^1.3.4",
     "react-input-color": "^4.0.1",
@@ -40,6 +42,7 @@
     "react-sortable-hoc": "^2.0.0",
     "redux": "^4.2.0",
     "sass": "^1.54.0",
+    "tslib": "^2.5.2",
     "typescript": "^4.7.4",
     "web-vitals": "^2.1.4"
   },

+ 16 - 14
pnpm-lock.yaml

@@ -20,6 +20,7 @@ specifiers:
   axios: ^0.27.2
   canvas-nest.js: ^2.0.4
   classnames: ^2.3.1
+  compare-versions: 6.0.0-rc.1
   craco-less: ^2.0.0
   dom-to-image: ^2.6.0
   http-proxy-middleware: ^2.0.6
@@ -30,6 +31,7 @@ specifiers:
   mitt: ^3.0.0
   react: ^18.2.0
   react-dom: ^18.2.0
+  react-easy-crop: ^4.7.4
   react-edit-table: 0.1.1-experimental.4
   react-grid-layout: ^1.3.4
   react-input-color: ^4.0.1
@@ -39,6 +41,7 @@ specifiers:
   react-sortable-hoc: ^2.0.0
   redux: ^4.2.0
   sass: ^1.54.0
+  tslib: ^2.5.2
   typescript: ^4.7.4
   web-vitals: ^2.1.4
 
@@ -62,6 +65,7 @@ dependencies:
   axios: 0.27.2
   canvas-nest.js: 2.0.4
   classnames: 2.3.2
+  compare-versions: 6.0.0-rc.1
   craco-less: 2.0.0_xgxnkve2gkwyphykbzlgtzhxmi
   dom-to-image: 2.6.0
   icons: link:@types/@ant-design/icons
@@ -71,6 +75,7 @@ dependencies:
   mitt: 3.0.0
   react: 18.2.0
   react-dom: 18.2.0_react@18.2.0
+  react-easy-crop: 4.7.4_biqbaboplfbrettd7655fr4n2y
   react-edit-table: 0.1.1-experimental.4
   react-grid-layout: 1.3.4_biqbaboplfbrettd7655fr4n2y
   react-input-color: 4.0.1_biqbaboplfbrettd7655fr4n2y
@@ -80,6 +85,7 @@ dependencies:
   react-sortable-hoc: 2.0.0_biqbaboplfbrettd7655fr4n2y
   redux: 4.2.0
   sass: 1.56.1
+  tslib: 2.5.2
   typescript: 4.9.3
   web-vitals: 2.1.4
 
@@ -3259,7 +3265,7 @@ packages:
       react: 18.2.0
       react-dom: 18.2.0_react@18.2.0
       react-easy-crop: 4.7.4_biqbaboplfbrettd7655fr4n2y
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /antd/4.24.4_biqbaboplfbrettd7655fr4n2y:
@@ -3823,7 +3829,7 @@ packages:
     resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
     dependencies:
       pascal-case: 3.1.2
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /camelcase-css/2.0.1:
@@ -4738,7 +4744,7 @@ packages:
     resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
     dependencies:
       no-case: 3.0.4
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /dotenv-expand/5.1.0:
@@ -7134,7 +7140,7 @@ packages:
     dependencies:
       copy-anything: 2.0.6
       parse-node-version: 1.0.1
-      tslib: 2.4.1
+      tslib: 2.5.2
     optionalDependencies:
       errno: 0.1.8
       graceful-fs: 4.2.10
@@ -7260,7 +7266,7 @@ packages:
   /lower-case/2.0.2:
     resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /lru-cache/6.0.0:
@@ -7488,7 +7494,7 @@ packages:
     resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
     dependencies:
       lower-case: 2.0.2
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /node-forge/1.3.1:
@@ -7741,7 +7747,7 @@ packages:
     resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
     dependencies:
       dot-case: 3.0.4
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /parent-module/1.0.1:
@@ -7779,7 +7785,7 @@ packages:
     resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
     dependencies:
       no-case: 3.0.4
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /path-exists/3.0.0:
@@ -10764,12 +10770,8 @@ packages:
     resolution: {integrity: sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==}
     dev: false
 
-  /tslib/2.4.1:
-    resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
-    dev: false
-
-  /tslib/2.5.0:
-    resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
+  /tslib/2.5.2:
+    resolution: {integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==}
     dev: false
 
   /tsutils/3.21.0_typescript@4.9.3:

File diff suppressed because it is too large
+ 268 - 0
src/views/draw-file/crop.js


+ 95 - 64
src/views/draw-file/modal.tsx

@@ -1,20 +1,21 @@
-import { Empty, Input, Modal } from 'antd'
-import { Button, Upload, message } from 'antd'
-import ImgCrop from 'antd-img-crop'
-import { useEffect, useMemo, useRef, useState } from 'react'
+import {Empty, Input, Modal} from 'antd'
+import {Button, Upload, message} from 'antd'
+import ImgCropRaw from './crop'
+import {useEffect, useMemo, useRef, useState} from 'react'
 import AMapLoader from '@amap/amap-jsapi-loader';
 import style from './style.module.scss'
-import { SceneType, SceneTypeDomain, SceneTypePaths } from 'constant';
-import { base64ToBlob, getHref, drawImage } from 'utils';
-import { asyncLoading } from 'components/loading';
-import { RedoOutlined } from '@ant-design/icons';
-import { fetchTaggings } from 'api'
-import { SortTransfer } from './sort-transfer'
-import { BoardType, BoardTypeDesc } from 'api'
-
-import type { Tagging } from 'api'
-import type { UploadProps } from 'antd'
-
+import {SceneType, SceneTypeDomain, SceneTypePaths} from 'constant';
+import {base64ToBlob, getHref, drawImage} from 'utils';
+import {asyncLoading} from 'components/loading';
+import {RedoOutlined} from '@ant-design/icons';
+import {fetchTaggings} from 'api'
+import {SortTransfer} from './sort-transfer'
+import {BoardType, BoardTypeDesc} from 'api'
+
+import type {Tagging} from 'api'
+import type {UploadProps} from 'antd'
+
+const ImgCrop = ImgCropRaw as any
 const width = 500
 
 const domScreenshot = async (dom: HTMLElement) => {
@@ -34,9 +35,9 @@ type SelectImageProps = {
 
 
 let AMap: any
-AMapLoader.load({ 
+AMapLoader.load({
   plugins: ['AMap.PlaceSearch'],
-  key: 'e661b00bdf2c44cccf71ef6070ef41b8', 
+  key: 'e661b00bdf2c44cccf71ef6070ef41b8',
   version: '2.0',
 }).then(result => AMap = result)
 
@@ -58,9 +59,9 @@ export const SelectMap = (props: SelectImageProps) => {
   }
 
   const renderInfo = info && <div className={style['def-map-info']}>
-    <p><span>经度</span>{ info.lat }</p>
-    <p><span>维度</span>{ info.lng }</p>
-    <p><span>缩放级别</span>{ info.zoom }</p>
+    <p><span>经度</span>{info.lat}</p>
+    <p><span>维度</span>{info.lng}</p>
+    <p><span>缩放级别</span>{info.zoom}</p>
   </div>
 
   useEffect(() => {
@@ -74,11 +75,11 @@ export const SelectMap = (props: SelectImageProps) => {
       resizeEnable: true
     })
     const placeSearch = new AMap.PlaceSearch({
-      pageSize: 5, 
-      pageIndex: 1, 
-      map: map, 
-      panel: searchResultEle.current, 
-      autoFitView: true 
+      pageSize: 5,
+      pageIndex: 1,
+      map: map,
+      panel: searchResultEle.current,
+      autoFitView: true
     });
     const getMapInfo = (): MapInfo => {
       var zoom = map.getZoom(); //获取当前地图级别
@@ -105,12 +106,12 @@ export const SelectMap = (props: SelectImageProps) => {
   }, [keyword, searchAMap])
 
   return (
-    <Modal 
+    <Modal
       width="588px"
-      title="选择地址" 
-      open={open} 
+      title="选择地址"
+      open={open}
       onCancel={() => setOpen(false)}
-      onOk={() => asyncLoading(onSubmit())} 
+      onOk={() => asyncLoading(onSubmit())}
       afterClose={props.onClose}
       okText="确定"
       cancelText="取消"
@@ -118,23 +119,23 @@ export const SelectMap = (props: SelectImageProps) => {
       <div className={style['search-layout']}>
         <Input.Search
           allowClear
-          placeholder="输入名称搜索" 
+          placeholder="输入名称搜索"
           onSearch={setKeyword}
-          style={{ width: 350 }} 
+          style={{width: 350}}
         />
-        <div 
-          className={`${style['search-result']} ${keyword ? style['show']: ''}`} 
-          ref={searchResultEle} 
+        <div
+          className={`${style['search-result']} ${keyword ? style['show'] : ''}`}
+          ref={searchResultEle}
         />
       </div>
       <div ref={mapEle} className={style['def-select-map']}></div>
-      { renderInfo }
+      {renderInfo}
     </Modal>
   )
 }
 
 const getFuseUrl = (caseId: number) =>
-  `${getHref(SceneTypeDomain[SceneType.SWMX]!, SceneTypePaths[SceneType.SWMX][0], { caseId: caseId.toString() })}&share=1#show/summary`
+  `${getHref(SceneTypeDomain[SceneType.SWMX]!, SceneTypePaths[SceneType.SWMX][0], {caseId: caseId.toString()})}&share=1#show/summary`
 
 
 enum ImageType {
@@ -142,10 +143,11 @@ enum ImageType {
   KANKAN,
   LASER
 }
+
 type FuseImageRet = { type: ImageType, blob: Blob | null }
 const getFuseImage = async (iframe: HTMLIFrameElement) => {
   const iframeElement = iframe.contentWindow?.document.documentElement
-  
+
   if (!iframeElement) {
     return null
   }
@@ -153,12 +155,12 @@ const getFuseImage = async (iframe: HTMLIFrameElement) => {
   const targetIframe = extIframe || iframe
   const targetWindow: any = targetIframe.contentWindow
   const fuseCnavas = targetWindow.document.querySelector('.scene-canvas > canvas') as HTMLElement
-  
+
   if (fuseCnavas) {
     const dataURL = await targetWindow.sdk.screenshot(targetIframe.offsetWidth, targetIframe.offsetHeight)
     const res = await fetch(dataURL)
     const blob = await res.blob()
-    return { type: ImageType.FUSE, blob }
+    return {type: ImageType.FUSE, blob}
     // return domScreenshot(fuseCnavas).then(blob => ({ type: ImageType.FUSE, blob }))
   }
   const isLaser = targetWindow.document.querySelector('.laser-layer')
@@ -167,14 +169,14 @@ const getFuseImage = async (iframe: HTMLIFrameElement) => {
     const sdk = await targetWindow.__sdk
     return new Promise<FuseImageRet>(resolve => {
       sdk.scene.screenshot(width, width).done((data: string) => {
-        resolve({ type: ImageType.FUSE, blob: base64ToBlob(data) })
+        resolve({type: ImageType.FUSE, blob: base64ToBlob(data)})
       })
     })
   } else {
     const sdk = targetWindow.__sdk
     return new Promise<FuseImageRet>(resolve => {
-      sdk.Camera.screenshot([ {width: width, height: width, name: '2k' }],false).then((result: any)=>{
-        resolve({ type: ImageType.KANKAN, blob: base64ToBlob(result[0].data) })
+      sdk.Camera.screenshot([{width: width, height: width, name: '2k'}], false).then((result: any) => {
+        resolve({type: ImageType.KANKAN, blob: base64ToBlob(result[0].data)})
       })
     })
   }
@@ -190,8 +192,8 @@ export const SelectFuse = (props: SelectImageProps & { caseId: number }) => {
   const coverUrl = useMemo(() => blob && URL.createObjectURL(blob), [blob])
   const url = useMemo(() => getFuseUrl(props.caseId), [props.caseId])
   const addTags = useMemo(() => addTagIds.map(id => tags.find(tag => tag.tagId.toString() === id)!), [addTagIds, tags])
-  const mockData = useMemo(() => tags.map(tag => ({ 
-    data: tag, 
+  const mockData = useMemo(() => tags.map(tag => ({
+    data: tag,
     key: tag.tagId.toString()
   })), [tags])
 
@@ -254,13 +256,13 @@ export const SelectFuse = (props: SelectImageProps & { caseId: number }) => {
           ctx.restore()
         }
       })
-      
+
       const $ccanvas = document.createElement('canvas')
       $ccanvas.width = width
       $ccanvas.height = width
       const cctx = $ccanvas.getContext('2d')!
       drawImage(
-        cctx, 
+        cctx,
         $ccanvas.width,
         $ccanvas.height,
         $canvas,
@@ -268,7 +270,7 @@ export const SelectFuse = (props: SelectImageProps & { caseId: number }) => {
         img.height,
         0, 0
       )
-      
+
       console.error('???')
       const blob = await new Promise<Blob | null>(resolve => $ccanvas.toBlob(resolve, 'png'))
       setBlob(blob)
@@ -277,23 +279,23 @@ export const SelectFuse = (props: SelectImageProps & { caseId: number }) => {
 
 
   return (
-    <Modal 
+    <Modal
       width="1500px"
-      title="选择户型图" 
-      open={open} 
+      title="选择户型图"
+      open={open}
       onCancel={() => setOpen(false)}
-      onOk={() => asyncLoading(onSubmit())} 
+      onOk={() => asyncLoading(onSubmit())}
       afterClose={props.onClose}
       okText="确定"
       cancelText="取消"
     >
       <div className={style['house-layout']}>
         <div className={style['iframe-layout']}>
-          <iframe src={url} ref={iframeRef} title="fuce-code" />
+          <iframe src={url} ref={iframeRef} title="fuce-code"/>
         </div>
         <div className={style['content-layout']}>
           <div className={style['house-tags']}>
-            <h4>请选择要同步到现场图的标注:</h4>  
+            <h4>请选择要同步到现场图的标注:</h4>
             <div className={style['tagging-transfer']}>
               <SortTransfer
                 dataSource={mockData}
@@ -305,15 +307,15 @@ export const SelectFuse = (props: SelectImageProps & { caseId: number }) => {
                 onChangeSort={tags => setAddTagIds(tags.map(tag => tag.data.tagId.toString()))}
               />
             </div>
-          </div>  
+          </div>
           <div className={style['house-image-layout']}>
-            <h4>户型图:<RedoOutlined className='icon' onClick={getCover} /></h4>
+            <h4>户型图:<RedoOutlined className='icon' onClick={getCover}/></h4>
             <div className={style['house-image']}>
               <div>
-                { coverUrl ? <img src={coverUrl} alt="预览图" /> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> }
+                {coverUrl ? <img src={coverUrl} alt="预览图"/> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
               </div>
             </div>
-          </div>  
+          </div>
         </div>
       </div>
     </Modal>
@@ -321,12 +323,12 @@ export const SelectFuse = (props: SelectImageProps & { caseId: number }) => {
 }
 
 
-
 type DfUploadCropProp = Partial<SelectImageProps> & {
   type: BoardType
   caseId: number
 }
-export const DfUploadCrop = ({ type, caseId, onClose, onSave }: DfUploadCropProp) => {
+export const DfUploadCrop = ({type, caseId, onClose, onSave}: DfUploadCropProp) => {
+  const [minZoom, setMinZoom] = useState(1)
   const onUpload: UploadProps['beforeUpload'] = async file => {
 
     const img = new Image();
@@ -349,7 +351,6 @@ export const DfUploadCrop = ({ type, caseId, onClose, onSave }: DfUploadCropProp
 
     const ext = filename.substring(filename.lastIndexOf('.'))
     const isImg = ['.png', '.jpg'].includes(ext.toLocaleLowerCase())
-    console.log(filename)
     if (!isImg) {
       message.error('只能上传png或jpg文件')
       return Upload.LIST_IGNORE
@@ -357,14 +358,44 @@ export const DfUploadCrop = ({ type, caseId, onClose, onSave }: DfUploadCropProp
       message.error('大小在100MB以内')
       return Upload.LIST_IGNORE
     } else {
-      return true
+      return new Promise(async (resolve) => {
+        const img = new Image()
+        img.src = URL.createObjectURL(file)
+        await new Promise(resolve => img.onload = resolve)
+        const size = img.width > img.height ? img.width : img.height
+        const $canvas = document.createElement('canvas')
+        $canvas.width = size
+        $canvas.height = size
+        const ctx = $canvas.getContext('2d')!
+        ctx.drawImage(
+          img,
+          (size - img.width) / 2,
+          (size - img.height) / 2,
+          img.width,
+          img.height
+        )
+        const blob = await new Promise<Blob | null>(resolve => $canvas.toBlob(resolve, 'png'))
+        if (blob) {
+          resolve(new File([blob], "test.png"))
+        } else {
+          resolve(blob)
+        }
+      })
     }
   }
 
+  console.log(minZoom)
+
   return (
-    <ImgCrop beforeCrop={beforeCrop} rotationSlider modalTitle={"裁剪" + BoardTypeDesc[type]} aspect={width / width} minZoom={1} maxZoom={5}>
-      <Upload beforeUpload={onUpload}  multiple={false} accept="png">
-          <Button type="primary" ghost block>上传{ BoardTypeDesc[type] }</Button>
+    <ImgCrop
+      beforeCrop={beforeCrop}
+      modalTitle={"裁剪" + BoardTypeDesc[type]}
+      aspect={width / width}
+      aspectSlider
+      maxZoom={5}
+      minZoom={minZoom}>
+      <Upload beforeUpload={onUpload} multiple={false} accept="png">
+        <Button type="primary" ghost block>上传{BoardTypeDesc[type]}</Button>
       </Upload>
     </ImgCrop>
   )