2025-08-11 22:42:29 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="start-dm-content">
|
|
|
|
|
|
<!-- 班级选择器 -->
|
|
|
|
|
|
<view class="section">
|
2025-09-13 21:48:00 +08:00
|
|
|
|
<text class="section-title">选择班级</text>
|
2025-09-19 09:47:19 +08:00
|
|
|
|
<view class="class-selector" @click="showClassTree">
|
|
|
|
|
|
<text :class="{ placeholder: !selectedClassText }">{{ selectedClassText || "请选择班级" }}</text>
|
|
|
|
|
|
<uni-icons type="right" size="16" color="#999"></uni-icons>
|
|
|
|
|
|
</view>
|
2025-08-11 22:42:29 +08:00
|
|
|
|
<!-- 班级选择提示 -->
|
|
|
|
|
|
<view v-if="!curBj" class="class-tip">
|
|
|
|
|
|
<text class="tip-icon">ℹ️</text>
|
|
|
|
|
|
<text class="tip-text">请先选择班级</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 陪餐教师选择 -->
|
2025-09-12 19:44:12 +08:00
|
|
|
|
<dm-js v-if="curBj" :bj-id="curBj?.key" ref="dmJsRef" />
|
2025-08-11 22:42:29 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 学生状态列表 -->
|
2025-09-12 19:44:12 +08:00
|
|
|
|
<dm-xs
|
|
|
|
|
|
v-if="curBj"
|
|
|
|
|
|
:bz-id="getJcBz.id"
|
|
|
|
|
|
:nj-id="curNj?.key"
|
|
|
|
|
|
:bj-id="curBj?.key"
|
|
|
|
|
|
ref="dmXsRef"
|
|
|
|
|
|
/>
|
2025-08-11 22:42:29 +08:00
|
|
|
|
|
2025-09-19 09:47:19 +08:00
|
|
|
|
<!-- 图片视频上传组件 -->
|
2025-08-18 20:47:50 +08:00
|
|
|
|
<view class="section" v-if="curBj">
|
2025-09-19 09:47:19 +08:00
|
|
|
|
<ImageVideoUpload
|
|
|
|
|
|
v-model:image-list="imageList"
|
|
|
|
|
|
v-model:video-list="videoList"
|
|
|
|
|
|
:max-image-count="9"
|
2025-08-18 20:47:50 +08:00
|
|
|
|
:max-video-count="3"
|
2025-09-19 09:47:19 +08:00
|
|
|
|
:compress-config="compressConfig"
|
|
|
|
|
|
:upload-api="attachmentUpload"
|
|
|
|
|
|
@image-upload-success="onImageUploadSuccess"
|
|
|
|
|
|
@video-upload-success="onVideoUploadSuccess"
|
|
|
|
|
|
ref="imageVideoUploadRef"
|
2025-08-18 20:47:50 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-08-11 22:42:29 +08:00
|
|
|
|
<!-- 加载提示 -->
|
2025-09-12 19:44:12 +08:00
|
|
|
|
<view v-if="isLoading" class="loading-overlay">
|
2025-08-11 22:42:29 +08:00
|
|
|
|
<view class="loading-content">
|
|
|
|
|
|
<text>加载中...</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 提交按钮 - 固定在底部 -->
|
|
|
|
|
|
<view class="fixed-bottom" v-if="curBj">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="submit-btn"
|
2025-09-12 19:44:12 +08:00
|
|
|
|
:disabled="isSubmitting"
|
2025-08-11 22:42:29 +08:00
|
|
|
|
@click="tjDm"
|
|
|
|
|
|
>
|
|
|
|
|
|
提交点名
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
2025-09-19 09:47:19 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 班级选择树 -->
|
|
|
|
|
|
<BasicTree
|
|
|
|
|
|
ref="treeRef"
|
|
|
|
|
|
:range="treeData"
|
|
|
|
|
|
idKey="key"
|
|
|
|
|
|
rangeKey="title"
|
|
|
|
|
|
title="选择班级"
|
|
|
|
|
|
:multiple="false"
|
|
|
|
|
|
:selectParent="false"
|
|
|
|
|
|
@confirm="onTreeConfirm"
|
|
|
|
|
|
@cancel="onTreeCancel"
|
|
|
|
|
|
/>
|
2025-08-11 22:42:29 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-09-19 09:47:19 +08:00
|
|
|
|
import { ref, computed, onMounted } from 'vue'
|
|
|
|
|
|
import BasicTree from '@/components/BasicTree/Tree.vue'
|
2025-09-12 19:44:12 +08:00
|
|
|
|
import DmJs from './dmJs.vue'
|
|
|
|
|
|
import DmXs from './dmXs.vue'
|
2025-09-19 09:47:19 +08:00
|
|
|
|
import { ImageVideoUpload, type ImageItem, type VideoItem, COMPRESS_PRESETS } from '@/components/ImageVideoUpload'
|
2025-09-12 19:44:12 +08:00
|
|
|
|
import { submitJcDmDataApi } from '@/api/base/jcApi'
|
2025-09-19 09:47:19 +08:00
|
|
|
|
import { findAllNjBjTree } from '@/api/base/server'
|
|
|
|
|
|
import { attachmentUpload } from '@/api/system/upload'
|
2025-08-11 22:42:29 +08:00
|
|
|
|
import { useUserStore } from '@/store/modules/user'
|
2025-09-11 18:59:56 +08:00
|
|
|
|
import { useDataStore } from '@/store/modules/data'
|
2025-09-12 19:44:12 +08:00
|
|
|
|
import { useDebounce } from "@/utils/debounce";
|
2025-08-11 22:42:29 +08:00
|
|
|
|
const { getJs } = useUserStore()
|
2025-09-11 18:59:56 +08:00
|
|
|
|
const { getJcBz } = useDataStore();
|
2025-09-12 19:44:12 +08:00
|
|
|
|
|
|
|
|
|
|
// 替换 isSubmitting 状态为 useDebounce
|
|
|
|
|
|
const { isProcessing: isSubmitting, debounce } = useDebounce(2000);
|
|
|
|
|
|
|
2025-09-19 09:47:19 +08:00
|
|
|
|
// 树形数据
|
|
|
|
|
|
const treeData = ref<any[]>([]);
|
|
|
|
|
|
const treeRef = ref();
|
2025-08-11 22:42:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 接收外部传入属性
|
|
|
|
|
|
const props = withDefaults(defineProps<{
|
|
|
|
|
|
title?: string
|
|
|
|
|
|
}>(), {
|
|
|
|
|
|
title: '点名'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-12 19:44:12 +08:00
|
|
|
|
const isLoading = ref(false);
|
|
|
|
|
|
|
2025-08-11 22:42:29 +08:00
|
|
|
|
// 响应式数据
|
|
|
|
|
|
const curNj = ref<any>(null);
|
|
|
|
|
|
const curBj = ref<any>(null);
|
|
|
|
|
|
|
2025-09-19 09:47:19 +08:00
|
|
|
|
// 计算属性:显示选中的班级文本
|
|
|
|
|
|
const selectedClassText = computed(() => {
|
|
|
|
|
|
if (curBj.value && curNj.value) {
|
|
|
|
|
|
return `${curNj.value.title} ${curBj.value.title}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-12 19:44:12 +08:00
|
|
|
|
const dmXsRef = ref<any>(null);
|
|
|
|
|
|
const dmJsRef = ref<any>(null);
|
2025-08-11 22:42:29 +08:00
|
|
|
|
|
2025-09-19 09:47:19 +08:00
|
|
|
|
// 压缩配置
|
|
|
|
|
|
const compressConfig = ref(COMPRESS_PRESETS.medium)
|
|
|
|
|
|
|
2025-08-18 20:47:50 +08:00
|
|
|
|
// 媒体数据
|
2025-09-19 09:47:19 +08:00
|
|
|
|
const imageList = ref<ImageItem[]>([]);
|
|
|
|
|
|
const videoList = ref<VideoItem[]>([]);
|
|
|
|
|
|
const imageVideoUploadRef = ref<any>(null);
|
|
|
|
|
|
|
|
|
|
|
|
// 上传成功回调
|
|
|
|
|
|
const onImageUploadSuccess = (image: ImageItem, index: number) => {
|
|
|
|
|
|
console.log('图片上传成功:', image, index);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onVideoUploadSuccess = (video: VideoItem, index: number) => {
|
|
|
|
|
|
console.log('视频上传成功:', video, index);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载树形数据
|
|
|
|
|
|
const loadTreeData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await findAllNjBjTree();
|
|
|
|
|
|
if (res.resultCode === 1 && res.result) {
|
|
|
|
|
|
// 递归转换数据格式以适配 BasicTree 组件
|
|
|
|
|
|
const convertTreeData = (items: any[]): any[] => {
|
|
|
|
|
|
return items.map((item: any) => ({
|
|
|
|
|
|
key: item.key,
|
|
|
|
|
|
title: item.title,
|
|
|
|
|
|
njmcId: item.njmcId,
|
|
|
|
|
|
children: item.children ? convertTreeData(item.children) : [],
|
|
|
|
|
|
}));
|
|
|
|
|
|
};
|
|
|
|
|
|
treeData.value = convertTreeData(res.result);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
uni.showToast({ title: "加载班级数据失败", icon: "error" });
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 显示班级选择树
|
|
|
|
|
|
const showClassTree = () => {
|
|
|
|
|
|
if (treeRef.value) {
|
|
|
|
|
|
treeRef.value._show();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 树形选择确认
|
|
|
|
|
|
const onTreeConfirm = (selectedItems: any[]) => {
|
|
|
|
|
|
if (selectedItems.length > 0) {
|
|
|
|
|
|
const selectedItem = selectedItems[0]; // 单选模式,取第一个
|
|
|
|
|
|
|
|
|
|
|
|
// 如果选择的是班级(有parents表示是班级)
|
|
|
|
|
|
if (selectedItem.parents && selectedItem.parents.length > 0) {
|
|
|
|
|
|
const parent = selectedItem.parents[0]; // 年级信息
|
|
|
|
|
|
const nj = parent; // 年级信息
|
|
|
|
|
|
const bj = selectedItem; // 班级信息
|
|
|
|
|
|
|
|
|
|
|
|
curNj.value = nj;
|
|
|
|
|
|
curBj.value = bj;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果选择的是年级,清空班级选择
|
|
|
|
|
|
curNj.value = selectedItem;
|
|
|
|
|
|
curBj.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 树形选择取消
|
|
|
|
|
|
const onTreeCancel = () => {
|
|
|
|
|
|
// 取消选择,不做任何操作
|
2025-08-18 20:47:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-12 19:44:12 +08:00
|
|
|
|
const tjDm = debounce(async () => {
|
|
|
|
|
|
if (!curBj.value) { // 改为检查已缴费学生数量
|
2025-08-11 22:42:29 +08:00
|
|
|
|
uni.showToast({
|
2025-09-12 19:44:12 +08:00
|
|
|
|
title: '请先选择班级',
|
2025-08-11 22:42:29 +08:00
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
2025-09-12 19:44:12 +08:00
|
|
|
|
return;
|
2025-08-11 22:42:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
try {
|
2025-09-12 19:44:12 +08:00
|
|
|
|
const dmXsList = dmXsRef.value.getDmXsList();
|
|
|
|
|
|
const dmJsList = dmJsRef.value.getDmJsList();
|
2025-09-19 09:47:19 +08:00
|
|
|
|
// 获取媒体文件URL
|
|
|
|
|
|
const photoUrls = imageList.value
|
|
|
|
|
|
.filter(img => img.url)
|
|
|
|
|
|
.map(img => img.url)
|
|
|
|
|
|
.join(',');
|
2025-08-18 20:47:50 +08:00
|
|
|
|
|
2025-09-19 09:47:19 +08:00
|
|
|
|
const videoUrls = videoList.value
|
|
|
|
|
|
.filter(video => video.url)
|
|
|
|
|
|
.map(video => video.url)
|
|
|
|
|
|
.join(',');
|
2025-09-12 19:44:12 +08:00
|
|
|
|
let dmData: any = {
|
|
|
|
|
|
jcTime: new Date(),
|
2025-08-11 22:42:29 +08:00
|
|
|
|
bjId: curBj.value.key,
|
|
|
|
|
|
njId: curNj.value.key,
|
|
|
|
|
|
bjmc: curBj.value.title,
|
|
|
|
|
|
njmc: curNj.value.title,
|
2025-09-13 11:35:30 +08:00
|
|
|
|
jsId: getJs.id || '', // 点名教师ID
|
2025-09-12 19:44:12 +08:00
|
|
|
|
pcRs: dmJsList.length,
|
|
|
|
|
|
zrs: dmXsList.length,
|
|
|
|
|
|
sdRs: dmXsList.filter((s: any) => s.jcZt === 'A').length,
|
|
|
|
|
|
qjRs: dmXsList.filter((s: any) => s.jcZt === 'B').length,
|
|
|
|
|
|
qqRs: dmXsList.filter((s: any) => s.jcZt === 'C').length,
|
2025-09-13 11:35:30 +08:00
|
|
|
|
wbmRs: dmXsList.filter((s: any) => s.jcZt === 'E').length,
|
2025-08-18 20:47:50 +08:00
|
|
|
|
// 媒体文件地址
|
|
|
|
|
|
zp: photoUrls, // 照片字段,逗号分隔的字符串
|
|
|
|
|
|
sp: videoUrls, // 视频字段,逗号分隔的字符串
|
2025-09-12 19:44:12 +08:00
|
|
|
|
xsList: dmXsList,
|
|
|
|
|
|
ptJsList: dmJsList
|
|
|
|
|
|
};
|
2025-09-12 20:12:48 +08:00
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: '提交中...',
|
|
|
|
|
|
mask: true
|
|
|
|
|
|
});
|
2025-08-11 22:42:29 +08:00
|
|
|
|
// 提交点名数据
|
2025-09-12 19:44:12 +08:00
|
|
|
|
const response = await submitJcDmDataApi(dmData);
|
2025-09-12 20:12:48 +08:00
|
|
|
|
uni.hideLoading();
|
2025-08-11 22:42:29 +08:00
|
|
|
|
if (response.result) {
|
|
|
|
|
|
// 重置表单
|
|
|
|
|
|
curNj.value = null
|
|
|
|
|
|
curBj.value = null
|
2025-09-19 09:47:19 +08:00
|
|
|
|
imageList.value = []
|
|
|
|
|
|
videoList.value = []
|
2025-08-11 22:42:29 +08:00
|
|
|
|
// 返回上一页
|
|
|
|
|
|
uni.navigateBack()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error(response.message || '提交失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('提交失败:', error)
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: error instanceof Error ? error.message : '提交失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-09-12 19:44:12 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-09-19 09:47:19 +08:00
|
|
|
|
// 组件挂载时加载数据
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
loadTreeData();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-11 22:42:29 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.start-dm-content {
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
padding-bottom: 120rpx; /* 为固定底部按钮留出空间 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
padding: 30rpx;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
2025-09-12 20:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
2025-08-11 22:42:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-19 09:47:19 +08:00
|
|
|
|
.class-selector {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 20rpx 30rpx;
|
|
|
|
|
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
|
|
|
|
border: 1rpx solid #e9ecef;
|
|
|
|
|
|
border-radius: 12rpx;
|
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
|
|
|
|
|
|
text {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
|
|
|
|
|
|
&.placeholder {
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 19:44:12 +08:00
|
|
|
|
.class-tip {
|
2025-08-11 22:42:29 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-09-12 19:44:12 +08:00
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
padding: 15rpx 20rpx;
|
|
|
|
|
|
background-color: #fffbe6;
|
|
|
|
|
|
border: 1rpx solid #ffe58f;
|
|
|
|
|
|
border-radius: 12rpx;
|
2025-08-11 22:42:29 +08:00
|
|
|
|
color: #faad14;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
|
2025-09-12 19:44:12 +08:00
|
|
|
|
.tip-icon {
|
|
|
|
|
|
margin-right: 10rpx;
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
}
|
2025-08-11 22:42:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-12 19:44:12 +08:00
|
|
|
|
.fixed-bottom {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
z-index: 10;
|
2025-08-11 22:42:29 +08:00
|
|
|
|
|
2025-09-12 20:07:35 +08:00
|
|
|
|
.submit-btn {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 80rpx;
|
|
|
|
|
|
background-color: #007aff;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 40rpx;
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
background-color: #d9d9d9;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
2025-08-11 22:42:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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;
|
2025-09-12 20:07:35 +08:00
|
|
|
|
|
|
|
|
|
|
.loading-content {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
padding: 40rpx;
|
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
}
|
2025-08-11 22:42:29 +08:00
|
|
|
|
}
|
2025-09-11 18:59:56 +08:00
|
|
|
|
</style>
|