├── src ├── assets │ ├── font │ │ └── font.less │ └── images │ │ └── page.png ├── component │ ├── images │ │ └── example.png │ ├── image-map │ │ ├── index.scss │ │ ├── helper.ts │ │ ├── style.ts │ │ ├── index.d.ts │ │ ├── index.tsx │ │ └── __test__ │ │ │ ├── __snapshots__ │ │ │ └── index.test.js.snap │ │ │ └── index.test.js │ └── index.tsx ├── container │ ├── images-map │ │ ├── images │ │ │ └── example.png │ │ ├── index.scss │ │ └── index.tsx │ └── index.tsx ├── setupTests.js ├── setupProxy.js ├── common │ ├── index.tsx │ ├── global-data.ts │ ├── detect-os.tsx │ ├── is-type.tsx │ ├── local-storage.ts │ └── utils.tsx ├── index.scss ├── router.tsx ├── react-app-env.d.ts ├── index.tsx └── serviceWorker.ts ├── .eslintignore ├── .npmrc ├── public ├── favicon.ico ├── manifest.json └── index.html ├── lib ├── index.js ├── index.d.ts └── react-image-map.js ├── .babelrc ├── config ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── pnpTs.js ├── modules.js ├── paths.js ├── env.js ├── webpackDevServer.config.js ├── webpack.npm.config.js └── webpack.config.js ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ └── main.yml ├── .travis.yml.bak ├── scripts ├── deploy.sh ├── build_lib.sh ├── test.js ├── start.js ├── build-lib.js └── build.js ├── .cz-config.js ├── .prettierrc.js ├── README-CN.md ├── README.md └── package.json /src/assets/font/font.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | config 2 | lib 3 | scripts -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuziz/react-image-map/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var ImageMap = require('./react-image-map.js'); 2 | module.exports = { ImageMap: ImageMap }; 3 | -------------------------------------------------------------------------------- /src/assets/images/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuziz/react-image-map/HEAD/src/assets/images/page.png -------------------------------------------------------------------------------- /src/component/images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuziz/react-image-map/HEAD/src/component/images/example.png -------------------------------------------------------------------------------- /src/container/images-map/images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuziz/react-image-map/HEAD/src/container/images-map/images/example.png -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | configure({ adapter: new Adapter() }); -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react-app" 4 | ], 5 | "plugins": [ 6 | ["import", { "libraryName": "antd-mobile", "style": "css"}, "antd-mobile"] 7 | ] 8 | } -------------------------------------------------------------------------------- /src/component/image-map/index.scss: -------------------------------------------------------------------------------- 1 | .image-map__content { 2 | position: relative; 3 | width: 100%; 4 | &__img { 5 | width: 100%; 6 | user-select: none; 7 | } 8 | .image-map__map { 9 | position: absolute; 10 | } 11 | } -------------------------------------------------------------------------------- /src/container/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2019-05-09 14:40:05 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2019-11-26 17:16:05 7 | */ 8 | 9 | export * from './images-map'; 10 | -------------------------------------------------------------------------------- /src/component/image-map/helper.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2021-04-09 14:43:13 5 | * @Last Modified by: qiuz 6 | */ 7 | 8 | export const isFunction = (value: any) => { 9 | return typeof value === 'function'; 10 | }; -------------------------------------------------------------------------------- /src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const proxy = require('http-proxy-middleware'); 2 | module.exports = function (app) { 3 | app.use( 4 | '/api', 5 | proxy({ 6 | target: 'https://api.weixin.qq.com', 7 | pathRewrite: { 8 | "^/api": "" 9 | }, 10 | changeOrigin: true, 11 | }) 12 | ); 13 | }; -------------------------------------------------------------------------------- /src/component/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2019-11-01 13:34:12 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2021-04-09 14:29:35 7 | */ 8 | export { ImageMap } from './image-map'; 9 | 10 | export type { Area } from './image-map/index.d'; 11 | 12 | -------------------------------------------------------------------------------- /src/common/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2019-05-09 14:30:34 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2019-11-27 21:38:33 7 | */ 8 | 9 | export * from './detect-os'; 10 | export * from './is-type'; 11 | export * from './local-storage'; 12 | export * from './utils'; 13 | export * from './global-data'; 14 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/component/image-map/style.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2022-04-16 14:55:15 5 | * @Last Modified by: qiuz 6 | */ 7 | 8 | export default { 9 | content: { 10 | position: 'relative', 11 | width: '100%' 12 | }, 13 | 14 | img: { 15 | width: '100%', 16 | userSelect: 'none' 17 | }, 18 | 19 | map: { 20 | position: 'absolute' 21 | } 22 | } as Record; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | build.zip 14 | *.zip 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | cache* -------------------------------------------------------------------------------- /src/common/global-data.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2020-06-01 19:13:52 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2020-08-11 12:21:44 7 | */ 8 | 9 | const globalData: any = {}; 10 | 11 | const setGlobalData = (key: string, val: any) => { 12 | globalData[key] = val; 13 | }; 14 | 15 | const getGlobalData = (key: string) => { 16 | return globalData[key]; 17 | }; 18 | 19 | export { setGlobalData, getGlobalData }; 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "baseUrl": "src", 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "preserve", 18 | "experimentalDecorators": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["config", "**__test__**"] 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - deploy 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v1 16 | 17 | - name: Build and Deploy 18 | uses: JamesIves/github-pages-deploy-action@releases/v2 19 | env: 20 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 21 | BRANCH: gh-pages 22 | FOLDER: build 23 | BUILD_SCRIPT: npm install && npm run build 24 | 25 | - name: delete deploy branch 26 | run: git push --delete "https://${{ secrets.ACCESS_TOKEN }}@${{ secrets.GIT_REF }}" deploy 27 | -------------------------------------------------------------------------------- /config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2019-11-27 22:05:04 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2021-05-07 09:34:26 7 | */ 8 | 9 | import * as React from 'react'; 10 | 11 | export interface Area 12 | extends React.DetailedHTMLProps, HTMLSpanElement> { 13 | left: string; 14 | top: string; 15 | height: string; 16 | width: string; 17 | style?: React.CSSProperties; 18 | render?: (area: Area, index: number) => React.ReactNode; 19 | } 20 | 21 | export interface ImageMapProps 22 | extends React.DetailedHTMLProps, HTMLImageElement> { 23 | className?: string; 24 | src: string; 25 | map?: Area[]; 26 | onClick?: () => void; 27 | onMapClick?: (area: Area, index: number) => void; 28 | } 29 | 30 | export class ImageMap extends React.Component {} 31 | -------------------------------------------------------------------------------- /src/component/image-map/index.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2019-11-27 22:05:04 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2021-05-07 09:34:26 7 | */ 8 | 9 | import * as React from 'react'; 10 | 11 | export interface Area 12 | extends React.DetailedHTMLProps, HTMLSpanElement> { 13 | left: string; 14 | top: string; 15 | height: string; 16 | width: string; 17 | style?: React.CSSProperties; 18 | render?: (area: Area, index: number) => React.ReactNode; 19 | } 20 | 21 | export interface ImageMapProps 22 | extends React.DetailedHTMLProps, HTMLImageElement> { 23 | className?: string; 24 | src: string; 25 | map?: Area[]; 26 | onClick?: () => void; 27 | onMapClick?: (area: Area, index: number) => void; 28 | } 29 | 30 | export class ImageMap extends React.Component {} 31 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | touch-action: manipulation; 5 | font-family: PingFangSC-Regular; 6 | } 7 | iframe { 8 | display: none; 9 | } 10 | html, body { 11 | width: 100%; 12 | height: 100%; 13 | } 14 | body { 15 | background-color: #f7f7f7; 16 | padding-top: constant(safe-area-inset-top); 17 | padding-left: constant(safe-area-inset-left); 18 | padding-right: constant(safe-area-inset-right); 19 | padding-bottom: constant(safe-area-inset-bottom); 20 | } 21 | 22 | #root { 23 | height: 100%; 24 | } 25 | 26 | li { 27 | list-style: none; 28 | } 29 | 30 | input:focus { 31 | outline-offset: 0; 32 | outline: none; 33 | } 34 | 35 | button { 36 | display: inline-block; 37 | cursor: pointer; 38 | text-decoration: none; 39 | outline: 0; 40 | border-radius: 6px; 41 | border: 1px solid #ddd; 42 | background-color: #fff; 43 | padding: 10px; 44 | } 45 | 46 | textarea { 47 | &:focus { 48 | outline: none; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.travis.yml.bak: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "10" 5 | 6 | branches: 7 | only: 8 | - deploy 9 | # 缓存依赖 10 | cache: yarn 11 | # before_install: 12 | # - if [ ${TRAVIS_BRANCH} == "master" ] && [ -z "$TRAVIS_TAG" ]; then 13 | # exit 0; 14 | # fi 15 | # - export TZ='Asia/Shanghai' # 更改时区 16 | before_install: 17 | - export TZ='Asia/Shanghai' # 更改时区 18 | install: 19 | - yarn install 20 | 21 | script: 22 | - yarn build; 23 | 24 | # GitHub Pages 部署 25 | deploy: 26 | - provider: pages 27 | skip_cleanup: true 28 | # 在项目仪表盘的 Settings -> Environment Variables 中配置 29 | github_token: $GITHUB_TOKEN 30 | # 将 build 目录下的内容推送到默认的 gh-pages 分支上,并不会连带 build 目录一起 31 | local_dir: build 32 | #fqdn: $CUSTOM_DOMAIN 33 | name: $GIT_NAME 34 | email: $GIT_EMAIL 35 | script: 36 | - yarn build 37 | on: 38 | branch: deploy 39 | after_success: 40 | - git push --delete "https://${GITHUB_TOKEN}@${GIT_REF}" deploy 41 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DEPLOY_BRANCH='deploy'; 6 | 7 | function build_deploy() { 8 | if [[ -n "$(git status --porcelain)" ]]; then 9 | echo "Working tree *NOT* clean. Please stash/commit your changes before any operations." 10 | exit 1 11 | fi 12 | 13 | current_branch="$(git symbolic-ref --short -q HEAD)" 14 | 15 | if [[ -n "$(git rev-parse --verify --quiet $DEPLOY_BRANCH)" ]]; then 16 | git branch -D $DEPLOY_BRANCH 17 | fi 18 | 19 | git checkout -b $DEPLOY_BRANCH 20 | 21 | git push -f origin $DEPLOY_BRANCH:$DEPLOY_BRANCH 22 | 23 | git checkout $current_branch 24 | 25 | git branch -D $DEPLOY_BRANCH 26 | 27 | 28 | } 29 | 30 | # for branch in $(git for-each-ref --format='%(refname)' refs/remotes/); do 31 | # if [[ $branch =~ "deploy" ]];then 32 | # git push --delete origin ${branch#*refs/remotes/origin/} 33 | # echo $branch 34 | # fi 35 | # done 36 | 37 | # 解决远程分支删除后还是能看到 38 | git remote show origin 39 | 40 | git remote prune origin 41 | 42 | 43 | build_deploy 44 | 45 | -------------------------------------------------------------------------------- /.cz-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 5 | types: [ 6 | { 7 | value: 'feat', 8 | name : 'feat: A new feature' 9 | }, 10 | { 11 | value: 'fix', 12 | name : 'fix: A bug fix' 13 | }, 14 | { 15 | value: 'refactor', 16 | name : 'refactor: A code change that neither fixes a bug nor adds a feature' 17 | }, 18 | { 19 | value: 'docs', 20 | name : 'docs: Documentation only changes' 21 | }, 22 | { 23 | value: 'test', 24 | name : 'test: Add missing tests or correcting existing tests' 25 | }, 26 | { 27 | value: 'chore', 28 | name : 'chore: Changes that don\'t modify src or test files. Such as updating build tasks, package manager' 29 | }, 30 | { 31 | value: 'style', 32 | name : 'style: Code Style, Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)' 33 | }, 34 | { 35 | value: 'revert', 36 | name : 'revert: Revert to a commit' 37 | } 38 | ], 39 | 40 | scopes: [], 41 | 42 | allowCustomScopes: true, 43 | allowBreakingChanges: ["feat", "fix"] 44 | }; -------------------------------------------------------------------------------- /scripts/build_lib.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | function build() { 6 | DEV=$1 7 | 8 | node scripts/build-lib.js 9 | 10 | BASE=lib 11 | 12 | cp -rf src/component/image-map/index.d.ts $BASE 13 | 14 | # create build/index.js 15 | cat > $BASE/index.js <<- EOT 16 | var ImageMap = require('./react-image-map.js'); 17 | module.exports = { ImageMap: ImageMap }; 18 | EOT 19 | 20 | if [[ $DEV = 'dev' ]]; then 21 | cp -rf $BASE node_modules/@qiuz/react-image-map 22 | fi 23 | } 24 | 25 | 26 | function buildProd() { 27 | version=$1 28 | 29 | VERSION=`npm version ${version:-patch}` 30 | 31 | build 32 | 33 | # git add . 34 | # git commit -m '**ImageMap npm build**' 35 | 36 | # npm login 37 | 38 | npm publish . --tag latest 39 | 40 | yarn add @qiuz/react-image-map@${VERSION#*v} -D 41 | 42 | git commit -a -m '**Update package.json react-image-map version**' 43 | 44 | git push 45 | 46 | yarn pages 47 | } 48 | 49 | if [[ $BUILD_ENV = 'prod' ]]; then 50 | npm who am i 51 | buildProd $@ 52 | else 53 | build $@ 54 | fi 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/router.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2019-11-07 16:03:31 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2021-01-24 23:18:15 7 | */ 8 | 9 | import { getGlobalData } from 'common'; 10 | import * as React from 'react'; 11 | import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom'; 12 | 13 | import { ImagesMap } from './container'; 14 | 15 | export const routes = [ 16 | { 17 | path: '/react-image-map', 18 | Component: ImagesMap, 19 | exact: true 20 | } 21 | ]; 22 | 23 | const App = () => { 24 | const prefix = getGlobalData('PREFIX'); 25 | return ( 26 | 27 | {routes.map(({ path, Component, exact }: any, index) => { 28 | return ( 29 | } 34 | /> 35 | ); 36 | })} 37 | 38 | 39 | ); 40 | }; 41 | 42 | // react-router4 不再推荐将所有路由规则放在同一个地方集中式路由,子路由应该由父组件动态配置,组件在哪里匹配就在哪里渲染,更加灵活 43 | export default class RouteConfig extends React.Component { 44 | render() { 45 | return ( 46 | 47 | 48 | 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFileName = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFileName}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // printWidth: 120, //一行的字符数,如果超过会进行换行,默认为80 3 | // tabWidth: 2, //一个tab代表几个空格数,默认为2 4 | // semi: true, // 句末加分号 5 | // singleQuote: true, // 用单引号 6 | // proseWrap: 'preserve', // 是否要换行 7 | // trailingComma: false, // 最后一个对象元素加逗号 8 | // bracketSpacing: true, // 对象,数组加空格 9 | // jsxBracketSameLine: true, // jsx format 10 | // quoteProps: 'as-needed', // 对象字面量中的属性名是否强制双引号 11 | // arrowParens: 'avoid' // 箭头函数参数只有一个时是否要有小括号 12 | // 一行最多 100 字符 13 | printWidth: 100, 14 | // 使用 4 个空格缩进 15 | tabWidth: 2, 16 | // 不使用缩进符,而使用空格 17 | useTabs: false, 18 | // 行尾需要有分号 19 | semi: true, 20 | // 使用单引号 21 | singleQuote: true, 22 | // 对象的 key 仅在必要时用引号 23 | quoteProps: 'as-needed', 24 | // jsx 不使用单引号,而使用双引号 25 | jsxSingleQuote: false, 26 | // 末尾不需要逗号 27 | trailingComma: 'none', 28 | // 大括号内的首尾需要空格 29 | bracketSpacing: true, 30 | // jsx 标签的反尖括号需要换行 31 | jsxBracketSameLine: false, 32 | // 箭头函数,只有一个参数的时候,也需要括号 33 | arrowParens: 'always', 34 | // 每个文件格式化的范围是文件的全部内容 35 | rangeStart: 0, 36 | rangeEnd: Infinity, 37 | // 不需要写文件开头的 @prettier 38 | requirePragma: false, 39 | // 不需要自动在文件开头插入 @prettier 40 | insertPragma: false, 41 | // 使用默认的折行标准 42 | proseWrap: 'preserve', 43 | // 根据显示样式决定 html 要不要折行 44 | htmlWhitespaceSensitivity: 'css', 45 | // 换行符使用 lf 46 | endOfLine: 'lf' 47 | }; 48 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 45 | ) { 46 | // https://github.com/facebook/create-react-app/issues/5210 47 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 48 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 49 | } 50 | 51 | 52 | jest.run(argv); 53 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare namespace NodeJS { 6 | interface ProcessEnv { 7 | readonly NODE_ENV: 'development' | 'production' | 'test'; 8 | readonly PUBLIC_URL: string; 9 | } 10 | } 11 | 12 | declare module '*.bmp' { 13 | const src: string; 14 | export default src; 15 | } 16 | 17 | declare module '*.gif' { 18 | const src: string; 19 | export default src; 20 | } 21 | 22 | declare module '*.jpg' { 23 | const src: string; 24 | export default src; 25 | } 26 | 27 | declare module '*.jpeg' { 28 | const src: string; 29 | export default src; 30 | } 31 | 32 | declare module '*.png' { 33 | const src: string; 34 | export default src; 35 | } 36 | 37 | declare module '*.webp' { 38 | const src: string; 39 | export default src; 40 | } 41 | 42 | declare module '*.svg' { 43 | import * as React from 'react'; 44 | 45 | export const ReactComponent: React.FunctionComponent>; 46 | 47 | const src: string; 48 | export default src; 49 | } 50 | 51 | declare module '*.module.css' { 52 | const classes: { [key: string]: string }; 53 | export default classes; 54 | } 55 | 56 | declare module '*.module.scss' { 57 | const classes: { [key: string]: string }; 58 | export default classes; 59 | } 60 | 61 | declare module '*.module.sass' { 62 | const classes: { [key: string]: string }; 63 | export default classes; 64 | } 65 | 66 | declare module '*.mp4' { 67 | const src: string; 68 | export default src; 69 | } 70 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.scss'; 4 | import * as serviceWorker from './serviceWorker'; 5 | 6 | import Router from './router'; 7 | import { setGlobalData } from 'common'; 8 | import { message } from 'antd'; 9 | 10 | declare const window: any; 11 | declare let __webpack_public_path__: any; 12 | 13 | 14 | 15 | setGlobalData('PREFIX', ''); 16 | 17 | /** 18 | * @see https://qiankun.umijs.org/zh/faq#a-%E4%BD%BF%E7%94%A8-webpack-%E8%BF%90%E8%A1%8C%E6%97%B6-publicpath-%E9%85%8D%E7%BD%AE 19 | * runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、图片 等地址不正确的问题。 20 | */ 21 | if (window.__POWERED_BY_QIANKUN__) { 22 | setGlobalData('PREFIX', '/q'); 23 | // eslint-disable-next-line no-unused-vars 24 | __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; 25 | } 26 | 27 | function render(props: any) { 28 | const { container } = props; 29 | message.config({ 30 | getContainer: () => (container || document).querySelector('#root') 31 | }); 32 | ReactDOM.render(, (container || document).querySelector('#root')); 33 | } 34 | 35 | if (!window.__POWERED_BY_QIANKUN__) { 36 | render({}); 37 | } 38 | 39 | export async function bootstrap() {} 40 | 41 | export async function mount(props: any) { 42 | render(props); 43 | } 44 | 45 | export async function unmount(props: any) { 46 | const { container } = props; 47 | ReactDOM.unmountComponentAtNode((container || document).querySelector('#root')); 48 | } 49 | 50 | // If you want your app to work offline and load faster, you can change 51 | // unregister() to register() below. Note this comes with some pitfalls. 52 | // Learn more about service workers: https://bit.ly/CRA-PWA 53 | serviceWorker.unregister(); 54 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | React Image Map 24 | 25 | 26 | 27 |
28 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/component/image-map/index.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Date: 2019-11-01 14:38:25 4 | * @Last Modified by: qiuz 5 | * @Last Modified time: 2022-04-16 15:04:22 6 | */ 7 | 8 | import React from 'react'; 9 | import Style from './style'; 10 | 11 | import { ImageMapProps, Area } from './index.d'; 12 | import { isFunction } from './helper'; 13 | 14 | export const ImageMap = (props: ImageMapProps) => { 15 | const { 16 | className = '', 17 | src = '', 18 | map = [], 19 | onMapClick = (area: Area, index: number) => {}, 20 | onClick = () => {}, 21 | style = {}, 22 | ...restProps 23 | } = props; 24 | 25 | const mapClick = (area: Area, index: number) => () => { 26 | onMapClick(area, index); 27 | }; 28 | 29 | return ( 30 |
31 | 39 | {map.map((area: Area, index: number) => { 40 | const { 41 | width = 0, 42 | height = 0, 43 | left = 0, 44 | top = 0, 45 | style = {}, 46 | render, 47 | ...restMapProps 48 | } = area; 49 | return ( 50 | 57 | {render && isFunction(render) && render(area, index)} 58 | 59 | ); 60 | })} 61 |
62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /src/component/image-map/__test__/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ImagePicker Test click event 1`] = `ReactWrapper {}`; 4 | 5 | exports[`ImagePicker renders correctly 1`] = ` 6 | initialize { 7 | "0": Object { 8 | "attribs": Object { 9 | "class": "image-map__content ", 10 | }, 11 | "children": Array [ 12 | Object { 13 | "attribs": Object { 14 | "alt": "", 15 | "src": "", 16 | }, 17 | "children": Array [], 18 | "name": "img", 19 | "namespace": "http://www.w3.org/1999/xhtml", 20 | "next": null, 21 | "parent": [Circular], 22 | "prev": null, 23 | "type": "tag", 24 | "x-attribsNamespace": Object { 25 | "alt": undefined, 26 | "src": undefined, 27 | }, 28 | "x-attribsPrefix": Object { 29 | "alt": undefined, 30 | "src": undefined, 31 | }, 32 | }, 33 | ], 34 | "name": "div", 35 | "namespace": "http://www.w3.org/1999/xhtml", 36 | "next": null, 37 | "parent": null, 38 | "prev": null, 39 | "root": Object { 40 | "attribs": Object {}, 41 | "children": Array [ 42 | [Circular], 43 | ], 44 | "name": "root", 45 | "namespace": "http://www.w3.org/1999/xhtml", 46 | "next": null, 47 | "parent": null, 48 | "prev": null, 49 | "type": "root", 50 | "x-attribsNamespace": Object {}, 51 | "x-attribsPrefix": Object {}, 52 | }, 53 | "type": "tag", 54 | "x-attribsNamespace": Object { 55 | "class": undefined, 56 | }, 57 | "x-attribsPrefix": Object { 58 | "class": undefined, 59 | }, 60 | }, 61 | "_root": [Circular], 62 | "length": 1, 63 | "options": Object { 64 | "decodeEntities": true, 65 | "normalizeWhitespace": false, 66 | "withDomLvl1": true, 67 | "xml": false, 68 | }, 69 | } 70 | `; 71 | -------------------------------------------------------------------------------- /src/component/image-map/__test__/index.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2019-11-28 10:30:01 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2019-11-28 13:30:59 7 | */ 8 | 9 | import * as React from 'react'; 10 | import { render, mount } from 'enzyme'; 11 | import { ImageMap } from '../index'; 12 | import EXAMPLE from '../../images/example.png'; 13 | 14 | describe('ImagePicker', () => { 15 | it('renders correctly', () => { 16 | const renderDom = render( 17 | , 18 | ); 19 | 20 | expect(renderDom).toMatchSnapshot(); 21 | }); 22 | 23 | it('renders className correctly', () => { 24 | const wrapper = mount( 25 | , 29 | ); 30 | 31 | expect(wrapper.find(".image-map-test").exists()); 32 | }); 33 | 34 | it('renders img correctly', () => { 35 | const wrapper = mount( 36 | , 39 | ); 40 | 41 | // show img 42 | expect(wrapper.find("img").prop("src")).toEqual(EXAMPLE); 43 | 44 | }); 45 | 46 | it('render map correctly', () => { 47 | const wrapper = mount( 48 | , 51 | ); 52 | 53 | expect(wrapper.find("span").length).toEqual(0); 54 | 55 | const mapArea = [{"left":"0%","top":"6%","height":"12%","width":"33%"}]; 56 | const wrapperMap = mount( 57 | , 61 | ); 62 | 63 | expect(wrapperMap.find("span").length).toEqual(1); 64 | }); 65 | 66 | it('Test click event', () => { 67 | const mockCallBack = jest.fn(index => index); 68 | const mapArea = [{"left":"0%","top":"6%","height":"12%","width":"33%"}]; 69 | const wrapperMapClick = mount( 70 | , 75 | ); 76 | const map1 = wrapperMapClick.find('span').at(0); 77 | 78 | expect(map1.exists()); 79 | 80 | map1.simulate('click'); 81 | 82 | expect(mockCallBack).toBeCalledWith(0); 83 | }); 84 | 85 | }); 86 | -------------------------------------------------------------------------------- /src/common/detect-os.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2018-12-06 17:43:19 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2019-06-11 16:51:42 7 | */ 8 | 9 | export function detectOS(version = false) { 10 | const sUserAgent = navigator.userAgent; 11 | 12 | const isWX = /MicroMessenger/i.test(navigator.userAgent); 13 | if(isWX) return 'isWX'; 14 | 15 | // 先判断手机系统 16 | const matchResult = sUserAgent.toLowerCase().match(/android/i) || []; 17 | const bIsAndroid = matchResult[0] === 'android'; 18 | if (bIsAndroid) return 'Android'; 19 | 20 | const isiOS = !!sUserAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); 21 | if (isiOS) return 'iOS'; 22 | 23 | // 再判断运行浏览器的 操作系统 或 硬件平台 24 | const isLinux = (String(navigator.platform).indexOf('Linux') > -1); 25 | if (isLinux) return 'Linux'; 26 | 27 | const isMac = (navigator.platform === 'Mac68K') || (navigator.platform === 'MacPPC') || (navigator.platform === 'Macintosh') || (navigator.platform === 'MacIntel'); 28 | if (isMac) return 'Mac'; 29 | 30 | const isWin = (navigator.platform === 'Win32') || (navigator.platform === 'Windows'); 31 | if (isWin && !version) return 'Win'; 32 | if (isWin && version) { 33 | const isWin2K = sUserAgent.indexOf('Windows NT 5.0') > -1 || sUserAgent.indexOf('Windows 2000') > -1; 34 | if (isWin2K) return 'Win2000'; 35 | const isWinXP = sUserAgent.indexOf('Windows NT 5.1') > -1 || sUserAgent.indexOf('Windows XP') > -1; 36 | if (isWinXP) return 'WinXP'; 37 | const isWin2003 = sUserAgent.indexOf('Windows NT 5.2') > -1 || sUserAgent.indexOf('Windows 2003') > -1; 38 | if (isWin2003) return 'Win2003'; 39 | const isWinVista = sUserAgent.indexOf('Windows NT 6.0') > -1 || sUserAgent.indexOf('Windows Vista') > -1; 40 | if (isWinVista) return 'WinVista'; 41 | const isWin7 = sUserAgent.indexOf('Windows NT 6.1') > -1 || sUserAgent.indexOf('Windows 7') > -1; 42 | if (isWin7) return 'Win7'; 43 | const isWin8 = sUserAgent.indexOf('Windows NT 6.2') > -1 || sUserAgent.indexOf('Windows 8') > -1; 44 | if (isWin8) return 'Win8'; 45 | const isWin8_1 = sUserAgent.indexOf('Windows NT 6.3') > -1 || sUserAgent.indexOf('Windows 8.1') > -1; 46 | if (isWin8_1) return 'Win8.1'; 47 | const isWin10 = sUserAgent.indexOf('Windows NT 6.4') > -1 || sUserAgent.indexOf('Windows 10') > -1; 48 | if (isWin10) return 'Win10'; 49 | return 'Win'; 50 | } 51 | 52 | const isUnix = (navigator.platform === 'X11') && !isWin && !isMac; 53 | if (isUnix) return 'Unix'; 54 | 55 | 56 | return 'other'; 57 | }; 58 | -------------------------------------------------------------------------------- /config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | 8 | /** 9 | * Get the baseUrl of a compilerOptions object. 10 | * 11 | * @param {Object} options 12 | */ 13 | function getAdditionalModulePaths(options = {}) { 14 | const baseUrl = options.baseUrl; 15 | 16 | // We need to explicitly check for null and undefined (and not a falsy value) because 17 | // TypeScript treats an empty string as `.`. 18 | if (baseUrl == null) { 19 | // If there's no baseUrl set we respect NODE_PATH 20 | // Note that NODE_PATH is deprecated and will be removed 21 | // in the next major release of create-react-app. 22 | 23 | const nodePath = process.env.NODE_PATH || ''; 24 | return nodePath.split(path.delimiter).filter(Boolean); 25 | } 26 | 27 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 28 | 29 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 30 | // the default behavior. 31 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 32 | return null; 33 | } 34 | 35 | // Allow the user set the `baseUrl` to `appSrc`. 36 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 37 | return [paths.appSrc]; 38 | } 39 | 40 | // Otherwise, throw an error. 41 | throw new Error( 42 | chalk.red.bold( 43 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 44 | ' Create React App does not support other values at this time.' 45 | ) 46 | ); 47 | } 48 | 49 | function getModules() { 50 | // Check if TypeScript is setup 51 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 52 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 53 | 54 | if (hasTsConfig && hasJsConfig) { 55 | throw new Error( 56 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 57 | ); 58 | } 59 | 60 | let config; 61 | 62 | // If there's a tsconfig.json we assume it's a 63 | // TypeScript project and set up the config 64 | // based on tsconfig.json 65 | if (hasTsConfig) { 66 | config = require(paths.appTsConfig); 67 | // Otherwise we'll check if there is jsconfig.json 68 | // for non TS projects. 69 | } else if (hasJsConfig) { 70 | config = require(paths.appJsConfig); 71 | } 72 | 73 | config = config || {}; 74 | const options = config.compilerOptions || {}; 75 | 76 | const additionalModulePaths = getAdditionalModulePaths(options); 77 | 78 | return { 79 | additionalModulePaths: additionalModulePaths, 80 | hasTsConfig, 81 | }; 82 | } 83 | 84 | module.exports = getModules(); 85 | -------------------------------------------------------------------------------- /src/common/is-type.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: qiuz 3 | * @Github: 4 | * @Date: 2018-12-11 11:27:12 5 | * @Last Modified by: qiuz 6 | * @Last Modified time: 2019-11-27 21:36:08 7 | */ 8 | 9 | const pattern = { 10 | // http://emailregex.com/ 11 | // eslint-disable-next-line 12 | email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 13 | // eslint-disable-next-line 14 | url: new RegExp('^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 'i'), 15 | hex: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i 16 | }; 17 | 18 | export const isNumber = (value: any) => { 19 | return typeof value === 'number'; 20 | }; 21 | 22 | export const isRegExp = (value: any) => { 23 | return Object.prototype.toString.call(value) === '[object RegExp]'; 24 | }; 25 | 26 | export const isObject = (value: any) => { 27 | return Object.prototype.toString.call(value) === '[object Object]'; 28 | }; 29 | 30 | export const isString = (value: any) => { 31 | return typeof value === 'string'; 32 | }; 33 | 34 | export const isFunction = (value: any) => { 35 | return typeof value === 'function'; 36 | }; 37 | 38 | export const isArray = (value: any) => { 39 | return Object.prototype.toString.call(value) === '[object Array]'; 40 | }; 41 | 42 | export const isEmptyObject = (obj: any) => { 43 | if (obj === null || obj === undefined) return 'Cannot convert undefined or null to object'; 44 | return Object.keys(obj).length === 0; 45 | }; 46 | 47 | export const isBoolean = (value: any) => { 48 | return value === true || value === false || 49 | (isObject(value) && Object.prototype.toString.call(value) === '[object Boolean]'); 50 | }; 51 | 52 | export const isInteger = (value: any) => { 53 | return isNumber(value) && parseInt(value, 10) === value; 54 | }; 55 | 56 | export const isFloat = (value: any) => { 57 | return isNumber(value) && !isInteger(value); 58 | }; 59 | 60 | export const isDate = (value: any) => { 61 | if (!value) return false; 62 | return typeof value.getTime === 'function' && 63 | typeof value.getMonth === 'function' && 64 | typeof value.getYear === 'function'; 65 | }; 66 | 67 | export const isEmail = (value: any) => { 68 | return typeof (value) === 'string' && !!value.match(pattern.email) && value.length < 255; 69 | }; 70 | 71 | export const isUrl = (value: any) => { 72 | return typeof (value) === 'string' && !!value.match(pattern.url); 73 | }; 74 | 75 | export const isPromise = (value: any) => { 76 | return value && typeof value.then === 'function'; 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right