diff --git a/.electron-vite/afterPack.js b/.electron-vite/afterPack.js new file mode 100644 index 0000000..d93e36c --- /dev/null +++ b/.electron-vite/afterPack.js @@ -0,0 +1,17 @@ +// pack后对不同位数的系统迁移不同的文件 +'use strict'; +const { copySync, ensureDirSync } = require('fs-extra'); +const { join } = require('path'); +const { Arch } = require('electron-builder'); +// type ElectronPlatformName = "darwin" | "linux" | "win32" | "mas" +exports.default = async context => { + const LIB_OUTPUT_DIR = context.appOutDir; + const LIB_INPUT_DIR = join("rootLib", context.electronPlatformName, Arch[context.arch]); + const LIB_COMMON_INPUT_DIR = join("rootLib", "common"); + // 确保文件夹存在 + ensureDirSync(LIB_COMMON_INPUT_DIR); + ensureDirSync(LIB_INPUT_DIR); + // 移动文件文件 + copySync(LIB_INPUT_DIR, LIB_OUTPUT_DIR); + copySync(LIB_COMMON_INPUT_DIR, LIB_OUTPUT_DIR); +}; \ No newline at end of file diff --git a/.electron-vite/build.ts b/.electron-vite/build.ts new file mode 100644 index 0000000..6befff8 --- /dev/null +++ b/.electron-vite/build.ts @@ -0,0 +1,94 @@ +process.env.NODE_ENV = 'production' + +import { join } from 'path' +import { say } from 'cfonts' +import { sync } from 'del' +import { build } from 'vite' +import chalk from 'chalk' +import { rollup, OutputOptions } from 'rollup' +import Multispinner from 'Multispinner' +import rollupOptions from './rollup.config' +import { okayLog, errorLog, doneLog } from './log' + + +const mainOpt = rollupOptions(process.env.NODE_ENV, "main"); +const isCI = process.env.CI || false + + +if (process.env.BUILD_TARGET === 'web') web() +else unionBuild() + +function clean() { + sync(['dist/electron/main/*', 'dist/electron/renderer/*', 'dist/web/*', 'build/*', '!build/icons', '!build/lib', '!build/lib/electron-build.*', '!build/icons/icon.*']) + console.log(`\n${doneLog}clear done`) + if (process.env.BUILD_TARGET === 'onlyClean') process.exit() +} + +function unionBuild() { + greeting() + if (process.env.BUILD_TARGET === 'clean' || process.env.BUILD_TARGET === 'onlyClean') clean() + + const tasks = ['main', 'renderer'] + const m = new Multispinner(tasks, { + preText: 'building', + postText: 'process' + }) + let results = '' + + m.on('success', () => { + process.stdout.write('\x1B[2J\x1B[0f') + console.log(`\n\n${results}`) + console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`) + process.exit() + }) + + rollup(mainOpt) + .then(build => { + results += `${doneLog}MainProcess build success` + '\n\n' + build.write(mainOpt.output as OutputOptions).then(() => { + m.success('main') + }) + }) + .catch(error => { + m.error('main') + console.log(`\n ${errorLog}failed to build main process`) + console.error(`\n${error}\n`) + process.exit(1) + }); + + build({ configFile: join(__dirname, 'vite.config') }).then(res => { + results += `${doneLog}RendererProcess build success` + '\n\n' + m.success('renderer') + }).catch(err => { + m.error('renderer') + console.log(`\n ${errorLog}failed to build renderer process`) + console.error(`\n${err}\n`) + process.exit(1) + }) +} + +function web() { + sync(['dist/web/*', '!.gitkeep']) + build({ configFile: join(__dirname, 'vite.config') }).then(res => { + console.log(`${doneLog}RendererProcess build success`) + process.exit() + }) +} + +function greeting() { + const cols = process.stdout.columns + let text: boolean | string = '' + + if (cols > 85) text = `let's-build` + else if (cols > 60) text = `let's-|build` + else text = false + + if (text && !isCI) { + say(text, { + colors: ['yellow'], + font: 'simple3d', + space: false + }) + } else console.log(chalk.yellow.bold(`\n let's-build`)) + console.log() +} \ No newline at end of file diff --git a/.electron-vite/dev-runner.ts b/.electron-vite/dev-runner.ts new file mode 100644 index 0000000..11e9415 --- /dev/null +++ b/.electron-vite/dev-runner.ts @@ -0,0 +1,189 @@ +process.env.NODE_ENV = 'development' + +import electron from 'electron'; +import chalk from 'chalk'; +import { join } from 'path'; +import { watch } from 'rollup'; +import Portfinder from 'Portfinder'; +import config from '../config' +import { say } from 'cfonts'; +import { spawn } from 'child_process'; +import type { ChildProcess } from 'child_process' +import { createServer } from 'vite' +import rollupOptions from './rollup.config' + + +const mainOpt = rollupOptions(process.env.NODE_ENV, "main"); + +let electronProcess: ChildProcess | null = null +let manualRestart = false + +function logStats(proc: string, data: any) { + let log = '' + + log += chalk.yellow.bold(`┏ ${proc} ${config.dev.chineseLog ? '编译过程' : 'Process'} ${new Array((19 - proc.length) + 1).join('-')}`) + log += '\n\n' + + if (typeof data === 'object') { + data.toString({ + colors: true, + chunks: false + }).split(/\r?\n/).forEach(line => { + log += ' ' + line + '\n' + }) + } else { + log += ` ${data}\n` + } + + log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' + console.log(log) +} + +function removeJunk(chunk: string) { + if (config.dev.removeElectronJunk) { + // Example: 2018-08-10 22:48:42.866 Electron[90311:4883863] *** WARNING: Textured window + if (/\d+-\d+-\d+ \d+:\d+:\d+\.\d+ Electron(?: Helper)?\[\d+:\d+] /.test(chunk)) { + return false; + } + + // Example: [90789:0810/225804.894349:ERROR:CONSOLE(105)] "Uncaught (in promise) Error: Could not instantiate: ProductRegistryImpl.Registry", source: chrome-devtools://devtools/bundled/inspector.js (105) + if (/\[\d+:\d+\/|\d+\.\d+:ERROR:CONSOLE\(\d+\)\]/.test(chunk)) { + return false; + } + + // Example: ALSA lib confmisc.c:767:(parse_card) cannot find card '0' + if (/ALSA lib [a-z]+\.c:\d+:\([a-z_]+\)/.test(chunk)) { + return false; + } + } + + + return chunk; +} + +function startRenderer(): Promise { + return new Promise((resolve, reject) => { + Portfinder.basePort = config.dev.port || 9080 + Portfinder.getPort(async (err, port) => { + if (err) { + reject("PortError:" + err) + } else { + const server = await createServer({ configFile: join(__dirname, 'vite.config') }) + process.env.PORT = String(port) + await server.listen(port) + console.log('\n\n' + chalk.blue(`${config.dev.chineseLog ? ' 正在准备主进程,请等待...' : ' Preparing main process, please wait...'}`) + '\n\n') + resolve() + } + }) + }) +} + +function startMain(): Promise { + return new Promise((resolve, reject) => { + const MainWatcher = watch(mainOpt); + MainWatcher.on('change', filename => { + // 主进程日志部分 + logStats(`${config.dev.chineseLog ? '主进程文件变更' : 'Main-FileChange'}`, filename) + }); + MainWatcher.on('event', event => { + if (event.code === 'END') { + if (electronProcess && electronProcess.kill) { + manualRestart = true + process.kill(electronProcess.pid) + electronProcess = null + startElectron() + + setTimeout(() => { + manualRestart = false + }, 5000) + } + + resolve() + + } else if (event.code === 'ERROR') { + reject(event.error) + } + }) + }) +} + + +function startElectron() { + + var args = [ + '--inspect=5858', + join(__dirname, '../dist/electron/main/main.js') + ] + + // detect yarn or npm and process commandline args accordingly + if (process.env.npm_execpath.endsWith('yarn.js')) { + args = args.concat(process.argv.slice(3)) + } else if (process.env.npm_execpath.endsWith('npm-cli.js')) { + args = args.concat(process.argv.slice(2)) + } + + electronProcess = spawn(electron as any, args) + + electronProcess.stdout.on('data', (data: string) => { + electronLog(removeJunk(data), 'blue') + }) + electronProcess.stderr.on('data', (data: string) => { + electronLog(removeJunk(data), 'red') + }) + + electronProcess.on('close', () => { + if (!manualRestart) process.exit() + }) +} + +function electronLog(data: any, color: string) { + if (data) { + let log = '' + data = data.toString().split(/\r?\n/) + data.forEach(line => { + log += ` ${line}\n` + }) + console.log( + chalk[color].bold(`┏ ${config.dev.chineseLog ? '主程序日志' : 'Electron'} -------------------`) + + '\n\n' + + log + + chalk[color].bold('┗ ----------------------------') + + '\n' + ) + } + +} + +function greeting() { + const cols = process.stdout.columns + let text: string | boolean = '' + + if (cols > 104) text = 'electron-vite' + else if (cols > 76) text = 'electron-|vite' + else text = false + + if (text) { + say(text, { + colors: ['yellow'], + font: 'simple3d', + space: false + }) + } else console.log(chalk.yellow.bold('\n electron-vite')) + console.log(chalk.blue(`${config.dev.chineseLog ? ' 准备启动...' : ' getting ready...'}`) + '\n') +} + +async function init() { + greeting() + + try { + await startRenderer() + await startMain() + startElectron() + } catch (error) { + console.error(error) + process.exit(1) + } + +} + +init() \ No newline at end of file diff --git a/.electron-vite/hot-updater.ts b/.electron-vite/hot-updater.ts new file mode 100644 index 0000000..718d628 --- /dev/null +++ b/.electron-vite/hot-updater.ts @@ -0,0 +1,130 @@ +/** + * power by biuuu + */ + +import chalk from 'chalk' +import { join } from 'path' +import { ensureDir, emptyDir, copy, outputJSON, remove, stat, readFile } from 'fs-extra' +import { createHmac } from 'crypto' +import { platform } from 'os' +import AdmZip from 'adm-zip' +import packageFile from '../package.json' +import buildConfig from '../build.json' +import config from '../config' +import { okayLog, infoLog, errorLog, doneLog } from './log' + + +const platformName = platform().includes('win32') ? 'win' : platform().includes('darwin') ? 'mac' : 'linux' +const buildPath = join('.', 'build', `${platformName === 'mac' ? 'mac' : platformName + '-unpacked'}`) + +const hash = (data, type = 'sha256') => { + const hmac = createHmac(type, 'Sky') + hmac.update(data) + return hmac.digest('hex') +} + +const createZip = (filePath: string, dest: string) => { + const zip = AdmZip() + zip.addLocalFolder(filePath, "", null) + zip.toBuffer() + zip.writeZip(dest, null) +} + +const start = async () => { + console.log(chalk.green.bold(`Start packing \n`)) + + if (buildConfig.asar) { + console.log( + "\n" + + errorLog + + " " + + chalk.red("Please make sure the build.asar option in the Package.json file is set to false") + + "\n" + ); + return; + } + + if (config.build.hotPublishConfigName === '') { + console.log( + "\n" + + errorLog + + " " + + chalk.red("HotPublishConfigName is not set, which will cause the update to fail, please set it in the config/index.js \n") + + chalk.red.bold(`\n Packing failed \n`) + ); + process.exit(1) + } + + stat(join(buildPath, 'resources', 'app'), async (err, stats) => { + if (err) { + console.log( + "\n" + + errorLog + + " " + + chalk.red("No resource files were found, please execute this command after the build command") + + "\n" + ); + return; + } + + try { + console.log(chalk.green.bold(`Check the resource files \n`)) + const packResourcesPath = join('.', 'build', 'resources', 'dist'); + const packPackagePath = join('.', 'build', 'resources'); + const resourcesPath = join('.', 'dist'); + const appPath = join('.', 'build', 'resources'); + const name = "app.zip"; + const outputPath = join('.', 'build', 'update'); + const zipPath = join(outputPath, name); + + + await ensureDir(packResourcesPath); + await emptyDir(packResourcesPath); + await copy(resourcesPath, packResourcesPath); + console.log(`${okayLog} ${chalk.cyan.bold(`File copy complete \n`)}`) + await outputJSON(join(packPackagePath, "package.json"), { + name: packageFile.name, + productName: buildConfig.productName, + version: packageFile.version, + description: packageFile.description, + main: packageFile.main, + author: packageFile.author, + dependencies: packageFile.dependencies + }); + console.log(`${okayLog} ${chalk.cyan.bold(`Rewrite package file complete \n`)}`) + await ensureDir(outputPath); + await emptyDir(outputPath); + createZip(appPath, zipPath); + const buffer = await readFile(zipPath); + const sha256 = hash(buffer); + const hashName = sha256.slice(7, 12); + await copy(zipPath, join(outputPath, `${hashName}.zip`)); + await outputJSON(join(outputPath, `${config.build.hotPublishConfigName}.json`), + { + version: packageFile.version, + name: `${hashName}.zip`, + hash: sha256 + } + ); + console.log(`${okayLog} ${chalk.cyan.bold(`Zip file complete, Start cleaning up redundant files \n`)}`) + await remove(zipPath); + await remove(appPath) + console.log(`${okayLog} ${chalk.cyan.bold(`Cleaning up redundant files completed \n`)}`) + console.log( + "\n" + doneLog + " " + "The resource file is packaged!\n" + ); + console.log("File location: " + chalk.green(outputPath) + "\n"); + } catch (error) { + console.log( + "\n" + + errorLog + + " " + + chalk.red(error.message || error) + + "\n" + ); + process.exit(1) + } + }); +} + +start() \ No newline at end of file diff --git a/.electron-vite/log/index.ts b/.electron-vite/log/index.ts new file mode 100644 index 0000000..ecb7327 --- /dev/null +++ b/.electron-vite/log/index.ts @@ -0,0 +1,7 @@ +import chalk from 'chalk' + +export const doneLog = chalk.bgGreen.white(' DONE ') + ' ' +export const errorLog = chalk.bgRed.white(' ERROR ') + ' ' +export const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' +export const warningLog = chalk.bgYellow.white(' WARNING ') + ' ' +export const infoLog = chalk.bgCyan.white(' INFO ') + ' ' \ No newline at end of file diff --git a/.electron-vite/rollup.config.ts b/.electron-vite/rollup.config.ts new file mode 100644 index 0000000..93cce3d --- /dev/null +++ b/.electron-vite/rollup.config.ts @@ -0,0 +1,85 @@ +import path from "path"; +import { nodeResolve } from '@rollup/plugin-node-resolve' +import { builtinModules } from 'module' +import commonjs from '@rollup/plugin-commonjs' +import replace from '@rollup/plugin-replace' +import alias from '@rollup/plugin-alias' +import json from '@rollup/plugin-json'; +import esbuild from 'rollup-plugin-esbuild' +// import obfuscator from 'rollup-plugin-obfuscator' +import { defineConfig } from 'rollup' + +export default (env = "production", type = "main") => { + return defineConfig({ + input: + type === "main" + ? path.join(__dirname, "..", "src", "main", "index.ts") + : path.join(__dirname, "..", "src", "preload", "index.ts"), + output: { + file: path.join( + __dirname, + "..", "dist", "electron", "main", `${type === "main" ? type : "preload"}.js` + ), + format: "cjs", + name: type === "main" ? "MainProcess" : "MainPreloadProcess", + sourcemap: false, + }, + plugins: [ + replace({ + preventAssignment: true, + "process.env.NODE_ENV": JSON.stringify(env), + }), + // 提供路径和读取别名 + nodeResolve({ preferBuiltins: true, browser: false, extensions: ['.mjs', '.ts', '.js', '.json', '.node'] }), + commonjs({ + sourceMap: false, + }), + json(), + esbuild({ + // All options are optional + include: /\.[jt]s?$/, // default, inferred from `loaders` option + exclude: /node_modules/, // default + // watch: process.argv.includes('--watch'), // rollup 中有配置 + sourceMap: env != "production", // default + minify: env === "production", + target: "es2017", // default, or 'es20XX', 'esnext' + // Like @rollup/plugin-replace + define: { + __VERSION__: '"x.y.z"', + }, + // Add extra loaders + loaders: { + // Add .json files support + // require @rollup/plugin-commonjs + ".json": "json", + // Enable JSX in .js files too + ".js": "jsx", + }, + }), + alias({ + entries: [ + { find: "@main", replacement: path.join(__dirname, "../src/main") }, + { + find: "@config", + replacement: path.join(__dirname, "..", "config"), + }, + ], + }), + // process.env.NODE_ENV == "production" ? obfuscator({}) : null, + ], + external: [ + ...builtinModules, + 'axios', + 'electron', + 'express', + 'ffi-napi', + 'ref-napi', + 'ref-struct-napi', + // 修正部分人会导致丢失依赖的问题,如果updater工作不正常请取消下面的注释,并自行安装semver + 'semver', + 'glob', + ], + }) +}; + + diff --git a/.electron-vite/vite.config.ts b/.electron-vite/vite.config.ts new file mode 100644 index 0000000..6f75778 --- /dev/null +++ b/.electron-vite/vite.config.ts @@ -0,0 +1,49 @@ +import { join } from 'path' +import { defineConfig } from 'vite' +import vuePlugin from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import userConfig from '../config' + +const IsWeb = process.env.BUILD_TARGET === 'web' + +function resolve(dir: string) { + return join(__dirname, '..', dir) +} +userConfig.build.env.is_web = IsWeb +userConfig.dev.env.is_web = IsWeb + +const root = resolve('src/renderer') + +export default defineConfig({ + mode: process.env.NODE_ENV, + root, + define: { + 'process.env': process.env.NODE_ENV === 'production' ? userConfig.build.env : userConfig.dev.env, + }, + resolve: { + alias: { + '@renderer': root, + '@store': join(root, '/store/modules'), + } + }, + base: './', + build: { + outDir: IsWeb ? resolve('dist/web') : resolve('dist/electron/renderer'), + emptyOutDir: true, + target: 'esnext', + minify: 'esbuild' + }, + server: { + }, + plugins: [ + vueJsx(), + vuePlugin({ + script: { + refSugar: true + } + }) + ], + optimizeDeps: { + }, + publicDir: resolve('static') +}) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 18772fe..9a15004 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .DS_Store node_modules -/dist +dist/ +build/ +!build/icons lib/CesiumUnminified/ mars3d-*src.* diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..3cb7228 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ \ No newline at end of file diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 0000000..3cb7228 --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index 0b91077..c73d816 100644 --- a/LICENSE +++ b/LICENSE @@ -203,4 +203,3 @@ See the License for the specific language governing permissions and limitations under the License. - diff --git a/README.md b/README.md index 1bfa873..71ee5d4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@

