视图路径修改
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -12,7 +12,7 @@
|
|||||||
"java.compile.nullAnalysis.mode": "automatic",
|
"java.compile.nullAnalysis.mode": "automatic",
|
||||||
// 终端编码设置
|
// 终端编码设置
|
||||||
"terminal.integrated.encoding": "utf8",
|
"terminal.integrated.encoding": "utf8",
|
||||||
"terminal.integrated.defaultProfile.windows": "PowerShell",
|
"terminal.integrated.defaultProfile.windows": "Command Prompt",
|
||||||
"terminal.integrated.profiles.windows": {
|
"terminal.integrated.profiles.windows": {
|
||||||
"PowerShell": {
|
"PowerShell": {
|
||||||
"source": "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
|
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()),
|
('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', 'study-plan/StudyTasksView', 'el-icon-s-order', 1, 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', 'study-plan/CourseCenterView', 'el-icon-video-play', 2, 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', 'study-plan/LearningTaskDetailView', 'el-icon-document', 3, 3, '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', 'study-plan/CourseDetailView', 'el-icon-video-play', 4, 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', 'study-plan/CourseStudyView', 'el-icon-video-play', 5, 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()),
|
('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()),
|
('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-center/LearningRecordsView', 'el-icon-document', 1, 0, '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-center/MyFavoritesView', 'el-icon-star-on', 2, 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-center/MyAchievementsView', 'el-icon-trophy', 3, 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()),
|
('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', 'profile/PersonalInfoView', 'el-icon-user', 1, 0, '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', 'profile/AccountSettingsView', 'el-icon-setting', 2, 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 侧边栏菜单)
|
-- 插入后端管理菜单数据 (type=0 侧边栏菜单)
|
||||||
INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, order_num, type, layout, creator, create_time) VALUES
|
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()),
|
('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', 'article/ArticleShowView', 'el-icon-document', 2, 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()),
|
('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()),
|
('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">
|
<template v-if="hasChildren">
|
||||||
<div
|
<div
|
||||||
class="menu-item-content"
|
class="menu-item-content"
|
||||||
:class="{
|
:class="{ 'active': isActive }"
|
||||||
'active': isActive,
|
|
||||||
'collapsed': collapsed
|
|
||||||
}"
|
|
||||||
@click="toggleExpanded"
|
@click="toggleExpanded"
|
||||||
>
|
>
|
||||||
<div class="menu-item-inner">
|
<div class="menu-item-inner">
|
||||||
<i class="menu-icon" :class="menu.icon || 'icon-folder'"></i>
|
<i class="menu-icon" :class="menu.icon || 'icon-folder'"></i>
|
||||||
<span class="menu-title" v-if="!collapsed">{{ menu.name }}</span>
|
<span class="menu-title">{{ menu.name }}</span>
|
||||||
<i
|
<img
|
||||||
|
src="@/assets/imgs/arrow-down.svg"
|
||||||
|
alt="arrow"
|
||||||
class="expand-icon"
|
class="expand-icon"
|
||||||
:class="{ 'expanded': expanded }"
|
:class="{ 'expanded': expanded }"
|
||||||
v-if="!collapsed"
|
/>
|
||||||
></i>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -25,13 +23,12 @@
|
|||||||
<transition name="submenu">
|
<transition name="submenu">
|
||||||
<div
|
<div
|
||||||
class="submenu"
|
class="submenu"
|
||||||
v-if="expanded && !collapsed"
|
v-if="expanded"
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
v-for="child in filteredChildren"
|
v-for="child in filteredChildren"
|
||||||
:key="child.menuID"
|
:key="child.menuID"
|
||||||
:menu="child"
|
:menu="child"
|
||||||
:collapsed="false"
|
|
||||||
:level="level + 1"
|
:level="level + 1"
|
||||||
@menu-click="$emit('menu-click', $event)"
|
@menu-click="$emit('menu-click', $event)"
|
||||||
/>
|
/>
|
||||||
@@ -43,15 +40,12 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<div
|
<div
|
||||||
class="menu-item-content"
|
class="menu-item-content"
|
||||||
:class="{
|
:class="{ 'active': isActive }"
|
||||||
'active': isActive,
|
|
||||||
'collapsed': collapsed
|
|
||||||
}"
|
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
>
|
>
|
||||||
<div class="menu-item-inner">
|
<div class="menu-item-inner">
|
||||||
<i class="menu-icon" :class="menu.icon || 'icon-file'"></i>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -72,7 +66,6 @@ defineOptions({
|
|||||||
// Props
|
// Props
|
||||||
interface Props {
|
interface Props {
|
||||||
menu: SysMenu;
|
menu: SysMenu;
|
||||||
collapsed?: boolean;
|
|
||||||
level?: number;
|
level?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,45 +119,38 @@ function handleClick() {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.menu-item {
|
.menu-item {
|
||||||
margin: 2px 8px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item-content {
|
.menu-item-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
height: 36px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(231, 0, 11, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: #1890ff;
|
background-color: #E7000B;
|
||||||
|
|
||||||
.menu-item-inner {
|
.menu-item-inner {
|
||||||
color: white;
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.collapsed {
|
|
||||||
margin: 4px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item-inner {
|
.menu-item-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px 16px;
|
height: 100%;
|
||||||
color: rgba(255, 255, 255, 0.85);
|
padding: 0 12px;
|
||||||
|
color: #0A0A0A;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1;
|
font-weight: 500;
|
||||||
|
line-height: 1.43;
|
||||||
.collapsed & {
|
|
||||||
justify-content: center;
|
|
||||||
padding: 12px 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-icon {
|
.menu-icon {
|
||||||
@@ -186,31 +172,30 @@ function handleClick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.expand-icon {
|
.expand-icon {
|
||||||
font-size: 12px;
|
width: 12px;
|
||||||
transition: transform 0.2s ease;
|
height: 12px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
&::before {
|
|
||||||
content: "▶";
|
|
||||||
}
|
|
||||||
|
|
||||||
&.expanded {
|
&.expanded {
|
||||||
transform: rotate(90deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.submenu {
|
.submenu {
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.menu-item-content {
|
.menu-item {
|
||||||
margin: 0 4px;
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item-content {
|
||||||
.menu-item-inner {
|
.menu-item-inner {
|
||||||
padding-left: 48px;
|
padding-left: 40px;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="sidebar-layout">
|
<div class="sidebar-layout">
|
||||||
<!-- 顶部导航栏 -->
|
|
||||||
<TopNavigation />
|
|
||||||
|
|
||||||
<!-- 主内容区域 -->
|
<!-- 主内容区域 -->
|
||||||
<div class="layout-content">
|
<div class="layout-content">
|
||||||
<!-- 侧边栏和内容 -->
|
<!-- 侧边栏和内容 -->
|
||||||
<div class="content-wrapper" v-if="hasSidebarMenus">
|
<div class="content-wrapper" v-if="hasSidebarMenus">
|
||||||
<!-- 侧边栏 -->
|
<!-- 侧边栏 -->
|
||||||
<aside class="sidebar" :class="{ collapsed: sidebarCollapsed }">
|
<aside class="sidebar">
|
||||||
<div class="sidebar-toggle-btn" @click="toggleSidebar">
|
<!-- Logo区域 -->
|
||||||
<i class="toggle-icon">{{ sidebarCollapsed ? '▶' : '◀' }}</i>
|
<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>
|
</div>
|
||||||
|
|
||||||
|
<!-- 导航菜单 -->
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
<MenuSidebar
|
<MenuSidebar
|
||||||
:menus="sidebarMenus"
|
:menus="sidebarMenus"
|
||||||
:collapsed="sidebarCollapsed"
|
:collapsed="false"
|
||||||
@menu-click="handleMenuClick"
|
@menu-click="handleMenuClick"
|
||||||
/>
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -39,19 +44,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import type { SysMenu } from '@/types';
|
import type { SysMenu } from '@/types';
|
||||||
import { MenuType } from '@/types/enums';
|
import { MenuType } from '@/types/enums';
|
||||||
import { TopNavigation, MenuSidebar } from '@/components';
|
import { MenuSidebar } from '@/components';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
const sidebarCollapsed = ref(false);
|
|
||||||
|
|
||||||
// 获取所有菜单
|
// 获取所有菜单
|
||||||
const allMenus = computed(() => store.getters['auth/menuTree']);
|
const allMenus = computed(() => store.getters['auth/menuTree']);
|
||||||
|
|
||||||
@@ -68,30 +71,19 @@ const sidebarMenus = computed(() => {
|
|||||||
// 是否有侧边栏菜单
|
// 是否有侧边栏菜单
|
||||||
const hasSidebarMenus = computed(() => sidebarMenus.value.length > 0);
|
const hasSidebarMenus = computed(() => sidebarMenus.value.length > 0);
|
||||||
|
|
||||||
// 切换侧边栏
|
|
||||||
function toggleSidebar() {
|
|
||||||
sidebarCollapsed.value = !sidebarCollapsed.value;
|
|
||||||
localStorage.setItem('sidebarCollapsed', String(sidebarCollapsed.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理菜单点击
|
// 处理菜单点击
|
||||||
function handleMenuClick(menu: SysMenu) {
|
function handleMenuClick(menu: SysMenu) {
|
||||||
if (menu.url && menu.url !== route.path) {
|
if (menu.url && menu.url !== route.path) {
|
||||||
router.push(menu.url);
|
router.push(menu.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复侧边栏状态
|
|
||||||
const savedState = localStorage.getItem('sidebarCollapsed');
|
|
||||||
if (savedState !== null) {
|
|
||||||
sidebarCollapsed.value = savedState === 'true';
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.sidebar-layout {
|
.sidebar-layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
background: #f0f2f5;
|
background: #f0f2f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,95 +91,101 @@ if (savedState !== null) {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: calc(100vh - 76px);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 16px;
|
gap: 0;
|
||||||
gap: 16px;
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 260px;
|
width: 256px;
|
||||||
background: #001529;
|
background: #FFFFFF;
|
||||||
border-radius: 4px;
|
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
height: 100vh;
|
||||||
max-height: calc(100vh - 108px); // 顶部导航76px + 上下边距32px
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&.collapsed {
|
|
||||||
width: 80px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-toggle-btn {
|
.sidebar-header {
|
||||||
position: absolute;
|
padding: 24px 24px 1px;
|
||||||
top: 50%;
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
right: -12px;
|
height: 97px;
|
||||||
width: 24px;
|
}
|
||||||
height: 48px;
|
|
||||||
background: white;
|
.logo-container {
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
border-radius: 0 12px 12px 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 12px;
|
||||||
cursor: pointer;
|
|
||||||
z-index: 10;
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f0f2f5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
font-size: 12px;
|
||||||
color: #666;
|
font-weight: 400;
|
||||||
}
|
line-height: 1.33;
|
||||||
|
color: #6A7282;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-nav {
|
.sidebar-nav {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 16px 0;
|
padding: 16px 16px 0;
|
||||||
|
|
||||||
// 美化滚动条
|
// 美化滚动条(深色,适配白色背景)
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: #f1f1f1;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: #c1c1c1;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: #a8a8a8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background: white;
|
background: #F9FAFB;
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: calc(100vh - 108px); // 固定高度:视口高度 - 顶部导航76px - 上下边距32px
|
|
||||||
max-height: calc(100vh - 108px);
|
// 使用 margin 而不是 padding,避免影响滚动高度计算
|
||||||
|
// margin 不计入元素尺寸,所以不会导致滚动条
|
||||||
|
> * {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
// 美化滚动条
|
// 美化滚动条
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
@@ -214,12 +212,15 @@ if (savedState !== null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-content-full {
|
.main-content-full {
|
||||||
background: white;
|
background: #F9FAFB;
|
||||||
border-radius: 4px;
|
height: 100vh;
|
||||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
|
||||||
height: calc(100vh - 108px);
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
// 使用 margin 而不是 padding,避免影响滚动高度计算
|
||||||
|
> * {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
// 美化滚动条
|
// 美化滚动条
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "Login",
|
name: "Login",
|
||||||
component: () => import("@/views/login/Login.vue"),
|
component: () => import("@/views/public/login/Login.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "登录",
|
title: "登录",
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
@@ -31,7 +31,7 @@ export const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "Register",
|
name: "Register",
|
||||||
component: () => import("@/views/login/Register.vue"),
|
component: () => import("@/views/public/login/Register.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "注册",
|
title: "注册",
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
@@ -47,7 +47,7 @@ export const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "ForgotPassword",
|
name: "ForgotPassword",
|
||||||
component: () => import("@/views/login/ForgotPassword.vue"),
|
component: () => import("@/views/public/login/ForgotPassword.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "忘记密码",
|
title: "忘记密码",
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
@@ -82,7 +82,7 @@ export const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "Forbidden",
|
name: "Forbidden",
|
||||||
component: () => import("@/views/error/403.vue"),
|
component: () => import("@/views/public/error/403.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "403 - 无权限访问",
|
title: "403 - 无权限访问",
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
@@ -98,7 +98,7 @@ export const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "NotFound",
|
name: "NotFound",
|
||||||
component: () => import("@/views/error/404.vue"),
|
component: () => import("@/views/public/error/404.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "404 - 页面不存在",
|
title: "404 - 页面不存在",
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
@@ -114,7 +114,7 @@ export const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
name: "ServerError",
|
name: "ServerError",
|
||||||
component: () => import("@/views/error/500.vue"),
|
component: () => import("@/views/public/error/500.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "500 - 服务器错误",
|
title: "500 - 服务器错误",
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ function getComponent(componentName: string) {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// 返回404组件
|
// 返回404组件
|
||||||
return import('@/views/error/404.vue').catch(() =>
|
return import('@/views/public/error/404.vue').catch(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
template: `<div class="component-error">
|
template: `<div class="component-error">
|
||||||
<h3>组件加载失败</h3>
|
<h3>组件加载失败</h3>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ import { ElButton, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElMessa
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { resourceApi, resourceTagApi } from '@/apis/resource'
|
import { resourceApi, resourceTagApi } from '@/apis/resource'
|
||||||
import type { PageParam, ResourceSearchParams, Resource, Tag } from '@/types';
|
import type { PageParam, ResourceSearchParams, Resource, Tag } from '@/types';
|
||||||
import { ArticleShowView } from '@/views/article';
|
import { ArticleShowView } from '@/views/public/article';
|
||||||
import { ArticleStatus } from '@/types/enums';
|
import { ArticleStatus } from '@/types/enums';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
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';
|
import type { Course } from '@/types/study';
|
||||||
|
|
||||||
type ViewType = 'list' | 'add' | 'edit';
|
type ViewType = 'list' | 'add' | 'edit';
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { LearningTaskList, LearningTaskAdd } from '@/views/task';
|
import { LearningTaskList, LearningTaskAdd } from '@/views/public/task';
|
||||||
import type { LearningTask } from '@/types/study';
|
import type { LearningTask } from '@/types/study';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ function initCharts() {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.system-overview {
|
.system-overview {
|
||||||
padding: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.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,
|
Clock,
|
||||||
Download
|
Download
|
||||||
} from '@element-plus/icons-vue';
|
} from '@element-plus/icons-vue';
|
||||||
import { ArticleShowView } from '@/views/article';
|
import { ArticleShowView } from '@/views/public/article';
|
||||||
import { courseApi } from '@/apis/study';
|
import { courseApi } from '@/apis/study';
|
||||||
import { learningRecordApi, learningHistoryApi } from '@/apis/study';
|
import { learningRecordApi, learningHistoryApi } from '@/apis/study';
|
||||||
import { resourceApi } from '@/apis/resource';
|
import { resourceApi } from '@/apis/resource';
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
{
|
{
|
||||||
path: '/editor',
|
path: '/editor',
|
||||||
name: 'RichTextEditor',
|
name: 'RichTextEditor',
|
||||||
component: () => import('@/views/editor/RichTextEditorView.vue'),
|
component: () => import('@/views/public/editor/RichTextEditorView.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '富文本编辑器',
|
title: '富文本编辑器',
|
||||||
requiresAuth: true
|
requiresAuth: true
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import RichTextEditorView from '@/views/editor/RichTextEditorView.vue';
|
import RichTextEditorView from '@/views/public/editor/RichTextEditorView.vue';
|
||||||
</script>
|
</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 { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { FloatingSidebar } from '@/components/base';
|
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 { getParentChildrenRoutes } from '@/utils/routeUtils';
|
||||||
import type { SysMenu } from '@/types/menu';
|
import type { SysMenu } from '@/types/menu';
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { ArticleShowView } from '@/views/article';
|
import { ArticleShowView } from '@/views/public/article';
|
||||||
import { ResouceCollect, ResouceBottom } from '@/views/resource-center/components';
|
import { ResouceCollect, ResouceBottom } from '@/views/user/resource-center/components';
|
||||||
import { resourceApi } from '@/apis/resource';
|
import { resourceApi } from '@/apis/resource';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import type { Resource } from '@/types/resource';
|
import type { Resource } from '@/types/resource';
|
||||||
@@ -101,7 +101,7 @@ import { ElMessage } from 'element-plus';
|
|||||||
import { Search, VideoPlay } from '@element-plus/icons-vue';
|
import { Search, VideoPlay } from '@element-plus/icons-vue';
|
||||||
import { courseApi } from '@/apis/study';
|
import { courseApi } from '@/apis/study';
|
||||||
import type { Course, PageParam } from '@/types';
|
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 defaultCover from '@/assets/imgs/default-course-bg.png'
|
||||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ async function loadCourseList() {
|
|||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
pageParam.value.page = 1;
|
pageParam.value.pageNumber = 1;
|
||||||
loadCourseList();
|
loadCourseList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { CourseDetail } from '@/views/course/components';
|
import { CourseDetail } from '@/views/public/course/components';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'CourseDetailView'
|
name: 'CourseDetailView'
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { CourseLearning } from '@/views/course/components';
|
import { CourseLearning } from '@/views/public/course/components';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'CourseStudyView'
|
name: 'CourseStudyView'
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { LearingTaskDetail } from '@/views/task';
|
import { LearingTaskDetail } from '@/views/public/task';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'LearningTaskDetailView'
|
name: 'LearningTaskDetailView'
|
||||||
@@ -114,7 +114,7 @@ import { DocumentCopy, DocumentChecked } from '@element-plus/icons-vue';
|
|||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import { learningTaskApi } from '@/apis/study';
|
import { learningTaskApi } from '@/apis/study';
|
||||||
import type { LearningTask, TaskItemVO } from '@/types';
|
import type { LearningTask, TaskItemVO } from '@/types';
|
||||||
import { StudyPlanLayout } from '@/views/study-plan';
|
import { StudyPlanLayout } from '@/views/user/study-plan';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'StudyTasksView'
|
name: 'StudyTasksView'
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { FloatingSidebar } from '@/components/base';
|
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 { getParentChildrenRoutes } from '@/utils/routeUtils';
|
||||||
import type { SysMenu } from '@/types/menu';
|
import type { SysMenu } from '@/types/menu';
|
||||||
|
|
||||||
Reference in New Issue
Block a user