2025-09-19 09:47:19 +08:00

383 lines
9.4 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="start-dm-content">
<!-- 班级选择器 -->
<view class="section">
<text class="section-title">选择班级</text>
<view class="class-selector" @click="showClassTree">
<text :class="{ placeholder: !selectedClassText }">{{ selectedClassText || "请选择班级" }}</text>
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
<!-- 班级选择提示 -->
<view v-if="!curBj" class="class-tip">
<text class="tip-icon"></text>
<text class="tip-text">请先选择班级</text>
</view>
</view>
<!-- 陪餐教师选择 -->
<dm-js v-if="curBj" :bj-id="curBj?.key" ref="dmJsRef" />
<!-- 学生状态列表 -->
<dm-xs
v-if="curBj"
:bz-id="getJcBz.id"
:nj-id="curNj?.key"
:bj-id="curBj?.key"
ref="dmXsRef"
/>
<!-- 图片视频上传组件 -->
<view class="section" v-if="curBj">
<ImageVideoUpload
v-model:image-list="imageList"
v-model:video-list="videoList"
:max-image-count="9"
:max-video-count="3"
:compress-config="compressConfig"
:upload-api="attachmentUpload"
@image-upload-success="onImageUploadSuccess"
@video-upload-success="onVideoUploadSuccess"
ref="imageVideoUploadRef"
/>
</view>
<!-- 加载提示 -->
<view v-if="isLoading" class="loading-overlay">
<view class="loading-content">
<text>加载中...</text>
</view>
</view>
</view>
<!-- 提交按钮 - 固定在底部 -->
<view class="fixed-bottom" v-if="curBj">
<button
class="submit-btn"
:disabled="isSubmitting"
@click="tjDm"
>
提交点名
</button>
</view>
<!-- 班级选择树 -->
<BasicTree
ref="treeRef"
:range="treeData"
idKey="key"
rangeKey="title"
title="选择班级"
:multiple="false"
:selectParent="false"
@confirm="onTreeConfirm"
@cancel="onTreeCancel"
/>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import BasicTree from '@/components/BasicTree/Tree.vue'
import DmJs from './dmJs.vue'
import DmXs from './dmXs.vue'
import { ImageVideoUpload, type ImageItem, type VideoItem, COMPRESS_PRESETS } from '@/components/ImageVideoUpload'
import { submitJcDmDataApi } from '@/api/base/jcApi'
import { findAllNjBjTree } from '@/api/base/server'
import { attachmentUpload } from '@/api/system/upload'
import { useUserStore } from '@/store/modules/user'
import { useDataStore } from '@/store/modules/data'
import { useDebounce } from "@/utils/debounce";
const { getJs } = useUserStore()
const { getJcBz } = useDataStore();
// 替换 isSubmitting 状态为 useDebounce
const { isProcessing: isSubmitting, debounce } = useDebounce(2000);
// 树形数据
const treeData = ref<any[]>([]);
const treeRef = ref();
// 接收外部传入属性
const props = withDefaults(defineProps<{
title?: string
}>(), {
title: '点名'
});
const isLoading = ref(false);
// 响应式数据
const curNj = ref<any>(null);
const curBj = ref<any>(null);
// 计算属性:显示选中的班级文本
const selectedClassText = computed(() => {
if (curBj.value && curNj.value) {
return `${curNj.value.title} ${curBj.value.title}`;
}
return '';
});
const dmXsRef = ref<any>(null);
const dmJsRef = ref<any>(null);
// 压缩配置
const compressConfig = ref(COMPRESS_PRESETS.medium)
// 媒体数据
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 = () => {
// 取消选择,不做任何操作
};
const tjDm = debounce(async () => {
if (!curBj.value) { // 改为检查已缴费学生数量
uni.showToast({
title: '请先选择班级',
icon: 'none'
})
return;
}
try {
const dmXsList = dmXsRef.value.getDmXsList();
const dmJsList = dmJsRef.value.getDmJsList();
// 获取媒体文件URL
const photoUrls = imageList.value
.filter(img => img.url)
.map(img => img.url)
.join(',');
const videoUrls = videoList.value
.filter(video => video.url)
.map(video => video.url)
.join(',');
let dmData: any = {
jcTime: new Date(),
bjId: curBj.value.key,
njId: curNj.value.key,
bjmc: curBj.value.title,
njmc: curNj.value.title,
jsId: getJs.id || '', // 点名教师ID
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,
wbmRs: dmXsList.filter((s: any) => s.jcZt === 'E').length,
// 媒体文件地址
zp: photoUrls, // 照片字段,逗号分隔的字符串
sp: videoUrls, // 视频字段,逗号分隔的字符串
xsList: dmXsList,
ptJsList: dmJsList
};
uni.showLoading({
title: '提交中...',
mask: true
});
// 提交点名数据
const response = await submitJcDmDataApi(dmData);
uni.hideLoading();
if (response.result) {
// 重置表单
curNj.value = null
curBj.value = null
imageList.value = []
videoList.value = []
// 返回上一页
uni.navigateBack()
} else {
throw new Error(response.message || '提交失败')
}
} catch (error) {
console.error('提交失败:', error)
uni.showToast({
title: error instanceof Error ? error.message : '提交失败',
icon: 'none'
})
}
});
// 组件挂载时加载数据
onMounted(() => {
loadTreeData();
});
</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);
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
}
.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%);
}
}
.class-tip {
display: flex;
align-items: center;
margin-top: 20rpx;
padding: 15rpx 20rpx;
background-color: #fffbe6;
border: 1rpx solid #ffe58f;
border-radius: 12rpx;
color: #faad14;
font-size: 28rpx;
font-weight: bold;
.tip-icon {
margin-right: 10rpx;
font-size: 32rpx;
}
}
.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;
.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;
}
}
}
.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>