课程强制发布

This commit is contained in:
2026-01-14 11:25:51 +08:00
parent d18a09e639
commit 109f552613
11 changed files with 327 additions and 8 deletions

View File

@@ -176,6 +176,16 @@ export const courseApi = {
return response.data;
},
/**
* 强制发布课程(跳过敏感词校验)
* @param courseID 课程ID
* @returns Promise<ResultDomain<Course>>
*/
async forcePublishCourse(courseID: string): Promise<ResultDomain<Course>> {
const response = await api.post<Course>(`${this.prefixCourse}/${courseID}/force-publish`);
return response.data;
},
/**
* 获取课程章节列表
* @param courseID 课程ID

View File

@@ -6,7 +6,7 @@ import App from "./App.vue";
import "./registerServiceWorker";
import router from "./router";
import store from "./store";
import { setupRouterGuards, setupTokenRefresh } from "@/utils/permission";
import { setupRouterGuards, setupTokenRefresh, setupPermissionUtils } from "@/utils/permission";
import { setupPermissionDirectives } from "@/directives/permission";
// 引入 Quill 富文本编辑器样式(全局)
@@ -49,6 +49,9 @@ async function initApp() {
// 设置权限指令
setupPermissionDirectives(app, store);
// 设置权限工具
setupPermissionUtils(store);
// 设置路由守卫
setupRouterGuards(router, store);

View File

@@ -266,3 +266,64 @@ export class PermissionChecker {
return roleCodes.some(code => this.hasRole(code));
}
}
// 全局store引用由setupPermissionUtils初始化
let globalStore: Store<any> | null = null;
/**
* 初始化权限工具在main.ts中调用
*/
export function setupPermissionUtils(store: Store<any>) {
globalStore = store;
}
/**
* 权限检查 Composition API用于Vue组件
*/
export function usePermission() {
return {
/**
* 检查是否有指定权限
*/
hasPermission: (permissionCode: string): boolean => {
if (!globalStore) return false;
return globalStore.getters['auth/hasPermission'](permissionCode);
},
/**
* 检查是否有任意一个权限
*/
hasAnyPermission: (permissionCodes: string[]): boolean => {
if (!globalStore) return false;
return globalStore.getters['auth/hasAnyPermission'](permissionCodes);
},
/**
* 检查是否有所有权限
*/
hasAllPermissions: (permissionCodes: string[]): boolean => {
if (!globalStore) return false;
return globalStore.getters['auth/hasAllPermissions'](permissionCodes);
},
/**
* 检查是否有指定角色
*/
hasRole: (roleCode: string): boolean => {
if (!globalStore) return false;
const userRoles = globalStore.getters['auth/userRoles'] || [];
return userRoles.some((role: any) => role.code === roleCode);
},
/**
* 检查是否有任意一个角色
*/
hasAnyRole: (roleCodes: string[]): boolean => {
if (!globalStore) return false;
const userRoles = globalStore.getters['auth/userRoles'] || [];
return roleCodes.some(code =>
userRoles.some((role: any) => role.code === code)
);
}
};
}

View File

@@ -53,7 +53,7 @@
{{ getActionButtonText(row.status) }}
</el-button>
<el-button
v-if="row.status === ResourceStatus.SENSITIVE_FAILED"
v-if="row.status === ResourceStatus.SENSITIVE_FAILED && canForcePublish"
size="small"
type="warning"
@click="forcePublishArticle(row)"
@@ -108,7 +108,7 @@ import { AdminLayout } from '@/views/admin';
defineOptions({
name: 'ArticleManagementView'
});
import { ref, onMounted } from 'vue';
import { ref, onMounted, computed } from 'vue';
import { ElButton, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElMessage, ElMessageBox, ElIcon } from 'element-plus';
import { Search } from '@element-plus/icons-vue';
import { useRouter } from 'vue-router';
@@ -116,8 +116,14 @@ import { resourceApi, resourceTagApi } from '@/apis/resource'
import type { PageParam, ResourceSearchParams, Resource, Tag } from '@/types';
import { ArticleShowView } from '@/views/public/article';
import { ResourceStatus } from '@/types/enums';
import { usePermission } from '@/utils/permission';
const router = useRouter();
// 权限检查
const { hasPermission } = usePermission();
const canForcePublish = computed(() => hasPermission('admin:article:force-publish'));
const searchKeyword = ref('');
const pageParam = ref<PageParam>({
pageNumber: 1,

View File

@@ -73,7 +73,7 @@
</template>
</el-table-column>
<el-table-column prop="orderNum" label="排序" width="80" />
<el-table-column label="操作" width="250" fixed="right">
<el-table-column label="操作" width="300" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" link @click="handleEdit(row)">
编辑
@@ -87,6 +87,15 @@
>
发布
</el-button>
<el-button
v-if="row.status === 4 && canForcePublish"
type="warning"
size="small"
link
@click="handleForcePublish(row)"
>
强制发布
</el-button>
<el-button
v-if="row.status === 1"
type="warning"
@@ -121,12 +130,13 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { ref, reactive, onMounted, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Search, Plus } from '@element-plus/icons-vue';
import { courseApi } from '@/apis/study';
import { FILE_DOWNLOAD_URL } from '@/config';
import type { Course } from '@/types';
import { usePermission } from '@/utils/permission';
defineOptions({
name: 'CourseList'
@@ -137,6 +147,10 @@ const emit = defineEmits<{
edit: [course: Course];
}>();
// 权限检查
const { hasPermission } = usePermission();
const canForcePublish = computed(() => hasPermission('admin:course:force-publish'));
const loading = ref(false);
const courseList = ref<Course[]>([]);
const total = ref(0);
@@ -211,7 +225,7 @@ async function handleUpdateStatus(course: Course, status: number) {
ElMessage.success(`${statusText}成功`);
loadCourses();
} else {
ElMessage.error(`${statusText}失败`);
ElMessage.error(res.message || `${statusText}失败`);
}
} catch (error) {
if (error !== 'cancel') {
@@ -221,6 +235,33 @@ async function handleUpdateStatus(course: Course, status: number) {
}
}
// 强制发布
async function handleForcePublish(course: Course) {
try {
await ElMessageBox.confirm(
`确定要强制发布课程「${course.name}」吗?此操作将跳过敏感词校验。`,
'强制发布确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
);
const res = await courseApi.forcePublishCourse(course.courseID!);
if (res.success) {
ElMessage.success('强制发布成功');
loadCourses();
} else {
ElMessage.error(res.message || '强制发布失败');
}
} catch (error) {
if (error !== 'cancel') {
console.error('强制发布失败:', error);
ElMessage.error('强制发布失败');
}
}
}
// 删除
async function handleDelete(course: Course) {
try {