diff --git a/backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/CheckInGroup.java b/backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/CheckInGroup.java
new file mode 100644
index 00000000..023010f5
--- /dev/null
+++ b/backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/CheckInGroup.java
@@ -0,0 +1,11 @@
+package org.dromara.zsjy.domain.bo;
+
+/**
+ * 签到验证组
+ * 用于签到时的数据验证,不包含签退相关字段的验证
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+public interface CheckInGroup {
+}
diff --git a/mobile/src/api/internship.js b/mobile/src/api/internship.js
new file mode 100644
index 00000000..53a12c31
--- /dev/null
+++ b/mobile/src/api/internship.js
@@ -0,0 +1,83 @@
+/**
+ * 实习管理API接口
+ * 提供学生实习签到、获取实习信息等功能
+ */
+
+import { request } from '@/common/request.js';
+
+/**
+ * 获取学生实习信息(班主任专用)
+ * @param {number} studentId 学生ID
+ * @returns Promise
+ */
+export const getInternshipInfo = (studentId) => {
+ return request({
+ url: `/zsjy/internship/${studentId}`,
+ method: 'GET'
+ });
+};
+
+/**
+ * 学生获取自己的实习信息(移动端专用)
+ * @returns Promise
+ */
+export const getStudentInternshipInfo = () => {
+ return request({
+ url: '/zsjy/internship/student/info',
+ method: 'GET'
+ });
+};
+
+/**
+ * 学生签到打卡
+ * @param {Object} data 签到数据
+ * @returns Promise
+ */
+export const checkIn = (data) => {
+ return request({
+ url: '/zsjy/internship/daily/checkin',
+ method: 'POST',
+ data
+ });
+};
+
+/**
+ * 获取学生考勤日历数据
+ * @param {number} studentId 学生ID
+ * @param {number} year 年份
+ * @param {number} month 月份
+ * @returns Promise
+ */
+export const getCalendarData = (studentId, year, month) => {
+ return request({
+ url: '/zsjy/internship/daily/calendar',
+ method: 'GET',
+ params: { studentId, year, month }
+ });
+};
+
+/**
+ * 获取考勤统计数据
+ * @param {Object} params 查询参数
+ * @returns Promise
+ */
+export const getStatistics = (params) => {
+ return request({
+ url: '/zsjy/internship/daily/statistics',
+ method: 'GET',
+ params
+ });
+};
+
+/**
+ * 查询实习日常记录列表
+ * @param {Object} params 查询参数
+ * @returns Promise
+ */
+export const getDailyList = (params) => {
+ return request({
+ url: '/zsjy/internship/daily/list',
+ method: 'GET',
+ params
+ });
+};
\ No newline at end of file
diff --git a/mobile/src/pages/internship/index.vue b/mobile/src/pages/internship/index.vue
new file mode 100644
index 00000000..ca7c5b41
--- /dev/null
+++ b/mobile/src/pages/internship/index.vue
@@ -0,0 +1,1248 @@
+
+
+
+
+
+
+
+
+
+ {{ userInfo.nickName || userInfo.userName || '学生' }}
+ 学号:{{ userInfo.userName || '暂无' }}
+ 部门:{{ userInfo.deptName || '暂无' }}
+
+
+
+
+
+
+
+
+
+ 实习单位:
+ {{ internshipInfo.companyName || '暂无' }}
+
+
+ 实习岗位:
+ {{ internshipInfo.internshipPosition || '暂无' }}
+
+
+ 实习期间:
+ {{ formatDateRange(internshipInfo.internshipStartDate, internshipInfo.internshipEndDate) }}
+
+
+ 状态:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 已签到
+
+
+ 签到时间:
+ {{ todayRecord.checkInTime }}
+
+
+ 签到位置:
+ {{ todayRecord.checkInLocation }}
+
+
+
+
+
+
+
+ {{ checkinLoading ? '签到中...' : '立即签到' }}
+
+
+
+
+ {{ checkinTips }}
+
+
+
+
+
+
+
+
+ {{ currentLocation.address }}
+
+ 经度:{{ currentLocation.longitude.toFixed(6) }}
+ 纬度:{{ currentLocation.latitude.toFixed(6) }}
+
+
+ 定位精度:{{ currentLocation.accuracy.toFixed(0) }}米
+
+
+
+
+
+ 在地图中查看
+
+
+
+
+
+
+
+
+
+
+ {{ locationError }}
+ 签到需要位置信息,请确保:
+ • 已开启位置权限
+ • GPS/网络定位已开启
+ • 网络连接正常
+
+
+
+
+
+
+
+
+ {{ monthStats.totalDays || 0 }}
+ 应签到
+
+
+ {{ monthStats.checkedDays || 0 }}
+ 已签到
+
+
+ {{ monthStats.missedDays || 0 }}
+ 未签到
+
+
+ {{ monthStats.exceptionDays || 0 }}
+ 异常
+
+
+
+
+
+
+
+
+
+ {{ formatDate(record.checkDate) }}
+ {{ record.checkInTime || '未签到' }}
+
+
+
+
+
+
+
+ 暂无签到记录
+
+
+
+
+
+
+
+
diff --git a/mobile/src/utils/location.js b/mobile/src/utils/location.js
new file mode 100644
index 00000000..7b85a7ed
--- /dev/null
+++ b/mobile/src/utils/location.js
@@ -0,0 +1,140 @@
+/**
+ * 定位服务工具类
+ * 提供获取当前位置、地址解析等功能
+ */
+
+/**
+ * 获取当前位置
+ * @returns Promise<{latitude, longitude, address}>
+ */
+export const getCurrentLocation = () => {
+ return new Promise((resolve, reject) => {
+ uni.showLoading({
+ title: '正在获取位置...'
+ });
+
+ uni.getLocation({
+ type: 'gcj02', // 使用国测局坐标系
+ geocode: true, // 解析地址信息
+ success: (res) => {
+ console.log('📍 获取位置成功:', res);
+
+ const locationData = {
+ latitude: res.latitude,
+ longitude: res.longitude,
+ address: res.address || '位置信息获取中...'
+ };
+
+ // 如果没有地址信息,尝试逆地理编码
+ if (!res.address) {
+ reverseGeocode(res.latitude, res.longitude)
+ .then(address => {
+ locationData.address = address;
+ resolve(locationData);
+ })
+ .catch(() => {
+ locationData.address = `${res.latitude.toFixed(6)}, ${res.longitude.toFixed(6)}`;
+ resolve(locationData);
+ });
+ } else {
+ resolve(locationData);
+ }
+ },
+ fail: (err) => {
+ console.error('❌ 获取位置失败:', err);
+
+ let errorMsg = '获取位置失败';
+ if (err.errMsg) {
+ if (err.errMsg.includes('auth deny')) {
+ errorMsg = '位置权限被拒绝,请在设置中开启位置权限';
+ } else if (err.errMsg.includes('locate fail')) {
+ errorMsg = '定位失败,请检查网络连接和GPS设置';
+ }
+ }
+
+ reject(new Error(errorMsg));
+ },
+ complete: () => {
+ uni.hideLoading();
+ }
+ });
+ });
+};
+
+/**
+ * 逆地理编码 - 根据坐标获取地址
+ * @param {number} latitude 纬度
+ * @param {number} longitude 经度
+ * @returns Promise
+ */
+export const reverseGeocode = (latitude, longitude) => {
+ return new Promise((resolve, reject) => {
+ // 这里可以调用第三方地图服务API进行逆地理编码
+ // 暂时返回坐标信息
+ const address = `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`;
+ resolve(address);
+ });
+};
+
+/**
+ * 检查定位权限
+ * @returns Promise
+ */
+export const checkLocationPermission = () => {
+ return new Promise((resolve) => {
+ // #ifdef APP-PLUS
+ const permissions = ['android.permission.ACCESS_FINE_LOCATION'];
+ plus.android.requestPermissions(permissions, (result) => {
+ resolve(result.granted && result.granted.length > 0);
+ }, (error) => {
+ console.error('权限检查失败:', error);
+ resolve(false);
+ });
+ // #endif
+
+ // #ifndef APP-PLUS
+ resolve(true); // 小程序和H5环境默认有权限
+ // #endif
+ });
+};
+
+/**
+ * 打开位置权限设置
+ */
+export const openLocationSettings = () => {
+ uni.showModal({
+ title: '位置权限',
+ content: '需要位置权限才能进行签到,请在设置中开启位置权限',
+ confirmText: '去设置',
+ success: (res) => {
+ if (res.confirm) {
+ // #ifdef APP-PLUS
+ plus.runtime.openURL('app-settings:');
+ // #endif
+
+ // #ifdef MP-WEIXIN
+ wx.openSetting();
+ // #endif
+ }
+ }
+ });
+};
+
+/**
+ * 计算两点间距离(米)
+ * @param {number} lat1 纬度1
+ * @param {number} lng1 经度1
+ * @param {number} lat2 纬度2
+ * @param {number} lng2 经度2
+ * @returns {number} 距离(米)
+ */
+export const calculateDistance = (lat1, lng1, lat2, lng2) => {
+ const R = 6371000; // 地球半径(米)
+ const dLat = (lat2 - lat1) * Math.PI / 180;
+ const dLng = (lng2 - lng1) * Math.PI / 180;
+ const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
+ Math.sin(dLng / 2) * Math.sin(dLng / 2);
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ return R * c;
+};
\ No newline at end of file