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

60
Task.md Normal file
View File

@@ -0,0 +1,60 @@
作为产品经理在完成了核心的“AI 读书心得生成”和“任务管理”功能后,接下来的迭代方向应该围绕**“用户粘性”**、**“内容资产化”**以及**“多模态扩展”**三个核心维度展开。
以下是我为你规划的几个进阶功能模块:
---
### 1. 灵感捕获与“碎片化”录入
目前的系统更像是一个“命题作文”工具,但阅读往往伴随着灵感碎片。
* **划线/拍照导入**:支持通过 OCR 识别纸质书上的段落,或导入 Kindle 的导出文件,让 AI 基于这些具体的“划线”生成针对性的心得,而不是泛泛而谈。
* **语音随笔**集成语音转文字让用户在走路或休息时能快速口述一段感悟AI 负责将其润色并整合进最终的心得报告。
### 2. 知识库与个人“数字书架”
心得不应该是孤立的 .json 文件,而应该形成个人的知识图谱。
* **可视化知识图谱**:根据心得中的关键词(如“认知偏差”、“教育心理”),自动生成球状链接图。点击一个词,就能看到所有相关的书籍心得。
* **阅读成就系统**:设计一个精致的仪表盘,统计年度阅读字数、心得产出量。使用阿里云心流平台生成的低成本优势,鼓励用户“多读多产出”。
### 3. 内容的分发与“美化”输出
心得写得好,用户一定会有“分享欲”。
* **精致海报生成**:一键将 AI 心得转化为排版优美的长图或小红书风格的方形卡片,包含书籍封面、核心金句、职业背景标签。
* **PDF/Markdown 模板导出**:提供多种风格的排版模板(如:学术风、极简风、复古信笺),支持一键导出到本地文件夹。
### 4. “双人对谈”模式(互动式阅读)
目前的生成是单向的,可以增加互动性。
* **AI 辩论/对话**:让 AI 扮演书中的作者用户提出反对意见或困惑AI 以作者的身份和职业专家的身份进行双重解答。
* **相似书籍推荐**基于当前生成的心得内容AI 自动推荐下一本可能感兴趣的书,并给出“推荐理由”。
### 5. 系统底层增强PM 视角的技术细节)
* **本地向量库 (RAG)**:如果用户的心得积累到一定程度,可以支持“搜索我的感悟”。例如:“搜索我去年关于‘情绪管理’的所有看法”,系统通过向量检索直接给出总结。
* **模型对比预览**:在设置页面增加一个“实验室”功能,允许用户用相同的 Prompt 同时让 DeepSeek-V3 和 GPT-4o 生成,左右分栏对比,帮助用户选出最适合自己的“文笔”。
---
### 💡 产品优先级建议 (Roadmap)
| 优先级 | 功能模块 | 理由 |
| --- | --- | --- |
| **P0 (必须)** | **通知中心增强** | 完善当前的通知逻辑,增加点击通知直接定位功能。 |
| **P1 (高)** | **卡片/海报分享** | 极大地提升产品的传播性,让用户觉得“产出”是有价值且漂亮的。 |
| **P2 (中)** | **知识库/搜索** | 随着心得增多,管理需求会成为痛点。 |
| **P3 (低)** | **多模态录入** | 开发成本较高,属于进阶体验。 |
---
### 接下来我们可以做什么?
基于这个规划,你最感兴趣的是哪个方向?
* **方案 A**:我可以帮你设计**“一键生成分享海报”**的逻辑Canvas 绘图或 HTML 转图片)。
* **方案 B**:我可以帮你实现**“本地知识检索”**的初步逻辑,让你能搜索过去的心得内容。
**你会更倾向于先把应用做得更“漂亮”(分享海报),还是更“智能”(搜索与关联)?**

View File

@@ -37,6 +37,7 @@
"@trpc/client": "^10.45.2", "@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2", "@trpc/server": "^10.45.2",
"better-sqlite3": "^12.5.0", "better-sqlite3": "^12.5.0",
"echarts": "^6.0.0",
"electron-log": "^5.4.3", "electron-log": "^5.4.3",
"electron-store": "^6.0.1", "electron-store": "^6.0.1",
"electron-trpc": "^0.7.1", "electron-trpc": "^0.7.1",

