2025-10-07 08:58:02 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="addkcrw-page">
|
2025-10-11 21:11:16 +08:00
|
|
|
|
<!-- 提交遮罩层 -->
|
|
|
|
|
|
<view v-if="isSubmitting" class="submit-overlay">
|
|
|
|
|
|
<view class="submit-loading">
|
|
|
|
|
|
<view class="loading-spinner"></view>
|
|
|
|
|
|
<text class="loading-text">保存中...</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2025-10-07 08:58:02 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 表单内容 -->
|
|
|
|
|
|
<view class="form-container">
|
|
|
|
|
|
<scroll-view scroll-y class="form-scroll">
|
|
|
|
|
|
<!-- 基本信息区域 -->
|
|
|
|
|
|
<view class="section-card">
|
|
|
|
|
|
<view class="section-title">
|
|
|
|
|
|
<text class="title-text">基本信息</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 任务名称 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<text class="item-label required">任务名称:</text>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="formData.rwmc"
|
|
|
|
|
|
placeholder="请输入任务名称"
|
|
|
|
|
|
class="item-input"
|
|
|
|
|
|
maxlength="100"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 任务描述 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<text class="item-label">任务描述:</text>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="formData.rwms"
|
|
|
|
|
|
placeholder="请输入任务描述"
|
|
|
|
|
|
class="item-textarea"
|
|
|
|
|
|
maxlength="500"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 截止时间 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<text class="item-label required">截止时间:</text>
|
|
|
|
|
|
<picker
|
|
|
|
|
|
mode="date"
|
|
|
|
|
|
:value="formData.rwjstime"
|
|
|
|
|
|
@change="onDateChange"
|
|
|
|
|
|
class="item-picker"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="picker-content">
|
|
|
|
|
|
<text :class="['picker-text', { placeholder: !formData.rwjstime }]">
|
|
|
|
|
|
{{ formData.rwjstime || '请选择截止时间' }}
|
|
|
|
|
|
</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</picker>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 负责人 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<text class="item-label required">负责人:</text>
|
|
|
|
|
|
<BasicJsPicker
|
|
|
|
|
|
:multiple="true"
|
|
|
|
|
|
title="选择负责人"
|
|
|
|
|
|
placeholder="请选择负责人"
|
|
|
|
|
|
searchPlaceholder="输入教师名称查询"
|
|
|
|
|
|
@change="onLeaderChange"
|
|
|
|
|
|
:defaultValue="selectedLeaderIds"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 附件上传 -->
|
|
|
|
|
|
<view class="form-item attachment-item">
|
|
|
|
|
|
<text class="item-label">附件:</text>
|
|
|
|
|
|
<view class="attachment-container">
|
|
|
|
|
|
<ImageVideoUpload
|
|
|
|
|
|
v-model:file-list="fileList"
|
|
|
|
|
|
:enable-image="false"
|
|
|
|
|
|
:enable-video="false"
|
|
|
|
|
|
:enable-file="true"
|
|
|
|
|
|
:max-file-count="5"
|
|
|
|
|
|
:allowed-file-types="['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'mp3', 'wav', 'zip', 'rar']"
|
|
|
|
|
|
:compress-config="compressConfig"
|
|
|
|
|
|
:upload-api="attachmentUpload"
|
|
|
|
|
|
@file-upload-success="onFileUploadSuccess"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 任务下发设置区域 -->
|
|
|
|
|
|
<view class="section-card">
|
|
|
|
|
|
<view class="section-title">
|
|
|
|
|
|
<text class="title-text">任务下发设置</text>
|
|
|
|
|
|
<view class="add-task-type-btn" @click="addTaskType">
|
|
|
|
|
|
<text class="add-icon">+</text>
|
|
|
|
|
|
<text class="add-text">新增任务方式</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 任务方式列表 -->
|
|
|
|
|
|
<view v-for="(taskType, typeIndex) in taskTypes" :key="taskType.id" class="task-type-section">
|
|
|
|
|
|
<!-- 任务方式选择 -->
|
|
|
|
|
|
<view class="task-type-header">
|
|
|
|
|
|
<view class="task-type-selector">
|
|
|
|
|
|
<text class="selector-label">任务方式:</text>
|
|
|
|
|
|
<picker
|
|
|
|
|
|
:range="taskTypeOptions"
|
|
|
|
|
|
range-key="label"
|
|
|
|
|
|
:value="getTaskTypeIndex(taskType.rwfl)"
|
|
|
|
|
|
@change="onTaskTypeChange($event, typeIndex)"
|
|
|
|
|
|
class="type-picker"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="picker-content">
|
|
|
|
|
|
<text class="picker-text">{{ getTaskTypeLabel(taskType.rwfl) }}</text>
|
|
|
|
|
|
<text class="picker-arrow">▼</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</picker>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="delete-type-btn" @click="removeTaskType(typeIndex)">
|
|
|
|
|
|
<text class="delete-icon">×</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 任务项列表 -->
|
|
|
|
|
|
<view class="task-items-container">
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="(item, itemIndex) in taskType.items"
|
|
|
|
|
|
:key="item.key"
|
|
|
|
|
|
class="task-item-card"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 删除按钮放在右上角 -->
|
|
|
|
|
|
<view class="delete-item-btn-top" @click="removeTaskItem(taskType, itemIndex)">
|
|
|
|
|
|
<text class="delete-icon">×</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="task-item-header">
|
|
|
|
|
|
<text class="item-number">{{ itemIndex + 1 }}</text>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="item.rwbt"
|
|
|
|
|
|
:placeholder="`任务项 ${itemIndex + 1}`"
|
|
|
|
|
|
class="item-title-input"
|
|
|
|
|
|
maxlength="100"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<view class="item-actions">
|
|
|
|
|
|
<view class="required-checkbox" @click="toggleRequired(taskType, itemIndex)">
|
|
|
|
|
|
<view :class="['checkbox-box', { checked: item.rwbs }]">
|
|
|
|
|
|
<text v-if="item.rwbs" class="check-icon">✓</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-if="showTooltip" class="tooltip">默认必填</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 选择题选项 -->
|
|
|
|
|
|
<view v-if="taskType.rwfl === 'dxxz' || taskType.rwfl === 'dxsx'" class="item-options">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="item.remark"
|
2025-10-11 21:11:16 +08:00
|
|
|
|
:placeholder="taskType.rwfl === 'dxxz' ? '单项选择,请输入如:选项1;选项2;选项3' : '多项选择,请输入如:选项1;选项2;选项3'"
|
2025-10-07 08:58:02 +08:00
|
|
|
|
class="options-textarea"
|
|
|
|
|
|
maxlength="500"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 添加任务项按钮 -->
|
|
|
|
|
|
<view class="add-item-btn" @click="addTaskItem(taskType)">
|
|
|
|
|
|
<text class="add-icon">+</text>
|
|
|
|
|
|
<text class="add-text">添加任务项</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view>
|
|
|
|
|
|
<!-- 空内容,删除了负责人选择弹窗 -->
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 底部操作按钮 -->
|
|
|
|
|
|
<view class="bottom-actions">
|
|
|
|
|
|
<view class="action-buttons">
|
|
|
|
|
|
<button
|
|
|
|
|
|
@click="goBack"
|
|
|
|
|
|
class="cancel-btn"
|
|
|
|
|
|
>
|
|
|
|
|
|
取消
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
@click="submitTask"
|
|
|
|
|
|
:disabled="isSubmitting"
|
|
|
|
|
|
class="submit-btn"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ isSubmitting ? '保存中...' : '保存' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
|
import { ref, reactive } from "vue";
|
|
|
|
|
|
import { onLoad } from "@dcloudio/uni-app";
|
|
|
|
|
|
import { kccyFindByKcjbIdApi } from "@/api/base/kccyApi";
|
|
|
|
|
|
import { rwSaveApi } from "@/api/base/rwApi";
|
|
|
|
|
|
import { attachmentUpload } from "@/api/system/upload";
|
|
|
|
|
|
import BasicJsPicker from "@/components/BasicJsPicker/Picker.vue";
|
|
|
|
|
|
import { ImageVideoUpload, type FileItem, COMPRESS_PRESETS } from "@/components/ImageVideoUpload";
|
|
|
|
|
|
|
|
|
|
|
|
// 接口类型定义
|
|
|
|
|
|
interface CourseInfo {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
kcmc: string;
|
|
|
|
|
|
kcms: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface CourseMember {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
jsId: string;
|
|
|
|
|
|
jsxm: string;
|
|
|
|
|
|
kcjbId: string;
|
|
|
|
|
|
fzmc?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface TaskType {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
rwfl: string;
|
|
|
|
|
|
items: TaskItem[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface TaskItem {
|
|
|
|
|
|
key: string;
|
|
|
|
|
|
id?: string; // 任务项ID,编辑时需要
|
|
|
|
|
|
rwbt: string;
|
|
|
|
|
|
rwbs: boolean;
|
|
|
|
|
|
remark: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 页面数据
|
|
|
|
|
|
const courseId = ref('');
|
|
|
|
|
|
const taskId = ref('');
|
|
|
|
|
|
const courseInfo = ref<CourseInfo>({
|
|
|
|
|
|
id: '',
|
|
|
|
|
|
kcmc: '',
|
|
|
|
|
|
kcms: ''
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const courseMembers = ref<CourseMember[]>([]);
|
|
|
|
|
|
const selectedLeaders = ref<any[]>([]);
|
|
|
|
|
|
const selectedLeaderIds = ref<string[]>([]);
|
|
|
|
|
|
const isSubmitting = ref(false);
|
|
|
|
|
|
const isLoadingMembers = ref(false);
|
2025-10-11 21:11:16 +08:00
|
|
|
|
const submitTimer = ref<any>(null); // 防抖定时器
|
2025-10-07 08:58:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 冒泡提示显示状态
|
|
|
|
|
|
const showTooltip = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 附件相关数据
|
|
|
|
|
|
const compressConfig = ref(COMPRESS_PRESETS.medium);
|
|
|
|
|
|
const fileList = ref<FileItem[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
|
const formData = reactive({
|
|
|
|
|
|
id: '', // 任务ID,编辑时需要
|
|
|
|
|
|
rwmc: '',
|
|
|
|
|
|
rwms: '',
|
|
|
|
|
|
rwjstime: '',
|
|
|
|
|
|
rwfzr: '',
|
|
|
|
|
|
rwly: 'B',
|
|
|
|
|
|
rwlyId: '',
|
|
|
|
|
|
rwtsfs: '1', // 默认推送
|
|
|
|
|
|
rwfj: '', // 附件URL
|
|
|
|
|
|
fjmx: '' // 附件明细
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 任务类型选项
|
|
|
|
|
|
const taskTypeOptions = [
|
|
|
|
|
|
{ label: '填写文本', value: 'text' },
|
|
|
|
|
|
{ label: '单项选择', value: 'dxxz' },
|
|
|
|
|
|
{ label: '多项选择', value: 'dxsx' },
|
|
|
|
|
|
{ label: '上传图片', value: 'sctp' },
|
|
|
|
|
|
{ label: '上传视频', value: 'scsp' },
|
|
|
|
|
|
{ label: '上传文档', value: 'scwd' }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 任务方式列表
|
|
|
|
|
|
const taskTypes = ref<TaskType[]>([
|
|
|
|
|
|
{
|
|
|
|
|
|
id: `task_type_${Date.now()}`,
|
|
|
|
|
|
rwfl: 'text',
|
|
|
|
|
|
items: []
|
|
|
|
|
|
}
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载
|
|
|
|
|
|
onLoad((options: any) => {
|
|
|
|
|
|
console.log('编辑任务页面接收到的参数:', options);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取任务ID
|
|
|
|
|
|
if (options.taskId) {
|
|
|
|
|
|
taskId.value = options.taskId;
|
|
|
|
|
|
formData.id = options.taskId;
|
|
|
|
|
|
console.log('设置任务ID为:', taskId.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 支持多种参数名(kcjbId 或 courseId)
|
|
|
|
|
|
const receivedCourseId = options.kcjbId || options.courseId;
|
|
|
|
|
|
|
|
|
|
|
|
if (receivedCourseId) {
|
|
|
|
|
|
courseId.value = receivedCourseId;
|
|
|
|
|
|
formData.rwlyId = receivedCourseId;
|
|
|
|
|
|
console.log('设置课程ID为:', courseId.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果传递了课程名称,设置课程信息
|
|
|
|
|
|
if (options.kcmc) {
|
|
|
|
|
|
courseInfo.value.kcmc = decodeURIComponent(options.kcmc);
|
|
|
|
|
|
console.log('设置课程名称为:', courseInfo.value.kcmc);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loadCourseInfo();
|
|
|
|
|
|
loadCourseMembers();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是编辑模式,加载任务数据
|
|
|
|
|
|
if (taskId.value) {
|
|
|
|
|
|
// 从全局存储中获取任务数据
|
|
|
|
|
|
loadTaskDataFromStorage();
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('未接收到课程ID参数');
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '参数错误,请重新进入',
|
|
|
|
|
|
icon: 'error'
|
|
|
|
|
|
});
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.navigateBack();
|
|
|
|
|
|
}, 1500);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 加载课程信息
|
|
|
|
|
|
const loadCourseInfo = () => {
|
|
|
|
|
|
console.log('加载课程信息,课程ID:', courseId.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经有课程名称,保持不变;否则设置默认值
|
|
|
|
|
|
if (!courseInfo.value.kcmc) {
|
|
|
|
|
|
courseInfo.value.kcmc = '课程名称加载中...';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
courseInfo.value.id = courseId.value;
|
|
|
|
|
|
courseInfo.value.kcms = '课程描述加载中...';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 从全局存储中加载任务数据
|
|
|
|
|
|
const loadTaskDataFromStorage = () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('从全局存储中加载任务数据');
|
|
|
|
|
|
const taskInfo = uni.getStorageSync('editTaskData');
|
|
|
|
|
|
|
|
|
|
|
|
if (!taskInfo) {
|
|
|
|
|
|
console.error('未找到任务数据');
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '任务数据不存在',
|
|
|
|
|
|
icon: 'error'
|
|
|
|
|
|
});
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.navigateBack();
|
|
|
|
|
|
}, 1500);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('任务数据:', taskInfo);
|
|
|
|
|
|
|
|
|
|
|
|
// 填充表单数据
|
|
|
|
|
|
formData.rwmc = taskInfo.rwmc || '';
|
|
|
|
|
|
formData.rwms = taskInfo.rwms || '';
|
|
|
|
|
|
formData.rwjstime = taskInfo.rwjstime || '';
|
|
|
|
|
|
formData.rwfzr = taskInfo.rwfzr || '';
|
|
|
|
|
|
formData.rwtsfs = taskInfo.rwtsfs || '1';
|
|
|
|
|
|
formData.rwfj = taskInfo.rwfj || '';
|
|
|
|
|
|
formData.fjmx = taskInfo.fjmx || '';
|
|
|
|
|
|
|
|
|
|
|
|
// 处理负责人数据
|
|
|
|
|
|
if (taskInfo.rwfzr) {
|
|
|
|
|
|
const leaderIds = taskInfo.rwfzr.split(',');
|
|
|
|
|
|
selectedLeaderIds.value = leaderIds;
|
|
|
|
|
|
// 这里需要根据ID找到对应的教师信息
|
|
|
|
|
|
// 暂时使用ID作为显示名称
|
|
|
|
|
|
selectedLeaders.value = leaderIds.map((id: string) => ({
|
|
|
|
|
|
id: id,
|
|
|
|
|
|
jsxm: taskInfo.rwfzrxm || `教师${id}`
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理任务类型数据
|
|
|
|
|
|
if (taskInfo.rwlxes && taskInfo.rwlxes.length > 0) {
|
|
|
|
|
|
// 按任务类型分组
|
|
|
|
|
|
const groupedTasks: { [key: string]: any[] } = {};
|
|
|
|
|
|
taskInfo.rwlxes.forEach((item: any) => {
|
|
|
|
|
|
if (!groupedTasks[item.rwfl]) {
|
|
|
|
|
|
groupedTasks[item.rwfl] = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
groupedTasks[item.rwfl].push(item);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 构建任务类型列表
|
|
|
|
|
|
taskTypes.value = Object.keys(groupedTasks).map((rwfl, index) => ({
|
|
|
|
|
|
id: `task_type_${index + 1}`,
|
|
|
|
|
|
rwfl: rwfl,
|
|
|
|
|
|
items: groupedTasks[rwfl].map((item, itemIndex) => ({
|
|
|
|
|
|
key: `item_${index + 1}_${itemIndex + 1}`,
|
|
|
|
|
|
id: item.id, // 保留任务项的ID
|
|
|
|
|
|
rwbt: item.rwbt || '',
|
|
|
|
|
|
rwbs: item.rwbs === 1 || item.rwbs === true,
|
|
|
|
|
|
remark: item.remark || ''
|
|
|
|
|
|
}))
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理附件数据
|
|
|
|
|
|
if (taskInfo.rwfj) {
|
|
|
|
|
|
loadAttachmentData(taskInfo.rwfj, taskInfo.fjmx);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('任务数据加载完成');
|
|
|
|
|
|
|
|
|
|
|
|
// 清除存储的任务数据
|
|
|
|
|
|
uni.removeStorageSync('editTaskData');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载任务数据失败:', error);
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '加载任务数据失败',
|
|
|
|
|
|
icon: 'error'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载附件数据
|
|
|
|
|
|
const loadAttachmentData = (attachmentUrls: string, attachmentDetails?: string) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('加载附件数据:', { attachmentUrls, attachmentDetails });
|
|
|
|
|
|
|
|
|
|
|
|
if (!attachmentUrls) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const urls = attachmentUrls.split(',');
|
|
|
|
|
|
const files: FileItem[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 解析附件明细,获取文件名
|
|
|
|
|
|
let fileNames: string[] = [];
|
|
|
|
|
|
if (attachmentDetails) {
|
|
|
|
|
|
// 处理 "文件1:file_1;文件2:新建 Microsoft Word 文档.docx" 格式
|
|
|
|
|
|
const details = attachmentDetails.split(';');
|
|
|
|
|
|
fileNames = details.map(detail => {
|
|
|
|
|
|
// 如果有 "文件X:" 前缀,去掉前缀
|
|
|
|
|
|
const colonIndex = detail.indexOf(':');
|
|
|
|
|
|
if (colonIndex > -1) {
|
|
|
|
|
|
return detail.substring(colonIndex + 1).trim();
|
|
|
|
|
|
}
|
|
|
|
|
|
return detail.trim();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
urls.forEach((url, index) => {
|
|
|
|
|
|
if (!url.trim()) return;
|
|
|
|
|
|
|
|
|
|
|
|
const extension = url.toLowerCase().split('.').pop();
|
|
|
|
|
|
|
|
|
|
|
|
// 确定文件类型
|
|
|
|
|
|
let fileType = 'document';
|
|
|
|
|
|
if (['mp4', 'mov', 'avi', 'wmv', 'flv'].includes(extension || '')) {
|
|
|
|
|
|
fileType = 'video';
|
|
|
|
|
|
} else if (['mp3', 'wav', 'aac', 'ogg'].includes(extension || '')) {
|
|
|
|
|
|
fileType = 'audio';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用解析出的文件名,如果没有则使用默认名称
|
|
|
|
|
|
const fileName = fileNames[index] || `file_${index + 1}`;
|
|
|
|
|
|
|
|
|
|
|
|
files.push({
|
|
|
|
|
|
url: url.trim(),
|
|
|
|
|
|
name: fileName,
|
|
|
|
|
|
type: fileType,
|
|
|
|
|
|
extension: extension || '',
|
|
|
|
|
|
tempPath: ''
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
fileList.value = files;
|
|
|
|
|
|
|
|
|
|
|
|
console.log('附件数据加载完成:', {
|
|
|
|
|
|
files: files.length,
|
|
|
|
|
|
fileNames: fileNames
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载附件数据失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载任务数据(编辑模式)
|
|
|
|
|
|
const loadTaskData = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('加载任务数据,任务ID:', taskId.value);
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: 调用API获取任务详情
|
|
|
|
|
|
// const response = await rwFindByIdApi(taskId.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟数据加载
|
|
|
|
|
|
uni.showLoading({ title: '加载中...' });
|
|
|
|
|
|
|
|
|
|
|
|
// 这里应该调用真实的API获取任务数据
|
|
|
|
|
|
// 暂时使用模拟数据
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
// 模拟加载任务数据
|
|
|
|
|
|
formData.rwmc = '示例任务名称';
|
|
|
|
|
|
formData.rwms = '示例任务描述';
|
|
|
|
|
|
formData.rwjstime = '2024-12-31';
|
|
|
|
|
|
formData.rwfzr = '1,2';
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟负责人数据
|
|
|
|
|
|
selectedLeaderIds.value = ['1', '2'];
|
|
|
|
|
|
selectedLeaders.value = [
|
|
|
|
|
|
{ id: '1', jsxm: '张三' },
|
|
|
|
|
|
{ id: '2', jsxm: '李四' }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟任务类型数据
|
|
|
|
|
|
taskTypes.value = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'task_type_1',
|
|
|
|
|
|
rwfl: 'text',
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{
|
|
|
|
|
|
key: 'item_1',
|
|
|
|
|
|
rwbt: '示例任务项',
|
|
|
|
|
|
rwbs: true,
|
|
|
|
|
|
remark: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载任务数据失败:', error);
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '加载任务数据失败',
|
|
|
|
|
|
icon: 'error'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载课程成员
|
|
|
|
|
|
const loadCourseMembers = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
isLoadingMembers.value = true;
|
|
|
|
|
|
console.log('开始加载课程成员,课程ID:', courseId.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 调用真实API获取课程成员
|
|
|
|
|
|
const response = await kccyFindByKcjbIdApi({ kcjbId: courseId.value });
|
|
|
|
|
|
|
|
|
|
|
|
let memberData = [];
|
|
|
|
|
|
if (response && (response.resultCode === 1 || response.resultCode === 0)) {
|
|
|
|
|
|
memberData = response.result || response.rows || response.data || [];
|
|
|
|
|
|
} else if (Array.isArray(response)) {
|
|
|
|
|
|
memberData = response;
|
|
|
|
|
|
} else if (response && response.length !== undefined) {
|
|
|
|
|
|
memberData = response;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
courseMembers.value = memberData;
|
|
|
|
|
|
console.log('课程成员加载成功:', courseMembers.value.length, '人');
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载课程成员失败:', error);
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '加载成员失败',
|
|
|
|
|
|
icon: 'error'
|
|
|
|
|
|
});
|
|
|
|
|
|
courseMembers.value = [];
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
isLoadingMembers.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 日期选择
|
|
|
|
|
|
const onDateChange = (e: any) => {
|
|
|
|
|
|
formData.rwjstime = e.detail.value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 负责人选择变化
|
|
|
|
|
|
const onLeaderChange = (selectedList: any[]) => {
|
|
|
|
|
|
console.log('负责人选择变化:', selectedList);
|
|
|
|
|
|
selectedLeaders.value = selectedList || [];
|
|
|
|
|
|
selectedLeaderIds.value = selectedLeaders.value.map(leader => leader.id);
|
|
|
|
|
|
formData.rwfzr = selectedLeaders.value.map(leader => leader.id).join(',');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 附件上传成功回调
|
|
|
|
|
|
const onFileUploadSuccess = (file: FileItem, index: number) => {
|
|
|
|
|
|
console.log('文件上传成功:', file, index);
|
|
|
|
|
|
updateAttachmentFields();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 更新附件字段
|
|
|
|
|
|
const updateAttachmentFields = () => {
|
|
|
|
|
|
// 收集文件URL
|
|
|
|
|
|
const fileUrls = fileList.value
|
|
|
|
|
|
.filter(file => file.url)
|
|
|
|
|
|
.map(file => file.url)
|
|
|
|
|
|
.filter((url): url is string => !!url);
|
|
|
|
|
|
|
|
|
|
|
|
formData.rwfj = fileUrls.join(',');
|
|
|
|
|
|
|
|
|
|
|
|
// 更新附件明细字段 - 只使用文件名,用分号分隔
|
|
|
|
|
|
const fileNames = fileList.value
|
|
|
|
|
|
.filter(file => file.url)
|
|
|
|
|
|
.map(file => file.name || `file_${Date.now()}`);
|
|
|
|
|
|
|
|
|
|
|
|
formData.fjmx = fileNames.join(';');
|
|
|
|
|
|
|
|
|
|
|
|
console.log('附件字段更新:', {
|
|
|
|
|
|
rwfj: formData.rwfj,
|
|
|
|
|
|
fjmx: formData.fjmx
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 任务类型管理函数
|
|
|
|
|
|
const addTaskType = () => {
|
|
|
|
|
|
const uniqueId = `task_type_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
|
|
taskTypes.value.push({
|
|
|
|
|
|
id: uniqueId,
|
|
|
|
|
|
rwfl: 'text',
|
|
|
|
|
|
items: []
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const removeTaskType = (typeIndex: number) => {
|
|
|
|
|
|
taskTypes.value.splice(typeIndex, 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getTaskTypeIndex = (rwfl: string) => {
|
|
|
|
|
|
return taskTypeOptions.findIndex(option => option.value === rwfl);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getTaskTypeLabel = (rwfl: string) => {
|
|
|
|
|
|
const option = taskTypeOptions.find(option => option.value === rwfl);
|
|
|
|
|
|
return option ? option.label : '填写文本';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onTaskTypeChange = (e: any, typeIndex: number) => {
|
|
|
|
|
|
const selectedIndex = e.detail.value;
|
|
|
|
|
|
const selectedType = taskTypeOptions[selectedIndex];
|
|
|
|
|
|
taskTypes.value[typeIndex].rwfl = selectedType.value;
|
|
|
|
|
|
// 清空现有的任务项
|
|
|
|
|
|
taskTypes.value[typeIndex].items = [];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const addTaskItem = (taskType: TaskType) => {
|
|
|
|
|
|
taskType.items.push({
|
|
|
|
|
|
key: `item_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
|
|
|
|
// 新增的任务项不设置id,让后端生成新的ID
|
|
|
|
|
|
rwbt: '',
|
|
|
|
|
|
rwbs: true, // 默认必填项打勾
|
|
|
|
|
|
remark: ''
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 显示冒泡提示 - 使用签到界面风格
|
|
|
|
|
|
showTooltip.value = true;
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
showTooltip.value = false;
|
|
|
|
|
|
}, 3000);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const removeTaskItem = (taskType: TaskType, itemIndex: number) => {
|
|
|
|
|
|
taskType.items.splice(itemIndex, 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const toggleRequired = (taskType: TaskType, itemIndex: number) => {
|
|
|
|
|
|
taskType.items[itemIndex].rwbs = !taskType.items[itemIndex].rwbs;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 表单验证
|
|
|
|
|
|
const validateForm = (): boolean => {
|
|
|
|
|
|
if (!formData.rwmc.trim()) {
|
|
|
|
|
|
uni.showToast({ title: '请输入任务名称', icon: 'error' });
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!formData.rwjstime) {
|
|
|
|
|
|
uni.showToast({ title: '请选择截止时间', icon: 'error' });
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedLeaders.value.length === 0) {
|
|
|
|
|
|
uni.showToast({ title: '请选择负责人', icon: 'error' });
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证是否有任务类型
|
|
|
|
|
|
let hasTaskType = false;
|
|
|
|
|
|
for (const taskType of taskTypes.value) {
|
|
|
|
|
|
if (taskType.items.length > 0) {
|
|
|
|
|
|
hasTaskType = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasTaskType) {
|
|
|
|
|
|
uni.showToast({ title: '请至少添加一个任务项', icon: 'error' });
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-11 21:11:16 +08:00
|
|
|
|
// 验证任务项:如果打勾(必填)且是单选/多选,则 remark 必填
|
|
|
|
|
|
for (const taskType of taskTypes.value) {
|
|
|
|
|
|
const taskTypeName = getTaskTypeLabel(taskType.rwfl);
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < taskType.items.length; i++) {
|
|
|
|
|
|
const item = taskType.items[i];
|
|
|
|
|
|
|
|
|
|
|
|
// 如果任务项打勾(必填)且是单项选择或多项选择
|
|
|
|
|
|
if (item.rwbs && (taskType.rwfl === 'dxxz' || taskType.rwfl === 'dxsx')) {
|
|
|
|
|
|
if (!item.remark || !item.remark.trim()) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: `${taskTypeName}的任务项${i + 1}选项内容不能为空`,
|
|
|
|
|
|
icon: 'error',
|
|
|
|
|
|
duration: 2000
|
|
|
|
|
|
});
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 08:58:02 +08:00
|
|
|
|
return true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 提交任务
|
|
|
|
|
|
const submitTask = async () => {
|
2025-10-11 21:11:16 +08:00
|
|
|
|
// 防抖处理
|
|
|
|
|
|
if (isSubmitting.value) {
|
|
|
|
|
|
console.log('正在提交中,请勿重复点击');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清除之前的定时器
|
|
|
|
|
|
if (submitTimer.value) {
|
|
|
|
|
|
clearTimeout(submitTimer.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置防抖延迟
|
|
|
|
|
|
submitTimer.value = setTimeout(async () => {
|
|
|
|
|
|
await performSubmit();
|
|
|
|
|
|
}, 300);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function performSubmit() {
|
|
|
|
|
|
if (isSubmitting.value) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-07 08:58:02 +08:00
|
|
|
|
if (!validateForm()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
isSubmitting.value = true;
|
|
|
|
|
|
console.log('开始提交任务:', formData);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新附件字段
|
|
|
|
|
|
updateAttachmentFields();
|
|
|
|
|
|
|
|
|
|
|
|
// 构建提交数据
|
|
|
|
|
|
const submitData = {
|
|
|
|
|
|
id: formData.id, // 编辑时需要传递ID
|
|
|
|
|
|
rwmc: formData.rwmc,
|
|
|
|
|
|
rwjstime: formData.rwjstime,
|
|
|
|
|
|
rwms: formData.rwms,
|
|
|
|
|
|
rwfzr: formData.rwfzr,
|
|
|
|
|
|
rwly: formData.rwly,
|
|
|
|
|
|
rwlyId: formData.rwlyId,
|
|
|
|
|
|
rwtsfs: formData.rwtsfs,
|
|
|
|
|
|
rwfj: formData.rwfj, // 附件URL
|
|
|
|
|
|
fjmx: formData.fjmx, // 附件明细
|
|
|
|
|
|
rwlxes: [] as any[]
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 收集所有任务类型
|
|
|
|
|
|
taskTypes.value.forEach(taskType => {
|
|
|
|
|
|
taskType.items.forEach(item => {
|
|
|
|
|
|
if (item.rwbt.trim()) {
|
|
|
|
|
|
const taskItem: any = {
|
|
|
|
|
|
rwfl: taskType.rwfl,
|
|
|
|
|
|
rwbt: item.rwbt,
|
|
|
|
|
|
rwbs: item.rwbs ? 1 : 0,
|
|
|
|
|
|
remark: item.remark
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 如果任务项有ID,说明是编辑现有记录,需要传递ID
|
|
|
|
|
|
if (item.id) {
|
|
|
|
|
|
taskItem.id = item.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
submitData.rwlxes.push(taskItem);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await rwSaveApi(submitData);
|
|
|
|
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: taskId.value ? '任务修改成功' : '任务创建成功',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟返回上一页,并刷新上一页数据
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.navigateBack({
|
|
|
|
|
|
success: () => {
|
|
|
|
|
|
// 通知上一页刷新数据
|
|
|
|
|
|
uni.$emit('refreshTaskList');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}, 1500);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('提交任务失败:', error);
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '提交失败,请重试',
|
|
|
|
|
|
icon: 'error'
|
|
|
|
|
|
});
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
isSubmitting.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 返回上一页
|
|
|
|
|
|
const goBack = () => {
|
|
|
|
|
|
uni.navigateBack();
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
/* 第一步:基础页面布局和卡片样式 */
|
|
|
|
|
|
.addkcrw-page {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #e8f4fd 100%);
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-container {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow: visible;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
|
|
|
|
|
|
.form-scroll {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
padding-bottom: 80px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-card {
|
|
|
|
|
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
|
margin: 0 0 20px 0;
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
box-shadow:
|
|
|
|
|
|
0 4px 20px rgba(0, 0, 0, 0.08),
|
|
|
|
|
|
0 1px 3px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
overflow: visible;
|
|
|
|
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
height: 3px;
|
|
|
|
|
|
background: linear-gradient(90deg, #409eff 0%, #67c23a 50%, #e6a23c 100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
padding-bottom: 12px;
|
|
|
|
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
|
|
|
|
|
|
.title-text {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
|
letter-spacing: 0.3px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.add-task-type-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.add-icon {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.item-label {
|
|
|
|
|
|
min-width: 90px;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
line-height: 44px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
|
|
|
|
|
|
&.required::after {
|
|
|
|
|
|
content: '*';
|
|
|
|
|
|
color: #ef4444;
|
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
border: 2px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
|
|
|
|
|
|
&:focus {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-textarea {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
border: 2px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
min-height: 67px;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
resize: vertical;
|
|
|
|
|
|
|
|
|
|
|
|
&:focus {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-picker {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
|
|
|
|
.picker-content {
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
border: 2px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.picker-text {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
|
|
|
|
|
|
&.placeholder {
|
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 附件上传区域
|
|
|
|
|
|
.attachment-item {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
|
|
|
|
|
|
.item-label {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.attachment-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 任务类型区域
|
|
|
|
|
|
.task-type-section {
|
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
border: 2px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-type-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
.task-type-selector {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
|
|
|
|
.selector-label {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
min-width: 80px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-picker {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
max-width: 180px;
|
|
|
|
|
|
|
|
|
|
|
|
.picker-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 10px 14px;
|
|
|
|
|
|
border: 2px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.picker-arrow {
|
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.delete-type-btn {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: -8px;
|
|
|
|
|
|
right: -8px;
|
|
|
|
|
|
width: 24px;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
border: 2px solid #ffffff;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
transform: scale(1.1);
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 任务项列表
|
|
|
|
|
|
.task-items-container {
|
|
|
|
|
|
.task-item-card {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border: 2px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 右上角删除按钮
|
|
|
|
|
|
.delete-item-btn-top {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: -8px;
|
|
|
|
|
|
right: -8px;
|
|
|
|
|
|
width: 24px;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
border: 2px solid #ffffff;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
transform: scale(1.1);
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-item-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.item-number {
|
|
|
|
|
|
width: 28px;
|
|
|
|
|
|
height: 28px;
|
|
|
|
|
|
background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
box-shadow: 0 2px 6px rgba(64, 158, 255, 0.3);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-title-input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 10px 14px;
|
|
|
|
|
|
border: 2px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:focus {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.required-checkbox {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
padding: 4px;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background-color: rgba(64, 158, 255, 0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.checkbox-box {
|
|
|
|
|
|
width: 18px;
|
|
|
|
|
|
height: 18px;
|
|
|
|
|
|
border: 2px solid #d1d5db;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&.checked {
|
|
|
|
|
|
background-color: #409eff;
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
|
|
|
|
|
|
.check-icon {
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 冒泡提示样式
|
|
|
|
|
|
.tooltip {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: -45px;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
|
background: rgba(0, 0, 0, 0.8);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
animation: tooltipFadeIn 0.3s ease-out;
|
|
|
|
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 100%;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
|
border: 5px solid transparent;
|
|
|
|
|
|
border-top-color: rgba(0, 0, 0, 0.8);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.item-options {
|
|
|
|
|
|
.options-textarea {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
border: 2px solid #e5e7eb;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
min-height: 80px;
|
2025-10-11 21:11:16 +08:00
|
|
|
|
background-color: #ffffff;
|
2025-10-07 08:58:02 +08:00
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
resize: vertical;
|
|
|
|
|
|
|
|
|
|
|
|
&:focus {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.add-item-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
padding: 14px;
|
|
|
|
|
|
border: 2px dashed #409eff;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: linear-gradient(135deg, rgba(64, 158, 255, 0.05) 0%, rgba(64, 158, 255, 0.1) 100%);
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: linear-gradient(135deg, rgba(64, 158, 255, 0.1) 0%, rgba(64, 158, 255, 0.15) 100%);
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.add-icon {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 单选按钮组
|
|
|
|
|
|
.radio-group {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.radio-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background-color: rgba(64, 158, 255, 0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.radio-dot {
|
|
|
|
|
|
width: 20px;
|
|
|
|
|
|
height: 20px;
|
|
|
|
|
|
border: 2px solid #d1d5db;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&.checked {
|
|
|
|
|
|
border-color: #409eff;
|
|
|
|
|
|
background-color: #409eff;
|
|
|
|
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
|
|
width: 8px;
|
|
|
|
|
|
height: 8px;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.radio-text {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: #374151;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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, .submit-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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.submit-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%);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 冒泡提示动画
|
|
|
|
|
|
@keyframes tooltipFadeIn {
|
|
|
|
|
|
from {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateX(-50%) translateY(-5px);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: translateX(-50%) translateY(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 响应式调整
|
|
|
|
|
|
@media screen and (max-width: 375px) {
|
|
|
|
|
|
.form-item {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
|
|
|
|
|
|
.item-label {
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-type-header {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-item-header {
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-11 21:11:16 +08:00
|
|
|
|
|
|
|
|
|
|
// 提交遮罩层样式
|
|
|
|
|
|
.submit-overlay {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
z-index: 9999;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.submit-loading {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
padding: 30px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-spinner {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
border: 3px solid #f3f3f3;
|
|
|
|
|
|
border-top: 3px solid #409eff;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
animation: spin 1s linear infinite;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
|
0% { transform: rotate(0deg); }
|
|
|
|
|
|
100% { transform: rotate(360deg); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-text {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
2025-10-07 08:58:02 +08:00
|
|
|
|
</style>
|