- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
770 lines
21 KiB
Vue
770 lines
21 KiB
Vue
<template>
|
||
<div class="assignment-center-container">
|
||
<div class="page-header">
|
||
<h1 class="page-title">任务中心</h1>
|
||
<el-button type="primary" @click="createTask">
|
||
<el-icon class="el-icon--left"><Plus /></el-icon>
|
||
创建任务
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 任务统计 -->
|
||
<div class="task-stats">
|
||
<div class="stat-card card" v-for="stat in taskStats" :key="stat.label">
|
||
<div class="stat-icon" :style="{ backgroundColor: stat.bgColor }">
|
||
<el-icon :size="24" :color="stat.color">
|
||
<component :is="stat.icon" />
|
||
</el-icon>
|
||
</div>
|
||
<div class="stat-content">
|
||
<div class="stat-value">{{ stat.value }}</div>
|
||
<div class="stat-label">{{ stat.label }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 任务列表 -->
|
||
<div class="task-section">
|
||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||
<el-tab-pane label="进行中" name="ongoing">
|
||
<span slot="label">
|
||
进行中 <el-badge :value="12" class="tab-badge" />
|
||
</span>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="待开始" name="pending">
|
||
<span slot="label">
|
||
待开始 <el-badge :value="5" class="tab-badge" />
|
||
</span>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="已完成" name="completed">
|
||
<span slot="label">
|
||
已完成 <el-badge :value="28" class="tab-badge" />
|
||
</span>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="已过期" name="expired">
|
||
<span slot="label">
|
||
已过期 <el-badge :value="3" class="tab-badge" />
|
||
</span>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
|
||
<!-- 任务卡片列表 -->
|
||
<div class="task-list">
|
||
<div class="task-card card" v-for="task in taskList" :key="task.id">
|
||
<div class="task-header">
|
||
<div class="task-title-section">
|
||
<h3 class="task-title">{{ task.title }}</h3>
|
||
<el-tag :type="getTaskTagType(task.priority)" size="small">
|
||
{{ task.priority }}
|
||
</el-tag>
|
||
</div>
|
||
<el-dropdown trigger="click">
|
||
<el-button link>
|
||
<el-icon><More /></el-icon>
|
||
</el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item @click="editTask(task)">
|
||
<el-icon><Edit /></el-icon>
|
||
编辑任务
|
||
</el-dropdown-item>
|
||
<el-dropdown-item @click="copyTask(task)">
|
||
<el-icon><CopyDocument /></el-icon>
|
||
复制任务
|
||
</el-dropdown-item>
|
||
<el-dropdown-item @click="endTask(task)" v-if="task.status === 'ongoing'">
|
||
<el-icon><CircleCheck /></el-icon>
|
||
结束任务
|
||
</el-dropdown-item>
|
||
<el-dropdown-item @click="deleteTaskItem(task)" divided>
|
||
<el-icon><Delete /></el-icon>
|
||
删除任务
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
|
||
<div class="task-content">
|
||
<p class="task-desc">{{ task.description }}</p>
|
||
|
||
<div class="task-info">
|
||
<div class="info-item">
|
||
<el-icon><User /></el-icon>
|
||
<span>分配人数:{{ task.assigned_count }} 人</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<el-icon><Calendar /></el-icon>
|
||
<span>截止时间:{{ formatDeadline(task.deadline) }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<el-icon><Collection /></el-icon>
|
||
<span>包含课程:{{ task.courses.length }}门</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-courses">
|
||
<el-tag v-for="course in task.courses.slice(0, 3)" :key="course" size="small">
|
||
{{ course }}
|
||
</el-tag>
|
||
<el-tag v-if="task.courses.length > 3" type="info" size="small">
|
||
+{{ task.courses.length - 3 }}
|
||
</el-tag>
|
||
</div>
|
||
|
||
<div class="task-progress">
|
||
<div class="progress-header">
|
||
<span class="progress-label">完成进度</span>
|
||
<span class="progress-text">{{ task.completed_count }}/{{ task.assigned_count }} 人完成</span>
|
||
</div>
|
||
<el-progress :percentage="task.progress" :color="getProgressColor(task.progress)" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="task-footer">
|
||
<el-button size="small" @click="viewDetail(task)">查看详情</el-button>
|
||
<el-button type="primary" size="small" @click="sendReminder(task)">
|
||
发送提醒
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 空状态 -->
|
||
<el-empty v-if="taskList.length === 0" description="暂无任务" />
|
||
</div>
|
||
|
||
<!-- 创建任务弹窗 -->
|
||
<el-dialog
|
||
v-model="createDialogVisible"
|
||
title="创建学习任务"
|
||
width="680px"
|
||
:close-on-click-modal="false"
|
||
>
|
||
<el-form ref="formRef" :model="taskForm" :rules="rules" label-width="100px">
|
||
<el-form-item label="任务名称" prop="title">
|
||
<el-input v-model="taskForm.title" placeholder="请输入任务名称" maxlength="50" show-word-limit />
|
||
</el-form-item>
|
||
|
||
<el-form-item label="任务描述" prop="description">
|
||
<el-input
|
||
v-model="taskForm.description"
|
||
type="textarea"
|
||
placeholder="请输入任务描述"
|
||
:rows="3"
|
||
maxlength="200"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="优先级" prop="priority">
|
||
<el-radio-group v-model="taskForm.priority">
|
||
<el-radio label="高">高优先级</el-radio>
|
||
<el-radio label="中">中优先级</el-radio>
|
||
<el-radio label="低">低优先级</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="分配对象" prop="assignType">
|
||
<el-radio-group v-model="taskForm.assignType" @change="handleAssignTypeChange">
|
||
<el-radio label="all">全体成员</el-radio>
|
||
<el-radio label="team">指定团队</el-radio>
|
||
<el-radio label="member">指定成员</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<el-form-item v-if="taskForm.assignType === 'team'" label="选择团队" prop="teams">
|
||
<el-select v-model="taskForm.teams" multiple placeholder="请选择团队" style="width: 100%">
|
||
<el-option label="销售一组" value="team1" />
|
||
<el-option label="销售二组" value="team2" />
|
||
<el-option label="销售三组" value="team3" />
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item v-if="taskForm.assignType === 'member'" label="选择成员" prop="members">
|
||
<el-select v-model="taskForm.members" multiple placeholder="请选择成员" style="width: 100%">
|
||
<el-option label="张三" value="user1" />
|
||
<el-option label="李四" value="user2" />
|
||
<el-option label="王五" value="user3" />
|
||
<el-option label="赵六" value="user4" />
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="选择课程" prop="courses">
|
||
<el-select v-model="taskForm.courses" multiple placeholder="请选择课程" style="width: 100%">
|
||
<el-option-group label="销售技巧">
|
||
<el-option label="客户沟通技巧" value="course1" />
|
||
<el-option label="需求挖掘方法" value="course2" />
|
||
<el-option label="异议处理技巧" value="course3" />
|
||
</el-option-group>
|
||
<el-option-group label="产品知识">
|
||
<el-option label="产品基础知识" value="course4" />
|
||
<el-option label="竞品分析" value="course5" />
|
||
</el-option-group>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="截止时间" prop="deadline">
|
||
<el-date-picker
|
||
v-model="taskForm.deadline"
|
||
type="datetime"
|
||
placeholder="请选择截止时间"
|
||
format="YYYY-MM-DD HH:mm"
|
||
value-format="YYYY-MM-DD HH:mm"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="任务要求" prop="requirements">
|
||
<el-checkbox-group v-model="taskForm.requirements">
|
||
<el-checkbox label="mustComplete">必须完成所有课程</el-checkbox>
|
||
<el-checkbox label="mustPass">考试必须及格</el-checkbox>
|
||
<el-checkbox label="mustPractice">必须完成AI陪练</el-checkbox>
|
||
</el-checkbox-group>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="createDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="handleCreateTask" :loading="createLoading">
|
||
确定
|
||
</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, computed, onMounted } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import type { FormInstance, FormRules } from 'element-plus'
|
||
import { getTasks, getTaskStats, createTask as createTaskApi, deleteTask, type Task } from '@/api/task'
|
||
|
||
// 当前标签页
|
||
const activeTab = ref('ongoing')
|
||
|
||
// 创建任务弹窗
|
||
const createDialogVisible = ref(false)
|
||
const formRef = ref<FormInstance>()
|
||
const createLoading = ref(false)
|
||
const loading = ref(false)
|
||
|
||
// 任务统计数据
|
||
const taskStats = ref([
|
||
{
|
||
label: '总任务数',
|
||
value: '0',
|
||
icon: 'Tickets',
|
||
color: '#667eea',
|
||
bgColor: 'rgba(102, 126, 234, 0.1)'
|
||
},
|
||
{
|
||
label: '进行中',
|
||
value: '0',
|
||
icon: 'Timer',
|
||
color: '#e6a23c',
|
||
bgColor: 'rgba(230, 162, 60, 0.1)'
|
||
},
|
||
{
|
||
label: '已完成',
|
||
value: '0',
|
||
icon: 'CircleCheck',
|
||
color: '#67c23a',
|
||
bgColor: 'rgba(103, 194, 58, 0.1)'
|
||
},
|
||
{
|
||
label: '平均完成率',
|
||
value: '0%',
|
||
icon: 'DataLine',
|
||
color: '#409eff',
|
||
bgColor: 'rgba(64, 158, 255, 0.1)'
|
||
}
|
||
])
|
||
|
||
// 任务列表数据
|
||
const allTasks = ref<Task[]>([])
|
||
|
||
// 任务表单
|
||
const taskForm = reactive({
|
||
title: '',
|
||
description: '',
|
||
priority: '中',
|
||
assignType: 'all',
|
||
teams: [],
|
||
members: [],
|
||
courses: [],
|
||
deadline: '',
|
||
requirements: ['mustComplete']
|
||
})
|
||
|
||
// 表单验证规则
|
||
const rules = reactive<FormRules>({
|
||
title: [
|
||
{ required: true, message: '请输入任务名称', trigger: 'blur' }
|
||
],
|
||
description: [
|
||
{ required: true, message: '请输入任务描述', trigger: 'blur' }
|
||
],
|
||
priority: [
|
||
{ required: true, message: '请选择优先级', trigger: 'change' }
|
||
],
|
||
courses: [
|
||
{ required: true, message: '请选择课程', trigger: 'change' }
|
||
],
|
||
deadline: [
|
||
{ required: true, message: '请选择截止时间', trigger: 'change' }
|
||
]
|
||
})
|
||
|
||
// 根据当前标签页筛选的任务列表
|
||
const taskList = computed(() => {
|
||
if (activeTab.value === 'ongoing') {
|
||
return allTasks.value
|
||
}
|
||
return allTasks.value.filter(task => task.status === activeTab.value)
|
||
})
|
||
|
||
/**
|
||
* 加载任务统计数据
|
||
*/
|
||
const loadTaskStats = async () => {
|
||
try {
|
||
const res = await getTaskStats()
|
||
if (res.code === 200 && res.data) {
|
||
const stats = res.data
|
||
taskStats.value[0].value = stats.total.toString()
|
||
taskStats.value[1].value = stats.ongoing.toString()
|
||
taskStats.value[2].value = stats.completed.toString()
|
||
taskStats.value[3].value = stats.avg_completion_rate.toFixed(1) + '%'
|
||
}
|
||
} catch (error) {
|
||
console.error('加载统计数据失败:', error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载任务列表
|
||
*/
|
||
const loadTasks = async () => {
|
||
loading.value = true
|
||
try {
|
||
const status = activeTab.value === 'ongoing' ? 'ongoing' : activeTab.value
|
||
const res = await getTasks({ status })
|
||
if (res.code === 200 && res.data) {
|
||
allTasks.value = res.data.items
|
||
}
|
||
} catch (error: any) {
|
||
console.error('加载任务列表失败:', error)
|
||
ElMessage.error(error.message || '加载任务列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 标签页切换
|
||
*/
|
||
const handleTabClick = async () => {
|
||
await loadTasks()
|
||
}
|
||
|
||
/**
|
||
* 格式化截止时间
|
||
*/
|
||
const formatDeadline = (deadline?: string) => {
|
||
if (!deadline) return '无截止时间'
|
||
const date = new Date(deadline)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
return `${year}-${month}-${day}`
|
||
}
|
||
|
||
/**
|
||
* 创建任务
|
||
*/
|
||
const createTask = () => {
|
||
createDialogVisible.value = true
|
||
}
|
||
|
||
/**
|
||
* 分配类型改变
|
||
*/
|
||
const handleAssignTypeChange = () => {
|
||
taskForm.teams = []
|
||
taskForm.members = []
|
||
}
|
||
|
||
/**
|
||
* 提交创建任务
|
||
*/
|
||
const handleCreateTask = async () => {
|
||
if (!formRef.value) return
|
||
|
||
await formRef.value.validate(async (valid) => {
|
||
if (valid) {
|
||
createLoading.value = true
|
||
|
||
try {
|
||
// 构建请求数据
|
||
const taskData = {
|
||
title: taskForm.title,
|
||
description: taskForm.description,
|
||
priority: taskForm.priority.toLowerCase(),
|
||
deadline: taskForm.deadline,
|
||
course_ids: taskForm.courses,
|
||
user_ids: taskForm.assignType === 'all' ? [] : taskForm.members,
|
||
requirements: {
|
||
mustComplete: taskForm.requirements.includes('mustComplete'),
|
||
allowRetake: taskForm.requirements.includes('allowRetake')
|
||
}
|
||
}
|
||
|
||
const res = await createTaskApi(taskData)
|
||
if (res.code === 200) {
|
||
ElMessage.success('任务创建成功')
|
||
createDialogVisible.value = false
|
||
formRef.value?.resetFields()
|
||
// 刷新数据
|
||
await loadTaskStats()
|
||
await loadTasks()
|
||
} else {
|
||
ElMessage.error(res.message || '创建任务失败')
|
||
}
|
||
} catch (error: any) {
|
||
console.error('创建任务失败:', error)
|
||
ElMessage.error(error.message || '创建任务失败')
|
||
} finally {
|
||
createLoading.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 查看详情
|
||
*/
|
||
const viewDetail = (task: any) => {
|
||
ElMessage.info(`查看任务详情:${task.title}`)
|
||
}
|
||
|
||
/**
|
||
* 发送提醒
|
||
*/
|
||
const sendReminder = (_task: any) => {
|
||
ElMessageBox.confirm(
|
||
`确定要向未完成的成员发送任务提醒吗?`,
|
||
'发送提醒',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'info'
|
||
}
|
||
).then(() => {
|
||
ElMessage.success('提醒发送成功')
|
||
}).catch(() => {})
|
||
}
|
||
|
||
/**
|
||
* 编辑任务
|
||
*/
|
||
const editTask = async (task: Task) => {
|
||
// 这里可以打开编辑对话框,填充task数据
|
||
// 简化实现:直接提示
|
||
ElMessage.info(`编辑任务功能开发中:${task.title}`)
|
||
// TODO: 实现完整的编辑功能
|
||
}
|
||
|
||
/**
|
||
* 复制任务
|
||
*/
|
||
const copyTask = (task: any) => {
|
||
ElMessage.success(`已复制任务:${task.title}`)
|
||
}
|
||
|
||
/**
|
||
* 结束任务
|
||
*/
|
||
const endTask = async (_task: Task) => {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
'确定要结束这个任务吗?结束后将不能再修改。',
|
||
'结束任务',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
)
|
||
ElMessage.success('任务已结束')
|
||
} catch {}
|
||
}
|
||
|
||
/**
|
||
* 删除任务
|
||
*/
|
||
const deleteTaskItem = async (task: Task) => {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`确定要删除任务"${task.title}"吗?此操作不可撤销。`,
|
||
'删除任务',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
)
|
||
|
||
const res = await deleteTask(task.id)
|
||
if (res.code === 200) {
|
||
ElMessage.success('任务已删除')
|
||
// 刷新数据
|
||
await loadTaskStats()
|
||
await loadTasks()
|
||
} else {
|
||
ElMessage.error(res.message || '删除任务失败')
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
console.error('删除任务失败:', error)
|
||
ElMessage.error(error.message || '删除任务失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取任务标签类型
|
||
*/
|
||
const getTaskTagType = (priority: string) => {
|
||
const typeMap: Record<string, string> = {
|
||
'高': 'danger',
|
||
'中': 'warning',
|
||
'低': 'info'
|
||
}
|
||
return typeMap[priority] || ''
|
||
}
|
||
|
||
/**
|
||
* 获取进度颜色
|
||
*/
|
||
const getProgressColor = (percentage: number) => {
|
||
if (percentage >= 80) return '#67c23a'
|
||
if (percentage >= 60) return '#409eff'
|
||
if (percentage >= 40) return '#e6a23c'
|
||
return '#f56c6c'
|
||
}
|
||
|
||
// 组件挂载时加载数据
|
||
onMounted(async () => {
|
||
await loadTaskStats()
|
||
await loadTasks()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.assignment-center-container {
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24px;
|
||
|
||
.page-title {
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.el-button {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border: none;
|
||
}
|
||
}
|
||
|
||
.task-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 24px;
|
||
|
||
.stat-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20px;
|
||
padding: 24px;
|
||
|
||
.stat-icon {
|
||
width: 56px;
|
||
height: 56px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.stat-content {
|
||
.stat-value {
|
||
font-size: 28px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.task-section {
|
||
:deep(.el-tabs) {
|
||
.el-tabs__header {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.tab-badge {
|
||
margin-left: 8px;
|
||
|
||
.el-badge__content {
|
||
height: 18px;
|
||
line-height: 18px;
|
||
padding: 0 6px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.task-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
||
gap: 20px;
|
||
|
||
.task-card {
|
||
padding: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.task-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 16px;
|
||
|
||
.task-title-section {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
|
||
.task-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.task-content {
|
||
flex: 1;
|
||
|
||
.task-desc {
|
||
font-size: 14px;
|
||
color: #666;
|
||
line-height: 1.5;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.task-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
|
||
.info-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
color: #909399;
|
||
|
||
.el-icon {
|
||
color: #c0c4cc;
|
||
}
|
||
}
|
||
}
|
||
|
||
.task-courses {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.task-progress {
|
||
.progress-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
font-size: 13px;
|
||
|
||
.progress-label {
|
||
color: #666;
|
||
}
|
||
|
||
.progress-text {
|
||
color: #909399;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.task-footer {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-top: 20px;
|
||
padding-top: 20px;
|
||
border-top: 1px solid #ebeef5;
|
||
|
||
.el-button {
|
||
flex: 1;
|
||
|
||
&--primary {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border: none;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 响应式
|
||
@media (max-width: 768px) {
|
||
.assignment-center-container {
|
||
.page-header {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 16px;
|
||
|
||
.el-button {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.task-stats {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.task-list {
|
||
grid-template-columns: 1fr !important;
|
||
}
|
||
}
|
||
}
|
||
</style>
|