23
pnpm-lock.yaml generated
View File

@@ -35,6 +35,9 @@ importers:
better-sqlite3: better-sqlite3:
specifier: ^12.5.0 specifier: ^12.5.0
version: 12.5.0 version: 12.5.0
echarts:
specifier: ^6.0.0
version: 6.0.0
electron-log: electron-log:
specifier: ^5.4.3 specifier: ^5.4.3
version: 5.4.3 version: 5.4.3
@@ -1917,6 +1920,9 @@ packages:
eastasianwidth@0.2.0: eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
echarts@6.0.0:
resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==}
ejs@3.1.10: ejs@3.1.10:
resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -3682,6 +3688,9 @@ packages:
peerDependencies: peerDependencies:
typescript: '>=4.8.4' typescript: '>=4.8.4'
tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -4038,6 +4047,9 @@ packages:
zod@4.3.5: zod@4.3.5:
resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==}
zrender@6.0.0:
resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==}
snapshots: snapshots:
7zip-bin@5.2.0: {} 7zip-bin@5.2.0: {}
@@ -5949,6 +5961,11 @@ snapshots:
eastasianwidth@0.2.0: {} eastasianwidth@0.2.0: {}
echarts@6.0.0:
dependencies:
tslib: 2.3.0
zrender: 6.0.0
ejs@3.1.10: ejs@3.1.10:
dependencies: dependencies:
jake: 10.9.4 jake: 10.9.4
@@ -7845,6 +7862,8 @@ snapshots:
dependencies: dependencies:
typescript: 5.9.3 typescript: 5.9.3
tslib@2.3.0: {}
tslib@2.8.1: {} tslib@2.8.1: {}
tunnel-agent@0.6.0: tunnel-agent@0.6.0:
@@ -8174,3 +8193,7 @@ snapshots:
yocto-queue@1.2.2: {} yocto-queue@1.2.2: {}
zod@4.3.5: {} zod@4.3.5: {}
zrender@6.0.0:
dependencies:
tslib: 2.3.0

View File

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

View File

