调整就餐点名和陪同的逻辑
This commit is contained in:
parent
35e8587ec5
commit
4957998b8b
276
src/api/base/jcApi.ts
Normal file
276
src/api/base/jcApi.ts
Normal file
@ -0,0 +1,276 @@
|
||||
import { get, post } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取就餐标准列表
|
||||
*/
|
||||
export const getJcBzListApi = async (params: any) => {
|
||||
return await get('/api/jcBz/getJcBzList', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取就餐清单列表
|
||||
*/
|
||||
export const getJcQdListApi = async (params: any) => {
|
||||
return await get('/api/jcQd/findPage', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取就餐点名分页
|
||||
*/
|
||||
export const getJcDmPageApi = async (params: any) => {
|
||||
return await get('/api/jcDm/findPage', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学生ID查询就餐清单状态
|
||||
*/
|
||||
export const getStudentJcStatusApi = async (xsId: string) => {
|
||||
return await get('/api/jcQd/findPage', {
|
||||
xsId,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交就餐点名
|
||||
*/
|
||||
export const submitJcDmApi = async (data: any) => {
|
||||
return await post('/api/jcDm/save', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交陪餐教师信息
|
||||
*/
|
||||
export const submitJcPtApi = async (data: any) => {
|
||||
return await post('/api/jcPt/save', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取就餐点名记录
|
||||
*/
|
||||
export const getJcDmListApi = async (params: any) => {
|
||||
return await get('/api/jcDm/findPage', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取陪餐教师记录
|
||||
*/
|
||||
export const getJcPtListApi = async (params: any) => {
|
||||
return await get('/api/jcPt/findPage', params)
|
||||
}
|
||||
|
||||
/**
|
||||
* 【教师端专用】根据班级ID获取学生点名数据
|
||||
* 返回两组学生数据:已缴费和未缴费/未报名
|
||||
*/
|
||||
export const getClassStudentDmDataApi = async (bjId: string, njId: string) => {
|
||||
try {
|
||||
const response = await get('/mobile/js/jc/getClassStudentDmData', { bjId, njId })
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('获取班级学生点名数据失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【教师端专用】提交就餐点名数据
|
||||
* 包含:点名教师ID、点名时间、学生列表、陪餐教师列表等
|
||||
*/
|
||||
export const submitJcDmDataApi = async (dmData: any) => {
|
||||
try {
|
||||
const response = await post('/mobile/js/jc/tjDmData', dmData)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('提交就餐点名数据失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【教师端专用】获取就餐点名详情
|
||||
*/
|
||||
export const getJcDmDetailApi = async (dmId: string) => {
|
||||
try {
|
||||
const response = await get('/mobile/js/jc/getDmDetail', { dmId })
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('获取就餐点名详情失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【优化版本】根据班级ID获取学生就餐状态 - 使用批量查询避免循环
|
||||
*/
|
||||
export const getClassStudentJcStatusBatchApi = async (bjId: string, njId: string) => {
|
||||
try {
|
||||
// 1. 批量查询班级学生列表
|
||||
const studentsRes = await get('/api/xs/findPage', {
|
||||
bjId,
|
||||
njId,
|
||||
pageNum: 1,
|
||||
pageSize: 1000
|
||||
})
|
||||
|
||||
if (!studentsRes.result || !studentsRes.result.rows || studentsRes.result.rows.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const students = studentsRes.result.rows
|
||||
const studentIds = students.map((s: any) => s.id)
|
||||
|
||||
// 2. 批量查询所有学生的就餐清单状态
|
||||
const jcQdRes = await get('/api/jcQd/findBatchByXsIds', {
|
||||
xsIds: studentIds.join(','),
|
||||
pageNum: 1,
|
||||
pageSize: 1000
|
||||
})
|
||||
|
||||
// 3. 将就餐清单转换为Map,便于快速查找
|
||||
const jcQdMap = new Map()
|
||||
if (jcQdRes.result && jcQdRes.result.rows) {
|
||||
jcQdRes.result.rows.forEach((qd: any) => {
|
||||
jcQdMap.set(qd.xsId, qd)
|
||||
})
|
||||
}
|
||||
|
||||
// 4. 构建学生状态列表
|
||||
const studentStatusList = students.map((student: any) => {
|
||||
const jcQdInfo = jcQdMap.get(student.id)
|
||||
const hasJcQd = !!jcQdInfo
|
||||
const jfZt = jcQdInfo?.jfZt || null
|
||||
|
||||
// 根据缴费状态和就餐清单情况设置默认状态
|
||||
let defaultStatus = 'A' // 默认正常状态
|
||||
if (!hasJcQd) {
|
||||
defaultStatus = 'E' // 未报名
|
||||
} else if (jfZt !== 'B') {
|
||||
defaultStatus = 'D' // 未缴费
|
||||
}
|
||||
|
||||
return {
|
||||
...student,
|
||||
hasJcQd,
|
||||
jcQdInfo,
|
||||
jfZt,
|
||||
jcZt: defaultStatus,
|
||||
studentType: hasJcQd && jfZt === 'B' ? 'paid' : 'unpaid'
|
||||
}
|
||||
})
|
||||
|
||||
return studentStatusList
|
||||
} catch (error) {
|
||||
console.error('批量查询学生就餐状态失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【原版本】根据班级ID获取学生就餐状态 - 保留作为备用
|
||||
* @deprecated 建议使用 getClassStudentDmDataApi 替代
|
||||
*/
|
||||
export const getClassStudentJcStatusApi = async (bjId: string, njId: string) => {
|
||||
// 先获取班级学生列表
|
||||
const studentsRes = await get('/api/xs/findPage', {
|
||||
bjId,
|
||||
njId,
|
||||
pageNum: 1,
|
||||
pageSize: 1000
|
||||
})
|
||||
|
||||
if (!studentsRes.result || !studentsRes.result.rows) {
|
||||
return []
|
||||
}
|
||||
|
||||
const students = studentsRes.result.rows
|
||||
const studentStatusList = []
|
||||
|
||||
// 为每个学生查询就餐清单状态
|
||||
for (const student of students) {
|
||||
try {
|
||||
const jcQdRes = await get('/api/jcQd/findPage', {
|
||||
xsId: student.id,
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
let hasJcQd = false
|
||||
let jcQdInfo = null
|
||||
let jfZt = null // 缴费状态
|
||||
|
||||
if (jcQdRes.result && jcQdRes.result.rows && jcQdRes.result.rows.length > 0) {
|
||||
jcQdInfo = jcQdRes.result.rows[0]
|
||||
hasJcQd = true
|
||||
jfZt = jcQdInfo.jfZt || null
|
||||
}
|
||||
|
||||
// 根据缴费状态和就餐清单情况设置默认状态
|
||||
let defaultStatus = 'A' // 默认正常状态
|
||||
if (!hasJcQd) {
|
||||
defaultStatus = 'E' // 未报名
|
||||
} else if (jfZt !== 'B') {
|
||||
defaultStatus = 'D' // 未缴费
|
||||
}
|
||||
|
||||
studentStatusList.push({
|
||||
...student,
|
||||
hasJcQd,
|
||||
jcQdInfo,
|
||||
jfZt,
|
||||
jcZt: defaultStatus, // 默认就餐状态
|
||||
// 添加学生类型标识,便于前端分类显示
|
||||
studentType: hasJcQd && jfZt === 'B' ? 'paid' : 'unpaid'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`查询学生 ${student.xm} 就餐状态失败:`, error)
|
||||
studentStatusList.push({
|
||||
...student,
|
||||
hasJcQd: false,
|
||||
jcQdInfo: null,
|
||||
jfZt: null,
|
||||
jcZt: 'E', // 查询失败时默认为未报名
|
||||
studentType: 'unpaid'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return studentStatusList
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量提交学生就餐点名记录
|
||||
*/
|
||||
export const submitBatchJcDmApi = async (dmList: any[]) => {
|
||||
const promises = dmList.map(dm => submitJcDmApi(dm))
|
||||
return await Promise.all(promises)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量提交学生就餐点名记录 - 优化版本
|
||||
*/
|
||||
export const submitBatchJcDmOptimizedApi = async (dmList: any[]) => {
|
||||
return await post('/api/jcDm/saveBatch', dmList)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据班级ID获取学生就餐状态统计信息
|
||||
*/
|
||||
export const getClassStudentJcStatusStatsApi = async (bjId: string, njId: string) => {
|
||||
return await get('/api/jcQd/getClassStats', { bjId, njId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据点名批次ID获取点名记录
|
||||
*/
|
||||
export const getJcDmByBatchIdApi = async (dmPcId: string) => {
|
||||
return await get('/api/jcDm/findByBatchId', { dmPcId })
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据班级和日期获取点名记录
|
||||
*/
|
||||
export const getJcDmByClassAndDateApi = async (bjId: string, njId: string, dmDate: string) => {
|
||||
return await get('/api/jcDm/findByClassAndDate', { bjId, njId, dmDate })
|
||||
}
|
||||
@ -565,6 +565,18 @@
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f4f5f7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/jc/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "就餐点名"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/view/routine/jc/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "就餐点名详情"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
@ -64,9 +64,9 @@
|
||||
<view class="title-line"></view>
|
||||
</view>
|
||||
<view class="section-grid-card">
|
||||
<template v-for="item in section.items" :key="item.id">
|
||||
<template v-for="(item, itemIdx) in section.items" :key="item.id">
|
||||
<view
|
||||
v-if="item.show && hasPermissionDirect(item.permissionKey)"
|
||||
v-if="item.show && hasPermissionDirect(item.permissionKey)"
|
||||
class="grid-item"
|
||||
@click="handleGridItemClick(item)"
|
||||
>
|
||||
@ -113,8 +113,8 @@ const isLoading = ref(true);
|
||||
|
||||
// 教师工作信息
|
||||
const jsWork = ref<any>({
|
||||
jf: 88,
|
||||
ks: 40
|
||||
jf: 88,
|
||||
ks: 40,
|
||||
});
|
||||
|
||||
interface GridItem {
|
||||
@ -122,7 +122,7 @@ interface GridItem {
|
||||
icon: string; // 图标文件名 (不含扩展名)
|
||||
text: string;
|
||||
show: boolean; // 是否显示
|
||||
permissionKey?: string; // 权限键
|
||||
permissionKey: string; // 权限键
|
||||
path?: string; // 页面路径
|
||||
}
|
||||
|
||||
@ -142,12 +142,15 @@ const handleLogout = () => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
// 从userdata中获取用户ID
|
||||
const userDataStr = uni.getStorageSync('app-user');
|
||||
const userDataStr = uni.getStorageSync("app-user");
|
||||
let userData = null;
|
||||
|
||||
if (userDataStr) {
|
||||
try {
|
||||
userData = typeof userDataStr === 'string' ? JSON.parse(userDataStr) : userDataStr;
|
||||
userData =
|
||||
typeof userDataStr === "string"
|
||||
? JSON.parse(userDataStr)
|
||||
: userDataStr;
|
||||
} catch (e) {
|
||||
// 静默处理错误
|
||||
}
|
||||
@ -260,6 +263,14 @@ const sections = reactive<Section[]>([
|
||||
permissionKey: "routine-qdfb", // 签到发布权限编码
|
||||
path: "/pages/view/routine/qd/index",
|
||||
},
|
||||
{
|
||||
id: "r11",
|
||||
icon: "draftfill",
|
||||
text: "就餐点名",
|
||||
show: true,
|
||||
permissionKey: "routine-jcdm", // 就餐点名权限编码
|
||||
path: "/pages/view/routine/jc/index",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -381,7 +392,7 @@ onMounted(async () => {
|
||||
isLoading.value = false;
|
||||
|
||||
// 强制清除权限缓存,确保数据一致性
|
||||
const { clearPermissionCachePublic } = await import('@/utils/permission');
|
||||
const { clearPermissionCachePublic } = await import("@/utils/permission");
|
||||
clearPermissionCachePublic();
|
||||
});
|
||||
|
||||
@ -395,20 +406,24 @@ async function initPositionInfo() {
|
||||
if (getJs.qtzw && typeof getJs.qtzw == "string") {
|
||||
qtZw = getJs.qtzw.split(",");
|
||||
}
|
||||
if (dzZw && dzZw.length){
|
||||
const res = await getZwListByLx({ zwlx: '党政职务' });
|
||||
dzZwLabel.value = dzZw.map((zwId: string) => {
|
||||
const zw = res.result.find((zw: any) => zwId == zw.id);
|
||||
return zw ? zw.zwmc : '';
|
||||
}).join(', ');
|
||||
};
|
||||
if (qtZw && qtZw.length){
|
||||
const res = await getZwListByLx({ zwlx: '其他职务' });
|
||||
qtZwLabel.value = qtZw.map((zwId: string) => {
|
||||
const zw = res.result.find((zw: any) => zwId == zw.id);
|
||||
return zw ? zw.zwmc : '';
|
||||
}).join(', ');
|
||||
};
|
||||
if (dzZw && dzZw.length) {
|
||||
const res = await getZwListByLx({ zwlx: "党政职务" });
|
||||
dzZwLabel.value = dzZw
|
||||
.map((zwId: string) => {
|
||||
const zw = res.result.find((zw: any) => zwId == zw.id);
|
||||
return zw ? zw.zwmc : "";
|
||||
})
|
||||
.join(", ");
|
||||
}
|
||||
if (qtZw && qtZw.length) {
|
||||
const res = await getZwListByLx({ zwlx: "其他职务" });
|
||||
qtZwLabel.value = qtZw
|
||||
.map((zwId: string) => {
|
||||
const zw = res.result.find((zw: any) => zwId == zw.id);
|
||||
return zw ? zw.zwmc : "";
|
||||
})
|
||||
.join(", ");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查区域权限
|
||||
@ -475,8 +490,12 @@ const hasPermissionDirect = (permissionKey: string) => {
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
// 顶部 Header
|
||||
|
||||
@ -109,6 +109,7 @@ const rebuildJsList = () => {
|
||||
jsList.value.push({
|
||||
label: item.jsxm,
|
||||
value: item.id,
|
||||
headPic: item.headPic
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -13,9 +13,9 @@ import { findAllNjBjTreeApi } from "@/api/base/server";
|
||||
|
||||
// 接收外部传入属性并设置默认值
|
||||
const props = withDefaults(defineProps<{
|
||||
defaultValue: any,
|
||||
customStyle: any,
|
||||
iconArrow: string
|
||||
defaultValue?: any,
|
||||
customStyle?: any,
|
||||
iconArrow?: string
|
||||
}>(), {
|
||||
defaultValue: [],
|
||||
customStyle: {},
|
||||
|
||||
1017
src/pages/view/routine/jc/components/dm.vue
Normal file
1017
src/pages/view/routine/jc/components/dm.vue
Normal file
File diff suppressed because it is too large
Load Diff
491
src/pages/view/routine/jc/components/dmList.vue
Normal file
491
src/pages/view/routine/jc/components/dmList.vue
Normal file
@ -0,0 +1,491 @@
|
||||
<template>
|
||||
<view class="record-content">
|
||||
<!-- 搜索筛选区域 -->
|
||||
<view class="search-section">
|
||||
<view class="search-row">
|
||||
<view class="search-item">
|
||||
<text class="label">班级:</text>
|
||||
<view class="flex-1">
|
||||
<NjBjPicker @change="changeNjBj" icon-arrow="right"
|
||||
:customStyle="{ borderRadius: '0', padding: '0.5rem 0.625rem' }" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-row">
|
||||
<view class="search-item">
|
||||
<text class="label">时间范围:</text>
|
||||
<uni-datetime-picker
|
||||
type="daterange"
|
||||
:value="[startTime, endTime]"
|
||||
@change="onTimeRangeChange"
|
||||
class="date-picker"
|
||||
>
|
||||
<view class="picker-text">{{ getTimeRangeText() }}</view>
|
||||
</uni-datetime-picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-row">
|
||||
<button class="search-btn" @click="searchRecords">搜索</button>
|
||||
<button class="reset-btn" @click="resetSearch">重置</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 使用 BasicListLayout 包裹记录列表 -->
|
||||
<BasicListLayout @register="register" :fixed="false">
|
||||
<template #top>
|
||||
<view class="records-header" v-if="dmRecords.length > 0">
|
||||
<view class="section-title">
|
||||
点名记录 ({{ totalCount }}条)
|
||||
<text class="export-btn" @click="exportRecords">导出</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template #default="{ data }">
|
||||
<view
|
||||
class="record-card"
|
||||
@click="goToDetail(data)"
|
||||
>
|
||||
<view class="card-header">
|
||||
<view class="time-info">
|
||||
<text class="record-date">{{ formatDate(data.dmTime) }}</text>
|
||||
<text class="record-time">{{ formatTime(data.dmTime) }}</text>
|
||||
</view>
|
||||
<view class="status-badge">
|
||||
<text class="status-text">已点名</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card-content">
|
||||
<view class="class-info">
|
||||
<text class="class-name">{{ data.njmc }} {{ data.bjmc }}</text>
|
||||
</view>
|
||||
|
||||
<view class="stats-row">
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">学生</text>
|
||||
<text class="stat-value">{{ data.xsCount }}人</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">陪餐教师</text>
|
||||
<text class="stat-value">{{ data.jsCount }}人</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card-footer">
|
||||
<text class="view-detail">点击查看详情 →</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</BasicListLayout>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && dmRecords.length === 0 && hasSearched" class="empty-state">
|
||||
<view class="empty-icon">📋</view>
|
||||
<text class="empty-text">暂无点名记录</text>
|
||||
<text class="empty-tip">请选择班级和时间范围进行搜索</text>
|
||||
</view>
|
||||
|
||||
<!-- 初始提示 -->
|
||||
<view v-if="!hasSearched" class="initial-tip">
|
||||
<view class="tip-icon">🔍</view>
|
||||
<text class="tip-text">请选择班级和时间范围开始搜索</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useLayout } from "@/components/BasicListLayout/hooks/useLayout"
|
||||
import NjBjPicker from '@/pages/components/NjBjPicker/index.vue'
|
||||
import { getJcDmPageApi } from '@/api/base/jcApi'
|
||||
import { useDataStore } from '@/store/modules/data'
|
||||
|
||||
const { setData } = useDataStore()
|
||||
|
||||
// 接收外部传入属性
|
||||
const props = withDefaults(defineProps<{
|
||||
title?: string
|
||||
}>(), {
|
||||
title: '点名列表'
|
||||
});
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const startTime = ref('')
|
||||
const endTime = ref('')
|
||||
const selectedClass = ref<any>(null)
|
||||
const dmRecords = ref<any[]>([])
|
||||
const totalCount = ref(0)
|
||||
const hasSearched = ref(false)
|
||||
|
||||
// 使用 BasicListLayout
|
||||
const [register, { reload, setParam }] = useLayout({
|
||||
api: getJcDmPageApi,
|
||||
componentProps: {
|
||||
auto: false
|
||||
}
|
||||
})
|
||||
|
||||
// 改变了年级班级
|
||||
const changeNjBj = async (nj: any, bj: any) => {
|
||||
selectedClass.value = {
|
||||
njId: nj.key,
|
||||
bjId: bj.key,
|
||||
}
|
||||
};
|
||||
|
||||
const onTimeRangeChange = (e: any) => {
|
||||
// uni-datetime-picker 返回的是一个数组,包含开始和结束时间
|
||||
if (e && Array.isArray(e)) {
|
||||
const [start, end] = e
|
||||
startTime.value = start
|
||||
endTime.value = end
|
||||
} else if (e && typeof e === 'string') {
|
||||
// 如果返回的是字符串,可能是单个时间
|
||||
startTime.value = e
|
||||
endTime.value = e
|
||||
}
|
||||
}
|
||||
|
||||
const getTimeRangeText = () => {
|
||||
if (!startTime.value || !endTime.value) return '选择时间范围'
|
||||
if (startTime.value === endTime.value) {
|
||||
return formatDate(startTime.value)
|
||||
}
|
||||
return `${formatDate(startTime.value)} - ${formatDate(endTime.value)}`
|
||||
}
|
||||
|
||||
const searchRecords = async () => {
|
||||
if (!selectedClass.value || !startTime.value || !endTime.value) {
|
||||
uni.showToast({
|
||||
title: '请选择班级和时间范围',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证开始时间不能大于结束时间
|
||||
if (startTime.value > endTime.value) {
|
||||
uni.showToast({
|
||||
title: '开始时间不能大于结束时间',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
hasSearched.value = true
|
||||
|
||||
// 设置搜索参数并重新加载
|
||||
setParam({
|
||||
njId: selectedClass.value.njId,
|
||||
bjId: selectedClass.value.bjId,
|
||||
startTime: startTime.value,
|
||||
endTime: endTime.value,
|
||||
pageNo: 1
|
||||
});
|
||||
reload()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
selectedClass.value = null
|
||||
startTime.value = ''
|
||||
endTime.value = ''
|
||||
dmRecords.value = []
|
||||
totalCount.value = 0
|
||||
hasSearched.value = false
|
||||
}
|
||||
|
||||
const goToDetail = (dm: any) => {
|
||||
setData(dm)
|
||||
uni.navigateTo({
|
||||
url: '/pages/view/routine/jc/detail'
|
||||
})
|
||||
}
|
||||
|
||||
const exportRecords = () => {
|
||||
if (dmRecords.value.length === 0) {
|
||||
uni.showToast({
|
||||
title: '暂无数据可导出',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 实现导出逻辑
|
||||
uni.showToast({
|
||||
title: '导出功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
const formatTime = (timeStr: string) => {
|
||||
if (!timeStr) return ''
|
||||
const date = new Date(timeStr)
|
||||
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 设置默认时间范围为最近一周
|
||||
const today = new Date()
|
||||
const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
|
||||
startTime.value = oneWeekAgo.toISOString().split('T')[0]
|
||||
endTime.value = today.toISOString().split('T')[0]
|
||||
// 不自动加载数据,等待用户搜索
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.record-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
flex: 0 0 160rpx;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
padding: 16rpx 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.search-btn, .reset-btn {
|
||||
flex: 1;
|
||||
height: 70rpx;
|
||||
border-radius: 35rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.records-header {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
font-size: 24rpx;
|
||||
color: #007aff;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.time-info {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.record-date {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 8rpx 16rpx;
|
||||
background-color: #e0f7fa;
|
||||
border-radius: 20rpx;
|
||||
border: 1px solid #b2ebf2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 24rpx;
|
||||
color: #007bff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.class-info {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.class-name {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
padding: 12rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
text-align: right;
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.view-detail {
|
||||
font-size: 26rpx;
|
||||
color: #007aff;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.initial-tip {
|
||||
text-align: center;
|
||||
padding: 50rpx 0;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
font-size: 60rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
</style>
|
||||
508
src/pages/view/routine/jc/detail.vue
Normal file
508
src/pages/view/routine/jc/detail.vue
Normal file
@ -0,0 +1,508 @@
|
||||
<template>
|
||||
<view class="dm-detail-content">
|
||||
<!-- 加载状态 -->
|
||||
<view v-if="loading" class="loading-state">
|
||||
<view class="loading-icon">⏳</view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 详情内容 -->
|
||||
<view v-else-if="dmDetail" class="detail-content">
|
||||
<!-- 基本信息 -->
|
||||
<view class="section">
|
||||
<view class="section-title">点名基本信息</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-item">
|
||||
<text class="info-label">班级:</text>
|
||||
<text class="info-value">{{ dmDetail.bjmc }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">年级:</text>
|
||||
<text class="info-value">{{ dmDetail.njmc }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">点名时间:</text>
|
||||
<text class="info-value">{{ formatDateTime(dmDetail.dmTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 陪餐教师列表 -->
|
||||
<view class="section" v-if="dmDetail.jsList && dmDetail.jsList.length > 0">
|
||||
<view class="section-title">
|
||||
陪餐教师 ({{ dmDetail.jsList.length }}人)
|
||||
</view>
|
||||
|
||||
<view class="teacher-list">
|
||||
<view class="teacher-grid">
|
||||
<view
|
||||
v-for="teacher in dmDetail.jsList"
|
||||
:key="teacher.id"
|
||||
class="teacher-item bg-white r-md p-12"
|
||||
>
|
||||
<view class="flex-row items-center">
|
||||
<view class="avatar-container mr-8">
|
||||
<image
|
||||
class="teacher-avatar"
|
||||
:src="teacher.tx || '/static/images/default-avatar.png'"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="teacher-name mb-8">
|
||||
<text class="font-14 cor-333">{{ teacher.jsXm }}</text>
|
||||
</view>
|
||||
<view class="flex-row">
|
||||
<view
|
||||
class="status-tag"
|
||||
:class="getTeacherStatusClass(teacher.pcZt)"
|
||||
>
|
||||
{{ getTeacherStatusText(teacher.pcZt) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学生状态列表 -->
|
||||
<view class="section" v-if="dmDetail.xsList && dmDetail.xsList.length > 0">
|
||||
<view class="section-title">
|
||||
学生状态列表 ({{ dmDetail.xsList.length }}人)
|
||||
</view>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<view class="stats-container">
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ dmDetail.xsList.length }}</text>
|
||||
<text class="stat-label">总人数</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number normal">{{ getStatusCount('A') }}</text>
|
||||
<text class="stat-label">正常</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number leave">{{ getStatusCount('B') }}</text>
|
||||
<text class="stat-label">请假</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-number absent">{{ getStatusCount('C') }}</text>
|
||||
<text class="stat-label">缺勤</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学生列表 -->
|
||||
<view class="student-list">
|
||||
<view class="student-grid">
|
||||
<view
|
||||
v-for="student in dmDetail.xsList"
|
||||
:key="student.id"
|
||||
class="student-item bg-white r-md p-12"
|
||||
>
|
||||
<view class="flex-row items-center">
|
||||
<view class="avatar-container mr-8">
|
||||
<image
|
||||
class="student-avatar"
|
||||
:src="student.tx || '/static/images/default-avatar.png'"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="student-name mb-8">
|
||||
<text class="font-14 cor-333">{{ student.xsXm }}</text>
|
||||
</view>
|
||||
<view class="flex-row">
|
||||
<view
|
||||
class="status-tag"
|
||||
:class="getStatusClass(student.jcZt)"
|
||||
>
|
||||
{{ getStatusText(student.jcZt) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!dmDetail.xsList || dmDetail.xsList.length === 0" class="empty-tip">
|
||||
暂无学生数据
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<view v-else-if="error" class="error-state">
|
||||
<view class="error-icon">❌</view>
|
||||
<text class="error-text">{{ error }}</text>
|
||||
<button class="retry-btn" @click="loadDetail">重试</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { getJcDmDetailApi } from '@/api/base/jcApi'
|
||||
import { useDataStore } from '@/store/modules/data'
|
||||
const { getData } = useDataStore()
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const dmDetail = ref<any>(null)
|
||||
const error = ref('')
|
||||
|
||||
// 方法
|
||||
const loadDetail = async () => {
|
||||
if (!getData.id) return
|
||||
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
const response = await getJcDmDetailApi(getData.id)
|
||||
|
||||
if (response.result) {
|
||||
dmDetail.value = response.result
|
||||
} else {
|
||||
error.value = response.message || '获取详情失败'
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('加载详情失败:', err)
|
||||
error.value = err.message || '加载详情失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const formatDateTime = (dateStr: string) => {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
const getStatusCount = (status: string) => {
|
||||
if (!dmDetail.value?.xsList) return 0
|
||||
return dmDetail.value.xsList.filter((s: any) => s.jcZt === status).length
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case 'A':
|
||||
return '正常'
|
||||
case 'B':
|
||||
return '请假'
|
||||
case 'C':
|
||||
return '缺勤'
|
||||
case 'D':
|
||||
return '未缴费'
|
||||
case 'E':
|
||||
return '未报名'
|
||||
default:
|
||||
return '正常'
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case 'A':
|
||||
return 'status-normal'
|
||||
case 'B':
|
||||
return 'status-leave'
|
||||
case 'C':
|
||||
return 'status-absent'
|
||||
case 'D':
|
||||
return 'status-unpaid'
|
||||
case 'E':
|
||||
return 'status-unregistered'
|
||||
default:
|
||||
return 'status-normal'
|
||||
}
|
||||
}
|
||||
|
||||
const getTeacherStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case 'A':
|
||||
return '正常陪餐'
|
||||
case 'B':
|
||||
return '请假'
|
||||
case 'C':
|
||||
return '缺勤'
|
||||
default:
|
||||
return '正常陪餐'
|
||||
}
|
||||
}
|
||||
|
||||
const getTeacherStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case 'A':
|
||||
return 'status-normal'
|
||||
case 'B':
|
||||
return 'status-leave'
|
||||
case 'C':
|
||||
return 'status-absent'
|
||||
default:
|
||||
return 'status-normal'
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadDetail();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dm-detail-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.loading-state, .error-state {
|
||||
text-align: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.loading-icon, .error-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.loading-text, .error-text {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
margin-top: 20rpx;
|
||||
padding: 16rpx 32rpx;
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
width: 120rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 30rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
gap: 60rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
&.normal {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
&.leave {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
&.absent {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.teacher-list, .student-list {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.teacher-grid, .student-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.teacher-item, .student-item {
|
||||
position: relative;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.teacher-item:hover, .student-item:hover {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
width: 92rpx;
|
||||
height: 92rpx;
|
||||
border-radius: 50%;
|
||||
padding: 6rpx;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.teacher-avatar, .student-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 20rpx;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-normal {
|
||||
background-color: #e6f7ff;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-leave {
|
||||
background-color: #fff7e6;
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.status-absent {
|
||||
background-color: #fff2f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.status-unpaid {
|
||||
background-color: #f9f0ff;
|
||||
color: #722ed1;
|
||||
}
|
||||
|
||||
.status-unregistered {
|
||||
background-color: #fff0f6;
|
||||
color: #eb2f96;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
padding: 60rpx 0;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mr-8 {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.mb-8 {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.font-14 {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.cor-333 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.r-md {
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.p-12 {
|
||||
padding: 24rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
113
src/pages/view/routine/jc/index.vue
Normal file
113
src/pages/view/routine/jc/index.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<view class="dm-container">
|
||||
<!-- 顶部Tab -->
|
||||
<view class="tab-container">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'start' }"
|
||||
@click="switchTab('start')"
|
||||
>
|
||||
开始点名
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'record' }"
|
||||
@click="switchTab('record')"
|
||||
>
|
||||
点名记录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开始点名内容 -->
|
||||
<DmComponent v-if="activeTab === 'start'" />
|
||||
|
||||
<!-- 点名记录内容 -->
|
||||
<DmListComponent v-if="activeTab === 'record'" />
|
||||
|
||||
<!-- 加载提示 -->
|
||||
<view v-if="loading" class="loading-overlay">
|
||||
<view class="loading-content">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import DmComponent from './components/dm.vue'
|
||||
import DmListComponent from './components/dmList.vue'
|
||||
|
||||
// 响应式数据
|
||||
const activeTab = ref('record')
|
||||
const loading = ref(false)
|
||||
|
||||
// 方法
|
||||
const switchTab = (tab: string) => {
|
||||
activeTab.value = tab
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dm-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.tab-container {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: #007aff;
|
||||
font-weight: bold;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 4rpx;
|
||||
background-color: #007aff;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
background-color: #fff;
|
||||
padding: 40rpx;
|
||||
border-radius: 16rpx;
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user