942 lines
23 KiB
Vue
942 lines
23 KiB
Vue
<template>
|
||
<view class="push-page">
|
||
<!-- 任务信息 -->
|
||
<view class="task-info-card">
|
||
<view class="task-title">{{ taskInfo.rwmc }}</view>
|
||
<view class="task-meta">
|
||
<text class="meta-item">截止时间:{{ formatDate(taskInfo.rwjstime) }}</text>
|
||
<text class="meta-item">负责人:{{ taskInfo.rwfzrxm }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 推送对象选择 -->
|
||
<view class="push-section">
|
||
<view class="section-header">
|
||
<text class="section-title">选择推送对象</text>
|
||
<view class="section-actions">
|
||
<text class="action-btn" @click="selectAllMembers">全选</text>
|
||
<text class="action-btn" @click="unselectAllMembers">取消全选</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 成员分组列表 -->
|
||
<scroll-view scroll-y class="members-scroll">
|
||
<view
|
||
v-for="(groupMembers, groupName) in groupedMembers"
|
||
:key="groupName"
|
||
class="member-group-section"
|
||
>
|
||
<!-- 分组头部 -->
|
||
<view class="group-header">
|
||
<view class="group-checkbox-container" @click="handleGroupCheckChange(String(groupName))">
|
||
<view
|
||
:class="['checkbox-box', {
|
||
checked: isGroupChecked(String(groupName)),
|
||
indeterminate: isGroupIndeterminate(String(groupName))
|
||
}]"
|
||
>
|
||
<text v-if="isGroupChecked(String(groupName))" class="check-icon">✓</text>
|
||
<text v-else-if="isGroupIndeterminate(String(groupName))" class="check-icon">-</text>
|
||
</view>
|
||
<view class="group-info">
|
||
<text class="group-title">{{ groupName }}</text>
|
||
<text class="group-count">({{ groupMembers.length }}人)</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分组成员列表 -->
|
||
<view class="group-members-list">
|
||
<view class="members-grid">
|
||
<view
|
||
v-for="member in groupMembers"
|
||
:key="member.id"
|
||
:class="['member-check-item', { 'is-pushed': isPushed(member.jsId) }]"
|
||
@click="handleMemberCheckChange(member.id)"
|
||
>
|
||
<view :class="['checkbox-box', { checked: selectedMembers[member.id] }]">
|
||
<text v-if="selectedMembers[member.id]" class="check-icon">✓</text>
|
||
</view>
|
||
<view class="member-info">
|
||
<view class="member-avatar">
|
||
<text class="avatar-text">{{ member.jsxm?.charAt(0) || '?' }}</text>
|
||
</view>
|
||
<view class="member-details">
|
||
<text class="member-name">{{ member.jsxm }}</text>
|
||
<text v-if="isPushed(member.jsId)" class="pushed-badge">已推送</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<view v-if="Object.keys(groupedMembers).length === 0" class="empty-state">
|
||
<text class="empty-text">暂无课程成员</text>
|
||
<text class="empty-hint">请先添加课程成员</text>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 推送统计 -->
|
||
<view class="push-summary">
|
||
<text class="summary-text">已选择 {{ selectedCount }} 名成员</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部操作按钮 -->
|
||
<view class="bottom-actions">
|
||
<view class="action-buttons">
|
||
<button class="cancel-btn" @click="goBack" :disabled="isSubmitting">
|
||
取消
|
||
</button>
|
||
<button
|
||
class="confirm-btn"
|
||
@click="handleConfirmPush"
|
||
:disabled="isSubmitting || selectedCount === 0"
|
||
>
|
||
{{ isSubmitting ? '推送中...' : '确认推送' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 推送遮罩层 -->
|
||
<view v-if="isSubmitting" class="push-overlay">
|
||
<view class="push-loading">
|
||
<view class="loading-spinner"></view>
|
||
<text class="loading-text">正在推送任务...</text>
|
||
<text class="loading-hint">请稍候,不要重复点击</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, reactive, computed } from "vue";
|
||
import { onLoad } from "@dcloudio/uni-app";
|
||
import { kccyFindByKcjbIdApi } from "@/api/base/kccyApi";
|
||
import { rwPushJsApi } from "@/api/base/rwApi";
|
||
import { executedInfoByRwIdApi } from "@/api/base/rwzxApi";
|
||
|
||
// 接口类型定义
|
||
interface TaskInfo {
|
||
id: string;
|
||
rwmc: string;
|
||
rwms: string;
|
||
rwjstime: string;
|
||
rwfzrxm: string;
|
||
rwfzr: string;
|
||
rwStatus: string;
|
||
rwlyId: string;
|
||
}
|
||
|
||
interface MemberItem {
|
||
id: string;
|
||
jsId: string;
|
||
jsxm: string;
|
||
kcjbId: string;
|
||
fzmc: string;
|
||
}
|
||
|
||
// 响应式数<E5BC8F>?
|
||
const taskInfo = ref<TaskInfo>({
|
||
id: '',
|
||
rwmc: '',
|
||
rwms: '',
|
||
rwjstime: '',
|
||
rwfzrxm: '',
|
||
rwfzr: '',
|
||
rwStatus: '',
|
||
rwlyId: ''
|
||
});
|
||
|
||
const courseId = ref('');
|
||
const memberList = ref<MemberItem[]>([]);
|
||
const selectedMembers = reactive<{ [key: string]: boolean }>({});
|
||
const pushedJsIds = ref<string[]>([]); // 已推送的教师ID列表
|
||
const isSubmitting = ref(false);
|
||
const lastClickTime = ref(0);
|
||
const DEBOUNCE_DELAY = 2000; // 防抖延迟2秒
|
||
|
||
// 计算属<E7AE97>?
|
||
const groupedMembers = computed(() => {
|
||
const groups: { [key: string]: MemberItem[] } = {};
|
||
|
||
memberList.value.forEach(member => {
|
||
const groupName = member.fzmc || '未分组';
|
||
if (!groups[groupName]) {
|
||
groups[groupName] = [];
|
||
}
|
||
groups[groupName].push(member);
|
||
});
|
||
|
||
return groups;
|
||
});
|
||
|
||
const selectedCount = computed(() => {
|
||
return Object.values(selectedMembers).filter(Boolean).length;
|
||
});
|
||
|
||
// 检查分组是否全
|
||
const isGroupChecked = (groupName: string) => {
|
||
const groupMembers = groupedMembers.value[groupName] || [];
|
||
return groupMembers.length > 0 && groupMembers.every(member => selectedMembers[member.id]);
|
||
};
|
||
|
||
// 检查分组是否半选
|
||
const isGroupIndeterminate = (groupName: string) => {
|
||
const groupMembers = groupedMembers.value[groupName] || [];
|
||
const checkedCount = groupMembers.filter(member => selectedMembers[member.id]).length;
|
||
return checkedCount > 0 && checkedCount < groupMembers.length;
|
||
};
|
||
|
||
// 检查教师是否已推送
|
||
const isPushed = (jsId: string) => {
|
||
return pushedJsIds.value.includes(jsId);
|
||
};
|
||
|
||
// 页面加载
|
||
onLoad((options: any) => {
|
||
console.log('推送页面接收到的参数:', options);
|
||
|
||
// 从全局存储中获取任务数据
|
||
const storedTaskData = uni.getStorageSync('pushTaskData');
|
||
|
||
if (storedTaskData) {
|
||
try {
|
||
taskInfo.value = storedTaskData;
|
||
courseId.value = taskInfo.value.rwlyId || options.courseId || '';
|
||
console.log('任务信息:', taskInfo.value);
|
||
console.log('课程ID:', courseId.value);
|
||
|
||
// 清除存储的任务数据
|
||
uni.removeStorageSync('pushTaskData');
|
||
|
||
loadCourseMembers();
|
||
} catch (error) {
|
||
console.error('解析任务信息失败:', error);
|
||
uni.showToast({
|
||
title: '参数解析失败',
|
||
icon: 'error'
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1500);
|
||
}
|
||
} else {
|
||
console.error('缺少任务信息参数');
|
||
uni.showToast({
|
||
title: '缺少任务信息',
|
||
icon: 'error'
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1500);
|
||
}
|
||
});
|
||
|
||
// 加载课程成员
|
||
const loadCourseMembers = async () => {
|
||
try {
|
||
console.log('开始加载课程成员,课程ID:', courseId.value);
|
||
|
||
// 并发请求:获取成员列表和任务执行记录(用于判断是否已推送)
|
||
const [memberResponse, executionResponse] = await Promise.all([
|
||
kccyFindByKcjbIdApi({ kcjbId: courseId.value }),
|
||
executedInfoByRwIdApi({ rwId: taskInfo.value.id })
|
||
]);
|
||
|
||
console.log('课程成员API响应:', memberResponse);
|
||
console.log('任务执行记录API响应:', executionResponse);
|
||
|
||
// 处理成员数据
|
||
let memberData = [];
|
||
if (memberResponse) {
|
||
if (memberResponse.hasOwnProperty('resultCode')) {
|
||
if (memberResponse.resultCode === 1 || memberResponse.resultCode === 0) {
|
||
memberData = memberResponse.result || memberResponse.rows || memberResponse.data || [];
|
||
} else {
|
||
throw new Error(memberResponse?.message || memberResponse?.msg || '获取课程成员失败');
|
||
}
|
||
} else if (memberResponse.rows || memberResponse.data) {
|
||
memberData = memberResponse.rows || memberResponse.data || [];
|
||
} else if (Array.isArray(memberResponse)) {
|
||
memberData = memberResponse;
|
||
}
|
||
}
|
||
|
||
memberList.value = memberData;
|
||
|
||
// 处理任务执行记录,提取已推送的教师ID(iszx = 1)
|
||
let executionData = [];
|
||
if (executionResponse) {
|
||
if (executionResponse.hasOwnProperty('resultCode')) {
|
||
if (executionResponse.resultCode === 1 || executionResponse.resultCode === 0) {
|
||
executionData = executionResponse.result || executionResponse.rows || executionResponse.data || [];
|
||
}
|
||
} else if (executionResponse.rows || executionResponse.data) {
|
||
executionData = executionResponse.rows || executionResponse.data || [];
|
||
} else if (Array.isArray(executionResponse)) {
|
||
executionData = executionResponse;
|
||
}
|
||
}
|
||
|
||
// 从任务执行记录中提取已推送的教师ID(iszx = 1)
|
||
pushedJsIds.value = executionData
|
||
.filter((item: any) => item.iszx === 1) // 筛选 iszx = 1(已推送)的记录
|
||
.map((item: any) => item.rwzxfzr) // 提取教师ID
|
||
.filter((jsId: string) => jsId != null && jsId.trim() !== ''); // 过滤空值
|
||
|
||
console.log('已推送的教师ID(基于iszx字段):', pushedJsIds.value);
|
||
|
||
// 默认勾选未推送的成员,不勾选已推送的成员
|
||
memberList.value.forEach(member => {
|
||
const isPushed = pushedJsIds.value.includes(member.jsId);
|
||
selectedMembers[member.id] = !isPushed; // 未推送的默认勾选,已推送的不勾选
|
||
});
|
||
|
||
console.log('解析后的成员列表:', memberList.value);
|
||
console.log('默认选中状态:', selectedMembers);
|
||
|
||
} catch (error) {
|
||
console.error('加载课程成员失败:', error);
|
||
uni.showToast({
|
||
title: '加载成员失败',
|
||
icon: 'error'
|
||
});
|
||
memberList.value = [];
|
||
pushedJsIds.value = [];
|
||
}
|
||
};
|
||
|
||
// 处理分组选择变化
|
||
const handleGroupCheckChange = (groupName: string) => {
|
||
const groupMembers = groupedMembers.value[groupName] || [];
|
||
const isChecked = isGroupChecked(groupName);
|
||
|
||
groupMembers.forEach(member => {
|
||
selectedMembers[member.id] = !isChecked;
|
||
});
|
||
};
|
||
|
||
// 处理单个成员选择变化
|
||
const handleMemberCheckChange = (memberId: string) => {
|
||
selectedMembers[memberId] = !selectedMembers[memberId];
|
||
};
|
||
|
||
// 全
|
||
const selectAllMembers = () => {
|
||
memberList.value.forEach(member => {
|
||
selectedMembers[member.id] = true;
|
||
});
|
||
};
|
||
|
||
// 取消全
|
||
const unselectAllMembers = () => {
|
||
memberList.value.forEach(member => {
|
||
selectedMembers[member.id] = false;
|
||
});
|
||
};
|
||
|
||
// 防抖处理确认推送
|
||
const handleConfirmPush = () => {
|
||
const currentTime = Date.now();
|
||
|
||
// 防抖检查:如果在2秒内重复点击,则忽略
|
||
if (currentTime - lastClickTime.value < DEBOUNCE_DELAY) {
|
||
uni.showToast({
|
||
title: '请勿重复点击,请稍候',
|
||
icon: 'none',
|
||
duration: 1500
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 如果正在提交中,直接返回
|
||
if (isSubmitting.value) {
|
||
uni.showToast({
|
||
title: '正在推送中,请稍候',
|
||
icon: 'none',
|
||
duration: 1500
|
||
});
|
||
return;
|
||
}
|
||
|
||
lastClickTime.value = currentTime;
|
||
confirmPush();
|
||
};
|
||
|
||
// 确认推送
|
||
const confirmPush = async () => {
|
||
if (selectedCount.value === 0) {
|
||
uni.showToast({
|
||
title: '请选择推送对象',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 获取选中的成员
|
||
const selectedMemberList = memberList.value.filter(member => selectedMembers[member.id]);
|
||
|
||
uni.showModal({
|
||
title: '确认推送',
|
||
content: `确定要推送任务"${taskInfo.value.rwmc}"给${selectedCount.value}名成员吗?`,
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
await executePush(selectedMemberList);
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
// 执行推
|
||
const executePush = async (selectedMemberList: MemberItem[]) => {
|
||
isSubmitting.value = true;
|
||
|
||
try {
|
||
|
||
// 调用推送API
|
||
const pushData = {
|
||
rwId: taskInfo.value.id,
|
||
jsIds: selectedMemberList.map(member => member.jsId).join(','),
|
||
rwlyId: courseId.value
|
||
};
|
||
|
||
const response = await rwPushJsApi(pushData);
|
||
console.log('推送API响应:', response);
|
||
|
||
if (response && response.resultCode === 1) {
|
||
uni.showToast({
|
||
title: `推送成功!已推送给${selectedCount.value}名成员`,
|
||
icon: 'success',
|
||
duration: 2000
|
||
});
|
||
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 2000);
|
||
} else {
|
||
throw new Error(response?.message);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('推送失败', error);
|
||
uni.showToast({
|
||
title: error?.message,
|
||
icon: 'error'
|
||
});
|
||
} finally {
|
||
isSubmitting.value = false;
|
||
}
|
||
};
|
||
|
||
// 工具函数
|
||
const formatDate = (dateStr: string) => {
|
||
if (!dateStr) return '';
|
||
const date = new Date(dateStr);
|
||
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
|
||
};
|
||
|
||
// 返回上一<E4B88A>?
|
||
const goBack = () => {
|
||
uni.navigateBack();
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.push-page {
|
||
min-height: 100vh;
|
||
background: linear-gradient(135deg, #f5f7fa 0%, #e8f4fd 100%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.task-info-card {
|
||
margin: 16px;
|
||
padding: 16px;
|
||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||
border-radius: 12px;
|
||
box-shadow:
|
||
0 2px 12px rgba(0, 0, 0, 0.06),
|
||
0 1px 3px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 3px;
|
||
background: linear-gradient(90deg, #409eff 0%, #67c23a 50%, #e6a23c 100%);
|
||
}
|
||
|
||
.task-title {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
margin-bottom: 8px;
|
||
line-height: 1.4;
|
||
letter-spacing: 0.2px;
|
||
}
|
||
|
||
.task-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
|
||
.meta-item {
|
||
font-size: 14px;
|
||
color: #6b7280;
|
||
font-weight: 500;
|
||
padding: 4px 8px;
|
||
background: rgba(0, 0, 0, 0.03);
|
||
border-radius: 6px;
|
||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||
}
|
||
}
|
||
}
|
||
|
||
.push-section {
|
||
flex: 1;
|
||
margin: 0 16px 80px 16px;
|
||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||
border-radius: 12px;
|
||
box-shadow:
|
||
0 2px 12px rgba(0, 0, 0, 0.06),
|
||
0 1px 3px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
position: relative;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 3px;
|
||
background: linear-gradient(90deg, #409eff 0%, #67c23a 50%, #e6a23c 100%);
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 16px;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||
margin-top: 3px;
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
letter-spacing: 0.2px;
|
||
}
|
||
|
||
.section-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
|
||
.action-btn {
|
||
color: #409eff;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
padding: 8px 16px;
|
||
border-radius: 8px;
|
||
background: rgba(64, 158, 255, 0.1);
|
||
border: 1px solid rgba(64, 158, 255, 0.2);
|
||
transition: all 0.3s ease;
|
||
|
||
&:active {
|
||
background: rgba(64, 158, 255, 0.2);
|
||
transform: translateY(1px);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.members-scroll {
|
||
flex: 1;
|
||
max-height: 60vh;
|
||
padding: 16px;
|
||
}
|
||
|
||
.member-group-section {
|
||
margin-bottom: 16px;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 10px;
|
||
background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
|
||
&:hover {
|
||
border-color: #409eff;
|
||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
|
||
}
|
||
|
||
.group-header {
|
||
padding: 12px;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||
|
||
.group-checkbox-container {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
|
||
.group-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
|
||
.group-title {
|
||
font-weight: 700;
|
||
color: #409eff;
|
||
font-size: 16px;
|
||
letter-spacing: 0.3px;
|
||
}
|
||
|
||
.group-count {
|
||
color: #6b7280;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.group-members-list {
|
||
padding: 8px 12px;
|
||
}
|
||
|
||
.members-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
}
|
||
}
|
||
|
||
.member-check-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px;
|
||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||
border-radius: 8px;
|
||
border: 1px solid #e5e7eb;
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
|
||
&.is-pushed {
|
||
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
||
border-color: #67c23a;
|
||
opacity: 0.85;
|
||
|
||
.member-avatar {
|
||
background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%);
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
|
||
&:active {
|
||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||
border-color: #409eff;
|
||
transform: translateY(1px);
|
||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
||
}
|
||
|
||
.member-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex: 1;
|
||
|
||
.member-avatar {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 1px 4px rgba(64, 158, 255, 0.3);
|
||
|
||
.avatar-text {
|
||
color: #fff;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||
}
|
||
}
|
||
|
||
.member-details {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
|
||
.member-name {
|
||
display: block;
|
||
font-size: 14px;
|
||
color: #1f2937;
|
||
font-weight: 600;
|
||
letter-spacing: 0.1px;
|
||
}
|
||
|
||
.pushed-badge {
|
||
display: inline-block;
|
||
padding: 2px 6px;
|
||
background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%);
|
||
color: #fff;
|
||
font-size: 10px;
|
||
border-radius: 4px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
box-shadow: 0 1px 3px rgba(103, 194, 58, 0.3);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.checkbox-box {
|
||
width: 18px;
|
||
height: 18px;
|
||
border: 2px solid #d1d5db;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #fff;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||
|
||
&.checked {
|
||
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
||
border-color: #409eff;
|
||
box-shadow: 0 2px 6px rgba(64, 158, 255, 0.3);
|
||
|
||
.check-icon {
|
||
color: #fff;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||
}
|
||
}
|
||
|
||
&.indeterminate {
|
||
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
||
border-color: #409eff;
|
||
box-shadow: 0 2px 6px rgba(64, 158, 255, 0.3);
|
||
|
||
.check-icon {
|
||
color: #fff;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
line-height: 1;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40px 20px;
|
||
color: #666;
|
||
|
||
.empty-text {
|
||
font-size: 16px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.empty-hint {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.push-summary {
|
||
padding: 12px 16px;
|
||
background: linear-gradient(135deg, #f6ffed 0%, #f0f9ff 100%);
|
||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||
text-align: center;
|
||
|
||
.summary-text {
|
||
color: #52c41a;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
letter-spacing: 0.1px;
|
||
}
|
||
}
|
||
|
||
.bottom-actions {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: #fff;
|
||
border-top: 1px solid #e5e5e5;
|
||
padding: 12px 16px;
|
||
z-index: 999;
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 12px;
|
||
|
||
.cancel-btn, .confirm-btn {
|
||
flex: 1;
|
||
height: 40px;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
|
||
&:active {
|
||
transform: translateY(1px);
|
||
}
|
||
}
|
||
|
||
.cancel-btn {
|
||
background-color: #909399;
|
||
color: #fff;
|
||
|
||
&:hover {
|
||
background-color: #82848a;
|
||
}
|
||
}
|
||
|
||
.confirm-btn {
|
||
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
||
color: #fff;
|
||
|
||
&:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
&:hover:not(:disabled) {
|
||
background: linear-gradient(135deg, #3a8ee6 0%, #337ecc 100%);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 响应式调<E5BC8F>?
|
||
@media screen and (max-width: 375px) {
|
||
.section-header {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 8px;
|
||
}
|
||
|
||
.section-actions {
|
||
justify-content: center;
|
||
}
|
||
|
||
.member-check-item {
|
||
padding: 6px;
|
||
|
||
.member-avatar {
|
||
width: 28px;
|
||
height: 28px;
|
||
|
||
.avatar-text {
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 推送遮罩层 */
|
||
.push-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 9999;
|
||
backdrop-filter: blur(4px);
|
||
|
||
.push-loading {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
border-radius: 16px;
|
||
padding: 32px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 16px;
|
||
box-shadow:
|
||
0 8px 32px rgba(0, 0, 0, 0.1),
|
||
0 2px 8px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
backdrop-filter: blur(10px);
|
||
min-width: 200px;
|
||
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 3px solid rgba(64, 158, 255, 0.2);
|
||
border-top: 3px solid #409eff;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
text-align: center;
|
||
letter-spacing: 0.2px;
|
||
}
|
||
|
||
.loading-hint {
|
||
font-size: 12px;
|
||
color: #6b7280;
|
||
text-align: center;
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* 遮罩层响应式调整 */
|
||
@media screen and (max-width: 375px) {
|
||
.push-overlay .push-loading {
|
||
margin: 20px;
|
||
padding: 24px;
|
||
|
||
.loading-spinner {
|
||
width: 32px;
|
||
height: 32px;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.loading-hint {
|
||
font-size: 11px;
|
||
}
|
||
}
|
||
}
|
||
</style>
|