${
+ html: `
${
index + 1
}
`,
- iconSize: [24, 24],
+ iconSize: [22, 22],
+ iconAnchor: [11, 11],
}),
- }).addTo(map);
-
- // 为中间点添加弹窗
- marker.bindPopup(`
-
-
点位 ${index + 1}
-
时间: ${currentPoint.track_time || '未知'}
-
坐标: ${currentPoint.latitude}, ${
- currentPoint.longitude
- }
-
- `);
+ })
+ .bindPopup(
+ `
+
点位 ${index + 1}
+
时间: ${currentPoint.track_time || '未知'}
+
坐标: ${currentPoint.latitude}, ${
+ currentPoint.longitude
+ }
+
`,
+ )
+ .addTo(map);
});
- // 自动调整地图视图以显示整个轨迹
- map.fitBounds(trackLine.getBounds());
- };
+ // 自动调整视图
+ map.fitBounds(trackLine.getBounds(), { padding: [40, 40] });
- const loadShow = (day: any) => {
- // showTrack([
- // { latitude: 22.566729, longitude: 114.062952 },
- // { latitude: 22.567982, longitude: 114.06278 },
- // { latitude: 22.569394, longitude: 114.062694 },
- // ]);
- Apis.Attendance.AttendanceEmployeeTracks.Heatmap({
- company_employees_id: Number(id),
- date: day, // 转换为Date类型
- }).then((res) => {
- console.log(res);
- // 假设API返回的数据结构为{ data: [{ latitude, longitude, time }, ...] }
- showTrack(res?.data);
- // setTrackData(res.data || []);
+ // 更新统计信息
+ setTrackInfo({
+ count: validPoints.length,
+ startTime: startPoint.track_time || '未知',
+ endTime: endPoint.track_time || '未知',
});
- };
+ }, []);
+
+ // 播放轨迹动画
+ const playTrack = useCallback(() => {
+ const map = mapInstanceRef.current;
+ const points = trackPointsRef.current;
+ if (!map || points.length < 2 || isPlaying) return;
+
+ setIsPlaying(true);
+
+ // 移除旧的移动标记
+ if (movingMarkerRef.current) {
+ map.removeLayer(movingMarkerRef.current);
+ }
+
+ const movingIcon = L.divIcon({
+ className: 'track-moving-marker',
+ html: '
',
+ iconSize: [28, 28],
+ iconAnchor: [14, 14],
+ });
+
+ const marker = L.marker(points[0], {
+ icon: movingIcon,
+ zIndexOffset: 2000,
+ }).addTo(map);
+ movingMarkerRef.current = marker;
+
+ let currentIndex = 0;
+ let progress = 0;
+ const speed = 0.03; // 动画速度
+
+ const animate = () => {
+ if (currentIndex >= points.length - 1) {
+ setIsPlaying(false);
+ return;
+ }
+
+ progress += speed;
+
+ if (progress >= 1) {
+ progress = 0;
+ currentIndex++;
+ }
+
+ const start = L.latLng(points[currentIndex]);
+ const end = L.latLng(points[currentIndex + 1]);
+ const lat = start.lat + (end.lat - start.lat) * progress;
+ const lng = start.lng + (end.lng - start.lng) * progress;
+
+ marker.setLatLng([lat, lng]);
+
+ // 自动跟随视角(可选:平滑移动)
+ if (currentIndex % 5 === 0 && progress < 0.1) {
+ map.panTo([lat, lng], { animate: true, duration: 0.3 });
+ }
+
+ animationRef.current = requestAnimationFrame(animate);
+ };
+
+ animationRef.current = requestAnimationFrame(animate);
+ }, [isPlaying]);
+
+ // 暂停轨迹
+ const pauseTrack = useCallback(() => {
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current);
+ animationRef.current = null;
+ }
+ setIsPlaying(false);
+ }, []);
+
+ // 重置视图
+ const resetView = useCallback(() => {
+ const map = mapInstanceRef.current;
+ const line = trackLineRef.current;
+ if (map && line) {
+ map.fitBounds(line.getBounds(), { padding: [40, 40] });
+ }
+ }, []);
+
+ // 加载轨迹数据
+ const loadShow = useCallback(
+ (day: any) => {
+ setLoading(true);
+ pauseTrack();
+
+ Apis.Attendance.AttendanceEmployeeTracks.Heatmap({
+ company_employees_id: Number(id),
+ date: day,
+ })
+ .then((res) => {
+ const data = res?.data || [];
+ const validPoints = data.filter(
+ (point: any) => point.latitude && point.longitude,
+ );
+
+ if (validPoints.length === 0) {
+ message.info('当天没有轨迹');
+ clearTrackLayers();
+ setTrackInfo(null);
+ return;
+ }
+
+ const firstPoint: L.LatLngTuple = [
+ parseFloat(validPoints[0].latitude),
+ parseFloat(validPoints[0].longitude),
+ ];
+ initMap(firstPoint);
+ showTrack(data);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ },
+ [id, pauseTrack, showTrack],
+ );
+
+ // 加载员工列表
+ const loadEmployees = useCallback(() => {
+ setEmployeeLoading(true);
+ Apis.Company.CompanyEmployees.Select()
+ .then((res) => {
+ const list = res?.data || [];
+ setEmployees(list);
+ })
+ .finally(() => {
+ setEmployeeLoading(false);
+ });
+ }, []);
+
useEffect(() => {
- // 初始化地图
- initMap();
- // 加载轨迹数据
+ loadEmployees();
loadShow(new Date(getTodayDate()));
- // 组件卸载时清理地图实例
return () => {
+ if (animationRef.current) {
+ cancelAnimationFrame(animationRef.current);
+ }
if (mapInstanceRef.current) {
mapInstanceRef.current.remove();
mapInstanceRef.current = null;
}
};
- }, [id]);
-
- // // 当轨迹数据变化时,显示轨迹
- // useEffect(() => {
- // if (trackData.length > 0) {
- // showTrack(trackData);
- // }
- // }, [trackData]);
+ }, [id, loadEmployees, loadShow]);
return (
-
- }
- onClick={() => navigate(-1)}
- >
- 员工轨迹底图
-
- }
- headerBordered
- extra={
+ }
+ onClick={() => navigate(-1)}
+ >
+ 员工轨迹底图
+
+ }
+ headerBordered
+ extra={
+
+
+ }
+ >
+
+ {/* 地图容器 */}
-
+
+ {/* 加载遮罩 */}
+ {loading && (
-
起点:
-

+
-
+
+ 轨迹点数:
+ {trackInfo.count} 个
+
+
+ 开始时间:
+ {trackInfo.startTime}
+
+
+ 结束时间:
+ {trackInfo.endTime}
+
+
+ )}
+
+
+ {/* 图例 */}
+
+
+
起点:
+

