123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- 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 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'
- const domScreenshot = async (dom: HTMLElement) => {
- const canvas = (dom.tagName.toUpperCase() === 'CANVAS' ? dom : dom.querySelector('canvas')) as HTMLCanvasElement
- return new Promise<Blob | null>(resolve => {
- if (!canvas) {
- return resolve(null)
- }
- canvas.toBlob(resolve)
- })
- }
- type SelectImageProps = {
- onClose: () => void
- onSave: (url: Blob | null, tagging: Tagging[]) => void
- }
- let AMap: any
- AMapLoader.load({
- plugins: ['AMap.PlaceSearch'],
- key: 'e661b00bdf2c44cccf71ef6070ef41b8',
- version: '2.0',
- }).then(result => AMap = result)
- type MapInfo = { lat: number, lng: number, zoom: number }
- export const SelectMap = (props: SelectImageProps) => {
- const [open, setOpen] = useState(true)
- const [info, setInfo] = useState<MapInfo>()
- const [keyword, setKeyword] = useState('')
- const mapEle = useRef<HTMLDivElement>(null)
- const searchResultEle = useRef<HTMLDivElement>(null)
- const searchAMap = useRef<any>()
- const onSubmit = async () => {
- if (mapEle.current) {
- const blob = await domScreenshot(mapEle.current)
- await props.onSave(blob, [])
- setOpen(false)
- }
- }
- 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>
- </div>
- useEffect(() => {
- if (!mapEle.current) {
- return;
- }
- const map = new AMap.Map(mapEle.current, {
- WebGLParams: {
- preserveDrawingBuffer: true
- },
- resizeEnable: true
- })
- const placeSearch = new AMap.PlaceSearch({
- pageSize: 5,
- pageIndex: 1,
- map: map,
- panel: searchResultEle.current,
- autoFitView: true
- });
- const getMapInfo = (): MapInfo => {
- var zoom = map.getZoom(); //获取当前地图级别
- var center = map.getCenter();
- return {
- zoom,
- lat: center.lat,
- lng: center.lng
- }
- }
- //绑定地图移动与缩放事件
- map.on('moveend', () => setInfo(getMapInfo()));
- map.on('zoomend', () => setInfo(getMapInfo()));
- searchAMap.current = placeSearch
- return () => {
- searchAMap.current = null
- map.destroy()
- }
- }, [mapEle])
- useEffect(() => {
- keyword && searchAMap.current?.search(keyword)
- }, [keyword, searchAMap])
- return (
- <Modal
- width="588px"
- title="选择地址"
- open={open}
- onCancel={() => setOpen(false)}
- onOk={() => asyncLoading(onSubmit())}
- afterClose={props.onClose}
- okText="确定"
- cancelText="取消"
- >
- <div className={style['search-layout']}>
- <Input.Search
- allowClear
- placeholder="输入名称搜索"
- onSearch={setKeyword}
- style={{ width: 350 }}
- />
- <div
- className={`${style['search-result']} ${keyword ? style['show']: ''}`}
- ref={searchResultEle}
- />
- </div>
- <div ref={mapEle} className={style['def-select-map']}></div>
- { renderInfo }
- </Modal>
- )
- }
- const getFuseUrl = (caseId: number) =>
- `${getHref(SceneTypeDomain[SceneType.SWMX]!, SceneTypePaths[SceneType.SWMX][0], { caseId: caseId.toString() })}&share=1#show/summary`
- enum ImageType {
- FUSE,
- KANKAN,
- LASER
- }
- type FuseImageRet = { type: ImageType, blob: Blob | null }
- const getFuseImage = async (iframe: HTMLIFrameElement) => {
- const iframeElement = iframe.contentWindow?.document.documentElement
-
- if (!iframeElement) {
- return null
- }
- const extIframe = iframeElement.querySelector('.external') as 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 domScreenshot(fuseCnavas).then(blob => ({ type: ImageType.FUSE, blob }))
- }
- const isLaser = targetWindow.document.querySelector('.laser-layer')
- if (isLaser) {
- const sdk = await targetWindow.__sdk
- return new Promise<FuseImageRet>(resolve => {
- sdk.scene.screenshot(540, 390).done((data: string) => {
- resolve({ type: ImageType.FUSE, blob: base64ToBlob(data) })
- })
- })
- } else {
- const sdk = targetWindow.__sdk
- return new Promise<FuseImageRet>(resolve => {
- sdk.Camera.screenshot([ {width: 540, height: 390, name: '2k' }],false).then((result: any)=>{
- resolve({ type: ImageType.KANKAN, blob: base64ToBlob(result[0].data) })
- })
- })
- }
- }
- export const SelectFuse = (props: SelectImageProps & { caseId: number }) => {
- const [open, setOpen] = useState(true)
- const [blob, setBlob] = useState<Blob | null>(null)
- const [tags, setTags] = useState<Tagging[]>([])
- const [selectTags, setSelectTags] = useState<string[]>([])
- const [addTagIds, setAddTagIds] = useState<string[]>([])
- const iframeRef = useRef<HTMLIFrameElement>(null)
- 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,
- key: tag.tagId.toString()
- })), [tags])
- useEffect(() => {
- fetchTaggings(props.caseId.toString()).then(setTags)
- }, [props.caseId])
- const onSubmit = async () => {
- const filterTags = addTagIds.map(id => tags.find(tag => tag.tagId.toString() === id)!)
- props.onSave(blob, filterTags)
- setOpen(false)
- }
- const getCover = async () => {
- if (iframeRef.current) {
- const fuseImage = await getFuseImage(iframeRef.current)
- if (!fuseImage?.blob) {
- return;
- } else if (fuseImage.type !== ImageType.FUSE) {
- setBlob(fuseImage.blob)
- return;
- }
- const img = new Image()
- img.src = URL.createObjectURL(fuseImage.blob)
- await new Promise(resolve => img.onload = resolve)
- const $canvas = document.createElement('canvas')
- $canvas.width = img.width
- $canvas.height = img.height
- const ctx = $canvas.getContext('2d')!
- ctx.drawImage(img, 0, 0, img.width, img.height)
- const contentDoc = iframeRef.current.contentWindow!.document
- const hotItems = Array.from(contentDoc.querySelectorAll('.hot-item')) as HTMLDivElement[]
- hotItems.forEach(hot => {
- const hotTitle = (hot.querySelector('.tip') as HTMLDivElement).innerText
- const index = addTags.findIndex(tag => tag.tagTitle.trim() === hotTitle.trim())
- if (index !== -1) {
- const bound = hot.getBoundingClientRect()
- const size = (img.width / 540) * 32
- const left = bound.left + size / 2
- const top = bound.top + size / 2
- ctx.save()
- ctx.translate(left, top)
- ctx.beginPath()
- ctx.arc(0, 0, size / 2, 0, 2 * Math.PI)
- ctx.strokeStyle = '#000'
- ctx.fillStyle = '#fff'
- ctx.stroke()
- ctx.fill()
- ctx.beginPath()
- ctx.fillStyle = '#000'
- ctx.textAlign = 'center'
- ctx.textBaseline = 'middle'
- ctx.font = `normal ${size / 2}px serif`
- ctx.fillText((index + 1).toString(), 0, 0)
- ctx.restore()
- }
- })
-
- const $ccanvas = document.createElement('canvas')
- $ccanvas.width = 540
- $ccanvas.height = 390
- const cctx = $ccanvas.getContext('2d')!
- drawImage(
- cctx,
- $ccanvas.width,
- $ccanvas.height,
- $canvas,
- img.width,
- img.height,
- 0, 0
- )
-
- console.error('???')
- const blob = await new Promise<Blob | null>(resolve => $ccanvas.toBlob(resolve, 'png'))
- setBlob(blob)
- }
- }
- return (
- <Modal
- width="1500px"
- title="选择户型图"
- open={open}
- onCancel={() => setOpen(false)}
- 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" />
- </div>
- <div className={style['content-layout']}>
- <div className={style['house-tags']}>
- <h4>请选择要同步到现场图的标注:</h4>
- <div className={style['tagging-transfer']}>
- <SortTransfer
- dataSource={mockData}
- titles={['所有', '需要']}
- targetKeys={addTagIds}
- selectedKeys={selectTags}
- onChange={setAddTagIds}
- onSelectChange={(sKeys, tKeys) => setSelectTags([...sKeys, ...tKeys])}
- onChangeSort={tags => setAddTagIds(tags.map(tag => tag.data.tagId.toString()))}
- />
- </div>
- </div>
- <div className={style['house-image-layout']}>
- <h4>户型图:<RedoOutlined className='icon' onClick={getCover} /></h4>
- <div className={style['house-image']}>
- <div>
- { coverUrl ? <img src={coverUrl} alt="预览图" /> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> }
- </div>
- </div>
- </div>
- </div>
- </div>
- </Modal>
- )
- }
- type DfUploadCropProp = Partial<SelectImageProps> & {
- type: BoardType
- caseId: number
- }
- export const DfUploadCrop = ({ type, caseId, onClose, onSave }: DfUploadCropProp) => {
- const onUpload: UploadProps['beforeUpload'] = async file => {
- const filename = file.name
-
- const ext = filename.substring(filename.lastIndexOf('.'))
- const isImg = ['.png', '.jpg'].includes(ext.toLocaleLowerCase())
- if (!isImg) {
- message.error('只能上传png或jpg文件')
- return Upload.LIST_IGNORE
- } else if (file.size > 100 * 1024 * 1024) {
- message.error('大小在100MB以内')
- return Upload.LIST_IGNORE
- }
- const img = new Image();
- img.src = URL.createObjectURL(file);
- await new Promise(resolve => {
- img.onload = resolve
- })
- const $canvas = document.createElement("canvas");
- $canvas.width = 540;
- $canvas.height = 390;
- const ctx = $canvas.getContext("2d")
- ctx?.drawImage(img, 0, 0, 540, 390);
- const blob = await domScreenshot($canvas);
- onSave && onSave(blob, [])
- return Upload.LIST_IGNORE
- }
- return (
- <ImgCrop rotationSlider modalTitle={"裁剪" + BoardTypeDesc[type]} aspect={540 / 390} minZoom={1} maxZoom={5}>
- <Upload beforeUpload={onUpload} multiple={false} accept="png">
- <Button type="primary" ghost block>上传{ BoardTypeDesc[type] }</Button>
- </Upload>
- </ImgCrop>
- )
- }
|