├── src
├── page
│ ├── info
│ │ ├── highwayXueye
│ │ │ ├── locations.json
│ │ │ └── HighwayXueye.vue
│ │ ├── carPlate
│ │ │ └── CarPlate.vue
│ │ ├── car
│ │ │ └── CarDepartment.vue
│ │ └── motorHighway
│ │ │ └── MotorHighway.vue
│ ├── Logout.vue
│ ├── pointer
│ │ ├── DrivingPolicy.ts
│ │ ├── Pointer.ts
│ │ ├── components
│ │ │ ├── PointerDetailPanel.vue
│ │ │ └── PointerListPanel.vue
│ │ └── PointerViewer.vue
│ ├── route
│ │ ├── DrivingPolicy.ts
│ │ ├── Route.ts
│ │ └── components
│ │ │ ├── RouteDetailPanel.vue
│ │ │ └── RouteLineListPanel.vue
│ ├── other
│ │ ├── map
│ │ │ ├── dataPoint.json
│ │ │ ├── dataLine.json
│ │ │ ├── province.json
│ │ │ └── MapLoca.vue
│ │ └── About.vue
│ ├── index
│ │ ├── Index.vue
│ │ └── LocaIndex.vue
│ ├── SearchPanel.vue
│ ├── MyMapLib.ts
│ ├── Login.vue
│ ├── tool
│ │ └── circle
│ │ │ ├── components
│ │ │ └── CirclePanel.vue
│ │ │ └── ToolCircle.vue
│ ├── Register.vue
│ └── debug
│ │ └── debug.vue
├── scss
│ ├── _plugin.scss
│ ├── _overwrite-amap.scss
│ ├── _gutter.scss
│ ├── _overwrite-el-ui.scss
│ ├── _reset.css
│ ├── _variables.scss
│ ├── _utility.scss
│ ├── _markdown.scss
│ ├── main.scss
│ ├── _marker.scss
│ └── animate.css
├── assets
│ └── logo.png
├── api
│ ├── ServerResponse.ts
│ ├── fileApi.ts
│ ├── routeApi.ts
│ ├── pointerApi.ts
│ ├── userApi.ts
│ └── request.ts
├── layout
│ ├── Container.vue
│ ├── FooterPagination.vue
│ ├── FloatingButton.vue
│ ├── Logo.vue
│ ├── Toolbar.vue
│ ├── Layout.vue
│ ├── Copyright.vue
│ ├── Aside.vue
│ ├── DetailPanel.vue
│ └── Navbar.vue
├── main.ts
├── mapConfig.ts
├── store.ts
├── App.vue
├── lib
│ └── colors.ts
├── utility.ts
└── router.ts
├── public
└── favicon.png
├── deploy.sh
├── tsconfig.node.json
├── CHANGELOG.md
├── .gitignore
├── tsconfig.json
├── index.html
├── vite.config.ts
├── package.json
└── README.md
/src/page/info/highwayXueye/locations.json:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/scss/_plugin.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 | @import "utility";
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleBing/map/HEAD/public/favicon.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleBing/map/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/page/Logout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
11 |
--------------------------------------------------------------------------------
/src/api/ServerResponse.ts:
--------------------------------------------------------------------------------
1 | interface ServerResponse{
2 | message: string,
3 | success: boolean,
4 | data: any
5 | }
6 |
7 | export {
8 | type ServerResponse
9 | }
10 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd /usr/share/nginx/html &&
3 | rm -Rf tools/map/* &&
4 | mv map-* tools/map/ &&
5 | cd tools/map &&
6 | unzip map-* &&
7 | rm -f map-*
8 | echo 'Map deploy finished.'
9 |
--------------------------------------------------------------------------------
/src/scss/_overwrite-amap.scss:
--------------------------------------------------------------------------------
1 | // 隐藏地图中的高德地图 LOGG
2 | .amap-copyright, .amap-logo{
3 | display: none !important;
4 | }
5 |
6 | .amap-control.amap-scalecontrol{
7 | bottom: 10px !important;
8 | }
--------------------------------------------------------------------------------
/src/api/fileApi.ts:
--------------------------------------------------------------------------------
1 | import {request} from "./request";
2 |
3 | function getUploadToken(params) {return request('get', params, null,false, '/image-qiniu/')}
4 | export {
5 | getUploadToken
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts", "src/*.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## CHANGELOG
2 |
3 | ## `2025-06-17`
4 | - 添加 gpx 导出到 svg
5 |
6 | ## v1.0 `2024-07-27`
7 | - 完成 ts 迁移
8 | - 编辑对应页面时,标点可拖拽改变位置
9 |
10 | ## `2024-07-22`
11 | - 改成 vite+ts+vue3 版本
12 |
13 | ### v0.23 `2023-07-03`
14 | - 添加注册页面
15 | - 去除主页 loca 动画
16 |
17 | ### `2023-06-27`
18 | - 添加市的区县信息显示
19 | - 添加路径上的天气信息
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
25 |
26 | archive
27 |
--------------------------------------------------------------------------------
/src/api/routeApi.ts:
--------------------------------------------------------------------------------
1 | import {request} from './request'
2 | export default {
3 | add( requestData){return request('post', null, requestData, false, 'map-route/add')},
4 | list(requestData){return request('post', null, requestData, false, 'map-route/list')},
5 | modify(requestData){return request('put', null, requestData, false, 'map-route/modify')},
6 | delete(requestData){return request('delete', null, requestData, false, 'map-route/delete')},
7 | detail(params){return request('get', params, null, false, 'map-route/detail')},
8 | }
9 |
--------------------------------------------------------------------------------
/src/api/pointerApi.ts:
--------------------------------------------------------------------------------
1 | import {request} from './request'
2 | export default {
3 | add( requestData){return request('post', null, requestData, false, 'map-pointer/add')},
4 | list(requestData){return request('post', null, requestData, false,'map-pointer/list')},
5 | modify(requestData){return request('put', null, requestData, false, 'map-pointer/modify')},
6 | delete(requestData){return request('delete', null, requestData, false, 'map-pointer/delete')},
7 | detail(params){return request('get', params, null, false, 'map-pointer/detail')},
8 | }
9 |
--------------------------------------------------------------------------------
/src/layout/Container.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 |
19 |
28 |
--------------------------------------------------------------------------------
/src/scss/_gutter.scss:
--------------------------------------------------------------------------------
1 | $timer: 10px;
2 | // common
3 | @for $item from 0 through 7 {
4 | .mt-#{$item}{margin-top: $timer * $item !important;}
5 | .mb-#{$item}{margin-bottom: $timer * $item !important;}
6 | .ml-#{$item}{margin-left: $timer * $item !important;}
7 | .mr-#{$item}{margin-right: $timer * $item !important;}
8 | .m-#{$item}{margin: $timer * $item !important;}
9 |
10 | .pt-#{$item}{padding-top: $timer * $item !important;}
11 | .pb-#{$item}{padding-bottom: $timer * $item !important;}
12 | .pl-#{$item}{padding-left: $timer * $item !important;}
13 | .pr-#{$item}{padding-right: $timer * $item !important;}
14 | .p-#{$item}{padding: $timer * $item !important;}
15 | }
16 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | // main.ts
2 | import { createApp } from 'vue'
3 |
4 | // APP
5 | import App from './App.vue'
6 | const app = createApp(App)
7 |
8 | // ELEMENT-UI
9 | import ElementPlus from 'element-plus'
10 | import 'element-plus/dist/index.css'
11 | import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
12 |
13 | app.use(ElementPlus, {
14 | locale: zhCn
15 | })
16 |
17 | // ELEMENT-UI-ICONS
18 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'
19 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
20 | app.component(key, component)
21 | }
22 |
23 | // PINIA
24 | import {createPinia} from "pinia"
25 | const pinia = createPinia()
26 | app.use(pinia)
27 |
28 |
29 | // ROUTER
30 | import {router} from "./router"
31 | app.use(router)
32 |
33 | app.mount('#app')
34 |
--------------------------------------------------------------------------------
/src/page/pointer/DrivingPolicy.ts:
--------------------------------------------------------------------------------
1 | const policyArray = [
2 | {label: '时间最少', key: 'LEAST_TIME', value: 0,},
3 | {label: '最少花费', key: 'LEAST_FEE', value: 1,},
4 | {label: '最短距离', key: 'LEAST_DISTANCE', value: 2,},
5 | {label: '考虑实际路况', key: 'REAL_TRAFFIC', value: 4,},
6 | // {label: '', key_service: 'MULTI_POLICIES', value: 5,},
7 | // {label: '', key_service: 'HIGHWAY', value: 6,},
8 | // {label: '', key_service: 'FEE_HIGHWAY', value: 7,},
9 | // {label: '', key_service: 'FEE_TRAFFIC', value: 8,},
10 | // {label: '', key_service: 'TRAFFIC_HIGHWAY', value: 9,},
11 | ]
12 |
13 | let policyMap = new Map()
14 | policyArray.forEach(item => {
15 | policyMap.set(item.value, item.label)
16 | })
17 |
18 |
19 | export {
20 | policyArray, policyMap
21 | }
22 |
23 | // 更新于 2023-04-12
24 |
--------------------------------------------------------------------------------
/src/api/userApi.ts:
--------------------------------------------------------------------------------
1 | import {request} from "./request";
2 |
3 | export default {
4 | login(requestData){return request('post', null, requestData, false, 'user/login')},
5 | register(requestData) {return request('post', {}, requestData, false, 'user/register')},
6 | add(requestData) {return request('post', {}, requestData, false, 'user/add')},
7 | delete(requestData) {return request('delete', {}, requestData, false, 'user/delete')},
8 | modify(requestData) {return request('put', {}, requestData, false, 'user/modify')},
9 | detail(params) {return request('get', params, null, false, 'user/detail')},
10 | list(requestData) {return request('post', null, requestData, false, 'user/list')},
11 | changePassword(requestData) {return request('put', null, requestData, false, 'user/change-password')},
12 | }
13 |
--------------------------------------------------------------------------------
/src/page/route/DrivingPolicy.ts:
--------------------------------------------------------------------------------
1 | const policyArray = [
2 | {label: '时间最少', key: 'LEAST_TIME', value: 0,},
3 | {label: '最少花费', key: 'LEAST_FEE', value: 1,},
4 | {label: '最短距离', key: 'LEAST_DISTANCE', value: 2,},
5 | {label: '考虑实时路况', key: 'REAL_TRAFFIC', value: 4,},
6 | {label: '综合考虑', key_service: 'MULTI_POLICIES', value: 5,},
7 | {label: '优先高速', key_service: 'HIGHWAY', value: 6,},
8 | {label: '经济且优先高速', key_service: 'FEE_HIGHWAY', value: 7,},
9 | {label: '经济并考虑交通情况', key_service: 'FEE_TRAFFIC', value: 8,},
10 | {label: '考虑交通并优先选择高速', key_service: 'TRAFFIC_HIGHWAY', value: 9,},
11 | ]
12 |
13 | let policyMap = new Map()
14 | policyArray.forEach(item => {
15 | policyMap.set(item.value, item.label)
16 | })
17 |
18 |
19 | export {
20 | policyArray, policyMap
21 | }
22 |
23 | // 更新于 2023-04-12
24 |
--------------------------------------------------------------------------------
/src/page/other/map/dataPoint.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "FeatureCollection",
3 | "features": [
4 | {"type": "Feature", "properties": {"country": "山东", "time": "1949年10月6日", "flagName": "shandong", "type": 1}, "geometry": {"type": "Point", "coordinates": [117.132546, 36.693774]}},
5 | {"type": "Feature", "properties": {"country": "蒙古", "time": "1949年10月16日", "flagName": "menggu", "type": 1}, "geometry": {"type": "Point", "coordinates": [103.453, 46.8528]}},
6 | {"type": "Feature", "properties": {"country": "阿尔巴尼亚", "time": "1949年11月23日", "flagName": "aerbaniya", "type": 1}, "geometry": {"type": "Point", "coordinates": [20.1179, 41.1489]}},
7 | {"type": "Feature", "properties": {"country": "保加利亚", "time": "1949年10月4日", "flagName": "baojialiya", "type": 1}, "geometry": {"type": "Point", "coordinates": [25.3611, 42.7454]}}
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 |
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": ["src/*"]
7 | },
8 |
9 | "target": "ES2020",
10 | "useDefineForClassFields": true,
11 | "module": "ESNext",
12 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
13 | "skipLibCheck": true,
14 |
15 | /* Bundler mode */
16 | "moduleResolution": "bundler",
17 | "allowImportingTsExtensions": true,
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "preserve",
22 |
23 | /* Linting */
24 | "strict": true,
25 | "noUnusedLocals": true,
26 | "noUnusedParameters": true,
27 | "noFallthroughCasesInSwitch": true,
28 | "allowSyntheticDefaultImports": true,
29 | },
30 |
31 | "include": ["src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
32 | "references": [{ "path": "./tsconfig.node.json" }]
33 | }
34 |
--------------------------------------------------------------------------------
/src/page/other/map/dataLine.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "FeatureCollection",
3 | "features": [
4 | {"type": "Feature", "properties": {"country": "山东", "time": "1949年10月6日", "flagName": "shandong", "type": 1}, "geometry": {"type": "LineString", "coordinates": [[121.504673, 25.046711], [117.132546, 36.693774]]}},
5 | {"type": "Feature", "properties": {"country": "蒙古", "time": "1949年10月16日", "flagName": "menggu", "type": 1}, "geometry": {"type": "LineString", "coordinates": [[121.504673, 25.046711], [103.453, 46.8528]]}},
6 | {"type": "Feature", "properties": {"country": "阿尔巴尼亚", "time": "1949年11月23日", "flagName": "aerbaniya", "type": 1}, "geometry": {"type": "LineString", "coordinates": [[121.504673, 25.046711], [20.1179, 41.1489]]}},
7 | {"type": "Feature", "properties": {"country": "保加利亚", "time": "1949年10月4日", "flagName": "baojialiya", "type": 1}, "geometry": {"type": "LineString", "coordinates": [[121.504673, 25.046711], [25.3611, 42.7454]]}}
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/src/scss/_overwrite-el-ui.scss:
--------------------------------------------------------------------------------
1 | //input[type=number] {
2 | // padding-right: 0!important;
3 | //}
4 | //
5 | //.el-input__inner{
6 | // padding-left: 6px !important;
7 | //}
8 | //.el-textarea__inner{
9 | // padding: 5px 8px !important;
10 | //}
11 |
12 | .el-table .cell {
13 | line-height: 18px;
14 | }
15 |
16 | // TAB
17 | .el-tabs__item{
18 | height: 30px !important;
19 | line-height: 30px !important;
20 | font-size: $fz-normal;
21 | }
22 | .el-tabs__content{
23 | padding: 0 !important;
24 | }
25 |
26 | .el-tabs--border-card>.el-tabs__header{
27 | height: 30px !important;
28 | margin-bottom: 0 !important;
29 | border-bottom: none;
30 | }
31 |
32 |
33 | .search-bar{
34 | margin-left: 20px;
35 | margin-right: 10px;
36 | padding: 0 10px;
37 | align-items: center;
38 | height: 50px;
39 | display: flex;
40 | justify-content: space-between;
41 | .el-form-item{
42 | margin-right: 15px !important;
43 | margin-bottom: 0;
44 | }
45 | }
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/mapConfig.ts:
--------------------------------------------------------------------------------
1 | const key_web_js = '581591b581149549d9035d039e83e368' // web js key
2 | const key_service = '401d946dc1152f0e1f110928ecc07a13' // web服务 key
3 | // 管理地址在:https://console.amap.com/dev/key/app
4 |
5 | // 七牛云 与 《标题日记》共用一个 仓库
6 | // 地址: https://portal.qiniu.com/kodo/overview
7 | const qiniu_img_base_url = 'http://apple-image.kylebing.cn/' // 空间域名,最后面带 `/`
8 | const qiniu_bucket_name = 'apple-image' // 七牛云对象存储空间的名称
9 | const thumbnail200_suffix = 'thumbnail_200px' // 七牛云缩略图样式名 200x200px 质量75
10 | const thumbnail600_suffix = 'thumbnail_600px' // 七牛云缩略图样式名 600x600px 质量75
11 | const thumbnail1000_suffix = 'thumbnail_1000px' // 七牛云缩略图样式名 1000x1000px 质量75
12 | const thumbnail1500_suffix = 'thumbnail_1500px' // 七牛云缩略图样式名 1500x1500px 质量75
13 |
14 | export {
15 | key_web_js,
16 | key_service,
17 | qiniu_img_base_url,
18 | qiniu_bucket_name,
19 | thumbnail200_suffix,
20 | thumbnail600_suffix,
21 | thumbnail1000_suffix,
22 | thumbnail1500_suffix,
23 | }
24 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 路书
9 |
10 |
11 |
12 |
13 |
14 |
15 |
25 |
30 |
31 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import zipPack from "vite-plugin-zip-pack" // make dist.zip file
4 |
5 | import {resolve} from "path"
6 | import {dateFormatter} from "./src/utility";
7 |
8 | const timeStringNow = dateFormatter(new Date(), 'yyyy-MM-dd hh-mm')
9 |
10 | // https://vitejs.dev/config/
11 | export default defineConfig({
12 | base: './',
13 | plugins: [
14 | vue(),
15 | zipPack({
16 | inDir: 'dist',
17 | outDir: 'archive',
18 | outFileName: `map-${timeStringNow}.zip`,
19 | pathPrefix: ''
20 | }),
21 | ],
22 | resolve: {
23 | alias: {
24 | '@': resolve(__dirname, 'src'),
25 | },
26 | },
27 | server: {
28 | proxy: {
29 | '/dev': {
30 | target: 'http://kylebing.cn/portal/',
31 | // target: 'http://localhost:3000/',
32 | changeOrigin: true,
33 | rewrite: (path) => path.replace(/^\/dev/, '/'),
34 | },
35 | }
36 | }
37 | })
38 |
--------------------------------------------------------------------------------
/src/store.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from "pinia";
2 | import {EntityAuthorization} from "@/utility.ts";
3 |
4 | export const useProjectStore = defineStore('storeProject', {
5 | state: ()=>({
6 | navWidth: 200, // 导航宽度
7 | windowInsets: { // 窗口数据
8 | height: 0,
9 | width: 0
10 | },
11 | contentInsets: {
12 | heightToolbar: 60, // toolbar 高度
13 | heightPager: 50, // pagination 高度
14 | heightContent: 0, // 内容高度
15 | widthContent: 0, // 内容宽度
16 | },
17 | isNavMenuFold: false, // navMenu 是否折叠状态
18 | isShowFloatingMenuBtn: false, // 是否显示移动端的菜单切换按钮
19 | authorization: null as EntityAuthorization, // authorization
20 |
21 | isShowUserSelfLocation: false, // 是否显示用户自己的位置, http 下无用,https 才有用
22 | }),
23 | getters: {
24 | isInPortraitMode (state) {
25 | return state.windowInsets.height > state.windowInsets.width
26 | },
27 | isAdmin (state) {
28 | return state.authorization && state.authorization.group_id === 1
29 | },
30 | }
31 | })
32 |
33 |
--------------------------------------------------------------------------------
/src/layout/FooterPagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
35 |
36 |
45 |
--------------------------------------------------------------------------------
/src/page/route/Route.ts:
--------------------------------------------------------------------------------
1 | interface EntityRoute {
2 | id?: number,
3 | name: string, // 路线名称
4 | area: string, // 地址位置
5 | road_type: string, // 路面类型
6 | note: string, // 备注
7 | is_public: 0|1, // 是否共享 0 否 1 是
8 |
9 | video_link?: string, // 视频链接
10 | thumb_up?: number, // 点赞数
11 | seasonsArray?: Array, // 季节数组
12 | seasons?: string, // 骑行季节
13 | paths?: string, // 路线节点
14 | pathArray?: Array,
15 | date_modify?: string, // 编辑时间
16 | date_init?: string, // 创建时间
17 | uid?: number, // user ID
18 | policy?: number, // 路线规划策略
19 |
20 | distance?: number,
21 | nickname?: string
22 | time?: string
23 | }
24 |
25 | interface EntityRoutePoint {
26 | type: string, // 颜色类别,为了匹配 EntityPointerPoint 的属性,共用一个方法
27 | position: [number, number], // 地址 lng,lat
28 | note: string, // 标点备注
29 | name: string, // 标点名称
30 | img?: string // 图片 http 路径
31 | }
32 |
33 | export {
34 | type EntityRoute,
35 | type EntityRoutePoint
36 | }
37 |
--------------------------------------------------------------------------------
/src/scss/_reset.css:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | border: 0;
5 | font-family: inherit;
6 | color: inherit;
7 | -webkit-box-sizing: border-box;
8 | -moz-box-sizing: border-box;
9 | box-sizing: border-box; }
10 |
11 | body {
12 | font-family: "PingFang SC", "Microsoft Yahei UI", "Microsoft Yahei", "Helvetica", sans-serif;
13 | width: 100%;
14 | margin: 0 auto; }
15 |
16 | a {
17 | text-decoration: none; }
18 |
19 | table {
20 | border-collapse: collapse; }
21 |
22 | code,
23 | kbd,
24 | pre,
25 | samp,
26 | tt {
27 | font-family: "Courier New", Courier, monospace; }
28 |
29 | :focus,
30 | :active {
31 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
32 | outline: none; }
33 |
34 | ::-webkit-scrollbar {
35 | z-index: 50;
36 | width: 3px;
37 | height: 3px; }
38 |
39 | ::-webkit-scrollbar-track {
40 | border-bottom: 1px solid #eeeeee;
41 | border-right: 1px solid #eeeeee;
42 | background-color: rgba(0, 0, 0, 0); }
43 |
44 | ::-webkit-scrollbar-thumb {
45 | -webkit-border-radius: 5px;
46 | -moz-border-radius: 5px;
47 | border-radius: 5px;
48 | background-color: #eee;
49 | transition: all .2s;
50 | height: 3px; }
51 |
52 | :hover::-webkit-scrollbar-thumb {
53 | background-color: #dfdfdf;
54 | transition: all .2s; }
55 |
56 | ::-webkit-scrollbar-button {
57 | display: none; }
58 |
59 | ::-webkit-scrollbar-corner {
60 | display: none; }
--------------------------------------------------------------------------------
/src/layout/FloatingButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
35 |
36 |
60 |
--------------------------------------------------------------------------------
/src/layout/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 |
7 |
8 |
9 |
10 |
30 |
31 |
56 |
--------------------------------------------------------------------------------
/src/layout/Toolbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
21 |
22 |
23 |
55 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 地图多次拖动后会变得卡顿,刷新页面即可
6 |
9 |
10 |
11 |
12 |
43 |
44 |
47 |
--------------------------------------------------------------------------------
/src/page/pointer/Pointer.ts:
--------------------------------------------------------------------------------
1 | export interface EntityPointer {
2 | id?: number, // ID
3 | name: string, // *点图名
4 | note: string, // 简介
5 | uid?: number, // 创建人
6 | date_create?: string, // 创建时间
7 | date_modify?: string, // 编辑时间
8 | area: string, // *地域
9 | thumb_up: number, // *点赞数
10 | is_public: number, // *是否公开
11 | visit_count?: number, // 访问次数
12 | video_link: string, // 路径视频演示
13 | pointers: string, // *路径点 json 字符串
14 | pointer_array?: Array, // *路径点
15 |
16 | nickname: string, // 创建用户
17 | username: string, // 用户名
18 | }
19 |
20 | export interface EntityPointerPoint{
21 | note: string,
22 | img?: string,
23 | position: [number, number],
24 | type: string,
25 | name: string
26 | }
27 |
28 | export enum EnumPointerType {
29 | "yellow" = "yellow",
30 | "orange" = "orange",
31 | "red" = "red",
32 | "green" = "green",
33 | "blue" = "blue",
34 | }
35 | export const EnumPointerTypeMap = new Map([
36 | [EnumPointerType.yellow, "黄色"],
37 | [EnumPointerType.orange, "橙色"],
38 | [EnumPointerType.red, "红色"],
39 | [EnumPointerType.green, "绿色"],
40 | [EnumPointerType.blue, "蓝色"],
41 | ])
42 |
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "riding-map",
3 | "date-update": "2025.10.23",
4 | "version": "1.0.1",
5 | "private": true,
6 | "github": "https://github.com/KyleBing/map",
7 | "author": {
8 | "name": "KyleBing",
9 | "email": "kylebing@163.com",
10 | "homepage": "http://kylebing.cn"
11 | },
12 | "scripts": {
13 | "dev": "vite --host",
14 | "build": "tsc && vite build",
15 | "preview": "vite preview"
16 | },
17 | "dependencies": {
18 | "@amap/amap-jsapi-loader": "^1.0.1",
19 | "@element-plus/icons-vue": "^2.1.0",
20 | "axios": "^1.6.3",
21 | "clipboard": "^2.0.11",
22 | "countup.js": "^2.8.0",
23 | "echarts": "^5.5.0",
24 | "js-base64": "^3.7.5",
25 | "moment": "^2.29.4",
26 | "pinia": "^2.1.7",
27 | "sass-loader": "^13.3.2",
28 | "vue": "^3.4.27",
29 | "vue-router": "^4.2.5",
30 | "animate-bg-canvas": "^0.1.2",
31 | "animate-heart-canvas": "^0.1.9",
32 | "core-js": "^3.8.3",
33 | "element-plus": "^2.6.0",
34 | "fast-xml-parser": "^4.3.3",
35 | "filemanager-webpack-plugin": "^7.0.0",
36 | "marked": "^4.2.4",
37 | "qiniu-js": "^3.4.1",
38 | "geosvg": "^1.2.3"
39 | },
40 | "devDependencies": {
41 | "@types/mockjs": "^1.0.10",
42 | "@types/spark-md5": "^3.0.4",
43 | "@types/amap-js-api": "^1.4.16",
44 | "@vitejs/plugin-vue": "^4.2.3",
45 | "sass": "^1.69.5",
46 | "typescript": "^5.0.2",
47 | "vite": "^4.4.5",
48 | "vite-plugin-mock": "^3.0.2",
49 | "vite-plugin-zip-pack": "^1.0.7",
50 | "vue-tsc": "^1.8.5"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | // Colors
2 | $green : #4CD964;
3 | $cyan : #5AC8FA;
4 | $blue : #007AFF;
5 | $purple : #5856D6;
6 | $magenta : #FF2D70;
7 | $red : #FF3B30;
8 | $orange : #FF9500;
9 | $yellow : #FFCC00;
10 | $gray : #8E8E93;
11 |
12 | // ELEMENT COLORS
13 | $color-main : $magenta;
14 | $border-color-nav: transparentize($color-main, 0.8);
15 | $border-light: #f2f2f2;
16 |
17 | // FONT SIZE
18 | $fz-title: 16px;
19 | $fz-info: 30px;
20 | $fz-normal: 13px;
21 | $fz-small: 11px;
22 |
23 | $bg-active: transparentize($color-main, 0.9);
24 | $bg-second: transparentize(black, 0.95);
25 | $bg-light: #f7f7f7;
26 |
27 |
28 | $color-primary : #409EFF;
29 | $color-success : #67C23A;
30 | $color-warning : #E6A23C;
31 | $color-danger : #F56C6C;
32 | $color-info : #909399;
33 |
34 | $border-normal: #eee;
35 |
36 | $text-main : #333;
37 | $text-description: #666;
38 | $text-subtitle : #848484;
39 |
40 | // USER CENTER
41 | $gradient-blue : $cyan, $blue;
42 | $gradient-gray : $gray, darken($gray, 15%);
43 | $gradient-green : $green, darken($green, 15%);
44 | $gradient-orange : $orange, darken($orange, 5%);
45 | $gradient-red : $red, darken($red, 15%);
46 | $gradient-purple : $purple, darken($purple, 15%);
47 | $gradient-magenta : $magenta, darken($magenta, 15%);
48 | $gradient-yellow : $yellow, darken($yellow, 15%);
49 | $gradient-time : #79C1F0, #7E87EF;
50 |
51 |
52 | $radius: 5px;
53 |
54 |
55 | $screen-width-threshold: 500px;
56 |
57 | $width-float-btn: 45px; // 悬浮的按钮宽度
58 |
--------------------------------------------------------------------------------
/src/page/index/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
路书
6 |
一个可以分享行车路线、地点信息的网站
7 |
8 |
在这里,你可以查看别人分享的一些路线 看这。
9 |
10 |
有时候,可能需要在地图上写写画画,想要确定去一个地方哪条路线最优,可以 点这里。
11 |
也有时候,可能需要查看一个地点周边方圆多少公里的覆盖范围是怎样的,可以 点这里。
12 |
再有时候,可能需要查看一个市的区、县信息,可以 看这里。
13 |
还有可能,需要查看一个 gpx 路径是怎样的,可以 看这里。
14 |
如果要查看一个 gpx 的 3D 路径,可以 看这里。
15 |
16 |
另外,该项目是开源的,可以从这里查看 https://github.com/KyleBing/map。
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
56 |
--------------------------------------------------------------------------------
/src/page/other/map/province.json:
--------------------------------------------------------------------------------
1 | [
2 | { "name": "北京市", "center": "116.407394,39.904211" },
3 | { "name": "天津市", "center": "117.200983,39.084158" },
4 | { "name": "河北省", "center": "114.530235,38.037433" },
5 | { "name": "山西省", "center": "112.562678,37.873499" },
6 | { "name": "内蒙古自治区", "center": "111.76629,40.81739" },
7 | { "name": "辽宁省", "center": "123.431382,41.836175" },
8 | { "name": "吉林省", "center": "125.32568,43.897016" },
9 | { "name": "黑龙江省", "center": "126.661665,45.742366" },
10 | { "name": "上海市", "center": "121.473662,31.230372" },
11 | { "name": "江苏省", "center": "118.762765,32.060875" },
12 | { "name": "浙江省", "center": "120.152585,30.266597" },
13 | { "name": "安徽省", "center": "117.329949,31.733806" },
14 | { "name": "福建省", "center": "119.295143,26.100779" },
15 | { "name": "江西省", "center": "115.81635,28.63666" },
16 | { "name": "山东省", "center": "117.019915,36.671156" },
17 | { "name": "河南省", "center": "113.753394,34.765869" },
18 | { "name": "湖北省", "center": "114.341745,30.546557" },
19 | { "name": "湖南省", "center": "112.9836,28.112743" },
20 | { "name": "广东省", "center": "113.26641,23.132324" },
21 | { "name": "广西壮族自治区", "center": "108.327546,22.815478" },
22 | { "name": "海南省", "center": "110.349228,20.017377" },
23 | { "name": "重庆市", "center": "106.551643,29.562849" },
24 | { "name": "四川省", "center": "104.075809,30.651239" },
25 | { "name": "贵州省", "center": "106.70546,26.600055" },
26 | { "name": "云南省", "center": "102.710002,25.045806" },
27 | { "name": "西藏自治区", "center": "91.117525,29.647535" },
28 | { "name": "陕西省", "center": "108.954347,34.265502" },
29 | { "name": "甘肃省", "center": "103.826447,36.05956" },
30 | { "name": "青海省", "center": "101.780268,36.620939" },
31 | { "name": "宁夏回族自治区", "center": "106.259126,38.472641" },
32 | { "name": "新疆维吾尔自治区", "center": "87.627704,43.793026" },
33 | { "name": "香港特别行政区", "center": "114.171203,22.277468" },
34 | { "name": "澳门特别行政区", "center": "113.543028,22.186835" }
35 | ]
36 |
--------------------------------------------------------------------------------
/src/lib/colors.ts:
--------------------------------------------------------------------------------
1 | const colors = {
2 | green: "#4CD964",
3 | syan: "#5AC8FA",
4 | blue: "#007AFF",
5 | purple: "#5856D6",
6 | magenta: "#FF2D70",
7 | red: "#FF3B30",
8 | orange: "#FF9500",
9 | yellow: "#FFCC00",
10 | gray: "#8E8E93",
11 | }
12 |
13 | const ColorsProvince = {
14 | '370600': {color: 'rgb(190,194,161)', name: "烟台市"},
15 | '371300': {color: 'rgb(237,236,208)', name: "临沂市"},
16 | '370200': {color: 'rgb(222,148,111)', name: "青岛市"},
17 | '370300': {color: 'rgb(103,104,099)', name: "淄博市"},
18 | '370700': {color: 'rgb(201,166,085)', name: "潍坊市"},
19 | '371100': {color: 'rgb(113,182,153)', name: "日照市"},
20 | '371500': {color: 'rgb(212,227,206)', name: "聊城市"},
21 | '370400': {color: 'rgb(208,198,170)', name: "枣庄市"},
22 | '370100': {color: 'rgb(205,087,085)', name: "济南市"},
23 | '371700': {color: 'rgb(214,201,193)', name: "菏泽市"},
24 | '370900': {color: 'rgb(212,144,167)', name: "泰安市"},
25 | '371400': {color: 'rgb(242,242,218)', name: "德州市"},
26 | '370800': {color: 'rgb(246,194,180)', name: "济宁市"},
27 | '371600': {color: 'rgb(200,239,186)', name: "滨州市"},
28 | '370500': {color: 'rgb(178,215,221)', name: "东营市"},
29 | '371000': {color: 'rgb(146,128,140)', name: "威海市"},
30 | }
31 |
32 | const colorArray = [
33 | "#4CD964",
34 | "#5AC8FA",
35 | "#007AFF",
36 | "#5856D6",
37 | "#FF2D70",
38 | "#FF3B30",
39 | "#FF9500",
40 | "#FFCC00",
41 | "#8E8E93",
42 | ]
43 |
44 | const colorsRGB = [
45 | 'rgb(190,194,161)',
46 | 'rgb(237,236,208)',
47 | 'rgb(222,148,111)',
48 | 'rgb(103,104,099)',
49 | 'rgb(201,166,085)',
50 | 'rgb(113,182,153)',
51 | 'rgb(212,227,206)',
52 | 'rgb(208,198,170)',
53 | 'rgb(205,087,085)',
54 | 'rgb(214,201,193)',
55 | 'rgb(212,144,167)',
56 | 'rgb(242,242,218)',
57 | 'rgb(246,194,180)',
58 | 'rgb(200,239,186)',
59 | 'rgb(178,215,221)',
60 | 'rgb(146,128,140)',
61 | ]
62 | export {
63 | colors, colorsRGB, colorArray, ColorsProvince
64 | }
65 |
--------------------------------------------------------------------------------
/src/page/pointer/components/PointerDetailPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | 编辑
16 |
17 |
18 |
19 |
20 |
21 |
22 |
64 |
65 |
68 |
--------------------------------------------------------------------------------
/src/layout/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
43 |
44 |
75 |
--------------------------------------------------------------------------------
/src/layout/Copyright.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
- 用户:
4 | - {{ store.authorization.nickname }}
5 | 退出
6 |
7 | -
8 | 登录
9 |
10 |
11 |
- 开源:
- github
12 |
- 更新:
- {{ packageInfo['date-update'] }}
13 |
- 版本:
- v{{ packageInfo['version'] }}
14 |
- API:
- 高德地图 v2.0.0
15 |
16 |
17 |
18 |
45 |
46 |
80 |
--------------------------------------------------------------------------------
/src/scss/_utility.scss:
--------------------------------------------------------------------------------
1 | // box-shadow
2 | @mixin box-shadow($value...){
3 | -webkit-box-shadow: $value;
4 | -moz-box-shadow: $value;
5 | box-shadow: $value;
6 | }
7 |
8 | // border-radius
9 | @mixin border-radius($corner...){
10 | -webkit-border-radius: $corner;
11 | -moz-border-radius: $corner;
12 | border-radius: $corner;
13 | }
14 |
15 | @mixin clearfix(){
16 | &:after{
17 | content: '';
18 | display: block;
19 | clear: both;
20 | visibility: hidden;
21 | }
22 | }
23 |
24 | @mixin transform($value){
25 | -webkit-transform: $value;
26 | -moz-transform: $value;
27 | -ms-transform: $value;
28 | -o-transform: $value;
29 | transform: $value;
30 | }
31 |
32 | @mixin transition($value...){
33 | -webkit-transition: $value;
34 | -moz-transition: $value;
35 | -ms-transition: $value;
36 | -o-transition: $value;
37 | transition: $value;
38 | }
39 |
40 | @mixin animation($value){
41 | animation: $value;
42 | -webkit-animation: $value;
43 | }
44 |
45 | @mixin linear-gradient($direct, $colors){
46 | background: linear-gradient($direct, $colors);
47 | background: -webkit-linear-gradient($direct, $colors);
48 | background: -moz-linear-gradient($direct, $colors);
49 | }
50 |
51 | @mixin backdrop-filter($value){
52 | backdrop-filter: $value ;
53 | -webkit-backdrop-filter: $value;
54 | }
55 |
56 | // 1像素
57 | @mixin divider-1px(){
58 | content:'';
59 | height: 1px;
60 | display: block;
61 | width: 100%;
62 | position: absolute;
63 | background-color: $border-normal;
64 | @include transform(scaleY(.5))
65 | }
66 |
67 | /*
68 | Extension
69 | */
70 |
71 | .unselectable {
72 | -webkit-user-select: none;
73 | -moz-user-select: none;
74 | -ms-user-select: none;
75 | user-select: none;
76 | }
77 |
78 | .btn-like{
79 | cursor: pointer;
80 | @extend .unselectable;
81 | &:active{
82 | @include transform(translateY(2px))
83 | }
84 | }
85 |
86 | .card{
87 | padding: 6px 8px;
88 | overflow: hidden;
89 | background-color: transparentize(white, 0.2);
90 | backdrop-filter: blur(5px) saturate(130%);
91 | @include border-radius(8px);
92 | @include box-shadow(1px 1px 3px rgba(0,0,0,0.2));
93 | }
94 |
95 | .text-center{text-align: center;}
96 | .text-left{text-align: left;}
97 | .text-right{text-align: right;}
98 |
--------------------------------------------------------------------------------
/src/layout/Aside.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
47 |
48 |
80 |
--------------------------------------------------------------------------------
/src/api/request.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import {getAuthorization} from "@/utility";
3 | import {ServerResponse} from "@/api/ServerResponse.ts";
4 | import {ElLoading, ElMessage} from "element-plus";
5 |
6 |
7 | const LOADING_OPTION = {
8 | lock: true,
9 | text: "载入中,请稍候...",
10 | background: "rgba(0, 0, 0, 0.3)"
11 | }
12 |
13 | const BASE_URL: string = process.env.NODE_ENV === 'development' ? '/dev/' : 'http://kylebing.cn/portal/' // 生产环境时是 ../portal
14 |
15 | function request(
16 | method: 'get' | 'post' | 'put' | 'delete',
17 | params: any,
18 | requestData: any,
19 | showLoading = false,
20 | url: string
21 | ): Promise {
22 | let layerLoading = null
23 | if (showLoading) layerLoading = ElLoading.service(LOADING_OPTION)
24 |
25 | let headers = {}
26 | /*
27 | * 所有 requestData 都会自动添加 authorization 信息
28 | * 给 requestData 添加 authorization 内部的数据: username email uid 等等
29 | * */
30 | if (url !== 'user/Login' && url !== 'user/Register') { // 注册和登录时不添加 Token 数据
31 | Object.assign(headers, {
32 | 'Diary-Token': getAuthorization() && getAuthorization().token,
33 | 'Diary-Uid': getAuthorization() && getAuthorization().uid
34 | })
35 | }
36 |
37 | return new Promise((resolve, reject) => {
38 | axios({
39 | url: BASE_URL + url,
40 | method,
41 | data: requestData,
42 | params,
43 | headers,
44 | withCredentials: true
45 | })
46 | .then(res => {
47 | if (showLoading && layerLoading) layerLoading.close()
48 | if (res.status === 200) {
49 | if (res.data.success) {
50 | resolve(res.data)
51 | } else {
52 | ElMessage.error({
53 | message: res.data.message || 'Error'
54 | })
55 | reject(res.data)
56 | }
57 | } else {
58 | reject(res.data)
59 | console.log('request err: ', res.data) // 如果演示模式,不用显示网络请求错误
60 | }
61 | })
62 | .catch(err => {
63 | if (showLoading && layerLoading) layerLoading.close()
64 | ElMessage.error({
65 | message: err.message
66 | })
67 | console.log(err, err.message)
68 | reject(err)
69 | })
70 | })
71 | }
72 |
73 |
74 | export {
75 | request
76 | }
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 路书
2 | 骑行路线规划应用
3 |
4 | 一个基于 [高德地图API](https://lbs.amap.com/api/javascript-api-v2/documentation) 制作的地图 web 应用,可以分享路线、查看 gpx 路径、地图信息点分享、各市区县布局等。
5 |
6 | > **注意**
7 | 高德地图api只用作开发测试用,并不能用于实际使用,如果持续使用,高德官方会打电话给你,督促你购买商业使用授权,每年5w,不缴纳就会过段时间给你把服务停了。
8 | 所以在未获取到授权之前,不要用在商业项目上,不然挺麻烦的。
9 | > 如果你想找一个免费的地图使用,可以看一下 [OpenStreetMap](https://www.openstreetmap.org/) OSM
10 | 是一个可以免费使用的地图,地图信息主要靠所有网友自行添加维护。
11 | > 它的用法也非常简单,我准备下一步的地图应用在这个基础上做,可以看一下例子: https://github.com/KyleBing/map-OSM
12 | 在使用它的时候,需要遵循一定的使用要求。
13 |
14 |
15 |
16 | 路线分享
17 |
18 |
19 | gpx 路径 展示
20 |
21 |
22 | gpx 3D 路径展示
23 | 
24 |
25 | 数据点展示
26 |
27 |
28 | 范围标记
29 |
30 |
31 | 市内区县展示
32 |
33 |
34 | ---
35 |
36 | # 开发
37 |
38 | ## 一、获取 高德地图 API key
39 | **注意**
40 | 高德地图api只用作开发测试用,并不能用于实际使用,如果持续使用,高德官方会打电话给你,督促你购买商业使用授权,每年5w
41 |
42 | ## 二、使用
43 |
44 | 修改 `/src/mapConfig.js` 中的高德地图开发 key,获取地址: [https://console.amap.com/dev/key/app](https://console.amap.com/dev/key/app)
45 |
46 | 有两个,别选错了
47 |
48 |
49 |
50 |
51 |
52 | ```js
53 | const key_web_js = '' // web js key
54 | const key_service = '' // web服务 key
55 |
56 | export {
57 | key_web_js,
58 | key_service
59 | }
60 | ```
61 |
62 | Web JS 在使用的时候还需要在服务器中设置对应的安全密钥,具体参见官方文档:
63 | > [JS API 安全密钥使用 https://lbs.amap.com/api/javascript-api-v2/guide/abc/jscode](https://lbs.amap.com/api/javascript-api-v2/guide/abc/jscode)
64 |
65 | ## 三、后台程序
66 |
67 | 后台:[https://github.com/KyleBing/portal](https://github.com/KyleBing/portal)
68 |
69 |
70 | ## 四、用到的技术
71 | - `vue3` + `pinia` + `router`
72 | - `ts`
73 | - 高德 API 2.0
74 |
75 | ## 五、历程
76 | - 2021-06-28 init `vue2`
77 | - 2024-07-26 `vite` + `ts` + `vue3`
78 |
79 |
80 | ## 六、todo
81 | - [x] 点过多时,列表滚动 `2025-04-18`
82 |
--------------------------------------------------------------------------------
/src/page/route/components/RouteDetailPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | 编辑
16 |
17 |
18 |
19 |
20 |
21 |
88 |
89 |
92 |
--------------------------------------------------------------------------------
/src/page/SearchPanel.vue:
--------------------------------------------------------------------------------
1 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 搜索
75 | 折叠搜索结果
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
96 |
97 |
--------------------------------------------------------------------------------
/src/page/pointer/components/PointerListPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
点图列表
4 |
5 |
14 |
{{pointer.id}}
15 |
{{pointer.name}}
16 |
{{pointer.area}}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
40 |
41 |
109 |
--------------------------------------------------------------------------------
/src/scss/_markdown.scss:
--------------------------------------------------------------------------------
1 | $gap: 15px;
2 |
3 | .markdown{
4 | line-height: 1.6;
5 | color: $text-main;
6 | font-size: 12px;
7 |
8 | hr{
9 | height: 1px;
10 | background-color: $border-normal;
11 | width: 100%;
12 | margin: 20px 0;
13 | }
14 |
15 | p{
16 | line-height: 1.5;
17 | margin-bottom: 5px;
18 | }
19 |
20 | img{
21 | border: 1px solid $border-normal;
22 | max-width: 100%;
23 | background-color: white;
24 | @include border-radius(3px);
25 | }
26 |
27 | @for $item from 1 through 7 {
28 | h#{$item}{
29 | margin-top: (7-$item)*5px;
30 | margin-bottom: (7-$item)*2px;
31 | font-size: 12px + (7-$item)*1.5;
32 | }
33 | }
34 |
35 | pre{
36 | margin-bottom: $gap;
37 | font-size: $fz-small;
38 | padding: $gap $gap + 5;
39 | background-color: $bg-active;
40 | code{
41 | padding: 0;
42 | background-color: $bg-active;
43 | }
44 | }
45 |
46 | code{
47 | padding: 2px 5px;
48 | font-size: 13.5px;
49 | color: #333;
50 | background-color: $bg-active;
51 | @include border-radius(2px);
52 | }
53 |
54 | table{
55 | margin-bottom: $gap;
56 | border: 1px solid $border-normal;
57 | tr{
58 | th{
59 | text-align: left;
60 | min-width: 50px;
61 | padding: 4px 10px;
62 | border: 1px solid $border-normal;
63 | background-color: $bg-active;
64 | &[align=left]{
65 | text-align: left;
66 | }
67 | &[align=right]{
68 | text-align: right;
69 | }
70 | &[align=center]{
71 | text-align: center;
72 | }
73 | }
74 | td{
75 | text-align: left;
76 | border: 1px solid $border-normal;
77 | padding: 3px 10px;
78 | &[align=left]{
79 | text-align: left;
80 | }
81 | &[align=right]{
82 | text-align: right;
83 | }
84 | &[align=center]{
85 | text-align: center;
86 | }
87 | }
88 | }
89 | }
90 |
91 | input[type=checkbox]{
92 | display: inline-block;
93 | -webkit-appearance: checkbox !important;
94 | }
95 |
96 | ol, ul{
97 | margin-left: $gap;
98 | margin-bottom: $gap;
99 | li{
100 | margin-left: 20px;
101 | line-height: 1.5;
102 | margin-bottom: 2px;
103 | }
104 | }
105 | blockquote{
106 | margin-top: 15px;
107 | margin-bottom: 15px;
108 | padding-top: 10px;
109 | padding-bottom: 10px;
110 | border-left: 3px solid transparentize(black, 0.8);
111 | padding-left: 18px;
112 | background-color: #f5f5f5;
113 | > *:last-child{
114 | margin-bottom: 0;
115 | }
116 | }
117 |
118 | a{
119 | color: $orange;
120 | text-decoration: underline;
121 | }
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/src/page/MyMapLib.ts:
--------------------------------------------------------------------------------
1 | import {thumbnail1000_suffix, thumbnail1500_suffix} from "../mapConfig";
2 |
3 | /**
4 | * 生成 Marker.note
5 | * @param note 注释
6 | * @param img 图片链接
7 | * @param type 类别:颜色
8 | * @param name 标题
9 | * @param index 序号
10 | */
11 | function generateMarkerContent(
12 | name: string,
13 | note?: string,
14 | img?: string,
15 | type?: string,
16 | index?: number
17 | ) {
18 | if (img && note){
19 | return `
20 |
21 |
22 |
${index! + 1}
23 |
${name}
24 |
25 |
26 |
${note.replace(/\n/g, '
')}
27 |
32 |
33 |
`
34 | } else if (img) {
35 | return `
36 |
37 |
38 |
${index! + 1}
39 |
${name}
40 |
41 |
48 |
`
49 | } else if (note){
50 | return `
51 |
52 |
53 |
${index! + 1}
54 |
${name}
55 |
56 |
57 |
${note.replace(/\n/g, '
')}
58 |
59 |
`
60 | } else {
61 | return `
62 |
63 |
64 |
${index! + 1}
65 |
${name}
66 |
67 |
`
68 | }
69 | }
70 |
71 |
72 | /**
73 | * 获取区域对角线的两点坐标,即这个区域内的最小坐标值和最大坐标值
74 | *
75 | * @return Array {min:number[a,b], max:number[c,d]}
76 | * @param pointerArray
77 | */
78 | function getMaxBoundsPointer(pointerArray: Array<[number, number]>): {min: [number,number], max: [number,number]} {
79 | let lngArray = pointerArray.map(item => item[0])
80 | let latArray = pointerArray.map(item => item[1])
81 |
82 | return {
83 | min: [Math.min(...lngArray), Math.min(...latArray)],
84 | max: [Math.max(...lngArray), Math.max(...latArray)],
85 | }
86 | }
87 |
88 |
89 | export {
90 | generateMarkerContent,
91 | getMaxBoundsPointer
92 | }
93 |
--------------------------------------------------------------------------------
/src/page/info/highwayXueye/HighwayXueye.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
111 |
112 |
118 |
--------------------------------------------------------------------------------
/src/scss/main.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:math';
2 |
3 | @import "reset";
4 | @import "plugin";
5 | @import "gutter";
6 | @import "animate";
7 | @import "markdown";
8 | @import "marker"; // map marker
9 |
10 | body{
11 | background-color: #f2f2f2;
12 | }
13 |
14 | .header{
15 | line-height: 60px;
16 | }
17 |
18 | body{
19 | position: relative;
20 | }
21 |
22 | .home{
23 | @include box-shadow(2px 2px 5px rgba(121, 85, 72, 0.39));
24 | padding: 5px 8px;
25 | @include border-radius(5px);
26 | position: fixed;
27 | top: 20px;
28 | right: 20px;
29 | font-size: 0.8rem;
30 | background-color: white;
31 | cursor: pointer;
32 | }
33 |
34 | #panel{
35 | position: absolute;
36 | top: 60px;
37 | right: 60px;
38 | bottom: 60px;
39 | }
40 |
41 | .textarea{
42 | padding: 2px 3px;
43 | min-height: 30px;
44 | border: 1px solid transparentize(black, 0.8);
45 | @include border-radius(3px);
46 | &:hover{
47 | outline: 2px solid transparentize($color-main, 0.5);
48 | //background-color: transparentize($color-main, 0.9);
49 | background-color: white;
50 | cursor: text;
51 | border-color: $color-main;
52 | }
53 | }
54 |
55 |
56 | .menu{
57 | overflow: hidden;
58 | background-color: white;
59 | top: 30px;
60 | width: 150px;
61 | @include border-radius(8px);
62 | }
63 | .menu-item{
64 | @extend .unselectable;
65 | cursor: pointer;
66 | border-bottom: 1px solid $border-normal;
67 | padding: 5px 10px 5px 30px;
68 | &:last-child{
69 | border: none;
70 | }
71 | &:hover{
72 | background-color: rgba(0,0,0,0.05);
73 | }
74 | &:active{
75 | transform: translateY(2px);
76 | }
77 | }
78 |
79 |
80 | .button-center{
81 | display: flex;
82 | justify-content: center;
83 | align-items: center;
84 | }
85 |
86 |
87 | .btn-narrow{
88 | padding: 7px;
89 | }
90 |
91 | .input-focus{
92 | input{
93 | &:focus{
94 | animation-name: focus;
95 | }
96 | }
97 | }
98 |
99 | @-webkit-keyframes focus {
100 | from {outline-width: 5px;}
101 | to {outline-width: 0;}
102 | }
103 | @keyframes focus {
104 | from {outline-width: 5px;}
105 | to {outline-width: 0;}
106 | }
107 |
108 |
109 |
110 | .table-date{
111 | font-family: "JetBrains Mono", sans-serif;
112 | line-height: 1;
113 | .create-date{}
114 | .modify-date{
115 | color: $orange;
116 | }
117 | }
118 |
119 |
120 | // 操作面板中 table 内部的一些操作按钮
121 | $height-btn: 28px;
122 | .lnglat{
123 | cursor: pointer;
124 | flex-shrink: 0;
125 | .lng, .lat{
126 | white-space: nowrap;
127 | font-size: 10px;
128 | height: math.div(( $height-btn - 2 ), 2);
129 | line-height: math.div(( $height-btn - 2 ), 2);
130 | }
131 | &:active{
132 | transform: translateY(1px);
133 | color: $color-main;
134 | }
135 | }
136 | .operation{
137 | display: flex;
138 | }
139 | .move{
140 | display: flex;
141 | flex-flow: column nowrap;
142 | flex-shrink: 0;
143 | > *{
144 | margin: 0 auto;
145 | cursor: pointer;
146 | text-align: center;
147 | font-size: 0.5rem;
148 | display: block;
149 | height: math.div(($height-btn - 2), 2);
150 | width: math.div(($height-btn - 2), 2) + 6;
151 | line-height: math.div(($height-btn - 2), 2);
152 | background-color: $border-normal;
153 |
154 | &:hover{
155 | color: white;
156 | background: $color-main;
157 | }
158 | &:active{
159 | transform: translateY(2px);
160 | }
161 | }
162 | .up{
163 | @include border-radius(3px 3px 0 0);
164 | }
165 | .down{
166 | @include border-radius(0 0 3px 3px);
167 | }
168 | }
169 | .delete{
170 | display: flex;
171 | justify-content: center;
172 | align-items: center;
173 | i {
174 | height: $height-btn;
175 | width: $height-btn;
176 | @include border-radius(3px);
177 | cursor: pointer;
178 | &:hover{
179 | color: white;
180 | background-color: $color-danger;
181 | }
182 | &:active{
183 | transform: translateY(2px);
184 | }
185 | }
186 | }
187 |
188 | .hidden{
189 | display: none;
190 | }
191 |
192 |
193 | @import "overwrite-el-ui";
194 | @import "overwrite-amap";
195 |
--------------------------------------------------------------------------------
/src/page/info/carPlate/CarPlate.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
132 |
133 |
141 |
--------------------------------------------------------------------------------
/src/page/other/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
77 |
78 |
170 |
--------------------------------------------------------------------------------
/src/scss/_marker.scss:
--------------------------------------------------------------------------------
1 | .amap-marker{
2 | &:hover{
3 | z-index: 9999 !important;
4 | }
5 | &-content{
6 | white-space: normal !important;
7 | }
8 | }
9 |
10 | .marker{
11 | @extend .unselectable;
12 | background-color: $bg-light;
13 | @include border-radius(10px);
14 | line-height: 1.3;
15 | color: $text-main;
16 | @include transition(all 0.3s);
17 | @include box-shadow(2px 2px 5px rgba(121, 85, 72, 0.39));
18 | display: flex;
19 | align-items: stretch;
20 | justify-content: flex-start;
21 | flex-flow: column nowrap;
22 | &.no-content{ // 没有内容的用,没有 padding
23 | @include border-radius(10px);
24 | overflow: hidden;
25 | .marker-index{
26 | border-bottom: none;
27 | }
28 | .marker-content{
29 | padding: 0;
30 | }
31 | &:hover{
32 | overflow: hidden;
33 | }
34 | }
35 | &:hover{
36 | z-index: 99999;
37 | @include border-radius(10px);
38 | @include transition(all 0.3s);
39 | transform: scale(1.2);
40 | transform-origin: center center;
41 | .marker-index{
42 | .index{
43 | color: $color-main;
44 | text-shadow: 1px 2px 1px transparentize($color-main, 0.6);
45 | }
46 | }
47 | .marker-content{
48 |
49 | }
50 | }
51 | .marker-index{
52 | @include border-radius(10px 10px 0 0 );
53 | border-bottom: 1px solid $border-normal;
54 | background-color: white;
55 | padding: 5px 10px;
56 | display: flex;
57 | flex-flow: row nowrap;
58 | align-items: center;
59 | justify-content: flex-start;
60 | .index{
61 | margin-right: 10px;
62 | font-weight: bold;
63 | color: transparentize(black, 0.6);
64 | font-size: 18px;
65 | }
66 | .title{
67 | white-space: nowrap;
68 | font-weight: bold;
69 | font-size: 15px;
70 | }
71 | }
72 | .marker-content{
73 | padding: 4px 8px 10px;
74 | .note{
75 | white-space: nowrap;
76 | font-size: 0.7rem;
77 | color: $text-subtitle;
78 | }
79 | .view{
80 | @include transition(all, 0.3s);
81 | margin-top: 5px;
82 | width: 60px;
83 | @include border-radius($radius);
84 | overflow: hidden;
85 | img{
86 | display: block;
87 | width: 100%;
88 | }
89 | &:hover{
90 | @include box-shadow(3px 3px 5px transparentize(black, 0.7));
91 | transform: scale(5);
92 | transform-origin: center center;
93 | margin-top: 10px;
94 | @include transition(all, 0.3s);
95 | }
96 | }
97 | }
98 |
99 | $marker-types: (
100 | "blue": $blue,
101 | "green": $green,
102 | "red": $red,
103 | "orange": $orange,
104 | "yellow": $yellow,
105 | );
106 |
107 |
108 | @each $name, $color in $marker-types {
109 | &.#{$name} {
110 | .marker-index{
111 | color: white;
112 | background-color: $color;
113 | border-bottom: 1px solid $color;
114 | text-shadow: 1px 1px 1px transparentize(black, 0.9);
115 | .index{
116 | margin-right: 10px;
117 | font-weight: bold;
118 | color: transparentize(white, 0.2);
119 | font-size: 18px;
120 | }
121 | }
122 | .marker-content{
123 | @include border-radius(0 0 10px 10px);
124 | background-color: lighten($color, 45%);
125 | .note{
126 | color: $text-main;
127 | }
128 | }
129 | &:hover{
130 | .marker-index {
131 | .index{
132 | }
133 | }
134 | }
135 | }
136 | }
137 | }
138 |
139 |
140 | .marker-plate{
141 | background-color: white;
142 | @include border-radius(4px);
143 | padding: 6px;
144 | line-height: 1.3;
145 | color: $text-main;
146 | @include box-shadow(2px 2px 5px rgba(121, 85, 72, 0.39));
147 | .title{
148 | font-family: "Times";
149 | text-align: center;
150 | white-space: nowrap;
151 | font-weight: bold;
152 | font-size: 1.2rem;
153 | }
154 | .note{
155 | white-space: nowrap;
156 | font-size: 0.8rem;
157 | color: $text-subtitle;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/utility.ts:
--------------------------------------------------------------------------------
1 | const AUTHORIZATION_NAME = 'Authorization' // 存储用户信息的 localStorage name,跟 Diary 通用
2 |
3 | interface EntityAuthorization {
4 | nickname: string,
5 | uid: number,
6 | email: string,
7 | phone: string,
8 | avatar: string,
9 | token: string,
10 | group_id: number,
11 | city: string,
12 | geolocation: string
13 | }
14 |
15 | // 设置 authorization
16 | function setAuthorization(nickname: string, uid: number, email: string, phone: string, avatar: string, token: string, group_id: number, city: string, geolocation: string) {
17 | localStorage.setItem(AUTHORIZATION_NAME, JSON.stringify({
18 | nickname, uid, email, phone, avatar, token, group_id, city, geolocation
19 | }))
20 | }
21 |
22 | // 获取 authorization
23 | function getAuthorization(): EntityAuthorization {
24 | return JSON.parse(localStorage.getItem(AUTHORIZATION_NAME))
25 | }
26 |
27 | // 删除 authorization
28 | function deleteAuthorization() {
29 | localStorage.removeItem(AUTHORIZATION_NAME)
30 | }
31 |
32 |
33 | function downloadBase64File(fileName: string, data: string) { // 下载 base64 图片
34 | let aLink = document.createElement('a')
35 | let blob = new Blob([data], { type: 'application/octet-stream' }); // Set MIME type
36 | let evt = document.createEvent("HTMLEvents")
37 | evt.initEvent("click", true, true); //initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
38 | aLink.download = fileName
39 | aLink.href = URL.createObjectURL(blob)
40 | aLink.click()
41 | }
42 |
43 |
44 | // CONST
45 | enum EnumWeekDay {
46 | '周日' = 0,
47 | '周一',
48 | '周二',
49 | '周三',
50 | '周四',
51 | '周五',
52 | '周六',
53 | }
54 |
55 | enum EnumWeekDayShort {
56 | '日' = 0,
57 | '一',
58 | '二',
59 | '三',
60 | '四',
61 | '五',
62 | '六',
63 | }
64 |
65 | // 格式化时间,输出字符串
66 | function dateFormatter(date: Date, formatString: string = 'yyyy-MM-dd hh:mm:ss') {
67 | let dateRegArray: Object = {
68 | "M+": date.getMonth() + 1, // 月份
69 | "d+": date.getDate(), // 日
70 | "h+": date.getHours(), // 小时
71 | "m+": date.getMinutes(), // 分
72 | "s+": date.getSeconds(), // 秒
73 | "q+": Math.floor((date.getMonth() + 3) / 3), // 季度
74 | "S": date.getMilliseconds() // 毫秒
75 | }
76 | if (/(y+)/.test(formatString)) {
77 | formatString = formatString.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
78 | }
79 | for (let section in dateRegArray) {
80 | if (new RegExp("(" + section + ")").test(formatString)) {
81 | formatString = formatString.replace(RegExp.$1, (RegExp.$1.length === 1) ? (dateRegArray[section]) : (("00" + dateRegArray[section]).substr(("" + dateRegArray[section]).length)))
82 | }
83 | }
84 | return formatString
85 | }
86 |
87 | interface DateUtilityObject {
88 | year: number,
89 | day: number,
90 | month: number,
91 | weekday: string,
92 | weekShort: string,
93 | dateShort: string,
94 | date: string,
95 | dateFull: string,
96 | dateFullSlash: string,
97 | timeLabel: string,
98 | time: string
99 | }
100 |
101 | function dateProcess(dateString: string): DateUtilityObject {
102 | let date = new Date(dateString)
103 | let year = date.getFullYear()
104 | let month = date.getMonth() + 1
105 | let day = date.getDate()
106 | let hour = date.getHours()
107 | let minutes = date.getMinutes()
108 | // let seconds = date.getSeconds()
109 | let week = date.getDay()
110 | let timeLabel = ''
111 | if (hour >= 23 && hour < 24 || hour <= 3 && hour >= 0) {
112 | timeLabel = '深夜'
113 | } else if (hour >= 19 && hour < 23) {
114 | timeLabel = '晚上'
115 | } else if (hour >= 14 && hour < 19) {
116 | timeLabel = '傍晚'
117 | } else if (hour >= 11 && hour < 14) {
118 | timeLabel = '中午'
119 | } else if (hour >= 6 && hour < 11) {
120 | timeLabel = '早上'
121 | } else if (hour >= 3 && hour < 6) {
122 | timeLabel = '凌晨'
123 | }
124 |
125 | return {
126 | year,
127 | day,
128 | month,
129 | weekday: EnumWeekDay[week],
130 | weekShort: EnumWeekDayShort[week],
131 | dateShort: `${padNumberWith0(month)}-${padNumberWith0(day)}`,
132 | date: `${padNumberWith0(month)}月${padNumberWith0(day)}日`,
133 | dateFull: `${year}年${month}月${day}日`,
134 | dateFullSlash: `${year}/${month}/${day}`,
135 | timeLabel: timeLabel,
136 | time: `${padNumberWith0(hour)}:${padNumberWith0(minutes)}`
137 | }
138 | }
139 |
140 | function padNumberWith0(num: number) {
141 | return String(num).padStart(2, '0')
142 | }
143 |
144 |
145 | export {
146 | getAuthorization,
147 | setAuthorization,
148 | dateProcess,
149 | dateFormatter,
150 | deleteAuthorization,
151 | downloadBase64File,
152 |
153 | type EntityAuthorization
154 | }
155 |
--------------------------------------------------------------------------------
/src/page/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 |
7 |
8 |
路书
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 登录
22 |
23 |
24 |
25 | 注册
26 | 演示账户
27 |
28 |
29 |
30 |
31 |
32 |
119 |
120 |
172 |
--------------------------------------------------------------------------------
/src/page/info/car/CarDepartment.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
118 |
119 |
125 |
--------------------------------------------------------------------------------
/src/layout/DetailPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 | {{props.title}}
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
{{info.title}}
{{info.value}}
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
![]()
45 |
扫一扫,打开该页面
46 |
47 |
48 |
49 |
50 |
51 |
93 |
94 |
218 |
--------------------------------------------------------------------------------
/src/page/index/LocaIndex.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
154 |
155 |
161 |
--------------------------------------------------------------------------------
/src/page/tool/circle/components/CirclePanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | | # |
7 | 经纬 |
8 | 地点 |
9 | 半径 |
10 | 操作 |
11 |
12 |
13 |
14 |
15 | |
16 |
17 |
18 | 经: {{lng || '--'}}
19 | 纬: {{lat || '--'}}
20 |
21 | |
22 | |
23 | |
24 |
25 | 添加
26 | |
27 |
28 |
29 |
30 | | {{modelValue.length - index}} |
31 |
32 |
33 | 经: {{item.center[0]}}
34 | 纬: {{item.center[1]}}
35 |
36 | |
37 | {{item.name}} |
38 | {{item.radius}} km |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
143 |
144 |
203 |
--------------------------------------------------------------------------------
/src/page/Register.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 注册
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
154 |
155 |
192 |
--------------------------------------------------------------------------------
/src/router.ts:
--------------------------------------------------------------------------------
1 | import {createRouter, createWebHashHistory, RouteRecordRaw} from "vue-router";
2 | import Layout from "./layout/Layout.vue"
3 | import Logout from "./page/Logout.vue"
4 | import Login from "./page/Login.vue"
5 | import Register from "./page/Register.vue"
6 |
7 | const FIXED_ROUTES: Array = [
8 | {
9 | name: 'Root',
10 | path: '/',
11 | meta: {title: '主页', showInMenu: true, icon: 'House'},
12 | component: Layout,
13 | redirect: '/index',
14 | children: [
15 | {name: 'Index', path: 'index', meta: {title: '主页', showInMenu: false, icon: 'el-icon-wind-power'}, component: () => import('./page/index/Index.vue')},
16 | ]
17 | },
18 | {
19 | name: 'Route',
20 | path: '/route',
21 | component: Layout,
22 | meta: {title: '路线规划', showInMenu: true, icon: 'WindPower' /* 菜单 icon 对应 Element UI 中的 ICON class 名 */},
23 | children: [
24 | {name: 'RouteLine', path: 'route-line', meta: {title: '展示', showInMenu: true, icon: 'el-icon-wind-power'}, component: () => import('./page/route/RouteLine.vue')},
25 | {name: 'RouteList', path: 'route-list', meta: {title: '列表', showInMenu: true, icon: 'el-icon-wind-power'}, component: () => import('./page/route/RouteList.vue')},
26 | {name: 'RouteEditor', path: 'route-editor', meta: {title: '新建 | 编辑', showInMenu: true, icon: 'el-icon-wind-power'}, component: () => import('./page/route/RouteEditor.vue')}
27 | ]
28 | },
29 | {
30 | name: 'Pointer',
31 | path: '/pointer',
32 | component: Layout,
33 | meta: {title: '地域信息', showInMenu: true, icon: 'LocationInformation' /* 菜单 icon 对应 Element UI 中的 ICON class 名 */},
34 | children: [
35 | {name: 'PointerViewer', path: 'pointer-viewer', meta: {title: '展示', showInMenu: true, icon: 'el-icon-wind-power'}, component: () => import('./page/pointer/PointerViewer.vue')},
36 | {name: 'PointerList', path: 'pointer-list', meta: {title: '列表', showInMenu: true, icon: 'el-icon-wind-power'}, component: () => import('./page/pointer/PointerList.vue')},
37 | {name: 'PointerEditor', path: 'pointer-editor', meta: {title: '新建 | 编辑', showInMenu: true, icon: 'el-icon-wind-power'}, component: () => import('./page/pointer/PointerEditor.vue')}
38 | ]
39 | },
40 | {
41 | name: 'GPX',
42 | path: '/gpx',
43 | component: Layout,
44 | meta: {title: 'GPX', showInMenu: true, icon: 'Promotion' /* 菜单 icon 对应 Element UI 中的 ICON class 名 */},
45 | children: [
46 | {name: 'GpxViewer', path: 'gpx-viewer', meta: {title: 'GPX 路径', showInMenu: true, icon: 'el-icon-s-promotion'}, component: () => import('./page/gpx/GpxViewer.vue')},
47 | {name: 'GpxViewer3D', path: 'gpx-viewer-3D', meta: {title: 'GPX 路径 3D', showInMenu: true, icon: 'el-icon-s-promotion'}, component: () => import('./page/gpx/GpxViewer3D.vue')},
48 | ]
49 | },
50 | {
51 | name: 'Debug',
52 | path: '/debug',
53 | component: Layout,
54 | redirect: '/debug/debug',
55 | meta: {title: '调试', showInMenu: false, icon: 'el-icon-position' /* 菜单 icon 对应 Element UI 中的 ICON class 名 */},
56 | children: [
57 | {name: 'debugIndex', path: 'debug', meta: {title: '调试', showInMenu: true, icon: 'el-icon-position' /* 菜单 icon 对应 Element UI 中的 ICON class 名 */}, component: () => import('./page/debug/debug.vue')}
58 | ]
59 | },
60 | {
61 | name: 'Tool',
62 | path: '/tool',
63 | component: Layout,
64 | redirect: '/tool/circle',
65 | meta: {title: '地图工具', showInMenu: true, icon: 'Coordinate' /* 菜单 icon 对应 Element UI 中的 ICON class 名 */},
66 | children: [
67 | {name: 'ToolCircle' , path: 'circle' , meta: {title: '范围标记' , showInMenu: true}, component: () => import('./page/tool/circle/ToolCircle.vue')},
68 | {name: 'DistrictInfo', path: 'district-info', meta: {title: '城市各区县', showInMenu: true}, component: () => import('./page/info/area/District.vue')},
69 | ]
70 | },
71 | {
72 | name: 'Info',
73 | path: '/info',
74 | component: Layout,
75 | redirect: '/info/plate',
76 | meta: {title: '济南本地信息', showInMenu: true, icon: 'CreditCard' /* 菜单 icon 对应 Element UI 中的 ICON class 名 */},
77 | children: [
78 | {name: 'MotorHighway' , path: 'motor-highway' , meta: {title: '摩托车高速' , showInMenu: true} , component: () => import('./page/info/motorHighway/MotorHighway.vue')} ,
79 | {name: 'InfoPlate' , path: 'plate' , meta: {title: '山东各市车牌' , showInMenu: true} , component: () => import('./page/info/carPlate/CarPlate.vue')} ,
80 | {name: 'InfoCarDepartment' , path: 'car-department' , meta: {title: '济南车管所' , showInMenu: true} , component: () => import('./page/info/car/CarDepartment.vue')} ,
81 | {name: 'HighwayXueye' , path: 'highway-xueye' , meta: {title: '济南籍车辆高速免费' , showInMenu: true} , component: () => import('./page/info/highwayXueye/HighwayXueye.vue')} ,
82 | ]
83 | },
84 |
85 | {
86 | name: 'Other',
87 | path: '/other',
88 | component: Layout,
89 | redirect: '/other/map-loca',
90 | meta: {title: '其它', showInMenu: true, icon: 'Grape'},
91 | children: [
92 | {name: 'MapLoca', path: 'map-loca', meta: {title: '脉冲图', showInMenu: true}, component: () => import('./page/other/map/MapLoca.vue')},
93 | ]
94 | },
95 | {
96 | name: 'AboutFramework',
97 | path: '/about',
98 | component: Layout,
99 | redirect: '/about/about',
100 | meta: {title: '关于', showInMenu: true, icon: 'ChatDotRound'},
101 | children: [
102 | {name: 'About', path: 'about', meta: {isAdmin: false, title: '关于', showInMenu: false, icon: 'el-icon-warning-outline',}, component: () => import('./page/other/About.vue')},
103 | ]
104 | },
105 | {
106 | name: 'Logout', path: '/logout',
107 | meta: {title: '退出登录', showInMenu: false, icon: 'BottomRight',},
108 | component: Logout,
109 | },
110 | {
111 | name: 'Login', path: '/login',
112 | meta: {title: '登录', showInMenu: false, icon: 'el-icon-user-solid',},
113 | component: Login,
114 |
115 | },
116 | {
117 | name: 'Register', path: '/register',
118 | meta: {title: '注册', showInMenu: false, icon: 'el-icon-user-solid',},
119 | component: Register,
120 | },
121 | // {
122 | // name: 'NoPage', path: '*',
123 | // meta: {title: '404', showInMenu: false, icon: 'el-icon-user-solid',},
124 | // component: Login,
125 | // }
126 | ]
127 |
128 |
129 | const router = createRouter({
130 | history: createWebHashHistory(),
131 | routes: FIXED_ROUTES
132 | })
133 |
134 |
135 | export {
136 | router,
137 | FIXED_ROUTES, // 需要显示在边栏的菜单
138 | }
139 |
--------------------------------------------------------------------------------
/src/layout/Navbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
52 |
53 |
54 |
119 |
120 |
227 |
--------------------------------------------------------------------------------
/src/page/info/motorHighway/MotorHighway.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
162 |
163 |
171 |
--------------------------------------------------------------------------------
/src/page/route/components/RouteLineListPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
14 |
15 |
16 |
17 |
25 |
{{line.id}}
26 |
{{line.name}}
27 |
{{line.area}}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
44 |
{{line.id}}
45 |
{{line.name}}
46 |
{{line.area}}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
152 |
153 |
234 |
--------------------------------------------------------------------------------
/src/scss/animate.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --animate-duration: 1s;
3 | --animate-delay: 1s;
4 | --animate-repeat: 1;
5 | }
6 | .animate__animated {
7 | -webkit-animation-duration: 1s;
8 | animation-duration: 1s;
9 | -webkit-animation-duration: var(--animate-duration);
10 | animation-duration: var(--animate-duration);
11 | -webkit-animation-fill-mode: both;
12 | animation-fill-mode: both;
13 | }
14 | /* Fading exits */
15 | @-webkit-keyframes fadeOut {
16 | from {
17 | opacity: 1;
18 | }
19 |
20 | to {
21 | opacity: 0;
22 | }
23 | }
24 | @keyframes fadeOut {
25 | from {
26 | opacity: 1;
27 | }
28 |
29 | to {
30 | opacity: 0;
31 | }
32 | }
33 | .animate__fadeOut {
34 | -webkit-animation-name: fadeOut;
35 | animation-name: fadeOut;
36 | }
37 | @-webkit-keyframes fadeOutDown {
38 | from {
39 | opacity: 1;
40 | }
41 |
42 | to {
43 | opacity: 0;
44 | -webkit-transform: translate3d(0, 100%, 0);
45 | transform: translate3d(0, 100%, 0);
46 | }
47 | }
48 | @keyframes fadeOutDown {
49 | from {
50 | opacity: 1;
51 | }
52 |
53 | to {
54 | opacity: 0;
55 | -webkit-transform: translate3d(0, 100%, 0);
56 | transform: translate3d(0, 100%, 0);
57 | }
58 | }
59 | .animate__fadeOutDown {
60 | -webkit-animation-name: fadeOutDown;
61 | animation-name: fadeOutDown;
62 | }
63 | @-webkit-keyframes fadeOutDownBig {
64 | from {
65 | opacity: 1;
66 | }
67 |
68 | to {
69 | opacity: 0;
70 | -webkit-transform: translate3d(0, 2000px, 0);
71 | transform: translate3d(0, 2000px, 0);
72 | }
73 | }
74 | @keyframes fadeOutDownBig {
75 | from {
76 | opacity: 1;
77 | }
78 |
79 | to {
80 | opacity: 0;
81 | -webkit-transform: translate3d(0, 2000px, 0);
82 | transform: translate3d(0, 2000px, 0);
83 | }
84 | }
85 |
86 | .animate__animated.animate__faster {
87 | -webkit-animation-duration: calc(1s / 2);
88 | animation-duration: calc(1s / 2);
89 | -webkit-animation-duration: calc(var(--animate-duration) / 2);
90 | animation-duration: calc(var(--animate-duration) / 2);
91 | }
92 | .animate__animated.animate__fast {
93 | -webkit-animation-duration: calc(1s * 0.8);
94 | animation-duration: calc(1s * 0.8);
95 | -webkit-animation-duration: calc(var(--animate-duration) * 0.8);
96 | animation-duration: calc(var(--animate-duration) * 0.8);
97 | }
98 |
99 | @-webkit-keyframes bounceInDown {
100 | from,
101 | 60%,
102 | 75%,
103 | 90%,
104 | to {
105 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
106 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
107 | }
108 |
109 | 0% {
110 | opacity: 0;
111 | -webkit-transform: translate3d(0, -3000px, 0) scaleY(3);
112 | transform: translate3d(0, -3000px, 0) scaleY(3);
113 | }
114 |
115 | 60% {
116 | opacity: 1;
117 | -webkit-transform: translate3d(0, 25px, 0) scaleY(0.9);
118 | transform: translate3d(0, 25px, 0) scaleY(0.9);
119 | }
120 |
121 | 75% {
122 | -webkit-transform: translate3d(0, -10px, 0) scaleY(0.95);
123 | transform: translate3d(0, -10px, 0) scaleY(0.95);
124 | }
125 |
126 | 90% {
127 | -webkit-transform: translate3d(0, 5px, 0) scaleY(0.985);
128 | transform: translate3d(0, 5px, 0) scaleY(0.985);
129 | }
130 |
131 | to {
132 | -webkit-transform: translate3d(0, 0, 0);
133 | transform: translate3d(0, 0, 0);
134 | }
135 | }
136 | @keyframes bounceInDown {
137 | from,
138 | 60%,
139 | 75%,
140 | 90%,
141 | to {
142 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
143 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
144 | }
145 |
146 | 0% {
147 | opacity: 0;
148 | -webkit-transform: translate3d(0, -3000px, 0) scaleY(3);
149 | transform: translate3d(0, -3000px, 0) scaleY(3);
150 | }
151 |
152 | 60% {
153 | opacity: 1;
154 | -webkit-transform: translate3d(0, 25px, 0) scaleY(0.9);
155 | transform: translate3d(0, 25px, 0) scaleY(0.9);
156 | }
157 |
158 | 75% {
159 | -webkit-transform: translate3d(0, -10px, 0) scaleY(0.95);
160 | transform: translate3d(0, -10px, 0) scaleY(0.95);
161 | }
162 |
163 | 90% {
164 | -webkit-transform: translate3d(0, 5px, 0) scaleY(0.985);
165 | transform: translate3d(0, 5px, 0) scaleY(0.985);
166 | }
167 |
168 | to {
169 | -webkit-transform: translate3d(0, 0, 0);
170 | transform: translate3d(0, 0, 0);
171 | }
172 | }
173 | .animate__bounceInDown {
174 | -webkit-animation-name: bounceInDown;
175 | animation-name: bounceInDown;
176 | }
177 |
178 |
179 | @-webkit-keyframes slideInLeft {
180 | from {
181 | -webkit-transform: translate3d(-100%, 0, 0);
182 | transform: translate3d(-100%, 0, 0);
183 | visibility: visible;
184 | }
185 |
186 | to {
187 | -webkit-transform: translate3d(0, 0, 0);
188 | transform: translate3d(0, 0, 0);
189 | }
190 | }
191 | @keyframes slideInLeft {
192 | from {
193 | -webkit-transform: translate3d(-100%, 0, 0);
194 | transform: translate3d(-100%, 0, 0);
195 | visibility: visible;
196 | }
197 |
198 | to {
199 | -webkit-transform: translate3d(0, 0, 0);
200 | transform: translate3d(0, 0, 0);
201 | }
202 | }
203 | .animate__slideInLeft {
204 | -webkit-animation-name: slideInLeft;
205 | animation-name: slideInLeft;
206 | }
207 | @-webkit-keyframes slideInRight {
208 | from {
209 | -webkit-transform: translate3d(100%, 0, 0);
210 | transform: translate3d(100%, 0, 0);
211 | visibility: visible;
212 | }
213 |
214 | to {
215 | -webkit-transform: translate3d(0, 0, 0);
216 | transform: translate3d(0, 0, 0);
217 | }
218 | }
219 | @keyframes slideInRight {
220 | from {
221 | -webkit-transform: translate3d(100%, 0, 0);
222 | transform: translate3d(100%, 0, 0);
223 | visibility: visible;
224 | }
225 |
226 | to {
227 | -webkit-transform: translate3d(0, 0, 0);
228 | transform: translate3d(0, 0, 0);
229 | }
230 | }
231 | .animate__slideInRight {
232 | -webkit-animation-name: slideInRight;
233 | animation-name: slideInRight;
234 | }
235 |
236 | @keyframes slideOutLeft {
237 | from {
238 | -webkit-transform: translate3d(0, 0, 0);
239 | transform: translate3d(0, 0, 0);
240 | }
241 |
242 | to {
243 | visibility: hidden;
244 | -webkit-transform: translate3d(-100%, 0, 0);
245 | transform: translate3d(-100%, 0, 0);
246 | }
247 | }
248 | .animate__slideOutLeft {
249 | -webkit-animation-name: slideOutLeft;
250 | animation-name: slideOutLeft;
251 | }
252 | @-webkit-keyframes slideOutRight {
253 | from {
254 | -webkit-transform: translate3d(0, 0, 0);
255 | transform: translate3d(0, 0, 0);
256 | }
257 |
258 | to {
259 | visibility: hidden;
260 | -webkit-transform: translate3d(100%, 0, 0);
261 | transform: translate3d(100%, 0, 0);
262 | }
263 | }
264 | @keyframes slideOutRight {
265 | from {
266 | -webkit-transform: translate3d(0, 0, 0);
267 | transform: translate3d(0, 0, 0);
268 | }
269 |
270 | to {
271 | visibility: hidden;
272 | -webkit-transform: translate3d(100%, 0, 0);
273 | transform: translate3d(100%, 0, 0);
274 | }
275 | }
276 | .animate__slideOutRight {
277 | -webkit-animation-name: slideOutRight;
278 | animation-name: slideOutRight;
279 | }
280 |
281 | @-webkit-keyframes bounceOutUp {
282 | 20% {
283 | -webkit-transform: translate3d(0, -10px, 0) scaleY(0.985);
284 | transform: translate3d(0, -10px, 0) scaleY(0.985);
285 | }
286 |
287 | 40%,
288 | 45% {
289 | opacity: 1;
290 | -webkit-transform: translate3d(0, 20px, 0) scaleY(0.9);
291 | transform: translate3d(0, 20px, 0) scaleY(0.9);
292 | }
293 |
294 | to {
295 | opacity: 0;
296 | -webkit-transform: translate3d(0, -2000px, 0) scaleY(3);
297 | transform: translate3d(0, -2000px, 0) scaleY(3);
298 | }
299 | }
300 | @keyframes bounceOutUp {
301 | 20% {
302 | -webkit-transform: translate3d(0, -10px, 0) scaleY(0.985);
303 | transform: translate3d(0, -10px, 0) scaleY(0.985);
304 | }
305 |
306 | 40%,
307 | 45% {
308 | opacity: 1;
309 | -webkit-transform: translate3d(0, 20px, 0) scaleY(0.9);
310 | transform: translate3d(0, 20px, 0) scaleY(0.9);
311 | }
312 |
313 | to {
314 | opacity: 0;
315 | -webkit-transform: translate3d(0, -2000px, 0) scaleY(3);
316 | transform: translate3d(0, -2000px, 0) scaleY(3);
317 | }
318 | }
319 | .animate__bounceOutUp {
320 | -webkit-animation-name: bounceOutUp;
321 | animation-name: bounceOutUp;
322 | }
--------------------------------------------------------------------------------
/src/page/tool/circle/ToolCircle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
266 |
267 |
288 |
--------------------------------------------------------------------------------
/src/page/debug/debug.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
315 |
316 |
323 |
--------------------------------------------------------------------------------
/src/page/pointer/PointerViewer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
378 |
379 |
403 |
--------------------------------------------------------------------------------
/src/page/other/map/MapLoca.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
355 |
356 |
360 |
--------------------------------------------------------------------------------