master
lion 2 months ago
parent 1cc5204591
commit ec6d6d08d7

@ -19,23 +19,58 @@ let apiApp = {
courseUpdateSign: "/api/mobile/course/update-sign",
courseContent: '/api/mobile/course/contents',
courseUserList: "/api/mobile/course/user-list",
courseGetSign: "/api/mobile/course/get-sign",
courseGetSign: "/api/mobile/course/get-sign",
myCourseContent:'/api/mobile/course/my-course-content',
courseContentDetail:'/api/mobile/course/course-content-detail',
courseContentForm:'/api/mobile/course/course-content-form',
// 资讯
courseNews: "/api/mobile/course/news",
// 预约
scheduleIndex: '/api/mobile/schedule/index',
scheduleSave: '/api/mobile/schedule/save',
scheduleDetail: '/api/mobile/schedule/detail',
scheduleCheck: '/api/mobile/schedule/check',
scheduleCancel: '/api/mobile/schedule/cancel',
scheduleCheck: '/api/mobile/schedule/check',
scheduleCancel: '/api/mobile/schedule/cancel',
// 其他
otherConfig: '/api/mobile/other/config',
otherBanner: '/api/mobile/other/banner',
otherUploadFile: '/api/mobile/upload-file',
getparameter: '/api/admin/parameter/show',
updateDonates: '/api/mobile/user/update-donates'
updateDonates: '/api/mobile/user/update-donates',
uploadFile:'/api/mobile/upload-file',
// 供应需求
supplyDemandSave: '/api/mobile/supply-demand/save',
supplyDemandList: '/api/mobile/supply-demand/index',
supplyDemandDetail: '/api/mobile/supply-demand/detail',
supplyDemandMessageList: '/api/mobile/supply-demand/message-list',
supplyDemandSendMessage: '/api/mobile/supply-demand/send-message',
supplyDemandDialogues: '/api/mobile/supply-demand/dialogues',
// 图书
bookIndex: '/api/mobile/book/index',
bookDetail: '/api/mobile/book/detail',
bookOther: '/api/mobile/book/other',
// 日历
calendarsGet:'/api/mobile/course/calendars',
// 签到
signGet:'/api/mobile/course/content-check-list',
signCheck:'/api/mobile/course/content-check',
signDistance:'/api/mobile/course/distance',
// 课程签到
courseCheck:'/api/mobile/course/course-check',
// 问卷
courseEvaluationDetail:'/api/mobile/course/evaluation-detail',
// 校友地图
schoolmateCompany:'/api/mobile/other/company-list',
}
// 此处第二个参数vm就是我们在页面使用的this你可以通过vm获取vuex等操作
@ -59,8 +94,11 @@ const install = (Vue, vm) => {
let courseUpdateSign = (params = {}) => vm.$u.post(apiApp.courseUpdateSign, params);
let courseContent = (params = {}) => vm.$u.get(apiApp.courseContent, params);
let courseUserList = (params = {}) => vm.$u.get(apiApp.courseUserList, params);
let courseGetSign = (params = {}) => vm.$u.get(apiApp.courseGetSign, params);
let myCourseContent = (params = {}) => vm.$u.get(apiApp.myCourseContent, params);
let courseGetSign = (params = {}) => vm.$u.get(apiApp.courseGetSign, params);
let myCourseContent = (params = {}) => vm.$u.get(apiApp.myCourseContent, params);
let courseContentDetail = (params = {}) => vm.$u.get(apiApp.courseContentDetail, params);
let courseContentForm = (params = {}) => vm.$u.post(apiApp.courseContentForm, params);
// 资讯
@ -70,9 +108,9 @@ const install = (Vue, vm) => {
let scheduleIndex = (params = {}) => vm.$u.get(apiApp.scheduleIndex, params);
let scheduleSave = (params = {}) => vm.$u.post(apiApp.scheduleSave, params);
let scheduleDetail = (params = {}) => vm.$u.get(apiApp.scheduleDetail, params);
let scheduleCheck = (params = {}) => vm.$u.get(apiApp.scheduleCheck, params);
let scheduleCancel = (params = {}) => vm.$u.get(apiApp.scheduleCancel, params);
let scheduleCheck = (params = {}) => vm.$u.get(apiApp.scheduleCheck, params);
let scheduleCancel = (params = {}) => vm.$u.get(apiApp.scheduleCancel, params);
// 其他
@ -81,7 +119,33 @@ const install = (Vue, vm) => {
let otherUploadFile = (params = {}) => vm.$u.post(apiApp.otherUploadFile, params);
let getparameter = (params = {}) => vm.$u.get(apiApp.getparameter, params);
let updateDonates = (params = {}) => vm.$u.post(apiApp.updateDonates, params);
let uploadFile = (params = {}) => vm.$u.post(apiApp.uploadFile, params);
// 供应需求
let supplyDemandSave = (params = {}) => vm.$u.post(apiApp.supplyDemandSave, params);
let supplyDemandList = (params = {}) => vm.$u.get(apiApp.supplyDemandList, params);
let supplyDemandDetail = (params = {}) => vm.$u.get(apiApp.supplyDemandDetail, params);
let supplyDemandMessageList = (params = {}) => vm.$u.get(apiApp.supplyDemandMessageList, params);
let supplyDemandSendMessage = (params = {}) => vm.$u.post(apiApp.supplyDemandSendMessage, params);
let supplyDemandDialogues = (params = {}) => vm.$u.get(apiApp.supplyDemandDialogues, params);
// 图书
let bookIndex = (params = {}) => vm.$u.get(apiApp.bookIndex, params);
let bookDetail = (params = {}) => vm.$u.get(apiApp.bookDetail, params);
let bookOther = (params = {}) => vm.$u.get(apiApp.bookOther, params);
// 日历
let calendarsGet = (params = {}) => vm.$u.get(apiApp.calendarsGet, params);
// 签到
let signGet = (params = {}) => vm.$u.get(apiApp.signGet, params);
let signCheck = (params = {}) => vm.$u.get(apiApp.signCheck, params);
let signDistance = (params = {}) => vm.$u.get(apiApp.signDistance, params);
let courseCheck = (params = {}) => vm.$u.get(apiApp.courseCheck, params);
// 问卷
let courseEvaluationDetail = (params = {}) => vm.$u.get(apiApp.courseEvaluationDetail, params);
// 校友地图
let schoolmateCompany = (params = {}) => vm.$u.get(apiApp.schoolmateCompany, params);
// 将各个定义的接口名称统一放进对象挂载到vm.$u.api(因为vm就是this也即this.$u.api)下
vm.$u.api = {
// 用户
@ -103,13 +167,15 @@ const install = (Vue, vm) => {
courseUpdateSign,
courseContent,
courseUserList,
courseGetSign,
courseGetSign,
myCourseContent,
courseContentDetail,
courseContentForm,
// 预约
scheduleIndex,
scheduleSave,
scheduleDetail,
scheduleCheck,
scheduleCheck,
scheduleCancel,
// 资讯
courseNews,
@ -117,8 +183,33 @@ const install = (Vue, vm) => {
otherConfig,
otherBanner,
otherUploadFile,
getparameter: getparameter,
updateDonates:updateDonates
getparameter: getparameter,
updateDonates:updateDonates,
uploadFile:uploadFile,
// 供应需求
supplyDemandSave,
supplyDemandList,
supplyDemandDetail,
supplyDemandMessageList,
supplyDemandSendMessage,
supplyDemandDialogues,
// 图书
bookIndex,
bookDetail,
bookOther,
// 日历
calendarsGet,
// 签到
signGet,
signCheck,
signDistance,
courseCheck,
// 问卷
courseEvaluationDetail,
// 校友地图
schoolmateCompany,
};
}

@ -37,7 +37,7 @@ async function getToken() {
resolve(result.data.token);
},
fail(err) {
console.log("login-error",err)
console.log("login-error",err)
reject(err);
}
@ -61,9 +61,13 @@ async function getUserInfo(tokenResult) {
'vuex_token': tokenResult,
"vuex_user": result1.data.user
})
uni.reLaunch({
url: '/pages/index/index'
});
// 登录成功后跳回之前的页面(带参),若无则回首页
const redirect = uni.getStorageSync('redirect_after_login')
uni.removeStorageSync('redirect_after_login')
const target = redirect || '/pages/index/index'
uni.reLaunch({
url: target
});
},
fail(err) {
console.log("uesr-error",err)
@ -104,7 +108,23 @@ const install = (Vue, vm) => {
console.log('res-http', res)
if (res.statusCode === 200) {
if (res.data.hasOwnProperty("errcode")) {
if (res.data?.errcode === 40001) {
if (res.data?.errcode === 40001) {
// 记录当前页面完整路径,登录成功后跳回
try {
const pages = getCurrentPages()
if (pages && pages.length) {
const current = pages[pages.length - 1]
const route = current.route || current.$page?.fullPath || ''
let url = '/' + route
if (current.options) {
const qs = Object.keys(current.options)
.map(k => `${k}=${encodeURIComponent(current.options[k])}`)
.join('&')
if (qs) url += '?' + qs
}
uni.setStorageSync('redirect_after_login', url)
}
} catch (e) {}
uni.showModal({
title: '用户信息已失效',
confirmText: '重新获取',

@ -0,0 +1,684 @@
<template>
<view class="checkin-page">
<view class="checkin-container">
<!-- 课程信息卡片 -->
<view class="course-card">
<view class="course-title">{{ course.name?course.name:'' }}</view>
<view class="course-info">
<view class="info-item">
<u-icon name="calendar-fill" class="info-icon"></u-icon>
<text>开始日期{{ course.start_date?course.start_date:'' }}</text>
</view>
<view class="info-item">
<u-icon name="calendar-fill" class="info-icon"></u-icon>
<text>结束日期{{ course.end_date?course.end_date:'' }}</text>
</view>
<view class="info-item">
<u-icon name="map-fill" class="info-icon"></u-icon>
<text>{{ course.address_detail?course.address_detail:'' }}</text>
</view>
</view>
</view>
<!-- 状态检测卡片 -->
<view class="status-card">
<h6 class="card-title">
<u-icon name="shield-checkmark" class="title-icon"></u-icon>
签到状态检测
</h6>
<view class="status-item">
<text class="status-label">定位状态</text>
<view :class="['status-value', 'status-' + locationStatus.type]">
<u-icon :name="getStatusIcon(locationStatus.type)"></u-icon>
<text>{{ locationStatus.text }}</text>
</view>
</view>
<view class="status-item">
<text class="status-label">打卡范围</text>
<view :class="['status-value', 'status-' + rangeStatus.type]">
<u-icon :name="getStatusIcon(rangeStatus.type)"></u-icon>
<text>{{ rangeStatus.text }}</text>
</view>
</view>
</view>
<!-- 距离信息 -->
<view v-if="distance !== null" class="distance-info">
<view class="distance-value">{{ formattedDistance }}</view>
<view class="distance-label">距离课程地点</view>
</view>
<!-- 提示信息 -->
<view v-if="alertInfo.message" :class="['alert-custom', 'alert-' + alertInfo.type]">
<u-icon :name="getStatusIcon(alertInfo.type)" class="alert-icon"></u-icon>
<text>{{ alertInfo.message }}</text>
</view>
<!-- 签到操作 -->
<view class="checkin-actions">
<u-button
type="primary"
class="checkin-btn"
:disabled="!canCheckin"
@click="performCheckin"
>
<u-icon name="map" class="btn-icon"></u-icon>
{{ hasCheckedIn ? '已签到' : '位置签到' }}
</u-button>
<u-button
type="success"
class="recheck-btn"
@click="handleRecheckClick"
>
<u-icon :name="hasLocationPermission ? 'reload' : 'location'" class="btn-icon"></u-icon>
{{ hasLocationPermission ? '重新定位' : '获取定位权限' }}
</u-button>
</view>
<!-- 签到记录 -->
<view class="status-card">
<h6 class="card-title">
<u-icon name="order" class="title-icon"></u-icon>
签到记录
</h6>
<view v-if="checkinHistory.length === 0" class="history-empty">
暂无签到记录
</view>
<view v-else class="history-list">
<view v-for="(record, index) in checkinHistory" :key="index" class="history-item">
<text>签到时间: {{ record.created_at }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
course_id:'',
course: {},
userLocation: null,
distance: null,
canCheckin: false,
hasCheckedIn: false,
locationStatus: { type: 'default', text: '未定位' },
rangeStatus: { type: 'default', text: '未计算' },
alertInfo: { type: '', message: '' },
checkinHistory: [],
hasLocationPermission: false,
//
coordinateConfig: {
// 'wgs84' 'gcj02'
backendSystem: 'wgs84',
//
enableConversion: true
}
};
},
computed: {
formattedDistance() {
const d = this.distance
if (d == null) return ''
// >= 1km km< 1km
if (d >= 1000) return `${Math.round(d / 1000)}km`
return `${d}m`
}
},
onLoad(options) {
console.log("页面加载options:", options)
this.course_id = options?.course_id
//
this.testCoordinateConversion()
//
console.log("开始检查权限...")
this.checkPermissionAndInit();
},
methods: {
async checkPermissionAndInit() {
console.log('开始检查权限...')
let granted = false
try {
const setting = await new Promise((resolve) => {
uni.getSetting({ success: resolve, fail: () => resolve({}) })
})
console.log('获取设置结果:', setting)
const auth = setting?.authSetting || {}
console.log('权限设置:', auth)
granted = !!auth['scope.userLocation']
this.hasLocationPermission = granted
console.log('权限检测结果:', granted, 'hasLocationPermission:', this.hasLocationPermission)
} catch (e) {
console.log('权限检测异常:', e)
this.hasLocationPermission = false
}
//
console.log('开始获取课程信息...')
await this.getCourse()
await this.signGet()
if (!granted) {
console.log('无权限,设置状态为未获取定位权限')
this.locationStatus = { type: 'warning', text: '未获取定位权限' }
this.rangeStatus = { type: 'default', text: '未计算' }
this.canCheckin = false
// 使
return
}
//
console.log('有权限,开始完整签到流程')
await this.initializeCheckin()
},
handleRecheckClick() {
console.log('按钮被点击,当前权限状态:', this.hasLocationPermission)
if (this.hasLocationPermission) {
console.log('有权限,执行重新定位')
this.initializeCheckin()
} else {
console.log('无权限,请求定位权限')
this.requestLocationPermission()
}
},
requestLocationPermission() {
console.log('开始请求定位权限...')
uni.authorize({
scope: 'scope.userLocation',
success: () => {
console.log('定位权限授权成功')
this.hasLocationPermission = true
this.locationStatus = { type: 'warning', text: '定位中...' }
this.initializeCheckin()
},
fail: (err) => {
console.log('定位权限授权失败:', err)
uni.openSetting({
success: (res) => {
const ok = res?.authSetting && res.authSetting['scope.userLocation']
if (ok) {
console.log('通过设置页面获取定位权限成功')
this.hasLocationPermission = true
this.locationStatus = { type: 'warning', text: '定位中...' }
this.initializeCheckin()
}
}
})
}
})
},
// Promise
// uni.getLocation
getLocation() {
return new Promise((resolve, reject) => {
// const candidates = [
// { lat: 31.401992, lng: 120.686876 },
// { lat: 32.899321, lng: 120.682145 },
// { lat: 31.272715, lng: 120.670337 }
// ]
// const idx = Math.floor(Math.random() * candidates.length)
// resolve(candidates[idx])
//
uni.getLocation({
// type: 'gcj02',
success: (res) => resolve({ lat: res.latitude, lng: res.longitude }),
fail: (err) => reject(err)
})
})
},
//
async getCourse() {
const res = await this.$u.api.courseDetail({
course_id: this.course_id
});
const data = res || {}
// latitude/longitude lat/lng
let lat = data.latitude || data.lat
let lng = data.longitude || data.lng
//
let convertedLng = lng
let convertedLat = lat
if (this.coordinateConfig.enableConversion && this.coordinateConfig.backendSystem !== 'gcj02') {
const [converted] = this.convertCoordinates(lng, lat, this.coordinateConfig.backendSystem, 'gcj02')
convertedLng = converted[0]
convertedLat = converted[1]
}
console.log('原始坐标:', { lng, lat, system: this.coordinateConfig.backendSystem })
console.log('转换后坐标:', { lng: convertedLng, lat: convertedLat, system: 'gcj02' })
this.course = {
...this.course,
...data,
location: {
lat: convertedLat,
lng: convertedLng,
originalLat: lat,
originalLng: lng,
coordinateSystem: 'gcj02'
},
allowedDistance: 500
}
},
//
async signGet() {
const res = await this.$u.api.signGet({
course_id: this.course_id
});
const rows = res?.list || []
this.checkinHistory = Array.isArray(rows) ? rows : []
if (this.checkinHistory.length > 0) {
this.hasCheckedIn = true
this.canCheckin = false
this.alertInfo = { type: 'warning', message: '已存在签到记录,不能重复打卡' }
}
},
// signDistance + calculateDistance
async signDistance() {
let distance = null
let allowed = this.course.allowedDistance || 500
try {
// GCJ-02uni.getLocation
const userLat = this.userLocation.lat
const userLng = this.userLocation.lng
console.log('用户位置坐标:', { lat: userLat, lng: userLng, system: 'gcj02' })
console.log('课程位置坐标:', {
lat: this.course.location.lat,
lng: this.course.location.lng,
system: this.course.location.coordinateSystem
})
const res = await this.$u.api.signDistance({
course_id: this.course_id,
latitude: userLat,
longitude: userLng
})
const data = res || {}
// km m
if (data && data.distance != null && !isNaN(parseFloat(data.distance))) {
const km = parseFloat(data.distance)
distance = Math.round(km * 1000)
}
if (data && data.content_check_range != null && !isNaN(parseFloat(data.content_check_range))) {
const kmRange = parseFloat(data.content_check_range)
allowed = Math.round(kmRange * 1000)
}
} catch (e) {}
if (distance === null) {
const distLocal = this.getDistanceFromLatLonInM(
this.userLocation.lat, this.userLocation.lng,
this.course.location.lat, this.course.location.lng
)
distance = Math.round(distLocal)
}
this.distance = distance
this.course.allowedDistance = allowed
if (this.distance <= allowed) {
this.rangeStatus = { type: 'success', text: '在打卡范围内' };
this.canCheckin = !this.hasCheckedIn;
this.alertInfo = this.hasCheckedIn
? { type: 'warning', message: '已存在签到记录,不能重复打卡' }
: { type: 'success', message: `您已进入打卡范围,距离${this.formatDistanceVal(this.distance)}` };
} else {
const over = Math.max(0, this.distance - allowed)
this.rangeStatus = { type: 'error', text: `超出范围约${this.formatDistanceVal(over)}` };
this.canCheckin = false;
this.alertInfo = { type: 'warning', message: `您需要进入${this.formatDistanceVal(allowed)}范围内才能签到。` };
}
},
//
async signCheck() {
try {
const res = await this.$u.api.courseCheck({
course_id: this.course_id,
latitude: this.userLocation.lat,
longitude: this.userLocation.lng
});
//
this.hasCheckedIn = true
this.canCheckin = false
this.alertInfo = { type: 'success', message: '签到成功' }
uni.showToast({ title: '签到成功!', icon: 'success' });
//
try { await this.signGet() } catch (e) {}
} catch (e) {
console.error('签到失败:', e);
uni.showToast({ title: '签到失败', icon: 'none' });
}
},
async initializeCheckin() {
this.locationStatus = { type: 'warning', text: '定位中...' };
this.rangeStatus = { type: 'default', text: '未计算' };
this.canCheckin = false;
this.distance = null;
this.alertInfo = { type: '', message: '' };
try {
// 1.
const loc = await this.getLocation()
console.log(loc)
this.userLocation = loc
this.locationStatus = { type: 'success', text: '定位成功' };
// 2.
await this.signDistance()
} catch (e) {
this.locationStatus = { type: 'error', text: '定位失败' };
this.rangeStatus = { type: 'error', text: '无法计算' };
this.alertInfo = { type: 'error', message: '获取信息或位置失败,请检查权限和网络。' };
}
},
//
// WGS84 GCJ-02 ()
wgs84ToGcj02(lng, lat) {
const a = 6378245.0;
const ee = 0.00669342162296594323;
if (this.outOfChina(lng, lat)) {
return [lng, lat];
}
let dLat = this.transformLat(lng - 105.0, lat - 35.0);
let dLng = this.transformLng(lng - 105.0, lat - 35.0);
const radLat = lat / 180.0 * Math.PI;
let magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
return [lng + dLng, lat + dLat];
},
// GCJ-02 WGS84
gcj02ToWgs84(lng, lat) {
if (this.outOfChina(lng, lat)) {
return [lng, lat];
}
let dLat = this.transformLat(lng - 105.0, lat - 35.0);
let dLng = this.transformLng(lng - 105.0, lat - 35.0);
const radLat = lat / 180.0 * Math.PI;
let magic = Math.sin(radLat);
magic = 1 - 0.00669342162296594323 * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((6378245.0 * (1 - 0.00669342162296594323)) / (magic * sqrtMagic) * Math.PI);
dLng = (dLng * 180.0) / (6378245.0 / sqrtMagic * Math.cos(radLat) * Math.PI);
return [lng - dLng, lat - dLat];
},
//
outOfChina(lng, lat) {
return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);
},
//
transformLat(lng, lat) {
let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 * Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320 * Math.sin(lat * Math.PI / 30.0)) * 2.0 / 3.0;
return ret;
},
//
transformLng(lng, lat) {
let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lat));
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * Math.PI) + 40.0 * Math.sin(lng / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * Math.PI) + 300.0 * Math.sin(lng / 30.0 * Math.PI)) * 2.0 / 3.0;
return ret;
},
//
convertCoordinates(lng, lat, fromSystem = 'gcj02', toSystem = 'gcj02') {
if (fromSystem === toSystem) {
return [lng, lat];
}
if (fromSystem === 'wgs84' && toSystem === 'gcj02') {
return this.wgs84ToGcj02(lng, lat);
}
if (fromSystem === 'gcj02' && toSystem === 'wgs84') {
return this.gcj02ToWgs84(lng, lat);
}
// WGS84GCJ-02
if (fromSystem === 'wgs84') {
const gcj02 = this.wgs84ToGcj02(lng, lat);
return this.convertCoordinates(gcj02[0], gcj02[1], 'gcj02', toSystem);
}
return [lng, lat];
},
//
testCoordinateConversion() {
//
const wgs84Lng = 116.397128
const wgs84Lat = 39.916527
console.log('=== 坐标转换测试 ===')
console.log('WGS84坐标:', { lng: wgs84Lng, lat: wgs84Lat })
const gcj02 = this.wgs84ToGcj02(wgs84Lng, wgs84Lat)
console.log('转换为GCJ-02:', { lng: gcj02[0], lat: gcj02[1] })
const backToWgs84 = this.gcj02ToWgs84(gcj02[0], gcj02[1])
console.log('转换回WGS84:', { lng: backToWgs84[0], lat: backToWgs84[1] })
const diff = {
lng: Math.abs(backToWgs84[0] - wgs84Lng),
lat: Math.abs(backToWgs84[1] - wgs84Lat)
}
console.log('转换精度误差:', diff)
console.log('=== 测试结束 ===')
},
// calculateDistance signDistance
getDistanceFromLatLonInM(lat1, lon1, lat2, lon2) {
const R = 6371;
const dLat = this.deg2rad(lat2 - lat1);
const dLon = this.deg2rad(lon2 - lon1);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.deg2rad(lat1)) * Math.cos(this.deg2rad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c * 1000;
},
deg2rad(deg) {
return deg * (Math.PI / 180);
},
formatDistanceVal(m) {
if (m == null) return ''
const n = Number(m)
if (isNaN(n)) return ''
if (n >= 1000) return `${Math.round(n / 1000)}km`
return `${Math.round(n)}m`
},
getStatusIcon(type) {
const icons = {
success: 'checkmark-circle-fill',
error: 'close-circle-fill',
warning: 'error-circle-fill',
default: 'question-circle-fill'
};
return icons[type] || 'question-circle-fill';
},
async performCheckin() {
if (this.hasCheckedIn) {
uni.showToast({ title: '您今天已经签过到了', icon: 'none' });
return;
}
if (!this.canCheckin) return
//
await this.signCheck();
},
loadCheckinHistory() {
const records = uni.getStorageSync('checkinHistory') || [];
this.checkinHistory = records;
if (records.length > 0) {
const lastRecordTime = new Date(records[0].time);
if (lastRecordTime.toDateString() === new Date().toDateString()) {
this.hasCheckedIn = true;
}
}
}
}
};
</script>
<style scoped>
.checkin-page {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20rpx;
}
.checkin-container {
max-width: 400px;
margin: 0 auto;
}
.course-card, .status-card {
background: white;
border-radius: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.1);
padding: 32rpx;
margin-bottom: 30rpx;
}
.course-title {
font-size: 36rpx;
font-weight: 600;
text-align: center;
margin-bottom: 24rpx;
}
.course-info {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.info-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 12rpx;
background: #f8f9fa;
border-radius: 16rpx;
font-size: 26rpx;
}
.info-icon {
color: #3498db;
}
.card-title {
display: flex;
align-items: center;
gap: 12rpx;
font-size: 30rpx;
font-weight: 600;
margin-bottom: 16rpx;
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #e9ecef;
font-size: 28rpx;
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
font-weight: 500;
}
.status-value {
display: flex;
align-items: center;
gap: 8rpx;
}
.status-success { color: #2ecc71; }
.status-error { color: #e74c3c; }
.status-warning { color: #f1c40f; }
.status-default { color: #909399; }
.distance-info {
text-align: center;
padding: 24rpx;
background: #fff;
border-radius: 32rpx;
margin-bottom: 24rpx;
}
.distance-value {
font-size: 48rpx;
font-weight: 700;
color: #3498db;
}
.distance-label {
font-size: 26rpx;
color: #6c757d;
margin-top: 8rpx;
}
.alert-custom {
border-radius: 24rpx;
padding: 24rpx;
margin-bottom: 24rpx;
display: flex;
align-items: center;
gap: 16rpx;
font-size: 26rpx;
color: #fff;
}
.alert-success { background: #2ecc71; }
.alert-error { background: #e74c3c; }
.alert-warning { background: #f1c40f; }
.checkin-actions {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-bottom: 30rpx;
}
.checkin-btn /deep/ .u-btn, .recheck-btn /deep/ .u-btn {
height: 90rpx;
}
.btn-icon {
margin-right: 12rpx;
}
.history-empty {
text-align: center;
color: #999;
padding: 30rpx 0;
font-size: 26rpx;
}
.history-list {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.history-item {
display: flex;
justify-content: space-between;
font-size: 24rpx;
color: #666;
background: #f8f9fa;
padding: 12rpx;
border-radius: 12rpx;
}
</style>

@ -0,0 +1,784 @@
<template>
<view class="checkin-page">
<view class="checkin-container">
<!-- 课程信息卡片 -->
<view class="course-card">
<view class="course-title">{{ course.theme?course.theme:'' }}</view>
<view class="course-info">
<view class="info-item">
<u-icon name="calendar-fill" class="info-icon"></u-icon>
<text>{{ course.date?course.date:'' }} - {{ course.period?course.period:'' }}</text>
</view>
<view class="info-item">
<u-icon name="map-fill" class="info-icon"></u-icon>
<text>{{ course.address?course.address:'' }}</text>
</view>
<view class="info-item">
<u-icon name="account-fill" class="info-icon"></u-icon>
<text>{{ course.teacher?course.teacher.name:'' }}</text>
</view>
</view>
</view>
<!-- 当前其他可签到课程 -->
<view class="status-card" v-if="courseContentList.length > 0">
<h6 class="card-title">
<u-icon name="order" class="title-icon"></u-icon>
当前其他可签到课程
</h6>
<view v-for="item in courseContentList" :key="item.id" class="course-card">
<view class="course-title">{{ item.theme || '' }}</view>
<view class="course-info">
<view class="info-item" v-if="item.date || item.period">
<u-icon name="calendar-fill" class="info-icon"></u-icon>
<text>{{ item.date || '' }} - {{ item.period || '' }}</text>
</view>
<view class="info-item" v-if="item.address">
<u-icon name="map-fill" class="info-icon"></u-icon>
<text>{{ item.address }}</text>
</view>
<view class="info-item" v-if="item.teacher && item.teacher.name">
<u-icon name="account-fill" class="info-icon"></u-icon>
<text>{{ item.teacher.name }}</text>
</view>
</view>
</view>
</view>
<!-- 状态检测卡片 -->
<view class="status-card">
<h6 class="card-title">
<u-icon name="shield-checkmark" class="title-icon"></u-icon>
签到状态检测
</h6>
<view class="status-item">
<text class="status-label">定位状态</text>
<view :class="['status-value', 'status-' + locationStatus.type]">
<u-icon :name="getStatusIcon(locationStatus.type)"></u-icon>
<text>{{ locationStatus.text }}</text>
</view>
</view>
<view class="status-item">
<text class="status-label">打卡范围</text>
<view :class="['status-value', 'status-' + rangeStatus.type]">
<u-icon :name="getStatusIcon(rangeStatus.type)"></u-icon>
<text>{{ rangeStatus.text }}</text>
</view>
</view>
</view>
<!-- 距离信息 -->
<view v-if="distance !== null" class="distance-info">
<view class="distance-value">{{ formattedDistance }}</view>
<view class="distance-label">距离课程地点</view>
</view>
<!-- 提示信息 -->
<view v-if="alertInfo.message" :class="['alert-custom', 'alert-' + alertInfo.type]">
<u-icon :name="getStatusIcon(alertInfo.type)" class="alert-icon"></u-icon>
<text>{{ alertInfo.message }}</text>
</view>
<!-- 签到操作 -->
<view class="checkin-actions">
<u-button
type="primary"
class="checkin-btn"
:disabled="!canCheckin"
@click="performCheckin"
>
<u-icon name="map" class="btn-icon"></u-icon>
{{ hasCheckedIn ? '已签到' : '位置签到' }}
</u-button>
<u-button
type="success"
class="recheck-btn"
@click="handleRecheckClick"
>
<u-icon :name="hasLocationPermission ? 'reload' : 'location'" class="btn-icon"></u-icon>
{{ hasLocationPermission ? '重新定位' : '获取定位权限' }}
</u-button>
</view>
<!-- 签到记录 -->
<view class="status-card">
<h6 class="card-title">
<u-icon name="order" class="title-icon"></u-icon>
签到记录
</h6>
<view v-if="checkinHistory.length === 0" class="history-empty">
暂无签到记录
</view>
<view v-else class="history-list">
<view v-for="(record, index) in checkinHistory" :key="index" class="history-item">
<text>签到时间: {{ record.created_at }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
courseContentList:[],
course_content_id:'',
course: {},
userLocation: null,
distance: null,
canCheckin: false,
hasCheckedIn: false,
locationStatus: { type: 'default', text: '未定位' },
rangeStatus: { type: 'default', text: '未计算' },
alertInfo: { type: '', message: '' },
checkinHistory: [],
hasLocationPermission: false,
//
coordinateConfig: {
// 'wgs84' 'gcj02'
backendSystem: 'wgs84',
//
enableConversion: true
}
};
},
computed: {
formattedDistance() {
const d = this.distance
if (d == null) return ''
// >= 1km km< 1km
if (d >= 1000) return `${Math.round(d / 1000)}km`
return `${d}m`
}
},
onLoad(options) {
console.log("页面加载options:", options)
this.course_content_id = options?.course_content_id
//
this.testCoordinateConversion()
//
console.log("开始检查权限...")
this.checkPermissionAndInit();
},
methods: {
//
async getCourseList() {
const res = await this.$u.api.myCourseContent({
date: this.course.date
});
//
if (!res.list || res.list.length === 0) {
//
this.canCheckin = false;
this.courseContentList = [];
return;
}
this.courseContentList = res.list.filter(item => item.id != this.course.id)
},
async checkPermissionAndInit() {
console.log('开始检查权限...')
let granted = false
try {
const setting = await new Promise((resolve) => {
uni.getSetting({ success: resolve, fail: () => resolve({}) })
})
console.log('获取设置结果:', setting)
const auth = setting?.authSetting || {}
console.log('权限设置:', auth)
granted = !!auth['scope.userLocation']
this.hasLocationPermission = granted
console.log('权限检测结果:', granted, 'hasLocationPermission:', this.hasLocationPermission)
} catch (e) {
console.log('权限检测异常:', e)
this.hasLocationPermission = false
}
//
console.log('开始获取课程信息...')
await this.getCourse()
await this.signGet()
if (!granted) {
console.log('无权限,设置状态为未获取定位权限')
this.locationStatus = { type: 'warning', text: '未获取定位权限' }
this.rangeStatus = { type: 'default', text: '未计算' }
this.canCheckin = false
// 使
return
}
//
console.log('有权限,开始完整签到流程')
await this.initializeCheckin()
},
handleRecheckClick() {
console.log('按钮被点击,当前权限状态:', this.hasLocationPermission)
if (this.hasLocationPermission) {
console.log('有权限,执行重新定位')
this.initializeCheckin()
} else {
console.log('无权限,请求定位权限')
this.requestLocationPermission()
}
},
requestLocationPermission() {
console.log('开始请求定位权限...')
uni.authorize({
scope: 'scope.userLocation',
success: () => {
console.log('定位权限授权成功')
this.hasLocationPermission = true
this.locationStatus = { type: 'warning', text: '定位中...' }
this.initializeCheckin()
},
fail: (err) => {
console.log('定位权限授权失败:', err)
uni.openSetting({
success: (res) => {
const ok = res?.authSetting && res.authSetting['scope.userLocation']
if (ok) {
console.log('通过设置页面获取定位权限成功')
this.hasLocationPermission = true
this.locationStatus = { type: 'warning', text: '定位中...' }
this.initializeCheckin()
}
}
})
}
})
},
// Promise
// uni.getLocation
getLocation() {
return new Promise((resolve, reject) => {
// const candidates = [
// { lat: 31.401992, lng: 120.686876 },
// { lat: 32.899321, lng: 120.682145 },
// { lat: 31.272715, lng: 120.670337 }
// ]
// const idx = Math.floor(Math.random() * candidates.length)
// resolve(candidates[idx])
//
uni.getLocation({
// type: 'gcj02',
success: (res) => resolve({ lat: res.latitude, lng: res.longitude }),
fail: (err) => reject(err)
})
})
},
//
async getCourse() {
const res = await this.$u.api.courseContentDetail({
course_content_id: this.course_content_id
});
const data = res || {}
// latitude/longitude lat/lng
let lat = data.latitude || data.lat
let lng = data.longitude || data.lng
//
let convertedLng = lng
let convertedLat = lat
if (this.coordinateConfig.enableConversion && this.coordinateConfig.backendSystem !== 'gcj02') {
const [converted] = this.convertCoordinates(lng, lat, this.coordinateConfig.backendSystem, 'gcj02')
convertedLng = converted[0]
convertedLat = converted[1]
}
console.log('原始坐标:', { lng, lat, system: this.coordinateConfig.backendSystem })
console.log('转换后坐标:', { lng: convertedLng, lat: convertedLat, system: 'gcj02' })
this.course = {
...this.course,
...data,
location: {
lat: convertedLat,
lng: convertedLng,
originalLat: lat,
originalLng: lng,
coordinateSystem: 'gcj02'
},
allowedDistance: 500
}
this.getCourseList()
},
//
async signGet() {
const res = await this.$u.api.signGet({
course_content_id: this.course_content_id
});
const rows = res?.list || []
this.checkinHistory = Array.isArray(rows) ? rows : []
if (this.checkinHistory.length > 0) {
this.hasCheckedIn = true
this.canCheckin = false
this.alertInfo = { type: 'warning', message: '已存在签到记录,不能重复打卡' }
}
},
// signDistance + calculateDistance
async signDistance() {
let distance = null
let allowed = this.course.allowedDistance || 500
try {
// GCJ-02uni.getLocation
const userLat = this.userLocation.lat
const userLng = this.userLocation.lng
console.log('用户位置坐标:', { lat: userLat, lng: userLng, system: 'gcj02' })
console.log('课程位置坐标:', {
lat: this.course.location.lat,
lng: this.course.location.lng,
system: this.course.location.coordinateSystem
})
const res = await this.$u.api.signDistance({
course_content_id: this.course_content_id,
latitude: userLat,
longitude: userLng
})
const data = res || {}
// km m
if (data && data.distance != null && !isNaN(parseFloat(data.distance))) {
const km = parseFloat(data.distance)
distance = Math.round(km * 1000)
}
if (data && data.content_check_range != null && !isNaN(parseFloat(data.content_check_range))) {
const kmRange = parseFloat(data.content_check_range)
allowed = Math.round(kmRange * 1000)
}
} catch (e) {}
if (distance === null) {
const distLocal = this.getDistanceFromLatLonInM(
this.userLocation.lat, this.userLocation.lng,
this.course.location.lat, this.course.location.lng
)
distance = Math.round(distLocal)
}
this.distance = distance
this.course.allowedDistance = allowed
if (this.distance <= allowed) {
this.rangeStatus = { type: 'success', text: '在打卡范围内' };
this.canCheckin = !this.hasCheckedIn;
this.alertInfo = this.hasCheckedIn
? { type: 'warning', message: '已存在签到记录,不能重复打卡' }
: { type: 'success', message: `您已进入打卡范围,距离${this.formatDistanceVal(this.distance)}` };
} else {
const over = Math.max(0, this.distance - allowed)
this.rangeStatus = { type: 'error', text: `超出范围约${this.formatDistanceVal(over)}` };
this.canCheckin = false;
this.alertInfo = { type: 'warning', message: `您需要进入${this.formatDistanceVal(allowed)}范围内才能签到。` };
}
},
//
async signCheck(batch_sign = 0) {
try {
const res = await this.$u.api.signCheck({
course_content_id: this.course_content_id,
latitude: this.userLocation.lat,
longitude: this.userLocation.lng,
batch_sign: batch_sign //
});
//
this.hasCheckedIn = true
this.canCheckin = false
//
if (batch_sign === 1) {
this.alertInfo = { type: 'success', message: '批量签到成功!已签到所有课程' }
uni.showToast({ title: '批量签到成功!', icon: 'success' });
} else {
this.alertInfo = { type: 'success', message: '签到成功' }
uni.showToast({ title: '签到成功!', icon: 'success' });
}
//
try { await this.signGet() } catch (e) {}
} catch (e) {
console.error('签到失败:', e);
uni.showToast({ title: '签到失败', icon: 'none' });
}
},
async initializeCheckin() {
this.locationStatus = { type: 'warning', text: '定位中...' };
this.rangeStatus = { type: 'default', text: '未计算' };
this.canCheckin = false;
this.distance = null;
this.alertInfo = { type: '', message: '' };
try {
// 1.
const loc = await this.getLocation()
console.log(loc)
this.userLocation = loc
this.locationStatus = { type: 'success', text: '定位成功' };
// 2.
await this.signDistance()
} catch (e) {
this.locationStatus = { type: 'error', text: '定位失败' };
this.rangeStatus = { type: 'error', text: '无法计算' };
this.alertInfo = { type: 'error', message: '获取信息或位置失败,请检查权限和网络。' };
}
},
//
// WGS84 GCJ-02 ()
wgs84ToGcj02(lng, lat) {
const a = 6378245.0;
const ee = 0.00669342162296594323;
if (this.outOfChina(lng, lat)) {
return [lng, lat];
}
let dLat = this.transformLat(lng - 105.0, lat - 35.0);
let dLng = this.transformLng(lng - 105.0, lat - 35.0);
const radLat = lat / 180.0 * Math.PI;
let magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI);
return [lng + dLng, lat + dLat];
},
// GCJ-02 WGS84
gcj02ToWgs84(lng, lat) {
if (this.outOfChina(lng, lat)) {
return [lng, lat];
}
let dLat = this.transformLat(lng - 105.0, lat - 35.0);
let dLng = this.transformLng(lng - 105.0, lat - 35.0);
const radLat = lat / 180.0 * Math.PI;
let magic = Math.sin(radLat);
magic = 1 - 0.00669342162296594323 * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((6378245.0 * (1 - 0.00669342162296594323)) / (magic * sqrtMagic) * Math.PI);
dLng = (dLng * 180.0) / (6378245.0 / sqrtMagic * Math.cos(radLat) * Math.PI);
return [lng - dLng, lat - dLat];
},
//
outOfChina(lng, lat) {
return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55);
},
//
transformLat(lng, lat) {
let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * Math.PI) + 40.0 * Math.sin(lat / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * Math.PI) + 320 * Math.sin(lat * Math.PI / 30.0)) * 2.0 / 3.0;
return ret;
},
//
transformLng(lng, lat) {
let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lat));
ret += (20.0 * Math.sin(6.0 * lng * Math.PI) + 20.0 * Math.sin(2.0 * lng * Math.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * Math.PI) + 40.0 * Math.sin(lng / 3.0 * Math.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * Math.PI) + 300.0 * Math.sin(lng / 30.0 * Math.PI)) * 2.0 / 3.0;
return ret;
},
//
convertCoordinates(lng, lat, fromSystem = 'gcj02', toSystem = 'gcj02') {
if (fromSystem === toSystem) {
return [lng, lat];
}
if (fromSystem === 'wgs84' && toSystem === 'gcj02') {
return this.wgs84ToGcj02(lng, lat);
}
if (fromSystem === 'gcj02' && toSystem === 'wgs84') {
return this.gcj02ToWgs84(lng, lat);
}
// WGS84GCJ-02
if (fromSystem === 'wgs84') {
const gcj02 = this.wgs84ToGcj02(lng, lat);
return this.convertCoordinates(gcj02[0], gcj02[1], 'gcj02', toSystem);
}
return [lng, lat];
},
//
testCoordinateConversion() {
//
const wgs84Lng = 116.397128
const wgs84Lat = 39.916527
console.log('=== 坐标转换测试 ===')
console.log('WGS84坐标:', { lng: wgs84Lng, lat: wgs84Lat })
const gcj02 = this.wgs84ToGcj02(wgs84Lng, wgs84Lat)
console.log('转换为GCJ-02:', { lng: gcj02[0], lat: gcj02[1] })
const backToWgs84 = this.gcj02ToWgs84(gcj02[0], gcj02[1])
console.log('转换回WGS84:', { lng: backToWgs84[0], lat: backToWgs84[1] })
const diff = {
lng: Math.abs(backToWgs84[0] - wgs84Lng),
lat: Math.abs(backToWgs84[1] - wgs84Lat)
}
console.log('转换精度误差:', diff)
console.log('=== 测试结束 ===')
},
// calculateDistance signDistance
getDistanceFromLatLonInM(lat1, lon1, lat2, lon2) {
const R = 6371;
const dLat = this.deg2rad(lat2 - lat1);
const dLon = this.deg2rad(lon2 - lon1);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.deg2rad(lat1)) * Math.cos(this.deg2rad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c * 1000;
},
deg2rad(deg) {
return deg * (Math.PI / 180);
},
formatDistanceVal(m) {
if (m == null) return ''
const n = Number(m)
if (isNaN(n)) return ''
if (n >= 1000) return `${Math.round(n / 1000)}km`
return `${Math.round(n)}m`
},
getStatusIcon(type) {
const icons = {
success: 'checkmark-circle-fill',
error: 'close-circle-fill',
warning: 'error-circle-fill',
default: 'question-circle-fill'
};
return icons[type] || 'question-circle-fill';
},
async performCheckin() {
if (this.hasCheckedIn) {
uni.showToast({ title: '您今天已经签过到了', icon: 'none' });
return;
}
if (!this.canCheckin) return
//
if (this.courseContentList.length > 0) {
//
console.log(this.courseContentList)
uni.showModal({
title: '批量签到提示',
content: `检测到您还有${this.courseContentList.length}门课程可以签到,是否要一次性合并签到所有课程?`,
confirmText: '批量签到',
cancelText: '单个签到',
success: async (res) => {
if (res.confirm) {
//
await this.signCheck(1);
} else {
//
await this.signCheck(0);
}
},
fail: (err) => {
console.log(err)
}
});
} else {
//
await this.signCheck(0);
}
},
loadCheckinHistory() {
const records = uni.getStorageSync('checkinHistory') || [];
this.checkinHistory = records;
if (records.length > 0) {
const lastRecordTime = new Date(records[0].time);
if (lastRecordTime.toDateString() === new Date().toDateString()) {
this.hasCheckedIn = true;
}
}
}
}
};
</script>
<style scoped>
.checkin-page {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20rpx;
}
.checkin-container {
max-width: 400px;
margin: 0 auto;
}
.course-card, .status-card {
background: white;
border-radius: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.1);
padding: 32rpx;
margin-bottom: 30rpx;
}
/* 其他可签到课程中的课程卡片样式 */
.status-card .course-card {
padding: 24rpx;
margin-bottom: 20rpx;
border: 1rpx solid #e9ecef;
background: #f8f9fa;
}
.status-card .course-card:last-child {
margin-bottom: 0;
}
.status-card .course-card .course-title {
font-size: 32rpx;
margin-bottom: 20rpx;
}
.status-card .course-card .course-info {
gap: 12rpx;
}
.status-card .course-card .info-item {
padding: 10rpx;
font-size: 24rpx;
}
.course-title {
font-size: 36rpx;
font-weight: 600;
text-align: center;
margin-bottom: 24rpx;
}
.course-info {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.info-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 12rpx;
background: #f8f9fa;
border-radius: 16rpx;
font-size: 26rpx;
}
.info-icon {
color: #3498db;
}
.card-title {
display: flex;
align-items: center;
gap: 12rpx;
font-size: 30rpx;
font-weight: 600;
margin-bottom: 16rpx;
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #e9ecef;
font-size: 28rpx;
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
font-weight: 500;
}
.status-value {
display: flex;
align-items: center;
gap: 8rpx;
}
.status-success { color: #2ecc71; }
.status-error { color: #e74c3c; }
.status-warning { color: #f1c40f; }
.status-default { color: #909399; }
.distance-info {
text-align: center;
padding: 24rpx;
background: #fff;
border-radius: 32rpx;
margin-bottom: 24rpx;
}
.distance-value {
font-size: 48rpx;
font-weight: 700;
color: #3498db;
}
.distance-label {
font-size: 26rpx;
color: #6c757d;
margin-top: 8rpx;
}
.alert-custom {
border-radius: 24rpx;
padding: 24rpx;
margin-bottom: 24rpx;
display: flex;
align-items: center;
gap: 16rpx;
font-size: 26rpx;
color: #fff;
}
.alert-success { background: #2ecc71; }
.alert-error { background: #e74c3c; }
.alert-warning { background: #f1c40f; }
.checkin-actions {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-bottom: 30rpx;
}
.checkin-btn /deep/ .u-btn, .recheck-btn /deep/ .u-btn {
height: 90rpx;
}
.btn-icon {
margin-right: 12rpx;
}
.history-empty {
text-align: center;
color: #999;
padding: 30rpx 0;
font-size: 26rpx;
}
.history-list {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.history-item {
display: flex;
justify-content: space-between;
font-size: 24rpx;
color: #666;
background: #f8f9fa;
padding: 12rpx;
border-radius: 12rpx;
}
</style>

@ -20,7 +20,7 @@
}
}, {
"path": "pages/me/index",
"style": {
"style": {
"navigationBarTitleText": "我的"
}
}],
@ -28,100 +28,110 @@
"root": "packages",
"pages": [{
"path": "my/index",
"style": {
"style": {
"navigationBarTitleText": "个人信息"
}
},{
"path": "course/detail",
"style": {
"style": {
"navigationBarTitleText": ""
}
},{
"path": "course/freeDetail",
"style": {
"style": {
"navigationBarTitleText": ""
}
},{
"path": "register/index",
"style": {
"style": {
"navigationBarTitleText": "注册"
}
},{
"path": "register/login",
"style": {
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},{
"path": "apply/index",
"style": {
"style": {
"navigationBarTitleText": "在线报名"
}
},{
"path": "booksubmit/index",
"style": {
"style": {
"navigationBarTitleText": "我要预约"
}
},{
"path": "booksubmit/appointment",
"style": {
"style": {
"navigationBarTitleText": "场地预约"
}
},{
"path": "schoolmate/index",
"style": {
"style": {
"navigationBarTitleText": "校友库"
}
},{
"path": "mycourse/index",
"style": {
"style": {
"navigationBarTitleText": "我的课程"
}
},{
"path": "mycourse/detail",
"style": {
"style": {
"navigationBarTitleText": "课程详情"
}
},{
"path": "mycourse/courseStatus",
"style": {
"style": {
"navigationBarTitleText": "报名状态"
}
},{
"path": "mycourse/coursePay",
"style": {
"style": {
"navigationBarTitleText": "报名缴费"
}
},{
"path": "mycourse/courseContents",
"style": {
"style": {
"navigationBarTitleText": ""
}
},{
"path": "mycourse/courseTxl",
"style": {
"style": {
"navigationBarTitleText": "本班通讯录"
}
},{
"path": "mybook/index",
"style": {
"style": {
"navigationBarTitleText": "我的预约"
}
},{
"path": "mybook/detail",
"style": {
"style": {
"navigationBarTitleText": "预约详情"
}
},{
"path": "donate/index",
"style": {
"style": {
"navigationBarTitleText": "校友捐赠"
}
},{
"path": "webview/index",
"style": {
"style": {
"navigationBarTitleText": "苏州科技商学院"
}
},{
"path": "sign/index",
"style": {
"navigationBarTitleText": "课程签到"
}
},{
"path": "sign/course",
"style": {
"navigationBarTitleText": "课程签到"
}
}]
}],
"preloadRule": {

Loading…
Cancel
Save