@@ -1,47 +1,58 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
// 保持图标库一致,使用 IconPark 或 Arco Icons import { SettingTwo, ApplicationTwo, Help } from '@icon-park/vue-next'
import { Help, Info, SettingTwo } from '@icon-park/vue-next'
import useRouterHook from '@renderer/hooks/useRouterHook' import useRouterHook from '@renderer/hooks/useRouterHook'
const { go } = useRouterHook() const { go } = useRouterHook()
const activeBtn = ref('') const activeBtn = ref('')
// 1. 核心常驻按钮
// 定义按钮配置,方便维护
const navButtons = [ const navButtons = [
{ key: 'setting', title: '设置', icon: SettingTwo }, { key: 'setting', title: '设置', icon: SettingTwo },
{ key: 'faq', title: '帮助', icon: Help }, { key: 'faq', title: '帮助', icon: Help }
{ key: 'about', title: '关于', icon: Info }
] ]
const active = (key: string) => { const active = (key: string) => {
activeBtn.value = key
go('/' + key) go('/' + key)
} }
</script> </script>
<template> <template>
<div class="p-4 w-full flex flex-col items-center gap-4"> <div class="p-4 w-full flex flex-col items-center gap-4 relative">
<div class="flex items-center bg-white border border-slate-100 p-1.5 rounded-xl shadow-sm"> <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 <div
v-for="btn in navButtons" v-for="btn in navButtons"
:key="btn.key" :key="btn.key"
class="nav-btn group" class="nav-btn group"
:class="{ active: activeBtn === btn.key }" :class="{ active: activeBtn === btn.key }"
:title="btn.title"
@click="active(btn.key)" @click="active(btn.key)"
> >
<component <component
:is="btn.icon" :is="btn.icon"
theme="outline" theme="outline"
size="16" size="16"
class="transition-colors"
:fill="activeBtn === btn.key ? '#7816ff' : '#64748b'" :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"> <div class="w-[1px] h-4 bg-slate-200 mx-1"></div>
{{ btn.title }}
</span> <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> </div>
</div> </div>
@@ -53,35 +64,49 @@ const active = (key: string) => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
padding: 6px 10px; padding: 8px 12px;
border-radius: 8px; border-radius: 12px;
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
position: relative; position: relative;
&:hover { &:hover {
background-color: #f5f3ff; // 极淡的紫色背景 background-color: #f5f3ff;
.arco-icon,
.i-icon {
color: #7816ff;
} }
}
&.active { &.active {
background-color: #f5f3ff; background-color: #f5f3ff;
&::after {
content: '';
position: absolute;
bottom: -4px;
width: 4px;
height: 4px;
background-color: #7816ff;
border-radius: 50%;
}
} }
} }
// 按钮之间的分割线(可选) .nav-text {
.nav-btn:not(:last-child) { font-size: 10px;
margin-right: 4px; 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> </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"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import useRouterHook from '@renderer/hooks/useRouterHook' 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 { trpc } from '@renderer/lib/trpc'
import { IReadingReflectionTaskItem } from '@shared/types/IReadingReflectionTask' import { IReadingReflectionTaskItem } from '@shared/types/IReadingReflectionTask'
import { Message } from '@arco-design/web-vue' 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 subTaskId = computed(() => getQuery('id') as string)
const readingData = ref<IReadingReflectionTaskItem>() const readingData = ref<IReadingReflectionTaskItem>()
@@ -51,22 +52,11 @@ onMounted(() => fetchDetail())
</script> </script>
<template> <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 <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 <BackPage title="返回列表" />
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>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<a-button <a-button
size="mini" size="mini"
@@ -95,7 +85,7 @@ onMounted(() => fetchDetail())
</div> </div>
</header> </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 flex-col gap-xl">
<div class="flex-1 flex flex-col gap-2"> <div class="flex-1 flex flex-col gap-2">
<h1 class="text-4xl font-black text-slate-900 leading-tight mb-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 { trpc } from '@renderer/lib/trpc'
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import BackPage from '@renderer/components/BackPage.vue'
const { getQuery, goBack } = useRouterHook() const { getQuery, goBack } = useRouterHook()
const subTaskId = computed(() => getQuery('itemId') as string) const subTaskId = computed(() => getQuery('itemId') as string)
@@ -67,18 +68,11 @@ onMounted(fetchDetail)
<aside <aside
class="w-60 h-full bg-white border-r border-slate-200 flex flex-col flex-shrink-0 z-30 shadow-xl" class="w-60 h-full bg-white border-r border-slate-200 flex flex-col flex-shrink-0 z-30 shadow-xl"
> >
<div <header
class="p-6 flex items-center gap-3 cursor-pointer group border-b border-slate-50" class="h-16 px-8 flex items-center gap-4 bg-white/80 backdrop-blur-md border-b border-slate-100 relative z-10"
@click="goBack(1)"
> >
<div <BackPage title="功能中心" />
class="w-8 h-8 rounded-full bg-slate-50 flex items-center justify-center group-hover:bg-[#7816ff]/10 transition-all" </header>
>
<left size="16" class="text-slate-400 group-hover:text-[#7816ff]" />
</div>
<span class="font-bold text-slate-800 text-sm">返回详情</span>
</div>
<div class="flex-1 overflow-y-auto p-8 space-y-10 custom-scroll"> <div class="flex-1 overflow-y-auto p-8 space-y-10 custom-scroll">
<section class="space-y-4"> <section class="space-y-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">

View File

@@ -25,7 +25,21 @@ const routes: RouteRecordRaw[] = [
name: 'ReflectionPoster', name: 'ReflectionPoster',
component: () => import('@renderer/pages/reflection/views/poster/index.vue') 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', path: '/setting',
name: 'Setting', name: 'Setting',