web-home
This commit is contained in:
@@ -248,7 +248,7 @@ const cleanDialogVisible = ref(false);
|
||||
const cleanDays = ref(30);
|
||||
|
||||
// 加载日志列表
|
||||
const loadLogList = async () => {
|
||||
async function loadLogList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const filter: Partial<CrontabLog> = {};
|
||||
@@ -273,37 +273,37 @@ const loadLogList = async () => {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
function handleSearch() {
|
||||
pageParam.pageNumber = 1;
|
||||
loadLogList();
|
||||
};
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
function handleReset() {
|
||||
searchForm.taskName = '';
|
||||
searchForm.taskGroup = '';
|
||||
searchForm.executeStatus = undefined;
|
||||
pageParam.pageNumber = 1;
|
||||
loadLogList();
|
||||
};
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
const handlePageChange = (page: number) => {
|
||||
function handlePageChange(page: number) {
|
||||
pageParam.pageNumber = page;
|
||||
loadLogList();
|
||||
};
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
function handleSizeChange(size: number) {
|
||||
pageParam.pageSize = size;
|
||||
pageParam.pageNumber = 1;
|
||||
loadLogList();
|
||||
};
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = async (row: CrontabLog) => {
|
||||
async function handleViewDetail(row: CrontabLog) {
|
||||
try {
|
||||
const result = await crontabApi.getLogById(row.id!);
|
||||
if (result.success && result.data) {
|
||||
@@ -316,10 +316,10 @@ const handleViewDetail = async (row: CrontabLog) => {
|
||||
console.error('获取日志详情失败:', error);
|
||||
ElMessage.error('获取日志详情失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 删除日志
|
||||
const handleDelete = async (row: CrontabLog) => {
|
||||
async function handleDelete(row: CrontabLog) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要删除这条日志吗?',
|
||||
@@ -344,15 +344,15 @@ const handleDelete = async (row: CrontabLog) => {
|
||||
ElMessage.error('删除日志失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 清理日志
|
||||
const handleCleanLogs = () => {
|
||||
function handleCleanLogs() {
|
||||
cleanDialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
// 确认清理
|
||||
const handleConfirmClean = async () => {
|
||||
async function handleConfirmClean() {
|
||||
submitting.value = true;
|
||||
try {
|
||||
const result = await crontabApi.cleanLogs(cleanDays.value);
|
||||
@@ -369,7 +369,7 @@ const handleConfirmClean = async () => {
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
|
||||
@@ -266,10 +266,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import {
|
||||
Plus, Search, Refresh, DocumentCopy, VideoPlay,
|
||||
VideoPause, Promotion, Edit, Delete
|
||||
} from '@element-plus/icons-vue';
|
||||
import { Plus, Search, Refresh, DocumentCopy, VideoPlay, VideoPause, Promotion, Edit, Delete } from '@element-plus/icons-vue';
|
||||
import { crontabApi } from '@/apis/crontab';
|
||||
import type { CrontabTask, PageParam } from '@/types';
|
||||
|
||||
@@ -310,7 +307,7 @@ const formData = reactive<Partial<CrontabTask>>({
|
||||
});
|
||||
|
||||
// 加载爬虫列表
|
||||
const loadCrawlerList = async () => {
|
||||
async function loadCrawlerList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const filter: Partial<CrontabTask> = {
|
||||
@@ -337,50 +334,50 @@ const loadCrawlerList = async () => {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
function handleSearch() {
|
||||
pageParam.pageNumber = 1;
|
||||
loadCrawlerList();
|
||||
};
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
function handleReset() {
|
||||
searchForm.taskName = '';
|
||||
searchForm.status = undefined;
|
||||
pageParam.pageNumber = 1;
|
||||
loadCrawlerList();
|
||||
};
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
const handlePageChange = (page: number) => {
|
||||
function handlePageChange(page: number) {
|
||||
pageParam.pageNumber = page;
|
||||
loadCrawlerList();
|
||||
};
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
function handleSizeChange(size: number) {
|
||||
pageParam.pageSize = size;
|
||||
pageParam.pageNumber = 1;
|
||||
loadCrawlerList();
|
||||
};
|
||||
}
|
||||
|
||||
// 新增爬虫
|
||||
const handleAdd = () => {
|
||||
function handleAdd() {
|
||||
isEdit.value = false;
|
||||
resetFormData();
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
// 编辑爬虫
|
||||
const handleEdit = (row: CrontabTask) => {
|
||||
function handleEdit(row: CrontabTask) {
|
||||
isEdit.value = true;
|
||||
Object.assign(formData, row);
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
// 启动爬虫
|
||||
const handleStart = async (row: CrontabTask) => {
|
||||
async function handleStart(row: CrontabTask) {
|
||||
try {
|
||||
const result = await crontabApi.startTask(row.taskId!);
|
||||
if (result.success) {
|
||||
@@ -393,10 +390,10 @@ const handleStart = async (row: CrontabTask) => {
|
||||
console.error('启动爬虫失败:', error);
|
||||
ElMessage.error('启动爬虫失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 暂停爬虫
|
||||
const handlePause = async (row: CrontabTask) => {
|
||||
async function handlePause(row: CrontabTask) {
|
||||
try {
|
||||
const result = await crontabApi.pauseTask(row.taskId!);
|
||||
if (result.success) {
|
||||
@@ -409,10 +406,10 @@ const handlePause = async (row: CrontabTask) => {
|
||||
console.error('暂停爬虫失败:', error);
|
||||
ElMessage.error('暂停爬虫失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 执行一次
|
||||
const handleExecute = async (row: CrontabTask) => {
|
||||
async function handleExecute(row: CrontabTask) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定立即执行爬虫"${row.taskName}"吗?`,
|
||||
@@ -436,10 +433,10 @@ const handleExecute = async (row: CrontabTask) => {
|
||||
ElMessage.error('执行爬虫失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 删除爬虫
|
||||
const handleDelete = async (row: CrontabTask) => {
|
||||
async function handleDelete(row: CrontabTask) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除爬虫"${row.taskName}"吗?`,
|
||||
@@ -464,10 +461,10 @@ const handleDelete = async (row: CrontabTask) => {
|
||||
ElMessage.error('删除爬虫失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 验证Cron表达式
|
||||
const validateCron = async () => {
|
||||
async function validateCron() {
|
||||
if (!formData.cronExpression) {
|
||||
ElMessage.warning('请输入Cron表达式');
|
||||
return;
|
||||
@@ -484,10 +481,10 @@ const validateCron = async () => {
|
||||
console.error('验证Cron表达式失败:', error);
|
||||
ElMessage.error('验证失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
async function handleSubmit() {
|
||||
// 表单验证
|
||||
if (!formData.taskName) {
|
||||
ElMessage.warning('请输入爬虫名称');
|
||||
@@ -533,15 +530,15 @@ const handleSubmit = async () => {
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
function resetForm() {
|
||||
resetFormData();
|
||||
};
|
||||
}
|
||||
|
||||
// 重置表单数据
|
||||
const resetFormData = () => {
|
||||
function resetFormData() {
|
||||
Object.assign(formData, {
|
||||
taskName: '',
|
||||
taskGroup: 'NEWS_CRAWLER',
|
||||
@@ -554,7 +551,7 @@ const resetFormData = () => {
|
||||
misfirePolicy: 3,
|
||||
description: ''
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
|
||||
@@ -315,48 +315,48 @@ const loadTaskList = async () => {
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
function handleSearch() {
|
||||
pageParam.pageNumber = 1;
|
||||
loadTaskList();
|
||||
};
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
function handleReset() {
|
||||
searchForm.taskName = '';
|
||||
searchForm.taskGroup = '';
|
||||
searchForm.status = undefined;
|
||||
pageParam.pageNumber = 1;
|
||||
loadTaskList();
|
||||
};
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
const handlePageChange = (page: number) => {
|
||||
function handlePageChange(page: number) {
|
||||
pageParam.pageNumber = page;
|
||||
loadTaskList();
|
||||
};
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
function handleSizeChange(size: number) {
|
||||
pageParam.pageSize = size;
|
||||
pageParam.pageNumber = 1;
|
||||
loadTaskList();
|
||||
};
|
||||
}
|
||||
|
||||
// 新增任务
|
||||
const handleAdd = () => {
|
||||
function handleAdd() {
|
||||
isEdit.value = false;
|
||||
resetFormData();
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
// 编辑任务
|
||||
const handleEdit = (row: CrontabTask) => {
|
||||
function handleEdit(row: CrontabTask) {
|
||||
isEdit.value = true;
|
||||
Object.assign(formData, row);
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
// 启动任务
|
||||
const handleStart = async (row: CrontabTask) => {
|
||||
async function handleStart(row: CrontabTask) {
|
||||
try {
|
||||
const result = await crontabApi.startTask(row.taskId!);
|
||||
if (result.success) {
|
||||
@@ -369,10 +369,10 @@ const handleStart = async (row: CrontabTask) => {
|
||||
console.error('启动任务失败:', error);
|
||||
ElMessage.error('启动任务失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 暂停任务
|
||||
const handlePause = async (row: CrontabTask) => {
|
||||
async function handlePause(row: CrontabTask) {
|
||||
try {
|
||||
const result = await crontabApi.pauseTask(row.taskId!);
|
||||
if (result.success) {
|
||||
@@ -385,10 +385,10 @@ const handlePause = async (row: CrontabTask) => {
|
||||
console.error('暂停任务失败:', error);
|
||||
ElMessage.error('暂停任务失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 执行一次
|
||||
const handleExecute = async (row: CrontabTask) => {
|
||||
async function handleExecute(row: CrontabTask) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定立即执行任务"${row.taskName}"吗?`,
|
||||
@@ -412,10 +412,10 @@ const handleExecute = async (row: CrontabTask) => {
|
||||
ElMessage.error('执行任务失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 删除任务
|
||||
const handleDelete = async (row: CrontabTask) => {
|
||||
async function handleDelete(row: CrontabTask) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除任务"${row.taskName}"吗?`,
|
||||
@@ -440,10 +440,10 @@ const handleDelete = async (row: CrontabTask) => {
|
||||
ElMessage.error('删除任务失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 验证Cron表达式
|
||||
const validateCron = async () => {
|
||||
async function validateCron() {
|
||||
if (!formData.cronExpression) {
|
||||
ElMessage.warning('请输入Cron表达式');
|
||||
return;
|
||||
@@ -460,10 +460,10 @@ const validateCron = async () => {
|
||||
console.error('验证Cron表达式失败:', error);
|
||||
ElMessage.error('验证失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
async function handleSubmit() {
|
||||
// 表单验证
|
||||
if (!formData.taskName) {
|
||||
ElMessage.warning('请输入任务名称');
|
||||
@@ -510,15 +510,15 @@ const handleSubmit = async () => {
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
function resetForm() {
|
||||
resetFormData();
|
||||
};
|
||||
}
|
||||
|
||||
// 重置表单数据
|
||||
const resetFormData = () => {
|
||||
function resetFormData() {
|
||||
Object.assign(formData, {
|
||||
taskName: '',
|
||||
taskGroup: 'DEFAULT',
|
||||
@@ -531,7 +531,7 @@ const resetFormData = () => {
|
||||
misfirePolicy: 2,
|
||||
description: ''
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
|
||||
@@ -455,7 +455,7 @@ const filteredModules = computed(() => {
|
||||
});
|
||||
|
||||
// 加载模块列表
|
||||
const loadModuleList = async () => {
|
||||
async function loadModuleList() {
|
||||
moduleLoading.value = true;
|
||||
try {
|
||||
const result = await moduleApi.getModuleList();
|
||||
@@ -470,10 +470,10 @@ const loadModuleList = async () => {
|
||||
} finally {
|
||||
moduleLoading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 加载权限列表
|
||||
const loadPermissions = async (moduleID: string) => {
|
||||
async function loadPermissions(moduleID: string) {
|
||||
permissionLoading.value = true;
|
||||
try {
|
||||
const result = await moduleApi.getModulePermissions(moduleID);
|
||||
@@ -489,32 +489,32 @@ const loadPermissions = async (moduleID: string) => {
|
||||
} finally {
|
||||
permissionLoading.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 选择模块
|
||||
const handleSelectModule = (module: SysModule) => {
|
||||
function handleSelectModule(module: SysModule) {
|
||||
currentModule.value = module;
|
||||
if (module.moduleID) {
|
||||
loadPermissions(module.moduleID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 新增模块
|
||||
const handleAddModule = () => {
|
||||
function handleAddModule() {
|
||||
isEditModule.value = false;
|
||||
resetModuleForm();
|
||||
moduleDialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
// 编辑模块
|
||||
const handleEditModule = (module: SysModule) => {
|
||||
function handleEditModule(module: SysModule) {
|
||||
isEditModule.value = true;
|
||||
Object.assign(moduleForm, module);
|
||||
moduleDialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
// 删除模块
|
||||
const handleDeleteModule = async (module: SysModule) => {
|
||||
async function handleDeleteModule(module: SysModule) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除模块"${module.name}"吗?删除后该模块下的所有权限也会被删除。`,
|
||||
@@ -543,10 +543,10 @@ const handleDeleteModule = async (module: SysModule) => {
|
||||
ElMessage.error('删除模块失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 提交模块表单
|
||||
const handleSubmitModule = async () => {
|
||||
async function handleSubmitModule() {
|
||||
if (!moduleForm.name) {
|
||||
ElMessage.warning('请输入模块名称');
|
||||
return;
|
||||
@@ -582,24 +582,24 @@ const handleSubmitModule = async () => {
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 新增权限
|
||||
const handleAddPermission = () => {
|
||||
function handleAddPermission() {
|
||||
isEditPermission.value = false;
|
||||
resetPermissionForm();
|
||||
permissionDialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
// 编辑权限
|
||||
const handleEditPermission = (permission: SysPermission) => {
|
||||
function handleEditPermission(permission: SysPermission) {
|
||||
isEditPermission.value = true;
|
||||
Object.assign(permissionForm, permission);
|
||||
permissionDialogVisible.value = true;
|
||||
};
|
||||
}
|
||||
|
||||
// 删除权限
|
||||
const handleDeletePermission = async (permission: SysPermission) => {
|
||||
async function handleDeletePermission(permission: SysPermission) {
|
||||
if (!currentModule.value) return;
|
||||
|
||||
try {
|
||||
@@ -630,10 +630,10 @@ const handleDeletePermission = async (permission: SysPermission) => {
|
||||
ElMessage.error('删除权限失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 提交权限表单
|
||||
const handleSubmitPermission = async () => {
|
||||
async function handleSubmitPermission() {
|
||||
if (!permissionForm.name) {
|
||||
ElMessage.warning('请输入权限名称');
|
||||
return;
|
||||
@@ -674,10 +674,10 @@ const handleSubmitPermission = async () => {
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 绑定菜单
|
||||
const handleBindMenu = async (permission: SysPermission) => {
|
||||
async function handleBindMenu(permission: SysPermission) {
|
||||
currentPermission.value = permission;
|
||||
permission.bindType = 'menu';
|
||||
|
||||
@@ -695,10 +695,10 @@ const handleBindMenu = async (permission: SysPermission) => {
|
||||
console.error('获取菜单绑定信息失败:', error);
|
||||
ElMessage.error('获取菜单绑定信息失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 绑定角色
|
||||
const handleBindRole = async (permission: SysPermission) => {
|
||||
async function handleBindRole(permission: SysPermission) {
|
||||
currentPermission.value = permission;
|
||||
permission.bindType = 'role';
|
||||
|
||||
@@ -716,14 +716,14 @@ const handleBindRole = async (permission: SysPermission) => {
|
||||
console.error('获取角色绑定信息失败:', error);
|
||||
ElMessage.error('获取角色绑定信息失败');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 菜单选择相关
|
||||
const isMenuSelected = (menuID: string | undefined): boolean => {
|
||||
function isMenuSelected(menuID: string | undefined): boolean {
|
||||
return menuID ? selectedMenus.value.includes(menuID) : false;
|
||||
};
|
||||
}
|
||||
|
||||
const toggleMenuSelection = (menu: SysMenu) => {
|
||||
function toggleMenuSelection(menu: SysMenu) {
|
||||
if (!menu.menuID) return;
|
||||
const index = selectedMenus.value.indexOf(menu.menuID);
|
||||
if (index > -1) {
|
||||
@@ -731,14 +731,14 @@ const toggleMenuSelection = (menu: SysMenu) => {
|
||||
} else {
|
||||
selectedMenus.value.push(menu.menuID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 角色选择相关
|
||||
const isRoleSelected = (roleID: string | undefined): boolean => {
|
||||
function isRoleSelected(roleID: string | undefined): boolean {
|
||||
return roleID ? selectedRoles.value.includes(roleID) : false;
|
||||
};
|
||||
}
|
||||
|
||||
const toggleRoleSelection = (role: SysRole) => {
|
||||
function toggleRoleSelection(role: SysRole) {
|
||||
if (!role.roleID) return;
|
||||
const index = selectedRoles.value.indexOf(role.roleID);
|
||||
if (index > -1) {
|
||||
@@ -746,10 +746,10 @@ const toggleRoleSelection = (role: SysRole) => {
|
||||
} else {
|
||||
selectedRoles.value.push(role.roleID);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 保存菜单绑定
|
||||
const saveMenuBinding = async () => {
|
||||
async function saveMenuBinding() {
|
||||
if (!currentPermission.value?.permissionID) {
|
||||
ElMessage.error('权限信息不完整');
|
||||
return;
|
||||
@@ -792,10 +792,10 @@ const saveMenuBinding = async () => {
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 保存角色绑定
|
||||
const saveRoleBinding = async () => {
|
||||
async function saveRoleBinding() {
|
||||
if (!currentPermission.value?.permissionID) {
|
||||
ElMessage.error('权限信息不完整');
|
||||
return;
|
||||
@@ -837,10 +837,10 @@ const saveRoleBinding = async () => {
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetModuleForm = () => {
|
||||
function resetModuleForm() {
|
||||
Object.assign(moduleForm, {
|
||||
name: '',
|
||||
code: '',
|
||||
@@ -849,21 +849,21 @@ const resetModuleForm = () => {
|
||||
orderNum: 0,
|
||||
status: 1
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const resetPermissionForm = () => {
|
||||
function resetPermissionForm() {
|
||||
Object.assign(permissionForm, {
|
||||
name: '',
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const resetBindList = () => {
|
||||
function resetBindList() {
|
||||
selectedMenus.value = [];
|
||||
selectedRoles.value = [];
|
||||
currentPermission.value = null;
|
||||
};
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
|
||||
160
schoolNewsWeb/src/views/public/article/card/HotArticleCard.vue
Normal file
160
schoolNewsWeb/src/views/public/article/card/HotArticleCard.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="article-card">
|
||||
<div class="article-image">
|
||||
<div class="image-placeholder"></div>
|
||||
<div class="article-tag">精选文章</div>
|
||||
</div>
|
||||
<div class="article-content">
|
||||
<h3 class="article-title">新时代中国特色社会主义发展历程</h3>
|
||||
<p class="article-desc">
|
||||
习近平新时代中国特色社会主义思想是当代中国马克思主义、二十一世纪马克思主义,是中华文化和中国精神的时代精华,其核心要义与实践要求内涵丰富、意义深远。
|
||||
</p>
|
||||
<div class="article-footer">
|
||||
<div class="meta-tag">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span>热门文章</span>
|
||||
</div>
|
||||
<span class="view-count">2.1w次浏览</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Document } from '@element-plus/icons-vue';
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.article-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 0.625em;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 0.5em 1.25em 0px rgba(164, 182, 199, 0.2);
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-0.25em);
|
||||
box-shadow: 0px 0.75em 1.75em 0px rgba(164, 182, 199, 0.3);
|
||||
}
|
||||
|
||||
.article-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 384 / 221;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
.image-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('data:image/svg+xml,<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"><defs><pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"><path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="1"/></pattern></defs><rect width="100%" height="100%" fill="url(%23grid)" /></svg>');
|
||||
}
|
||||
}
|
||||
|
||||
.article-tag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #D1AD79;
|
||||
border-radius: 0px 0px 0.625em 0px;
|
||||
padding: 0.2em 1.6em;
|
||||
min-width: 5.4em;
|
||||
min-height: 2.1em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.57;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.article-content {
|
||||
padding: 5.2% 5.7% 5.7% 5.7%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.article-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 1.25em;
|
||||
line-height: 1.4;
|
||||
color: #141F38;
|
||||
margin: 0 0 0.2em 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.article-desc {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.57;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.article-footer {
|
||||
margin-top: 1.25em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.meta-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
|
||||
.el-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
span {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.57;
|
||||
color: #C62828;
|
||||
}
|
||||
}
|
||||
|
||||
.view-count {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.57;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="ideological-card">
|
||||
<div class="card-image">
|
||||
<div class="image-placeholder"></div>
|
||||
</div>
|
||||
<div class="date-box">
|
||||
<div class="day">10</div>
|
||||
<div class="month">2025.10</div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">学校召开"习近平新时代中国特色社会主义思想概论"课程集体备课会</h3>
|
||||
<p class="card-desc">
|
||||
深入贯彻习近平总书记关于思政课建设的重要论述,持续推进思政课教学改革创新。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ideological-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 0.625em;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 0.5em 1.25em 0px rgba(164, 182, 199, 0.2);
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-0.25em);
|
||||
box-shadow: 0px 0.75em 1.75em 0px rgba(164, 182, 199, 0.3);
|
||||
}
|
||||
|
||||
.card-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 384 / 221;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.image-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('data:image/svg+xml,<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"><defs><pattern id="dots" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1.5" fill="rgba(255,255,255,0.2)"/></pattern></defs><rect width="100%" height="100%" fill="url(%23dots)" /></svg>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date-box {
|
||||
position: absolute;
|
||||
top: calc(57.55% - 3.5em);
|
||||
left: 5.7%;
|
||||
width: 18.75%;
|
||||
aspect-ratio: 1 / 1;
|
||||
background: #C62828;
|
||||
border-radius: 0.25em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.3em;
|
||||
padding: 0.4em 0.3em;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
|
||||
.day {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 1.875em;
|
||||
line-height: 0.73;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.month {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.57;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 17.4% 5.7% 5.7% 5.7%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.card-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 1.25em;
|
||||
line-height: 1.4;
|
||||
color: #141F38;
|
||||
margin: 0 0 0.25em 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.57;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
2
schoolNewsWeb/src/views/public/article/card/index.ts
Normal file
2
schoolNewsWeb/src/views/public/article/card/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as HotArticleCard } from './HotArticleCard.vue';
|
||||
export { default as IdeologicalArticleCard } from './IdeologicalArticleCard.vue';
|
||||
@@ -1,2 +1,3 @@
|
||||
export { default as ArticleAddView } from './ArticleAddView.vue';
|
||||
export { default as ArticleShowView } from './ArticleShowView.vue';
|
||||
export { default as ArticleShowView } from './ArticleShowView.vue';
|
||||
export * from './card';
|
||||
10
schoolNewsWeb/src/views/public/banner/BannerAdd.vue
Normal file
10
schoolNewsWeb/src/views/public/banner/BannerAdd.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div class="banner-add"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
88
schoolNewsWeb/src/views/public/banner/BannerCard.vue
Normal file
88
schoolNewsWeb/src/views/public/banner/BannerCard.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div class="banner-card">
|
||||
<div class="banner-content" @click="handleLearn">
|
||||
<!-- <img :src="FILE_DOWNLOAD_URL + props.banner.imageUrl" alt="banner" class="banner-image"> -->
|
||||
<span>test</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Banner } from '@/types';
|
||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const props = defineProps<{
|
||||
banner: Banner;
|
||||
}>();
|
||||
|
||||
function handleLearn() {
|
||||
if (props.banner.linkType === 1) {
|
||||
router.push(`/resource/${props.banner.linkID}`);
|
||||
} else if (props.banner.linkType === 2) {
|
||||
router.push(`/course/${props.banner.linkID}`);
|
||||
} else if (props.banner.linkType === 3) {
|
||||
window.open(props.banner.linkUrl, '_blank');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.banner-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.banner-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: red;
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.banner-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 60px 120px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.3) 100%);
|
||||
|
||||
.learn-btn {
|
||||
width: 120px;
|
||||
height: 39px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 22.4px;
|
||||
color: #C62828;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
3
schoolNewsWeb/src/views/public/banner/index.ts
Normal file
3
schoolNewsWeb/src/views/public/banner/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// 导出目录下组件
|
||||
export { default as BannerAdd } from './BannerAdd.vue';
|
||||
export { default as BannerCard } from './BannerCard.vue';
|
||||
1
schoolNewsWeb/src/views/public/editor/index.ts
Normal file
1
schoolNewsWeb/src/views/public/editor/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as RichTextEditorView } from './RichTextEditorView.vue'
|
||||
4
schoolNewsWeb/src/views/public/error/index.ts
Normal file
4
schoolNewsWeb/src/views/public/error/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// 导出错误页面
|
||||
export { default as Error403 } from './403.vue'
|
||||
export { default as Error404 } from './404.vue'
|
||||
export { default as Error500 } from './500.vue'
|
||||
7
schoolNewsWeb/src/views/public/index.ts
Normal file
7
schoolNewsWeb/src/views/public/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './article'
|
||||
export * from './banner'
|
||||
export * from './course'
|
||||
export * from './editor'
|
||||
export * from './error'
|
||||
export * from './login'
|
||||
export * from './task'
|
||||
3
schoolNewsWeb/src/views/public/login/index.ts
Normal file
3
schoolNewsWeb/src/views/public/login/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as Login } from './Login.vue'
|
||||
export { default as Register } from './Register.vue'
|
||||
export { default as ForgotPassword } from './ForgotPassword.vue'
|
||||
262
schoolNewsWeb/src/views/public/task/LearningProgress.vue
Normal file
262
schoolNewsWeb/src/views/public/task/LearningProgress.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div class="learning-progress">
|
||||
<div class="progress-header">
|
||||
<div class="header-left">
|
||||
<h3 class="progress-title">学习进度</h3>
|
||||
<p class="update-time">更新时间:2025-09-25 18:30:00</p>
|
||||
</div>
|
||||
<div class="tab-buttons">
|
||||
<button v-for="(tab, index) in tabs" :key="index" :class="['tab-btn', { active: activeTab === index }]"
|
||||
@click="handleTabChange(index)">
|
||||
{{ tab }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container" ref="chartRef"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import type { EChartsOption } from 'echarts';
|
||||
|
||||
const tabs = ref(['今日', '近一周', '近一月']);
|
||||
const activeTab = ref(0);
|
||||
const chartRef = ref<HTMLElement | null>(null);
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
|
||||
// 模拟不同时间段的数据
|
||||
const chartDataMap = {
|
||||
0: [
|
||||
{ name: '党史', value: 43, highlight: false },
|
||||
{ name: '理论', value: 58, highlight: false },
|
||||
{ name: '政策', value: 34, highlight: false },
|
||||
{ name: '思政', value: 20, highlight: false },
|
||||
{ name: '文化', value: 52, highlight: true }
|
||||
],
|
||||
1: [
|
||||
{ name: '党史', value: 68, highlight: true },
|
||||
{ name: '理论', value: 52, highlight: false },
|
||||
{ name: '政策', value: 45, highlight: false },
|
||||
{ name: '思政', value: 38, highlight: false },
|
||||
{ name: '文化', value: 41, highlight: false }
|
||||
],
|
||||
2: [
|
||||
{ name: '党史', value: 85, highlight: true },
|
||||
{ name: '理论', value: 72, highlight: false },
|
||||
{ name: '政策', value: 58, highlight: false },
|
||||
{ name: '思政', value: 65, highlight: false },
|
||||
{ name: '文化', value: 43, highlight: false }
|
||||
]
|
||||
};
|
||||
|
||||
const chartData = computed(() => chartDataMap[activeTab.value as keyof typeof chartDataMap]);
|
||||
|
||||
// 图表配置
|
||||
function getChartOption(): EChartsOption {
|
||||
const data = chartData.value;
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
borderColor: 'transparent',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
},
|
||||
formatter: function(params: any) {
|
||||
const item = params[0];
|
||||
return `${item.name}: ${item.value}%`;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '0',
|
||||
right: '20px',
|
||||
top: '20px',
|
||||
bottom: '40px',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: data.map(item => item.name),
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#979797',
|
||||
fontSize: 14,
|
||||
fontFamily: 'PingFang SC'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 20,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#979797',
|
||||
fontSize: 14,
|
||||
fontFamily: 'PingFang SC'
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#D4D4D5',
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
data: data.map(item => ({
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: item.highlight ? '#FF6B6B' : '#C62828',
|
||||
borderRadius: [8, 8, 0, 0]
|
||||
}
|
||||
})),
|
||||
barWidth: 40,
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const chartOption = computed(getChartOption);
|
||||
|
||||
// 初始化图表
|
||||
function initChart() {
|
||||
if (!chartRef.value) return;
|
||||
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
chartInstance.setOption(chartOption.value);
|
||||
|
||||
// 自动调整大小
|
||||
window.addEventListener('resize', handleResize);
|
||||
}
|
||||
|
||||
// 处理窗口大小变化
|
||||
function handleResize() {
|
||||
if (chartInstance) {
|
||||
chartInstance.resize();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理标签切换
|
||||
function handleTabChange(index: number) {
|
||||
activeTab.value = index;
|
||||
if (chartInstance) {
|
||||
chartInstance.setOption(chartOption.value, true);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听配置变化
|
||||
watch(() => chartOption.value, () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.setOption(chartOption.value, true);
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
}
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.learning-progress {
|
||||
width: 100%;
|
||||
height: 371px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 10px;
|
||||
padding: 30px 30px 40px 30px;
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 38px;
|
||||
color: #141F38;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #B4B8BF;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
background: #F9F9F9;
|
||||
border: 1px solid #EBEBEB;
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
|
||||
.tab-btn {
|
||||
padding: 8px;
|
||||
min-width: 71px;
|
||||
height: 40px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #B4B8BF;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
background: #FFFFFF;
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
&:hover:not(.active) {
|
||||
color: #141F38;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 243px;
|
||||
}
|
||||
</style>
|
||||
@@ -434,8 +434,8 @@ async function loadTaskList() {
|
||||
|
||||
try {
|
||||
const pageParam: PageParam = {
|
||||
page: pagination.value.current,
|
||||
size: pagination.value.pageSize
|
||||
pageNumber: pagination.value.current,
|
||||
pageSize: pagination.value.pageSize
|
||||
};
|
||||
|
||||
const filter: any = {};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as LearningTaskAdd } from './LearningTaskAdd.vue';
|
||||
export { default as LearningTaskList } from './LearningTaskList.vue';
|
||||
export { default as LearingTaskDetail } from './LearingTaskDetail.vue';
|
||||
export { default as LearingTaskDetail } from './LearningTaskDetail.vue';
|
||||
export { default as LearningProgress } from './LearningProgress.vue';
|
||||
@@ -1,7 +1,142 @@
|
||||
<template>
|
||||
<div class="home-view"></div>
|
||||
<div class="home-view">
|
||||
<!-- 轮播横幅区域 -->
|
||||
<div class="banner-section">
|
||||
<Carousel
|
||||
:items="banners"
|
||||
:interval="5000"
|
||||
:active-icon="dangIcon"
|
||||
indicator-position="bottom-right"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<BannerCard :banner="item" />
|
||||
</template>
|
||||
</Carousel>
|
||||
</div>
|
||||
|
||||
<!-- 热门资源推荐 -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">热门资源推荐</h2>
|
||||
<div class="more-link">
|
||||
<span>查看更多</span>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="article-grid">
|
||||
<HotArticleCard v-for="item in 3" :key="item" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 思政新闻概览 -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">思政新闻概览</h2>
|
||||
<div class="more-link">
|
||||
<span>查看更多</span>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="article-grid">
|
||||
<IdeologicalArticleCard v-for="item in 3" :key="item" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的学习数据 -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">我的学习数据</h2>
|
||||
<div class="more-link">
|
||||
<span>查看更多</span>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<LearningProgress />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { BannerCard, LearningProgress } from '@/views/public/';
|
||||
import { HotArticleCard, IdeologicalArticleCard } from '@/views/public/article';
|
||||
import { Carousel } from '@/components/base';
|
||||
import { ArrowRight } from '@element-plus/icons-vue';
|
||||
import dangIcon from '@/assets/imgs/dang.svg';
|
||||
|
||||
// 模拟轮播数据,实际应该从接口获取
|
||||
const banners = ref([
|
||||
{ id: 1, imageUrl: '', linkType: 1, linkID: '', linkUrl: '' },
|
||||
{ id: 2, imageUrl: '', linkType: 1, linkID: '', linkUrl: '' },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home-view {
|
||||
background-color: #F9F9F9;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.banner-section {
|
||||
width: 100%;
|
||||
height: 30vh;
|
||||
}
|
||||
|
||||
.section {
|
||||
max-width: 1440px;
|
||||
margin: 0 auto;
|
||||
padding: 0 120px;
|
||||
margin-top: 60px;
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.section-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 28px;
|
||||
line-height: 38px;
|
||||
color: #141F38;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.more-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
|
||||
span {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: rgba(20, 31, 56, 0.3);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
color: rgba(20, 31, 56, 0.3);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
span, .el-icon {
|
||||
color: rgba(20, 31, 56, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.article-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -143,10 +143,10 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
// 获取当前用户ID
|
||||
const getUserID = () => {
|
||||
function getUserID() {
|
||||
const userInfo = store.getters['auth/user'];
|
||||
return userInfo?.id || '';
|
||||
};
|
||||
}
|
||||
|
||||
// 加载任务列表(用户视角)
|
||||
async function loadTaskList() {
|
||||
@@ -161,8 +161,8 @@ async function loadTaskList() {
|
||||
|
||||
// 调用用户任务分页接口
|
||||
const pageParam = {
|
||||
page: 1,
|
||||
size: 100 // 获取所有任务,不做分页
|
||||
pageNumber: 1,
|
||||
pageSize: 100 // 获取所有任务,不做分页
|
||||
};
|
||||
|
||||
const filter: TaskItemVO = {
|
||||
|
||||
Reference in New Issue
Block a user