Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c30187435 | ||
|
|
9431553951 | ||
|
|
5293b509b2 | ||
|
|
f55544321b | ||
|
|
ecd1e5cd46 | ||
|
|
a22c59f220 | ||
|
|
c33ad0d807 | ||
|
|
7b6d6e52b2 | ||
|
|
336a881855 | ||
|
|
9f6deb49a0 | ||
|
|
4d9b1ac3cb | ||
|
|
e021bad791 | ||
|
|
8095b6b503 | ||
|
|
8cc8945b68 | ||
|
|
bbb2cdc04e | ||
|
|
c583f85516 | ||
|
|
849fc8778d | ||
|
|
49597a4dd2 | ||
|
|
1c908284e6 | ||
|
|
6241504095 | ||
|
|
4f9d79461c | ||
|
|
bb0536c7e4 | ||
|
|
d95a6942cb | ||
|
|
bbc5685be0 | ||
|
|
f943ee8df3 | ||
|
|
0b67be998a | ||
|
|
482d165651 | ||
|
|
822fecc3b1 | ||
|
|
f2570ef494 | ||
|
|
8c9eafa2fb | ||
|
|
ded845793a | ||
|
|
53195cc5c2 | ||
|
|
87490ae4b5 | ||
|
|
faa0fa991c | ||
|
|
5589f92e72 | ||
|
|
4db07874f5 | ||
|
|
e09f90203f | ||
|
|
593c8bfe0b | ||
|
|
61e83f6c78 | ||
|
|
4e1c4b87c4 | ||
|
|
ea062a4ba9 | ||
|
|
cb8f23e9ab | ||
|
|
9407ba979c | ||
|
|
626fb84a6b | ||
|
|
c7f571399a | ||
|
|
ef86526095 | ||
|
|
f617922cd3 | ||
|
|
f44e372210 | ||
|
|
f12c73a911 | ||
|
|
17f9478233 | ||
|
|
98ff86f89c | ||
|
|
028cc03aeb | ||
|
|
ba91d7ff13 | ||
|
|
5d2249d4c7 | ||
|
|
28c61a0731 | ||
|
|
c9f075b106 | ||
|
|
7775b14bba | ||
|
|
e4d3e3ba1c | ||
|
|
7154314e93 | ||
|
|
f024615260 | ||
|
|
d103785a45 | ||
|
|
c7abcf1f67 | ||
|
|
a97d9c64aa | ||
|
|
1714ff19f5 | ||
|
|
bd2d066314 | ||
|
|
832291570f | ||
|
|
3755349ea8 | ||
|
|
68548362eb | ||
|
|
baec4e1ff0 | ||
|
|
0a59f01616 | ||
|
|
ff78173b25 | ||
|
|
a89cc758f7 | ||
|
|
8a386eba5b | ||
|
|
b56efa9f7f | ||
|
|
269e35d19b | ||
|
|
b61ca7f567 | ||
|
|
b52d46cd76 | ||
|
|
4b8f3ae1ef | ||
|
|
cf129e6d23 | ||
|
|
cd1b085499 | ||
|
|
055e28b457 | ||
|
|
b0a16c6f0b | ||
|
|
91abcfd78d | ||
|
|
f6e5bb78da | ||
|
|
7565056a7e | ||
|
|
726bd19ad3 | ||
|
|
67a2ea75d1 | ||
|
|
b55bc1857a | ||
|
|
8a630dd1cd | ||
|
|
07f14c7703 | ||
|
|
a2da7e4be7 | ||
|
|
01f3339833 | ||
|
|
aa71fd5c7e | ||
|
|
86ed8341e2 | ||
|
|
22c5b89f90 | ||
|
|
7e2346f5fb | ||
|
|
ff493d83d9 | ||
|
|
1cbda373e0 | ||
|
|
0a6843406a | ||
|
|
8b9c3abf77 | ||
|
|
2db1e6d33c | ||
|
|
25b8acdda6 | ||
|
|
736ee91e93 | ||
|
|
fd02cf131a | ||
|
|
48a137133d | ||
|
|
5c3bf4a7ed | ||
|
|
35c5625e1b | ||
|
|
8dc44b465b | ||
|
|
58d3b5a933 |
@@ -1,7 +1,7 @@
|
||||
# 王昊地址
|
||||
#VITE_GLOB_API_URL=http://192.168.1.27
|
||||
#VITE_GLOB_API_URL=http://192.168.1.98
|
||||
#开发环境
|
||||
VITE_GLOB_API_URL=http://cqyt.dev.yg.dt.io
|
||||
VITE_GLOB_API_URL=http://api-xj.tri.dt.io
|
||||
#VITE_GLOB_API_URL=https://api.cqygjk.com
|
||||
#健康监测工具报警-websocket
|
||||
#VITE_GLOB_API_YIN_JI=ws://cqyt.dev.yg.dt.io/health-watch/websocket/watchMonitor
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
|
||||
#后台地址
|
||||
VITE_GLOB_API_URL=https://api.cqygjk.com
|
||||
#健康监测工具报警-websocket
|
||||
VITE_GLOB_API_YIN_JI=wss://api.cqygjk.com/health-watch/websocket/watchMonitor
|
||||
#应急求助-websocket
|
||||
VITE_GLOB_API_JIAN_CE=wss://api.cqygjk.com/health-emergency/websocket/emergency
|
||||
|
||||
#西安云坐席
|
||||
VITE_GLOB_API_XIAN_YUN=wss://api.cqygjk.com/health-emergency/websocket/emergency3
|
||||
#银川云坐席
|
||||
VITE_GLOB_API_YIN_CHUAN=wss://api.cqygjk.com/health-emergency/websocket/emergency4
|
||||
VITE_GLOB_API_URL=http://api-xj.tri.dt.io
|
||||
@@ -0,0 +1,32 @@
|
||||
FROM nginx
|
||||
MAINTAINER jeecgos@163.com
|
||||
VOLUME /tmp
|
||||
ENV LANG en_US.UTF-8
|
||||
|
||||
ENV VITE_GLOB_API_URL http://api-xj.tri.dt.io
|
||||
|
||||
RUN echo "server { \
|
||||
#解决Router(mode: 'history')模式下,刷新路由地址不能找到页面的问题 \
|
||||
location / { \
|
||||
root /var/www/html/; \
|
||||
index index.html index.htm; \
|
||||
if (!-e \$request_filename) { \
|
||||
rewrite ^(.*)\$ /index.html?s=\$1 last; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
access_log /var/log/nginx/access.log ; \
|
||||
} " > /etc/nginx/conf.d/default.conf \
|
||||
&& mkdir -p /var/www \
|
||||
&& mkdir -p /var/www/html
|
||||
|
||||
ADD dist/ /var/www/html/
|
||||
|
||||
# 👇 新增:复制启动脚本(用于运行时替换环境变量)
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# 使用脚本作为入口点
|
||||
ENTRYPOINT ["bash","/entrypoint.sh"]
|
||||
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 设置默认值(构建时 .env 不生效时兜底)
|
||||
: ${VITE_GLOB_API_URL:=https://api.xjygjk.com}
|
||||
|
||||
# 替换 index.html 中的占位符
|
||||
sed -i "s@__VITE_GLOB_API_URL__@$VITE_GLOB_API_URL@g" /var/www/html/index.html
|
||||
|
||||
exec nginx -g 'daemon off;'
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" type="image/svg+xml" href="/screen.svg"/>
|
||||
@@ -7,11 +7,18 @@
|
||||
<title>应急就医服务中心</title>
|
||||
<!-- 全局配置 -->
|
||||
<script>
|
||||
window._CONFIG = {};
|
||||
// // 默认值(可选)
|
||||
// window.__APP_ENV__ = {
|
||||
// VITE_GLOB_API_URL: '__VITE_GLOB_API_URL__', // 占位符,Docker 替换
|
||||
// };
|
||||
window._AMapSecurityConfig = {
|
||||
securityJsCode: "1b3b86585c863d22db1e7f7636a9e495",
|
||||
securityJsCode: "54f1d830d2bb225c173b76f5780b65eb",
|
||||
}
|
||||
</script>
|
||||
<!-- __APP_ENV__ 由 Vite 在构建时注入 -->
|
||||
<!-- 开发环境:不注入或注入空值 -->
|
||||
<!-- 生产环境:注入真实值(或占位符供 Docker 替换) -->
|
||||
<!-- __INJECT_APP_ENV__ -->
|
||||
</head>
|
||||
<style>
|
||||
html, body, #app {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
"pro": "vite --open --mode production",
|
||||
"test": "vite --open --mode test",
|
||||
"build": "vite build",
|
||||
"build:dev": "vite build --mode dev",
|
||||
"build:test": "vite build --mode test",
|
||||
@@ -18,7 +20,10 @@
|
||||
"@turf/turf": "^6.5.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"axios": "^1.6.2",
|
||||
"cnpm": "^9.4.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "5.4.3",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
@@ -30,6 +35,7 @@
|
||||
"postcss-px2rem": "^0.3.0",
|
||||
"prettier": "^3.1.0",
|
||||
"qs": "^6.11.2",
|
||||
"swiper": "^12.0.2",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue3-seamless-scroll": "^2.0.1"
|
||||
},
|
||||
@@ -40,9 +46,8 @@
|
||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||
"@typescript-eslint/parser": "^6.15.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"echarts": "^5.4.3",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-define-config": "^1.24.1",
|
||||
@@ -52,7 +57,7 @@
|
||||
"jest": "^29.7.0",
|
||||
"less": "^4.2.0",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5",
|
||||
"vite": "^5.4.20",
|
||||
"vite-plugin-resolve-externals": "^0.2.2",
|
||||
"vue-tsc": "^1.8.5"
|
||||
}
|
||||
|
||||
@@ -10,25 +10,21 @@
|
||||
import { onMounted, watch } from 'vue';
|
||||
import { getEmergencyCenterApi } from '/@/api/user.ts';
|
||||
import { useUserStore } from '/@/store/modules/user.ts';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getBaseLogin } from '/@/enum/pageEnum.ts';
|
||||
import { openTestModal } from '/@/utils/utils.ts';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
const locle = zhCN;
|
||||
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
onMounted(async () => {
|
||||
openTestModal();
|
||||
try {
|
||||
if (userStore.getToken && getBaseLogin() === '/centralScreenLogin') {
|
||||
const { result } = await getEmergencyCenterApi();
|
||||
if (result?.screenPath) {
|
||||
router.replace(result?.screenPath);
|
||||
} else {
|
||||
router.replace('centralScreen');
|
||||
if (userStore.getToken) {
|
||||
const { code, result } = await getEmergencyCenterApi();
|
||||
if (code === 200) {
|
||||
userStore.setCenterInfo(result);
|
||||
}
|
||||
userStore.setCenterInfo(result);
|
||||
}
|
||||
} catch (e) {}
|
||||
if (userStore.getUserInfo) {
|
||||
|
||||
@@ -31,10 +31,8 @@ service.interceptors.response.use(
|
||||
},
|
||||
(error) => {
|
||||
if (error.response) {
|
||||
console.log('response', error.response);
|
||||
switch (error.response.data.code) {
|
||||
case 401:
|
||||
// debugger;
|
||||
let path = getBaseLogin();
|
||||
if (!path) {
|
||||
path = toLoginByNowPath(router.currentRoute.value.path);
|
||||
@@ -64,6 +62,22 @@ export const get = (url, params = {}) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
export const Dictget = (url, params = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
service
|
||||
.get(url, { params: params })
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
reject(res.data);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err.data);
|
||||
});
|
||||
});
|
||||
};
|
||||
export const post = (url: string, params: any) =>
|
||||
new Promise((resolve, reject) => {
|
||||
service
|
||||
|
||||
@@ -2,6 +2,7 @@ import { get, post } from '/@/api/request.ts';
|
||||
|
||||
enum Api {
|
||||
getInputCode = '/sys/randomImage',
|
||||
videoApi = '/health-emergency/api/anon/screen-animation/query',
|
||||
Login = '/sys/login',
|
||||
Logout = '/sys/logout',
|
||||
GetUserInfo = '/sys/user/getUserInfo',
|
||||
@@ -9,8 +10,12 @@ enum Api {
|
||||
emergencyCenter = '/health-emergency/emergency/center/getCenterDetail',
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const getCodeInfo = (params: any) => get(Api.getInputCode + `/${params}`);
|
||||
|
||||
export const getVideo = () => get(Api.videoApi);
|
||||
|
||||
export function loginApi(params: any) {
|
||||
return post(Api.Login, params);
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 538 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="72.000000pt" height="176.000000pt" viewBox="0 0 72.000000 176.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,176.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M269 1715 c-107 -34 -194 -124 -225 -233 -43 -146 37 -312 185 -380
|
||||
45 -22 71 -27 131 -27 130 1 234 66 292 183 63 127 39 271 -62 372 -82 82
|
||||
-218 118 -321 85z m151 -190 c5 -38 7 -40 45 -43 22 -2 43 -6 47 -10 4 -4 8
|
||||
-51 9 -105 1 -53 5 -100 10 -103 5 -3 9 -14 9 -25 0 -18 -9 -19 -165 -20 -91
|
||||
-1 -171 -2 -177 -3 -23 -2 -21 41 1 47 19 5 21 13 21 95 0 49 3 117 6 151 l7
|
||||
62 91 -3 91 -3 5 -40z"/>
|
||||
<path d="M298 1536 c-24 -18 -23 -39 3 -62 22 -19 43 -13 63 19 8 12 5 21 -8
|
||||
36 -22 24 -34 26 -58 7z"/>
|
||||
<path d="M431 1366 c2 -102 18 -126 21 -32 1 36 2 76 2 88 1 12 -5 23 -12 26
|
||||
-10 3 -13 -15 -11 -82z"/>
|
||||
<path d="M267 1424 c-14 -15 6 -24 54 -24 36 0 49 4 47 13 -5 13 -89 23 -101
|
||||
11z"/>
|
||||
<path d="M272 1358 c5 -15 87 -18 91 -3 1 6 -20 11 -47 13 -36 2 -47 0 -44
|
||||
-10z"/>
|
||||
<path d="M283 1313 c-7 -2 -13 -9 -13 -14 0 -12 87 -12 94 0 11 17 -49 27 -81
|
||||
14z"/>
|
||||
<path d="M345 718 c-8 -308 -5 -393 15 -393 12 0 16 16 18 75 5 114 -9 560
|
||||
-18 560 -4 0 -11 -109 -15 -242z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,22 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 64 64">
|
||||
<defs>
|
||||
<radialGradient id="a" cx="32" cy="32" r="32" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="red" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="red"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="b" cy="32" r="20" xlink:href="#a"/>
|
||||
</defs>
|
||||
<g data-name="图层 2">
|
||||
<g data-name="图层 1">
|
||||
<circle cx="32" cy="32" r="6" style="fill:url(#a);opacity:1">
|
||||
<animate attributeName="r" from="6" to="32" begin="0s" dur="2s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" from="1" to="0" begin="0s" dur="2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle cx="32" cy="32" r="6" style="fill:url(#b);opacity:1">
|
||||
<animate attributeName="r" from="6" to="32" begin="1s" dur="2s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" from="1" to="0" begin="1s" dur="2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle cx="32" cy="32" r="6" style="fill:red"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 648 B |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 649 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 920 B |
@@ -8,7 +8,6 @@ html, body, #app {
|
||||
}
|
||||
|
||||
.type-time {
|
||||
padding-top: 3%;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
@@ -116,8 +115,9 @@ html, body, #app {
|
||||
|
||||
// 下拉框样式-start
|
||||
.ant-select {
|
||||
color: var(--antSelectFontColor) !important;
|
||||
background-color: var(--antSelectBg);
|
||||
color: #fff !important;
|
||||
background-color: #082147 !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
.ant-select-dropdown {
|
||||
@@ -127,14 +127,15 @@ html, body, #app {
|
||||
}
|
||||
|
||||
.ant-select-item {
|
||||
color: #1b7ef2 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
//框背景色
|
||||
.ant-select:not(.ant-select-customize-input) .ant-select-selector {
|
||||
color: var(--antSelectFontColor) !important;
|
||||
background-color: transparent !important;
|
||||
border: 1px solid var(--antSelectDropdownBorderColor) !important;
|
||||
border: 1px solid transparent !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
.ant-select-item-option-selected {
|
||||
@@ -214,7 +215,7 @@ html, body, #app {
|
||||
|
||||
.ant-modal-title {
|
||||
color: #576BAD !important;
|
||||
background: url('/@/assets/images/dialog-light.png') no-repeat;
|
||||
//background: url('/@/assets/images/dialog-light.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
@@ -230,8 +231,10 @@ html, body, #app {
|
||||
|
||||
.ant-modal-title {
|
||||
color: #ffffff !important;
|
||||
background: url('/@/assets/images/dialog.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
//background: url('/@/assets/images/dialog.png') no-repeat;
|
||||
//background-size: 100% 100%;
|
||||
background: #082147;
|
||||
border: 1px solid #00CCFF;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { mapIcon1, mapIcon2, mapIcon3, mapIcon4, mapIcon7 } from '/@/components/chinaMap/chinaHooks.ts';
|
||||
import { watchIcon } from '/@/components/secondaryScreen/secondMap/secondMapHooks.ts';
|
||||
import { mapIcon1, mapIcon11, mapIcon3, mapIcon4, mapIcon41, mapIcon5, mapIcon51, watchIconR } from '/@/components/chinaMap/chinaHooks.ts';
|
||||
import * as turf from '@turf/turf';
|
||||
|
||||
/**
|
||||
@@ -18,29 +17,53 @@ export function createIcon({ icon, iconSize, imageSize }) {
|
||||
|
||||
/**
|
||||
* @desc 根据类型创建Icon
|
||||
* @type 1:油田医院,2:合作医院,3:医疗点
|
||||
* @type 1:医院,2:健康小屋,4:AED
|
||||
*/
|
||||
export function typeToIcon(type: string, centerResource: boolean) {
|
||||
const iconSize = [18, 44];
|
||||
const imageSize = [18, 44];
|
||||
export function typeToIcon(type: string) {
|
||||
// const iconSize = [28, 49];
|
||||
// const imageSize = [28, 49];
|
||||
if (type == '1') {
|
||||
return createIcon({ icon: mapIcon1, iconSize, imageSize });
|
||||
}
|
||||
if (type == '2') {
|
||||
return createIcon({ icon: mapIcon4, iconSize, imageSize });
|
||||
}
|
||||
//关联医疗点
|
||||
if (type == '3' && centerResource) {
|
||||
return createIcon({ icon: mapIcon7, iconSize, imageSize });
|
||||
// return createIcon({ icon: mapIcon1, iconSize, imageSize });
|
||||
return mapIcon1;
|
||||
}
|
||||
if (type == '3') {
|
||||
return createIcon({ icon: mapIcon2, iconSize, imageSize });
|
||||
// return createIcon({ icon: mapIcon5, iconSize, imageSize });
|
||||
return mapIcon5;
|
||||
}
|
||||
if (type == '5' || type == '4') {
|
||||
if (type == '4') {
|
||||
// return createIcon({ icon: mapIcon4, iconSize, imageSize });
|
||||
return mapIcon4;
|
||||
}
|
||||
if (type == '5') {
|
||||
// return createIcon({ icon: mapIcon3, iconSize, imageSize });
|
||||
return mapIcon3;
|
||||
}
|
||||
if (type === 'user') {
|
||||
// return createIcon({ icon: watchIconR, iconSize, imageSize });
|
||||
return watchIconR;
|
||||
}
|
||||
}
|
||||
export function typeToIcon1(type: string) {
|
||||
const iconSize = [21, 52];
|
||||
const imageSize = [21, 52];
|
||||
if (type == '1') {
|
||||
const iconS = [28, 65];
|
||||
const imageS = [28, 65];
|
||||
return createIcon({ icon: mapIcon11, iconSize: iconS, imageSize: imageS });
|
||||
}
|
||||
if (type == '3') {
|
||||
return createIcon({ icon: mapIcon51, iconSize, imageSize });
|
||||
}
|
||||
if (type == '4') {
|
||||
return createIcon({ icon: mapIcon41, iconSize, imageSize });
|
||||
}
|
||||
if (type == '5') {
|
||||
return createIcon({ icon: mapIcon3, iconSize, imageSize });
|
||||
}
|
||||
if (type == '6') {
|
||||
return createIcon({ icon: watchIcon, iconSize: [23, 51], imageSize: [23, 51] });
|
||||
if (type === 'user') {
|
||||
const iconS = [36, 88];
|
||||
const imageS = [36, 88];
|
||||
return createIcon({ icon: watchIconR, iconSize: iconS, imageSize: imageS });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,14 +92,17 @@ export function createCircle(option) {
|
||||
export function getLonLat(result = []) {
|
||||
let arr = [];
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
let { lon, lat, lng } = result[i];
|
||||
if (lng) {
|
||||
lon = lng;
|
||||
let { lon, lat, lng, longitude, latitude } = result[i];
|
||||
if (longitude) {
|
||||
lon = longitude;
|
||||
}
|
||||
if (!lon || !lat) {
|
||||
if (latitude) {
|
||||
lat = latitude;
|
||||
}
|
||||
if (!longitude || !latitude) {
|
||||
continue;
|
||||
}
|
||||
let point = turf.point([lon, lat]);
|
||||
let point = turf.point([lon * 1, lat * 1]);
|
||||
arr.push(point);
|
||||
}
|
||||
return arr;
|
||||
@@ -96,3 +122,330 @@ export function getCenter(list = []) {
|
||||
let centerObj = turf.center(features);
|
||||
return centerObj?.geometry?.coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 球面距离(haversine),返回米
|
||||
* @param lng1 经度1
|
||||
* @param lat1 纬度1
|
||||
* @param lng2 经度2
|
||||
* @param lat2 纬度2
|
||||
*/
|
||||
export function haversine(lng1: number, lat1: number, lng2: number, lat2: number): number {
|
||||
const toRad = (d: number) => (d * Math.PI) / 180;
|
||||
const R = 6371008.8; // 地球平均半径,单位米
|
||||
const dLat = toRad(lat2 - lat1);
|
||||
const dLng = toRad(lng2 - lng1);
|
||||
const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2;
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
return R * c;
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 单链聚合:距离阈值(米)内的点归为同一簇;簇内不足2点返回 null
|
||||
* @param items 待聚合的点位(必须含 lon/lat 经纬度)
|
||||
* @param radiusMeters 聚合半径,单位米,默认 30
|
||||
*/
|
||||
export function clusterByRadius(items: any[] = [], radiusMeters = 30): any[][] {
|
||||
const clusters: any[][] = [];
|
||||
const visited = new Array(items.length).fill(false);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (visited[i]) continue;
|
||||
const seed = items[i];
|
||||
const seedLng = Number(seed.lon ?? seed.longitude ?? seed.lng);
|
||||
const seedLat = Number(seed.lat ?? seed.latitude);
|
||||
if (!seedLng || !seedLat) continue;
|
||||
const cluster: any[] = [seed];
|
||||
visited[i] = true;
|
||||
for (let j = i + 1; j < items.length; j++) {
|
||||
if (visited[j]) continue;
|
||||
const cur = items[j];
|
||||
const curLng = Number(cur.lon ?? cur.longitude ?? cur.lng);
|
||||
const curLat = Number(cur.lat ?? cur.latitude);
|
||||
if (!curLng || !curLat) continue;
|
||||
const d = haversine(seedLng, seedLat, curLng, curLat);
|
||||
if (d <= radiusMeters) {
|
||||
cluster.push(cur);
|
||||
visited[j] = true;
|
||||
}
|
||||
}
|
||||
// 不足 2 个点不形成聚合(保留为独立 marker)
|
||||
if (cluster.length >= 2) {
|
||||
clusters.push(cluster);
|
||||
}
|
||||
}
|
||||
return clusters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 计算簇的几何中心(取所有点的经纬度平均)
|
||||
*/
|
||||
export function getClusterCenter(cluster: any[] = []): [number, number] {
|
||||
if (!cluster.length) return [0, 0];
|
||||
let sumLng = 0;
|
||||
let sumLat = 0;
|
||||
let count = 0;
|
||||
for (const item of cluster) {
|
||||
const lng = Number(item.lon ?? item.longitude ?? item.lng);
|
||||
const lat = Number(item.lat ?? item.latitude);
|
||||
if (!lng || !lat) continue;
|
||||
sumLng += lng;
|
||||
sumLat += lat;
|
||||
count++;
|
||||
}
|
||||
if (!count) return [0, 0];
|
||||
return [sumLng / count, sumLat / count];
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 按数量返回聚合徽标的颜色
|
||||
* @param count 簇内点数
|
||||
*/
|
||||
export function getClusterColor(count: number): { bg: string; border: string } {
|
||||
if (count >= 10) return { bg: '#E74C3C', border: '#B83A2D' };
|
||||
if (count >= 5) return { bg: '#F5A623', border: '#C77F12' };
|
||||
return { bg: '#3FA9F5', border: '#2380C7' };
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 生成聚合 marker 的 HTML 内容(纯色圆形 + 数量 N + 左上角位置编号角标)
|
||||
* @param count 簇内点数(主圈中心数字)
|
||||
* @param items 簇内所有点位,用于挑最近资源并生成 title 提示
|
||||
* @param userLocation [lng, lat] 用户位置,用于挑最近资源
|
||||
* @param clusterNumber 聚合点本身的位置编号(按距离用户升序),左上角小角标显示;不传则不显示
|
||||
*/
|
||||
export function buildClusterIcon(
|
||||
count: number,
|
||||
items: any[] = [],
|
||||
userLocation?: [number, number],
|
||||
clusterNumber?: number
|
||||
): string {
|
||||
// 挑选最近的资源(仅用于 title 提示)
|
||||
let nearest: any = items[0] || {};
|
||||
if (userLocation && items.length > 1) {
|
||||
let minDist = Infinity;
|
||||
for (const it of items) {
|
||||
const lng = Number(it.lon ?? it.longitude ?? it.lng);
|
||||
const lat = Number(it.lat ?? it.latitude);
|
||||
if (!lng || !lat) continue;
|
||||
const d = haversine(userLocation[0], userLocation[1], lng, lat);
|
||||
if (d < minDist) {
|
||||
minDist = d;
|
||||
nearest = it;
|
||||
}
|
||||
}
|
||||
}
|
||||
const { bg, border } = getClusterColor(count);
|
||||
const nearestName = nearest.name || typeToLabel(nearest.type);
|
||||
const tip = `附近 ${count} 个资源,最近:${nearestName}`;
|
||||
|
||||
// 左上角位置编号角标(仅当传了 clusterNumber 才显示)
|
||||
const badgeHtml =
|
||||
clusterNumber !== undefined && clusterNumber !== null
|
||||
? `<div style="
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
min-width: 22px;
|
||||
height: 22px;
|
||||
padding: 0 6px;
|
||||
line-height: 22px;
|
||||
border-radius: 11px;
|
||||
background: #E74C3C;
|
||||
border: 2px solid #fff;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
">${clusterNumber}</div>`
|
||||
: '';
|
||||
|
||||
// 主圈:纯色圆 + 数量 N 在中心
|
||||
return `
|
||||
<div class="cluster-marker" title="${tip}" style="
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
">
|
||||
<div style="
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background: ${bg};
|
||||
border: 3px solid ${border};
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.35);
|
||||
">${count}</div>
|
||||
${badgeHtml}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 异步创建带数字角标的图标:图片加载完成后绘制到 canvas,再叠加红色数字角标
|
||||
* @param originalIconUrl 原始图标 url(来自 typeToIcon)
|
||||
* @param text 角标文字(数字 1/2/3/...)
|
||||
* @param type 类型:1 医院 / 3 健康小屋 / 4 AED / 5 救护车,影响 canvas 尺寸
|
||||
*/
|
||||
export function createNumberedMarkerIcon(originalIconUrl: string, text: string | number, type: string | number): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
// 异常兜底:url 为空直接返回空串,让调用方决定
|
||||
if (!originalIconUrl) return resolve('');
|
||||
// 兜底:不在浏览器环境(SSR / 测试),返回原 url
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
||||
return resolve(originalIconUrl);
|
||||
}
|
||||
const width = type == 1 ? 40 : 28;
|
||||
const height = type == 1 ? 69 : 49;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height + 15;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return resolve(originalIconUrl);
|
||||
const img = new Image();
|
||||
// 不设置 crossOrigin:Vite 的本地资源是 blob URL,无需 CORS;
|
||||
// 设置 crossOrigin=anonymous 在某些环境下反而触发 onerror。
|
||||
img.onload = () => {
|
||||
try {
|
||||
ctx.drawImage(img, 0, 10, width, height);
|
||||
const textX = type == 1 ? 32 : 22;
|
||||
const textY = 11;
|
||||
const textStr = String(text);
|
||||
const textWidth = ctx.measureText(textStr).width;
|
||||
const circleRadius = Math.max(textWidth / 2 + 1, 6);
|
||||
ctx.beginPath();
|
||||
ctx.arc(textX, textY, circleRadius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.fill();
|
||||
ctx.font = 'bold 10px Arial';
|
||||
ctx.textAlign = 'right';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillText(textStr, type == 1 ? 35 : 24.5, 10.8);
|
||||
resolve(canvas.toDataURL());
|
||||
} catch (e) {
|
||||
// canvas 污染等异常:降级到原 url
|
||||
resolve(originalIconUrl);
|
||||
}
|
||||
};
|
||||
img.onerror = () => resolve(originalIconUrl);
|
||||
img.src = originalIconUrl;
|
||||
// 兜底:5 秒未触发 onload/onerror 也降级
|
||||
setTimeout(() => resolve(originalIconUrl), 5000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 类型编号 → 中文标签(用于簇列表)
|
||||
* @param type 1 医院 / 3 健康小屋 / 4 AED / 5 救护车
|
||||
*/
|
||||
export function typeToLabel(type: string | number): string {
|
||||
const map: Record<string, string> = {
|
||||
'1': '医院',
|
||||
'3': '健康小屋',
|
||||
'4': 'AED',
|
||||
'5': '救护车',
|
||||
};
|
||||
return map[String(type)] || '资源';
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 生成簇列表 InfoWindow 的 HTML 内容
|
||||
* @param items 簇内点位
|
||||
* @param userLocation [lng, lat] 用户位置,用于计算直线距离并排序
|
||||
*/
|
||||
export function buildClusterInfoContent(items: any[] = [], userLocation?: [number, number]): string {
|
||||
// 计算直线距离并按距离升序
|
||||
const list = items.map((it) => {
|
||||
const lng = Number(it.lon ?? it.longitude ?? it.lng);
|
||||
const lat = Number(it.lat ?? it.latitude);
|
||||
let distance = Number(it.distance);
|
||||
if (!distance && userLocation) {
|
||||
distance = haversine(userLocation[0], userLocation[1], lng, lat);
|
||||
}
|
||||
return { ...it, _distance: distance || 0, _lng: lng, _lat: lat };
|
||||
});
|
||||
list.sort((a, b) => a._distance - b._distance);
|
||||
|
||||
const formatDist = (m: number) => (m >= 1000 ? `${(m / 1000).toFixed(2)} 公里` : `${Math.round(m)} 米`);
|
||||
|
||||
const rows = list
|
||||
.map(
|
||||
(it, idx) => `
|
||||
<div class="cluster-row" data-idx="${idx}" style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
transition: background 0.2s;
|
||||
">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: #3FA9F5;
|
||||
flex-shrink: 0;
|
||||
"></span>
|
||||
<span style="color: #fff; font-size: 14px;">${it.name || typeToLabel(it.type)}</span>
|
||||
<span style="color: rgba(255,255,255,0.6); font-size: 12px;">${typeToLabel(it.type)}</span>
|
||||
</div>
|
||||
<span style="color: rgba(255,255,255,0.85); font-size: 13px;">${formatDist(it._distance)}</span>
|
||||
</div>`
|
||||
)
|
||||
.join('');
|
||||
|
||||
return `
|
||||
<div class="cluster-info-window" style="
|
||||
position: relative;
|
||||
width: 260px;
|
||||
background: rgba(15, 32, 60, 0.95);
|
||||
border-radius: 8px;
|
||||
padding: 12px 0;
|
||||
color: #fff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.35);
|
||||
box-sizing: border-box;
|
||||
">
|
||||
<button class="cluster-info-close" type="button" style="
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.15);
|
||||
border: 0;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
">×</button>
|
||||
<div style="
|
||||
padding: 0 36px 8px 14px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.15);
|
||||
">附近 ${items.length} 个资源</div>
|
||||
<div class="cluster-rows">${rows}</div>
|
||||
<div style="
|
||||
padding: 6px 14px 0;
|
||||
font-size: 12px;
|
||||
color: rgba(255,255,255,0.5);
|
||||
">点击列表项查看详情并规划路线</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
import AMapLoader from '@amap/amap-jsapi-loader';
|
||||
import china from '/@/assets/lngLat/china.ts';
|
||||
import chinaInner from '/@/assets/lngLat/chinaInner.ts';
|
||||
import { createCircle, createIcon, getCenter, getLonLat, typeToIcon } from '/@/assets/mapUtils/commonFun.ts';
|
||||
import { aircraftContent, alarmIcon, female, male } from '/@/components/chinaMap/chinaHooks.ts';
|
||||
import {
|
||||
buildClusterIcon,
|
||||
buildClusterInfoContent,
|
||||
clusterByRadius,
|
||||
createCircle,
|
||||
createIcon,
|
||||
createNumberedMarkerIcon,
|
||||
getCenter,
|
||||
getClusterCenter,
|
||||
getLonLat,
|
||||
haversine,
|
||||
typeToIcon,
|
||||
typeToIcon1,
|
||||
} from '/@/assets/mapUtils/commonFun.ts';
|
||||
import { aircraftContent, alarmIcon, female, male, watchIconR } from '/@/components/chinaMap/chinaHooks.ts';
|
||||
import { isFunction } from '/@/utils/is.ts';
|
||||
import { Callback, ExtDataType, InitMap, MarkerOption, ResultType } from '/@/assets/mapUtils/mapFunTypes.ts';
|
||||
import { defaultOption, getZoom, mapKey, mapPlugin } from '/@/assets/mapUtils/mapConstant.ts';
|
||||
|
||||
let AMap = null;
|
||||
export const Map = {
|
||||
aMap: null,
|
||||
map: null,
|
||||
overlayGroup: null, // 地图中marker
|
||||
circleGroup: null, //直升机背景圆
|
||||
@@ -19,6 +33,16 @@ export const Map = {
|
||||
infoWindow: null, //地图弹窗
|
||||
driveLine: null, // 导航轨迹线
|
||||
emergencyMarker: null, // 应急人员marker
|
||||
startMarker: null,
|
||||
endMarker: null,
|
||||
driving: null,
|
||||
walking: null,
|
||||
mapDriverOrWalkLoading: false,
|
||||
layerGroup: null,
|
||||
layerData: null,
|
||||
clusterInfoWindow: null, // 簇列表弹窗(聚合 marker 点击后展示)
|
||||
clusterRadius: 30, // 聚合半径,单位米
|
||||
clusterUserLocation: null, // 用户位置 [lng, lat],用于列表显示距离
|
||||
/**
|
||||
* @desc 地图舒适化
|
||||
* @param el dom元素 id选择器
|
||||
@@ -34,6 +58,7 @@ export const Map = {
|
||||
plugins: mapPlugin, // 需要使用的的插件列表,如比例尺'AMap.Scale'等
|
||||
}).then((res) => {
|
||||
AMap = res;
|
||||
this.aMap = res;
|
||||
const { zoom, center, mapStyle } = { ...defaultOption, ...option };
|
||||
//基本地图加载
|
||||
this.map = new AMap.Map(el, {
|
||||
@@ -53,6 +78,18 @@ export const Map = {
|
||||
this.drawOutLine();
|
||||
}
|
||||
});
|
||||
const satellite = new AMap.TileLayer.Satellite({
|
||||
zIndex: 11,
|
||||
});
|
||||
|
||||
const roadNet = new AMap.TileLayer.RoadNet({
|
||||
zIndex: 12,
|
||||
});
|
||||
this.layerData = { satellite, roadNet };
|
||||
this.layerGroup = new AMap.LayerGroup([satellite, roadNet]);
|
||||
this.layerGroup.setMap(this.map);
|
||||
satellite.hide();
|
||||
roadNet.hide();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -125,6 +162,11 @@ export const Map = {
|
||||
}
|
||||
this.clearActiveHospital();
|
||||
this.clearInfoModal();
|
||||
// 关闭簇列表弹窗(聚合 marker 列表弹窗)
|
||||
if (this.clusterInfoWindow) {
|
||||
this.clusterInfoWindow.close();
|
||||
this.clusterInfoWindow = null;
|
||||
}
|
||||
// this.map.remove(this.airMarkerList);
|
||||
}, //
|
||||
/**
|
||||
@@ -134,17 +176,22 @@ export const Map = {
|
||||
* @param callback
|
||||
* @param computedCenter 是否计算中心点
|
||||
*/
|
||||
createOverlay(result: ResultType[], extData?: ExtDataType, callback?: Callback, computedCenter?: boolean) {
|
||||
createOverlay(result: ResultType[], extData?: ExtDataType, callback?: Callback, computedCenter?: boolean, iconType?: string) {
|
||||
this.removeOverlayGroup();
|
||||
this.overlayGroup = new AMap.OverlayGroup();
|
||||
let list = getLonLat(result);
|
||||
let center = getCenter(list);
|
||||
let zoom = getZoom(list);
|
||||
if (computedCenter) {
|
||||
this.setZoomCenter(center, zoom);
|
||||
}
|
||||
// const list: any[] = getLonLat(result);
|
||||
// const center = getCenter(list);
|
||||
// const zoom = getZoom(list);
|
||||
let arr: any[] = [];
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
let { type, lon, lat, lng, centerResource } = result[i];
|
||||
let { type, lon, lat, longitude, latitude, lng, centerResource } = result[i];
|
||||
|
||||
if (longitude) {
|
||||
lon = longitude;
|
||||
}
|
||||
if (latitude) {
|
||||
lat = latitude;
|
||||
}
|
||||
if (lng) {
|
||||
lon = lng;
|
||||
}
|
||||
@@ -152,7 +199,7 @@ export const Map = {
|
||||
continue;
|
||||
}
|
||||
// 创建一个 Icon
|
||||
let startIcon = typeToIcon(type, centerResource);
|
||||
let startIcon = typeToIcon1(type);
|
||||
// 将 icon 传入 marker
|
||||
let startMarker = new AMap.Marker({
|
||||
// 点的坐标
|
||||
@@ -167,6 +214,121 @@ export const Map = {
|
||||
},
|
||||
...extData?.extOption,
|
||||
});
|
||||
arr.push(startMarker);
|
||||
// 可以调用setExtData()设置自定义属性
|
||||
//将组 挂载到地图上
|
||||
this.overlayGroup && this.overlayGroup.setMap(this.map);
|
||||
//将需要批量控制的点放到一个组中,通过控制组,就可以操作组内所有所有覆盖物
|
||||
this.overlayGroup && this.overlayGroup.addOverlay(startMarker);
|
||||
}
|
||||
|
||||
this.overlayGroup &&
|
||||
this.overlayGroup.on('click', function (e: any) {
|
||||
//拿到点中携带的数据
|
||||
if (!isFunction(callback)) return;
|
||||
callback(e.target.getExtData());
|
||||
});
|
||||
if (computedCenter) {
|
||||
this.map && this.map.setFitView(arr, false, [50, 50, 0, 50]);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
/**
|
||||
* @desc 创建覆盖物组,用于批量处理点 (控制显隐,点击事件等)
|
||||
* @param result
|
||||
* @param extData
|
||||
* @param callback
|
||||
* @param computedCenter 是否计算中心点
|
||||
*/
|
||||
async createOverlay1(result: ResultType[], extData?: ExtDataType, callback?: Callback, computedCenter?: boolean, iconType?: string) {
|
||||
this.removeOverlayGroup();
|
||||
this.overlayGroup = new AMap.OverlayGroup();
|
||||
const list: any[] = getLonLat(result);
|
||||
const center = getCenter(list);
|
||||
const zoom = getZoom(list);
|
||||
if (computedCenter) {
|
||||
this.setZoomCenter(center, zoom);
|
||||
}
|
||||
function createTextOverlayIcon(originalIconUrl, text, type) {
|
||||
return new Promise((resolve) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = new Image();
|
||||
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.onload = function () {
|
||||
const width = type == 1 ? 40 : 28;
|
||||
const height = type == 1 ? 69 : 49;
|
||||
// 设置画布大小(比原图稍大以容纳文字)
|
||||
canvas.width = width;
|
||||
canvas.height = height + 15; // 为文字留出空间
|
||||
|
||||
// 绘制原始图标
|
||||
ctx.drawImage(img, 0, 10, width, height);
|
||||
// 绘制圆形文字背景
|
||||
const textX = type == 1 ? 32 : 22; // 文字X坐标(居右对齐)
|
||||
|
||||
const textY = 11; // 文字Y坐标
|
||||
const textWidth = ctx.measureText(text).width;
|
||||
const circleRadius = Math.max(textWidth / 2 + 1, 6); // 根据文字宽度计算半径
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(textX, textY, circleRadius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = 'red';
|
||||
ctx.fill();
|
||||
|
||||
// 绘制文字背景(可选)
|
||||
// ctx.fillStyle = 'red';
|
||||
// ctx.fillRect(0, 0, 28, 15);
|
||||
|
||||
// 绘制文字
|
||||
ctx.font = `bold 10px Arial`;
|
||||
ctx.textAlign = 'right';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillText(text, type == 1 ? 35 : 24.5, 10.8);
|
||||
|
||||
resolve(canvas.toDataURL());
|
||||
};
|
||||
|
||||
img.src = originalIconUrl;
|
||||
});
|
||||
}
|
||||
let arr = [];
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
let { type, lon, lat, longitude, latitude, lng, centerResource } = result[i];
|
||||
|
||||
if (longitude) {
|
||||
lon = longitude;
|
||||
}
|
||||
if (latitude) {
|
||||
lat = latitude;
|
||||
}
|
||||
if (lng) {
|
||||
lon = lng;
|
||||
}
|
||||
if (!lon || !lat || !type) {
|
||||
continue;
|
||||
}
|
||||
// 创建一个 Icon
|
||||
const startIcon: any = typeToIcon(type);
|
||||
const newIconUrl = await createTextOverlayIcon(startIcon, i + 1 + '', result[i]?.type);
|
||||
// 将 icon 传入 marker
|
||||
const startMarker = new AMap.Marker({
|
||||
// 点的坐标
|
||||
position: [lon, lat], // 指定点的icon,也可以是个url ,但这样就不能对图片进行设置了
|
||||
icon: newIconUrl, //启用点击事件 如果需要点击事件,必须开启
|
||||
clickable: true, //点额外携带的数据,用户自定义属性,支持JavaScript API任意数据类型
|
||||
offset: [-18, -32],
|
||||
maxZoom: 14,
|
||||
extData: result[i],
|
||||
label: {
|
||||
content: isFunction(extData?.content) && extData?.content(result[i]),
|
||||
},
|
||||
zIndex: 99,
|
||||
...extData?.extOption,
|
||||
});
|
||||
arr.push(startMarker);
|
||||
// 可以调用setExtData()设置自定义属性
|
||||
//将组 挂载到地图上
|
||||
this.overlayGroup.setMap(this.map);
|
||||
@@ -179,6 +341,228 @@ export const Map = {
|
||||
if (!isFunction(callback)) return;
|
||||
callback(e.target.getExtData());
|
||||
});
|
||||
return arr;
|
||||
},
|
||||
/**
|
||||
* @desc 创建带聚合的覆盖物组:距离阈值内的点位合并为聚合 marker(最近资源图标+数字角标),
|
||||
* 点击聚合 marker 弹出 InfoWindow 展示簇内点位列表;簇外点位独立渲染。
|
||||
* @param result 待渲染的点位(医院/医疗点/AED 等)
|
||||
* @param extData 与 createOverlay1 一致,content/extOption 用于 label 等
|
||||
* @param callback 单个点位被选中时的回调(被聚合列表选中也会调用,签名相同)
|
||||
* @param computedCenter 是否计算视野中心
|
||||
* @param iconType 同 createOverlay1(保留参数占位)
|
||||
* @param radius 聚合半径,单位米,默认 30
|
||||
* @param userLocation 用户位置 [lng, lat],用于挑选最近资源 + 簇列表显示距离
|
||||
*/
|
||||
async createClusterOverlay(
|
||||
result: ResultType[],
|
||||
extData?: ExtDataType,
|
||||
callback?: Callback,
|
||||
computedCenter?: boolean,
|
||||
iconType?: string,
|
||||
radius?: number,
|
||||
userLocation?: [number, number]
|
||||
) {
|
||||
this.removeOverlayGroup();
|
||||
this.overlayGroup = new AMap.OverlayGroup();
|
||||
// 关闭上一次的簇列表弹窗
|
||||
if (this.clusterInfoWindow) {
|
||||
this.clusterInfoWindow.close();
|
||||
this.clusterInfoWindow = null;
|
||||
}
|
||||
|
||||
const r = radius || this.clusterRadius || 30;
|
||||
if (userLocation) {
|
||||
this.clusterUserLocation = userLocation;
|
||||
}
|
||||
|
||||
// 过滤掉缺少经纬度或类型的点
|
||||
const validItems: any[] = [];
|
||||
for (const item of result || []) {
|
||||
const lon = Number(item.lon ?? item.longitude ?? item.lng);
|
||||
const lat = Number(item.lat ?? item.latitude);
|
||||
const type = item.type;
|
||||
if (!lon || !lat || !type) continue;
|
||||
validItems.push({ ...item, lon, lat });
|
||||
}
|
||||
|
||||
// 单链聚类(30m 内合并)
|
||||
const clusters = clusterByRadius(validItems, r);
|
||||
const clusteredSet = new Set();
|
||||
for (const c of clusters) {
|
||||
for (const item of c) clusteredSet.add(item);
|
||||
}
|
||||
const singles = validItems.filter((it) => !clusteredSet.has(it));
|
||||
|
||||
const allMarkers: any[] = [];
|
||||
|
||||
// 收集所有"位置点":每个聚合点(按 cluster center)和每个独立点都是一个位置点
|
||||
// 统一按距离用户位置升序排序后连续编号 1, 2, 3, ...
|
||||
// 这样聚合点和独立 marker 不会各自从 #1 开始,地图上编号全局唯一
|
||||
const positionPoints: any[] = [];
|
||||
for (const cluster of clusters) {
|
||||
const [cLng, cLat] = getClusterCenter(cluster);
|
||||
positionPoints.push({ kind: 'cluster', cluster, cLng, cLat });
|
||||
}
|
||||
for (const item of singles) {
|
||||
positionPoints.push({ kind: 'single', item });
|
||||
}
|
||||
if (userLocation) {
|
||||
positionPoints.sort((a, b) => {
|
||||
const da =
|
||||
a.kind === 'cluster'
|
||||
? haversine(userLocation[0], userLocation[1], a.cLng, a.cLat)
|
||||
: haversine(userLocation[0], userLocation[1], a.item.lon, a.item.lat);
|
||||
const db =
|
||||
b.kind === 'cluster'
|
||||
? haversine(userLocation[0], userLocation[1], b.cLng, b.cLat)
|
||||
: haversine(userLocation[0], userLocation[1], b.item.lon, b.item.lat);
|
||||
return da - db;
|
||||
});
|
||||
}
|
||||
|
||||
// 同步渲染聚合 marker(HTML,无异步依赖)
|
||||
const singleMarkerRefs: Array<{ marker: any; item: any; number: number }> = [];
|
||||
positionPoints.forEach((p, idx) => {
|
||||
const number = idx + 1;
|
||||
if (p.kind === 'cluster') {
|
||||
// 聚合 marker:纯色圆 + 数量 N + 左上角位置编号角标
|
||||
const clusterMarker: any = new AMap.Marker({
|
||||
position: [p.cLng, p.cLat],
|
||||
content: buildClusterIcon(p.cluster.length, p.cluster, userLocation, number),
|
||||
clickable: true,
|
||||
offset: new AMap.Pixel(-22, -22), // 主圈 44x44,中心对准坐标
|
||||
extData: { __isCluster: true, items: p.cluster, clusterNumber: number },
|
||||
zIndex: 110,
|
||||
});
|
||||
// 点击聚合 → 打开 InfoWindow
|
||||
clusterMarker.on('click', () => {
|
||||
this.openClusterInfoWindow(p.cluster, [p.cLng, p.cLat], callback);
|
||||
});
|
||||
this.overlayGroup.setMap(this.map);
|
||||
this.overlayGroup.addOverlay(clusterMarker);
|
||||
allMarkers.push(clusterMarker);
|
||||
} else {
|
||||
// 同步渲染独立 marker:先用 typeToIcon 的纯 url(保证渲染成功,无角标也行)
|
||||
// 后续异步优化:用 canvas 画带角标的图标,setIcon 替换
|
||||
const iconUrl: string = typeToIcon(p.item.type);
|
||||
const startMarker: any = new AMap.Marker({
|
||||
position: [p.item.lon, p.item.lat],
|
||||
icon: iconUrl,
|
||||
clickable: true,
|
||||
offset: [-18, -32],
|
||||
maxZoom: 14,
|
||||
extData: p.item,
|
||||
label: {
|
||||
content: isFunction(extData?.content) && extData?.content(p.item),
|
||||
},
|
||||
zIndex: 99,
|
||||
...extData?.extOption,
|
||||
});
|
||||
this.overlayGroup.setMap(this.map);
|
||||
this.overlayGroup.addOverlay(startMarker);
|
||||
allMarkers.push(startMarker);
|
||||
// 记录独立 marker 引用,后续异步替换为带角标图标
|
||||
singleMarkerRefs.push({ marker: startMarker, item: p.item, number });
|
||||
}
|
||||
});
|
||||
|
||||
// 异步优化:为独立 marker 叠加数字角标(canvas 失败也不影响主渲染流程)
|
||||
// 主渲染已经完成(marker 已上地图),这里只是 setIcon 替换图标
|
||||
singleMarkerRefs.forEach(({ marker, item, number }) => {
|
||||
createNumberedMarkerIcon(typeToIcon(item.type), number, item.type)
|
||||
.then((numberedUrl) => {
|
||||
if (numberedUrl && marker && typeof marker.setIcon === 'function') {
|
||||
try {
|
||||
marker.setIcon(numberedUrl);
|
||||
} catch (e) {
|
||||
// 静默:setIcon 失败不影响
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 静默:canvas 失败保留原 url 图标
|
||||
});
|
||||
});
|
||||
|
||||
// 独立 marker 点击 → 直接走原回调
|
||||
this.overlayGroup.on('click', (e: any) => {
|
||||
const ext = e?.target?.getExtData?.();
|
||||
if (!ext || ext.__isCluster) return; // 聚合已自行处理
|
||||
if (!isFunction(callback)) return;
|
||||
callback(ext);
|
||||
});
|
||||
|
||||
if (computedCenter && allMarkers.length) {
|
||||
try {
|
||||
this.map && this.map.setFitView(allMarkers, false, [50, 50, 0, 50]);
|
||||
} catch (e) {
|
||||
console.warn('[createClusterOverlay] setFitView failed:', e);
|
||||
}
|
||||
}
|
||||
return allMarkers;
|
||||
},
|
||||
/**
|
||||
* @desc 打开簇列表 InfoWindow,点击列表项触发与单点点击相同的回调
|
||||
*/
|
||||
openClusterInfoWindow(cluster: any[], position: [number, number], callback?: Callback) {
|
||||
if (!this.map || !AMap) return;
|
||||
// 关闭上一次的
|
||||
if (this.clusterInfoWindow) {
|
||||
this.clusterInfoWindow.close();
|
||||
this.clusterInfoWindow = null;
|
||||
}
|
||||
// 把 HTML 转成 DOM 元素直接传给 InfoWindow,避免 setTimeout + querySelector 的时序问题
|
||||
// 事件直接绑在元素上,AMap 插入到地图 DOM 后 listener 依然生效(事件 listener 跟元素绑定,不跟 DOM 位置绑定)
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = buildClusterInfoContent(cluster, this.clusterUserLocation);
|
||||
const root = wrapper.querySelector('.cluster-info-window') as HTMLElement | null;
|
||||
|
||||
const closeWindow = () => {
|
||||
if (this.clusterInfoWindow) {
|
||||
this.clusterInfoWindow.close();
|
||||
this.clusterInfoWindow = null;
|
||||
}
|
||||
};
|
||||
|
||||
if (root) {
|
||||
// 列表行点击
|
||||
const rows = root.querySelectorAll('.cluster-row');
|
||||
rows.forEach((row) => {
|
||||
row.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const idx = Number((row as HTMLElement).getAttribute('data-idx') || 0);
|
||||
const target = cluster[idx];
|
||||
if (!target) return;
|
||||
closeWindow();
|
||||
if (isFunction(callback)) callback(target);
|
||||
});
|
||||
row.addEventListener('mouseenter', () => {
|
||||
(row as HTMLElement).style.background = 'rgba(255,255,255,0.08)';
|
||||
});
|
||||
row.addEventListener('mouseleave', () => {
|
||||
(row as HTMLElement).style.background = 'transparent';
|
||||
});
|
||||
});
|
||||
// 关闭按钮
|
||||
const closeBtn = root.querySelector('.cluster-info-close');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
closeWindow();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.clusterInfoWindow = new AMap.InfoWindow({
|
||||
position,
|
||||
offset: new AMap.Pixel(0, -10),
|
||||
content: wrapper,
|
||||
isCustom: true,
|
||||
autoMove: true,
|
||||
closeWhenClickMap: true,
|
||||
});
|
||||
this.clusterInfoWindow.open(this.map, position);
|
||||
},
|
||||
clearOverlay() {
|
||||
if (this.overlayGroup) {
|
||||
@@ -309,13 +693,23 @@ export const Map = {
|
||||
* @desc 创建地图详情弹窗
|
||||
*
|
||||
* */
|
||||
createInfoModal({ option, content }) {
|
||||
let { lat, lng } = option;
|
||||
createInfoModal({ option, content }, flag = true) {
|
||||
let { latitude, longitude } = option;
|
||||
this.infoWindow = new AMap.InfoWindow({
|
||||
position: [lng, lat],
|
||||
offset: new AMap.Pixel(-5, -25),
|
||||
position: [longitude, latitude],
|
||||
offset: new AMap.Pixel(-8, -25),
|
||||
content: content,
|
||||
closeWhenClickMap: true,
|
||||
closeWhenClickMap: flag,
|
||||
});
|
||||
this.infoWindow.open(this.map);
|
||||
},
|
||||
createEmerInfoModal({ option, content }, flag = true) {
|
||||
let { latitude, longitude } = option;
|
||||
this.infoWindow = new AMap.InfoWindow({
|
||||
position: [longitude, latitude],
|
||||
offset: new AMap.Pixel(0, -8),
|
||||
content: content,
|
||||
closeWhenClickMap: flag,
|
||||
});
|
||||
this.infoWindow.open(this.map);
|
||||
},
|
||||
@@ -362,6 +756,32 @@ export const Map = {
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @desc 计算两个坐标点的步行时间
|
||||
* @param {Array} listA 坐标点数组
|
||||
* @param {Array} listB 坐标点数组
|
||||
* @param {Function} callback 回调
|
||||
* @return {Number} 时间
|
||||
*/
|
||||
getWalkingTime(listA: number[], listB: number[], callback?: Callback) {
|
||||
let lnglatA = new AMap.LngLat(listA[0], listA[1]);
|
||||
let lnglatB = new AMap.LngLat(listB[0], listB[1]);
|
||||
const drivingOptions = {
|
||||
policy: AMap.DrivingPolicy.LEAST_TIME,
|
||||
};
|
||||
let walking = new AMap.Walking(drivingOptions);
|
||||
return new Promise((resolve, reject) => {
|
||||
walking.search(lnglatA, lnglatB, (status, result) => {
|
||||
if (status === 'complete') {
|
||||
// console.log('result', result);
|
||||
if (callback && isFunction(callback)) {
|
||||
callback(result);
|
||||
}
|
||||
return resolve(result.routes[0]);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @desc 创建导航轨迹线路
|
||||
@@ -387,7 +807,6 @@ export const Map = {
|
||||
});
|
||||
this.driveLine.on('error', function (result) {
|
||||
let str = '定位失败:';
|
||||
console.log('error', result);
|
||||
switch (result.info) {
|
||||
case 'PERMISSION_DENIED':
|
||||
str += '浏览器阻止了定位操作';
|
||||
@@ -402,7 +821,6 @@ export const Map = {
|
||||
str += '未知错误';
|
||||
break;
|
||||
}
|
||||
console.log(str);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
@@ -417,27 +835,37 @@ export const Map = {
|
||||
/**
|
||||
* @desc 创建应急人员Marker
|
||||
*/
|
||||
createEmergencyMarker(option) {
|
||||
createEmergencyMarker(option: any, isLabel = true, callback?: Callback) {
|
||||
this.clearEmergencyMarker();
|
||||
const { sex, address, name, position } = option;
|
||||
const icon = sex == '1' ? female : male;
|
||||
this.emergencyMarker = new AMap.Marker({
|
||||
position: position,
|
||||
icon: createIcon({ icon: icon, iconSize: [36, 36], imageSize: [36, 36] }),
|
||||
offset: new AMap.Pixel(0, 0), //设置偏移量
|
||||
label: {
|
||||
content: `<div class="alarm-content" style="width: 180px" >
|
||||
icon: createIcon({ icon: watchIconR, iconSize: [80, 80], imageSize: [80, 80] }),
|
||||
offset: new AMap.Pixel(-40, -40), //设置偏移量
|
||||
...(isLabel
|
||||
? {
|
||||
label: {
|
||||
content: `<div class="alarm-content" style="width: 180px;z-index: 99" >
|
||||
<div style="text-align: center;padding-bottom: 6px">${name}</div>
|
||||
<div style="text-wrap: wrap">${address}</div>
|
||||
</div>`,
|
||||
offset: new AMap.Pixel(-100, 55),
|
||||
},
|
||||
offset: new AMap.Pixel(-120, -70),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
this.map.add(this.emergencyMarker);
|
||||
this.setZoomCenter(position);
|
||||
this.emergencyMarker.on('click', () => {
|
||||
if (callback && isFunction(callback)) {
|
||||
callback(option);
|
||||
}
|
||||
});
|
||||
// this.setZoomCenter(position);
|
||||
},
|
||||
/**
|
||||
* @desc 清除应急人员Marker
|
||||
* @desc 清除应急人员Marker..
|
||||
*
|
||||
*/
|
||||
clearEmergencyMarker() {
|
||||
if (this.emergencyMarker) {
|
||||
@@ -464,6 +892,30 @@ export const Map = {
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @desc 根据坐标获取地址信息
|
||||
* @param {Array} list 坐标点数组
|
||||
*/
|
||||
getInfoByPoint(list: number[]) {
|
||||
const geocoder = new AMap.Geocoder({
|
||||
city: '全国',
|
||||
radius: 1000,
|
||||
});
|
||||
let lnglat = new AMap.LngLat(list[0], list[1]);
|
||||
return new Promise((resolve, reject) => {
|
||||
geocoder.getAddress(lnglat, (status, result) => {
|
||||
if (status === 'complete' && result.regeocode) {
|
||||
let code = result?.regeocode?.addressComponent?.adcode;
|
||||
code = code.substring(0, 4) + '00';
|
||||
if (code.substring(0, 3) !== '650') {
|
||||
code = '650100';
|
||||
}
|
||||
return resolve(code);
|
||||
}
|
||||
return reject(result);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @desc 清除导航元素
|
||||
*/
|
||||
@@ -471,4 +923,40 @@ export const Map = {
|
||||
this.clearNaviLine();
|
||||
this.clearEmergencyMarker();
|
||||
},
|
||||
createSMarker(position: any) {
|
||||
if (this.startMarker) {
|
||||
this.startMarker?.setMap(null);
|
||||
this.startMarker = null;
|
||||
}
|
||||
|
||||
this.startMarker = new AMap.Marker({
|
||||
position: position,
|
||||
offset: new AMap.Pixel(0, 0),
|
||||
});
|
||||
this.map.add(this.startMarker);
|
||||
this.setZoomCenter(position);
|
||||
},
|
||||
createEMarker(position: any) {
|
||||
if (this.endMarker) {
|
||||
this.endMarker?.setMap(null);
|
||||
this.endMarker = null;
|
||||
}
|
||||
|
||||
this.endMarker = new AMap.Marker({
|
||||
position: position,
|
||||
offset: new AMap.Pixel(0, 0),
|
||||
});
|
||||
this.map.add(this.endMarker);
|
||||
this.setZoomCenter(position);
|
||||
},
|
||||
clearSOrE() {
|
||||
if (this.startMarker) {
|
||||
this.startMarker?.setMap(null);
|
||||
this.startMarker = null;
|
||||
}
|
||||
if (this.endMarker) {
|
||||
this.endMarker?.setMap(null);
|
||||
this.endMarker = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 地图默认配置
|
||||
export const defaultOption = {
|
||||
center: [108.660704, 36.386058], //地图中心点
|
||||
zoom: 6.5, //地图显示的缩放级别
|
||||
center: [87.627313, 43.826809], //地图中心点
|
||||
zoom: 11, //地图显示的缩放级别
|
||||
mapStyle: 'darkblue', //地图样式
|
||||
};
|
||||
export const YinChuanOption = {
|
||||
@@ -21,9 +21,21 @@ export const innerLineLight = {
|
||||
strokeColor: '#93aaf8',
|
||||
};
|
||||
// 地图key
|
||||
export const mapKey = '4bbcb216b889f2d612cbed05ae6979c8';
|
||||
// export const mapKey = '4bbcb216b889f2d612cbed05ae6979c8';
|
||||
// 2025年11月12日11:25:52 更新新的key
|
||||
export const mapKey = '0075068dd9782107fc0e6a7209a6b494';
|
||||
// 地图插件
|
||||
export const mapPlugin = ['AMap.ToolBar', 'AMap.Driving', 'AMap.GeoJSON', 'AMap.MarkerCluster', 'AMap.PlaceSearch', 'AMap.Driving', 'AMap.Geocoder'];
|
||||
export const mapPlugin = [
|
||||
'AMap.ToolBar',
|
||||
'AMap.Driving',
|
||||
'AMap.GeoJSON',
|
||||
'AMap.MarkerCluster',
|
||||
'AMap.PlaceSearch',
|
||||
'AMap.Driving',
|
||||
'AMap.Walking',
|
||||
'AMap.Geocoder',
|
||||
'AMap.AutoComplete',
|
||||
];
|
||||
|
||||
//根据数据量设置地图层级
|
||||
export function getZoom(list = []) {
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
--antSelectDropdownBorderColor: #1b7ef2;
|
||||
--antSelectItemActive: #002e64;
|
||||
--antSelectFontColor: #fff;
|
||||
--antSelectBg: #000;
|
||||
--antSelectBg: #082147;
|
||||
/*服务详情数据*/
|
||||
--typeTimeSelectColor: #45a2ff;
|
||||
--typeTimeUnSelectColor: #a3a4a4;
|
||||
/*表格 */
|
||||
--tableHeadBg: rgba(35, 132, 221, 0.5);
|
||||
--tableHeadBg: #073063;
|
||||
--tableFontColor: #fff;
|
||||
--tableRowBg: #00152b;
|
||||
--tableRowBorder: #2384dd;
|
||||
|
||||
@@ -13,14 +13,16 @@ export enum ServerDetailType {
|
||||
export function useStatistics() {
|
||||
const statistics = ref([
|
||||
{
|
||||
name: '最新呼入电话',
|
||||
name: '本周呼入电话',
|
||||
value: '',
|
||||
key: 'gross',
|
||||
dateType: '0',
|
||||
},
|
||||
{
|
||||
name: '最新受理电话',
|
||||
name: '本周受理电话',
|
||||
value: '',
|
||||
key: 'alerdyreceive',
|
||||
key: 'gross', // 20251213临时处理方案
|
||||
dateType: '0',
|
||||
},
|
||||
{
|
||||
name: '突发重大伤病应急',
|
||||
@@ -85,6 +87,21 @@ export const sexType = [
|
||||
},
|
||||
];
|
||||
|
||||
export const timeTabNew = [
|
||||
{
|
||||
name: '本周',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
name: '本月',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
name: '本年',
|
||||
value: '4',
|
||||
},
|
||||
];
|
||||
|
||||
export function useSpin() {
|
||||
const spinning = ref(true);
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ export enum Api {
|
||||
complaint = '/health-emergency/emergency/tblHelpCenter/sicknessTypeStatistic',
|
||||
orderDetail = '/health-emergency/emergency/order/list',
|
||||
listCustom = '/health-watch/watch/watchMonitorData/listCustom',
|
||||
|
||||
listCustomNew = 'health-watch/watch/watchMonitorData/largeScreen/listCustom',
|
||||
}
|
||||
|
||||
//获取全部部门
|
||||
@@ -21,3 +23,4 @@ export const getDetail = (params: any) => get(Api.getDetail, params);
|
||||
export const getOrderDetail = (params: any) => get(Api.orderDetail, params);
|
||||
//副屏预警查看详情
|
||||
export const getEarlyWarningDetail = (params: any) => get(Api.listCustom, params);
|
||||
export const getEarlyWarningDetailNew = (params: any) => get(Api.listCustomNew, params);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="common-card" :class="[themeType]">
|
||||
<public-title title="服务详情数据" :themeType="themeType" />
|
||||
<public-title title="服务详情数据" themeType="dark" />
|
||||
<div class="inner">
|
||||
<div class="type-time">
|
||||
<span
|
||||
v-for="(item, index) in typeTime"
|
||||
:key="index"
|
||||
:class="[selectIndex === index ? 'select' : 'unSelect', themeType]"
|
||||
:class="[selectIndex === index ? 'select' : 'unSelect', 'dark']"
|
||||
@click="handleSelect(index, item.value)"
|
||||
>{{ item.name }}</span
|
||||
>
|
||||
@@ -16,7 +16,7 @@
|
||||
v-for="(item, index) in statistics"
|
||||
:key="index"
|
||||
class="server-item"
|
||||
:class="[['gross', 'alerdyreceive'].includes(item.key) ? 'hasCursor' : '', themeType]"
|
||||
:class="[['gross', 'alerdyreceive'].includes(item.key) ? 'hasCursor' : '', 'dark']"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<span>{{ item?.name }}</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="common-card" :class="[themeType]">
|
||||
<public-title title="主诉分类" :themeType="themeType">
|
||||
<public-title title="应急求助信息" :themeType="themeType">
|
||||
<template #right><span class="right-text" :class="[themeType]" @click="viewDetail">查看详情</span> </template>
|
||||
</public-title>
|
||||
<div class="zc-inner">
|
||||
|
||||
@@ -153,7 +153,6 @@
|
||||
});
|
||||
|
||||
async function getScrollList() {
|
||||
console.log(dayjs());
|
||||
try {
|
||||
const { code, result } = await getMonitorList({
|
||||
pageSize: 10,
|
||||
|
||||
@@ -7,5 +7,5 @@ export enum Api {
|
||||
|
||||
//健康终端报警
|
||||
export const getMonitorList = (params: any) => get(Api.getMonitorList, params);
|
||||
//长庆油田大病人数
|
||||
//大病人数
|
||||
export const getHealthDataList = (params: any) => get(Api.getHealthDataList, params);
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
<div class="common-card" :class="themeType">
|
||||
<public-title title="基层医疗点远程会诊" :theme-type="themeType" />
|
||||
<div class="inner">
|
||||
<div class="inner-item" data-text="应急就医-服务中心"></div>
|
||||
<div class="inner-item" data-text="采油九厂-薛岔作业区"></div>
|
||||
<div class="inner-item" data-text="第九采油厂-五谷城作业区"></div>
|
||||
<div class="inner-item" data-text="采油十一厂-彭阳作业区"></div>
|
||||
<div class="inner-item" data-text="服务中心"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="common-card" :class="themeType">
|
||||
<public-title title="长庆油田大病人数" :themeType="themeType" />
|
||||
<public-title title="大病人数" :themeType="themeType" />
|
||||
<div class="inner" ref="healthChart"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getAllList, getAmbulanceList } from '/@/components/chinaMap/chinaMapApi
|
||||
|
||||
export enum TabType {
|
||||
all = '', //所有资源
|
||||
youTianYiYuan = '1', //油田医院
|
||||
youTianYiYuan = '1', //医院
|
||||
heZuoYiYuan = '2', //合作医院
|
||||
yiLiaoDian = '3', //医疗点
|
||||
jiuHuChe = '5', //救护车
|
||||
@@ -105,7 +105,7 @@ export function mapApiSwitch(type, params: any) {
|
||||
lat: 34, //纬度34
|
||||
lon: 108, //经度
|
||||
};
|
||||
// 全部 油田 医疗点 合作医院
|
||||
// 全部 医疗点 合作医院
|
||||
if ([TabType.all, TabType.youTianYiYuan, TabType.yiLiaoDian, TabType.heZuoYiYuan, TabType.jiuHuChe, TabType.jiuHuChe].includes(type)) {
|
||||
return getAllList({ ...basicParams, ...params });
|
||||
}
|
||||
@@ -115,12 +115,14 @@ export function mapApiSwitch(type, params: any) {
|
||||
}
|
||||
|
||||
export const mapIcon1 = new URL('/@/assets/img/mapIcon1.png', import.meta.url).href;
|
||||
export const mapIcon11 = new URL('/@/assets/img/mapIcon1-1.png', import.meta.url).href;
|
||||
export const mapIcon2 = new URL('/@/assets/img/mapIcon2.png', import.meta.url).href;
|
||||
export const mapIcon3 = new URL('/@/assets/img/mapIcon3.png', import.meta.url).href;
|
||||
export const mapIcon4 = new URL('/@/assets/img/mapIcon4.png', import.meta.url).href;
|
||||
export const mapIcon41 = new URL('/@/assets/img/mapIcon4-1.png', import.meta.url).href;
|
||||
export const mapIcon5 = new URL('/@/assets/img/mapIcon5.png', import.meta.url).href;
|
||||
export const mapIcon6 = new URL('/@/assets/img/mapIcon6.png', import.meta.url).href;
|
||||
export const mapIcon7 = new URL('/@/assets/img/mapIcon7.png', import.meta.url).href;
|
||||
export const mapIcon51 = new URL('/@/assets/img/mapIcon5-1.png', import.meta.url).href;
|
||||
export const watchIconR = new URL('/@/assets/img/watchIconR.svg', import.meta.url).href;
|
||||
|
||||
export const legend1 = new URL('/@/assets/img/legend1.png', import.meta.url).href;
|
||||
export const legend2 = new URL('/@/assets/img/legend2.png', import.meta.url).href;
|
||||
|
||||
@@ -1,16 +1,39 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:class="['dialog', themeType]"
|
||||
v-model:visible="props.openVis"
|
||||
:title="props.title"
|
||||
:width="props.width"
|
||||
:maskClosable="true"
|
||||
@cancel="handleCancel"
|
||||
destroy-on-close
|
||||
>
|
||||
<slot name="container"></slot>
|
||||
<template #footer></template>
|
||||
</a-modal>
|
||||
<template v-if="props.title">
|
||||
<a-modal
|
||||
:class="['dialog', themeType]"
|
||||
v-model:visible="props.openVis"
|
||||
:title="props.title"
|
||||
:width="props.width"
|
||||
:maskClosable="true"
|
||||
@cancel="handleCancel"
|
||||
v-bind="$attrs"
|
||||
destroy-on-close
|
||||
>
|
||||
<template #title>
|
||||
<slot name="modalTitle"></slot>
|
||||
</template>
|
||||
<slot name="container"></slot>
|
||||
<template #footer></template>
|
||||
</a-modal>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-modal
|
||||
:class="['dialog', themeType]"
|
||||
v-model:visible="props.openVis"
|
||||
:width="props.width"
|
||||
:maskClosable="true"
|
||||
@cancel="handleCancel"
|
||||
v-bind="$attrs"
|
||||
destroy-on-close
|
||||
>
|
||||
<template #title>
|
||||
<slot name="modalTitle"></slot>
|
||||
</template>
|
||||
<slot name="container"></slot>
|
||||
<template #footer></template>
|
||||
</a-modal>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -26,6 +49,7 @@
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
});
|
||||
const { themeType } = useTheme();
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// 步数
|
||||
import {visibleChartLabel} from "/@/utils/utils.ts";
|
||||
import { visibleChartLabel } from '/@/utils/utils.ts';
|
||||
|
||||
export function screenPie(chartData) {
|
||||
const data = visibleChartLabel(chartData)
|
||||
const data = visibleChartLabel(chartData).map((item) => {
|
||||
item = { ...item, label: { show: item.value > 0 }, labelLine: { show: item.value > 0 } };
|
||||
return item;
|
||||
});
|
||||
return {
|
||||
color: ['#B750BE', '#6C63F0', '#3AACFF', '#ED589D', '#FB466C'],
|
||||
tooltip: {
|
||||
@@ -24,7 +27,7 @@ export function screenPie(chartData) {
|
||||
color: '#ffffff',
|
||||
fontSize: 16,
|
||||
},
|
||||
selectedMode: false
|
||||
selectedMode: false,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
@@ -35,13 +38,14 @@ export function screenPie(chartData) {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
formatter: '{c}人次',
|
||||
color: '#ffffff',
|
||||
fontSize: 14,
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
length: 6,
|
||||
length2: 10,
|
||||
length2: 30,
|
||||
minTurnAngle: 120,
|
||||
},
|
||||
data: data,
|
||||
@@ -52,7 +56,7 @@ export function screenPie(chartData) {
|
||||
|
||||
//左侧圆
|
||||
export function circleRing(chartData, total, color) {
|
||||
const data = visibleChartLabel(chartData)
|
||||
const data = visibleChartLabel(chartData);
|
||||
return {
|
||||
color: color,
|
||||
tooltip: {
|
||||
@@ -74,7 +78,7 @@ export function circleRing(chartData, total, color) {
|
||||
color: '#ffffff',
|
||||
fontSize: 16,
|
||||
},
|
||||
selectedMode: false
|
||||
selectedMode: false,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
@@ -95,14 +99,14 @@ export function circleRing(chartData, total, color) {
|
||||
label: {
|
||||
show: true,
|
||||
position: 'outside',
|
||||
formatter: '{d}%',
|
||||
formatter: '{d}%\n{c}人次',
|
||||
color: '#ffffff',
|
||||
fontSize: 14,
|
||||
},
|
||||
labelLine: {
|
||||
show: true,
|
||||
length: 4,
|
||||
length2: 12,
|
||||
length: 2,
|
||||
length2: 30,
|
||||
minTurnAngle: 120,
|
||||
},
|
||||
data: data,
|
||||
|
||||
@@ -3,17 +3,23 @@ import { get, post } from '/@/api/request.ts';
|
||||
export enum Api {
|
||||
depart = '/health-watch/watch/watchDevice/getUnitList',
|
||||
getWatchDataByOrgCode = '/health-watch/watch/watchData/getWatchDataByOrgCode',
|
||||
|
||||
getWatchDataByOrgCodeNew = '/health-watch/watch/watchData/getWatchDataByOrgCode/v2',
|
||||
getMonitorList = '/health-watch/watch/watchMonitorData/getMonitorList',
|
||||
sleepApi = '/health-watch/watch/watchData/getSleep',
|
||||
sleepApi = '/health-watch/watch/watchData/getSleep/v2',
|
||||
stepApi = '/health-watch/watch/watchData/getSdc',
|
||||
stepNewApi = '/health-watch/watch/watchData/getSdc/v2',
|
||||
listCustom = 'health-watch/watch/watchMonitorData/listCustom',
|
||||
listCustomNew = 'health-watch/watch/watchMonitorData/largeScreen/listCustom',
|
||||
}
|
||||
|
||||
// 单位-
|
||||
export const allSecondaryDeparts = () => get(Api.depart);
|
||||
|
||||
//左側
|
||||
export const getWatchDataByOrgCode = (params) => post(Api.getWatchDataByOrgCode, params);
|
||||
export const getWatchDataByOrgCode = (params: any) => post(Api.getWatchDataByOrgCode, params);
|
||||
|
||||
export const getWatchDataByOrgCodeNew = (params: any) => post(Api.getWatchDataByOrgCodeNew, params);
|
||||
|
||||
//右-健康终端报警
|
||||
export const getMonitorList = (params: any) => get(Api.getMonitorList, params);
|
||||
@@ -21,6 +27,8 @@ export const getMonitorList = (params: any) => get(Api.getMonitorList, params);
|
||||
export const getSleep = (params: any) => post(Api.sleepApi, params);
|
||||
// 右-步数
|
||||
export const getStep = (params: any) => post(Api.stepApi, params);
|
||||
export const getStepNew = (params: any) => post(Api.stepNewApi, params);
|
||||
|
||||
//副屏-预警
|
||||
export const getCustom = (params: any) => get(Api.listCustom, params);
|
||||
export const getCustomNew = (params: any) => get(Api.listCustomNew, params);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import ItemD from '/@/components/item-d/item-d.vue';
|
||||
import { getWatchDataByOrgCode } from '/@/components/secondaryScreen/commonApi.ts';
|
||||
import { getWatchDataByOrgCode, getWatchDataByOrgCodeNew } from '/@/components/secondaryScreen/commonApi.ts';
|
||||
import { useSpin } from '/@/components/body-d-left/bodyLeftHooks.ts';
|
||||
import { useSession, caching } from '/@/hooks/autoRefresh/caching.ts';
|
||||
|
||||
@@ -79,8 +79,14 @@
|
||||
orgCode: String,
|
||||
type: String,
|
||||
themeType: String,
|
||||
refreshState: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
});
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
onMounted(() => {});
|
||||
watch(props, () => {
|
||||
init();
|
||||
});
|
||||
@@ -88,14 +94,15 @@
|
||||
async function init() {
|
||||
setSpin(true);
|
||||
try {
|
||||
const { code, result } = await getWatchDataByOrgCode(props);
|
||||
// const { code, result } = await getWatchDataByOrgCode(props);
|
||||
const { code, result } = await getWatchDataByOrgCodeNew(props);
|
||||
if (code == 200) {
|
||||
setValue(result);
|
||||
setSession(PAGE2_LEFT, JSON.stringify(result));
|
||||
// setSession(PAGE2_LEFT, JSON.stringify(result));
|
||||
}
|
||||
} catch {
|
||||
const result = JSON.parse(getSession(PAGE2_LEFT));
|
||||
setValue(result);
|
||||
// const result = JSON.parse(getSession(PAGE2_LEFT));
|
||||
// setValue(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +112,10 @@
|
||||
data.forEach((item) => {
|
||||
let resData = result.find((v) => v.type == item.type);
|
||||
item.userCount = resData.userCount;
|
||||
item.data.forEach((val) => {
|
||||
item.data = item.data.map((val) => {
|
||||
val.value = resData[val.key];
|
||||
val = { ...val, label: { show: val.value > 0 }, labelLine: { show: val.value > 0 } };
|
||||
return val;
|
||||
});
|
||||
});
|
||||
dataList.value = data;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<public-title title="睡眠" :theme-type="themeType" />
|
||||
<div class="inner">
|
||||
<div class="title">
|
||||
<span class="lightSleep">平均睡眠 {{ nums.avg }} 深睡 {{ nums.avgLong }} 浅睡 {{ nums.avgShort }}</span>
|
||||
<span class="lightSleep">平均睡眠 {{ nums.avg || 0 }} 深睡 {{ nums.avgLong || 0 }} 浅睡 {{ nums.avgShort || 0 }}</span>
|
||||
</div>
|
||||
<div class="sleepChart" ref="sleepChart"></div>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ export function sleepCharts({ time, short, long, awake, type }) {
|
||||
grid: {
|
||||
top: '15%',
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
right: '8%',
|
||||
bottom: 30,
|
||||
},
|
||||
tooltip: {
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
</a-table>
|
||||
</div>
|
||||
<!--预警查看详情-->
|
||||
<EarlyWarningDialog ref="earlyWarningRef"></EarlyWarningDialog>
|
||||
<EarlyWarningDialog ref="earlyWarningRef" :org-codes="orgCode" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onUnmounted, ref, watch } from 'vue';
|
||||
import PublicTitle from '/@/components/publicTitle.vue';
|
||||
import EarlyWarningDialog from '/@/views/components/earlyWarningDialog.vue';
|
||||
import { getCustom, getMonitorList } from '/@/components/secondaryScreen/commonApi.ts';
|
||||
import { getCustom, getCustomNew, getMonitorList } from '/@/components/secondaryScreen/commonApi.ts';
|
||||
import { monitorColumns } from '/@/components/secondaryScreen/screen-right/screenRightHooks.ts';
|
||||
import $bus from '/@/utils/bus.ts';
|
||||
import { earlyWarning } from '/@/utils/busConstant.ts';
|
||||
@@ -64,7 +64,7 @@
|
||||
function handlePoliceDetail(record) {
|
||||
$bus.emit(earlyWarning, record);
|
||||
}
|
||||
|
||||
const threeCodes = ref();
|
||||
watch(
|
||||
props,
|
||||
() => {
|
||||
@@ -75,7 +75,12 @@
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.orgCode,
|
||||
() => {
|
||||
threeCodes.value = props.orgCode;
|
||||
}
|
||||
);
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', setHeight);
|
||||
});
|
||||
@@ -89,7 +94,8 @@
|
||||
async function init(page) {
|
||||
try {
|
||||
const { pageSize } = pagination.value;
|
||||
const { code, result } = await getCustom({ pageSize, pageNo: page, orgCode: props.orgCode });
|
||||
// const { code, result } = await getCustom({ pageSize, pageNo: page, orgCode: props.orgCode });
|
||||
const { code, result } = await getCustomNew({ pageSize, pageNo: page, orgCodes: props.orgCode });
|
||||
if (code == 200) {
|
||||
setSession(PAGE2_RIGHT3, JSON.stringify({ page, result }));
|
||||
setValue(result, page);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import { useSpin } from '/@/components/body-d-left/bodyLeftHooks.ts';
|
||||
import { useStep } from '/@/components/secondaryScreen/screen-right/screenRightHooks.ts';
|
||||
import { screenPie } from '/@/components/item-d/pieCharts.ts';
|
||||
import { getStep } from '/@/components/secondaryScreen/commonApi.ts';
|
||||
import { getStep, getStepNew } from '/@/components/secondaryScreen/commonApi.ts';
|
||||
import { caching, useSession } from '/@/hooks/autoRefresh/caching.ts';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -41,7 +41,8 @@
|
||||
orgCode: props.orgCode,
|
||||
type: props.type,
|
||||
};
|
||||
const { code, result } = await getStep(params);
|
||||
// const { code, result } = await getStep(params);
|
||||
const { code, result } = await getStepNew(params);
|
||||
if (code == 200) {
|
||||
setValue(result);
|
||||
setSession(PAGE2_RIGHT2, JSON.stringify(result));
|
||||
|
||||
@@ -9,13 +9,12 @@
|
||||
:rowClassName="(record, index) => (index % 2 === 1 ? 'table-default' : 'table-striped')"
|
||||
>
|
||||
<template #bodyCell="{ column, record, index, text }">
|
||||
<template v-if="column.customRender && isFunction(column.customRender)" v-html="column.customRender({ column, record, index, text })">
|
||||
<template v-if="column?.customRender && isFunction(column.customRender)" v-html="column.customRender({ column, record, index, text })">
|
||||
</template>
|
||||
<slot v-else-if="column.slot" :name="column.slot" v-bind="{ column, record, index, text } || {}"></slot>
|
||||
<slot v-else-if="column?.slot" :name="column.slot" v-bind="{ column, record, index, text } || {}"></slot>
|
||||
<template v-else>
|
||||
{{ text }}
|
||||
</template>
|
||||
|
||||
<slot name="bodyCell" v-bind="{ column, record, index, text } || {}"></slot>
|
||||
</template>
|
||||
</a-table>
|
||||
@@ -32,6 +31,7 @@
|
||||
type: Function,
|
||||
},
|
||||
apiParams: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
afterFetch: {
|
||||
@@ -53,6 +53,7 @@
|
||||
showTotal: (total) => `总 ${total} 条`,
|
||||
onChange: pageChange,
|
||||
});
|
||||
const emit = defineEmits(['tableLoad']);
|
||||
|
||||
function pageChange(page, pageS) {
|
||||
current.value = page;
|
||||
@@ -75,34 +76,35 @@
|
||||
async function getRecords() {
|
||||
try {
|
||||
tableLoading.value = true;
|
||||
emit('tableLoad', true);
|
||||
const { code, result } = await props.api({ ...props.apiParams, pageSize: pageSize.value, pageNo: current.value });
|
||||
if (code == 200) {
|
||||
if (props.afterFetch && isFunction(props.afterFetch)) {
|
||||
dataSource.value = props.afterFetch({ result, setPage: setPage, setDataSource: setDataSource });
|
||||
} else {
|
||||
dataSource.value = result?.records;
|
||||
dataSource.value = result?.records ? result?.records : result;
|
||||
}
|
||||
if (result.hasOwnProperty('records')) {
|
||||
paginationProp.value = { ...paginationProp.value, ...result };
|
||||
paginationProp.value.pageSize = result.size;
|
||||
}
|
||||
paginationProp.value = { ...paginationProp.value, ...result };
|
||||
paginationProp.value.pageSize = result.size;
|
||||
}
|
||||
} catch (e) {
|
||||
dataSource.value = [];
|
||||
tableLoading.value = false;
|
||||
} finally {
|
||||
tableLoading.value = false;
|
||||
emit('tableLoad', false);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
props,
|
||||
(v, o) => {
|
||||
if (JSON.stringify(v) !== JSON.stringify(o)) {
|
||||
nextTick(() => {
|
||||
pageSize.value = 10;
|
||||
current.value = 1;
|
||||
getRecords();
|
||||
});
|
||||
}
|
||||
() => {
|
||||
nextTick(() => {
|
||||
pageSize.value = 10;
|
||||
current.value = 1;
|
||||
getRecords();
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
@@ -115,8 +117,8 @@
|
||||
<style scoped lang="less">
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
color: var(--tableFontColor) !important;
|
||||
background: var(--tableHeadBg) !important;
|
||||
border-top: 1px solid var(--tableHeadBg) !important;
|
||||
background: #073063 !important;
|
||||
border-top: 1px solid #073063 !important;
|
||||
border-bottom: none;
|
||||
padding: 9px 9px;
|
||||
text-align: center;
|
||||
|
||||
@@ -12,12 +12,22 @@
|
||||
<div v-show="isShow" :class="className" class="footer-data" style="--moveUp: 20px; --moveDown: -80px">
|
||||
<div
|
||||
v-for="item in footerList"
|
||||
:style="{ cursor: item.key == 'allotDeviceNum' ? 'pointer' : 'default' }"
|
||||
:style="{
|
||||
cursor: item.key == 'allotDeviceNum' ? 'pointer' : 'default',
|
||||
}"
|
||||
:key="`footer-list-${item.key}`"
|
||||
class="col-item nowrap"
|
||||
@click="openWatchList(item)"
|
||||
>
|
||||
<div class="col-text">{{ item?.name }}</div>
|
||||
<div :title="item.value" class="col-value nowrap">{{ item?.value }}</div>
|
||||
<div
|
||||
:title="item.value"
|
||||
class="col-value nowrap"
|
||||
:style="{
|
||||
color: item.key == 'allotDeviceNum' ? '#0098fa' : '#fff',
|
||||
}"
|
||||
>{{ item?.value }}</div
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -26,10 +36,10 @@
|
||||
class="footer-dialog-con"
|
||||
style="--moveUp: 20px; --moveDown: -180px"
|
||||
>
|
||||
<FooterDialog :detailLeft="detailLeft" :detailRight="detailRight" @closeDialog="closeDialog"></FooterDialog>
|
||||
<FooterDialog :detailLeft="detailLeft" :detailRight="detailRight" @closeDialog="closeDialog" />
|
||||
</div>
|
||||
</div>
|
||||
<WatchListDialog ref="watchDialogRef" @close="closeWatch"></WatchListDialog>
|
||||
<WatchListDialog ref="watchDialogRef" @close="closeWatch" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -103,7 +113,7 @@
|
||||
async (val) => {
|
||||
let params = {
|
||||
orgCodeLeaf: val.orgCode,
|
||||
orgCode: null,
|
||||
orgCode: props.orgCode,
|
||||
};
|
||||
const { code, result } = await footerApi(params);
|
||||
if (code == 200) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export function useDepart() {
|
||||
async function getOptionData() {
|
||||
const { result = [], code } = await allSecondaryDeparts();
|
||||
if (code == 200) {
|
||||
let res = [{ departName: '长庆油田', orgCode: 'A01' }, ...result];
|
||||
let res = [...result];
|
||||
setOption(res);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,14 +264,12 @@
|
||||
// console.log('绘制驾车路线完成');
|
||||
searchFlag.value = false;
|
||||
} else {
|
||||
console.log('获取驾车数据失败:', result);
|
||||
searchFlag.value = false;
|
||||
}
|
||||
});
|
||||
AMap.Event.addListener(driving, 'complete', (res) => {
|
||||
const data = JSON.parse(JSON.stringify(res));
|
||||
searchFlag.value = false;
|
||||
console.log('查询结果', data);
|
||||
if (data.info == 'OK') {
|
||||
const { routes } = data;
|
||||
setTitle(routes[0]);
|
||||
|
||||
@@ -2,10 +2,12 @@ import { get } from '/@/api/request.ts';
|
||||
|
||||
export enum Api {
|
||||
allList = '/health-emergency/emergency/tblAmbulanceGps/getAllList',
|
||||
allListNew = '/health-emergency/emergency/LargeScreenNew/map',
|
||||
getAmbulanceList = '/health-emergency/emergency/tblAmbulanceGps/getAmbulanceList',
|
||||
}
|
||||
|
||||
// 获取所有资源
|
||||
export const getAllList = (params) => get(Api.allList, params);
|
||||
export const getAllListNew = (params) => get(Api.allListNew, params);
|
||||
//获取救护车GPS信息
|
||||
export const getAmbulanceList = (params) => get(Api.getAmbulanceList, params);
|
||||