已完成教学任务归档纠错功能:
已归档任务新增“撤销归档”和“删除”,支持单条及批量操作。 撤销后恢复为“已发布”状态。 已发布任务仍不可直接删除;被排课、选课、成绩或考试引用的归档任务也会拒绝删除并明确提示。 状态与操作文案统一为“归档”
This commit is contained in:
@@ -228,10 +228,14 @@ public sealed class TeachingTasksController(
|
|||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
if (task is null) return NotFound();
|
if (task is null) return NotFound();
|
||||||
if (!CanManage(task.Course!)) return Forbid();
|
if (!CanManage(task.Course!)) return Forbid();
|
||||||
if (task.Status != TeachingTaskStatus.Draft)
|
if (task.Status is not (TeachingTaskStatus.Draft or TeachingTaskStatus.Closed))
|
||||||
return ConflictProblem("仅草稿教学任务可以删除。");
|
return ConflictProblem("仅草稿或已归档的教学任务可以删除。");
|
||||||
db.TeachingTasks.Remove(task);
|
db.TeachingTasks.Remove(task);
|
||||||
return await SaveAsync(id, false, cancellationToken);
|
return await SaveAsync(
|
||||||
|
id,
|
||||||
|
false,
|
||||||
|
cancellationToken,
|
||||||
|
"教学任务已被排课、选课、成绩或考试等业务引用,不能删除;如需继续使用,可撤销归档。");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id:guid}/publish")]
|
[HttpPost("{id:guid}/publish")]
|
||||||
@@ -263,11 +267,25 @@ public sealed class TeachingTasksController(
|
|||||||
if (task is null) return NotFound();
|
if (task is null) return NotFound();
|
||||||
if (!CanManage(task.Course!)) return Forbid();
|
if (!CanManage(task.Course!)) return Forbid();
|
||||||
if (task.Status != TeachingTaskStatus.Published)
|
if (task.Status != TeachingTaskStatus.Published)
|
||||||
return ConflictProblem("只有已发布的教学任务可以结课。");
|
return ConflictProblem("只有已发布的教学任务可以归档。");
|
||||||
task.Status = TeachingTaskStatus.Closed;
|
task.Status = TeachingTaskStatus.Closed;
|
||||||
return await SaveAsync(id, false, cancellationToken);
|
return await SaveAsync(id, false, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id:guid}/unarchive")]
|
||||||
|
public async Task<ActionResult> Unarchive(Guid id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var task = await ScopedTasks()
|
||||||
|
.Include(x => x.Course)
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
|
if (task is null) return NotFound();
|
||||||
|
if (!CanManage(task.Course!)) return Forbid();
|
||||||
|
if (task.Status != TeachingTaskStatus.Closed)
|
||||||
|
return ConflictProblem("只有已归档的教学任务可以撤销归档。");
|
||||||
|
task.Status = TeachingTaskStatus.Published;
|
||||||
|
return await SaveAsync(id, false, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("batch")]
|
[HttpPost("batch")]
|
||||||
public async Task<ActionResult> Batch(
|
public async Task<ActionResult> Batch(
|
||||||
TeachingTaskBatchRequest request,
|
TeachingTaskBatchRequest request,
|
||||||
@@ -305,9 +323,10 @@ public sealed class TeachingTasksController(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TeachingTaskBatchOperation.Delete:
|
case TeachingTaskBatchOperation.Delete:
|
||||||
if (tasks.Any(x => x.Status != TeachingTaskStatus.Draft))
|
if (tasks.Any(x =>
|
||||||
|
x.Status is not (TeachingTaskStatus.Draft or TeachingTaskStatus.Closed)))
|
||||||
return ConflictProblem(
|
return ConflictProblem(
|
||||||
$"教学任务“{tasks.First(x => x.Status != TeachingTaskStatus.Draft).Name}”不是草稿,整批未删除。");
|
$"教学任务“{tasks.First(x => x.Status is not (TeachingTaskStatus.Draft or TeachingTaskStatus.Closed)).Name}”不是草稿或已归档状态,整批未删除。");
|
||||||
db.TeachingTasks.RemoveRange(tasks);
|
db.TeachingTasks.RemoveRange(tasks);
|
||||||
break;
|
break;
|
||||||
case TeachingTaskBatchOperation.Archive:
|
case TeachingTaskBatchOperation.Archive:
|
||||||
@@ -316,6 +335,12 @@ public sealed class TeachingTasksController(
|
|||||||
$"教学任务“{tasks.First(x => x.Status != TeachingTaskStatus.Published).Name}”不是已发布状态,整批未归档。");
|
$"教学任务“{tasks.First(x => x.Status != TeachingTaskStatus.Published).Name}”不是已发布状态,整批未归档。");
|
||||||
foreach (var task in tasks) task.Status = TeachingTaskStatus.Closed;
|
foreach (var task in tasks) task.Status = TeachingTaskStatus.Closed;
|
||||||
break;
|
break;
|
||||||
|
case TeachingTaskBatchOperation.Unarchive:
|
||||||
|
if (tasks.Any(x => x.Status != TeachingTaskStatus.Closed))
|
||||||
|
return ConflictProblem(
|
||||||
|
$"教学任务“{tasks.First(x => x.Status != TeachingTaskStatus.Closed).Name}”不是已归档状态,整批未撤销归档。");
|
||||||
|
foreach (var task in tasks) task.Status = TeachingTaskStatus.Published;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return ValidationProblem("不支持的批量操作。");
|
return ValidationProblem("不支持的批量操作。");
|
||||||
}
|
}
|
||||||
@@ -664,7 +689,8 @@ public sealed class TeachingTasksController(
|
|||||||
private async Task<ActionResult> SaveAsync(
|
private async Task<ActionResult> SaveAsync(
|
||||||
Guid id,
|
Guid id,
|
||||||
bool created,
|
bool created,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken,
|
||||||
|
string conflictMessage = "教学任务编号重复,或关联数据已失效。")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -673,7 +699,7 @@ public sealed class TeachingTasksController(
|
|||||||
}
|
}
|
||||||
catch (DbUpdateException)
|
catch (DbUpdateException)
|
||||||
{
|
{
|
||||||
return ConflictProblem("教学任务编号重复,或关联数据已失效。");
|
return ConflictProblem(conflictMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,5 +765,6 @@ public enum TeachingTaskBatchOperation
|
|||||||
{
|
{
|
||||||
Publish = 1,
|
Publish = 1,
|
||||||
Delete = 2,
|
Delete = 2,
|
||||||
Archive = 3
|
Archive = 3,
|
||||||
|
Unarchive = 4
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
using Jiaowu.Api.Controllers;
|
||||||
|
using Jiaowu.Api.Domain.Academic;
|
||||||
|
using Jiaowu.Api.Domain.Identity;
|
||||||
|
using Jiaowu.Api.Infrastructure.Auth;
|
||||||
|
using Jiaowu.Api.Infrastructure.Persistence;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Jiaowu.Api.Tests;
|
||||||
|
|
||||||
|
public sealed class TeachingTasksControllerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Delete_AllowsArchivedTask_ButRejectsPublishedTask()
|
||||||
|
{
|
||||||
|
await using var database = await TestDatabase.CreateAsync();
|
||||||
|
var archived = await database.AddTaskAsync("ARCHIVED", TeachingTaskStatus.Closed);
|
||||||
|
var published = await database.AddTaskAsync("PUBLISHED", TeachingTaskStatus.Published);
|
||||||
|
|
||||||
|
var deleteArchived = await database.Controller.Delete(
|
||||||
|
archived.Id,
|
||||||
|
CancellationToken.None);
|
||||||
|
var deletePublished = await database.Controller.Delete(
|
||||||
|
published.Id,
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.IsType<NoContentResult>(deleteArchived);
|
||||||
|
Assert.IsType<ConflictObjectResult>(deletePublished);
|
||||||
|
Assert.False(await database.Db.TeachingTasks.AnyAsync(x => x.Id == archived.Id));
|
||||||
|
Assert.True(await database.Db.TeachingTasks.AnyAsync(x => x.Id == published.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Unarchive_RestoresArchivedTaskToPublished()
|
||||||
|
{
|
||||||
|
await using var database = await TestDatabase.CreateAsync();
|
||||||
|
var task = await database.AddTaskAsync("RESTORE", TeachingTaskStatus.Closed);
|
||||||
|
|
||||||
|
var result = await database.Controller.Unarchive(task.Id, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.IsType<NoContentResult>(result);
|
||||||
|
await database.Db.Entry(task).ReloadAsync();
|
||||||
|
Assert.Equal(TeachingTaskStatus.Published, task.Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Batch_UnarchivesOnlyArchivedTasks()
|
||||||
|
{
|
||||||
|
await using var database = await TestDatabase.CreateAsync();
|
||||||
|
var first = await database.AddTaskAsync("FIRST", TeachingTaskStatus.Closed);
|
||||||
|
var second = await database.AddTaskAsync("SECOND", TeachingTaskStatus.Closed);
|
||||||
|
|
||||||
|
var result = await database.Controller.Batch(
|
||||||
|
new TeachingTaskBatchRequest(
|
||||||
|
TeachingTaskBatchOperation.Unarchive,
|
||||||
|
[first.Id, second.Id]),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.IsType<OkObjectResult>(result);
|
||||||
|
Assert.All(
|
||||||
|
await database.Db.TeachingTasks
|
||||||
|
.Where(x => x.Id == first.Id || x.Id == second.Id)
|
||||||
|
.ToListAsync(),
|
||||||
|
task => Assert.Equal(TeachingTaskStatus.Published, task.Status));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestDatabase : IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly SqliteConnection connection;
|
||||||
|
private readonly AcademicTerm term;
|
||||||
|
private readonly Course course;
|
||||||
|
|
||||||
|
private TestDatabase(
|
||||||
|
SqliteConnection connection,
|
||||||
|
AppDbContext db,
|
||||||
|
AcademicTerm term,
|
||||||
|
Course course)
|
||||||
|
{
|
||||||
|
this.connection = connection;
|
||||||
|
Db = db;
|
||||||
|
this.term = term;
|
||||||
|
this.course = course;
|
||||||
|
Controller = new TeachingTasksController(
|
||||||
|
db,
|
||||||
|
new TestDataScope());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppDbContext Db { get; }
|
||||||
|
public TeachingTasksController Controller { get; }
|
||||||
|
|
||||||
|
public static async Task<TestDatabase> CreateAsync()
|
||||||
|
{
|
||||||
|
var connection = new SqliteConnection("Data Source=:memory:");
|
||||||
|
await connection.OpenAsync();
|
||||||
|
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||||
|
.UseSqlite(connection)
|
||||||
|
.Options;
|
||||||
|
var db = new AppDbContext(options);
|
||||||
|
await db.Database.EnsureCreatedAsync();
|
||||||
|
|
||||||
|
var college = new College { Code = "TEST", Name = "测试学院" };
|
||||||
|
var term = new AcademicTerm
|
||||||
|
{
|
||||||
|
Code = "2026-1",
|
||||||
|
Name = "2026—2027 学年第一学期",
|
||||||
|
AcademicYear = "2026-2027",
|
||||||
|
Season = TermSeason.Autumn,
|
||||||
|
StartDate = new DateOnly(2026, 9, 1),
|
||||||
|
EndDate = new DateOnly(2027, 1, 15)
|
||||||
|
};
|
||||||
|
var course = new Course
|
||||||
|
{
|
||||||
|
Code = "TEST101",
|
||||||
|
Name = "测试课程",
|
||||||
|
CollegeId = college.Id,
|
||||||
|
Nature = CourseNature.MajorRequired,
|
||||||
|
TotalHours = 32,
|
||||||
|
LectureHours = 32
|
||||||
|
};
|
||||||
|
db.AddRange(college, term, course);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return new TestDatabase(connection, db, term, course);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TeachingTask> AddTaskAsync(
|
||||||
|
string suffix,
|
||||||
|
TeachingTaskStatus status)
|
||||||
|
{
|
||||||
|
var task = new TeachingTask
|
||||||
|
{
|
||||||
|
TaskNumber = $"TASK-{suffix}",
|
||||||
|
Name = $"教学任务 {suffix}",
|
||||||
|
AcademicTermId = term.Id,
|
||||||
|
CourseId = course.Id,
|
||||||
|
Capacity = 40,
|
||||||
|
StartWeek = 1,
|
||||||
|
EndWeek = 16,
|
||||||
|
WeeklyHours = 2,
|
||||||
|
Status = status,
|
||||||
|
PublishedAt = status == TeachingTaskStatus.Draft
|
||||||
|
? null
|
||||||
|
: DateTime.UtcNow
|
||||||
|
};
|
||||||
|
Db.TeachingTasks.Add(task);
|
||||||
|
await Db.SaveChangesAsync();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await Db.DisposeAsync();
|
||||||
|
await connection.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestDataScope : ICurrentUserDataScope
|
||||||
|
{
|
||||||
|
public CurrentUserScope Current { get; } = new(
|
||||||
|
Guid.NewGuid(),
|
||||||
|
"测试管理员",
|
||||||
|
null,
|
||||||
|
DataScope.All,
|
||||||
|
new HashSet<string>([SystemRoles.SuperAdmin]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@ const manualEligibleTeachers = ref<any[]>([])
|
|||||||
const statusLabels: Record<string, string> = {
|
const statusLabels: Record<string, string> = {
|
||||||
Draft: '草稿',
|
Draft: '草稿',
|
||||||
Published: '已发布',
|
Published: '已发布',
|
||||||
Closed: '已结课',
|
Closed: '已归档',
|
||||||
}
|
}
|
||||||
const courseNatureLabels: Record<string, string> = {
|
const courseNatureLabels: Record<string, string> = {
|
||||||
GeneralRequired: '公共必修',
|
GeneralRequired: '公共必修',
|
||||||
@@ -166,6 +166,9 @@ const selectedDraftRows = computed(() =>
|
|||||||
const selectedPublishedRows = computed(() =>
|
const selectedPublishedRows = computed(() =>
|
||||||
selectedRows.value.filter((item) => item.status === 'Published'),
|
selectedRows.value.filter((item) => item.status === 'Published'),
|
||||||
)
|
)
|
||||||
|
const selectedClosedRows = computed(() =>
|
||||||
|
selectedRows.value.filter((item) => item.status === 'Closed'),
|
||||||
|
)
|
||||||
const responsibilityText = computed(() => {
|
const responsibilityText = computed(() => {
|
||||||
if (isSuperAdmin.value) return '超级管理员可处理全部教学任务,并保留跨层级纠错权限。'
|
if (isSuperAdmin.value) return '超级管理员可处理全部教学任务,并保留跨层级纠错权限。'
|
||||||
if (isAcademicAdmin.value && isCollegeAdmin.value) {
|
if (isAcademicAdmin.value && isCollegeAdmin.value) {
|
||||||
@@ -242,7 +245,7 @@ function clearFilteredClasses(target: Record<string, any>, filtered: any[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rowSelectable(row: any) {
|
function rowSelectable(row: any) {
|
||||||
return canManageRow(row) && ['Draft', 'Published'].includes(row.status)
|
return canManageRow(row) && ['Draft', 'Published', 'Closed'].includes(row.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTaskSelectionChanged(selection: any[]) {
|
function onTaskSelectionChanged(selection: any[]) {
|
||||||
@@ -368,11 +371,21 @@ async function save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function remove(row: any) {
|
async function remove(row: any) {
|
||||||
|
const isArchived = row.status === 'Closed'
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除教学任务“${row.name}”吗?`, '删除草稿', {
|
await ElMessageBox.confirm(
|
||||||
type: 'warning',
|
isArchived
|
||||||
})
|
? `将永久删除已归档教学任务“${row.name}”。如任务已被排课、选课、成绩或考试引用,系统会拒绝删除。确定继续吗?`
|
||||||
|
: `确定删除教学任务草稿“${row.name}”吗?`,
|
||||||
|
isArchived ? '删除已归档任务' : '删除草稿',
|
||||||
|
{
|
||||||
|
type: 'error',
|
||||||
|
confirmButtonText: '确认删除',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
},
|
||||||
|
)
|
||||||
await http.delete(`/teaching-tasks/${row.id}`)
|
await http.delete(`/teaching-tasks/${row.id}`)
|
||||||
|
ElMessage.success(isArchived ? '已归档教学任务已删除' : '教学任务草稿已删除')
|
||||||
await load()
|
await load()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
||||||
@@ -396,12 +409,32 @@ async function publish(row: any) {
|
|||||||
|
|
||||||
async function closeTask(row: any) {
|
async function closeTask(row: any) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm('结课后将停止后续选课和排课调整,确定继续吗?', '教学任务结课', {
|
await ElMessageBox.confirm('归档后将停止后续选课和排课调整,确定继续吗?', '归档教学任务', {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
confirmButtonText: '确认结课',
|
confirmButtonText: '确认归档',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
})
|
})
|
||||||
await http.post(`/teaching-tasks/${row.id}/close`)
|
await http.post(`/teaching-tasks/${row.id}/close`)
|
||||||
|
ElMessage.success('教学任务已归档')
|
||||||
|
await load()
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unarchive(row: any) {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'撤销归档后,教学任务将恢复为已发布状态,并重新进入相关业务流程。确定继续吗?',
|
||||||
|
'撤销教学任务归档',
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonText: '撤销归档',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await http.post(`/teaching-tasks/${row.id}/unarchive`)
|
||||||
|
ElMessage.success('教学任务已撤销归档')
|
||||||
await load()
|
await load()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
||||||
@@ -409,10 +442,12 @@ async function closeTask(row: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function batchAction(
|
async function batchAction(
|
||||||
operation: 'Publish' | 'Delete' | 'Archive',
|
operation: 'Publish' | 'Delete' | 'Archive' | 'Unarchive',
|
||||||
targets: any[],
|
targets: any[],
|
||||||
) {
|
) {
|
||||||
if (!targets.length) return
|
if (!targets.length) return
|
||||||
|
const deletingArchived = operation === 'Delete' &&
|
||||||
|
targets.every((item) => item.status === 'Closed')
|
||||||
const descriptions = {
|
const descriptions = {
|
||||||
Publish: {
|
Publish: {
|
||||||
title: '批量发布教学任务',
|
title: '批量发布教学任务',
|
||||||
@@ -422,10 +457,12 @@ async function batchAction(
|
|||||||
type: 'warning',
|
type: 'warning',
|
||||||
},
|
},
|
||||||
Delete: {
|
Delete: {
|
||||||
title: '批量删除教学任务',
|
title: deletingArchived ? '批量删除已归档任务' : '批量删除教学任务草稿',
|
||||||
message: `将永久删除选中的 ${targets.length} 个草稿,此操作不可撤销。确定继续吗?`,
|
message: deletingArchived
|
||||||
|
? `将永久删除选中的 ${targets.length} 个已归档任务。已被后续业务引用的任务会拒绝删除,此操作不可撤销。确定继续吗?`
|
||||||
|
: `将永久删除选中的 ${targets.length} 个草稿,此操作不可撤销。确定继续吗?`,
|
||||||
confirmButtonText: '批量删除',
|
confirmButtonText: '批量删除',
|
||||||
success: '教学任务草稿已批量删除',
|
success: deletingArchived ? '已归档教学任务已批量删除' : '教学任务草稿已批量删除',
|
||||||
type: 'error',
|
type: 'error',
|
||||||
},
|
},
|
||||||
Archive: {
|
Archive: {
|
||||||
@@ -435,6 +472,13 @@ async function batchAction(
|
|||||||
success: '教学任务已批量归档',
|
success: '教学任务已批量归档',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
},
|
},
|
||||||
|
Unarchive: {
|
||||||
|
title: '批量撤销教学任务归档',
|
||||||
|
message: `将选中的 ${targets.length} 个已归档任务恢复为已发布状态,并重新进入相关业务流程。确定继续吗?`,
|
||||||
|
confirmButtonText: '批量撤销归档',
|
||||||
|
success: '教学任务已批量撤销归档',
|
||||||
|
type: 'warning',
|
||||||
|
},
|
||||||
} as const
|
} as const
|
||||||
const description = descriptions[operation]
|
const description = descriptions[operation]
|
||||||
try {
|
try {
|
||||||
@@ -572,7 +616,7 @@ onMounted(async () => {
|
|||||||
<div v-if="selectedRows.length" class="batch-action-bar">
|
<div v-if="selectedRows.length" class="batch-action-bar">
|
||||||
<div>
|
<div>
|
||||||
<b>已选择 {{ selectedRows.length }} 个教学任务</b>
|
<b>已选择 {{ selectedRows.length }} 个教学任务</b>
|
||||||
<span>可操作:{{ selectedDraftRows.length }} 个草稿,{{ selectedPublishedRows.length }} 个已发布</span>
|
<span>可操作:{{ selectedDraftRows.length }} 个草稿,{{ selectedPublishedRows.length }} 个已发布,{{ selectedClosedRows.length }} 个已归档</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<el-button
|
<el-button
|
||||||
@@ -599,6 +643,22 @@ onMounted(async () => {
|
|||||||
>
|
>
|
||||||
归档已发布({{ selectedPublishedRows.length }})
|
归档已发布({{ selectedPublishedRows.length }})
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
:disabled="!selectedClosedRows.length"
|
||||||
|
@click="batchAction('Unarchive', selectedClosedRows)"
|
||||||
|
>
|
||||||
|
撤销归档({{ selectedClosedRows.length }})
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:disabled="!selectedClosedRows.length"
|
||||||
|
@click="batchAction('Delete', selectedClosedRows)"
|
||||||
|
>
|
||||||
|
删除已归档({{ selectedClosedRows.length }})
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -625,14 +685,16 @@ onMounted(async () => {
|
|||||||
<el-table-column label="人数 / 容量" width="105"><template #default="{ row }">{{ row.studentCount }} / {{ row.capacity }}</template></el-table-column>
|
<el-table-column label="人数 / 容量" width="105"><template #default="{ row }">{{ row.studentCount }} / {{ row.capacity }}</template></el-table-column>
|
||||||
<el-table-column label="周次" width="105"><template #default="{ row }">{{ row.startWeek }}—{{ row.endWeek }} 周</template></el-table-column>
|
<el-table-column label="周次" width="105"><template #default="{ row }">{{ row.startWeek }}—{{ row.endWeek }} 周</template></el-table-column>
|
||||||
<el-table-column label="状态" width="90"><template #default="{ row }"><span class="table-status" :class="{ off: row.status === 'Closed' }">{{ statusLabels[row.status] }}</span></template></el-table-column>
|
<el-table-column label="状态" width="90"><template #default="{ row }"><span class="table-status" :class="{ off: row.status === 'Closed' }">{{ statusLabels[row.status] }}</span></template></el-table-column>
|
||||||
<el-table-column label="操作" width="185" fixed="right">
|
<el-table-column label="操作" width="210" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button v-if="canManageRow(row) && row.status === 'Draft'" link type="primary" @click="openEdit(row)">编辑</el-button>
|
<el-button v-if="canManageRow(row) && row.status === 'Draft'" link type="primary" @click="openEdit(row)">编辑</el-button>
|
||||||
<el-button v-if="canManageRow(row) && row.status === 'Draft'" link type="success" @click="publish(row)">发布</el-button>
|
<el-button v-if="canManageRow(row) && row.status === 'Draft'" link type="success" @click="publish(row)">发布</el-button>
|
||||||
<el-button v-if="canManageRow(row) && row.status === 'Draft'" link type="danger" @click="remove(row)">删除</el-button>
|
<el-button v-if="canManageRow(row) && row.status === 'Draft'" link type="danger" @click="remove(row)">删除</el-button>
|
||||||
<el-button v-if="canManageRow(row) && row.status === 'Published'" link type="warning" @click="closeTask(row)">结课</el-button>
|
<el-button v-if="canManageRow(row) && row.status === 'Published'" link type="warning" @click="closeTask(row)">归档</el-button>
|
||||||
|
<el-button v-if="canManageRow(row) && row.status === 'Closed'" link type="primary" @click="unarchive(row)">撤销归档</el-button>
|
||||||
|
<el-button v-if="canManageRow(row) && row.status === 'Closed'" link type="danger" @click="remove(row)">删除</el-button>
|
||||||
<span v-if="!canManageRow(row) && row.status !== 'Closed'" class="muted-action">只读</span>
|
<span v-if="!canManageRow(row) && row.status !== 'Closed'" class="muted-action">只读</span>
|
||||||
<span v-if="row.status === 'Closed'" class="muted-action">已结课归档</span>
|
<span v-if="!canManageRow(row) && row.status === 'Closed'" class="muted-action">已归档</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<template #empty><el-empty description="没有符合条件的教学任务" /></template>
|
<template #empty><el-empty description="没有符合条件的教学任务" /></template>
|
||||||
|
|||||||
Reference in New Issue
Block a user