-

基于Vue3.x + Vite2 + Electron 技术栈的 Mars3D🌎CS桌面程序项目模板

@@ -17,62 +16,64 @@

- Mars3D平台基于`Vue3.x ` 、 `Vite ` 和 `Electron` 的最简的CS应用项目模版,可以使用该模板建立自己的CS桌面版三维地球程序。 +Mars3D 平台基于`Vue3.x ` 、 `Vite ` 和 `Electron` 的最简的 CS 应用项目模版,可以使用该模板建立自己的 CS 桌面版三维地球程序。 - +## 如何安装 +> 请确保您的 node 版本大于等于 16. -## 首次运行前安装依赖 -``` -npm install -``` +```bash +npm config edit +# 该命令会打开npm的配置文件,请在空白处添加,记得去除#号 +# electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ +# electron_custom_dir={{ version }} +# electron_mirror=https://cdn.npmmirror.com/binaries/electron/v +# registry=https://registry.npmmirror.com/ +# 然后关闭该窗口,重启命令行. -## CS桌面程序访问 +# 使用yarn安装 +yarn or yarn install -### 编译并热部署的Electron开发环境 (Compiles and hot-reloads for Electron development) -``` -npm run dev -``` +# 启动之后,会在9080端口监听 +yarn dev + +# build命令在不同系统环境中,需要的的不一样,需要自己根据自身环境进行配置 +yarn build -### 编译并压缩的Electron生产环境 (Compiles and minifies for Electron production) -``` -npm run build ``` +## 常见问题 +### npm 安装是 electron 总是超时 -## BS浏览器访问 +删除 package.json 中 electron 包,通过 npm install 安装成功后,再恢复 electron 包,通过 cnpm install 安装完成。 -### 编译并热部署的web浏览器开发环境 (Compiles and hot-reloads for development) -``` -npm run dev:vue -``` -### 编译并压缩的web浏览器生产环境 (Compiles and minifies for production) -``` -npm run build:vue -``` - -## 常见问题 -### npm安装是electron总是超时 -删除package.json中electron包,通过npm install安装成功后,再恢复electron包,通过cnpm install安装完成。 +## 参考了仓库 +- [electron-vite-template](https://gitee.com/Zh-Sky/electron-vite-template) + + -## Mars3D 是什么 -> `Mars3D平台` 是[火星科技](http://marsgis.cn/)研发的一款基于 WebGL 技术实现的三维客户端开发平台,基于[Cesium](https://cesium.com/cesiumjs/)优化提升与B/S架构设计,支持多行业扩展的轻量级高效能GIS开发平台,能够免安装、无插件地在浏览器中高效运行,并可快速接入与使用多种GIS数据和三维模型,呈现三维空间的可视化,完成平台在不同行业的灵活应用。 - > Mars3D平台可用于构建无插件、跨操作系统、 跨浏览器的三维 GIS 应用程序。平台使用 WebGL 来进行硬件加速图形化,跨平台、跨浏览器来实现真正的动态大数据三维可视化。通过 Mars3D产品可快速实现浏览器和移动端上美观、流畅的三维地图呈现与空间分析。 -### 相关网站 -- Mars3D官网:[http://mars3d.cn](http://mars3d.cn) +## Mars3D 是什么 -- Mars3D开源项目列表:[https://github.com/marsgis/mars3d](https://github.com/marsgis/mars3d) +> `Mars3D平台` 是[火星科技](http://marsgis.cn/)研发的一款基于 WebGL 技术实现的三维客户端开发平台,基于[Cesium](https://cesium.com/cesiumjs/)优化提升与 B/S 架构设计,支持多行业扩展的轻量级高效能 GIS 开发平台,能够免安装、无插件地在浏览器中高效运行,并可快速接入与使用多种 GIS 数据和三维模型,呈现三维空间的可视化,完成平台在不同行业的灵活应用。 +> Mars3D 平台可用于构建无插件、跨操作系统、 跨浏览器的三维 GIS 应用程序。平台使用 WebGL 来进行硬件加速图形化,跨平台、跨浏览器来实现真正的动态大数据三维可视化。通过 Mars3D 产品可快速实现浏览器和移动端上美观、流畅的三维地图呈现与空间分析。 + +### 相关网站 + +- Mars3D 官网:[http://mars3d.cn](http://mars3d.cn) + +- Mars3D 开源项目列表:[https://github.com/marsgis/mars3d](https://github.com/marsgis/mars3d) ## 版权说明 -1. Mars3D平台由[火星科技](http://marsgis.cn/)自主研发,拥有所有权利。 + +1. Mars3D 平台由[火星科技](http://marsgis.cn/)自主研发,拥有所有权利。 2. 任何个人或组织可以在遵守相关要求下可以免费无限制使用。 diff --git a/build.json b/build.json new file mode 100644 index 0000000..3cbbb10 --- /dev/null +++ b/build.json @@ -0,0 +1,43 @@ +{ + "asar": false, + "extraFiles": [], + "publish": [ + { + "provider": "generic", + "url": "http://127.0.0.1" + } + ], + "afterPack": ".electron-vite/afterPack.js", + "productName": "electron-vite-template", + "appId": "org.sky.electron-vite-template", + "directories": { + "output": "build" + }, + "files": ["dist/electron/**/*"], + "dmg": { + "contents": [ + { + "x": 410, + "y": 150, + "type": "link", + "path": "/Applications" + }, + { + "x": 130, + "y": 150, + "type": "file" + } + ] + }, + "mac": { + "icon": "build/icons/icon.icns" + }, + "win": { + "icon": "build/icons/icon.ico", + "target": "nsis" + }, + "linux": { + "target": "deb", + "icon": "build/icons" + } +} diff --git a/config/dev.env.ts b/config/dev.env.ts new file mode 100644 index 0000000..bc20170 --- /dev/null +++ b/config/dev.env.ts @@ -0,0 +1,5 @@ +export default { + NODE_ENV: 'development', + BASE_API: 'http://127.0.0.1:25565', + is_web: false +} diff --git a/config/index.ts b/config/index.ts new file mode 100644 index 0000000..ec7ce0e --- /dev/null +++ b/config/index.ts @@ -0,0 +1,23 @@ +import prod from './prod.env' +import dev from './dev.env' + +export default { + build: { + DisableF12: true, + env: prod, + // 示例 + hotPublishUrl:"http://umbrella22.github.io/electron-vite-template", + hotPublishConfigName: "update-config" + }, + dev: { + env: dev, + removeElectronJunk: true, + chineseLog: false, + port: 9080, + }, + DllFolder: '', + HotUpdateFolder: 'update', + UseStartupChart: true, + IsUseSysTitle: true, + BuiltInServerPort: 25565 +} \ No newline at end of file diff --git a/config/prod.env.ts b/config/prod.env.ts new file mode 100644 index 0000000..20c6c00 --- /dev/null +++ b/config/prod.env.ts @@ -0,0 +1,5 @@ +export default { + NODE_ENV: 'production', + BASE_API: 'http://127.0.0.1:25565', + is_web: false +} diff --git a/customTypes/Item.d.ts b/customTypes/Item.d.ts new file mode 100644 index 0000000..ac18238 --- /dev/null +++ b/customTypes/Item.d.ts @@ -0,0 +1,4 @@ +interface ColorInfo { + color?: string, + percentage?: number +} \ No newline at end of file diff --git a/customTypes/global.d.ts b/customTypes/global.d.ts new file mode 100644 index 0000000..0e0138b --- /dev/null +++ b/customTypes/global.d.ts @@ -0,0 +1,17 @@ +interface AnyObject { + [key: string]: any +} + +interface memoryInfo { + jsHeapSizeLimit: number; + totalJSHeapSize: number; + usedJSHeapSize: number; +} + +interface Window { + performance: { + memory: memoryInfo + } + __lib: string; + __static: string; +} \ No newline at end of file diff --git a/customTypes/image.d.ts b/customTypes/image.d.ts new file mode 100644 index 0000000..4bc2b76 --- /dev/null +++ b/customTypes/image.d.ts @@ -0,0 +1,7 @@ +declare module '*.svg' +declare module '*.png' +declare module '*.jpg' +declare module '*.jpeg' +declare module '*.gif' +declare module '*.bmp' +declare module '*.tiff' \ No newline at end of file diff --git a/customTypes/shims-vue.d.ts b/customTypes/shims-vue.d.ts new file mode 100644 index 0000000..ac1ded7 --- /dev/null +++ b/customTypes/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8146a0d --- /dev/null +++ b/package.json @@ -0,0 +1,87 @@ +{ + "name": "mars3d-electron", + "version": "3.3.0", + "description": "Mars3D平台,electron桌面程序最简项目模版", + "main": "./dist/electron/main/main.js", + "scripts": { + "dev": "esno .electron-vite/dev-runner.ts", + "build": "cross-env BUILD_TARGET=clean esno .electron-vite/build.ts && electron-builder -c build.json", + "build:win32": "cross-env BUILD_TARGET=clean esno .electron-vite/build.ts && electron-builder -c build.json --win --ia32", + "build:win64": "cross-env BUILD_TARGET=clean esno .electron-vite/build.ts && electron-builder -c build.json --win --x64", + "build:mac": "cross-env BUILD_TARGET=clean esno .electron-vite/build.ts && electron-builder -c build.json --mac", + "build:dir": "cross-env BUILD_TARGET=clean esno .electron-vite/build.ts && electron-builder -c build.json --dir", + "build:clean": "cross-env BUILD_TARGET=onlyClean esno .electron-vite/build.ts", + "build:web": "cross-env BUILD_TARGET=web esno .electron-vite/build.ts", + "pack:resources": "esno .electron-vite/hot-updater.ts", + "pack:rustUpdater": "electron_updater_node_cli -p -c updateConfig.json", + "dep:upgrade": "yarn upgrade-interactive --latest", + "postinstall": "electron-builder install-app-deps" + }, + "dependencies": { + "axios": "^0.27.2", + "electron-log": "^4.4.6", + "electron-updater": "^5.0.1", + "express": "^4.18.1", + "glob": "^8.0.1", + "semver": "^7.3.5" + }, + "devDependencies": { + "@rollup/plugin-alias": "^3.1.9", + "@rollup/plugin-commonjs": "^22.0.0", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.1.3", + "@rollup/plugin-replace": "^4.0.0", + "@types/fs-extra": "^9.0.13", + "@types/node": "^17.0.31", + "@vitejs/plugin-vue": "^2.3.1", + "@vitejs/plugin-vue-jsx": "^1.3.9", + "@vue/compiler-sfc": "^3.2.31", + "adm-zip": "^0.5.9", + "cfonts": "^2.10.0", + "chalk": "5.0.1", + "cross-env": "^7.0.3", + "del": "^6.0.0", + "electron": "17.2.0", + "electron-builder": "^23.0.3", + "electron-devtools-vendor": "^1.0.5", + "electron_updater_node_cli": "^0.1.3", + "electron_updater_node_core": "^0.1.4", + "element-plus": "^2.1.11", + "esno": "^0.14.1", + "extract-zip": "^2.0.1", + "fs-extra": "^10.1.0", + "multispinner": "^0.2.1", + "pinia": "^2.0.12", + "portfinder": "^1.0.28", + "rollup-plugin-esbuild": "^4.8.2", + "sass": "^1.51.0", + "tslib": "^2.4.0", + "typescript": "^4.6.4", + "vite": "^2.9.6", + "vue": "^3.2.31", + "vue-i18n": "^9.1.10", + "vue-router": "^4.0.14" + }, + "repository": { + "type": "git", + "url": "https://github.com/marsgis/mars3d-vue-template.git" + }, + "bugs": { + "url": "https://github.com/marsgis/mars3d-vue-template/issues", + "email": "wh@marsgis.cn" + }, + "keywords": [ + "marsgis", + "mars3d", + "cesium", + "vite", + "electron", + "vue3", + "rollup", + "vue", + "gis" + ], + "author": "火星科技", + "license": "Apache-2.0", + "homepage": "http://mars3d.cn" +} diff --git a/rootLib/win32/ia32/updater.exe b/rootLib/win32/ia32/updater.exe new file mode 100644 index 0000000..2e50055 Binary files /dev/null and b/rootLib/win32/ia32/updater.exe differ diff --git a/rootLib/win32/x64/updater.exe b/rootLib/win32/x64/updater.exe new file mode 100644 index 0000000..2e50055 Binary files /dev/null and b/rootLib/win32/x64/updater.exe differ diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..ef6ac5e --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,3 @@ +interface Window { + mars3d: any; +} diff --git a/src/main/config/DisableButton.ts b/src/main/config/DisableButton.ts new file mode 100644 index 0000000..c93f2ba --- /dev/null +++ b/src/main/config/DisableButton.ts @@ -0,0 +1,12 @@ +import { globalShortcut } from 'electron' +import config from '@config/index' + +export default { + Disablef12 () { + if (process.env.NODE_ENV === 'production' && config.DisableF12) { + globalShortcut.register('f12', () => { + console.log('用户试图启动控制台') + }) + } + } +} diff --git a/src/main/config/StaticPath.ts b/src/main/config/StaticPath.ts new file mode 100644 index 0000000..22647d6 --- /dev/null +++ b/src/main/config/StaticPath.ts @@ -0,0 +1,81 @@ +// 这里定义了静态文件路径的位置 +import { join } from 'path' +import config from '@config/index' +import { app } from 'electron' +import { URL } from 'url'; +const isDev = process.env.NODE_ENV === 'development'; +class StaticPath { + constructor() { + const basePath = isDev ? join(__dirname, '..', '..', '..') : join(app.getAppPath(), '..', '..'); + this.__updateFolder = join(basePath, `${config.HotUpdateFolder}`) + if (isDev) { + this.__static = join(basePath, 'static'); + this.__lib = join(basePath, `rootLib`, `${process.platform}`, `${process.arch}`); + this.__common = join(basePath, 'rootLib', 'common'); + } else { + this.__static = join(__dirname, '..', 'renderer'); + this.__lib = basePath; + this.__common = basePath; + } + } + /** + * 静态文件路径 渲染进程目录下 + * + * @type {string} + * @memberof StaticPath + */ + __static: string; + /** + * dll文件夹及其他os平台相关的文件路径 + * + * @type {string} + * @memberof StaticPath + */ + __lib: string; + /** + * 与os无关的资源 + * + * @type {string} + * @memberof StaticPath + */ + __common: string; + /** + * 增量更新文件夹 + * + * @type {string} + * @memberof StaticPath + */ + __updateFolder: string; +} +const staticPath = new StaticPath(); +/** + * 获取真正的地址 + * + * @param {string} devPath 开发环境路径 + * @param {string} proPath 生产环境路径 + * @param {string} [hash=""] hash值 + * @param {string} [search=""] search值 + * @return {*} {string} 地址 + */ +function getUrl(devPath: string, proPath: string, hash: string = "", search: string = ""): string { + const url = isDev ? new URL(`http://localhost:${process.env.PORT}`) : new URL('file://'); + url.pathname = isDev ? devPath : proPath; + url.hash = hash; + url.search = search; + return url.href; +} +export const winURL = getUrl("", join(__dirname, '..', 'renderer', 'index.html')); +export const loadingURL = getUrl("/loader.html", `${staticPath.__static}/loader.html`); +export const preloadURL = getUrl("/preload.html", `${staticPath.__static}/preload.html`); +export const printURL = getUrl("", join(__dirname, '..', 'renderer', 'index.html'), "#/Print"); +export const preloadPath = isDev ? join(app.getAppPath(), "..", "preload.js") : join(app.getAppPath(), "dist", "electron", "preload.js"); +export const lib = staticPath.__lib +export const common = staticPath.__common +export const updateFolder = staticPath.__updateFolder +export const staticPaths = getUrl('', staticPath.__static) + +// process.env 修改 +for (const key in staticPath) { + process.env[key] = staticPath[key]; +} + diff --git a/src/main/config/hotPublish.ts b/src/main/config/hotPublish.ts new file mode 100644 index 0000000..b146208 --- /dev/null +++ b/src/main/config/hotPublish.ts @@ -0,0 +1,10 @@ +import config from '@config/index.js' +interface hotPublish { + url: string; + configName: string; +} + +export const hotPublishConfig: hotPublish = { + url: config.build.hotPublishUrl, + configName: config.build.hotPublishConfigName +} \ No newline at end of file diff --git a/src/main/config/menu.ts b/src/main/config/menu.ts new file mode 100644 index 0000000..fc27be8 --- /dev/null +++ b/src/main/config/menu.ts @@ -0,0 +1,38 @@ +// 这里是定义菜单的地方,详情请查看 https://electronjs.org/docs/api/menu +import { dialog } from 'electron' +import { type, arch, release } from 'os' +import packageInfo from '../../../package.json' + +const menu = [ + { + label: '设置', + submenu: [{ + label: '快速重启', + accelerator: 'F5', + role: 'reload' + }, { + label: '退出', + accelerator: 'CmdOrCtrl+F4', + role: 'close' + }] + }, { + label: '帮助', + submenu: [{ + label: '关于', + click: function () { + info() + } + }] + }] +function info() { + dialog.showMessageBox({ + title: '关于', + type: 'info', + message: 'electron-Vue框架', + detail: `版本信息:${packageInfo.version}\n引擎版本:${process.versions.v8}\n当前系统:${type()} ${arch()} ${release()}`, + noLink: true, + buttons: ['查看github', '确定'] + }) +} + +export default menu diff --git a/src/main/config/windowsConfig.ts b/src/main/config/windowsConfig.ts new file mode 100644 index 0000000..ea7d0a1 --- /dev/null +++ b/src/main/config/windowsConfig.ts @@ -0,0 +1,40 @@ +import config from '@config/index' +import { BrowserWindowConstructorOptions } from 'electron'; +import { preloadPath } from './StaticPath'; + +export const mainWindowConfig: BrowserWindowConstructorOptions = { + height: 800, + useContentSize: true, + width: 1700, + minWidth: 1366, + show: false, + frame: config.IsUseSysTitle, + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + webSecurity: false, + // 如果是开发模式可以使用devTools + devTools: process.env.NODE_ENV === 'development', + // 在macos中启用橡皮动画 + scrollBounce: process.platform === 'darwin', + } +}; + +export const otherWindowConfig: BrowserWindowConstructorOptions = { + height: 595, + useContentSize: true, + width: 1140, + autoHideMenuBar: true, + minWidth: 842, + frame: config.IsUseSysTitle, + show: false, + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + webSecurity: false, + // 如果是开发模式可以使用devTools + devTools: process.env.NODE_ENV === 'development', + // 在macos中启用橡皮动画 + scrollBounce: process.platform === 'darwin', + } +} diff --git a/src/main/index.ts b/src/main/index.ts new file mode 100644 index 0000000..9f7b4a9 --- /dev/null +++ b/src/main/index.ts @@ -0,0 +1,38 @@ +'use strict' + +import { app, session } from 'electron' +import InitWindow from './services/windowManager' +import DisableButton from './config/DisableButton' +function onAppReady() { + new InitWindow().initWindow() + DisableButton.Disablef12() + if (process.env.NODE_ENV === 'development') { + const { VUEJS3_DEVTOOLS } = require("electron-devtools-vendor"); + session.defaultSession.loadExtension(VUEJS3_DEVTOOLS, { + allowFileAccess: true, + }); + console.log('已安装: vue-devtools') + } +} + +app.whenReady().then(onAppReady) + +// 由于9.x版本问题,需要加入该配置关闭跨域问题 +app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') + +app.on('window-all-closed', () => { + // 所有平台均为所有窗口关闭就退出软件 + app.quit() +}) +app.on('browser-window-created', () => { + console.log('window-created') +}) + +if (process.defaultApp) { + if (process.argv.length >= 2) { + app.removeAsDefaultProtocolClient('electron-vue-template') + console.log('由于框架特殊性开发环境下无法使用') + } +} else { + app.setAsDefaultProtocolClient('electron-vue-template') +} diff --git a/src/main/server/index.ts b/src/main/server/index.ts new file mode 100644 index 0000000..7318c62 --- /dev/null +++ b/src/main/server/index.ts @@ -0,0 +1,68 @@ +/* eslint-disable prefer-promise-reject-errors */ +import app from './server' +import config from '@config/index' +import { createServer, Server } from 'http'; +const port = config.BuiltInServerPort + +class SingleServer { + constructor(app: any) { + app.set('port', port) + this.server = createServer(app); + this.server.keepAliveTimeout = 0; + this.server.on('connection', (socket) => { + // keep-alive 1s后自动关闭 + socket.setTimeout(1000); + }) + } + server: Server + statrServer() { + return new Promise((resolve, reject) => { + try { + this.server.listen(port); + resolve("服务端已经启动"); + } catch (error) { + switch (error.code) { + case "ERR_SERVER_ALREADY_LISTEN": + resolve("服务端已经启动") + break; + case "EACCES": + reject("权限不足内置服务器启动失败,请使用管理员权限运行。") + break; + case "EADDRINUSE": + reject("内置服务器端口已被占用,请检查。") + break; + default: + reject(error) + } + } + }) + } + stopServer() { + return new Promise((resolve, reject) => { + this.server.close((err) => { + if (err) { + switch ((err as any).code) { + case "ERR_SERVER_NOT_RUNNING": + resolve("服务端未启动") + break; + default: + reject(err) + } + } else { + resolve(1) + } + }); + }) + } +} + +const singleServer = new SingleServer(app); + +export default { + StatrServer() { + return singleServer.statrServer(); + }, + StopServer() { + return singleServer.stopServer(); + } +} diff --git a/src/main/server/server.ts b/src/main/server/server.ts new file mode 100644 index 0000000..96fa658 --- /dev/null +++ b/src/main/server/server.ts @@ -0,0 +1,14 @@ +import express from 'express' +const app = express() + +app.get('/message', (req, res) => { + res.send('这是来自node服务端的信息') +}) + +app.post('/message', (req, res) => { + if (req) { + res.send(req + '--来自node') + } +}) + +export default app diff --git a/src/main/services/HotUpdater.ts b/src/main/services/HotUpdater.ts new file mode 100644 index 0000000..79cbc99 --- /dev/null +++ b/src/main/services/HotUpdater.ts @@ -0,0 +1,93 @@ +/** + * power by biuuu + */ + +import { emptyDir, createWriteStream, readFile, copy, remove } from 'fs-extra' +import { join, resolve } from 'path' +import { promisify } from 'util' +import { pipeline } from 'stream' +import { app, BrowserWindow } from 'electron' +import { gt } from 'semver' +import { createHmac } from 'crypto' +import extract from 'extract-zip' +import { version } from '../../../package.json' +import { hotPublishConfig } from '../config/hotPublish' +import axios from 'axios' + +const streamPipeline = promisify(pipeline) +const appPath = app.getAppPath() +const updatePath = resolve(appPath, '..', '..', 'update') +const request = axios.create() + +/** + * @param data 文件流 + * @param type 类型,默认sha256 + * @param key 密钥,用于匹配计算结果 + * @returns {string} 计算结果 + * @author umbrella22 + * @date 2021-03-05 + */ +function hash(data, type = 'sha256', key = 'Sky') { + const hmac = createHmac(type, key) + hmac.update(data) + return hmac.digest('hex') +} + + +/** + * @param url 下载地址 + * @param filePath 文件存放地址 + * @returns {void} + * @author umbrella22 + * @date 2021-03-05 + */ +async function download(url: string, filePath: string) { + const res = await request({ url, responseType: "stream" }) + await streamPipeline(res.data, createWriteStream(filePath)) +} + +const updateInfo = { + status: 'init', + message: '' +} + +/** + * @param windows 指主窗口 + * @returns {void} + * @author umbrella22 + * @date 2021-03-05 + */ +export const updater = async (windows?: BrowserWindow) => { + try { + const res = await request({ url: `${hotPublishConfig.url}/${hotPublishConfig.configName}.json?time=${new Date().getTime()}`, }) + if (!gt(res.data.version, version)) return + + await emptyDir(updatePath) + const filePath = join(updatePath, res.data.name) + updateInfo.status = 'downloading' + if (windows) windows.webContents.send('hot-update-status', updateInfo); + await download(`${hotPublishConfig.url}/${res.data.name}`, filePath); + const buffer = await readFile(filePath) + const sha256 = hash(buffer) + if (sha256 !== res.data.hash) throw new Error('sha256 error') + const appPathTemp = join(updatePath, 'temp') + await extract(filePath, { dir: appPathTemp }) + updateInfo.status = 'moving' + if (windows) windows.webContents.send('hot-update-status', updateInfo); + await remove(join(`${appPath}`, 'dist')); + await remove(join(`${appPath}`, 'package.json')); + await copy(appPathTemp, appPath) + updateInfo.status = 'finished' + if (windows) windows.webContents.send('hot-update-status', updateInfo); + resolve('success') + + + + } catch (error) { + updateInfo.status = 'failed' + updateInfo.message = error + if (windows) windows.webContents.send('hot-update-status', updateInfo) + } +} + +export const getUpdateInfo = () => updateInfo \ No newline at end of file diff --git a/src/main/services/HotUpdaterTest.ts b/src/main/services/HotUpdaterTest.ts new file mode 100644 index 0000000..320c1cb --- /dev/null +++ b/src/main/services/HotUpdaterTest.ts @@ -0,0 +1,40 @@ +import { app, BrowserWindow } from "electron"; +import { updateElectron, UpdateInfo, UpdateJson } from "electron_updater_node_core" +import { dirname, join } from "path"; +import { version } from '../../../package.json' +import { Readable } from "stream"; +import axios from 'axios' +const request = axios.create() +import updateConfig from "../../../updateConfig.json"; +/** + * 增量更新 + * + * @export + * @param {BrowserWindow} [windows] + */ +export async function updater(windows?: BrowserWindow) { + const statusCallback = (status: UpdateInfo) => { + if (windows) windows.webContents.send('hot-update-status', status); + } + const downloadFn = async (url: string):Promise => { + const response = await request({ + method: 'get', + url: url, + responseType: 'stream', + }); + return response.data; + } + const dirDirectory = join(app.getAppPath(), '..', '..'); + const tempDirectory = join(dirDirectory, updateConfig.tempDirectory); + try { + const res = await request({ url: `${updateConfig.url}/${updateConfig.updateJsonName}.json?time=${new Date().getTime()}`, }) + const updateJson: UpdateJson = res.data; + return await updateElectron(statusCallback, updateConfig.updaterName || "updater", version, app.getPath('exe'), tempDirectory, updateConfig.updateJsonName, updateJson, `${updateConfig.url}/${updateConfig.target + updateJson.version}`, downloadFn); + } catch (error) { + console.log(error); + const updateInfo = new UpdateInfo(); + updateInfo.status = 'failed' + updateInfo.message = error + if (windows) windows.webContents.send('hot-update-status', updateInfo) + } +} diff --git a/src/main/services/checkupdate.ts b/src/main/services/checkupdate.ts new file mode 100644 index 0000000..5a5f715 --- /dev/null +++ b/src/main/services/checkupdate.ts @@ -0,0 +1,76 @@ +import { autoUpdater } from 'electron-updater' +import { BrowserWindow } from 'electron' +/** + * -1 检查更新失败 0 正在检查更新 1 检测到新版本,准备下载 2 未检测到新版本 3 下载中 4 下载完成 + **/ +class Update { + public mainWindow: BrowserWindow + constructor() { + // 设置url + autoUpdater.setFeedURL('http://127.0.0.1:25565/') + + // 当更新发生错误的时候触发。 + autoUpdater.on('error', (err) => { + console.log('更新出现错误', err.message) + if (err.message.includes('sha512 checksum mismatch')) { + this.Message(this.mainWindow, -1, 'sha512校验失败') + } else { + this.Message(this.mainWindow, -1, '错误信息请看主进程控制台') + + } + }) + + // 当开始检查更新的时候触发 + autoUpdater.on('checking-for-update', (event, arg) => { + console.log('开始检查更新') + this.Message(this.mainWindow, 0) + }) + + // 发现可更新数据时 + autoUpdater.on('update-available', (event, arg) => { + console.log('有更新') + this.Message(this.mainWindow, 1) + }) + + // 没有可更新数据时 + autoUpdater.on('update-not-available', (event, arg) => { + console.log('没有更新') + this.Message(this.mainWindow, 2) + }) + + // 下载监听 + autoUpdater.on('download-progress', (progressObj) => { + this.Message(this.mainWindow, 3, progressObj) + }) + + // 下载完成 + autoUpdater.on('update-downloaded', () => { + console.log('下载完成') + this.Message(this.mainWindow, 4) + }) + + + } + // 负责向渲染进程发送信息 + Message(mainWindow: BrowserWindow, type: Number, data?: String) { + const senddata = { + state: type, + msg: data || '' + } + mainWindow.webContents.send('UpdateMsg', senddata) + } + + // 执行自动更新检查 + checkUpdate(mainWindow: BrowserWindow) { + this.mainWindow = mainWindow + autoUpdater.checkForUpdates().catch(err => { + console.log('网络连接问题', err) + }) + } + // 退出并安装 + quitAndInstall() { + autoUpdater.quitAndInstall() + } +} + +export default Update diff --git a/src/main/services/downloadFile.ts b/src/main/services/downloadFile.ts new file mode 100644 index 0000000..4c1a1ff --- /dev/null +++ b/src/main/services/downloadFile.ts @@ -0,0 +1,77 @@ +import { app, ipcMain, BrowserWindow, dialog } from 'electron' +import { join } from 'path' +import { arch, platform } from 'os' +import { stat, remove } from 'fs-extra' +import packageInfo from '../../../package.json' + + +/** + * + * @description + * @returns {void} 下载类 + * @param {mainWindow} 主窗口 + * @param {downloadUrl} 下载地址,当未传入时则会使用预先设置好的baseUrl拼接名称 + * @author Sky + * @date 2020-08-12 + */ + +class Main { + + public mainWindow: BrowserWindow = null + public downloadUrl: string = "" + public version: string = packageInfo.version + public baseUrl: string = '' + public Sysarch: string = arch().includes('64') ? 'win64' : 'win32' + public HistoryFilePath = join(app.getPath('downloads'), platform().includes('win32') ? `electron_${this.version}_${this.Sysarch}.exe` : `electron_${this.version}_mac.dmg`) + + + constructor(mainWindow: BrowserWindow, downloadUrl?: string) { + this.mainWindow = mainWindow + this.downloadUrl = downloadUrl || platform().includes('win32') ? this.baseUrl + `electron_${this.version}_${this.Sysarch}.exe?${new Date().getTime()}` : this.baseUrl + `electron_${this.version}_mac.dmg?${new Date().getTime()}` + } + + start() { + // 更新时检查有无同名文件,若有就删除,若无就开始下载 + stat(this.HistoryFilePath, async (err, stats) => { + try { + if (stats) { + await remove(this.HistoryFilePath) + } + this.mainWindow.webContents.downloadURL(this.downloadUrl) + } catch (error) { console.log(error) } + }) + this.mainWindow.webContents.session.on('will-download', (event: any, item: any, webContents: any) => { + const filePath = join(app.getPath('downloads'), item.getFilename()) + item.setSavePath(filePath) + item.on('updated', (event: any, state: String) => { + switch (state) { + case 'progressing': + this.mainWindow.webContents.send('download-progress', (item.getReceivedBytes() / item.getTotalBytes() * 100).toFixed(0)) + break + default: + this.mainWindow.webContents.send('download-error', true) + dialog.showErrorBox('下载出错', '由于网络或其他未知原因导致下载出错') + break + } + }) + item.once('done', (event: any, state: String) => { + switch (state) { + case 'completed': + const data = { + filePath + } + this.mainWindow.webContents.send('download-done', data) + break + case 'interrupted': + this.mainWindow.webContents.send('download-error', true) + dialog.showErrorBox('下载出错', '由于网络或其他未知原因导致下载出错.') + break + default: + break + } + }) + }) + } +} + +export default Main diff --git a/src/main/services/ipcMain.ts b/src/main/services/ipcMain.ts new file mode 100644 index 0000000..158335b --- /dev/null +++ b/src/main/services/ipcMain.ts @@ -0,0 +1,139 @@ +import { ipcMain, dialog, BrowserWindow, app } from 'electron' +import config from '@config/index' +import Server from '../server' +import { winURL, preloadURL, staticPaths } from '../config/StaticPath' +import { updater } from './HotUpdater' +import { updater as updaterTest } from './HotUpdaterTest' +import DownloadFile from './downloadFile' +import Update from './checkupdate'; +import { otherWindowConfig } from "../config/windowsConfig" +import { usePrintHandle } from './printHandle' +import { UpdateStatus } from 'electron_updater_node_core' + +export default { + Mainfunc() { + usePrintHandle() + const allUpdater = new Update(); + ipcMain.handle('IsUseSysTitle', async () => { + return config.IsUseSysTitle + }) + ipcMain.handle('windows-mini', (event, args) => { + BrowserWindow.fromWebContents(event.sender)?.minimize() + }) + ipcMain.handle('window-max', async (event, args) => { + if (BrowserWindow.fromWebContents(event.sender)?.isMaximized()) { + BrowserWindow.fromWebContents(event.sender)?.restore() + return { status: false } + } else { + BrowserWindow.fromWebContents(event.sender)?.maximize() + return { status: true } + } + }) + ipcMain.handle('window-close', (event, args) => { + BrowserWindow.fromWebContents(event.sender)?.close() + }) + ipcMain.handle('check-update', (event) => { + allUpdater.checkUpdate(BrowserWindow.fromWebContents(event.sender)) + }) + ipcMain.handle('confirm-update', () => { + allUpdater.quitAndInstall() + }) + ipcMain.handle('app-close', (event, args) => { + app.quit() + }) + ipcMain.handle('get-static-path', (event, args) => { + return staticPaths + }) + ipcMain.handle('open-messagebox', async (event, arg) => { + const res = await dialog.showMessageBox(BrowserWindow.fromWebContents(event.sender), { + type: arg.type || 'info', + title: arg.title || '', + buttons: arg.buttons || [], + message: arg.message || '', + noLink: arg.noLink || true + }) + return res + }) + ipcMain.handle('open-errorbox', (event, arg) => { + dialog.showErrorBox( + arg.title, + arg.message + ) + }) + ipcMain.handle('start-server', async () => { + try { + const serveStatus = await Server.StatrServer() + console.log(serveStatus) + return serveStatus + } catch (error) { + dialog.showErrorBox( + '错误', + error + ) + } + }) + ipcMain.handle('stop-server', async (event, arg) => { + try { + const serveStatus = await Server.StopServer() + return serveStatus + } catch (error) { + dialog.showErrorBox( + '错误', + error + ) + } + }) + ipcMain.handle('hot-update', (event, arg) => { + updater(BrowserWindow.fromWebContents(event.sender)) + }) + ipcMain.handle('hot-update-test', async (event, arg) => { + console.log('hot-update-test') + try { + let updateInfo = await updaterTest(BrowserWindow.fromWebContents(event.sender)); + if (updateInfo === UpdateStatus.Success) { + app.quit(); + } else if (updateInfo === UpdateStatus.HaveNothingUpdate) { + console.log('不需要更新'); + } else if (updateInfo === UpdateStatus.Failed) { + console.error('更新出错'); + } + } catch (error) { + // 更新出错 + console.error('更新出错'); + } + }) + ipcMain.handle('start-download', (event, msg) => { + new DownloadFile(BrowserWindow.fromWebContents(event.sender), msg.downloadUrl).start() + }) + ipcMain.handle('open-win', (event, arg) => { + const ChildWin = new BrowserWindow({ + titleBarStyle: config.IsUseSysTitle ? 'default' : 'hidden', + ...Object.assign(otherWindowConfig, {}) + }) + // 开发模式下自动开启devtools + if (process.env.NODE_ENV === 'development') { + ChildWin.webContents.openDevTools({ mode: 'undocked', activate: true }) + } + ChildWin.loadURL(winURL + `#${arg.url}`) + ChildWin.once('ready-to-show', () => { + ChildWin.show() + if (arg.IsPay) { + // 检查支付时候自动关闭小窗口 + const testUrl = setInterval(() => { + const Url = ChildWin.webContents.getURL() + if (Url.includes(arg.PayUrl)) { + ChildWin.close() + } + }, 1200) + ChildWin.on('close', () => { + clearInterval(testUrl) + }) + } + }) + // 渲染进程显示时触发 + ChildWin.once("show", () => { + ChildWin.webContents.send('send-data-test', arg.sendData) + }) + }) + } +} diff --git a/src/main/services/printHandle.ts b/src/main/services/printHandle.ts new file mode 100644 index 0000000..3755150 --- /dev/null +++ b/src/main/services/printHandle.ts @@ -0,0 +1,41 @@ +import { BrowserWindow, ipcMain, WebContentsPrintOptions } from 'electron' +import config from '@config/index' +import { otherWindowConfig } from "../config/windowsConfig" +import { printURL } from '@main/config/StaticPath' + +export function usePrintHandle() { + ipcMain.handle('getPrinters', async event => { + return await event.sender.getPrintersAsync() + }) + + ipcMain.handle('printHandlePrint', async (event, options: WebContentsPrintOptions) => { + return new Promise(resolve => { + event.sender.print(options, (success: boolean, failureReason: string) => { + resolve({ success, failureReason }) + }) + }) + }) + + ipcMain.handle('openPrintDemoWindow', () => { + openPrintDemoWindow() + }) +} + +let win: BrowserWindow +export function openPrintDemoWindow() { + if (win) { + win.show() + return + } + win = new BrowserWindow({ + titleBarStyle: config.IsUseSysTitle ? 'default' : 'hidden', + ...Object.assign(otherWindowConfig, {}) + }) + win.loadURL(printURL) + win.on('ready-to-show', () => { + win.show() + }) + win.on('closed', () => { + win = null + }) +} \ No newline at end of file diff --git a/src/main/services/windowManager.ts b/src/main/services/windowManager.ts new file mode 100644 index 0000000..cefb610 --- /dev/null +++ b/src/main/services/windowManager.ts @@ -0,0 +1,186 @@ +import setIpc from './ipcMain' +import config from '@config/index' +import menuconfig from '../config/menu' +import { app, BrowserWindow, Menu, dialog } from 'electron' +import { winURL, loadingURL } from '../config/StaticPath' +import { mainWindowConfig } from "../config/windowsConfig" + +class MainInit { + + public winURL: string = '' + public shartURL: string = '' + public loadWindow: BrowserWindow = null + public mainWindow: BrowserWindow = null + + constructor() { + this.winURL = winURL + this.shartURL = loadingURL + if (process.env.NODE_ENV === 'development') { + menuconfig.push({ + label: '开发者设置', + submenu: [{ + label: '切换到开发者模式', + accelerator: 'CmdOrCtrl+I', + role: 'toggledevtools' + }] + }) + } + // 启用协议 + setIpc.Mainfunc() + } + // 主窗口函数 + createMainWindow() { + this.mainWindow = new BrowserWindow({ + titleBarStyle: config.IsUseSysTitle ? 'default' : 'hidden', + ...Object.assign(mainWindowConfig, {}) + }) + // 赋予模板 + const menu = Menu.buildFromTemplate(menuconfig as any) + // 加载模板 + Menu.setApplicationMenu(menu) + // 加载主窗口 + this.mainWindow.loadURL(this.winURL) + // ready-to-show之后显示界面 + this.mainWindow.once('ready-to-show', () => { + this.mainWindow.show() + // 开发模式下自动开启devtools + if (process.env.NODE_ENV === 'development') { + this.mainWindow.webContents.openDevTools({ mode: 'undocked', activate: true }) + } + if (config.UseStartupChart) this.loadWindow.destroy() + }) + // 当确定渲染进程卡死时,分类型进行告警操作 + app.on('render-process-gone', (event, webContents, details) => { + const message = { + title: "", + buttons: [], + message: '', + } + switch (details.reason) { + case 'crashed': + message.title = "警告" + message.buttons = ['确定', '退出'] + message.message = "图形化进程崩溃,是否进行软重启操作?" + break; + case 'killed': + message.title = "警告" + message.buttons = ['确定', '退出'] + message.message = "由于未知原因导致图形化进程被终止,是否进行软重启操作?" + break; + case 'oom': + message.title = "警告" + message.buttons = ['确定', '退出'] + message.message = "内存不足,是否软重启释放内存?" + break; + + default: + break; + } + dialog.showMessageBox(this.mainWindow, { + type: 'warning', + title: message.title, + buttons: message.buttons, + message: message.message, + noLink: true + }).then(res => { + if (res.response === 0) this.mainWindow.reload() + else this.mainWindow.close() + }) + }) + // 不知道什么原因,反正就是这个窗口里的页面触发了假死时执行 + this.mainWindow.on('unresponsive', () => { + dialog.showMessageBox(this.mainWindow, { + type: 'warning', + title: '警告', + buttons: ['重载', '退出'], + message: '图形化进程失去响应,是否等待其恢复?', + noLink: true + }).then(res => { + if (res.response === 0) this.mainWindow.reload() + else this.mainWindow.close() + }) + }) + /** + * 新的gpu崩溃检测,详细参数详见:http://www.electronjs.org/docs/api/app + * @returns {void} + * @author zmr (umbrella22) + * @date 2020-11-27 + */ + app.on('child-process-gone', (event, details) => { + const message = { + title: "", + buttons: [], + message: '', + } + switch (details.type) { + case 'GPU': + switch (details.reason) { + case 'crashed': + message.title = "警告"; + message.buttons = ['确定', '退出']; + message.message = "硬件加速进程已崩溃,是否关闭硬件加速并重启?"; + break; + case 'killed': + message.title = "警告"; + message.buttons = ['确定', '退出']; + message.message = "硬件加速进程被意外终止,是否关闭硬件加速并重启?"; + break; + default: + break; + } + break; + + default: + break; + } + dialog.showMessageBox(this.mainWindow, { + type: 'warning', + title: message.title, + buttons: message.buttons, + message: message.message, + noLink: true + }).then(res => { + // 当显卡出现崩溃现象时使用该设置禁用显卡加速模式。 + if (res.response === 0) { + if (details.type === 'GPU') app.disableHardwareAcceleration(); + this.mainWindow.reload() + } else { + this.mainWindow.close() + } + }) + }) + this.mainWindow.on('closed', () => { + this.mainWindow = null + }) + } + // 加载窗口函数 + loadingWindow(loadingURL: string) { + this.loadWindow = new BrowserWindow({ + width: 400, + height: 600, + // frame: false, + skipTaskbar: true, + transparent: true, + resizable: false, + webPreferences: { experimentalFeatures: true } + }) + + this.loadWindow.loadURL(loadingURL) + this.loadWindow.show() + this.loadWindow.setAlwaysOnTop(true) + // 延迟两秒可以根据情况后续调快,= =,就相当于个,sleep吧,就那种。 = =。。。 + setTimeout(() => { + this.createMainWindow() + }, 1500) + } + // 初始化窗口函数 + initWindow() { + if (config.UseStartupChart) { + return this.loadingWindow(this.shartURL) + } else { + return this.createMainWindow() + } + + } +} +export default MainInit diff --git a/src/renderer/App.vue b/src/renderer/App.vue new file mode 100644 index 0000000..59f62a2 --- /dev/null +++ b/src/renderer/App.vue @@ -0,0 +1,312 @@ + + + + + diff --git a/src/renderer/assets/404_images/404.png b/src/renderer/assets/404_images/404.png new file mode 100644 index 0000000..3d8e230 Binary files /dev/null and b/src/renderer/assets/404_images/404.png differ diff --git a/src/renderer/assets/404_images/404_cloud.png b/src/renderer/assets/404_images/404_cloud.png new file mode 100644 index 0000000..c6281d0 Binary files /dev/null and b/src/renderer/assets/404_images/404_cloud.png differ diff --git a/src/renderer/assets/icons/svg/close.svg b/src/renderer/assets/icons/svg/close.svg new file mode 100644 index 0000000..32de3b6 --- /dev/null +++ b/src/renderer/assets/icons/svg/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/assets/icons/svg/electron-logo.svg b/src/renderer/assets/icons/svg/electron-logo.svg new file mode 100644 index 0000000..1d7ca60 --- /dev/null +++ b/src/renderer/assets/icons/svg/electron-logo.svg @@ -0,0 +1,529 @@ + + + + diff --git a/src/renderer/assets/icons/svg/mini.svg b/src/renderer/assets/icons/svg/mini.svg new file mode 100644 index 0000000..0268802 --- /dev/null +++ b/src/renderer/assets/icons/svg/mini.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/assets/icons/svg/mix.svg b/src/renderer/assets/icons/svg/mix.svg new file mode 100644 index 0000000..b88133f --- /dev/null +++ b/src/renderer/assets/icons/svg/mix.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/assets/icons/svg/reduction.svg b/src/renderer/assets/icons/svg/reduction.svg new file mode 100644 index 0000000..c219e30 --- /dev/null +++ b/src/renderer/assets/icons/svg/reduction.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/renderer/assets/logo.png b/src/renderer/assets/logo.png new file mode 100644 index 0000000..63736e2 Binary files /dev/null and b/src/renderer/assets/logo.png differ diff --git a/src/renderer/components/mars-work/mars-map.vue b/src/renderer/components/mars-work/mars-map.vue new file mode 100644 index 0000000..431a918 --- /dev/null +++ b/src/renderer/components/mars-work/mars-map.vue @@ -0,0 +1,222 @@ + + + + diff --git a/src/renderer/error.ts b/src/renderer/error.ts new file mode 100644 index 0000000..ebf748f --- /dev/null +++ b/src/renderer/error.ts @@ -0,0 +1,20 @@ +import type { App } from 'vue' +import { nextTick } from "vue" +export const errorHandler = (App: App) => { + App.config.errorHandler = (err, vm, info) => { + nextTick(() => { + if (process.env.NODE_ENV === 'development') { + console.group('%c >>>>>> 错误信息 >>>>>>', 'color:red') + console.log(`%c ${info}`, 'color:blue') + console.groupEnd() + console.group('%c >>>>>> 发生错误的Vue 实例对象 >>>>>>', 'color:green') + console.log(vm) + console.groupEnd() + console.group('%c >>>>>> 发生错误的原因及位置 >>>>>>', 'color:red') + console.error(err) + console.groupEnd() + } + }) + } + +} \ No newline at end of file diff --git a/src/renderer/index.css b/src/renderer/index.css new file mode 100644 index 0000000..3987869 --- /dev/null +++ b/src/renderer/index.css @@ -0,0 +1,20 @@ +body, +html { + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} + +#app { + font-family: Avenir, Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* text-align: center; */ + color: #2c3e50; + /* margin-top: 60px; */ + width: 100%; + height: 100%; + margin: 0; + overflow: hidden; +} \ No newline at end of file diff --git a/src/renderer/index.html b/src/renderer/index.html new file mode 100644 index 0000000..9258945 --- /dev/null +++ b/src/renderer/index.html @@ -0,0 +1,27 @@ + + + + + + + + Mars3D三维地球 桌面程序 + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/src/renderer/main.ts b/src/renderer/main.ts new file mode 100644 index 0000000..2abb8e2 --- /dev/null +++ b/src/renderer/main.ts @@ -0,0 +1,10 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './index.css' +import { errorHandler } from './error' + +const app = createApp(App) +errorHandler(app) + +app.mount("#app") + diff --git a/static/config/config.json b/static/config/config.json new file mode 100644 index 0000000..91564c4 --- /dev/null +++ b/static/config/config.json @@ -0,0 +1,1103 @@ +{ + "map3d": { + "scene": { + "center": { "lat": 31.686288, "lng": 117.229619, "alt": 11333.9, "heading": 359.2, "pitch": -39.5 }, + "scene3DOnly": false, + "shadows": false, + "removeDblClick": true, + "sceneMode": 3, + "showSun": true, + "showMoon": true, + "showSkyBox": true, + "showSkyAtmosphere": true, + "fog": true, + "fxaa": true, + "requestRenderMode": true, + "globe": { + "depthTestAgainstTerrain": false, + "baseColor": "#546a53", + "showGroundAtmosphere": true, + "enableLighting": false + }, + "cameraController": { + "zoomFactor": 3.0, + "minimumZoomDistance": 1, + "maximumZoomDistance": 50000000, + "enableRotate": true, + "enableTranslate": true, + "enableTilt": true, + "enableZoom": true, + "enableCollisionDetection": true, + "minimumCollisionTerrainHeight": 15000 + } + }, + "control": { + "homeButton": true, + "baseLayerPicker": true, + "sceneModePicker": true, + "vrButton": false, + "fullscreenButton": true, + "navigationHelpButton": true, + "animation": false, + "timeline": false, + "infoBox": false, + "geocoder": false, + "selectionIndicator": false, + + "defaultContextMenu": true, + "mouseDownView": true, + "zoom": { "insertIndex": 1 }, + "compass": { "bottom": "toolbar", "left": "5px" }, + "distanceLegend": { "left": "100px", "bottom": "2px" }, + "locationBar": { + "fps": true, + "crs": "CGCS2000_GK_Zone_3", + "crsDecimal": 0, + "template": "
经度:{lng}
纬度:{lat}
横{crsx} 纵{crsy}
海拔:{alt}米
层级:{level}
方向:{heading}°
俯仰角:{pitch}°
视高:{cameraHeight}米
" + } + }, + "templateValues": { + "dataServer": "//data.mars3d.cn", + "gltfServerUrl": "//data.mars3d.cn/gltf" + }, + "terrain": { + "url": "//data.mars3d.cn/terrain", + "show": true + }, + "basemaps": [ + { "id": 10, "name": "地图底图", "type": "group" }, + { + "id": 2021, + "pid": 10, + "name": "天地图影像", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/tdt_img.png", + "type": "group", + "layers": [ + { "name": "底图", "type": "tdt", "layer": "img_d" }, + { "name": "注记", "type": "tdt", "layer": "img_z" } + ], + "show": true + }, + { + "pid": 10, + "name": "天地图电子", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/tdt_vec.png", + "type": "group", + "layers": [ + { "name": "底图", "type": "tdt", "layer": "vec_d" }, + { "name": "注记", "type": "tdt", "layer": "vec_z" } + ] + }, + { + "pid": 10, + "name": "高德影像", + "type": "group", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/gaode_img.png", + "layers": [ + { "name": "底图", "type": "gaode", "layer": "img_d" }, + { "name": "注记", "type": "gaode", "layer": "img_z" } + ] + }, + { + "pid": 10, + "name": "高德电子", + "type": "gaode", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/gaode_vec.png", + "layer": "vec" + }, + { + "pid": 10, + "name": "百度影像", + "type": "group", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/bd-img.png", + "layers": [ + { "name": "底图", "type": "baidu", "layer": "img_d" }, + { "name": "注记", "type": "baidu", "layer": "img_z" } + ] + }, + { + "pid": 10, + "name": "百度电子", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/bd-vec.png", + "type": "baidu", + "layer": "vec" + }, + { + "pid": 10, + "name": "腾讯影像", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/gaode_img.png", + "type": "group", + "layers": [ + { "name": "底图", "type": "tencent", "layer": "img_d" }, + { "name": "注记", "type": "tencent", "layer": "img_z" } + ] + }, + { + "pid": 10, + "name": "腾讯电子", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/gaode_vec.png", + "type": "tencent", + "layer": "vec" + }, + { + "pid": 10, + "name": "ArcGIS影像", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/esriWorldImagery.png", + "type": "xyz", + "url": "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", + "enablePickFeatures": false + }, + { + "pid": 10, + "name": "微软影像", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/bingAerial.png", + "type": "bing", + "layer": "Aerial" + }, + { + "pid": 10, + "name": "OSM地图", + "type": "xyz", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/osm.png", + "url": "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + "subdomains": "abc" + }, + { + "id": 2017, + "pid": 10, + "name": "暗色底图", + "type": "gaode", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/blackMarble.png", + "layer": "vec", + "invertColor": true, + "filterColor": "#4e70a6", + "brightness": 0.6, + "contrast": 1.8, + "gamma": 0.3, + "hue": 1, + "saturation": 0 + }, + { + "pid": 10, + "name": "蓝色底图", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/bd-c-midnight.png", + "type": "xyz", + "url": "http://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}", + "chinaCRS": "GCJ02", + "enablePickFeatures": false + }, + { + "pid": 10, + "name": "黑色底图", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/bd-c-dark.png", + "type": "tencent", + "layer": "custom", + "style": "4" + }, + { + "pid": 10, + "name": "离线地图 (供参考)", + "type": "group", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/google_img.png", + "layers": [ + { + "name": "全球", + "type": "xyz", + "url": "{dataServer}/tile/googleImg/{z}/{x}/{y}.jpg", + "minimumLevel": 0, + "maximumLevel": 9 + }, + { + "name": "中国", + "type": "xyz", + "url": "{dataServer}/tile/googleImg/{z}/{x}/{y}.jpg", + "minimumTerrainLevel": 10, + "minimumLevel": 10, + "maximumLevel": 12, + "rectangle": { "xmin": 69.706929, "xmax": 136.560941, "ymin": 15.831038, "ymax": 52.558005 } + }, + { + "name": "具体项目(如合肥)", + "type": "xyz", + "url": "{dataServer}/tile/googleImg/{z}/{x}/{y}.jpg", + "minimumTerrainLevel": 12, + "minimumLevel": 12, + "maximumLevel": 18, + "rectangle": { "xmin": 116.33236, "xmax": 118.183557, "ymin": 31.143784, "ymax": 32.565035 } + } + ] + }, + { + "pid": 10, + "name": "单张图片 (本地离线)", + "icon": "https://muyao1987.gitee.io/cdn/mars3d.cn/img/basemaps/offline.png", + "type": "image", + "url": "//data.mars3d.cn/file/img/world/world.jpg" + } + ], + "layers": [ + { "id": 50, "name": "辅助图层", "type": "group" }, + { "pid": 50, "type": "graticule", "name": "经纬网" }, + { + "pid": 50, + "name": "行政区划界线", + "type": "tdt", + "url": "https://t{s}.tianditu.gov.cn/DataServer?T=ibo_w&x={x}&y={y}&l={z}", + "maximumLevel": 10, + "mapSplit": false + }, + { + "pid": 50, + "name": "高德实时路况", + "type": "gaode", + "layer": "time", + "minimumTerrainLevel": 4, + "minimumLevel": 4, + "proxy": "//server.mars3d.cn/proxy/", + "mapSplit": false + }, + { + "pid": 50, + "name": "百度实时路况", + "type": "baidu", + "layer": "time", + "mapSplit": false + }, + + { "id": 60, "name": "地形", "type": "group" }, + { "pid": 60, "type": "terrain", "name": "Cesium地形", "terrain": { "type": "ion" }, "radio": true }, + { "pid": 60, "type": "terrain", "name": "Mars3D地形", "terrain": { "type": "xyz", "url": "{dataServer}/terrain" }, "radio": true }, + { + "pid": 60, + "type": "terrain", + "name": "ArcGIS地形", + "terrain": { "type": "arcgis", "url": "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer" }, + "radio": true + }, + { "pid": 60, "type": "terrain", "name": "无地形", "terrain": { "type": "none" }, "radio": true }, + + { "id": 40, "name": "栅格数据", "type": "group" }, + { "id": 4020, "pid": 40, "name": "OGC WMS服务", "type": "group" }, + { + "pid": 4020, + "name": "教育设施点", + "type": "wms", + "url": "//server.mars3d.cn/geoserver/mars/wms", + "layers": "mars:hfjy", + "crs": "EPSG:4326", + "parameters": { "transparent": "true", "format": "image/png" }, + "popup": "名称:{项目名称}
类型:{设施类型}
面积:{用地面积}亩
位置:{具体位置}", + "mapSplit": false, + "show": false, + "flyTo": true + }, + { + "pid": 4020, + "name": "道路线", + "type": "wms", + "url": "//server.mars3d.cn/geoserver/mars/wms", + "layers": "mars:hfdl", + "crs": "EPSG:4326", + "parameters": { "transparent": "true", "format": "image/png" }, + "center": { "lat": 31.743214, "lng": 117.277097, "alt": 47197.7, "heading": 0.3, "pitch": -78.8 }, + "popup": "all", + "mapSplit": false, + "show": false, + "flyTo": true + }, + { + "pid": 4020, + "name": "建筑物面", + "type": "wms", + "url": "//server.mars3d.cn/geoserver/mars/wms", + "layers": "mars:hfjzw", + "crs": "EPSG:4326", + "parameters": { "transparent": "true", "format": "image/png" }, + "highlight": { + "showTime": 5000, + "fill": true, + "color": "#2deaf7", + "opacity": 0.6, + "outline": true, + "outlineWidth": 3, + "outlineColor": "#e000d9", + "outlineOpacity": 1.0, + "clampToGround": true + }, + "center": { "lat": 31.79513, "lng": 117.236172, "alt": 3784.6, "heading": 0.7, "pitch": -42.2 }, + "popup": "all", + "show": false, + "flyTo": true + }, + { + "pid": 4020, + "name": "规划面", + "type": "wms", + "url": "//server.mars3d.cn/geoserver/mars/wms", + "layers": "mars:hfgh", + "crs": "EPSG:4326", + "parameters": { "transparent": "true", "format": "image/png" }, + "center": { "lat": 31.743214, "lng": 117.277097, "alt": 47197.7, "heading": 0.3, "pitch": -78.8 }, + "popup": "all", + "show": false, + "flyTo": true + }, + { "id": 4030, "pid": 40, "name": "ArcGIS 瓦片", "type": "group" }, + { + "pid": 4030, + "name": "合肥规划图", + "type": "arcgis_cache", + "url": "{dataServer}/arcgis_cache/hfgh/_alllayers/{z}/{y}/{x}.png", + "minimumLevel": 1, + "maximumLevel": 17, + "minimumTerrainLevel": 1, + "maximumTerrainLevel": 17, + "rectangle": { "xmin": 116.846, "xmax": 117.642, "ymin": 31.533, "ymax": 32.185 } + }, + { "id": 4010, "pid": 40, "name": "ArcGIS Dynamic", "type": "group" }, + { + "id": 401085, + "pid": 4010, + "type": "arcgis", + "name": "主要道路", + "url": "//server.mars3d.cn/arcgis/rest/services/mars/hefei/MapServer", + "layers": "24", + "highlight": { "type": "polyline", "color": "#2deaf7", "width": 4, "clampToGround": true }, + "center": { "lat": 31.814176, "lng": 117.225362, "alt": 5105.3, "heading": 359.2, "pitch": -83.1 }, + "popup": "all", + "onWidget": "layer-picture-heatmap", + "mapSplit": false + }, + { + "id": 401086, + "pid": 4010, + "type": "arcgis", + "name": "建筑物", + "url": "//server.mars3d.cn/arcgis/rest/services/mars/hefei/MapServer", + "layers": "35,36,37,39", + "highlight": { + "fill": true, + "color": "#2deaf7", + "opacity": 0.6, + "outline": true, + "outlineWidth": 3, + "outlineColor": "#e000d9", + "outlineOpacity": 1.0, + "clampToGround": true + }, + "center": { "lat": 31.816951, "lng": 117.22898, "alt": 2916.7, "heading": 0.3, "pitch": -78.8 }, + "popup": "名称:{NAME}
层数:{floor}", + "onWidget": "layer-picture-heatmap" + }, + { + "id": 401087, + "pid": 4010, + "type": "arcgis", + "name": "规划", + "url": "//server.mars3d.cn/arcgis/rest/services/mars/guihua/MapServer", + "highlight": { + "showTime": 5000, + "fill": true, + "color": "#2deaf7", + "opacity": 0.6, + "outline": true, + "outlineWidth": 3, + "outlineColor": "#e000d9", + "outlineOpacity": 1.0, + "clampToGround": true + }, + "center": { "lat": 31.816951, "lng": 117.22898, "alt": 2916.7, "heading": 0.3, "pitch": -78.8 }, + "popup": [ + { "field": "用地名称", "name": "名称" }, + { "field": "用地编号", "name": "编号" }, + { "field": "规划用地", "name": "规划" }, + { "type": "html", "html": "
数据仅供参考
" } + ], + "popupNoTitle": true, + "onWidget": "layer-picture-guihua" + }, + { "id": 30, "name": "矢量数据", "type": "group" }, + { "id": 3040, "pid": 30, "name": "平台标绘JSON", "type": "group" }, + { + "id": 303011, + "pid": 3040, + "type": "geojson", + "name": "示例数据", + "url": "{dataServer}/file/geojson/mars3d-draw.json", + "popup": "{type}{name}", + "show": false, + "flyTo": true + }, + { "id": 3030, "pid": 30, "name": "GeoJSON数据", "type": "group" }, + { + "pid": 3030, + "type": "geojson", + "name": "用地规划", + "url": "{dataServer}/file/geojson/guihua.json", + "symbol": { + "styleOptions": { "opacity": 0.6, "color": "#0000FF", "width": 3, "clampToGround": true }, + "styleField": "类型", + "styleFieldOptions": { + "一类居住用地": { "color": "#FFDF7F" }, + "二类居住用地": { "color": "#FFFF00" }, + "社区服务用地": { "color": "#FF6A38" }, + "幼托用地": { "color": "#FF6A38" }, + "商住混合用地": { "color": "#FF850A" }, + "行政办公用地": { "color": "#FF00FF" }, + "文化设施用地": { "color": "#FF00FF" }, + "小学用地": { "color": "#FF7FFF" }, + "初中用地": { "color": "#FF7FFF" }, + "体育场用地": { "color": "#00A57C" }, + "医院用地": { "color": "#A5527C" }, + "社会福利用地": { "color": "#FF7F9F" }, + "商业用地": { "color": "#FF0000" }, + "商务用地": { "color": "#7F0000" }, + "营业网点用地": { "color": "#FF7F7F" }, + "一类工业用地": { "color": "#A57C52" }, + "社会停车场用地": { "color": "#C0C0C0" }, + "通信用地": { "color": "#007CA5" }, + "排水用地": { "color": "#00BFFF" }, + "公园绿地": { "color": "#00FF00" }, + "防护绿地": { "color": "#007F00" }, + "河流水域": { "color": "#7FFFFF" }, + "配建停车场": { "color": "#ffffff" }, + "道路用地": { "color": "#ffffff" } + } + }, + "popup": "{类型}", + "show": false, + "flyTo": true + }, + { + "pid": 3030, + "type": "geojson", + "name": "建筑物面", + "url": "{dataServer}/file/geojson/buildings-demo.json", + "symbol": { "styleOptions": { "color": "#0d3685", "outlineColor": "#0d3685", "opacity": 0.8 } }, + "buildings": { "cloumn": "floors", "height": "flo_height" }, + "popup": "all", + "flyTo": true + }, + { + "pid": 3030, + "type": "geojson", + "name": "安徽各市", + "url": "{dataServer}/file/geojson/areas/340000_full.json", + "symbol": { + "type": "polygon", + "styleOptions": { + "materialType": "PolyGradient", + "color": "rgb(15,176,255)", + "opacity": 0.7, + "alphaPower": 1.3, + "diffHeight": "{gdp}", + "label": { + "text": "{name}", + "opacity": 1, + "font_size": 25, + "color": "#ffffff", + "outline": true, + "outlineColor": "#000000", + "outlineWidth": 3, + "scaleByDistance": true, + "scaleByDistance_far": 2743804, + "scaleByDistance_farValue": 0.3, + "scaleByDistance_near": 10000, + "scaleByDistance_nearValue": 1, + "distanceDisplayCondition": true, + "distanceDisplayCondition_far": 2743804, + "distanceDisplayCondition_near": 0 + } + } + }, + "popup": "{name}", + "show": false, + "flyTo": true + }, + { + "pid": 3030, + "type": "geojson", + "name": "中国省界", + "url": "{dataServer}/file/geojson/areas/100000_full.json", + "symbol": { + "type": "polylineP", + "styleOptions": { + "color": "#ffffff", + "width": 2, + "opacity": 0.8, + "label": { + "text": "{name}", + "position": "center", + "font_size": 30, + "color": "#ffffff", + "outline": true, + "outlineColor": "#000000", + "scaleByDistance": true, + "scaleByDistance_far": 60000000, + "scaleByDistance_farValue": 0.2, + "scaleByDistance_near": 1000000, + "scaleByDistance_nearValue": 1, + "distanceDisplayCondition": true, + "distanceDisplayCondition_far": 12000000, + "distanceDisplayCondition_near": 0 + } + } + }, + "show": false, + "flyTo": true + }, + { + "pid": 3030, + "type": "geojson", + "name": "西藏垭口", + "url": "{dataServer}/file/geojson/xizangyakou.json", + "symbol": { + "styleOptions": { + "image": "img/marker/mark1.png", + "scale": 1, + "scaleByDistance": true, + "scaleByDistance_far": 5000000, + "scaleByDistance_farValue": 0.5, + "scaleByDistance_near": 1000, + "scaleByDistance_nearValue": 1, + "verticalOrigin": 1, + "horizontalOrigin": 0, + "clampToGround": true, + "label": { + "text": "{NAME}", + "font_size": 25, + "color": "#ffff00", + "font_family": "微软雅黑", + "outline": true, + "outlineColor": "#000000", + "pixelOffsetY": -40, + "scaleByDistance": true, + "scaleByDistance_far": 1000000, + "scaleByDistance_farValue": 0.5, + "scaleByDistance_near": 1000, + "scaleByDistance_nearValue": 1, + "distanceDisplayCondition": true, + "distanceDisplayCondition_far": 1000000, + "distanceDisplayCondition_near": 0, + "visibleDepth": true + } + } + }, + "popup": [ + { "field": "NAME", "name": "名称" }, + { "type": "details", "callback": "showPopupDetails", "field": "图片", "className": "mars3d-popup-btn-custom" } + ], + "show": false, + "flyTo": true + }, + { + "pid": 3030, + "type": "geojson", + "name": "体育设施点", + "url": "{dataServer}/file/geojson/hfty-point.json", + "symbol": { + "styleOptions": { + "image": "img/marker/mark1.png", + "scale": 1, + "scaleByDistance": true, + "scaleByDistance_far": 20000, + "scaleByDistance_farValue": 0.5, + "scaleByDistance_near": 1000, + "scaleByDistance_nearValue": 1, + "verticalOrigin": 1, + "horizontalOrigin": 0, + "clampToGround": true, + "label": { + "text": "{项目名称}", + "font_size": 25, + "color": "#ffffff", + "outline": true, + "outlineColor": "#000000", + "pixelOffsetY": -25, + "scaleByDistance": true, + "scaleByDistance_far": 80000, + "scaleByDistance_farValue": 0.5, + "scaleByDistance_near": 1000, + "scaleByDistance_nearValue": 1, + "distanceDisplayCondition": true, + "distanceDisplayCondition_far": 80000, + "distanceDisplayCondition_near": 0 + } + } + }, + "popup": [ + { "field": "项目名称", "name": "项目名称" }, + { "field": "建设性质", "name": "建设性质" }, + { "field": "设施级别", "name": "设施级别" }, + { "field": "所属区县", "name": "所属区县" }, + { "field": "建筑内容及", "name": "建筑内容" }, + { "field": "新增用地(", "name": "新增用地" }, + { "field": "开工", "name": "开工" }, + { "field": "总投资(万", "name": "总投资" }, + { "field": "资金来源", "name": "资金来源" }, + { "field": "初步选址", "name": "初步选址" }, + { "field": "设施类型", "name": "设施类型" }, + { "field": "设施等级", "name": "设施等级" }, + { "field": "所在区县", "name": "所在区县" }, + { "field": "具体位置", "name": "具体位置" }, + { "field": "建设内容(", "name": "建设内容" }, + { "field": "用地面积(", "name": "用地面积", "format": "mars3d.MeasureUtil.formatArea" }, + { "field": "设施规模(", "name": "设施规模" }, + { "field": "举办者类型", "name": "举办者类型" }, + { "field": "开工时间", "name": "开工时间" }, + { "field": "总投资额(", "name": "总投资额", "unit": "亿元" }, + { "field": "项目推进主", "name": "项目推进主体" }, + { "field": "项目进度", "name": "项目进度" }, + { "field": "项目来源", "name": "项目来源" }, + { "field": "备注", "name": "备注" } + ], + "show": false, + "flyTo": true + }, + { "id": 3070, "pid": 30, "name": "GeoServer WFS", "type": "group" }, + { + "pid": 3070, + "type": "wfs", + "name": "建筑物面", + "url": "//server.mars3d.cn/geoserver/mars/ows", + "layer": "mars:hfjzw", + "parameters": { "maxFeatures": 500 }, + "minimumLevel": 15, + "symbol": { + "type": "polygonP", + "styleOptions": { "color": "#00469c", "outline": false, "opacity": 1 } + }, + "buildings": { "cloumn": "floor" }, + "center": { "lat": 31.818396, "lng": 117.229083, "alt": 2554.4, "heading": 359.2, "pitch": -83.1 }, + "popup": "名称:{NAME}
层数:{floor}" + }, + { + "pid": 3070, + "name": "教育设施点", + "type": "wfs", + "url": "//server.mars3d.cn/geoserver/mars/ows", + "layer": "mars:hfjy", + "parameters": { "maxFeatures": 500 }, + "minimumLevel": 13, + "symbol": { + "type": "billboardP", + "styleOptions": { + "image": "img/marker/mark1.png", + "scale": 0.7, + "scaleByDistance": true, + "scaleByDistance_far": 20000, + "scaleByDistance_farValue": 0.5, + "scaleByDistance_near": 1000, + "scaleByDistance_nearValue": 1, + "clampToGround": true, + "label": { + "text": "{项目名称}", + "font_size": 15, + "color": "#ffffff", + "outline": true, + "outlineColor": "#000000", + "pixelOffsetY": -30, + "distanceDisplayCondition": true, + "distanceDisplayCondition_far": 2000, + "distanceDisplayCondition_near": 0 + } + } + }, + "center": { "lat": 31.812256, "lng": 117.229873, "alt": 4683.91, "heading": 357.4, "pitch": -65.4 }, + "popup": "all" + }, + { "id": 3010, "pid": 30, "name": "ArcGIS WFS", "type": "group" }, + { + "pid": 3010, + "type": "arcgis_wfs", + "name": "兴趣点", + "url": "//server.mars3d.cn/arcgis/rest/services/mars/hefei/MapServer/1", + "where": " 1=1 ", + "minimumLevel": 15, + "center": { "lat": 31.818396, "lng": 117.229083, "alt": 2554.4, "heading": 359.2, "pitch": -83.1 }, + "symbol": { + "type": "billboardP", + "styleOptions": { + "image": "img/marker/mark3.png", + "scale": 0.7, + "scaleByDistance": true, + "scaleByDistance_far": 20000, + "scaleByDistance_farValue": 0.5, + "scaleByDistance_near": 1000, + "scaleByDistance_nearValue": 1, + "clampToGround": true, + "label": { + "text": "{NAME}", + "font_size": 15, + "color": "#ffffff", + "outline": true, + "outlineColor": "#000000", + "pixelOffsetY": -30, + "distanceDisplayCondition": true, + "distanceDisplayCondition_far": 3000, + "distanceDisplayCondition_near": 0 + } + }, + "styleField": "address", + "styleFieldOptions": { + "AB03": { "image": "img/marker/mark1.png" }, + "A980": { "image": "img/marker/mark2.png" }, + "A900": { "image": "img/marker/mark4.png" } + } + }, + "popup": "名称:{NAME}
地址:{address}", + "show": false + }, + { + "pid": 3010, + "type": "arcgis_wfs", + "name": "道路", + "url": "//server.mars3d.cn/arcgis/rest/services/mars/hefei/MapServer/28", + "minimumLevel": 14, + "symbol": { + "type": "polylineP", + "styleOptions": { "color": "#3388ff", "width": 3, "clampToGround": true }, + "styleField": "NAME", + "styleFieldOptions": { + "祁门路": { "color": "#8744c0", "width": 3 }, + "东流路": { "color": "#f7ba2a", "width": 3 }, + "翡翠路": { "color": "#20a0ff", "width": 3 }, + "岳西路": { "color": "#50bfff", "width": 3 } + } + }, + "popup": "名称:{NAME}", + "center": { "lat": 31.814176, "lng": 117.225362, "alt": 5105.3, "heading": 359.2, "pitch": -83.1 } + }, + { + "pid": 3010, + "type": "arcgis_wfs", + "name": "建筑物面", + "url": "//server.mars3d.cn/arcgis/rest/services/mars/hefei/MapServer/37", + "minimumLevel": 15, + "symbol": { "styleOptions": { "color": "#0d3685", "outlineColor": "#0d3685", "opacity": 0.8 } }, + "buildings": { "cloumn": "floor" }, + "debuggerTileInfo": false, + "center": { "lat": 31.816951, "lng": 117.22898, "alt": 1916.7, "heading": 0.3, "pitch": -78.8 }, + "popup": "名称:{NAME}
层数:{floor}" + }, + { "id": 3060, "pid": 30, "name": "CZML数据", "type": "group" }, + { + "id": 306010, + "pid": 3060, + "type": "czml", + "name": "汽车", + "url": "{dataServer}/file/czml/car.czml", + "center": { "lat": 40.894745, "lng": 121.920252, "alt": 904, "heading": 64, "pitch": -67 }, + "onWidget": "control-clock", + "radio": true, + "flyTo": true + }, + { + "id": 306011, + "pid": 3060, + "type": "czml", + "name": "飞行编队", + "url": "{dataServer}/file/czml/flight.czml", + "popup": "all", + "onWidget": "control-clock", + "radio": true, + "flyTo": true + }, + { + "id": 306012, + "pid": 3060, + "type": "czml", + "name": "船舶编队", + "url": "{dataServer}/file/czml/ship.czml", + "popup": "all", + "onWidget": "control-clock", + "radio": true, + "flyTo": true + }, + { "id": 3050, "pid": 30, "name": "KML数据", "type": "group" }, + { "pid": 3050, "type": "kml", "name": "海上安全警告", "url": "{dataServer}/file/kml/NAVWARN.kmz", "popup": "all" }, + { + "pid": 3050, + "type": "kml", + "name": "国境线", + "url": "{dataServer}/file/kml/countryboundary.kml", + "symbol": { "styleOptions": { "color": "#FED976", "width": 2 } } + }, + { + "pid": 3050, + "type": "kml", + "name": "省界线", + "url": "{dataServer}/file/kml/province.kml", + "symbol": { "styleOptions": { "color": "#00FF00", "width": 2 } } + }, + { "id": 20, "name": "三维模型", "type": "group" }, + { "id": 2010, "pid": 20, "name": "gltf模型", "type": "group" }, + { + "pid": 2010, + "type": "gltf", + "name": "风力发电机", + "url": "{dataServer}/gltf/mars/fengche.gltf", + "position": { "lng": 117.219071, "lat": 31.828783, "alt": 39.87 }, + "style": { "scale": 50, "heading": -93 }, + "popup": "示例信息,这是一个风力发电机", + "center": { "lat": 31.821083, "lng": 117.21832, "alt": 832.64, "heading": 2.3, "pitch": -39.2 } + }, + { + "pid": 2010, + "type": "gltf", + "name": "起重车", + "url": "{dataServer}/gltf/mars/qzcar/GKZY_anim.gltf", + "position": { "lng": 117.217458, "lat": 31.815349, "alt": 35.03 }, + "style": { "scale": 2, "heading": -95, "clampToGround": true }, + "center": { "lat": 31.815363, "lng": 117.215958, "alt": 107.35, "heading": 90.7, "pitch": -26.1 } + }, + { "id": 2040, "pid": 20, "name": "城市白模", "type": "group" }, + { + "id": 204011, + "pid": 2040, + "type": "3dtiles", + "name": "合肥市区", + "url": "{dataServer}/3dtiles/jzw-hefei/tileset.json", + "maximumScreenSpaceError": 1, + "maximumMemoryUsage": 1024, + "marsJzwStyle": true, + "highlight": { "type": "click", "color": "#FFFF00" }, + "popup": [ + { "field": "objectid", "name": "编号" }, + { "field": "name", "name": "名称" }, + { "field": "height", "name": "楼高", "unit": "米" } + ], + "center": { "lat": 31.786281, "lng": 117.223716, "alt": 3718, "heading": 2, "pitch": -45 } + }, + { + "id": 204012, + "pid": 2040, + "type": "3dtiles", + "name": "上海市区", + "url": "{dataServer}/3dtiles/jzw-shanghai/tileset.json", + "maximumScreenSpaceError": 4, + "maximumMemoryUsage": 1024, + "style": { + "color": { + "conditions": [ + ["${floor} >= 200", "rgba(45, 0, 75, 0.5)"], + ["${floor} >= 100", "rgb(170, 162, 204)"], + ["${floor} >= 50", "rgb(224, 226, 238)"], + ["${floor} >= 25", "rgb(252, 230, 200)"], + ["${floor} >= 10", "rgb(248, 176, 87)"], + ["${floor} >= 5", "rgb(198, 106, 11)"], + ["true", "rgb(127, 59, 8)"] + ] + } + }, + "highlight": { "type": "click", "color": "#FFFF00" }, + "popup": [ + { "field": "name", "name": "名称" }, + { "field": "floor", "name": "楼层" } + ], + "center": { "lat": 31.257341, "lng": 121.466139, "alt": 2170.8, "heading": 122.2, "pitch": -31.8 } + }, + + { "id": 2050, "pid": 20, "name": "点云", "type": "group" }, + { + "pid": 2050, + "type": "3dtiles", + "name": "高压线塔杆", + "url": "//data.mars3d.cn/3dtiles/pnts-ganta/tileset.json", + "maximumScreenSpaceError": 1, + "position": { "alt": 31 }, + "style": { + "color": { + "conditions": [ + ["(${Classification} >= 4) && (${Classification} < 5) ", "color('#DC143C')"], + ["(${Classification} >= 7) && (${Classification} < 8) ", "color('#7B68EE')"], + ["(${Classification} >= 16) && (${Classification} < 17) ", "color('#00CED1')"], + ["(${Classification} >= 17) && (${Classification} < 18) ", "color('#3CB371')"], + ["(${Classification} >= 18) && (${Classification} < 19) ", "color('#FFFF00')"], + ["(${Classification} >= 19) && (${Classification} < 20) ", "color('#FFA500')"], + ["(${Classification} >= 20) && (${Classification} < 21) ", "color('#FF6347')"] + ] + } + }, + "center": { "lat": 31.504746, "lng": 118.264278, "alt": 580, "heading": 29, "pitch": -49 } + }, + { "id": 2060, "pid": 20, "name": "BIM模型", "type": "group" }, + { + "id": 20601121, + "pid": 2060, + "type": "3dtiles", + "name": "大学教学楼", + "url": "{dataServer}/3dtiles/bim-daxue/tileset.json", + "position": { "lng": 117.251229, "lat": 31.844015, "alt": 31.2 }, + "highlight": { "type": "click", "color": "#FFFF00" }, + "popup": "all", + "scenetree": "scenetree.json", + "center": { "lat": 31.842516, "lng": 117.25107, "alt": 145, "heading": 8, "pitch": -39 } + }, + { + "pid": 2060, + "type": "3dtiles", + "name": "轻轨地铁站", + "url": "{dataServer}/3dtiles/bim-ditiezhan/tileset.json", + "position": { "lng": 117.203994, "lat": 31.857999, "alt": 28.9 }, + "rotation": { "z": 168.1 }, + "highlight": { "type": "click", "color": "#00FF00" }, + "popup": "all", + "scenetree": "scenetree.json", + "center": { "lat": 31.856125, "lng": 117.204513, "alt": 155, "heading": 350, "pitch": -31 } + }, + { + "id": 206012, + "pid": 2060, + "type": "3dtiles", + "name": "桥梁", + "url": "{dataServer}/3dtiles/bim-qiaoliang/tileset.json", + "position": { "lng": 117.096906, "lat": 31.851564, "alt": 45 }, + "rotation": { "z": 17.5 }, + "maximumScreenSpaceError": 16, + "maximumMemoryUsage": 1024, + "skipLevelOfDetail": true, + "loadSiblings": true, + "cullRequestsWhileMoving": true, + "cullRequestsWhileMovingMultiplier": 10, + "preferLeaves": true, + "progressiveResolutionHeightFraction": 0.5, + "dynamicScreenSpaceError": true, + "preloadWhenHidden": true, + "center": { "lat": 31.849357, "lng": 117.099194, "alt": 306.2, "heading": 327.1, "pitch": -30.9 }, + "scenetree": "scenetree.json", + "highlight": { "type": "click", "color": "#00FF00" }, + "popup": "all" + }, + { "id": 2020, "pid": 20, "name": "人工建模", "type": "group" }, + { + "id": 202013, + "pid": 2020, + "type": "3dtiles", + "name": "地下管网", + "url": "{dataServer}/3dtiles/max-piping/tileset.json", + "position": { "lng": 117.215457, "lat": 31.843363, "alt": -3.6 }, + "rotation": { "z": 336.7 }, + "maximumScreenSpaceError": 2, + "maximumMemoryUsage": 1024, + "highlight": { "type": "click", "color": "#00FF00" }, + "popup": "all", + "center": { "lat": 31.838821, "lng": 117.216402, "alt": 461, "heading": 0, "pitch": -46 }, + "msg": "演示数据,地下数据拖动时会在地面漂移" + }, + { + "id": 202012, + "pid": 2020, + "type": "3dtiles", + "name": "石化工厂", + "url": "{dataServer}/3dtiles/max-shihua/tileset.json", + "position": { "lng": 117.077158, "lat": 31.659116, "alt": 24.6 }, + "maximumScreenSpaceError": 1, + "maximumMemoryUsage": 1024, + "highlight": { "type": "click", "color": "#00FF00" }, + "popup": "all", + "scenetree": "scenetree.json", + "center": { "lat": 31.654916, "lng": 117.08278, "alt": 279, "heading": 316, "pitch": -29 } + }, + { + "id": 202030, + "pid": 2020, + "name": "水利闸门", + "type": "group", + "center": { "lat": 29.794301, "lng": 121.47998, "alt": 262, "heading": 191, "pitch": -35 } + }, + { + "pid": 202030, + "type": "gltf", + "name": "闸门", + "url": "{dataServer}/gltf/mars/zhamen.glb", + "position": { "lng": 121.479813, "lat": 29.791278, "alt": 16 }, + "style": { "heading": 105 }, + "center": { "lat": 29.791607, "lng": 121.479925, "alt": 27, "heading": 198, "pitch": -18 } + }, + { + "id": 202011, + "pid": 202030, + "type": "3dtiles", + "name": "整体", + "url": "{dataServer}/3dtiles/max-fsdzm/tileset.json", + "position": { "alt": 15.2 }, + "maximumScreenSpaceError": 1, + "maximumMemoryUsage": 1024, + "center": { "lat": 29.792675, "lng": 121.480207, "alt": 190.8, "heading": 196.1, "pitch": -49 } + }, + { "id": 2030, "pid": 20, "name": "倾斜摄影", "type": "group" }, + { + "id": 203014, + "pid": 2030, + "type": "3dtiles", + "name": "县城社区", + "url": "{dataServer}/3dtiles/qx-shequ/tileset.json", + "position": { "alt": 11.5 }, + "maximumScreenSpaceError": 2, + "maximumMemoryUsage": 2048, + "dynamicScreenSpaceError": true, + "cullWithChildrenBounds": false, + "center": { "lat": 28.440864, "lng": 119.486477, "alt": 588.23, "heading": 268.6, "pitch": -37.8 }, + "show": false, + "flyTo": false + }, + { + "id": 203015, + "pid": 2030, + "name": "合肥天鹅湖", + "type": "3dtiles", + "url": "{dataServer}/3dtiles/qx-teh/tileset.json", + "position": { "lng": 117.218434, "lat": 31.81807, "alt": 163 }, + "maximumScreenSpaceError": 16, + "maximumMemoryUsage": 1024, + "dynamicScreenSpaceError": true, + "cullWithChildrenBounds": false, + "skipLevelOfDetail": true, + "preferLeaves": true, + "center": { "lat": 31.795308, "lng": 117.21948, "alt": 1820, "heading": 0, "pitch": -39 } + }, + { + "id": 203013, + "pid": 2030, + "type": "geojson", + "name": "文庙-单体化", + "url": " {dataServer}/file/geojson/dth-wm.json", + "symbol": { + "type": "polygonP", + "styleOptions": { + "color": "rgba(255, 255, 255, 0.01)", + "clampToGround": true, + "classification": true, + "buffer": 1, + "highlight": { + "color": "rgba(255,255,0,0.5)" + } + } + }, + "popup": [ + { "field": "name", "name": "房屋名称" }, + { "field": "jznf", "name": "建造年份" }, + { "field": "ssdw", "name": "所属单位" }, + { "field": "remark", "name": "备注信息" } + ] + }, + { + "id": 203012, + "pid": 2030, + "type": "3dtiles", + "name": "文庙", + "url": "{dataServer}/3dtiles/qx-simiao/tileset.json", + "position": { "alt": 80.6 }, + "maximumScreenSpaceError": 2, + "maximumMemoryUsage": 2048, + "dynamicScreenSpaceError": true, + "cullWithChildrenBounds": false, + "skipLevelOfDetail": true, + "preferLeaves": true, + "center": { "lat": 33.589536, "lng": 119.032216, "alt": 145.08, "heading": 3.1, "pitch": -22.9 } + }, + { "id": 99, "name": "数据图层", "type": "group" } + ] + } +} diff --git a/static/loader.html b/static/loader.html new file mode 100644 index 0000000..3d070c3 --- /dev/null +++ b/static/loader.html @@ -0,0 +1,175 @@ + + + + + + Mars3D三维地球 桌面程序 + + + + + +
+ +
+ + + + +
+
正在准备资源中...
+
+ + + + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6a9bcc4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "resolveJsonModule": true, + "baseUrl": ".", + "outDir": "./dist/electron/main", + "sourceMap": true, + "declaration": false, + "module": "esnext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "es2017", + "types": ["vite/client"], + "paths": { + "@config/*": ["config/*"], + "@renderer/*": ["src/renderer/*"], + "@main/*": ["src/main/*"], + "@store/*": ["src/renderer/store/modules/*"] + }, + "typeRoots": ["node_modules/@types"], + "lib": ["es2018", "dom"] + }, + "include": ["src/**/*", "customTypes/*"], + "exclude": ["node_modules"] +} diff --git a/updateConfig.json b/updateConfig.json new file mode 100644 index 0000000..c4c852d --- /dev/null +++ b/updateConfig.json @@ -0,0 +1,11 @@ +{ + "output": "./build", + "target": "gzip", + "input": "./build/win-unpacked", + "updateJsonName": "update-config", + "version": "1.0.0", + "tempDirectory": "update_temp", + "oldDirectory": "old_temp", + "url": "http://localhost:5000", + "updaterName": "updater" +}