+
+
+
终点:
+

+
+
+
轨迹线:
+
- 终点:
-
-
-
-
-
+ />
+
+
+
);
}
diff --git a/src/pages/attendance/employee_tracks/ending.png b/src/pages/attendance/employee_tracks/ending.png
new file mode 100644
index 0000000..6ef5d15
Binary files /dev/null and b/src/pages/attendance/employee_tracks/ending.png differ
diff --git a/src/pages/attendance/employee_tracks/starting.png b/src/pages/attendance/employee_tracks/starting.png
new file mode 100644
index 0000000..5fe2baf
Binary files /dev/null and b/src/pages/attendance/employee_tracks/starting.png differ
diff --git a/src/pages/attendance/employee_tracks/style.scss b/src/pages/attendance/employee_tracks/style.scss
index 5be1f57..54cba66 100644
--- a/src/pages/attendance/employee_tracks/style.scss
+++ b/src/pages/attendance/employee_tracks/style.scss
@@ -1,26 +1,138 @@
-.track-start-marker {
- background-color: #007bff;
- color: #fff;
- width: 200px;
- height: 200px;
- border-radius: 100px;
- font-size: 0.6em;
- text-align: center;
- line-height: 200px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
+.track-start-marker,
.track-end-marker {
- background-color: #f00;
- color: #fff;
- width: 200px;
- height: 200px;
- border-radius: 100px;
- font-size: 0.6em;
- text-align: center;
- line-height: 200px;
+ background: none !important;
+ border: none !important;
display: flex;
align-items: center;
justify-content: center;
}
+
+.track-start-marker img,
+.track-end-marker img {
+ display: block;
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.25));
+}
+
+.track-point-marker {
+ background: none !important;
+ border: none !important;
+}
+
+.track-point-marker div {
+ border: 2px solid #fff;
+ transition: transform 0.2s ease;
+}
+
+/* 轨迹线流动动画 */
+.track-animated-line {
+ animation: dashFlow 1.5s linear infinite;
+}
+
+@keyframes dashFlow {
+ 0% {
+ stroke-dashoffset: 20;
+ }
+ 100% {
+ stroke-dashoffset: 0;
+ }
+}
+
+/* 移动标记 */
+.track-moving-marker {
+ background: none !important;
+ border: none !important;
+ z-index: 1000 !important;
+}
+
+.track-moving-marker .moving-icon {
+ width: 28px;
+ height: 28px;
+ background: #1890ff;
+ border: 3px solid #fff;
+ border-radius: 50%;
+ box-shadow: 0 2px 8px rgba(24, 144, 255, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: pulse 1.2s ease-in-out infinite;
+}
+
+.track-moving-marker .moving-icon::after {
+ content: '';
+ width: 8px;
+ height: 8px;
+ background: #fff;
+ border-radius: 50%;
+}
+
+@keyframes pulse {
+ 0%,
+ 100% {
+ box-shadow: 0 2px 8px rgba(24, 144, 255, 0.5);
+ }
+ 50% {
+ box-shadow: 0 2px 16px rgba(24, 144, 255, 0.8);
+ }
+}
+
+/* Leaflet 弹窗样式优化 */
+.leaflet-popup-content-wrapper {
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.leaflet-popup-content {
+ margin: 12px 16px;
+ font-size: 13px;
+ line-height: 1.6;
+}
+
+.leaflet-popup-content h4 {
+ font-size: 14px;
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.leaflet-popup-content p {
+ margin: 4px 0;
+ color: #666;
+}
+
+/* 地图自定义控件 */
+.map-custom-controls {
+ position: absolute;
+ bottom: 20px;
+ right: 20px;
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.map-custom-controls .ant-btn {
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+/* 轨迹统计面板 */
+.track-stats-panel {
+ position: absolute;
+ top: 12px;
+ left: 12px;
+ z-index: 1000;
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(4px);
+ border-radius: 8px;
+ padding: 12px 16px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ font-size: 12px;
+ line-height: 1.8;
+}
+
+.track-stats-panel .stats-label {
+ color: #999;
+}
+
+.track-stats-panel .stats-value {
+ color: #333;
+ font-weight: 500;
+}
diff --git a/src/pages/bills/all_bills/audit/index.tsx b/src/pages/bills/all_bills/audit/index.tsx
deleted file mode 100644
index bf22dec..0000000
--- a/src/pages/bills/all_bills/audit/index.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { MyColumns, MyProTableProps, useCurrentPermissions } from '@/common';
-import { Apis } from '@/gen/Apis';
-import { HouseOrdersPaymentMethodEnum } from '@/gen/Enums';
-import { ProTable } from '@ant-design/pro-components';
-import { Space } from 'antd';
-import Review from './modals/Review';
-
-export default function Index() {
- const getCurrentPermissions = useCurrentPermissions();
- let tableRender = (item: any, action: any) => {
- return getCurrentPermissions(
- {
- audit: