feat(desktop): ✨ 新增更多功能菜单,但是没有实现功能
1. 实现了用户画像页面设计
This commit is contained in:
60
Task.md
Normal file
60
Task.md
Normal 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**:我可以帮你实现**“本地知识检索”**的初步逻辑,让你能搜索过去的心得内容。
|
||||||
|
|
||||||
|
**你会更倾向于先把应用做得更“漂亮”(分享海报),还是更“智能”(搜索与关联)?**
|
||||||
@@ -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
23
pnpm-lock.yaml
generated
@@ -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
|
||||||
|
|||||||
1
src/renderer/components.d.ts
vendored
1
src/renderer/components.d.ts
vendored
@@ -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']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
29
src/renderer/src/components/BackPage.vue
Normal file
29
src/renderer/src/components/BackPage.vue
Normal 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>
|
||||||
83
src/renderer/src/pages/menus/data/MenusData.ts
Normal file
83
src/renderer/src/pages/menus/data/MenusData.ts
Normal 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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
86
src/renderer/src/pages/menus/index.vue
Normal file
86
src/renderer/src/pages/menus/index.vue
Normal 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>
|
||||||
133
src/renderer/src/pages/menus/views/Developing.vue
Normal file
133
src/renderer/src/pages/menus/views/Developing.vue
Normal 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>
|
||||||
92
src/renderer/src/pages/menus/views/statistics/index.vue
Normal file
92
src/renderer/src/pages/menus/views/statistics/index.vue
Normal 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>
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user