438 lines
9.1 KiB
Vue
438 lines
9.1 KiB
Vue
<template>
|
||
<BasicLayout>
|
||
<scroll-view scroll-y class="detail-scroll-view">
|
||
<view class="detail-container">
|
||
<!-- 签到基本信息 -->
|
||
<view class="info-card">
|
||
<view class="card-header">
|
||
<text class="qd-title">{{ qdInfo.qdmc }}</text>
|
||
<text class="qd-status" :class="getStatusClass(qdInfo.qdStatus)">
|
||
{{ getStatusText(qdInfo.qdStatus) }}
|
||
</text>
|
||
</view>
|
||
|
||
<view class="info-list">
|
||
<view class="info-item">
|
||
<text class="info-label">签到地点:</text>
|
||
<text class="info-value">{{ qdInfo.qdwz || '未设置' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="info-label">开始时间:</text>
|
||
<text class="info-value">{{ formatTime(qdInfo.qdkstime) }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="info-label">结束时间:</text>
|
||
<text class="info-value">{{ formatTime(qdInfo.qdjstime) }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="info-label">发布者:</text>
|
||
<text class="info-value">{{ qdInfo.jsxm || '未知' }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="info-label">发布时间:</text>
|
||
<text class="info-value">{{ formatTime(qdInfo.qdFbtime) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 签到人员统计 -->
|
||
<view class="stats-card">
|
||
<view class="stats-header">
|
||
<text class="stats-title">签到统计</text>
|
||
</view>
|
||
<view class="stats-content">
|
||
<view class="stat-item">
|
||
<text class="stat-number">{{ totalCount }}</text>
|
||
<text class="stat-label">总人数</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-number signed">{{ signedCount }}</text>
|
||
<text class="stat-label">已签到</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-number unsigned">{{ unsignedCount }}</text>
|
||
<text class="stat-label">未签到</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 签到人员列表 -->
|
||
<view class="teacher-list-card">
|
||
<view class="list-header">
|
||
<text class="list-title">签到人员</text>
|
||
</view>
|
||
<view class="teacher-list">
|
||
<view
|
||
v-for="teacher in teacherList"
|
||
:key="teacher.id"
|
||
class="teacher-item"
|
||
>
|
||
<view class="teacher-info">
|
||
<text class="teacher-name">{{ teacher.jsxm }}</text>
|
||
<text class="teacher-position">{{ teacher.dzzw || '' }} {{ teacher.qtzw || '' }}</text>
|
||
</view>
|
||
<view class="teacher-status">
|
||
<text class="status-text" :class="getStatusClass(teacher.qdStatus)">
|
||
{{ getStatusText(teacher.qdStatus) }}
|
||
</text>
|
||
<text v-if="teacher.qdwctime" class="sign-time">
|
||
{{ formatTime(teacher.qdwctime) }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 底部操作 -->
|
||
<template #bottom>
|
||
<view class="bottom-actions">
|
||
<button class="action-btn back-btn" @click="handleBack">
|
||
返回列表
|
||
</button>
|
||
</view>
|
||
</template>
|
||
</BasicLayout>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, computed, onMounted } from "vue";
|
||
import { onLoad } from "@dcloudio/uni-app";
|
||
import { qdFindByIdApi, qdzxFindByQdParamsApi } from "@/api/base/server";
|
||
|
||
interface QdInfo {
|
||
id: string;
|
||
qdmc: string;
|
||
qdwz: string;
|
||
qdStatus: string;
|
||
jsId: string;
|
||
jsxm: string;
|
||
qdFbtime: string;
|
||
qdkstime: string;
|
||
qdjstime: string;
|
||
qdry: string;
|
||
}
|
||
|
||
interface TeacherInfo {
|
||
id: string;
|
||
jsId: string;
|
||
jsxm: string;
|
||
dzzw: string;
|
||
qtzw: string;
|
||
qdStatus: string;
|
||
qdwctime: string;
|
||
}
|
||
|
||
const qdId = ref<string>('');
|
||
const qdInfo = ref<QdInfo>({} as QdInfo);
|
||
const teacherList = ref<TeacherInfo[]>([]);
|
||
|
||
const totalCount = computed(() => teacherList.value.length);
|
||
const signedCount = computed(() => teacherList.value.filter(t => t.qdStatus === '1').length);
|
||
const unsignedCount = computed(() => totalCount.value - signedCount.value);
|
||
|
||
onLoad((options) => {
|
||
if (options && options.id) {
|
||
qdId.value = options.id;
|
||
loadQdDetail();
|
||
loadTeacherList();
|
||
} else {
|
||
uni.showToast({ title: "缺少签到ID参数", icon: "error" });
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1500);
|
||
}
|
||
});
|
||
|
||
const loadQdDetail = async () => {
|
||
try {
|
||
const result = await qdFindByIdApi({ id: qdId.value });
|
||
if (result.resultCode === 1 && result.result) {
|
||
qdInfo.value = result.result;
|
||
}
|
||
} catch (error) {
|
||
console.error('加载签到详情失败:', error);
|
||
}
|
||
};
|
||
|
||
const loadTeacherList = async () => {
|
||
try {
|
||
const result = await qdzxFindByQdParamsApi({ qdId: qdId.value });
|
||
if (result.resultCode === 1 && result.result) {
|
||
teacherList.value = result.result;
|
||
}
|
||
} catch (error) {
|
||
console.error('加载教师列表失败:', error);
|
||
}
|
||
};
|
||
|
||
const getStatusClass = (status: string) => {
|
||
switch (status) {
|
||
case 'A':
|
||
return 'status-pending';
|
||
case 'B':
|
||
return 'status-draft';
|
||
case 'C':
|
||
return 'status-pushed';
|
||
case '1':
|
||
return 'status-signed';
|
||
case '0':
|
||
default:
|
||
return 'status-unsigned';
|
||
}
|
||
};
|
||
|
||
const getStatusText = (status: string) => {
|
||
switch (status) {
|
||
case 'A':
|
||
return '待推送';
|
||
case 'B':
|
||
return '暂存';
|
||
case 'C':
|
||
return '已推送';
|
||
case '1':
|
||
return '已签到';
|
||
case '0':
|
||
default:
|
||
return '未签到';
|
||
}
|
||
};
|
||
|
||
const formatTime = (time: string) => {
|
||
if (!time) return '未设置';
|
||
return new Date(time).toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
};
|
||
|
||
const handleBack = () => {
|
||
uni.navigateBack();
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.detail-scroll-view {
|
||
height: calc(100vh - 120px);
|
||
}
|
||
|
||
.detail-container {
|
||
padding: 15px;
|
||
}
|
||
|
||
.info-card {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin-bottom: 15px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.qd-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
flex: 1;
|
||
}
|
||
|
||
.qd-status {
|
||
padding: 6px 12px;
|
||
border-radius: 16px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.status-pending {
|
||
background: #fff3cd;
|
||
color: #856404;
|
||
}
|
||
|
||
.status-draft {
|
||
background: #d1ecf1;
|
||
color: #0c5460;
|
||
}
|
||
|
||
.status-pushed {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
}
|
||
|
||
.status-signed {
|
||
background: #d4edda;
|
||
color: #155724;
|
||
}
|
||
|
||
.status-unsigned {
|
||
background: #f8d7da;
|
||
color: #721c24;
|
||
}
|
||
|
||
.info-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
min-width: 80px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 14px;
|
||
color: #333;
|
||
flex: 1;
|
||
}
|
||
|
||
.stats-card {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin-bottom: 15px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.stats-header {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.stats-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.stats-content {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
}
|
||
|
||
.stat-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.stat-number {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #333;
|
||
|
||
&.signed {
|
||
color: #28a745;
|
||
}
|
||
|
||
&.unsigned {
|
||
color: #dc3545;
|
||
}
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
.teacher-list-card {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.list-header {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.list-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.teacher-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.teacher-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 15px;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.teacher-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.teacher-name {
|
||
display: block;
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #333;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.teacher-position {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
.teacher-status {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 4px;
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
padding: 2px 8px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.sign-time {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
.bottom-actions {
|
||
padding: 15px;
|
||
background: white;
|
||
border-top: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.back-btn {
|
||
width: 100%;
|
||
padding: 12px;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
}
|
||
</style> |