添加相关功能
This commit is contained in:
parent
5a533e15ce
commit
6547123a83
@ -9,8 +9,11 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"element-plus": "^2.10.4",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.17",
|
||||||
"@tailwindcss/vite": "^4.1.11"
|
"vue-router": "4",
|
||||||
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.0",
|
||||||
|
|||||||
@ -2,9 +2,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<router-view />
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import router from './router/index.js'
|
||||||
createApp(App).mount('#app')
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
createApp(App).use(router).use(ElementPlus).mount('#app')
|
||||||
|
|||||||
24
web/src/router/index.js
Normal file
24
web/src/router/index.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { createMemoryHistory, createRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import index from '../views/index.vue'
|
||||||
|
import home from '../views/home.vue'
|
||||||
|
import appcenter from '../views/appcenter.vue'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/', component: index,
|
||||||
|
children:[
|
||||||
|
{path: '', redirect: '/home'},
|
||||||
|
{ path: '/home', component: home },
|
||||||
|
{ path: '/app-center', component: appcenter }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createMemoryHistory(),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router;
|
||||||
@ -1 +1,12 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
/*
|
||||||
|
* 配置教程 https://juejin.cn/post/7480450288421109787
|
||||||
|
*/
|
||||||
|
@theme inline {
|
||||||
|
--color-primary: #085ce6;
|
||||||
|
--color-secondary: #FFB81C;
|
||||||
|
--color-dark: #12192C;
|
||||||
|
--color-light: #F5F7FA;
|
||||||
|
--color-muted: #6B7280;
|
||||||
|
--font-display: 'Inter', 'system-ui', 'sans-serif';
|
||||||
|
}
|
||||||
|
|||||||
646
web/src/views/appcenter.vue
Normal file
646
web/src/views/appcenter.vue
Normal file
@ -0,0 +1,646 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen bg-gray-50 p-6">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-800 mb-2">应用中心</h1>
|
||||||
|
<p class="text-gray-600">发现和使用企业内部的各类应用系统</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索和筛选 -->
|
||||||
|
<div class="bg-white rounded-lg shadow-sm p-4 mb-8">
|
||||||
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||||
|
<div class="relative w-full md:w-1/3">
|
||||||
|
<el-input
|
||||||
|
v-model="searchQuery"
|
||||||
|
placeholder="搜索应用..."
|
||||||
|
:prefix-icon="Search"
|
||||||
|
clearable
|
||||||
|
></el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<el-button
|
||||||
|
v-for="category in categories"
|
||||||
|
:key="category.id"
|
||||||
|
:type="activeCategory === category.id ? 'primary' : 'text'"
|
||||||
|
@click="activeCategory = category.id"
|
||||||
|
>
|
||||||
|
{{ category.name }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="text" @click="activeCategory = 'all'">全部</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 我的应用 -->
|
||||||
|
<div class="mb-10">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-800">我的应用</h2>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<el-tooltip content="拖拽可调整顺序" placement="top">
|
||||||
|
<el-button type="text" icon="el-icon-rank" size="small"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="管理我的应用" placement="top">
|
||||||
|
<el-button type="text" icon="el-icon-setting" @click="showManageMyApps = true"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 使用draggable实现拖拽排序 -->
|
||||||
|
|
||||||
|
|
||||||
|
<draggable
|
||||||
|
v-model="myApps"
|
||||||
|
group="people"
|
||||||
|
@start="drag=true"
|
||||||
|
@end="onDragEnd"
|
||||||
|
class="grid grid-cols-1 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4"
|
||||||
|
|
||||||
|
>
|
||||||
|
<template #item="{element}">
|
||||||
|
<el-card
|
||||||
|
class="app-card cursor-pointer hover:shadow-lg transition-shadow duration-300 relative"
|
||||||
|
@click="openApp(element)"
|
||||||
|
>
|
||||||
|
<div class="absolute top-2 left-2 drag-handle cursor-move opacity-50 hover:opacity-100 transition-opacity mt-1">
|
||||||
|
<el-icon size="16"><Rank /></el-icon>
|
||||||
|
</div>
|
||||||
|
<!-- 卡片内容保持不变 -->
|
||||||
|
<div class="absolute top-2 right-2">
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
:icon="Delete"
|
||||||
|
@click.stop="confirmRemoveFromMyApps(element)"
|
||||||
|
></el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col items-center p-4">
|
||||||
|
<div class="w-14 h-14 rounded-lg bg-primary/10 flex items-center justify-center mb-3">
|
||||||
|
<el-icon :size="24" :class="element.icon"></el-icon>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-medium text-gray-800 mb-1 text-center truncate w-full">{{ element.name }}</h3>
|
||||||
|
<p class="text-xs text-gray-500 text-center truncate w-full">{{ element.description }}</p>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 没有我的应用时的提示 -->
|
||||||
|
<div
|
||||||
|
v-if="myApps.length === 0 && !searchQuery.trim()"
|
||||||
|
class="col-span-full flex flex-col items-center justify-center py-10 text-gray-500"
|
||||||
|
>
|
||||||
|
<el-icon class="text-4xl mb-3"><Collection /></el-icon>
|
||||||
|
<p>您还没有添加任何应用到"我的应用"</p>
|
||||||
|
<el-button type="text" size="small" @click="showManageMyApps = true">添加应用</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索无结果提示 -->
|
||||||
|
<div
|
||||||
|
v-if="filteredMyApps.length === 0 && searchQuery.trim()"
|
||||||
|
class="col-span-full flex flex-col items-center justify-center py-10 text-gray-500"
|
||||||
|
>
|
||||||
|
<el-icon class="text-4xl mb-3"><Search /></el-icon>
|
||||||
|
<p>没有找到匹配的应用</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 全部应用 -->
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-800">全部应用</h2>
|
||||||
|
<span class="text-sm text-gray-500">{{ filteredAllApps.length }} 个应用</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||||
|
<el-card
|
||||||
|
v-for="app in filteredAllApps"
|
||||||
|
:key="app.id"
|
||||||
|
class="app-card cursor-pointer hover:shadow-lg transition-shadow duration-300"
|
||||||
|
@click="openApp(app)"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col items-center p-4">
|
||||||
|
<div class="w-14 h-14 rounded-lg bg-primary/10 flex items-center justify-center mb-3">
|
||||||
|
<el-icon :size="24" :class="app.icon"></el-icon>
|
||||||
|
</div>
|
||||||
|
<h3 class="font-medium text-gray-800 mb-1 text-center truncate w-full">{{ app.name }}</h3>
|
||||||
|
<p class="text-xs text-gray-500 text-center truncate w-full">{{ app.description }}</p>
|
||||||
|
|
||||||
|
<!-- 添加到我的应用按钮 -->
|
||||||
|
<el-button
|
||||||
|
v-if="!isAppInMyApps(app.id)"
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
icon="el-icon-plus"
|
||||||
|
class="mt-2"
|
||||||
|
@click.stop="addToMyApps(app)"
|
||||||
|
>
|
||||||
|
添加
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 没有应用时的提示 -->
|
||||||
|
<div
|
||||||
|
v-if="filteredAllApps.length === 0"
|
||||||
|
class="col-span-full flex flex-col items-center justify-center py-10 text-gray-500"
|
||||||
|
>
|
||||||
|
<el-icon class="text-4xl mb-3"><Search /></el-icon>
|
||||||
|
<p>没有找到匹配的应用</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 管理我的应用对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
title="管理我的应用"
|
||||||
|
:visible.sync="showManageMyApps"
|
||||||
|
width="40%"
|
||||||
|
:before-close="handleCloseManageMyApps"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div class="p-4">
|
||||||
|
<p class="text-sm text-gray-600 mb-4">从下方选择您常用的应用添加到"我的应用"</p>
|
||||||
|
|
||||||
|
<el-input
|
||||||
|
v-model="manageSearchQuery"
|
||||||
|
placeholder="搜索应用..."
|
||||||
|
:prefix-icon="Search"
|
||||||
|
clearable
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||||
|
<el-checkbox-group v-model="selectedApps">
|
||||||
|
<el-checkbox
|
||||||
|
v-for="app in filteredManageApps"
|
||||||
|
:key="app.id"
|
||||||
|
:label="app.id"
|
||||||
|
class="flex items-center"
|
||||||
|
>
|
||||||
|
<el-icon :class="app.icon" class="mr-2"></el-icon>
|
||||||
|
<span class="text-sm">{{ app.name }}</span>
|
||||||
|
</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showManageMyApps = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveMyApps">保存</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 删除确认对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
title="确认删除"
|
||||||
|
:model-value="showDeleteConfirm"
|
||||||
|
width="30%"
|
||||||
|
>
|
||||||
|
<p>确定要从"我的应用"中移除 <span class="font-medium">{{ deletingAppName }}</span> 吗?</p>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showDeleteConfirm = false">取消</el-button>
|
||||||
|
<el-button type="danger" @click="confirmDelete">确认</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
import {
|
||||||
|
Collection,
|
||||||
|
Search,
|
||||||
|
Delete,
|
||||||
|
Remove,
|
||||||
|
Rank,
|
||||||
|
// 应用图标
|
||||||
|
Monitor,
|
||||||
|
Document,
|
||||||
|
Setting,
|
||||||
|
User,
|
||||||
|
Message,
|
||||||
|
Calendar,
|
||||||
|
PieChart,
|
||||||
|
Download,
|
||||||
|
Upload,
|
||||||
|
Folder,
|
||||||
|
Clock,
|
||||||
|
Bell,
|
||||||
|
Lock,
|
||||||
|
Share,
|
||||||
|
Star
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage, ElDialog } from 'element-plus'
|
||||||
|
|
||||||
|
// 应用分类数据
|
||||||
|
const categories = ref([
|
||||||
|
{ id: 'workflow', name: '工作流程' },
|
||||||
|
{ id: 'data', name: '数据分析' },
|
||||||
|
{ id: 'communication', name: '沟通协作' },
|
||||||
|
{ id: 'document', name: '文档管理' },
|
||||||
|
{ id: 'system', name: '系统工具' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 应用类型定义
|
||||||
|
interface App {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
icon: any
|
||||||
|
category: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const drag=ref(false)
|
||||||
|
|
||||||
|
// 所有应用数据
|
||||||
|
const allApps = ref<App[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '项目管理系统',
|
||||||
|
description: '管理项目进度、任务分配和资源',
|
||||||
|
icon: Monitor,
|
||||||
|
category: 'workflow',
|
||||||
|
url: '/project-management'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '文档中心',
|
||||||
|
description: '企业级文档管理和协作平台',
|
||||||
|
icon: Document,
|
||||||
|
category: 'document',
|
||||||
|
url: '/document-center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '企业邮箱',
|
||||||
|
description: '安全可靠的企业邮件系统',
|
||||||
|
icon: Message,
|
||||||
|
category: 'communication',
|
||||||
|
url: '/mail'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '人力资源系统',
|
||||||
|
description: '员工信息管理和人事流程',
|
||||||
|
icon: User,
|
||||||
|
category: 'workflow',
|
||||||
|
url: '/hr-system'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: '数据分析平台',
|
||||||
|
description: '企业数据可视化和分析工具',
|
||||||
|
icon: PieChart,
|
||||||
|
category: 'data',
|
||||||
|
url: '/data-analysis'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: '日程安排',
|
||||||
|
description: '团队和个人日程管理',
|
||||||
|
icon: Calendar,
|
||||||
|
category: 'workflow',
|
||||||
|
url: '/calendar'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: '知识库',
|
||||||
|
description: '企业知识沉淀和共享平台',
|
||||||
|
icon: Folder,
|
||||||
|
category: 'document',
|
||||||
|
url: '/knowledge-base'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: '视频会议',
|
||||||
|
description: '高清视频会议和远程协作',
|
||||||
|
icon: PieChart,
|
||||||
|
category: 'communication',
|
||||||
|
url: '/video-conference'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: '系统设置',
|
||||||
|
description: '企业系统配置和管理',
|
||||||
|
icon: Setting,
|
||||||
|
category: 'system',
|
||||||
|
url: '/system-settings'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: '数据备份',
|
||||||
|
description: '企业数据定期备份和恢复',
|
||||||
|
icon: Download,
|
||||||
|
category: 'system',
|
||||||
|
url: '/data-backup'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: '报表生成器',
|
||||||
|
description: '自定义报表和数据导出',
|
||||||
|
icon: PieChart,
|
||||||
|
category: 'data',
|
||||||
|
url: '/report-generator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: '移动办公',
|
||||||
|
description: '随时随地处理工作事务',
|
||||||
|
icon: PieChart,
|
||||||
|
category: 'workflow',
|
||||||
|
url: '/mobile-office'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: '文件传输',
|
||||||
|
description: '安全高效的大文件传输工具',
|
||||||
|
icon: Upload,
|
||||||
|
category: 'system',
|
||||||
|
url: '/file-transfer'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
name: '工单系统',
|
||||||
|
description: 'IT服务请求和问题跟踪',
|
||||||
|
icon: PieChart,
|
||||||
|
category: 'workflow',
|
||||||
|
url: '/work-order'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
name: '实时监控',
|
||||||
|
description: '系统性能和状态实时监控',
|
||||||
|
icon: Clock,
|
||||||
|
category: 'system',
|
||||||
|
url: '/monitoring'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 16,
|
||||||
|
name: '企业公告',
|
||||||
|
description: '公司通知和重要消息发布',
|
||||||
|
icon: Bell,
|
||||||
|
category: 'communication',
|
||||||
|
url: '/announcements'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 17,
|
||||||
|
name: '权限管理',
|
||||||
|
description: '用户权限和访问控制',
|
||||||
|
icon: Lock,
|
||||||
|
category: 'system',
|
||||||
|
url: '/permission-management'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 18,
|
||||||
|
name: '数据同步',
|
||||||
|
description: '多系统间数据实时同步',
|
||||||
|
icon: PieChart,
|
||||||
|
category: 'data',
|
||||||
|
url: '/data-sync'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 19,
|
||||||
|
name: '协作白板',
|
||||||
|
description: '团队在线协作和头脑风暴',
|
||||||
|
icon: Share,
|
||||||
|
category: 'communication',
|
||||||
|
url: '/collaboration-board'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20,
|
||||||
|
name: '应用商店',
|
||||||
|
description: '发现和安装企业应用',
|
||||||
|
icon: Star,
|
||||||
|
category: 'system',
|
||||||
|
url: '/app-store'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 我的应用ID列表(从本地存储加载或设置默认值)
|
||||||
|
const myAppIds = ref<number[]>([])
|
||||||
|
|
||||||
|
// 搜索和筛选相关状态
|
||||||
|
const searchQuery = ref('')
|
||||||
|
const activeCategory = ref('all')
|
||||||
|
const showManageMyApps = ref(false)
|
||||||
|
const manageSearchQuery = ref('')
|
||||||
|
const selectedApps = ref<number[]>([])
|
||||||
|
|
||||||
|
// 删除确认相关状态
|
||||||
|
const showDeleteConfirm = ref(false)
|
||||||
|
const deletingAppId = ref<number | null>(null)
|
||||||
|
const deletingAppName = ref('')
|
||||||
|
|
||||||
|
// 计算属性:我的应用列表
|
||||||
|
const myApps = computed<App[]>({
|
||||||
|
get() {
|
||||||
|
// 保持与myAppIds相同的顺序
|
||||||
|
return myAppIds.value.map(id => allApps.value.find(app => app.id === id)).filter(Boolean) as App[]
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
// 当拖拽排序后更新myAppIds
|
||||||
|
myAppIds.value = newValue.map(app => app.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性:筛选后的我的应用
|
||||||
|
const filteredMyApps = computed(() => {
|
||||||
|
if (!searchQuery.value.trim()) return myApps.value
|
||||||
|
|
||||||
|
const query = searchQuery.value.toLowerCase().trim()
|
||||||
|
return myApps.value.filter(app =>
|
||||||
|
app.name.toLowerCase().includes(query) ||
|
||||||
|
app.description.toLowerCase().includes(query)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性:筛选后的所有应用
|
||||||
|
const filteredAllApps = computed(() => {
|
||||||
|
let apps = allApps.value
|
||||||
|
|
||||||
|
// 按分类筛选
|
||||||
|
if (activeCategory.value !== 'all') {
|
||||||
|
apps = apps.filter(app => app.category === activeCategory.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按搜索词筛选
|
||||||
|
if (searchQuery.value.trim()) {
|
||||||
|
const query = searchQuery.value.toLowerCase().trim()
|
||||||
|
apps = apps.filter(app =>
|
||||||
|
app.name.toLowerCase().includes(query) ||
|
||||||
|
app.description.toLowerCase().includes(query)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性:管理对话框中的应用列表
|
||||||
|
const filteredManageApps = computed(() => {
|
||||||
|
if (!manageSearchQuery.value.trim()) return allApps.value
|
||||||
|
|
||||||
|
const query = manageSearchQuery.value.toLowerCase().trim()
|
||||||
|
return allApps.value.filter(app =>
|
||||||
|
app.name.toLowerCase().includes(query) ||
|
||||||
|
app.description.toLowerCase().includes(query)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查应用是否在我的应用中
|
||||||
|
const isAppInMyApps = (appId: number) => {
|
||||||
|
return myAppIds.value.includes(appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加应用到我的应用
|
||||||
|
const addToMyApps = (app: App) => {
|
||||||
|
if (!isAppInMyApps(app.id)) {
|
||||||
|
myAppIds.value.push(app.id)
|
||||||
|
saveMyAppsToLocalStorage()
|
||||||
|
|
||||||
|
// 显示成功提示
|
||||||
|
ElMessage({
|
||||||
|
message: `已将"${app.name}"添加到我的应用`,
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示删除确认对话框
|
||||||
|
const confirmRemoveFromMyApps = (app: App) => {
|
||||||
|
console.log(app)
|
||||||
|
deletingAppId.value = app.id
|
||||||
|
deletingAppName.value = app.name
|
||||||
|
showDeleteConfirm.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认删除应用
|
||||||
|
const confirmDelete = () => {
|
||||||
|
if (deletingAppId.value) {
|
||||||
|
myAppIds.value = myAppIds.value.filter(id => id !== deletingAppId.value)
|
||||||
|
saveMyAppsToLocalStorage()
|
||||||
|
|
||||||
|
// 显示成功提示
|
||||||
|
ElMessage({
|
||||||
|
message: `已从我的应用中移除"${deletingAppName.value}"`,
|
||||||
|
type: 'info'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 关闭对话框
|
||||||
|
showDeleteConfirm.value = false
|
||||||
|
deletingAppId.value = null
|
||||||
|
deletingAppName.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开应用
|
||||||
|
const openApp = (app: App) => {
|
||||||
|
// 在实际应用中,这里可以实现跳转逻辑
|
||||||
|
console.log(`Opening app: ${app.name} (${app.url})`)
|
||||||
|
|
||||||
|
// 显示打开提示
|
||||||
|
ElMessage({
|
||||||
|
message: `正在打开"${app.name}"`,
|
||||||
|
type: 'info'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存我的应用设置
|
||||||
|
const saveMyApps = () => {
|
||||||
|
myAppIds.value = [...selectedApps.value]
|
||||||
|
saveMyAppsToLocalStorage()
|
||||||
|
showManageMyApps.value = false
|
||||||
|
|
||||||
|
// 显示保存成功提示
|
||||||
|
ElMessage({
|
||||||
|
message: '我的应用设置已保存',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭管理对话框前的处理
|
||||||
|
const handleCloseManageMyApps = (done: () => void) => {
|
||||||
|
// 恢复之前的选择
|
||||||
|
selectedApps.value = [...myAppIds.value]
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽结束事件处理
|
||||||
|
const onDragEnd = () => {
|
||||||
|
drag.value=false
|
||||||
|
// 拖拽排序后保存
|
||||||
|
saveMyAppsToLocalStorage()
|
||||||
|
ElMessage({
|
||||||
|
message: '应用顺序已更新',
|
||||||
|
type: 'success',
|
||||||
|
duration: 1000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将我的应用保存到本地存储
|
||||||
|
const saveMyAppsToLocalStorage = () => {
|
||||||
|
localStorage.setItem('myApps', JSON.stringify(myAppIds.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从本地存储加载我的应用
|
||||||
|
const loadMyAppsFromLocalStorage = () => {
|
||||||
|
const savedMyApps = localStorage.getItem('myApps')
|
||||||
|
if (savedMyApps) {
|
||||||
|
try {
|
||||||
|
myAppIds.value = JSON.parse(savedMyApps)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse saved my apps', e)
|
||||||
|
// 解析失败时使用默认值
|
||||||
|
myAppIds.value = [1, 2, 3, 4]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有保存的我的应用,设置一些默认值
|
||||||
|
myAppIds.value = [1, 2, 3, 4]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化管理对话框中的选择
|
||||||
|
selectedApps.value = [...myAppIds.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生命周期钩子:组件挂载后加载我的应用
|
||||||
|
onMounted(() => {
|
||||||
|
loadMyAppsFromLocalStorage()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-card {
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 拖拽相关样式 */
|
||||||
|
.drag-handle {
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏拖拽过程中的默认高亮样式 */
|
||||||
|
:deep(.ghost) {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.dragging) {
|
||||||
|
opacity: 0.8;
|
||||||
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
120
web/src/views/components/layout.vue
Normal file
120
web/src/views/components/layout.vue
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col h-screen overflow-hidden bg-gray-50">
|
||||||
|
<!-- 顶部导航栏 -->
|
||||||
|
<header class="bg-primary text-white shadow-md h-16 flex items-center justify-between px-6">
|
||||||
|
<!-- 左侧Logo和菜单按钮 -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
<button @click="isCollapse = !isCollapse" class="mr-4 text-white focus:outline-none">
|
||||||
|
<el-icon><Fold /></el-icon>
|
||||||
|
</button>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<el-icon><Platform /></el-icon>
|
||||||
|
<span class="ml-2 text-lg font-semibold hidden md:block">企业内网门户</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧用户信息 -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="relative mr-6">
|
||||||
|
<button class="relative text-white focus:outline-none">
|
||||||
|
<el-icon><Bell /></el-icon>
|
||||||
|
<span class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">3</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<img src="https://picsum.photos/200/200?random=1" alt="User Avatar" class="w-8 h-8 rounded-full object-cover">
|
||||||
|
<span class="ml-2 hidden sm:block">管理员</span>
|
||||||
|
<el-icon><CaretBottom /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- 主内容区 -->
|
||||||
|
<div class="flex flex-1 overflow-hidden">
|
||||||
|
<!-- 侧边栏菜单 -->
|
||||||
|
<aside class="bg-white shadow-md z-10 transition-all duration-300" :style="{ width: isCollapse ? '64px' : '200px' }">
|
||||||
|
<el-menu
|
||||||
|
default-active="2"
|
||||||
|
class="el-menu-vertical-demo h-full"
|
||||||
|
:collapse="isCollapse"
|
||||||
|
@open="handleOpen"
|
||||||
|
@close="handleClose"
|
||||||
|
router
|
||||||
|
>
|
||||||
|
<el-menu-item index="/home">
|
||||||
|
<el-icon><IconMenu /></el-icon>
|
||||||
|
<template #title>首页</template>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-menu-item index="/app-center">
|
||||||
|
<el-icon><IconMenu /></el-icon>
|
||||||
|
<template #title>应用中心</template>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-sub-menu index="2">
|
||||||
|
<template #title>
|
||||||
|
<el-icon><Location /></el-icon>
|
||||||
|
<span>导航一</span>
|
||||||
|
</template>
|
||||||
|
<el-menu-item-group>
|
||||||
|
<template #title><span>分组一</span></template>
|
||||||
|
<el-menu-item index="1-1">菜单项一</el-menu-item>
|
||||||
|
<el-menu-item index="1-2">菜单项二</el-menu-item>
|
||||||
|
</el-menu-item-group>
|
||||||
|
<el-menu-item-group title="分组二">
|
||||||
|
<el-menu-item index="1-3">菜单项三</el-menu-item>
|
||||||
|
</el-menu-item-group>
|
||||||
|
<el-sub-menu index="1-4">
|
||||||
|
<template #title><span>菜单项四</span></template>
|
||||||
|
<el-menu-item index="1-4-1">子菜单项一</el-menu-item>
|
||||||
|
</el-sub-menu>
|
||||||
|
</el-sub-menu>
|
||||||
|
<el-menu-item index="3" disabled>
|
||||||
|
<el-icon><Document /></el-icon>
|
||||||
|
<template #title>导航三</template>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-menu-item index="4">
|
||||||
|
<el-icon><Setting /></el-icon>
|
||||||
|
<template #title>导航四</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-menu>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<main class="flex-1 overflow-y-auto p-6 bg-gray-50">
|
||||||
|
<router-view></router-view>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import {
|
||||||
|
Document,
|
||||||
|
Menu as IconMenu,
|
||||||
|
Location,
|
||||||
|
Setting,
|
||||||
|
// 新增:导入需要使用的图标
|
||||||
|
Fold,
|
||||||
|
Platform,
|
||||||
|
Bell,
|
||||||
|
CaretBottom
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const isCollapse = ref(false)
|
||||||
|
|
||||||
|
const handleOpen = (key: string, keyPath: string[]) => {
|
||||||
|
console.log(key, keyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = (key: string, keyPath: string[]) => {
|
||||||
|
console.log(key, keyPath)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||||
|
width: 200px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
70
web/src/views/home.vue
Normal file
70
web/src/views/home.vue
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-800 mb-6">欢迎使用企业内网门户</h1>
|
||||||
|
|
||||||
|
<!-- 页面内容示例 -->
|
||||||
|
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-700 mb-4">系统公告</h2>
|
||||||
|
<el-alert
|
||||||
|
title="系统升级通知"
|
||||||
|
type="info"
|
||||||
|
description="本系统将于本周六凌晨2点进行例行维护,预计维护时间为2小时。维护期间系统将暂停服务,请提前做好工作安排。"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
></el-alert>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计卡片示例 -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
||||||
|
<el-card class="bg-white rounded-lg shadow-md p-4">
|
||||||
|
<div slot="header" class="flex justify-between items-center">
|
||||||
|
<span>项目统计</span>
|
||||||
|
<el-button icon="el-icon-refresh" circle size="small" type="text"></el-button>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<el-progress :percentage="75" type="circle" :width="120"></el-progress>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-2">
|
||||||
|
<p class="text-gray-500 text-sm">进行中项目</p>
|
||||||
|
<p class="text-blue-600 text-2xl font-bold">12/16</p>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="bg-white rounded-lg shadow-md p-4">
|
||||||
|
<div slot="header" class="flex justify-between items-center">
|
||||||
|
<span>待办事项</span>
|
||||||
|
<el-button icon="el-icon-refresh" circle size="small" type="text"></el-button>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<el-progress :percentage="40" type="circle" :width="120" color="#f56c6c"></el-progress>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-2">
|
||||||
|
<p class="text-gray-500 text-sm">今日待办</p>
|
||||||
|
<p class="text-red-500 text-2xl font-bold">6/15</p>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="bg-white rounded-lg shadow-md p-4">
|
||||||
|
<div slot="header" class="flex justify-between items-center">
|
||||||
|
<span>通知消息</span>
|
||||||
|
<el-button icon="el-icon-refresh" circle size="small" type="text"></el-button>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<el-progress :percentage="20" type="circle" :width="120" color="#e6a23c"></el-progress>
|
||||||
|
</div>
|
||||||
|
<div class="text-center mt-2">
|
||||||
|
<p class="text-gray-500 text-sm">未读消息</p>
|
||||||
|
<p class="text-orange-500 text-2xl font-bold">3/15</p>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
13
web/src/views/index.vue
Normal file
13
web/src/views/index.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import Layout from "./components/layout.vue";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<layout></layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@ -6,6 +6,7 @@ import tailwindcss from '@tailwindcss/vite'
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
tailwindcss(),
|
tailwindcss(
|
||||||
|
),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user