feat(desktop): 新增更多功能菜单,但是没有实现功能

1. 实现了用户画像页面设计
This commit is contained in:
2026-01-11 02:42:46 +08:00
parent 311aa59482
commit 75cc9dc06d
13 changed files with 594 additions and 63 deletions

View File

@@ -31,6 +31,7 @@ declare module 'vue' {
ATag: typeof import('@arco-design/web-vue')['Tag']
ATextarea: typeof import('@arco-design/web-vue')['Textarea']
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']
BackPage: typeof import('./src/components/BackPage.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

View File

@@ -1,47 +1,58 @@
<script setup lang="ts">
import { ref } from 'vue'
// 保持图标库一致,使用 IconPark 或 Arco Icons
import { Help, Info, SettingTwo } from '@icon-park/vue-next'
import { SettingTwo, ApplicationTwo, Help } from '@icon-park/vue-next'
import useRouterHook from '@renderer/hooks/useRouterHook'
const { go } = useRouterHook()
const activeBtn = ref('')
// 定义按钮配置,方便维护
// 1. 核心常驻按钮
const navButtons = [
{ key: 'setting', title: '设置', icon: SettingTwo },
{ key: 'faq', title: '帮助', icon: Help },
{ key: 'about', title: '关于', icon: Info }
{ key: 'faq', title: '帮助', icon: Help }
]
const active = (key: string) => {
activeBtn.value = key
go('/' + key)
}
</script>
<template>
<div class="p-4 w-full flex flex-col items-center gap-4">
<div class="flex items-center bg-white border border-slate-100 p-1.5 rounded-xl shadow-sm">
<div class="p-4 w-full flex flex-col items-center gap-4 relative">
<div
class="flex items-center bg-white/80 backdrop-blur-md border border-slate-100 p-1.5 rounded-2xl shadow-sm relative z-[60]"
>
<div
v-for="btn in navButtons"
:key="btn.key"
class="nav-btn group"
:class="{ active: activeBtn === btn.key }"
:title="btn.title"
@click="active(btn.key)"
>
<component
:is="btn.icon"
theme="outline"
size="16"
class="transition-colors"
:fill="activeBtn === btn.key ? '#7816ff' : '#64748b'"
/>
<span class="nav-text" :class="{ 'active-text': activeBtn === btn.key }">{{
btn.title
}}</span>
</div>
<span class="text-[10px] ml-1.5 group-hover:inline-block text-slate-500 font-medium">
{{ btn.title }}
</span>
<div class="w-[1px] h-4 bg-slate-200 mx-1"></div>
<div
class="nav-btn group"
:class="{ active: activeBtn === 'menus' }"
@click="active('menus')"
>
<application-two
theme="outline"
size="16"
:fill="activeBtn === 'menus' ? '#7816ff' : '#64748b'"
/>
<span class="nav-text" :class="{ 'active-text': activeBtn === 'menus' }">更多</span>
</div>
</div>
</div>
@@ -53,35 +64,49 @@ const active = (key: string) => {
align-items: center;
justify-content: center;
cursor: pointer;
padding: 6px 10px;
border-radius: 8px;
padding: 8px 12px;
border-radius: 12px;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
&:hover {
background-color: #f5f3ff; // 极淡的紫色背景
.arco-icon,
.i-icon {
color: #7816ff;
}
background-color: #f5f3ff;
}
&.active {
background-color: #f5f3ff;
&::after {
content: '';
position: absolute;
bottom: -4px;
width: 4px;
height: 4px;
background-color: #7816ff;
border-radius: 50%;
}
}
}
// 按钮之间的分割线(可选)
.nav-btn:not(:last-child) {
margin-right: 4px;
.nav-text {
font-size: 10px;
max-width: 0;
opacity: 0;
overflow: hidden;
white-space: nowrap;
transition: all 0.3s ease;
.group:hover & {
max-width: 40px;
margin-left: 6px;
opacity: 1;
}
}
.active-text {
max-width: 40px;
margin-left: 6px;
opacity: 1;
font-weight: bold;
color: #7816ff;
}
/* 动画 */
.slide-up-enter-active,
.slide-up-leave-active {
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.slide-up-enter-from,
.slide-up-leave-to {
opacity: 0;
transform: translateY(10px) scale(0.95);
}
</style>

View File

@@ -0,0 +1,29 @@
<script setup lang="ts">
import { Left } from '@icon-park/vue-next'
import useRouterHook from '@renderer/hooks/useRouterHook'
import { toRefs } from 'vue'
const { goBack } = useRouterHook()
const props = defineProps<{
title: string
}>()
const { title } = toRefs(props)
</script>
<template>
<div
class="flex items-center gap-3 cursor-pointer group border-b border-slate-50"
@click="goBack(1)"
>
<div
class="w-8 h-8 rounded-full bg-slate-50 flex items-center justify-center group-hover:bg-[#7816ff]/10 transition-all"
>
<left size="16" class="text-slate-400 group-hover:text-[#7816ff]" />
</div>
<span class="font-bold text-slate-800 text-sm">{{ title }}</span>
</div>
</template>
<style scoped></style>

View File

@@ -0,0 +1,83 @@
import {
DatabaseConfig,
DocumentFolder,
FileCode,
Headset,
Refresh,
Search,
TrendTwo
} from '@icon-park/vue-next'
export const features = [
{
group: '数据探索',
items: [
{
id: 'search',
title: '全局检索',
desc: '秒级搜索所有本地心得内容',
icon: Search,
color: '#7816ff',
path: 'search'
},
{
id: 'statistics',
title: '阅读画像',
desc: '可视化你的知识边界与偏好',
icon: TrendTwo,
color: '#00b42a',
path: 'statistics'
}
]
},
{
group: '自动化与导出',
items: [
{
id: 'obsidian',
title: 'Obsidian 同步',
desc: '自动同步至本地双链笔记库',
icon: Refresh,
color: '#165dff',
path: 'sync'
},
{
id: 'export',
title: '批量导出',
icon: FileCode,
desc: '导出 PDF / Markdown 格式',
color: '#ff7d00',
path: 'export'
},
{
id: 'monitor',
title: '书库监控',
icon: DocumentFolder,
desc: '自动扫描本地文件夹新书',
color: '#eb2f96',
path: 'monitor'
}
]
},
{
group: '实验室功能',
items: [
{
id: 'tts',
title: '听书模式',
desc: '调用系统引擎朗读心得摘要',
icon: Headset,
color: '#722ed1',
path: 'tts'
},
{
id: 'model',
title: '模型实验室',
desc: '对比不同 Prompt 的生成效果',
icon: DatabaseConfig,
color: '#13c2c2',
path: 'lab'
}
]
}
]

View File

@@ -0,0 +1,86 @@
<script setup lang="ts">
import useRouterHook from '@renderer/hooks/useRouterHook'
import { Left } from '@icon-park/vue-next'
import { features } from '@renderer/pages/menus/data/MenusData'
const { go } = useRouterHook()
const handleNavigate = (path: string) => {
go(`/menus/${path}`)
}
</script>
<template>
<div class="flex flex-col bg-[#F8F9FB] overflow-hidden animate-in py-6">
<div class="mx-auto space-y-12">
<div v-for="group in features" :key="group.group" class="space-y-6">
<div class="flex items-center gap-3">
<div class="w-1 h-5 bg-[#7816ff] rounded-full"></div>
<h2 class="text-sm font-black tracking-[0.3em]">
{{ group.group }}
</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div
v-for="item in group.items"
:key="item.id"
@click="handleNavigate(item.path)"
class="feature-card group flex gap-sm cursor-pointer"
>
<div class="flex items-start justify-between">
<div
class="w-12 h-12 rounded-2xl flex items-center justify-center transition-transform group-hover:scale-110 duration-300"
:style="{ backgroundColor: item.color + '10' }"
>
<component :is="item.icon" size="24" :style="{ color: item.color }" />
</div>
</div>
<div class="flex-1 flex flex-col">
<span class="text-sm font-bold text-slate-800 group-hover:text-[#7816ff]">
{{ item.title }}
</span>
<p class="text-[11px] text-slate-400 leading-relaxed">
{{ item.desc }}
</p>
</div>
<div class="opacity-0 group-hover:opacity-100 transition-opacity">
<div class="w-6 h-6 rounded-full bg-slate-50 flex items-center justify-center">
<left class="rotate-180 text-slate-300" size="12" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.feature-card {
@apply bg-white p-6 rounded-[24px] border border-slate-100 cursor-pointer
transition-all duration-300 hover:shadow-xl hover:shadow-slate-200/50
hover:-translate-y-1 active:scale-95;
}
.custom-scroll::-webkit-scrollbar {
width: 4px;
}
.custom-scroll::-webkit-scrollbar-thumb {
background: #e2e8f0;
border-radius: 10px;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-in {
animation: fadeIn 0.4s ease-out forwards;
}
</style>

View File

@@ -0,0 +1,133 @@
<script setup lang="ts">
import { Left, Rocket, Timer } from '@icon-park/vue-next'
import useRouterHook from '@renderer/hooks/useRouterHook'
import { computed, toRefs } from 'vue'
import { features } from '@renderer/pages/menus/data/MenusData'
import { useRoute } from 'vue-router'
const route = useRoute()
const { goBack } = useRouterHook()
const currentTitle = computed(() => {
// 获取路径中最后一部分,例如 /menus/tts 得到 tts
const pathKey = route.path.split('/').pop()
// 在配置数组中深度查找
for (const group of features) {
const item = group.items.find((i) => i.path === pathKey)
if (item) return item.title
}
return '新功能'
})
// 动态接收正在开发的功能名称
const props = defineProps<{
title?: string
}>()
const { title } = toRefs(props)
</script>
<template>
<div class="h-full w-full flex flex-col bg-[#F8F9FB] overflow-hidden">
<header class="h-16 px-8 flex items-center relative z-20">
<div
class="w-8 h-8 rounded-full bg-white shadow-sm flex items-center justify-center cursor-pointer hover:bg-purple-50 transition-all group"
@click="goBack(1)"
>
<left size="16" class="text-slate-400 group-hover:text-[#7816ff]" />
</div>
</header>
<main class="flex-1 flex flex-col items-center justify-center p-12 relative">
<div
class="absolute w-[500px] h-[500px] bg-[#7816ff]/5 rounded-full blur-[100px] -z-10 animate-pulse"
></div>
<div class="flex flex-col items-center max-w-md text-center space-y-8 animate-in">
<div class="relative">
<div class="pt-8 w-full flex items-center">
<div class="flex items-center flex-col gap-4 m-a">
<div
class="w-20 h-20 rounded-2xl flex items-center justify-center text-white font-black text-xl italic shadow-lg bg-[#7816ff]"
>
Z
</div>
<div>
<div class="text-[13px] font-black text-slate-800 tracking-tight">读书心得助手</div>
<div class="text-[9px] text-slate-400 font-bold uppercase tracking-widest mt-0.5">
让每一篇阅读者享受阅读与思考的乐趣
</div>
</div>
</div>
</div>
</div>
<div class="space-y-3">
<h1 class="text-2xl font-black text-slate-800">
{{ currentTitle || '功能' }} 正在精心打磨中
</h1>
<p class="text-sm text-slate-400 leading-relaxed px-6">
为了提供更精准的体验我们正在优化该功能预计在接下来的版本中与您见面
</p>
</div>
<div
class="w-full bg-white rounded-2xl p-6 border border-slate-100 shadow-sm flex flex-col gap-4"
>
<div class="flex items-center gap-2 m-a">
<timer theme="outline" size="14" class="text-slate-300" />
<span class="text-[11px] text-slate-400 italic">好的深度值得一点时间的等待</span>
</div>
</div>
<button
@click="goBack(1)"
class="px-8 h-11 bg-slate-800 text-white rounded-xl font-bold text-sm hover:bg-slate-900 transition-all shadow-lg active:scale-95"
>
返回探索其他功能
</button>
</div>
</main>
</div>
</template>
<style scoped>
@keyframes float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
@keyframes shine {
from {
transform: translateX(-100%);
}
to {
transform: translateX(200%);
}
}
.animate-shine {
animation: shine 2s infinite linear;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-in {
animation: fadeIn 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
</style>

View File

@@ -0,0 +1,92 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import * as echarts from 'echarts'
import { ChartGraph, TrendTwo, Local } from '@icon-park/vue-next'
import BackPage from '@renderer/components/BackPage.vue'
const chartRef = ref<HTMLElement | null>(null)
const initChart = () => {
if (!chartRef.value) return
const myChart = echarts.init(chartRef.value)
const option = {
radar: {
// 这里的指示器可以根据用户本地数据的标签动态生成
indicator: [
{ name: '认知深度', max: 100 },
{ name: '职场技能', max: 100 },
{ name: '人文素养', max: 100 },
{ name: '技术探索', max: 100 },
{ name: '生活哲学', max: 100 }
],
splitArea: {
areaStyle: {
color: ['#F8F9FB', '#fff'],
shadowColor: 'rgba(0, 0, 0, 0.05)',
shadowBlur: 10
}
},
axisLine: { lineStyle: { color: '#E5E7EB' } },
splitLine: { lineStyle: { color: '#E5E7EB' } }
},
series: [
{
type: 'radar',
data: [
{
value: [85, 72, 90, 65, 80], // 这里接入本地统计的真实数据
name: '阅读画像',
itemStyle: { color: '#7816ff' },
areaStyle: { color: 'rgba(120, 22, 255, 0.1)' },
lineStyle: { width: 3 }
}
]
}
]
}
myChart.setOption(option)
}
onMounted(() => initChart())
</script>
<template>
<div class="h-full w-full flex flex-col gap-xl relative overflow-hidden">
<header
class="h-16 border-b border-slate-100/50 px-6 flex items-center justify-between relative z-20 backdrop-blur-md bg-white/70"
>
<BackPage title="返回应用菜单" />
</header>
<main class="flex-1 bg-white overflow-y-auto relative z-10 p-4 bg-white rounded-xl">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-purple-50 flex items-center justify-center">
<chart-graph theme="outline" size="22" fill="#7816ff" />
</div>
<div class="flex flex-col">
<span class="text-sm font-bold text-slate-800">个人阅读画像</span>
<p class="text-[10px] text-slate-400 uppercase tracking-widest">你的阅读指导专家</p>
</div>
</div>
</div>
<div ref="chartRef" class="w-full h-[320px]"></div>
<div class="p-4 bg-[#7816ff]/5 rounded-2xl border border-[#7816ff]/10">
<div class="flex items-start gap-3 flex-col">
<div class="flex flex-row gap-sm">
<trend-two theme="outline" size="16" fill="#7816ff" />
<span class="text-sm font-bold text-[#7816ff]">你的阅读分享报告</span>
</div>
<span class="text-[11px] text-[#7816ff] leading-relaxed font-medium">
基于你最近生成的 12
篇心得分析你在<strong>人文素养</strong>领域积累深厚近期对<strong>认知科学</strong>的关注度上升了
24%
</span>
</div>
</div>
</main>
</div>
</template>

View File

@@ -1,12 +1,13 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import useRouterHook from '@renderer/hooks/useRouterHook'
import { Copy, Left, Quote, FileCode, Share } from '@icon-park/vue-next'
import { Copy, Quote, FileCode, Share } from '@icon-park/vue-next'
import { trpc } from '@renderer/lib/trpc'
import { IReadingReflectionTaskItem } from '@shared/types/IReadingReflectionTask'
import { Message } from '@arco-design/web-vue'
import BackPage from '@renderer/components/BackPage.vue'
const { getQuery, goBack, go } = useRouterHook()
const { getQuery, go } = useRouterHook()
const subTaskId = computed(() => getQuery('id') as string)
const readingData = ref<IReadingReflectionTaskItem>()
@@ -51,22 +52,11 @@ onMounted(() => fetchDetail())
</script>
<template>
<div class="h-full w-full flex flex-col relative overflow-hidden">
<div class="h-full w-full flex flex-col gap-xl relative overflow-hidden">
<header
class="h-14 border-b border-slate-100/50 px-6 flex items-center justify-between relative z-20 backdrop-blur-md bg-white/70"
class="h-16 border-b border-slate-100/50 px-6 flex items-center justify-between relative z-20 backdrop-blur-md bg-white/70"
>
<div
class="flex items-center gap-3 cursor-pointer group border-b border-slate-50"
@click="goBack(1)"
>
<div
class="w-5 h-5 rounded-full bg-slate-50 flex items-center justify-center group-hover:bg-[#7816ff]/10 transition-all"
>
<left size="16" class="text-slate-400 group-hover:text-[#7816ff]" />
</div>
<span class="font-bold text-slate-800 text-sm">返回列表</span>
</div>
<BackPage title="返回列表" />
<div class="flex items-center gap-2">
<a-button
size="mini"
@@ -95,7 +85,7 @@ onMounted(() => fetchDetail())
</div>
</header>
<div class="flex-1 overflow-y-auto relative z-10 px-8 bg-white top-5 rounded-xl">
<div class="flex-1 overflow-y-auto relative z-10 px-8 bg-white rounded-xl">
<div class="flex flex-col gap-xl">
<div class="flex-1 flex flex-col gap-2">
<h1 class="text-4xl font-black text-slate-900 leading-tight mb-2">

View File

@@ -5,6 +5,7 @@ import { ApplicationEffect, Camera, FontSize, Left } from '@icon-park/vue-next'
import { trpc } from '@renderer/lib/trpc'
import html2canvas from 'html2canvas'
import { Message } from '@arco-design/web-vue'
import BackPage from '@renderer/components/BackPage.vue'
const { getQuery, goBack } = useRouterHook()
const subTaskId = computed(() => getQuery('itemId') as string)
@@ -67,18 +68,11 @@ onMounted(fetchDetail)
<aside
class="w-60 h-full bg-white border-r border-slate-200 flex flex-col flex-shrink-0 z-30 shadow-xl"
>
<div
class="p-6 flex items-center gap-3 cursor-pointer group border-b border-slate-50"
@click="goBack(1)"
<header
class="h-16 px-8 flex items-center gap-4 bg-white/80 backdrop-blur-md border-b border-slate-100 relative z-10"
>
<div
class="w-8 h-8 rounded-full bg-slate-50 flex items-center justify-center group-hover:bg-[#7816ff]/10 transition-all"
>
<left size="16" class="text-slate-400 group-hover:text-[#7816ff]" />
</div>
<span class="font-bold text-slate-800 text-sm">返回详情</span>
</div>
<BackPage title="功能中心" />
</header>
<div class="flex-1 overflow-y-auto p-8 space-y-10 custom-scroll">
<section class="space-y-4">
<div class="flex items-center justify-between">

View File

@@ -25,7 +25,21 @@ const routes: RouteRecordRaw[] = [
name: 'ReflectionPoster',
component: () => import('@renderer/pages/reflection/views/poster/index.vue')
},
{
path: '/menus/statistics',
name: 'ReadingStatistics',
component: () => import('@renderer/pages/menus/views/statistics/index.vue')
},
{
path: '/menus/:pathMatch(.*)*',
name: 'MenusDeveloping',
component: () => import('@renderer/pages/menus/views/Developing.vue')
},
{
path: '/menus',
name: 'Menus',
component: () => import('@renderer/pages/menus/index.vue')
},
{
path: '/setting',
name: 'Setting',