feat: 首页开发

This commit is contained in:
2026-04-12 09:55:14 +08:00
parent fb3b072975
commit a2a3bd2bee
3 changed files with 249 additions and 386 deletions

30
package-lock.json generated
View File

@@ -947,6 +947,7 @@
"integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"chalk": "^4.1.1",
"fs-extra": "^9.0.1",
@@ -2102,6 +2103,7 @@
"integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@inquirer/checkbox": "^3.0.1",
"@inquirer/confirm": "^4.0.1",
@@ -2903,6 +2905,7 @@
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/lodash": "*"
}
@@ -2923,6 +2926,7 @@
"integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.18.0"
}
@@ -3352,6 +3356,7 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3418,6 +3423,7 @@
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -3781,6 +3787,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -5395,17 +5402,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -7394,13 +7390,15 @@
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash-es": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash-unified": {
"version": "1.0.3",
@@ -8546,6 +8544,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -9931,6 +9930,7 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -10042,6 +10042,7 @@
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -10162,6 +10163,7 @@
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@@ -10221,6 +10223,7 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz",
"integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.32",
"@vue/compiler-sfc": "3.5.32",
@@ -10301,6 +10304,7 @@
"integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",

View File

@@ -214,10 +214,10 @@ async function send() {
const text = inputText.value.trim();
if (!text || isSending.value) return;
// 首次发送时创建会话
// 首次发送时创建会话,使用用户输入的第一条消息作为 title
if (!currentSessionId.value) {
try {
const session = await window.opencode.createSession();
const session = await window.opencode.createSession({ title: text });
currentSessionId.value = session.id;
} catch (err) {
ElMessage.error(`创建会话失败: ${err.message}`);

View File

@@ -1,417 +1,276 @@
<template>
<div class="home-container">
<!-- 顶部欢迎区 -->
<div class="welcome-section">
<div class="welcome-content">
<h1 class="welcome-title">
<span class="greeting">你好开发者</span>
<span class="emoji">👋</span>
</h1>
<p class="welcome-subtitle">准备好开始今天的创作了吗</p>
<LucideIcon name="user" color="red" />
<LucideIcon name="rocket" :size="30" class="hover:text-red-500" />
<!-- 中间内容区标题副标题卡片 -->
<div class="center-content">
<!-- 第一行标题 -->
<div class="title-section">
<span class="title-highlight"></span>
<span class="title-normal mr-4">鉴万物</span>
<span class="title-highlight"></span>
<span class="title-normal">知明理 </span>
</div>
<!-- 第二行副标题 -->
<div class="subtitle-section">本地运行自主规划安全可控的AI工作搭子</div>
<!-- 第三行卡片 -->
<div class="card-section flex gap-8 items-center">
<div class="feature-card">
<div class="card-icon">
<el-icon :size="42"><Document /></el-icon>
</div>
<div class="card-title">智能文档处理</div>
<div class="card-desc">支持多种格式文档的智能解析与处理</div>
</div>
<div class="feature-card">
<div class="card-icon">
<el-icon :size="42"><Document /></el-icon>
</div>
<div class="card-title">智能文档处理</div>
<div class="card-desc">支持多种格式文档的智能解析与处理</div>
</div>
<div class="feature-card">
<div class="card-icon">
<el-icon :size="42"><Document /></el-icon>
</div>
<div class="card-title">智能文档处理</div>
<div class="card-desc">支持多种格式文档的智能解析与处理</div>
</div>
</div>
</div>
<!-- 统计卡片区 -->
<el-row :gutter="16" class="stats-row">
<el-col :span="8" v-for="(stat, index) in stats" :key="index">
<el-card class="stat-card" shadow="hover">
<div class="stat-content">
<div class="stat-icon" :style="{ background: stat.color + '20' }">
<el-icon :size="24" :color="stat.color">
<component :is="stat.icon" />
</el-icon>
<!-- 底部输入框 -->
<div class="input-section">
<div class="input-wrapper">
<textarea class="input-textarea" placeholder="描述任务,/ 调用技能与工具,@调用知识库"></textarea>
<div class="input-toolbar">
<div class="toolbar-left">
<button class="toolbar-btn file-btn">
<el-icon><Plus /></el-icon>
<span>添加文件</span>
</button>
<button class="toolbar-btn symbol-btn">/</button>
<button class="toolbar-btn symbol-btn">@</button>
</div>
<div class="stat-info">
<p class="stat-value">{{ stat.value }}</p>
<p class="stat-label">{{ stat.label }}</p>
<div class="toolbar-right">
<button class="send-btn">
<el-icon><Promotion /></el-icon>
</button>
</div>
</div>
<div class="stat-trend" :class="stat.trend > 0 ? 'up' : 'down'">
<el-icon><component :is="stat.trend > 0 ? Top : Bottom" /></el-icon>
<span>{{ Math.abs(stat.trend) }}% 较上周</span>
</div>
</el-card>
</el-col>
</el-row>
<!-- 主要内容区 -->
<el-row :gutter="16" class="main-content">
<!-- 快捷操作区 -->
<el-col :span="12">
<el-card class="action-card" shadow="hover">
<template #header>
<div class="card-header">
<el-icon><Grid /></el-icon>
<span>快捷操作</span>
</div>
</template>
<div class="action-grid">
<div v-for="action in actions" :key="action.label" class="action-item" @click="action.onClick">
<div class="action-icon" :style="{ background: action.color + '20' }">
<el-icon :size="28" :color="action.color">
<component :is="action.icon" />
</el-icon>
</div>
<span class="action-label">{{ action.label }}</span>
</div>
</div>
</el-card>
</el-col>
<!-- 最近文件区 -->
<el-col :span="12">
<el-card class="recent-card" shadow="hover">
<template #header>
<div class="card-header">
<el-icon><Clock /></el-icon>
<span>最近访问</span>
</div>
</template>
<el-scrollbar height="280px">
<div class="recent-list">
<div v-for="(item, index) in recents" :key="index" class="recent-item" @click="handleFileClick(item)">
<div class="file-icon">
<el-icon :size="20"><Document /></el-icon>
</div>
<div class="file-info">
<p class="file-name">{{ item.name }}</p>
<p class="file-path">{{ item.path }}</p>
</div>
<span class="file-time">{{ item.time }}</span>
</div>
</div>
</el-scrollbar>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
import {
Document,
Plus,
FolderOpened,
Setting,
Upload,
Top,
Bottom,
Grid,
Clock,
Timer,
VideoCamera,
} from '@element-plus/icons-vue';
const router = useRouter();
const stats = [
{
label: '文件总数',
value: '128',
trend: 12,
icon: Document,
color: '#409EFF',
},
{
label: '今日编辑',
value: '24',
trend: -3,
icon: Timer,
color: '#67C23A',
},
{
label: '运行次数',
value: '56',
trend: 8,
icon: VideoCamera,
color: '#E6A23C',
},
];
const actions = [
{
label: '新建文件',
icon: Plus,
color: '#409EFF',
onClick: () => router.push('/editor'),
},
{
label: '打开文件',
icon: FolderOpened,
color: '#67C23A',
onClick: () => {},
},
{
label: '导入项目',
icon: Upload,
color: '#E6A23C',
onClick: () => {},
},
{
label: '系统设置',
icon: Setting,
color: '#F56C6C',
onClick: () => {},
},
];
const recents = [
{ name: 'main.js', path: '/src/main', time: '2 分钟前' },
{ name: 'App.vue', path: '/src/renderer', time: '1 小时前' },
{ name: 'index.css', path: '/src/renderer', time: '昨天' },
{ name: 'forge.config.js', path: '/', time: '3 天前' },
{ name: 'package.json', path: '/', time: '5 天前' },
];
const handleFileClick = (item) => {
console.log('点击文件:', item);
};
import { Document, Plus, Promotion } from '@element-plus/icons-vue';
</script>
<style scoped>
.home-container {
padding: 24px;
height: 100%;
display: flex;
flex-direction: column;
gap: 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
/* 欢迎区样式 */
.welcome-section {
margin-bottom: 8px;
}
.welcome-content {
display: inline-block;
}
.welcome-title {
font-size: 28px;
font-weight: 600;
color: #303133;
margin: 0;
display: flex;
align-items: center;
gap: 12px;
background: #ffffff;
position: relative;
}
.greeting {
background: linear-gradient(90deg, #409eff 0%, #67c23a 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.emoji {
font-size: 32px;
}
.welcome-subtitle {
font-size: 14px;
color: #909399;
margin: 8px 0 0 0;
}
/* 统计卡片区 */
.stats-row {
margin-bottom: 8px;
}
.stat-card {
border-radius: 12px;
border: none;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-4px);
}
.stat-content {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 12px;
}
.stat-icon {
width: 56px;
height: 56px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.stat-info {
/* 中间内容区 */
.center-content {
flex: 1;
}
.stat-value {
font-size: 32px;
font-weight: 700;
color: #303133;
margin: 0;
line-height: 1;
}
.stat-label {
font-size: 13px;
color: #909399;
margin: 6px 0 0 0;
}
.stat-trend {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
}
.stat-trend.up {
color: #67c23a;
}
.stat-trend.down {
color: #f56c6c;
}
/* 主要内容区 */
.main-content {
flex: 1;
min-height: 0;
}
.action-card,
.recent-card {
border-radius: 12px;
border: none;
height: 100%;
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
font-size: 15px;
color: #303133;
}
/* 快捷操作区 */
.action-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
padding: 8px 0;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 20px 16px;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
background: #f5f7fa;
justify-content: center;
gap: 24px;
width: 100%;
}
.action-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.action-icon {
width: 56px;
height: 56px;
border-radius: 12px;
/* 第一行:标题 */
.title-section {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.3s ease;
gap: 0;
}
.action-item:hover .action-icon {
transform: scale(1.1);
}
.action-label {
font-size: 13px;
color: #606266;
font-weight: 500;
.title-highlight {
font-weight: 900;
font-size: 42px;
line-height: 48px;
letter-spacing: -0.39px;
text-align: center;
}
/* 最近文件区 */
.recent-list {
padding: 8px 0;
}
.recent-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 4px;
}
.recent-item:hover {
background: #f5f7fa;
}
.file-icon {
width: 40px;
height: 40px;
border-radius: 8px;
background: #ecf5ff;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: #409eff;
}
.file-info {
flex: 1;
min-width: 0;
}
.file-name {
font-size: 14px;
.title-normal {
font-weight: 900;
font-size: 42px;
line-height: 48px;
letter-spacing: -0.39px;
text-align: center;
color: #303133;
margin: 0;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-path {
/* 第二行:副标题 */
.subtitle-section {
font-weight: 400;
font-size: 14px;
line-height: 22.75px;
letter-spacing: -0.15px;
text-align: center;
color: #606266;
}
/* 第三行:卡片 */
.card-section {
margin: 16px 0;
}
.feature-card {
width: 220px;
height: 158px;
border: 1px solid #e4e7ed;
border-radius: 8px;
display: flex;
flex-direction: column;
padding: 25px 16px;
gap: 8px;
background: #ffffff;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.card-icon {
width: 42px;
height: 42px;
display: flex;
align-items: center;
justify-content: center;
color: #409eff;
margin-bottom: 4px;
}
.card-title {
font-weight: 600;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.55px;
color: #303133;
}
.card-desc {
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: #909399;
margin: 4px 0 0 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-time {
font-size: 12px;
/* 底部:输入框 */
.input-section {
margin-top: auto;
padding-top: 24px;
}
.input-wrapper {
width: 760px;
height: 114px;
border-radius: 12px;
border: 1px solid #dcdfe6;
display: flex;
flex-direction: column;
background: #ffffff;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.input-textarea {
width: 758px;
height: 60px;
padding: 16px;
border: none;
outline: none;
resize: none;
font-size: 14px;
line-height: 20px;
color: #303133;
background: transparent;
font-family: inherit;
}
.input-textarea::placeholder {
color: #c0c4cc;
flex-shrink: 0;
}
.input-toolbar {
height: 54px;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid #ebeef5;
}
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
}
.toolbar-btn {
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #dcdfe6;
background: #ffffff;
cursor: pointer;
transition: all 0.2s ease;
color: #606266;
}
.toolbar-btn:hover {
border-color: #409eff;
color: #409eff;
}
.file-btn {
width: 84px;
height: 28px;
gap: 4px;
padding: 0 8px;
border-radius: 6px;
font-size: 12px;
}
.symbol-btn {
width: 28px;
height: 28px;
padding: 0;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
.toolbar-right {
display: flex;
align-items: center;
}
.send-btn {
width: 32px;
height: 32px;
border: none;
background: #409eff;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
color: #ffffff;
}
.send-btn:hover {
background: #66b1ff;
}
</style>