first init
This commit is contained in:
commit
4edf954980
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
42
README.md
Normal file
42
README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# portal-test
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Recommended Browser Setup
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
3264
package-lock.json
generated
Normal file
3264
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "portal-test",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/node": "^22.18.6",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"typescript": "~5.9.0",
|
||||
"vite": "^7.1.7",
|
||||
"vite-plugin-vue-devtools": "^8.0.2",
|
||||
"vue-tsc": "^3.1.0"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
572
src.html
Normal file
572
src.html
Normal file
@ -0,0 +1,572 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Portal 主页</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@600&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
min-height: 100vh;
|
||||
color: #2C3E50;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
header {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: 24px;
|
||||
color: #2C3E50;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: #3498DB;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-weight: 600;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #95A5A6;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
color: #E74C3C;
|
||||
}
|
||||
|
||||
/* Tab导航 */
|
||||
.tabs {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ECF0F1;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 16px 24px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: #95A5A6;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
color: #3498DB;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #3498DB;
|
||||
}
|
||||
|
||||
.tab-item.active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: #3498DB;
|
||||
}
|
||||
|
||||
/* 卡片容器 */
|
||||
.cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.card-badge {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.badge-public {
|
||||
background-color: rgba(46, 204, 113, 0.15);
|
||||
color: #2ECC71;
|
||||
}
|
||||
|
||||
.badge-internal {
|
||||
background-color: rgba(231, 76, 60, 0.15);
|
||||
color: #E74C3C;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
background-color: #f8f9fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
font-size: 24px;
|
||||
color: #3498DB;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #2C3E50;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: #95A5A6;
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.card-action {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-link {
|
||||
color: #3498DB;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card-link:hover {
|
||||
color: #2980B9;
|
||||
}
|
||||
|
||||
.card-link i {
|
||||
margin-left: 6px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.card-link:hover i {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.card-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #95A5A6;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.card-stats i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* 工具提示 */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tooltip::before {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: 125%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #2C3E50;
|
||||
color: white;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tooltip::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 115%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #2C3E50 transparent transparent transparent;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.tooltip:hover::before,
|
||||
.tooltip:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #95A5A6;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: #ECF0F1;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 8px;
|
||||
color: #7F8C8D;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.tab-list {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.cards-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
header {
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header>
|
||||
<div class="logo">
|
||||
<i class="fas fa-cube"></i>
|
||||
<h1>PORTAL</h1>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div class="user-avatar">U</div>
|
||||
<div class="user-name">用户名</div>
|
||||
<button class="logout-btn"><i class="fas fa-sign-out-alt"></i></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Tab导航 -->
|
||||
<div class="tabs">
|
||||
<div class="tab-list">
|
||||
<div class="tab-item active" data-tab="common">常用应用</div>
|
||||
<div class="tab-item" data-tab="dev">开发工具</div>
|
||||
<div class="tab-item" data-tab="office">办公系统</div>
|
||||
<div class="tab-item" data-tab="resources">资源中心</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 卡片容器 -->
|
||||
<div class="cards-container" id="cards-container">
|
||||
<!-- 卡片将通过JavaScript动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 模拟数据
|
||||
const portalData = {
|
||||
common: [
|
||||
{
|
||||
id: 1,
|
||||
title: "企业邮箱",
|
||||
description: "公司官方邮件系统,支持多设备同步",
|
||||
icon: "fa-envelope",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1280
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "内部通讯",
|
||||
description: "企业内部即时通讯工具,支持群组聊天",
|
||||
icon: "fa-comments",
|
||||
network: "internal",
|
||||
networkName: "IntraNet",
|
||||
visits: 2450
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "云文档",
|
||||
description: "企业文档协作平台,支持多人实时编辑",
|
||||
icon: "fa-file-alt",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1876
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "项目管理系统",
|
||||
description: "全流程项目管理工具,支持敏捷开发",
|
||||
icon: "fa-tasks",
|
||||
network: "internal",
|
||||
networkName: "DevNet",
|
||||
visits: 932
|
||||
}
|
||||
],
|
||||
dev: [
|
||||
{
|
||||
id: 5,
|
||||
title: "代码仓库",
|
||||
description: "企业级代码版本控制系统",
|
||||
icon: "fa-code-branch",
|
||||
network: "internal",
|
||||
networkName: "DevNet",
|
||||
visits: 1542
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "CI/CD平台",
|
||||
description: "持续集成与持续部署自动化平台",
|
||||
icon: "fa-rocket",
|
||||
network: "internal",
|
||||
networkName: "DevNet",
|
||||
visits: 876
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: "API网关",
|
||||
description: "统一API管理与监控平台",
|
||||
icon: "fa-plug",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1203
|
||||
}
|
||||
],
|
||||
office: [
|
||||
{
|
||||
id: 8,
|
||||
title: "人力资源系统",
|
||||
description: "员工信息、考勤、绩效管理平台",
|
||||
icon: "fa-users",
|
||||
network: "internal",
|
||||
networkName: "IntraNet",
|
||||
visits: 2105
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: "财务系统",
|
||||
description: "企业财务核算与报销管理系统",
|
||||
icon: "fa-calculator",
|
||||
network: "internal",
|
||||
networkName: "FinanceNet",
|
||||
visits: 1876
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: "合同管理",
|
||||
description: "企业合同全生命周期管理系统",
|
||||
icon: "fa-file-contract",
|
||||
network: "internal",
|
||||
networkName: "IntraNet",
|
||||
visits: 945
|
||||
}
|
||||
],
|
||||
resources: [
|
||||
{
|
||||
id: 11,
|
||||
title: "知识库",
|
||||
description: "企业知识管理与文档共享平台",
|
||||
icon: "fa-book",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 3241
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: "培训中心",
|
||||
description: "员工在线学习与技能提升平台",
|
||||
icon: "fa-graduation-cap",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1876
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
title: "资源下载",
|
||||
description: "软件、模板、文档资源下载中心",
|
||||
icon: "fa-download",
|
||||
network: "internal",
|
||||
networkName: "IntraNet",
|
||||
visits: 2654
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// 获取DOM元素
|
||||
const tabItems = document.querySelectorAll('.tab-item');
|
||||
const cardsContainer = document.getElementById('cards-container');
|
||||
|
||||
// 初始化页面
|
||||
function init() {
|
||||
// 渲染默认Tab内容
|
||||
renderCards('common');
|
||||
|
||||
// 添加Tab点击事件
|
||||
tabItems.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
// 更新Tab状态
|
||||
tabItems.forEach(item => item.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
// 渲染对应卡片
|
||||
const tabId = tab.getAttribute('data-tab');
|
||||
renderCards(tabId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染卡片
|
||||
function renderCards(tabId) {
|
||||
// 清空容器
|
||||
cardsContainer.innerHTML = '';
|
||||
|
||||
// 获取当前Tab数据
|
||||
const cards = portalData[tabId] || [];
|
||||
|
||||
// 如果没有数据,显示空状态
|
||||
if (cards.length === 0) {
|
||||
cardsContainer.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<h3>暂无应用</h3>
|
||||
<p>此分类下暂无可用的应用入口</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// 渲染卡片
|
||||
cards.forEach(card => {
|
||||
const badgeClass = card.network === 'public' ? 'badge-public' : 'badge-internal';
|
||||
const badgeText = card.network === 'public' ? '公开' : '内网';
|
||||
|
||||
const cardElement = document.createElement('div');
|
||||
cardElement.className = 'card';
|
||||
cardElement.innerHTML = `
|
||||
<div class="card-badge ${badgeClass} tooltip" data-tooltip="${card.networkName}">
|
||||
${badgeText}
|
||||
</div>
|
||||
<div class="card-icon">
|
||||
<i class="fas ${card.icon}"></i>
|
||||
</div>
|
||||
<h3 class="card-title">${card.title}</h3>
|
||||
<p class="card-description">${card.description}</p>
|
||||
<div class="card-action">
|
||||
<a href="#" class="card-link">
|
||||
访问应用 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
<div class="card-stats">
|
||||
<i class="fas fa-eye"></i> ${card.visits}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
cardsContainer.appendChild(cardElement);
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
56
src/App.vue
Normal file
56
src/App.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<script setup>
|
||||
import CardViews from './components/CardViews.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>You did it!</h1>
|
||||
<p>
|
||||
Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the
|
||||
documentation
|
||||
</p>
|
||||
<CardViews />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* 工具提示 */
|
||||
.tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tooltip::before {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: 125%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #2C3E50;
|
||||
color: white;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tooltip::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 115%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #2C3E50 transparent transparent transparent;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.tooltip:hover::before,
|
||||
.tooltip:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
193
src/components/CardViews.vue
Normal file
193
src/components/CardViews.vue
Normal file
@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="cards-container" id="cards-container">
|
||||
<!-- 卡片将通过JavaScript动态生成 -->
|
||||
<LinkCardItem
|
||||
v-for="it in portalData.common"
|
||||
:key="it.id"
|
||||
:title="it.title"
|
||||
:description="it.description"
|
||||
:visits="it.visits"
|
||||
:network-name="it.networkName"
|
||||
:badge-name="it.network"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LinkCardItem from './LinkCardItem.vue';
|
||||
import { reactive } from 'vue';
|
||||
|
||||
// 模拟数据
|
||||
const portalData = reactive({
|
||||
common: [
|
||||
{
|
||||
id: 1,
|
||||
title: "企业邮箱",
|
||||
description: "公司官方邮件系统,支持多设备同步",
|
||||
icon: "fa-envelope",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1280
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "内部通讯",
|
||||
description: "企业内部即时通讯工具,支持群组聊天",
|
||||
icon: "fa-comments",
|
||||
network: "internal",
|
||||
networkName: "IntraNet",
|
||||
visits: 2450
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "云文档",
|
||||
description: "企业文档协作平台,支持多人实时编辑",
|
||||
icon: "fa-file-alt",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1876
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "项目管理系统",
|
||||
description: "全流程项目管理工具,支持敏捷开发",
|
||||
icon: "fa-tasks",
|
||||
network: "internal",
|
||||
networkName: "DevNet",
|
||||
visits: 932
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "企业邮箱",
|
||||
description: "公司官方邮件系统,支持多设备同步",
|
||||
icon: "fa-envelope",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1280
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "内部通讯",
|
||||
description: "企业内部即时通讯工具,支持群组聊天",
|
||||
icon: "fa-comments",
|
||||
network: "internal",
|
||||
networkName: "IntraNet",
|
||||
visits: 2450
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "云文档",
|
||||
description: "企业文档协作平台,支持多人实时编辑",
|
||||
icon: "fa-file-alt",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1876
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "项目管理系统",
|
||||
description: "全流程项目管理工具,支持敏捷开发",
|
||||
icon: "fa-tasks",
|
||||
network: "internal",
|
||||
networkName: "DevNet",
|
||||
visits: 932
|
||||
}
|
||||
],
|
||||
dev: [
|
||||
{
|
||||
id: 5,
|
||||
title: "代码仓库",
|
||||
description: "企业级代码版本控制系统",
|
||||
icon: "fa-code-branch",
|
||||
network: "internal",
|
||||
networkName: "DevNet",
|
||||
visits: 1542
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: "CI/CD平台",
|
||||
description: "持续集成与持续部署自动化平台",
|
||||
icon: "fa-rocket",
|
||||
network: "internal",
|
||||
networkName: "DevNet",
|
||||
visits: 876
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: "API网关",
|
||||
description: "统一API管理与监控平台",
|
||||
icon: "fa-plug",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1203
|
||||
}
|
||||
],
|
||||
office: [
|
||||
{
|
||||
id: 8,
|
||||
title: "人力资源系统",
|
||||
description: "员工信息、考勤、绩效管理平台",
|
||||
icon: "fa-users",
|
||||
network: "internal",
|
||||
networkName: "IntraNet",
|
||||
visits: 2105
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: "财务系统",
|
||||
description: "企业财务核算与报销管理系统",
|
||||
icon: "fa-calculator",
|
||||
network: "internal",
|
||||
networkName: "FinanceNet",
|
||||
visits: 1876
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: "合同管理",
|
||||
description: "企业合同全生命周期管理系统",
|
||||
icon: "fa-file-contract",
|
||||
network: "internal",
|
||||
networkName: "IntraNet",
|
||||
visits: 945
|
||||
}
|
||||
],
|
||||
resources: [
|
||||
{
|
||||
id: 11,
|
||||
title: "知识库",
|
||||
description: "企业知识管理与文档共享平台",
|
||||
icon: "fa-book",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 3241
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: "培训中心",
|
||||
description: "员工在线学习与技能提升平台",
|
||||
icon: "fa-graduation-cap",
|
||||
network: "public",
|
||||
networkName: "StarNet",
|
||||
visits: 1876
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
title: "资源下载",
|
||||
description: "软件、模板、文档资源下载中心",
|
||||
icon: "fa-download",
|
||||
network: "internal",
|
||||
networkName: "IntraNet",
|
||||
visits: 2654
|
||||
}
|
||||
]
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 50px;
|
||||
row-gap: 80px;
|
||||
}
|
||||
</style>
|
||||
149
src/components/LinkCardItem.vue
Normal file
149
src/components/LinkCardItem.vue
Normal file
@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-badge tooltip" :class="props.badgeName == 'public' ? 'badge-public' : 'badge-internal'" :data-tooltip="props.networkName">
|
||||
{{props.badgeName == "public" ? "公网" : "私网"}}
|
||||
</div>
|
||||
<div class="card-icon">
|
||||
<i class="fas ${card.icon}"></i>
|
||||
</div>
|
||||
<h3 class="card-title">{{ props.title }}</h3>
|
||||
<p class="card-description">{{ props.description }}</p>
|
||||
<div class="card-action">
|
||||
<a href="#" class="card-link">
|
||||
访问应用 <i class="fas fa-arrow-right"></i>
|
||||
</a>
|
||||
<div class="card-stats">
|
||||
<i class="fas fa-eye"></i> {{ props.visits }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(defineProps<{
|
||||
title: string,
|
||||
description: string,
|
||||
networkName: string,
|
||||
visits: number,
|
||||
badgeName: string
|
||||
}>(), {
|
||||
title: "null",
|
||||
description: "null",
|
||||
networkName: "null",
|
||||
visits: 0,
|
||||
badgeName: "public"
|
||||
});
|
||||
console.log(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="css">
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
/* overflow: hidden; */
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
/* 卡片图标背景 */
|
||||
.card-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
background-color: #f8f9fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
font-size: 24px;
|
||||
color: #3498DB;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #2C3E50;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: #95A5A6;
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.card-action {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-link {
|
||||
color: #3498DB;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card-link:hover {
|
||||
color: #2980B9;
|
||||
}
|
||||
|
||||
.card-link i {
|
||||
margin-left: 6px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.card-link:hover i {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.card-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #95A5A6;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.card-stats i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* 卡片右上角徽标 */
|
||||
.card-badge {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 徽标样式 */
|
||||
.badge-public {
|
||||
background-color: rgba(46, 204, 113, 0.15);
|
||||
color: #2ECC71;
|
||||
}
|
||||
|
||||
.badge-internal {
|
||||
background-color: rgba(231, 76, 60, 0.15);
|
||||
color: #E74C3C;
|
||||
}
|
||||
</style>
|
||||
12
src/main.ts
Normal file
12
src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
8
src/router/index.ts
Normal file
8
src/router/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [],
|
||||
})
|
||||
|
||||
export default router
|
||||
12
src/stores/counter.ts
Normal file
12
src/stores/counter.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
||||
12
tsconfig.app.json
Normal file
12
tsconfig.app.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
18
vite.config.ts
Normal file
18
vite.config.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user