├── .env ├── .env.development ├── .env.production ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitpod.yml ├── .husky ├── commit-msg └── pre-commit ├── .vscode ├── extensions.json └── launch.json ├── LICENSE ├── README.md ├── build ├── utils.ts └── vite │ ├── plugin │ ├── antdResolver.ts │ ├── compress.ts │ ├── index.ts │ └── svgSprite.ts │ └── proxy.ts ├── commitlint.config.js ├── components.d.ts ├── index.html ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── prettier.config.js ├── public └── favicon.ico ├── src ├── App.vue ├── api │ └── index.js ├── assets │ └── logo.png ├── components │ ├── CesiumMap.vue │ └── PanelMenu.vue ├── hooks │ ├── demo │ │ ├── 3DTitleSet.ts │ │ ├── heatMap.ts │ │ └── setOSMBuildings.ts │ ├── draw │ │ ├── index.ts │ │ └── types.ts │ ├── functional │ │ ├── highlightEntity.ts │ │ └── useRotate.ts │ ├── measure │ │ ├── area.ts │ │ └── line.ts │ ├── removeTools.ts │ ├── useCesium.ts │ ├── useCesiumMap.ts │ └── useHeat.ts ├── main.ts ├── router │ └── index.ts ├── store │ ├── index.ts │ └── modules │ │ └── app.ts ├── utils │ └── axios.ts └── views │ ├── 3d-tile.vue │ ├── base.vue │ └── osm-building.vue ├── stylelint.config.js ├── tsconfig.json ├── types ├── env.d.ts ├── global.d.ts └── module.d.ts ├── vite.config.ts └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | # port 2 | VITE_PORT = 8099 3 | 4 | # spa-title 5 | VITE_GLOB_APP_TITLE = Vben Admin 6 | 7 | # spa shortname 8 | VITE_GLOB_APP_SHORT_NAME = vue_vben_admin -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = / 6 | 7 | # Cross-domain proxy, you can configure multiple 8 | # Please note that no line breaks 9 | VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]] 10 | # VITE_PROXY=[["/api","https://vvbin.cn/test"]] 11 | 12 | # Delete console 13 | VITE_DROP_CONSOLE = false 14 | 15 | # Basic interface address SPA 16 | VITE_GLOB_API_URL = /basic-api 17 | 18 | # File upload address, optional 19 | VITE_GLOB_UPLOAD_URL = /upload 20 | 21 | # Interface prefix 22 | VITE_GLOB_API_URL_PREFIX = -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # Whether to open mock 2 | VITE_USE_MOCK = true 3 | 4 | # public path 5 | VITE_PUBLIC_PATH = ./ 6 | 7 | # Delete console 8 | VITE_DROP_CONSOLE = true 9 | 10 | # Whether to enable gzip or brotli compression 11 | # Optional: gzip | brotli | none 12 | # If you need multiple forms, you can use `,` to separate 13 | VITE_BUILD_COMPRESS = 'none' 14 | 15 | # Whether to delete origin files when using compress, default false 16 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false 17 | 18 | # Basic interface address SPA 19 | VITE_GLOB_API_URL=/basic-api 20 | 21 | # File upload address, optional 22 | # It can be forwarded by nginx or write the actual address directly 23 | VITE_GLOB_UPLOAD_URL=/upload 24 | 25 | # Interface prefix 26 | VITE_GLOB_API_URL_PREFIX= 27 | 28 | # Whether to enable image compression 29 | VITE_USE_IMAGEMIN= true 30 | 31 | # use pwa 32 | VITE_USE_PWA = false 33 | 34 | # Is it compatible with older browsers 35 | VITE_LEGACY = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | node_modules 3 | *.md 4 | *.woff 5 | *.ttf 6 | .vscode 7 | .idea 8 | dist 9 | /public 10 | /docs 11 | .husky 12 | .local 13 | /bin 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { defineConfig } = require('eslint-define-config') 3 | module.exports = defineConfig({ 4 | root: true, 5 | env: { 6 | browser: true, 7 | node: true, 8 | es6: true, 9 | }, 10 | parser: 'vue-eslint-parser', 11 | parserOptions: { 12 | parser: '@typescript-eslint/parser', 13 | ecmaVersion: 2020, 14 | sourceType: 'module', 15 | jsxPragma: 'React', 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | }, 20 | extends: [ 21 | 'plugin:vue/vue3-recommended', 22 | 'plugin:@typescript-eslint/recommended', 23 | 'prettier', 24 | 'plugin:prettier/recommended', 25 | ], 26 | rules: { 27 | 'vue/script-setup-uses-vars': 'error', 28 | '@typescript-eslint/ban-ts-ignore': 'off', 29 | '@typescript-eslint/explicit-function-return-type': 'off', 30 | '@typescript-eslint/no-explicit-any': 'off', 31 | '@typescript-eslint/no-var-requires': 'off', 32 | '@typescript-eslint/no-empty-function': 'off', 33 | 'vue/custom-event-name-casing': 'off', 34 | 'no-use-before-define': 'off', 35 | '@typescript-eslint/no-use-before-define': 'off', 36 | '@typescript-eslint/ban-ts-comment': 'off', 37 | '@typescript-eslint/ban-types': 'off', 38 | '@typescript-eslint/no-non-null-assertion': 'off', 39 | '@typescript-eslint/explicit-module-boundary-types': 'off', 40 | '@typescript-eslint/no-unused-vars': [ 41 | 'error', 42 | { 43 | argsIgnorePattern: '^_', 44 | varsIgnorePattern: '^_', 45 | }, 46 | ], 47 | 'no-unused-vars': [ 48 | 'error', 49 | { 50 | argsIgnorePattern: '^_', 51 | varsIgnorePattern: '^_', 52 | }, 53 | ], 54 | 'space-before-function-paren': 'off', 55 | 56 | 'vue/attributes-order': 'off', 57 | 'vue/one-component-per-file': 'off', 58 | 'vue/html-closing-bracket-newline': 'off', 59 | 'vue/max-attributes-per-line': 'off', 60 | 'vue/multiline-html-element-content-newline': 'off', 61 | 'vue/singleline-html-element-content-newline': 'off', 62 | 'vue/attribute-hyphenation': 'off', 63 | 'vue/require-default-prop': 'off', 64 | 'vue/require-explicit-emits': 'off', 65 | 'vue/html-self-closing': [ 66 | 'error', 67 | { 68 | html: { 69 | void: 'always', 70 | normal: 'never', 71 | component: 'always', 72 | }, 73 | svg: 'always', 74 | math: 'always', 75 | }, 76 | ], 77 | 'vue/multi-word-component-names': 'off', 78 | }, 79 | }) 80 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: # 监听 master 分支上的 push 事件 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest # 构建环境使用 ubuntu 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | with: 13 | persist-credentials: false 14 | 15 | - name: Install and Build # 下载依赖 打包项目 16 | run: | 17 | npm install 18 | npm run build 19 | 20 | - name: Deploy # 将打包内容发布到 github page 21 | uses: JamesIves/github-pages-deploy-action@releases/v3 22 | with: # 自定义环境变量 23 | ACCESS_TOKEN: ${{ secrets.VUE_CESIUM }} 24 | BASE_BRANCH: master 25 | BRANCH: gh-pages 26 | FOLDER: dist 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | ports: 2 | - port: 5555 3 | onOpen: open-preview 4 | tasks: 5 | - init: corepack enable && npm install 6 | command: pnpm run dev 7 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck source=./_/husky.sh 4 | . "$(dirname "$0")/_/husky.sh" 5 | 6 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "stylelint.vscode-stylelint", 5 | "esbenp.prettier-vscode", 6 | "mikestead.dotenv" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "Launch Chrome", 8 | "url": "http://localhost:3100", 9 | "webRoot": "${workspaceFolder}/src", 10 | "sourceMaps": true 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present, Vben 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Typescript + Vite + cesium 2 | 据不完全统计,现有的很多都是按照[Cesium 官方的 webpack 教程](https://cesium.com/learn/cesiumjs-learn/cesiumjs-webpack/)(基本上使用vue3的都是搭配vuecli4.x)来开发的,很少使用vue3最优的vite工具开发实现,本demo项目使用Vue3.x、Vite2.x、Cesium.js进行开发,也方便学习和交流。 3 | 4 | 这里用到的是vite 搭配[vite-plugin-cesium](https://github.com/nshen/vite-plugin-cesium),能很好实现在vite使用cesium。 5 | 6 | ### 安装 7 | 8 | ```shell 9 | npm i vite-plugin-cesium vite -D 10 | # yarn add vite-plugin-cesium vite -D 11 | ``` 12 | 13 | ### 配置 14 | 15 | 在 `vite.config.js`使用插件配置 16 | 17 | ```shell 18 | ... 19 | + import cesium from 'vite-plugin-cesium'; 20 | export default defineConfig({ 21 | ... 22 | + plugins: [cesium()] 23 | }); 24 | ``` 25 | 26 | ### 项目运行 27 | 28 | 1. 克隆项目 29 | ``` 30 | # git clone https://github.com/guixianleng/vue-vite-cesium.git 31 | ``` 32 | 33 | 2. 安装依赖 34 | 35 | ```shell 36 | # npm install or yarn or pnpm i 37 | ``` 38 | 39 | 3. 运行 40 | 41 | ```shell 42 | # npm run dev or yarn dev or pnpm run dev 43 | ``` 44 | ## 预览图 45 | ![preview](https://images.prismic.io/cesium/tutorials-imagery-layers-bing-labels.jpg?auto=compress%2Cformat&w=599) 46 | 47 | 48 | ![preview](https://images.prismic.io/cesium/tutorials-3d-tiles-styling-showAllBuildings.jpg?auto=compress%2Cformat&w=1040) 49 | 50 | ## 使用 Gitpod 51 | 52 | 在 Gitpod(适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码. 53 | 54 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/guixianleng/vue-vite-cesium) 55 | -------------------------------------------------------------------------------- /build/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 格式化处理环境配置 3 | * @param {*} envConf 环境配置参数 4 | * @returns 5 | */ 6 | export function wrapperEnv(envConf: Recordable): ViteEnv { 7 | const ret: any = {} 8 | 9 | for (const envName of Object.keys(envConf)) { 10 | let realName = envConf[envName].replace(/\\n/g, '\n') 11 | realName = realName === 'true' ? true : realName === 'false' ? false : realName 12 | 13 | if (envName === 'VITE_PORT') { 14 | realName = Number(realName) 15 | } 16 | if (envName === 'VITE_PROXY' && realName) { 17 | try { 18 | realName = JSON.parse(realName.replace(/'/g, '"')) 19 | } catch (error) { 20 | realName = '' 21 | } 22 | } 23 | ret[envName] = realName 24 | if (typeof realName === 'string') { 25 | process.env[envName] = realName 26 | } else if (typeof realName === 'object') { 27 | process.env[envName] = JSON.stringify(realName) 28 | } 29 | } 30 | return ret 31 | } 32 | -------------------------------------------------------------------------------- /build/vite/plugin/antdResolver.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Introduces component library styles on demand. 3 | * https://github.com/antfu/unplugin-vue-components 4 | */ 5 | import Components from 'unplugin-vue-components/vite' 6 | import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' 7 | 8 | export function configAutoImportPlugin() { 9 | const styleImportPlugin = Components({ 10 | resolvers: [AntDesignVueResolver()], 11 | dts: true, 12 | types: [], 13 | }) 14 | return styleImportPlugin 15 | } 16 | -------------------------------------------------------------------------------- /build/vite/plugin/compress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated 3 | * https://github.com/anncwb/vite-plugin-compression 4 | */ 5 | import type { Plugin } from 'vite' 6 | import compressPlugin from 'vite-plugin-compression' 7 | 8 | export function configCompressPlugin( 9 | compress: 'gzip' | 'brotli' | 'none', 10 | deleteOriginFile = false, 11 | ): Plugin | Plugin[] { 12 | const compressList = compress.split(',') 13 | 14 | const plugins: Plugin[] = [] 15 | 16 | if (compressList.includes('gzip')) { 17 | plugins.push( 18 | compressPlugin({ 19 | ext: '.gz', 20 | deleteOriginFile, 21 | }), 22 | ) 23 | } 24 | 25 | if (compressList.includes('brotli')) { 26 | plugins.push( 27 | compressPlugin({ 28 | ext: '.br', 29 | algorithm: 'brotliCompress', 30 | deleteOriginFile, 31 | }), 32 | ) 33 | } 34 | return plugins 35 | } 36 | -------------------------------------------------------------------------------- /build/vite/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import purgeIcons from 'vite-plugin-purge-icons' 5 | 6 | import { configCompressPlugin } from './compress' 7 | import { configAutoImportPlugin } from './antdResolver' 8 | import { configSvgIconsPlugin } from './svgSprite' 9 | 10 | export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { 11 | const { VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv 12 | 13 | const vitePlugins: (Plugin | Plugin[])[] = [vue(), vueJsx()] 14 | 15 | // vite-plugin-svg-icons 16 | vitePlugins.push(configSvgIconsPlugin(isBuild)) 17 | 18 | // vite-plugin-purge-icons 19 | vitePlugins.push(purgeIcons()) 20 | 21 | // vite-plugin-style-import 22 | vitePlugins.push(configAutoImportPlugin()) 23 | 24 | // The following plugins only work in the production environment 25 | if (isBuild) { 26 | // rollup-plugin-gzip 27 | vitePlugins.push(configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE)) 28 | } 29 | 30 | return vitePlugins 31 | } 32 | -------------------------------------------------------------------------------- /build/vite/plugin/svgSprite.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vite Plugin for fast creating SVG sprites. 3 | * https://github.com/anncwb/vite-plugin-svg-icons 4 | */ 5 | 6 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 7 | import path from 'path' 8 | 9 | export function configSvgIconsPlugin(isBuild: boolean) { 10 | const svgIconsPlugin = createSvgIconsPlugin({ 11 | iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], 12 | svgoOptions: isBuild, 13 | // default 14 | symbolId: 'icon-[dir]-[name]', 15 | }) 16 | return svgIconsPlugin 17 | } 18 | -------------------------------------------------------------------------------- /build/vite/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to parse the .env.development proxy configuration 3 | */ 4 | import type { ProxyOptions } from 'vite' 5 | 6 | type ProxyItem = [string, string] 7 | 8 | type ProxyList = ProxyItem[] 9 | 10 | type ProxyTargetList = Record 11 | 12 | const httpsRE = /^https:\/\// 13 | 14 | /** 15 | * Generate proxy 16 | * @param list 17 | */ 18 | export function createProxy(list: ProxyList = []) { 19 | const ret: ProxyTargetList = {} 20 | for (const [prefix, target] of list) { 21 | const isHttps = httpsRE.test(target) 22 | 23 | // https://github.com/http-party/node-http-proxy#options 24 | ret[prefix] = { 25 | target: target, 26 | changeOrigin: true, 27 | ws: true, 28 | rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''), 29 | // https is require secure=false 30 | ...(isHttps ? { secure: false } : {}), 31 | } 32 | } 33 | return ret 34 | } 35 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignores: [(commit) => commit.includes('init')], 3 | extends: ['@commitlint/config-conventional'], 4 | rules: { 5 | 'body-leading-blank': [2, 'always'], 6 | 'footer-leading-blank': [1, 'always'], 7 | 'header-max-length': [2, 'always', 108], 8 | 'subject-empty': [2, 'never'], 9 | 'type-empty': [2, 'never'], 10 | 'subject-case': [0], 11 | 'type-enum': [ 12 | 2, 13 | 'always', 14 | [ 15 | 'feat', 16 | 'fix', 17 | 'perf', 18 | 'style', 19 | 'docs', 20 | 'test', 21 | 'refactor', 22 | 'build', 23 | 'ci', 24 | 'chore', 25 | 'revert', 26 | 'wip', 27 | 'workflow', 28 | 'types', 29 | 'release', 30 | ], 31 | ], 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | // generated by unplugin-vue-components 2 | // We suggest you to commit this file into source control 3 | // Read more: https://github.com/vuejs/vue-next/pull/3399 4 | import '@vue/runtime-core' 5 | 6 | declare module '@vue/runtime-core' { 7 | export interface GlobalComponents { 8 | AButton: typeof import('ant-design-vue/es')['Button'] 9 | AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] 10 | ASelect: typeof import('ant-design-vue/es')['Select'] 11 | ASpace: typeof import('ant-design-vue/es')['Space'] 12 | ASpin: typeof import('ant-design-vue/es')['Spin'] 13 | CesiumMap: typeof import('./src/components/CesiumMap.vue')['default'] 14 | PanelMenu: typeof import('./src/components/PanelMenu.vue')['default'] 15 | } 16 | } 17 | 18 | export {} 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Cesium Map 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-cesium", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vue-tsc --noEmit && vite build", 7 | "preview": "vite preview", 8 | "prepare": "husky install", 9 | "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite", 10 | "clean:lib": "rimraf node_modules", 11 | "lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix", 12 | "lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", 13 | "lint:stylelint": "stylelint --fix \"**/*.{tsx,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", 14 | "lint:lint-staged": "lint-staged", 15 | "lint:all": "npm run lint:eslint && npm run lint:stylelint && npm run lint:prettier && npm run lint:lint-staged" 16 | }, 17 | "dependencies": { 18 | "@ant-design/icons-vue": "^6.1.0", 19 | "ant-design-vue": "^3.2.3", 20 | "axios": "^0.24.0", 21 | "cesium": "^1.93.0", 22 | "heatmap.js": "^2.0.5", 23 | "nprogress": "^0.2.0", 24 | "pinia": "^2.0.8", 25 | "vue": "^3.2.25", 26 | "vue-router": "^4.0.12" 27 | }, 28 | "devDependencies": { 29 | "@commitlint/cli": "^15.0.0", 30 | "@commitlint/config-conventional": "^15.0.0", 31 | "@types/cesium": "^1.70.0", 32 | "@types/node": "^17.0.32", 33 | "@typescript-eslint/eslint-plugin": "^5.8.0", 34 | "@typescript-eslint/parser": "^5.8.0", 35 | "@vitejs/plugin-vue": "^2.0.0", 36 | "@vitejs/plugin-vue-jsx": "^1.3.10", 37 | "commitizen": "^4.2.4", 38 | "cz-conventional-changelog": "^3.3.0", 39 | "eslint": "^8.5.0", 40 | "eslint-config-prettier": "^8.3.0", 41 | "eslint-define-config": "^1.2.0", 42 | "eslint-plugin-import": "^2.25.3", 43 | "eslint-plugin-node": "^11.1.0", 44 | "eslint-plugin-prettier": "^4.0.0", 45 | "eslint-plugin-promise": "^6.0.0", 46 | "eslint-plugin-vue": "^8.2.0", 47 | "husky": "^7.0.0", 48 | "less": "^4.1.2", 49 | "lint-staged": "^12.1.3", 50 | "postcss-html": "^1.3.0", 51 | "prettier": "^2.5.1", 52 | "rimraf": "^3.0.2", 53 | "stylelint": "^14.2.0", 54 | "stylelint-config-html": "^1.0.0", 55 | "stylelint-config-prettier": "^9.0.3", 56 | "stylelint-config-recommended": "^6.0.0", 57 | "stylelint-config-standard": "^24.0.0", 58 | "stylelint-order": "^5.0.0", 59 | "typescript": "^4.4.4", 60 | "unplugin-vue-components": "^0.19.5", 61 | "vite": "^2.7.2", 62 | "vite-plugin-cesium": "^1.2.18", 63 | "vite-plugin-compression": "^0.5.1", 64 | "vite-plugin-purge-icons": "^0.8.1", 65 | "vite-plugin-svg-icons": "^2.0.1", 66 | "vue-eslint-parser": "^8.0.1", 67 | "vue-tsc": "^0.29.8" 68 | }, 69 | "gitHooks": { 70 | "pre-commit": "lint-staged" 71 | }, 72 | "lint-staged": { 73 | "*.{js,jsx,ts,tsx}": [ 74 | "eslint --fix", 75 | "prettier --write" 76 | ], 77 | "*.vue": [ 78 | "eslint --fix", 79 | "stylelint --fix", 80 | "prettier --write" 81 | ], 82 | "*.{scss,less,styl}": [ 83 | "stylelint --fix", 84 | "prettier --write" 85 | ] 86 | }, 87 | "config": { 88 | "commitizen": { 89 | "path": "./node_modules/cz-conventional-changelog" 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: false, 6 | vueIndentScriptAndStyle: true, 7 | singleQuote: true, 8 | quoteProps: 'as-needed', 9 | bracketSpacing: true, 10 | trailingComma: 'all', 11 | jsxSingleQuote: false, 12 | arrowParens: 'always', 13 | insertPragma: false, 14 | requirePragma: false, 15 | proseWrap: 'never', 16 | htmlWhitespaceSensitivity: 'strict', 17 | endOfLine: 'auto', 18 | } 19 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LenGxien/vue-vite-cesium/bb4abd4d51f398267767b3768582e86e1abc4201/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 56 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 存放http请求的接口 3 | * @Author: LenGxin 4 | * @Date: 2021-12-23 11:45:45 5 | * @LastEditors: LenGxin 6 | * @LastEditTime: 2021-12-23 11:46:50 7 | */ 8 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LenGxien/vue-vite-cesium/bb4abd4d51f398267767b3768582e86e1abc4201/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/CesiumMap.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 57 | 58 | 76 | -------------------------------------------------------------------------------- /src/components/PanelMenu.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 165 | 255 | -------------------------------------------------------------------------------- /src/hooks/demo/3DTitleSet.ts: -------------------------------------------------------------------------------- 1 | import useCesium from '/@/hooks/useCesium' 2 | 3 | /** 4 | * 加载倾斜摄影示例 5 | */ 6 | export default async function use3DTitleSet(viewer: ElRef) { 7 | const Cesium = useCesium() 8 | viewer.imageryLayers.addImageryProvider(new Cesium.IonImageryProvider({ assetId: 3 })) 9 | 10 | const titleSet = new Cesium.Cesium3DTileset({ 11 | url: Cesium.IonResource.fromAssetId(354759), 12 | }) 13 | 14 | viewer.scene.primitives.add(titleSet) 15 | 16 | try { 17 | await titleSet.readyPromise 18 | await viewer.zoomTo(titleSet) 19 | 20 | // Apply the default style if it exists 21 | const extras = titleSet.asset.extras 22 | if (Cesium.defined(extras) && Cesium.defined(extras.ion) && Cesium.defined(extras.ion.defaultStyle)) { 23 | titleSet.style = new Cesium.Cesium3DTileStyle(extras.ion.defaultStyle) 24 | } 25 | } catch (error) { 26 | console.log(error) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/demo/heatMap.ts: -------------------------------------------------------------------------------- 1 | import h337 from 'heatmap.js' 2 | import useCesium from '/@/hooks/useCesium' 3 | const Cesium = useCesium() 4 | 5 | interface DataEnum { 6 | x: number 7 | y: number 8 | value: number 9 | radius?: number 10 | } 11 | 12 | export default function useHeatMap() { 13 | // 生成len个随机数据 14 | const getRandomData = (len: number) => { 15 | //构建一些随机数据点 16 | const points: Array = [] 17 | let max = 0 18 | const width = 840 19 | const height = 400 20 | while (len--) { 21 | const val = Math.floor(Math.random() * 100) 22 | max = Math.max(max, val) 23 | const point = { 24 | x: Math.floor(Math.random() * width), 25 | y: Math.floor(Math.random() * height), 26 | value: val, 27 | } 28 | points.push(point) 29 | } 30 | return { max: max, data: points } 31 | } 32 | 33 | // 生成热力图 34 | const createHeatMap = (max: number, data: any) => { 35 | // 获取元素 36 | const heatDoc = document.createElement('div') 37 | heatDoc.setAttribute('style', 'width: 100px; height: 100px; margin: 0px; display: none;') 38 | document.body.appendChild(heatDoc) 39 | // 创建热力图对象 40 | const heatmap = h337.create({ 41 | container: heatDoc, 42 | radius: 5, 43 | maxOpacity: 0.5, 44 | minOpacity: 0, 45 | gradient: { 46 | '0.9': 'red', 47 | '0.8': 'orange', 48 | '0.7': 'yellow', 49 | '0.5': 'blue', 50 | '0.3': 'green', 51 | }, 52 | }) 53 | // 添加数据 54 | heatmap.setData({ 55 | max: max, 56 | data: data, 57 | }) 58 | return heatmap 59 | } 60 | 61 | // 创建正方形 绑定热力图 62 | const createRectangle = (viewer: ElRef, coordinate: Array, heatMap: ElRef) => { 63 | viewer.entities.add({ 64 | name: '热力图', 65 | show: true, 66 | rectangle: { 67 | coordinates: Cesium.Rectangle.fromDegrees(coordinate[0], coordinate[1], coordinate[2], coordinate[3]), 68 | material: heatMap._renderer.canvas, 69 | transparent: true, 70 | }, 71 | }) 72 | viewer.zoomTo(viewer.entities) 73 | } 74 | 75 | return { 76 | createRectangle, 77 | getRandomData, 78 | createHeatMap, 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/hooks/demo/setOSMBuildings.ts: -------------------------------------------------------------------------------- 1 | import useCesium from '/@/hooks/useCesium' 2 | 3 | export default function useSetOSMBuildings(viewer: ElRef) { 4 | const Cesium = useCesium() 5 | viewer.imageryLayers.addImageryProvider(new Cesium.IonImageryProvider({ assetId: 3 })) 6 | 7 | viewer.scene.primitives.add(Cesium.createOsmBuildings()) 8 | 9 | viewer.scene.camera.flyTo({ 10 | destination: Cesium.Cartesian3.fromDegrees(-74.019, 40.6912, 750), 11 | orientation: { 12 | heading: Cesium.Math.toRadians(20), 13 | pitch: Cesium.Math.toRadians(-20), 14 | }, 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/draw/index.ts: -------------------------------------------------------------------------------- 1 | import { unref } from 'vue' 2 | 3 | import useCesium from '/@/hooks/useCesium' 4 | 5 | const Cesium = useCesium() 6 | 7 | /** 8 | * 根据类型绘制对象 9 | * @param type point、polyline、polygon 10 | */ 11 | export default function useDraw(viewer: ElRef, type: 'point' | 'polyline' | 'polygon') { 12 | const activeShapePoints = unref([]) 13 | let activeShape = unref(null) 14 | let floatingPoint = unref(null) 15 | 16 | // Zoom in to an area with mountains 17 | // viewer.camera.lookAt( 18 | // Cesium.Cartesian3.fromDegrees(-122.2058, 46.1955, 1000.0), 19 | // new Cesium.Cartesian3(5000.0, 5000.0, 5000.0), 20 | // ) 21 | // viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY) 22 | 23 | // 开启深度检测 24 | viewer.scene.globe.depthTestAgainstTerrain = true 25 | //双击鼠标左键清除默认事件 26 | viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) 27 | const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas) 28 | 29 | // 绘制点 30 | const drawPoint = (position: Array) => { 31 | const pointer = viewer.entities.add({ 32 | name: '点', 33 | position: position, 34 | point: { 35 | color: Cesium.Color.WHITE, 36 | pixelSize: 10, 37 | outlineColor: Cesium.Color.YELLOW, 38 | outlineWidth: 3, 39 | disableDepthTestDistance: Number.POSITIVE_INFINITY, 40 | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, 41 | }, 42 | }) 43 | return pointer 44 | } 45 | 46 | // 绘制线 47 | const drawPolyline = (positions: Array) => { 48 | const polyline = viewer.entities.add({ 49 | name: '线', 50 | polyline: { 51 | positions: positions, 52 | clampToGround: true, 53 | width: 5, 54 | }, 55 | }) 56 | return polyline 57 | } 58 | 59 | // 绘制面 60 | const drawPolygon = (positions: Array) => { 61 | const polygon = viewer.entities.add({ 62 | name: '面', 63 | // polyline: { 64 | // positions: positions, 65 | // clampToGround: true, 66 | // width: 2, 67 | // }, 68 | polygon: { 69 | hierarchy: positions, 70 | material: new Cesium.ColorMaterialProperty(Cesium.Color.WHITE.withAlpha(0.7)), 71 | }, 72 | }) 73 | return polygon 74 | } 75 | 76 | const drawShape = (positionData) => { 77 | let shape = null 78 | switch (type) { 79 | case 'polyline': 80 | shape = drawPolyline(positionData) 81 | return shape 82 | case 'polygon': 83 | shape = drawPolygon(positionData) 84 | return shape 85 | default: 86 | return shape 87 | } 88 | } 89 | 90 | // 鼠标左键 91 | handler.setInputAction(function (event) { 92 | // scene.pickPosition只有在开启地形深度检测,且不使用默认地形时是准确的。 93 | const ray = viewer.camera.getPickRay(event.position) 94 | const earthPosition = viewer.scene.globe.pick(ray, viewer.scene) 95 | // const earthPosition = viewer.scene.pickPosition(event.position) 96 | 97 | if (activeShapePoints.length === 0) { 98 | console.log(111) 99 | floatingPoint = drawPoint(earthPosition) 100 | activeShapePoints.push(earthPosition) 101 | const dynamicPositions = new Cesium.CallbackProperty(function () { 102 | if (type === 'polygon') { 103 | return new Cesium.PolygonHierarchy(activeShapePoints) 104 | } 105 | return activeShapePoints 106 | }, false) 107 | activeShape = drawShape(dynamicPositions) //绘制动态图 108 | } 109 | drawPoint(earthPosition) 110 | activeShapePoints.push(earthPosition) 111 | }, Cesium.ScreenSpaceEventType.LEFT_CLICK) 112 | 113 | // 鼠标移动 114 | handler.setInputAction(function (event) { 115 | if (Cesium.defined(floatingPoint)) { 116 | const newPosition = viewer.scene.pickPosition(event.endPosition) 117 | if (Cesium.defined(newPosition)) { 118 | floatingPoint.position.setValue(newPosition) 119 | activeShapePoints.pop() 120 | activeShapePoints.push(newPosition) 121 | } 122 | } 123 | }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) 124 | 125 | // 左键双击停止绘制 126 | handler.setInputAction(function () { 127 | terminateShape() //关闭事件句柄 128 | }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) 129 | 130 | // Redraw the shape so it's not dynamic and remove the dynamic shape. 131 | const terminateShape = () => { 132 | handler.destroy() //关闭事件句柄 133 | activeShapePoints.pop() //去除最后一个动态点 134 | if (activeShapePoints.length) { 135 | drawShape(activeShapePoints) //绘制最终图 136 | } 137 | viewer.entities.remove(floatingPoint) //去除动态点图形(当前鼠标点) 138 | viewer.entities.remove(activeShape) //去除动态图形 139 | floatingPoint = null 140 | activeShape = null 141 | // activeShapePoints.splice(0, activeShapePoints.length) // 清空数组 142 | } 143 | 144 | handler.setInputAction(function () { 145 | terminateShape() 146 | }, Cesium.ScreenSpaceEventType.RIGHT_CLICK) 147 | } 148 | -------------------------------------------------------------------------------- /src/hooks/draw/types.ts: -------------------------------------------------------------------------------- 1 | export interface drawConfig { 2 | color?: string 3 | width?: number 4 | key?: unknown 5 | } 6 | -------------------------------------------------------------------------------- /src/hooks/functional/highlightEntity.ts: -------------------------------------------------------------------------------- 1 | import useCesium from '/@/hooks/useCesium' 2 | 3 | export default function useHighLightEntity(viewer: ElRef) { 4 | const Cesium = useCesium() 5 | // 鼠标hover样式 6 | const nameOverlay = document.createElement('div') 7 | viewer.container.appendChild(nameOverlay) 8 | nameOverlay.className = 'backdrop' 9 | nameOverlay.style.display = 'none' 10 | nameOverlay.style.position = 'absolute' 11 | nameOverlay.style.bottom = '0' 12 | nameOverlay.style.left = '0' 13 | nameOverlay.style['pointer-events'] = 'none' 14 | nameOverlay.style.padding = '4px' 15 | nameOverlay.style.backgroundColor = 'black' 16 | 17 | interface selectedType { 18 | feature: any 19 | originalColor: any 20 | } 21 | 22 | // Information about the currently selected feature 23 | const selected: selectedType = { 24 | feature: undefined, 25 | originalColor: new Cesium.Color(), 26 | } 27 | 28 | // An entity object which will hold info about the currently selected feature for infobox display 29 | const selectedEntity: ElRef = new Cesium.Entity() 30 | 31 | // Get default left click handler for when a feature is not picked on left click 32 | const clickHandler = viewer.screenSpaceEventHandler.getInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK) 33 | 34 | if (Cesium.PostProcessStageLibrary.isSilhouetteSupported(viewer.scene)) { 35 | // Silhouettes are supported 36 | const silhouetteBlue = Cesium.PostProcessStageLibrary.createEdgeDetectionStage() 37 | silhouetteBlue.uniforms.color = Cesium.Color.BLUE 38 | silhouetteBlue.uniforms.length = 0.01 39 | silhouetteBlue.selected = [] 40 | 41 | const silhouetteGreen = Cesium.PostProcessStageLibrary.createEdgeDetectionStage() 42 | silhouetteGreen.uniforms.color = Cesium.Color.LIME 43 | silhouetteGreen.uniforms.length = 0.01 44 | silhouetteGreen.selected = [] 45 | 46 | viewer.scene.postProcessStages.add( 47 | Cesium.PostProcessStageLibrary.createSilhouetteStage([silhouetteBlue, silhouetteGreen]), 48 | ) // Silhouette a feature blue on hover. 49 | viewer.screenSpaceEventHandler.setInputAction(function onMouseMove(movement) { 50 | // If a feature was previously highlighted, undo the highlight 51 | silhouetteBlue.selected = [] 52 | 53 | // Pick a new feature 54 | const pickedFeature = viewer.scene.pick(movement.endPosition) 55 | if (!Cesium.defined(pickedFeature)) { 56 | nameOverlay.style.display = 'none' 57 | return 58 | } 59 | 60 | // A feature was picked, so show it's overlay content 61 | nameOverlay.style.display = 'block' 62 | nameOverlay.style.bottom = `${viewer.canvas.clientHeight - movement.endPosition.y}px` 63 | nameOverlay.style.left = `${movement.endPosition.x}px` 64 | const name = pickedFeature.getProperty('BIN') 65 | nameOverlay.textContent = name 66 | 67 | // Highlight the feature if it's not already selected. 68 | if (pickedFeature !== selected.feature) { 69 | silhouetteBlue.selected = [pickedFeature] 70 | } 71 | }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) 72 | 73 | // Silhouette a feature on selection and show metadata in the InfoBox. 74 | viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) { 75 | // If a feature was previously selected, undo the highlight 76 | silhouetteGreen.selected = [] 77 | 78 | // Pick a new feature 79 | const pickedFeature = viewer.scene.pick(movement.position) 80 | if (!Cesium.defined(pickedFeature)) { 81 | clickHandler(movement) 82 | return 83 | } 84 | 85 | // Select the feature if it's not already selected 86 | if (silhouetteGreen.selected[0] === pickedFeature) { 87 | return 88 | } 89 | 90 | // Save the selected feature's original color 91 | const highlightedFeature = silhouetteBlue.selected[0] 92 | if (pickedFeature === highlightedFeature) { 93 | silhouetteBlue.selected = [] 94 | } 95 | 96 | // Highlight newly selected feature 97 | silhouetteGreen.selected = [pickedFeature] 98 | 99 | // Set feature infobox description 100 | const featureName = pickedFeature.getProperty('name') 101 | selectedEntity.name = featureName 102 | selectedEntity.description = 'Loading
' 103 | viewer.selectedEntity = selectedEntity 104 | selectedEntity.description = 105 | `${'' + '` + 108 | `` + 109 | `` + 110 | `
BIN'}${pickedFeature.getProperty( 106 | 'BIN', 107 | )}
DOITT ID${pickedFeature.getProperty('DOITT_ID')}
SOURCE ID${pickedFeature.getProperty('SOURCE_ID')}
` 111 | }, Cesium.ScreenSpaceEventType.LEFT_CLICK) 112 | } else { 113 | // Silhouettes are not supported. Instead, change the feature color. 114 | 115 | // Information about the currently highlighted feature 116 | const highlighted: selectedType = { 117 | feature: undefined, 118 | originalColor: new Cesium.Color(), 119 | } 120 | 121 | // Color a feature yellow on hover. 122 | viewer.screenSpaceEventHandler.setInputAction(function onMouseMove(movement) { 123 | // If a feature was previously highlighted, undo the highlight 124 | if (Cesium.defined(highlighted.feature)) { 125 | highlighted.feature.color = highlighted.originalColor 126 | highlighted.feature = undefined 127 | } 128 | // Pick a new feature 129 | const pickedFeature = viewer.scene.pick(movement.endPosition) 130 | if (!Cesium.defined(pickedFeature)) { 131 | nameOverlay.style.display = 'none' 132 | return 133 | } 134 | // A feature was picked, so show it's overlay content 135 | nameOverlay.style.display = 'block' 136 | nameOverlay.style.bottom = `${viewer.canvas.clientHeight - movement.endPosition.y}px` 137 | nameOverlay.style.left = `${movement.endPosition.x}px` 138 | let name = pickedFeature.getProperty('name') 139 | if (!Cesium.defined(name)) { 140 | name = pickedFeature.getProperty('id') 141 | } 142 | nameOverlay.textContent = name 143 | // Highlight the feature if it's not already selected. 144 | if (pickedFeature !== selected.feature) { 145 | highlighted.feature = pickedFeature 146 | Cesium.Color.clone(pickedFeature.color, highlighted.originalColor) 147 | pickedFeature.color = Cesium.Color.YELLOW 148 | } 149 | }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) 150 | 151 | // Color a feature on selection and show metadata in the InfoBox. 152 | viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) { 153 | // If a feature was previously selected, undo the highlight 154 | if (Cesium.defined(selected.feature)) { 155 | selected.feature.color = selected.originalColor 156 | selected.feature = undefined 157 | } 158 | // Pick a new feature 159 | const pickedFeature = viewer.scene.pick(movement.position) 160 | if (!Cesium.defined(pickedFeature)) { 161 | clickHandler(movement) 162 | return 163 | } 164 | // Select the feature if it's not already selected 165 | if (selected.feature === pickedFeature) { 166 | return 167 | } 168 | selected.feature = pickedFeature 169 | // Save the selected feature's original color 170 | if (pickedFeature === highlighted.feature) { 171 | Cesium.Color.clone(highlighted.originalColor, selected.originalColor) 172 | highlighted.feature = undefined 173 | } else { 174 | Cesium.Color.clone(pickedFeature.color, selected.originalColor) 175 | } 176 | // Highlight newly selected feature 177 | pickedFeature.color = Cesium.Color.LIME 178 | // Set feature infobox description 179 | const featureName = pickedFeature.getProperty('name') 180 | selectedEntity.name = featureName 181 | selectedEntity.description = 'Loading
' 182 | viewer.selectedEntity = selectedEntity 183 | selectedEntity.description = 184 | `${'' + '` + 187 | `` + 188 | `` + 189 | `` + 190 | `` + 191 | `` + 192 | `` + 193 | `
BIN'}${pickedFeature.getProperty( 185 | 'BIN', 186 | )}
DOITT ID${pickedFeature.getProperty('DOITT_ID')}
SOURCE ID${pickedFeature.getProperty('SOURCE_ID')}
Longitude${pickedFeature.getProperty('longitude')}
Latitude${pickedFeature.getProperty('latitude')}
Height${pickedFeature.getProperty('height')}
Terrain Height (Ellipsoid)${pickedFeature.getProperty('TerrainHeight')}
` 194 | }, Cesium.ScreenSpaceEventType.LEFT_CLICK) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/hooks/functional/useRotate.ts: -------------------------------------------------------------------------------- 1 | import useCesium from '/@/hooks/useCesium' 2 | 3 | /** 4 | * 3d地图自动旋转 5 | * @param viewer 地图实例 6 | * @returns 开始和结束的旋转 7 | */ 8 | export default function useGlobalRotate(viewer: ElRef) { 9 | const Cesium = useCesium() 10 | const autoRotate = () => { 11 | if (!viewer || viewer.scene.mode !== Cesium.SceneMode.SCENE3D) { 12 | return false 13 | } 14 | const icrfToFixed = Cesium.Transforms.computeIcrfToFixedMatrix(viewer.clock.currentTime) 15 | if (Cesium.defined(icrfToFixed)) { 16 | const offset = Cesium.Cartesian3.clone(viewer.camera.position) 17 | const transform = Cesium.Matrix4.fromRotationTranslation(icrfToFixed) 18 | // 偏移相机,否则会场景旋转而地球不转 19 | viewer.camera.lookAtTransform(transform, offset) 20 | } 21 | } 22 | 23 | // 获取矩阵及更新相机 24 | const startRotate = (option: {}) => { 25 | // 监听每次渲染前执行矩阵求解 26 | viewer.scene.postUpdate.addEventListener(autoRotate) 27 | // 根据option修改一些参数 28 | if (viewer.clock) { 29 | const keys = Object.keys(option) 30 | for (const k of keys) { 31 | viewer.clock[k] = option[k] 32 | } 33 | } 34 | } 35 | 36 | // 移除监听 37 | const stopRotate = () => { 38 | viewer.clock.multiplier = 1 39 | viewer.scene.postUpdate.removeEventListener(autoRotate) 40 | } 41 | 42 | return { 43 | startRotate, 44 | stopRotate, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/hooks/measure/area.ts: -------------------------------------------------------------------------------- 1 | import useCesium from '/@/hooks/useCesium' 2 | const Cesium = useCesium() 3 | 4 | //测量空间面积 5 | export default function useMeasureArea(viewer: ElRef) { 6 | const _this: any = {} 7 | // 取消双击事件-追踪该位置 8 | viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) 9 | // 鼠标事件 10 | _this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene._imageryLayerCollection) 11 | const positions = [] 12 | const tempPoints = [] 13 | let polygon = null 14 | let areaText = null 15 | let cartesian: any = null 16 | const areaArray = [] 17 | let floatingPoint 18 | 19 | _this.handler.setInputAction(function (movement) { 20 | const ray = viewer.camera.getPickRay(movement.endPosition) 21 | cartesian = viewer.scene.globe.pick(ray, viewer.scene) 22 | if (positions.length >= 2) { 23 | if (!Cesium.defined(polygon)) { 24 | polygon = new PolygonPrimitive(positions) 25 | } else { 26 | positions.pop() 27 | positions.push(cartesian) 28 | } 29 | } 30 | }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) 31 | 32 | _this.handler.setInputAction(function (movement) { 33 | const ray = viewer.camera.getPickRay(movement.position) 34 | cartesian = viewer.scene.globe.pick(ray, viewer.scene) 35 | if (positions.length == 0) { 36 | positions.push(cartesian.clone()) 37 | } 38 | positions.push(cartesian) 39 | //在三维场景中添加点 40 | const cartographic = Cesium.Cartographic.fromCartesian(positions[positions.length - 1]) 41 | const longitudeString = Cesium.Math.toDegrees(cartographic.longitude) 42 | const latitudeString = Cesium.Math.toDegrees(cartographic.latitude) 43 | const heightString = cartographic.height 44 | tempPoints.push({ lon: longitudeString, lat: latitudeString, hei: heightString }) 45 | floatingPoint = viewer.entities.add({ 46 | name: '多边形面积', 47 | position: positions[positions.length - 1], 48 | point: { 49 | pixelSize: 5, 50 | color: Cesium.Color.RED, 51 | outlineColor: Cesium.Color.WHITE, 52 | outlineWidth: 2, 53 | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, 54 | }, 55 | }) 56 | areaArray.push(floatingPoint) 57 | _this.areapointArray = areaArray 58 | }, Cesium.ScreenSpaceEventType.LEFT_CLICK) 59 | 60 | _this.handler.setInputAction(function () { 61 | _this.handler.destroy() 62 | positions.pop() 63 | 64 | const textArea = getArea(tempPoints) + '平方公里' 65 | areaText = viewer.entities.add({ 66 | name: '多边形面积', 67 | position: positions[positions.length - 1], 68 | point: { 69 | pixelSize: 5, 70 | color: Cesium.Color.RED, 71 | outlineColor: Cesium.Color.WHITE, 72 | outlineWidth: 2, 73 | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, 74 | }, 75 | label: { 76 | text: textArea, 77 | font: '18px sans-serif', 78 | fillColor: Cesium.Color.GOLD, 79 | style: Cesium.LabelStyle.FILL_AND_OUTLINE, 80 | outlineWidth: 2, 81 | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, 82 | pixelOffset: new Cesium.Cartesian2(20, -40), 83 | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, 84 | }, 85 | }) 86 | 87 | areaArray.push(areaText) 88 | _this.areapointArray = areaArray 89 | }, Cesium.ScreenSpaceEventType.RIGHT_CLICK) 90 | 91 | const radiansPerDegree = Math.PI / 180.0 //角度转化为弧度(rad) 92 | const degreesPerRadian = 180.0 / Math.PI //弧度转化为角度 93 | 94 | //计算多边形面积 95 | function getArea(points) { 96 | let res = 0 97 | //拆分三角曲面 98 | 99 | for (let i = 0; i < points.length - 2; i++) { 100 | const j = (i + 1) % points.length 101 | const k = (i + 2) % points.length 102 | const totalAngle = Angle(points[i], points[j], points[k]) 103 | 104 | const dis_temp1 = distance(positions[i], positions[j]) 105 | const dis_temp2 = distance(positions[j], positions[k]) 106 | res += dis_temp1 * dis_temp2 * Math.abs(Math.sin(totalAngle)) 107 | // console.log(res); 108 | } 109 | 110 | return (res / 1000000.0).toFixed(4) 111 | } 112 | 113 | //角度 114 | function Angle(p1, p2, p3) { 115 | const bearing21 = Bearing(p2, p1) 116 | const bearing23 = Bearing(p2, p3) 117 | let angle = bearing21 - bearing23 118 | if (angle < 0) { 119 | angle += 360 120 | } 121 | return angle 122 | } 123 | 124 | //方向 125 | function Bearing(from, to) { 126 | const lat1 = from.lat * radiansPerDegree 127 | const lon1 = from.lon * radiansPerDegree 128 | const lat2 = to.lat * radiansPerDegree 129 | const lon2 = to.lon * radiansPerDegree 130 | let angle = -Math.atan2( 131 | Math.sin(lon1 - lon2) * Math.cos(lat2), 132 | Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon1 - lon2), 133 | ) 134 | if (angle < 0) { 135 | angle += Math.PI * 2.0 136 | } 137 | angle = angle * degreesPerRadian //角度 138 | return angle 139 | } 140 | 141 | const PolygonPrimitive = (function () { 142 | function _(positions) { 143 | _this.options = { 144 | id: 'areaPolygon', 145 | name: '多边形', 146 | polygon: { 147 | hierarchy: [], 148 | material: Cesium.Color.GREEN.withAlpha(0.5), 149 | heightReference: 20000, 150 | }, 151 | } 152 | 153 | _this.hierarchy = { positions } 154 | 155 | const _update = function () { 156 | return _this.hierarchy 157 | } 158 | //实时更新polygon.hierarchy 159 | _this.options.polygon.hierarchy = new Cesium.CallbackProperty(_update, false) 160 | viewer.entities.add(_this.options) 161 | } 162 | 163 | return _ 164 | })() 165 | 166 | function distance(point1, point2) { 167 | const point1cartographic = Cesium.Cartographic.fromCartesian(point1) 168 | const point2cartographic = Cesium.Cartographic.fromCartesian(point2) 169 | //根据经纬度计算出距离 170 | const geodesic = new Cesium.EllipsoidGeodesic() 171 | geodesic.setEndPoints(point1cartographic, point2cartographic) 172 | let s = geodesic.surfaceDistance 173 | //返回两点之间的距离 174 | s = Math.sqrt(Math.pow(s, 2) + Math.pow(point2cartographic.height - point1cartographic.height, 2)) 175 | return s 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/hooks/measure/line.ts: -------------------------------------------------------------------------------- 1 | import useCesium from '/@/hooks/useCesium' 2 | 3 | const Cesium = useCesium() 4 | 5 | /** 6 | * 距离测量 -- 线长度 7 | * @param viewer 3d地图实例 8 | */ 9 | export default function useMeasureLineSpace(viewer: ElRef) { 10 | const _this: any = {} 11 | // 取消双击事件-追踪该位置 12 | viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) 13 | 14 | _this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene._imageryLayerCollection) 15 | const positions = [] 16 | let poly = null 17 | let distance = 0 18 | let cartesian: any = null 19 | const floatingPointArray = [] 20 | 21 | // 鼠标移动事件 22 | _this.handler.setInputAction(function (movement) { 23 | const ray = viewer.camera.getPickRay(movement.endPosition) 24 | cartesian = viewer.scene.globe.pick(ray, viewer.scene) 25 | if (positions.length >= 2) { 26 | if (!Cesium.defined(poly)) { 27 | poly = new PolyLinePrimitive(positions) 28 | } else { 29 | positions.pop() 30 | positions.push(cartesian) 31 | } 32 | distance = getSpaceDistance(positions) 33 | } 34 | }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) 35 | 36 | // 鼠标左键点击事件 37 | _this.handler.setInputAction(function (movement) { 38 | const ray = viewer.camera.getPickRay(movement.position) 39 | cartesian = viewer.scene.globe.pick(ray, viewer.scene) 40 | if (positions.length == 0) { 41 | positions.push(cartesian.clone()) 42 | } 43 | positions.push(cartesian) 44 | //在三维场景中添加Label 45 | const textDistance = distance + '米' 46 | _this.floatingPoint = viewer.entities.add({ 47 | name: '空间直线距离', 48 | position: positions[positions.length - 1], 49 | point: { 50 | pixelSize: 4, 51 | color: Cesium.Color.RED, 52 | outlineColor: Cesium.Color.WHITE, 53 | outlineWidth: 2, 54 | }, 55 | label: { 56 | text: textDistance, 57 | font: '18px sans-serif', 58 | fillColor: Cesium.Color.GOLD, 59 | style: Cesium.LabelStyle.FILL_AND_OUTLINE, 60 | outlineWidth: 2, 61 | verticalOrigin: Cesium.VerticalOrigin.BOTTOM, 62 | pixelOffset: new Cesium.Cartesian2(20, -20), 63 | }, 64 | }) 65 | 66 | floatingPointArray.push(_this.floatingPoint) 67 | }, Cesium.ScreenSpaceEventType.LEFT_CLICK) 68 | 69 | // 鼠标右键点击事件 70 | _this.handler.setInputAction(function () { 71 | _this.handler.destroy() //关闭事件句柄 72 | positions.pop() //最后一个点无效 73 | floatingPointArray.pop() 74 | }, Cesium.ScreenSpaceEventType.RIGHT_CLICK) 75 | 76 | const PolyLinePrimitive = (function () { 77 | function _(positions) { 78 | _this.options = { 79 | id: 'distancePolyline', 80 | name: '直线', 81 | polyline: { 82 | show: true, 83 | positions: [], 84 | material: Cesium.Color.CHARTREUSE, 85 | width: 2, 86 | clampToGround: true, 87 | }, 88 | } 89 | _this.positions = positions 90 | // 实时更新线的位置 91 | const _update = function () { 92 | return _this.positions 93 | } 94 | // 实时更新 polyline.positions 95 | _this.options.polyline.positions = new Cesium.CallbackProperty(_update, false) 96 | viewer.entities.add(_this.options) 97 | } 98 | 99 | return _ 100 | })() 101 | 102 | //空间两点距离计算函数 103 | function getSpaceDistance(positions) { 104 | let distance = 0 105 | for (let i = 0; i < positions.length - 1; i++) { 106 | const point1cartographic = Cesium.Cartographic.fromCartesian(positions[i]) 107 | const point2cartographic = Cesium.Cartographic.fromCartesian(positions[i + 1]) 108 | /**根据经纬度计算出距离**/ 109 | const geodesic = new Cesium.EllipsoidGeodesic() 110 | geodesic.setEndPoints(point1cartographic, point2cartographic) 111 | let s = geodesic.surfaceDistance 112 | //返回两点之间的距离 113 | s = Math.sqrt(Math.pow(s, 2) + Math.pow(point2cartographic.height - point1cartographic.height, 2)) 114 | distance = distance + s 115 | } 116 | return Number(distance.toFixed(2)) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/hooks/removeTools.ts: -------------------------------------------------------------------------------- 1 | import useCesium from '/@/hooks/useCesium' 2 | const Cesium = useCesium() 3 | 4 | export default function useRemoveTools() { 5 | /** 6 | * 清除测量数据 7 | * @param viewer 3d地图实例 8 | * @param handler 事件处理器 9 | * @param arrayEntities 删除实体 10 | */ 11 | const removeEntities = (viewer: ElRef, arrayEntities: Array) => { 12 | let handler: any = new Cesium.ScreenSpaceEventHandler(viewer.scene._imageryLayerCollection) 13 | if (handler) { 14 | handler.destroy() 15 | handler = null 16 | } 17 | if (arrayEntities && Array.isArray(arrayEntities)) { 18 | for (let i = 0; i < arrayEntities.length; i++) { 19 | viewer.entities.remove(arrayEntities[i]) 20 | } 21 | } 22 | } 23 | /** 24 | * 删除primitives实体 25 | * @param viewer 3d地图实例 26 | */ 27 | const removePrimitives = (viewer: ElRef) => { 28 | viewer.scene.primitives.removeAll() 29 | } 30 | 31 | /** 32 | * 删除所有实体 33 | * @param viewer 3d地图实例 34 | */ 35 | const removeAllDraw = (viewer: ElRef, deleteType: Array = ['点', '线', '面']) => { 36 | // viewer.entities.removeAll() 37 | const drawEntities = viewer.entities._entities._array 38 | const length = drawEntities.length 39 | // 倒叙遍历防止实体减少之后 不存在 40 | for (let f = length - 1; f >= 0; f--) { 41 | if (drawEntities[f]._name && deleteType.includes(drawEntities[f]._name)) { 42 | viewer.entities.remove(drawEntities[f]) 43 | } 44 | } 45 | } 46 | return { 47 | removeEntities, 48 | removePrimitives, 49 | removeAllDraw, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/hooks/useCesium.ts: -------------------------------------------------------------------------------- 1 | import * as Cesium from 'cesium' 2 | 3 | export default function useCesium() { 4 | return { ...Cesium } 5 | } 6 | -------------------------------------------------------------------------------- /src/hooks/useCesiumMap.ts: -------------------------------------------------------------------------------- 1 | import useCesium from '/@/hooks/useCesium' 2 | const Cesium = useCesium() 3 | 4 | import nProgress from 'nprogress' 5 | import { useAppStore } from '/@/store/modules/app' 6 | 7 | /** 8 | * 初始化 Cesium 地图 9 | * @param viewerName 地图类型 10 | * @param extendConf 地图其他配置 11 | * @returns 实例化的cesium viewer 12 | */ 13 | export default function useCesiumMap(viewerName = 'cesium3DContainer', extendConf?: any) { 14 | nProgress.start() 15 | 16 | // 设置使用的token 17 | Cesium.Ion.defaultAccessToken = 18 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiZjgwM2VkMy0wOTQxLTRlMDQtOTA3NC02ZDJhNmFlYWI2M2MiLCJpZCI6OTMyNzEsImlhdCI6MTY1MzYxNTc3MX0.PZXaawvZhCgcahjwZFrmfXRtzvgF5_Vq7S1RtHO0sE8' 19 | 20 | // 设置查看的默认矩形(当前设置在中国) 21 | Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(80, 22, 130, 50) 22 | // 配置参数 23 | const baseConf = { 24 | // imageryProvider: false, 25 | selectionIndicator: false, // 去掉框选 26 | showRenderLoopErrors: false, 27 | baseLayerPicker: false, // 基础影响图层选择器 28 | navigationHelpButton: false, // 导航帮助按钮 29 | animation: false, // 动画控件 30 | timeline: false, // 时间控件 31 | shadows: false, // 显示阴影 32 | shouldAnimate: true, // 模型动画效果 大气 33 | // skyBox: false, // 天空盒 34 | infoBox: false, // 显示 信息框 35 | fullscreenButton: false, // 是否显示全屏按钮 36 | homeButton: true, // 是否显示首页按钮 37 | geocoder: false, // 默认不显示搜索栏地址 38 | sceneModePicker: true, // 是否显示视角切换按钮 39 | requestRenderMode: true, //启用请求渲染模式 40 | scene3DOnly: false, //每个几何实例将只能以3D渲染以节省GPU内存 41 | sceneMode: 3, //初始场景模式 1 2D模式 2 2D循环模式 3 3D模式 Cesium.SceneMode 42 | } 43 | const viewer = new Cesium.Viewer(viewerName, { 44 | ...baseConf, 45 | ...extendConf, 46 | // imageryProvider: new Cesium.ArcGisMapServerImageryProvider({ 47 | // // url: 'http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer', 48 | // // arcGIS三维地图 49 | // url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer', 50 | // }), 51 | }) 52 | // 加载Cesium 官网的地形,亦可以加载自己的地形 53 | // const terrainLayer = new Cesium.CesiumTerrainProvider({ 54 | // url: Cesium.IonResource.fromAssetId(1), 55 | // requestWaterMask: true, 56 | // requestVertexNormals: true, 57 | // }) 58 | // viewer.scene.terrainProvider = terrainLayer 59 | 60 | viewer.imageryLayers.addImageryProvider(new Cesium.IonImageryProvider({ assetId: 3 })) 61 | 62 | viewer.scene.globe.enableLighting = true 63 | // 显示 fps 64 | viewer.scene.debugShowFramesPerSecond = false 65 | 66 | viewer.camera.setView({ 67 | // Cesium的坐标是以地心为原点,一向指向南美洲,一向指向亚洲,一向指向北极州 68 | // fromDegrees()方法,将经纬度和高程转换为世界坐标 69 | destination: Cesium.Cartesian3.fromDegrees(104, 30, 10000000), 70 | orientation: { 71 | // 指向 72 | heading: Cesium.Math.toRadians(0), 73 | // 视角 74 | pitch: Cesium.Math.toRadians(-90), 75 | roll: 0.0, 76 | }, 77 | }) 78 | viewer.clock.shouldAnimate = true 79 | 80 | window.CViewer = viewer 81 | 82 | const appStore = useAppStore() 83 | 84 | const helper = new Cesium.EventHelper() 85 | helper.add(viewer.scene.globe.tileLoadProgressEvent, (e) => { 86 | if (e > 20 || e === 0) { 87 | // console.log('矢量切片加载完成时的回调') 88 | nProgress.done() 89 | appStore.setPageLoading(false) 90 | } else { 91 | // console.log('地图资源加载中') 92 | } 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /src/hooks/useHeat.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LenGxien/vue-vite-cesium/bb4abd4d51f398267767b3768582e86e1abc4201/src/hooks/useHeat.ts -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | import { createApp } from 'vue' 3 | import App from './App.vue' 4 | 5 | import router from './router' 6 | import { setupStore } from '/@/store' 7 | 8 | // 由于插件无法处理非组件模块 样式需手动加载 9 | import { message } from 'ant-design-vue' 10 | import 'ant-design-vue/es/message/style/css' 11 | 12 | const app = createApp(App) 13 | 14 | app.use(router).use(createPinia()).mount('#app') 15 | 16 | setupStore(app) 17 | 18 | app.config.globalProperties.$message = message 19 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' 2 | import Base from '/@/views/base.vue' 3 | import osm from '/@/views/osm-building.vue' 4 | import tile from '/@/views/3d-tile.vue' 5 | 6 | const routes: Array = [ 7 | { 8 | path: '/', 9 | name: 'base', 10 | component: Base, 11 | }, 12 | { 13 | path: '/osm', 14 | name: 'osm', 15 | component: osm, 16 | }, 17 | { 18 | path: '/3d-tiles', 19 | name: 'tile', 20 | component: tile, 21 | }, 22 | ] 23 | 24 | const router = createRouter({ 25 | history: createWebHashHistory(), 26 | routes, 27 | }) 28 | 29 | export default router 30 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import { createPinia } from 'pinia' 3 | 4 | const store = createPinia() 5 | 6 | export function setupStore(app: App) { 7 | app.use(store) 8 | } 9 | 10 | export { store } 11 | -------------------------------------------------------------------------------- /src/store/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { store } from '/@/store' 3 | 4 | interface AppState { 5 | // Page loading status 6 | pageLoading: boolean 7 | } 8 | 9 | export const useAppStore = defineStore({ 10 | id: 'app', 11 | state: (): AppState => ({ 12 | pageLoading: true, 13 | }), 14 | getters: { 15 | getPageLoading(): boolean { 16 | return this.pageLoading 17 | }, 18 | }, 19 | actions: { 20 | setPageLoading(loading: boolean): void { 21 | this.pageLoading = loading 22 | }, 23 | }, 24 | }) 25 | 26 | // Need to be used outside the setup 27 | export function useAppStoreWithOut() { 28 | return useAppStore(store) 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/axios.ts: -------------------------------------------------------------------------------- 1 | import Axios from 'axios' 2 | 3 | const baseURL = 'https://api.github.com' 4 | 5 | const axios = Axios.create({ 6 | baseURL, 7 | timeout: 20000, // 请求超时 20s 8 | }) 9 | 10 | // 前置拦截器(发起请求之前的拦截) 11 | axios.interceptors.request.use( 12 | (response) => { 13 | /** 14 | * 根据你的项目实际情况来对 config 做处理 15 | * 这里对 config 不做任何处理,直接返回 16 | */ 17 | return response 18 | }, 19 | (error) => { 20 | return Promise.reject(error) 21 | }, 22 | ) 23 | 24 | // 后置拦截器(获取到响应时的拦截) 25 | axios.interceptors.response.use( 26 | (response) => { 27 | /** 28 | * 根据你的项目实际情况来对 response 和 error 做处理 29 | * 这里对 response 和 error 不做任何处理,直接返回 30 | */ 31 | return response 32 | }, 33 | (error) => { 34 | if (error.response && error.response.data) { 35 | const code = error.response.status 36 | const msg = error.response.data.message 37 | // 错误逻辑处理 38 | console.error(`[Axios Error]`, error.response) 39 | console.log(code, msg) 40 | } else { 41 | // 错误逻辑处理 42 | } 43 | return Promise.reject(error) 44 | }, 45 | ) 46 | 47 | export default axios 48 | -------------------------------------------------------------------------------- /src/views/3d-tile.vue: -------------------------------------------------------------------------------- 1 | 4 | 20 | -------------------------------------------------------------------------------- /src/views/base.vue: -------------------------------------------------------------------------------- 1 | 5 | 27 | -------------------------------------------------------------------------------- /src/views/osm-building.vue: -------------------------------------------------------------------------------- 1 | 4 | 20 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | plugins: ['stylelint-order'], 4 | customSyntax: 'postcss-html', 5 | extends: ['stylelint-config-standard', 'stylelint-config-prettier'], 6 | rules: { 7 | 'selector-class-pattern': null, 8 | 'selector-pseudo-class-no-unknown': [ 9 | true, 10 | { 11 | ignorePseudoClasses: ['global'], 12 | }, 13 | ], 14 | 'selector-pseudo-element-no-unknown': [ 15 | true, 16 | { 17 | ignorePseudoElements: ['v-deep'], 18 | }, 19 | ], 20 | 'at-rule-no-unknown': [ 21 | true, 22 | { 23 | ignoreAtRules: [ 24 | 'tailwind', 25 | 'apply', 26 | 'variants', 27 | 'responsive', 28 | 'screen', 29 | 'function', 30 | 'if', 31 | 'each', 32 | 'include', 33 | 'mixin', 34 | ], 35 | }, 36 | ], 37 | 'no-empty-source': null, 38 | 'named-grid-areas-no-invalid': null, 39 | 'unicode-bom': 'never', 40 | 'no-descending-specificity': null, 41 | 'font-family-no-missing-generic-family-keyword': null, 42 | 'declaration-colon-space-after': 'always-single-line', 43 | 'declaration-colon-space-before': 'never', 44 | // 'declaration-block-trailing-semicolon': 'always', 45 | 'rule-empty-line-before': [ 46 | 'always', 47 | { 48 | ignore: ['after-comment', 'first-nested'], 49 | }, 50 | ], 51 | 'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }], 52 | 'order/order': [ 53 | [ 54 | 'dollar-variables', 55 | 'custom-properties', 56 | 'at-rules', 57 | 'declarations', 58 | { 59 | type: 'at-rule', 60 | name: 'supports', 61 | }, 62 | { 63 | type: 'at-rule', 64 | name: 'media', 65 | }, 66 | 'rules', 67 | ], 68 | { severity: 'warning' }, 69 | ], 70 | }, 71 | ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'], 72 | overrides: [ 73 | { 74 | files: ['*.vue', '**/*.vue', '*.html', '**/*.html'], 75 | extends: ['stylelint-config-recommended', 'stylelint-config-html'], 76 | rules: { 77 | 'keyframes-name-pattern': null, 78 | 'selector-pseudo-class-no-unknown': [ 79 | true, 80 | { 81 | ignorePseudoClasses: ['deep', 'global'], 82 | }, 83 | ], 84 | 'selector-pseudo-element-no-unknown': [ 85 | true, 86 | { 87 | ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'], 88 | }, 89 | ], 90 | }, 91 | }, 92 | ], 93 | } 94 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "noLib": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strictFunctionTypes": false, 11 | "jsx": "preserve", 12 | "baseUrl": ".", 13 | "allowJs": true, 14 | "sourceMap": true, 15 | "esModuleInterop": true, 16 | "resolveJsonModule": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "experimentalDecorators": true, 20 | "lib": ["dom", "esnext"], 21 | "noImplicitAny": false, 22 | "skipLibCheck": true, 23 | "removeComments": true, 24 | "paths": { 25 | "/@/*": ["src/*"], 26 | "/#/*": ["types/*"] 27 | } 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.d.ts", 32 | "src/**/*.tsx", 33 | "src/**/*.vue", 34 | "types/**/*.d.ts", 35 | "types/**/*.ts", 36 | "build/**/*.ts", 37 | "build/**/*.d.ts", 38 | "mock/**/*.ts", 39 | "vite.config.ts", 40 | "components.d.ts" 41 | ], 42 | "exclude": ["node_modules", "dist", "**/*.js"] 43 | } 44 | -------------------------------------------------------------------------------- /types/env.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ViteEnv { 2 | VITE_PORT: number 3 | VITE_USE_MOCK: boolean 4 | VITE_USE_PWA: boolean 5 | VITE_PUBLIC_PATH: string 6 | VITE_PROXY: [string, string][] 7 | VITE_GLOB_APP_TITLE: string 8 | VITE_GLOB_APP_SHORT_NAME: string 9 | VITE_USE_CDN: boolean 10 | VITE_DROP_CONSOLE: boolean 11 | VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none' 12 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean 13 | VITE_LEGACY: boolean 14 | VITE_USE_IMAGEMIN: boolean 15 | VITE_GENERATE_UI: string 16 | } 17 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare type ElRef = Nullable 2 | 3 | declare global { 4 | interface Window { 5 | CViewer: any 6 | } 7 | } 8 | 9 | declare type Recordable = Record 10 | -------------------------------------------------------------------------------- /types/module.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | 8 | declare global { 9 | interface Window { 10 | CViewer: any 11 | linePointArray: Array 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig, ConfigEnv } from 'vite' 2 | import { loadEnv } from 'vite' 3 | import cesium from 'vite-plugin-cesium' 4 | import { createProxy } from './build/vite/proxy' 5 | import { wrapperEnv } from './build/utils' 6 | import { createVitePlugins } from './build/vite/plugin' 7 | 8 | import { resolve } from 'path' 9 | 10 | function pathResolve(dir: string) { 11 | return resolve(process.cwd(), '.', dir) 12 | } 13 | 14 | export default ({ command, mode }: ConfigEnv): UserConfig => { 15 | const root = process.cwd() 16 | 17 | const env = loadEnv(mode, root) 18 | 19 | // The boolean type read by loadEnv is a string. This function can be converted to boolean type 20 | const viteEnv = wrapperEnv(env) 21 | 22 | const { VITE_PORT, VITE_PUBLIC_PATH, VITE_PROXY, VITE_DROP_CONSOLE } = viteEnv 23 | 24 | const isBuild = command === 'build' 25 | 26 | return { 27 | base: VITE_PUBLIC_PATH, 28 | root, 29 | plugins: [ 30 | ...createVitePlugins(viteEnv, isBuild), 31 | cesium({ 32 | rebuildCesium: true, 33 | }), 34 | ], 35 | resolve: { 36 | // 路径别名 37 | alias: [ 38 | { 39 | find: /\/@\//, 40 | replacement: pathResolve('src') + '/', 41 | }, 42 | { 43 | find: /\/#\//, 44 | replacement: pathResolve('types') + '/', 45 | }, 46 | ], 47 | }, 48 | server: { 49 | host: true, 50 | open: true, 51 | port: VITE_PORT, 52 | // Load proxy configuration from .env 53 | proxy: createProxy(VITE_PROXY), 54 | }, 55 | build: { 56 | target: 'es2015', 57 | terserOptions: { 58 | compress: { 59 | keep_infinity: true, 60 | drop_console: VITE_DROP_CONSOLE, 61 | }, 62 | }, 63 | minify: 'terser', 64 | brotliSize: false, 65 | chunkSizeWarningLimit: 2000, 66 | }, 67 | } 68 | } 69 | --------------------------------------------------------------------------------