视图路径修改
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -12,7 +12,7 @@
|
||||
"java.compile.nullAnalysis.mode": "automatic",
|
||||
// 终端编码设置
|
||||
"terminal.integrated.encoding": "utf8",
|
||||
"terminal.integrated.defaultProfile.windows": "PowerShell",
|
||||
"terminal.integrated.defaultProfile.windows": "Command Prompt",
|
||||
"terminal.integrated.profiles.windows": {
|
||||
"PowerShell": {
|
||||
"source": "PowerShell",
|
||||
|
||||
@@ -73,30 +73,30 @@ INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, creat
|
||||
|
||||
-- 插入前端菜单数据
|
||||
INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, order_num, type, layout, creator, create_time) VALUES
|
||||
('100', 'menu_home', '首页', NULL, '/home', 'home/HomeView', 'el-icon-house', 1, 1, 'NavigationLayout', '1', now()),
|
||||
('100', 'menu_home', '首页', NULL, '/home', 'user/home/HomeView', 'el-icon-house', 1, 1, 'NavigationLayout', '1', now()),
|
||||
-- 资源中心
|
||||
('200', 'menu_resource_center', '资源中心', NULL, '/resource-center', 'resource-center/ResourceCenterView', 'el-icon-folder-opened', 2, 1, 'NavigationLayout', '1', now()),
|
||||
('200', 'menu_resource_center', '资源中心', NULL, '/resource-center', 'user/resource-center/ResourceCenterView', 'el-icon-folder-opened', 2, 1, 'NavigationLayout', '1', now()),
|
||||
-- 学习计划
|
||||
('300', 'menu_study_plan', '学习计划', NULL, '/study-plan', 'study-plan/StudyPlanView', 'el-icon-reading', 3, 1, 'NavigationLayout', '1', now()),
|
||||
('301', 'menu_study_tasks', '学习任务', 'menu_study_plan', '/study-plan/tasks', 'study-plan/StudyTasksView', 'el-icon-s-order', 1, 1, 'NavigationLayout', '1', now()),
|
||||
('302', 'menu_course_center', '课程中心', 'menu_study_plan', '/study-plan/course', 'study-plan/CourseCenterView', 'el-icon-video-play', 2, 1, 'NavigationLayout', '1', now()),
|
||||
('303', 'menu_task_detail', '任务详情', 'menu_study_plan', '/study-plan/task-detail', 'study-plan/LearningTaskDetailView', 'el-icon-document', 3, 3, 'NavigationLayout', '1', now()),
|
||||
('304', 'menu_course_detail', '课程详情', 'menu_study_plan', '/study-plan/course-detail', 'study-plan/CourseDetailView', 'el-icon-video-play', 4, 3, 'NavigationLayout', '1', now()),
|
||||
('305', 'menu_course_study', '课程学习', 'menu_study_plan', '/study-plan/course-study', 'study-plan/CourseStudyView', 'el-icon-video-play', 5, 3, 'NavigationLayout', '1', now()),
|
||||
('300', 'menu_study_plan', '学习计划', NULL, '/study-plan', 'user/study-plan/StudyPlanView', 'el-icon-reading', 3, 1, 'NavigationLayout', '1', now()),
|
||||
('301', 'menu_study_tasks', '学习任务', 'menu_study_plan', '/study-plan/tasks', 'user/study-plan/StudyTasksView', 'el-icon-s-order', 1, 1, 'NavigationLayout', '1', now()),
|
||||
('302', 'menu_course_center', '课程中心', 'menu_study_plan', '/study-plan/course', 'user/study-plan/CourseCenterView', 'el-icon-video-play', 2, 1, 'NavigationLayout', '1', now()),
|
||||
('303', 'menu_task_detail', '任务详情', 'menu_study_plan', '/study-plan/task-detail', 'user/study-plan/LearningTaskDetailView', 'el-icon-document', 3, 3, 'NavigationLayout', '1', now()),
|
||||
('304', 'menu_course_detail', '课程详情', 'menu_study_plan', '/study-plan/course-detail', 'user/study-plan/CourseDetailView', 'el-icon-video-play', 4, 3, 'NavigationLayout', '1', now()),
|
||||
('305', 'menu_course_study', '课程学习', 'menu_study_plan', '/study-plan/course-study', 'user/study-plan/CourseStudyView', 'el-icon-video-play', 5, 3, 'NavigationLayout', '1', now()),
|
||||
('400', 'menu_user_dropdown', '用户下拉菜单', NULL, '', '', 'el-icon-user', 4, 0, 'NavigationLayout', '1', now()),
|
||||
-- 个人中心
|
||||
('401', 'menu_user_center', '个人中心', 'menu_user_dropdown', '/user-center', 'user-center/UserCenterView', 'el-icon-user', 4, 1, 'NavigationLayout', '1', now()),
|
||||
('402', 'menu_learning_records', '学习记录', 'menu_user_center', '/user-center/learning-records', 'user-center/LearningRecordsView', 'el-icon-document', 1, 0, 'NavigationLayout', '1', now()),
|
||||
('403', 'menu_my_favorites', '我的收藏', 'menu_user_center', '/user-center/favorites', 'user-center/MyFavoritesView', 'el-icon-star-on', 2, 0, 'NavigationLayout', '1', now()),
|
||||
('404', 'menu_my_achievements', '我的成就', 'menu_user_center', '/user-center/achievements', 'user-center/MyAchievementsView', 'el-icon-trophy', 3, 0, 'NavigationLayout', '1', now()),
|
||||
('401', 'menu_user_center', '个人中心', 'menu_user_dropdown', '/user-center', 'user/user-center/UserCenterView', 'el-icon-user', 4, 1, 'NavigationLayout', '1', now()),
|
||||
('402', 'menu_learning_records', '学习记录', 'menu_user_center', '/user-center/learning-records', 'user/user-center/LearningRecordsView', 'el-icon-document', 1, 0, 'NavigationLayout', '1', now()),
|
||||
('403', 'menu_my_favorites', '我的收藏', 'menu_user_center', '/user-center/favorites', 'user/user-center/MyFavoritesView', 'el-icon-star-on', 2, 0, 'NavigationLayout', '1', now()),
|
||||
('404', 'menu_my_achievements', '我的成就', 'menu_user_center', '/user-center/achievements', 'user/user-center/MyAchievementsView', 'el-icon-trophy', 3, 0, 'NavigationLayout', '1', now()),
|
||||
|
||||
-- 账号中心
|
||||
('500', 'menu_profile', '账号中心', 'menu_user_dropdown', '/profile', 'profile/ProfileView', 'el-icon-user-solid', 5, 1, 'NavigationLayout', '1', now()),
|
||||
('501', 'menu_personal_info', '个人信息', 'menu_profile', '/profile/personal-info', 'profile/PersonalInfoView', 'el-icon-user', 1, 0, 'NavigationLayout', '1', now()),
|
||||
('502', 'menu_account_settings', '账号设置', 'menu_profile', '/profile/account-settings', 'profile/AccountSettingsView', 'el-icon-setting', 2, 0, 'NavigationLayout', '1', now()),
|
||||
('500', 'menu_profile', '账号中心', 'menu_user_dropdown', '/profile', 'user/profile/ProfileView', 'el-icon-user-solid', 5, 1, 'NavigationLayout', '1', now()),
|
||||
('501', 'menu_personal_info', '个人信息', 'menu_profile', '/profile/personal-info', 'user/profile/PersonalInfoView', 'el-icon-user', 1, 0, 'NavigationLayout', '1', now()),
|
||||
('502', 'menu_account_settings', '账号设置', 'menu_profile', '/profile/account-settings', 'user/profile/AccountSettingsView', 'el-icon-setting', 2, 0, 'NavigationLayout', '1', now()),
|
||||
|
||||
-- 智能体模块
|
||||
('600', 'menu_ai_assistant', '智能体模块', NULL, '/ai-assistant', 'ai-assistant/AIAssistantView', 'el-icon-cpu', 6, 1, 'NavigationLayout', '1', now());
|
||||
('600', 'menu_ai_assistant', '智能体模块', NULL, '/ai-assistant', 'user/ai-assistant/AIAssistantView', 'el-icon-cpu', 6, 1, 'NavigationLayout', '1', now());
|
||||
|
||||
-- 插入后端管理菜单数据 (type=0 侧边栏菜单)
|
||||
INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, order_num, type, layout, creator, create_time) VALUES
|
||||
@@ -119,8 +119,8 @@ INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, o
|
||||
|
||||
|
||||
-- 文章相关
|
||||
('3010', 'menu_article_add', '文章添加', 'menu_admin_article', '/article/add', 'article/ArticleAddView', 'el-icon-plus', 1, 3, 'SidebarLayout', '1', now()),
|
||||
('3011', 'menu_article_show', '文章展示', 'menu_admin_article', '/article/show', 'article/ArticleShowView', 'el-icon-document', 2, 3, 'SidebarLayout', '1', now()),
|
||||
('3010', 'menu_article_add', '文章添加', 'menu_admin_article', '/article/add', 'public/article/ArticleAddView', 'el-icon-plus', 1, 3, 'SidebarLayout', '1', now()),
|
||||
('3011', 'menu_article_show', '文章展示', 'menu_admin_article', '/article/show', 'public/article/ArticleShowView', 'el-icon-document', 2, 3, 'SidebarLayout', '1', now()),
|
||||
-- 运营管理
|
||||
('4000', 'menu_admin_content_manage', '运营管理', NULL, '', '', 'el-icon-s-operation', 4, 0, 'SidebarLayout', '1', now()),
|
||||
('4001', 'menu_admin_banner', 'Banner管理', 'menu_admin_content_manage', '/admin/manage/content/banner', 'admin/manage/content/BannerManagementView', 'el-icon-picture', 1, 0, 'SidebarLayout', '1', now()),
|
||||
|
||||
@@ -4,20 +4,18 @@
|
||||
<template v-if="hasChildren">
|
||||
<div
|
||||
class="menu-item-content"
|
||||
:class="{
|
||||
'active': isActive,
|
||||
'collapsed': collapsed
|
||||
}"
|
||||
:class="{ 'active': isActive }"
|
||||
@click="toggleExpanded"
|
||||
>
|
||||
<div class="menu-item-inner">
|
||||
<i class="menu-icon" :class="menu.icon || 'icon-folder'"></i>
|
||||
<span class="menu-title" v-if="!collapsed">{{ menu.name }}</span>
|
||||
<i
|
||||
<span class="menu-title">{{ menu.name }}</span>
|
||||
<img
|
||||
src="@/assets/imgs/arrow-down.svg"
|
||||
alt="arrow"
|
||||
class="expand-icon"
|
||||
:class="{ 'expanded': expanded }"
|
||||
v-if="!collapsed"
|
||||
></i>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,13 +23,12 @@
|
||||
<transition name="submenu">
|
||||
<div
|
||||
class="submenu"
|
||||
v-if="expanded && !collapsed"
|
||||
v-if="expanded"
|
||||
>
|
||||
<MenuItem
|
||||
v-for="child in filteredChildren"
|
||||
:key="child.menuID"
|
||||
:menu="child"
|
||||
:collapsed="false"
|
||||
:level="level + 1"
|
||||
@menu-click="$emit('menu-click', $event)"
|
||||
/>
|
||||
@@ -43,15 +40,12 @@
|
||||
<template v-else>
|
||||
<div
|
||||
class="menu-item-content"
|
||||
:class="{
|
||||
'active': isActive,
|
||||
'collapsed': collapsed
|
||||
}"
|
||||
:class="{ 'active': isActive }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div class="menu-item-inner">
|
||||
<i class="menu-icon" :class="menu.icon || 'icon-file'"></i>
|
||||
<span class="menu-title" v-if="!collapsed">{{ menu.name }}</span>
|
||||
<span class="menu-title">{{ menu.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -72,7 +66,6 @@ defineOptions({
|
||||
// Props
|
||||
interface Props {
|
||||
menu: SysMenu;
|
||||
collapsed?: boolean;
|
||||
level?: number;
|
||||
}
|
||||
|
||||
@@ -126,45 +119,38 @@ function handleClick() {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-item {
|
||||
margin: 2px 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.menu-item-content {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
height: 36px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
background-color: rgba(231, 0, 11, 0.05);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #1890ff;
|
||||
background-color: #E7000B;
|
||||
|
||||
.menu-item-inner {
|
||||
color: white;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
margin: 4px 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
color: #0A0A0A;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
|
||||
.collapsed & {
|
||||
justify-content: center;
|
||||
padding: 12px 8px;
|
||||
}
|
||||
font-weight: 500;
|
||||
line-height: 1.43;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
@@ -186,31 +172,30 @@ function handleClick() {
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
font-size: 12px;
|
||||
transition: transform 0.2s ease;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
transition: transform 0.3s ease;
|
||||
margin-left: auto;
|
||||
|
||||
&::before {
|
||||
content: "▶";
|
||||
}
|
||||
flex-shrink: 0;
|
||||
|
||||
&.expanded {
|
||||
transform: rotate(90deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.submenu {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
margin: 4px 0;
|
||||
overflow: hidden;
|
||||
|
||||
.menu-item-content {
|
||||
margin: 0 4px;
|
||||
.menu-item {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.menu-item-content {
|
||||
.menu-item-inner {
|
||||
padding-left: 48px;
|
||||
font-size: 13px;
|
||||
padding-left: 40px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
<template>
|
||||
<div class="sidebar-layout">
|
||||
<!-- 顶部导航栏 -->
|
||||
<TopNavigation />
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div class="layout-content">
|
||||
<!-- 侧边栏和内容 -->
|
||||
<div class="content-wrapper" v-if="hasSidebarMenus">
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar" :class="{ collapsed: sidebarCollapsed }">
|
||||
<div class="sidebar-toggle-btn" @click="toggleSidebar">
|
||||
<i class="toggle-icon">{{ sidebarCollapsed ? '▶' : '◀' }}</i>
|
||||
<aside class="sidebar">
|
||||
<!-- Logo区域 -->
|
||||
<div class="sidebar-header">
|
||||
<div class="logo-container">
|
||||
<img src="@/assets/imgs/logo-icon.svg" alt="Logo" class="logo-icon" />
|
||||
<div class="logo-text">
|
||||
<div class="logo-title">管理后台</div>
|
||||
<div class="logo-subtitle">红色思政平台</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导航菜单 -->
|
||||
<nav class="sidebar-nav">
|
||||
<MenuSidebar
|
||||
:menus="sidebarMenus"
|
||||
:collapsed="sidebarCollapsed"
|
||||
:collapsed="false"
|
||||
@menu-click="handleMenuClick"
|
||||
/>
|
||||
</nav>
|
||||
@@ -39,19 +44,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
import type { SysMenu } from '@/types';
|
||||
import { MenuType } from '@/types/enums';
|
||||
import { TopNavigation, MenuSidebar } from '@/components';
|
||||
import { MenuSidebar } from '@/components';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
|
||||
const sidebarCollapsed = ref(false);
|
||||
|
||||
// 获取所有菜单
|
||||
const allMenus = computed(() => store.getters['auth/menuTree']);
|
||||
|
||||
@@ -68,30 +71,19 @@ const sidebarMenus = computed(() => {
|
||||
// 是否有侧边栏菜单
|
||||
const hasSidebarMenus = computed(() => sidebarMenus.value.length > 0);
|
||||
|
||||
// 切换侧边栏
|
||||
function toggleSidebar() {
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value;
|
||||
localStorage.setItem('sidebarCollapsed', String(sidebarCollapsed.value));
|
||||
}
|
||||
|
||||
// 处理菜单点击
|
||||
function handleMenuClick(menu: SysMenu) {
|
||||
if (menu.url && menu.url !== route.path) {
|
||||
router.push(menu.url);
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复侧边栏状态
|
||||
const savedState = localStorage.getItem('sidebarCollapsed');
|
||||
if (savedState !== null) {
|
||||
sidebarCollapsed.value = savedState === 'true';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sidebar-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
@@ -99,95 +91,101 @@ if (savedState !== null) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 76px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
margin: 16px;
|
||||
gap: 16px;
|
||||
gap: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 260px;
|
||||
background: #001529;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
transition: width 0.3s ease;
|
||||
width: 256px;
|
||||
background: #FFFFFF;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
max-height: calc(100vh - 108px); // 顶部导航76px + 上下边距32px
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
&.collapsed {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-toggle-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -12px;
|
||||
width: 24px;
|
||||
height: 48px;
|
||||
background: white;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 0 12px 12px 0;
|
||||
.sidebar-header {
|
||||
padding: 24px 24px 1px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
height: 97px;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #f0f2f5;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
.logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.logo-title {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #C10007;
|
||||
}
|
||||
|
||||
.logo-subtitle {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
font-weight: 400;
|
||||
line-height: 1.33;
|
||||
color: #6A7282;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px 0;
|
||||
padding: 16px 16px 0;
|
||||
|
||||
// 美化滚动条
|
||||
// 美化滚动条(深色,适配白色背景)
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
background: #a8a8a8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
background: #F9FAFB;
|
||||
overflow-y: auto;
|
||||
min-width: 0;
|
||||
height: calc(100vh - 108px); // 固定高度:视口高度 - 顶部导航76px - 上下边距32px
|
||||
max-height: calc(100vh - 108px);
|
||||
|
||||
// 使用 margin 而不是 padding,避免影响滚动高度计算
|
||||
// margin 不计入元素尺寸,所以不会导致滚动条
|
||||
> * {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
// 美化滚动条
|
||||
&::-webkit-scrollbar {
|
||||
@@ -214,12 +212,15 @@ if (savedState !== null) {
|
||||
}
|
||||
|
||||
.main-content-full {
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
||||
height: calc(100vh - 108px);
|
||||
background: #F9FAFB;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
|
||||
// 使用 margin 而不是 padding,避免影响滚动高度计算
|
||||
> * {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
// 美化滚动条
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
|
||||
@@ -15,7 +15,7 @@ export const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "",
|
||||
name: "Login",
|
||||
component: () => import("@/views/login/Login.vue"),
|
||||
component: () => import("@/views/public/login/Login.vue"),
|
||||
meta: {
|
||||
title: "登录",
|
||||
requiresAuth: false,
|
||||
@@ -31,7 +31,7 @@ export const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "",
|
||||
name: "Register",
|
||||
component: () => import("@/views/login/Register.vue"),
|
||||
component: () => import("@/views/public/login/Register.vue"),
|
||||
meta: {
|
||||
title: "注册",
|
||||
requiresAuth: false,
|
||||
@@ -47,7 +47,7 @@ export const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "",
|
||||
name: "ForgotPassword",
|
||||
component: () => import("@/views/login/ForgotPassword.vue"),
|
||||
component: () => import("@/views/public/login/ForgotPassword.vue"),
|
||||
meta: {
|
||||
title: "忘记密码",
|
||||
requiresAuth: false,
|
||||
@@ -82,7 +82,7 @@ export const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "",
|
||||
name: "Forbidden",
|
||||
component: () => import("@/views/error/403.vue"),
|
||||
component: () => import("@/views/public/error/403.vue"),
|
||||
meta: {
|
||||
title: "403 - 无权限访问",
|
||||
requiresAuth: false,
|
||||
@@ -98,7 +98,7 @@ export const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "",
|
||||
name: "NotFound",
|
||||
component: () => import("@/views/error/404.vue"),
|
||||
component: () => import("@/views/public/error/404.vue"),
|
||||
meta: {
|
||||
title: "404 - 页面不存在",
|
||||
requiresAuth: false,
|
||||
@@ -114,7 +114,7 @@ export const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "",
|
||||
name: "ServerError",
|
||||
component: () => import("@/views/error/500.vue"),
|
||||
component: () => import("@/views/public/error/500.vue"),
|
||||
meta: {
|
||||
title: "500 - 服务器错误",
|
||||
requiresAuth: false,
|
||||
|
||||
@@ -261,7 +261,7 @@ function getComponent(componentName: string) {
|
||||
})
|
||||
.catch(error => {
|
||||
// 返回404组件
|
||||
return import('@/views/error/404.vue').catch(() =>
|
||||
return import('@/views/public/error/404.vue').catch(() =>
|
||||
Promise.resolve({
|
||||
template: `<div class="component-error">
|
||||
<h3>组件加载失败</h3>
|
||||
|
||||
@@ -70,7 +70,7 @@ import { ElButton, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElMessa
|
||||
import { useRouter } from 'vue-router';
|
||||
import { resourceApi, resourceTagApi } from '@/apis/resource'
|
||||
import type { PageParam, ResourceSearchParams, Resource, Tag } from '@/types';
|
||||
import { ArticleShowView } from '@/views/article';
|
||||
import { ArticleShowView } from '@/views/public/article';
|
||||
import { ArticleStatus } from '@/types/enums';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { CourseList, CourseAdd } from '@/views/course/components';
|
||||
import { CourseList, CourseAdd } from '@/views/public/course/components';
|
||||
import type { Course } from '@/types/study';
|
||||
|
||||
type ViewType = 'list' | 'add' | 'edit';
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { LearningTaskList, LearningTaskAdd } from '@/views/task';
|
||||
import { LearningTaskList, LearningTaskAdd } from '@/views/public/task';
|
||||
import type { LearningTask } from '@/types/study';
|
||||
|
||||
defineOptions({
|
||||
|
||||
@@ -184,7 +184,6 @@ function initCharts() {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.system-overview {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
|
||||
@@ -1,466 +0,0 @@
|
||||
<template>
|
||||
<div class="workplace">
|
||||
<div class="workplace-header">
|
||||
<div class="welcome-info">
|
||||
<h1 class="welcome-title">
|
||||
欢迎回来,{{ userInfo?.realName || userInfo?.username }}!
|
||||
</h1>
|
||||
<p class="welcome-subtitle">
|
||||
今天是 {{ currentDate }},{{ greetingText }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="user-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">📝</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ todayNews }}</div>
|
||||
<div class="stat-label">今日新闻</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">👥</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ onlineUsers }}</div>
|
||||
<div class="stat-label">在线用户</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card" v-permission="'system:news:view'">
|
||||
<div class="stat-icon">📊</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ totalViews }}</div>
|
||||
<div class="stat-label">总阅读量</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="workplace-content">
|
||||
<!-- 快捷操作 -->
|
||||
<div class="quick-actions">
|
||||
<h2 class="section-title">快捷操作</h2>
|
||||
<div class="action-grid">
|
||||
<div class="action-card" @click="goToCreateNews" v-permission="'news:create'">
|
||||
<div class="action-icon">✏️</div>
|
||||
<div class="action-title">发布新闻</div>
|
||||
<div class="action-desc">创建新的校园新闻</div>
|
||||
</div>
|
||||
|
||||
<div class="action-card" @click="goToUserManage" v-permission="'system:user:view'">
|
||||
<div class="action-icon">👤</div>
|
||||
<div class="action-title">用户管理</div>
|
||||
<div class="action-desc">管理系统用户</div>
|
||||
</div>
|
||||
|
||||
<div class="action-card" @click="goToSystemSettings" v-permission="'system:settings:view'">
|
||||
<div class="action-icon">⚙️</div>
|
||||
<div class="action-title">系统设置</div>
|
||||
<div class="action-desc">配置系统参数</div>
|
||||
</div>
|
||||
|
||||
<div class="action-card" @click="goToProfile">
|
||||
<div class="action-icon">📋</div>
|
||||
<div class="action-title">个人资料</div>
|
||||
<div class="action-desc">编辑个人信息</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 最近活动 -->
|
||||
<div class="recent-activities">
|
||||
<h2 class="section-title">最近活动</h2>
|
||||
<div class="activity-list">
|
||||
<div class="activity-item" v-for="activity in recentActivities" :key="activity.id">
|
||||
<div class="activity-icon" :class="activity.type">
|
||||
{{ getActivityIcon(activity.type) }}
|
||||
</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-title">{{ activity.title }}</div>
|
||||
<div class="activity-time">{{ formatTime(activity.time) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统公告 -->
|
||||
<div class="system-notice">
|
||||
<h2 class="section-title">系统公告</h2>
|
||||
<div class="notice-list">
|
||||
<div class="notice-item" v-for="notice in systemNotices" :key="notice.id">
|
||||
<div class="notice-type" :class="notice.type">
|
||||
{{ getNoticeTypeText(notice.type) }}
|
||||
</div>
|
||||
<div class="notice-content">
|
||||
<div class="notice-title">{{ notice.title }}</div>
|
||||
<div class="notice-time">{{ formatTime(notice.time) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
// import { usePermission } from '@/directives/permission'; // 暂时注释,后续权限功能开发时启用
|
||||
|
||||
// 数据
|
||||
const todayNews = ref(12);
|
||||
const onlineUsers = ref(56);
|
||||
const totalViews = ref(8924);
|
||||
|
||||
const recentActivities = ref([
|
||||
{
|
||||
id: 1,
|
||||
type: 'news',
|
||||
title: '发布了新闻《学校举办科技创新大赛》',
|
||||
time: new Date(Date.now() - 10 * 60 * 1000) // 10分钟前
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'user',
|
||||
title: '新用户注册:张三',
|
||||
time: new Date(Date.now() - 30 * 60 * 1000) // 30分钟前
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'system',
|
||||
title: '系统维护完成',
|
||||
time: new Date(Date.now() - 2 * 60 * 60 * 1000) // 2小时前
|
||||
}
|
||||
]);
|
||||
|
||||
const systemNotices = ref([
|
||||
{
|
||||
id: 1,
|
||||
type: 'info',
|
||||
title: '系统将于本周日进行例行维护',
|
||||
time: new Date(Date.now() - 24 * 60 * 60 * 1000) // 1天前
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'warning',
|
||||
title: '请及时更新个人资料信息',
|
||||
time: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000) // 3天前
|
||||
}
|
||||
]);
|
||||
|
||||
// Composition API
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
// const { hasPermission } = usePermission(); // 暂时注释,后续权限功能开发时启用
|
||||
|
||||
// 计算属性
|
||||
const userInfo = computed(() => store.getters['auth/user']);
|
||||
|
||||
const currentDate = computed(() => {
|
||||
return new Date().toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
weekday: 'long'
|
||||
});
|
||||
});
|
||||
|
||||
const greetingText = computed(() => {
|
||||
const hour = new Date().getHours();
|
||||
if (hour < 6) return '夜深了,注意休息';
|
||||
if (hour < 12) return '早上好';
|
||||
if (hour < 18) return '下午好';
|
||||
return '晚上好';
|
||||
});
|
||||
|
||||
// 方法
|
||||
function goToCreateNews() {
|
||||
router.push('/news/create');
|
||||
}
|
||||
|
||||
function goToUserManage() {
|
||||
router.push('/system/user');
|
||||
}
|
||||
|
||||
function goToSystemSettings() {
|
||||
router.push('/system/settings');
|
||||
}
|
||||
|
||||
function goToProfile() {
|
||||
router.push('/profile');
|
||||
}
|
||||
|
||||
function getActivityIcon(type: string) {
|
||||
const icons = {
|
||||
news: '📰',
|
||||
user: '👤',
|
||||
system: '⚙️'
|
||||
};
|
||||
return icons[type as keyof typeof icons] || '📝';
|
||||
}
|
||||
|
||||
function getNoticeTypeText(type: string) {
|
||||
const types = {
|
||||
info: '通知',
|
||||
warning: '提醒',
|
||||
error: '警告'
|
||||
};
|
||||
return types[type as keyof typeof types] || '公告';
|
||||
}
|
||||
|
||||
function formatTime(time: Date) {
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - time.getTime();
|
||||
|
||||
if (diff < 60 * 1000) return '刚刚';
|
||||
if (diff < 60 * 60 * 1000) return `${Math.floor(diff / 60 / 1000)}分钟前`;
|
||||
if (diff < 24 * 60 * 60 * 1000) return `${Math.floor(diff / 60 / 60 / 1000)}小时前`;
|
||||
if (diff < 7 * 24 * 60 * 60 * 1000) return `${Math.floor(diff / 24 / 60 / 60 / 1000)}天前`;
|
||||
|
||||
return time.toLocaleDateString('zh-CN');
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 可以在这里加载统计数据
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.workplace {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.workplace-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 32px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.welcome-info {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.welcome-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.welcome-subtitle {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.user-stats {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
min-width: 150px;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
.stat-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
.stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workplace-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
padding: 20px;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.action-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.action-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recent-activities {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
grid-column: 1;
|
||||
|
||||
.activity-list {
|
||||
.activity-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.activity-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
|
||||
&.news { background: #e6f7ff; }
|
||||
&.user { background: #f6ffed; }
|
||||
&.system { background: #fff7e6; }
|
||||
}
|
||||
|
||||
.activity-content {
|
||||
flex: 1;
|
||||
|
||||
.activity-title {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.activity-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.system-notice {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
grid-column: 2;
|
||||
|
||||
.notice-list {
|
||||
.notice-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.notice-type {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
&.info {
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background: #fff7e6;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
&.error {
|
||||
background: #fff2f0;
|
||||
color: #f5222d;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-content {
|
||||
flex: 1;
|
||||
|
||||
.notice-title {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.notice-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,39 +0,0 @@
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<!-- 个人学习数据概览 -->
|
||||
<LearningDataOverview />
|
||||
|
||||
<!-- 书报馆件 -->
|
||||
<BookHallSection />
|
||||
|
||||
<!-- TOP音乐推荐 -->
|
||||
<TopMusicRecommend />
|
||||
|
||||
<!-- 新闻概览 -->
|
||||
<NewsOverview />
|
||||
|
||||
<!-- 导航栏 -->
|
||||
<NavigationBar />
|
||||
|
||||
<!-- 破破栏索 -->
|
||||
<SearchIndex />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import LearningDataOverview from './components/LearningDataOverview.vue';
|
||||
import BookHallSection from './components/BookHallSection.vue';
|
||||
import TopMusicRecommend from './components/TopMusicRecommend.vue';
|
||||
import NewsOverview from './components/NewsOverview.vue';
|
||||
import NavigationBar from './components/NavigationBar.vue';
|
||||
import SearchIndex from './components/SearchIndex.vue';
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
<template>
|
||||
<div class="book-hall-section">
|
||||
<h2 class="section-title">书报馆件</h2>
|
||||
<div class="book-list">
|
||||
<div class="book-item" v-for="book in books" :key="book.id">
|
||||
<div class="book-cover">
|
||||
<img :src="book.cover" :alt="book.title" />
|
||||
</div>
|
||||
<div class="book-info">
|
||||
<h3>{{ book.title }}</h3>
|
||||
<p>{{ book.author }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const books = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载书籍数据
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.book-hall-section {
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.book-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.book-item {
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.book-cover {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.book-info {
|
||||
margin-top: 12px;
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #141F38;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<div class="learning-data-overview">
|
||||
<h2 class="section-title">个人学习数据概览</h2>
|
||||
<div class="data-cards">
|
||||
<div class="data-card" v-for="item in dataItems" :key="item.label">
|
||||
<div class="card-icon">{{ item.icon }}</div>
|
||||
<div class="card-content">
|
||||
<div class="card-value">{{ item.value }}</div>
|
||||
<div class="card-label">{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const dataItems = ref([
|
||||
{ icon: '📚', label: '学习时长', value: '0小时' },
|
||||
{ icon: '✅', label: '完成任务', value: '0个' },
|
||||
{ icon: '⭐', label: '获得成就', value: '0个' },
|
||||
{ icon: '📖', label: '阅读文章', value: '0篇' }
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载用户学习数据
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.learning-data-overview {
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.data-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.data-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<template>
|
||||
<div class="navigation-bar">
|
||||
<div class="nav-item" v-for="item in navItems" :key="item.id" @click="navigate(item)">
|
||||
<div class="nav-icon">{{ item.icon }}</div>
|
||||
<div class="nav-label">{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const navItems = ref([
|
||||
{ id: 1, icon: '📚', label: '资源中心', path: '/resource-center' },
|
||||
{ id: 2, icon: '📝', label: '学习计划', path: '/study-plan' },
|
||||
{ id: 3, icon: '👤', label: '个人中心', path: '/user-center' },
|
||||
{ id: 4, icon: '🤖', label: 'AI助手', path: '/ai-assistant' }
|
||||
]);
|
||||
|
||||
function navigate(item: any) {
|
||||
router.push(item.path);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navigation-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: linear-gradient(135deg, #C62828, #E53935);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #141F38;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
<template>
|
||||
<div class="news-overview">
|
||||
<h2 class="section-title">新闻概览</h2>
|
||||
<div class="news-list">
|
||||
<div class="news-item" v-for="news in newsList" :key="news.id" @click="goToDetail(news)">
|
||||
<div class="news-image">
|
||||
<img :src="news.image" :alt="news.title" />
|
||||
</div>
|
||||
<div class="news-content">
|
||||
<h3>{{ news.title }}</h3>
|
||||
<p class="news-summary">{{ news.summary }}</p>
|
||||
<div class="news-meta">
|
||||
<span class="news-date">{{ news.publishDate }}</span>
|
||||
<span class="news-views">{{ news.views }} 阅读</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const newsList = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载新闻数据
|
||||
});
|
||||
|
||||
function goToDetail(news: any) {
|
||||
router.push(`/news/${news.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.news-overview {
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.news-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.news-image {
|
||||
width: 200px;
|
||||
height: 140px;
|
||||
flex-shrink: 0;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.news-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.news-summary {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.news-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<template>
|
||||
<div class="search-index">
|
||||
<h2 class="section-title">搜索索引</h2>
|
||||
<div class="search-tags">
|
||||
<span
|
||||
class="tag"
|
||||
v-for="tag in popularTags"
|
||||
:key="tag"
|
||||
@click="handleTagClick(tag)"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const popularTags = ref([
|
||||
'红色思政', '党史学习', '新时代精神', '爱国主义',
|
||||
'社会主义核心价值观', '中国梦', '改革开放', '脱贫攻坚'
|
||||
]);
|
||||
|
||||
function handleTagClick(tag: string) {
|
||||
router.push(`/search?keyword=${encodeURIComponent(tag)}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-index {
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 8px 16px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #C62828;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
<template>
|
||||
<div class="top-music-recommend">
|
||||
<h2 class="section-title">TOP音乐推荐</h2>
|
||||
<div class="music-list">
|
||||
<div class="music-item" v-for="music in musicList" :key="music.id">
|
||||
<div class="music-cover">
|
||||
<img :src="music.cover" :alt="music.title" />
|
||||
<div class="play-btn">▶</div>
|
||||
</div>
|
||||
<div class="music-info">
|
||||
<h3>{{ music.title }}</h3>
|
||||
<p>{{ music.artist }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const musicList = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载音乐推荐数据
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.top-music-recommend {
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.music-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.music-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.music-cover {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(198, 40, 40, 0.9);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
&:hover .play-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.music-info {
|
||||
margin-top: 12px;
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #141F38;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -219,7 +219,7 @@ import {
|
||||
Clock,
|
||||
Download
|
||||
} from '@element-plus/icons-vue';
|
||||
import { ArticleShowView } from '@/views/article';
|
||||
import { ArticleShowView } from '@/views/public/article';
|
||||
import { courseApi } from '@/apis/study';
|
||||
import { learningRecordApi, learningHistoryApi } from '@/apis/study';
|
||||
import { resourceApi } from '@/apis/resource';
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
path: '/editor',
|
||||
name: 'RichTextEditor',
|
||||
component: () => import('@/views/editor/RichTextEditorView.vue'),
|
||||
component: () => import('@/views/public/editor/RichTextEditorView.vue'),
|
||||
meta: {
|
||||
title: '富文本编辑器',
|
||||
requiresAuth: true
|
||||
@@ -54,7 +54,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import RichTextEditorView from '@/views/editor/RichTextEditorView.vue';
|
||||
import RichTextEditorView from '@/views/public/editor/RichTextEditorView.vue';
|
||||
</script>
|
||||
```
|
||||
|
||||
7
schoolNewsWeb/src/views/user/home/HomeView.vue
Normal file
7
schoolNewsWeb/src/views/user/home/HomeView.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div class="home-view"></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -18,7 +18,7 @@
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { FloatingSidebar } from '@/components/base';
|
||||
import { UserCard } from '@/views/user-center/components';
|
||||
import { UserCard } from '@/views/user/user-center/components';
|
||||
import { getParentChildrenRoutes } from '@/utils/routeUtils';
|
||||
import type { SysMenu } from '@/types/menu';
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { ArticleShowView } from '@/views/article';
|
||||
import { ResouceCollect, ResouceBottom } from '@/views/resource-center/components';
|
||||
import { ArticleShowView } from '@/views/public/article';
|
||||
import { ResouceCollect, ResouceBottom } from '@/views/user/resource-center/components';
|
||||
import { resourceApi } from '@/apis/resource';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { Resource } from '@/types/resource';
|
||||
@@ -101,7 +101,7 @@ import { ElMessage } from 'element-plus';
|
||||
import { Search, VideoPlay } from '@element-plus/icons-vue';
|
||||
import { courseApi } from '@/apis/study';
|
||||
import type { Course, PageParam } from '@/types';
|
||||
import { StudyPlanLayout } from '@/views/study-plan';
|
||||
import { StudyPlanLayout } from '@/views/user/study-plan';
|
||||
import defaultCover from '@/assets/imgs/default-course-bg.png'
|
||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||
|
||||
@@ -155,7 +155,7 @@ async function loadCourseList() {
|
||||
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
pageParam.value.page = 1;
|
||||
pageParam.value.pageNumber = 1;
|
||||
loadCourseList();
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { CourseDetail } from '@/views/course/components';
|
||||
import { CourseDetail } from '@/views/public/course/components';
|
||||
|
||||
defineOptions({
|
||||
name: 'CourseDetailView'
|
||||
@@ -12,7 +12,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { CourseLearning } from '@/views/course/components';
|
||||
import { CourseLearning } from '@/views/public/course/components';
|
||||
|
||||
defineOptions({
|
||||
name: 'CourseStudyView'
|
||||
@@ -10,7 +10,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { LearingTaskDetail } from '@/views/task';
|
||||
import { LearingTaskDetail } from '@/views/public/task';
|
||||
|
||||
defineOptions({
|
||||
name: 'LearningTaskDetailView'
|
||||
@@ -114,7 +114,7 @@ import { DocumentCopy, DocumentChecked } from '@element-plus/icons-vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { learningTaskApi } from '@/apis/study';
|
||||
import type { LearningTask, TaskItemVO } from '@/types';
|
||||
import { StudyPlanLayout } from '@/views/study-plan';
|
||||
import { StudyPlanLayout } from '@/views/user/study-plan';
|
||||
|
||||
defineOptions({
|
||||
name: 'StudyTasksView'
|
||||
@@ -18,7 +18,7 @@
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { FloatingSidebar } from '@/components/base';
|
||||
import { UserCard } from '@/views/user-center/components';
|
||||
import { UserCard } from '@/views/user/user-center/components';
|
||||
import { getParentChildrenRoutes } from '@/utils/routeUtils';
|
||||
import type { SysMenu } from '@/types/menu';
|
||||
|
||||
Reference in New Issue
Block a user