完成了笔计本的开发

This commit is contained in:
expdsn 2024-11-07 18:48:45 +08:00
parent 4446ba3759
commit 27b4623500
20 changed files with 523 additions and 21 deletions

View File

@ -15,7 +15,16 @@
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"@fingerprintjs/fingerprintjs": "^4.4.3",
"@milkdown-lab/plugin-menu": "^1.3.1",
"@milkdown/kit": "7.5.0",
"@milkdown/plugin-menu": "^6.5.4",
"@milkdown/plugin-prism": "^7.5.0",
"@milkdown/plugin-tooltip": "^7.5.3",
"@milkdown/theme-nord": "7.5.0",
"@milkdown/vue": "7.5.0",
"@pixi/spine-pixi": "^2.1.0",
"@tailwindcss/typography": "^0.5.9",
"@type-config/strict": "^1.2.1",
"ali-oss": "^6.21.0",
"ant-design-vue": "4.x",
"clsx": "^2.1.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

View File

@ -22,7 +22,7 @@ export default defineComponent({
setup(props) {
const settings = useSettingsStore()
const layout = useLayoutStore()
let it = 0
let it: any = 0
const hover = ref(false)
return () => (
<div

View File

@ -53,14 +53,7 @@ export default defineComponent(() => {
// 设置标记,后续访问不会再次显示
}
})
watch(() =>
store.remainingTime
, (val) => {
if (val <= 0) {
store.stopTomatoTime()
}
})
return () =>
store.openFullscreen && (
<div class="fixed left-0 top-0 z-50 w-full ">

View File

@ -2,6 +2,48 @@
@tailwind components;
@tailwind utilities;
.milkdown {
@apply bg-slate-50 p-3 border rounded;
width: 100%;
height: calc(100% - 18px);
border-radius: 12px 12px 20px 20px;
background: rgba(189, 171, 155, 0.1) !important;
border: none !important;
box-shadow: 0 0 #fff !important;
color: #333 !important;
box-sizing: border-box;
overflow-y: auto;
position: relative;
padding-top: 80px;
}
.milkdown-menu {
position: absolute;
left: 0;
top: 0;
right: 0;
overflow: hidden;
border-radius: 12px 12px 0 0 !important;
padding: 0;
background-color: rgba(215, 203, 185, 0.4) !important;
border: none !important;
outline: none;
margin-bottom: 10px;
}
.milkdown ul {
margin: 0;
border: none;
}
.milkdown-menu ul li {
margin: 0;
padding: 0;
}
.milkdown-menu button {
color: #bca694 !important;
font-weight: bold;
padding-top: 5px !important;
padding-bottom: 5px !important;
}
:root {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;

View File

@ -35,7 +35,6 @@ export default defineComponent({
})
watch(number, (n, old) => {
console.log(Math.abs(n - old) / 400)
gsap.to(tweened, {
number: n,

View File

@ -10,6 +10,7 @@ import constellation from './constellation'
import gameVideo from './gameVideo'
import work from './work'
import game from './game'
import notepad from './notepad'
export interface Widget {
name: string // 小组件类型唯一标识
label: string // 小组件名称
@ -24,4 +25,4 @@ export interface Widget {
}[] // 不同尺寸小组件块
}
export default [game, calendar, weather, weApply, gameNews, eat, discount, hotspot, constellation, gameVideo, work] as Widget[]
export default [game, calendar, weather, weApply, gameNews, eat, discount, hotspot, constellation, gameVideo, work, notepad] as Widget[]

View File

@ -0,0 +1,29 @@
import { defineComponent } from 'vue'
import useNotepadStore from './useNotepadStore'
export default defineComponent(() => {
const store = useNotepadStore()
return () => (
<div class="w-full h-full bg-[#fff] flex ">
<div class={"w-1/2 h-full flex items-center justify-center flex-col gap-y-1 relative"}>
<img class={'w-[52px] h-[52px]'} src={'/tab/icons/note_pad_icon.png'}></img>
<span></span>
<img src="/tab/icons/notepad/note_pad_line.png" class={"absolute w-[25px] right-2 top-1/2 -translate-y-1/2"} alt="" />
</div>
<div class="w-0 flex-1 h-full bg-[#fff] flex flex-col px-4 pb-0 pt-5 gap-y-2">
<div>
<span class={"text-[14px] text-[#333] pb-1 border-b-[1px] border-b-[#e5daca]"}>使</span>
</div>
{
store.state.list.filter((_, idx) => idx < 4).map(item => (
<div class={"w-full pb-[1px] border-b-[1px] border-b-[#e5daca]"}>
<div class={"w-[130px] text-[12px] text-[#656565] text-ellipsis overflow-hidden whitespace-nowrap"}>{item.title || '新建笔记'}</div>
</div>
))
}
</div>
</div>
)
})

View File

@ -0,0 +1,21 @@
import { defineComponent } from 'vue'
import useNotepadStore from './useNotepadStore'
export default defineComponent(() => {
const store = useNotepadStore()
return () => (
<div class="w-full h-full bg-[#fff] flex flex-col px-4 pb-0 pt-5 gap-y-2">
<div>
<span class={"text-[14px] text-[#333] pb-1 border-b-[1px] border-b-[#e5daca]"}>使</span>
</div>
{
store.state.list.filter((_, idx) => idx < 4).map(item => (
<div class={"w-full pb-[1px] border-b-[1px] border-b-[#e5daca]"}>
<div class={"w-[130px] text-[12px] text-[#656565] text-ellipsis overflow-hidden whitespace-nowrap"}>{item.title || '新建笔记'}</div>
</div>
))
}
</div>
)
})

View File

@ -0,0 +1,138 @@
import { addIcons, OhVueIcon } from 'oh-vue-icons'
import { computed, defineComponent, onMounted, ref } from 'vue'
import decorativeImg from "~/icons/notepad/decorative.png"
import { HiSearch } from "oh-vue-icons/icons";
import useNotepadStore from './useNotepadStore';
import dayjs from 'dayjs';
import clsx from 'clsx';
import Editor from './editor';
import "@milkdown/theme-nord/style.css"
import "@milkdown-lab/plugin-menu/style.css"
import { MilkdownProvider } from '@milkdown/vue';
addIcons(HiSearch)
export default defineComponent(() => {
const store = useNotepadStore()
const selectId = ref(store.state.list[0]?.id || '')
const trriger = ref(true)
const searchWorks = ref('')
const currentIndex = computed(() => {
trriger.value = false
setTimeout(() => {
trriger.value = true
}, 0)
return store.state.list.findIndex(val => val.id === selectId.value)
})
const showList = computed(() => {
return store.state.list.map(val => val).sort(a => a.pin ? -1 : 1)
})
return () => (
<div
class="w-full h-full bg-white flex p-4 "
>
<div class={"w-[227px] h-full flex flex-col gap-y-3"}>
<div class={"flex gap-x-2"}>
<div class={"flex-1 w-0 relative "}>
<input type="text"
value={searchWorks.value}
onInput={(e: any) => {
searchWorks.value = e.target.value
}}
placeholder='输入内容'
class={"w-full outline-none h-full pl-2 pr-8 border-[1px] border-[#ddd] rounded-lg text-[#333] placeholder:text-[#C5C5C5]"} />
<OhVueIcon name={HiSearch.name} class={" absolute right-2 top-1/2 -translate-y-1/2"} fill='#C5C5C5'></OhVueIcon>
</div>
<button
onClick={() => {
store.addNewNote()
selectId.value = store.state.list[0]?.id
}}
class={"w-[50px] hover:opacity-90 h-[30px] flex items-center justify-center bg-[#b59e86] rounded text-white"}></button>
</div>
<div class={"flex-1 h-0 w-full pt-2 "}>
<div class={"w-full flex flex-col gap-y-2 "}>
{
showList.value.filter(val => val.content.includes(searchWorks.value)).map((item) => (
<div
onClick={() => {
selectId.value = item.id
}}
class={clsx("flex justify-between w-full cursor-pointer h-[66px] p-2 pl-3 rounded-lg group ",
selectId.value === item.id ? "bg-[#c7ad9666]" : "bg-[#bdab9b33]"
)}>
<div class={"flex flex-col justify-between text-[14px]"}>
<span class={"text-[#333]"}>{item.title || '新建笔记'}</span>
<span class={"text-[#656565]"}>{dayjs(item.date).format('YYYY-MM-DD')}</span>
</div>
<div class={"flex flex-col justify-between"}>
<img
onClick={() => {
item.pin = !item.pin
}
}
src='/tab/icons/notepad/top.png' alt='top' class={clsx("w-[22px]", item.pin ? "" : "hidden opacity-70 group-hover:block")}></img>
<img
onClick={() => {
const idx = store.state.list.findIndex(val => val.id === item.id)
if (idx !== -1) {
store.state.list.splice(idx, 1)
// if (index === showList.value.length - 1) {
// selectId.value = showList.value[index - 2]?.id || ''
// } else {
// selectId.value = showList.value[index + 1]?.id || ''
// }
// selectId.value = showList.value[index - 1 > 0 ? index - 1 : (showList.value.length - 1)]?.id || ''
}
}}
src='/tab/icons/notepad/delete.png' alt='delete' class={"hidden group-hover:block w-[22px] opacity-70"}></img>
</div>
</div>
))
}
</div>
</div>
</div>
<div class={"w-0 flex-1 h-full pt-2"}>
<div class={"w-full h-full flex justify-between pl-5 gap-x-5"}>
<span class={"absolute opacity-0 prose"}></span>
<img src={decorativeImg} alt='decorativeImg' class={"h-full"}></img>
<div class={"flex-1 w-0 h-full flex flex-col"}>
<input
maxlength={20}
onInput={(e: any) => {
if (store.state.list[currentIndex.value]) {
store.state.list[currentIndex.value].title = e.target.value
}
}}
type="text" class={"w-full border-b-[1px] pb-[3px] border-b-[#bdab9b] outline-none "}
value={store.state.list[currentIndex.value]?.title || ''} />
<div class={"flex flex-1 h-0 rounded-lg mt-3 w-full"}>
{
trriger.value &&
<MilkdownProvider >
<Editor
class={"w-full h-full "}
modelValue={store.state.list[currentIndex.value]?.content || ''} onUpdate:modelValue={(e) => {
if (store.state.list[currentIndex.value]) {
store.state.list[currentIndex.value].content = e
}
}}></Editor>
</MilkdownProvider>
}
</div>
</div>
</div>
</div>
</div >
)
})

View File

@ -0,0 +1,22 @@
import { defineComponent } from 'vue'
export default defineComponent(() => {
return () => (
<div
class="w-full h-full flex items-center px-3 bg-white"
>
<img class={'w-[52px] h-[52px]'} src={'/tab/icons/note_pad_icon.png'}></img>
<div class={'flex-1 flex justify-center'}>
<div class="flex-col flex">
<span class={'text-[16px] text-[#AE9680]'}></span>
<div class={'flex items-center text-[#AE9680] text-[12px] '}>
<div class={"opacity-50 ml-[2px] scale-80"}>
</div>
</div>
</div>
</div>
</div>
)
})

View File

@ -0,0 +1,72 @@
import { Milkdown, useEditor } from '@milkdown/vue';
import { defaultValueCtx, Editor, rootCtx } from '@milkdown/kit/core';
import { nord } from '@milkdown/theme-nord'
import { commonmark } from '@milkdown/kit/preset/commonmark'
import { gfm } from '@milkdown/kit/preset/gfm';
import { defineComponent, watch} from 'vue';
import { menu, menuConfigCtx, type MenuConfigItem } from '@milkdown-lab/plugin-menu'
import { listener, listenerCtx } from '@milkdown/kit/plugin/listener';
const menuItems: MenuConfigItem[][] = [
[
{
type: 'button',
content: 'B',
// commandKey
key: 'ToggleStrong',
},
{
type: 'button',
content: 'I',
key: 'ToggleEmphasis',
},
{
type: 'button',
content: 'S',
key: 'ToggleStrikeThrough',
},
],
]
export default defineComponent({
props: {
modelValue: {
type: String,
default: '',
}
},
emits: ['update:modelValue'],
setup: (props, ct) => {
useEditor((root) => {
return Editor.make()
.config(nord)
.config((ctx) => {
const listener = ctx.get(listenerCtx);
ctx.set(menuConfigCtx.key, {
attributes: { class: 'milkdown-menu', 'data-menu': 'true' },
items: menuItems,
})
ctx.set(rootCtx, document.querySelector('#app'))
listener.markdownUpdated((ctx, markdown, prevMarkdown) => {
if (markdown !== prevMarkdown) {
ct.emit('update:modelValue', markdown)
}
})
ctx.set(rootCtx, root)
ctx.set(defaultValueCtx, props.modelValue || ' ')
})
.use(listener)
.use(commonmark)
.use(gfm)
.use(menu)
})
return () => (
<Milkdown />
)
}
})

View File

@ -0,0 +1,30 @@
import asyncLoader from '@/utils/asyncLoader'
import type { Widget } from '..'
export default {
name: 'notepad',
label: '记事本',
description: '记事本',
icon: '/tab/icons/note_pad_icon.png',
modal: asyncLoader(() => import('./Modal')),
list: [
{
w: 2,
h: 1,
label: '小',
component: asyncLoader(() => import('./Small'))
},
{
w: 2,
h: 2,
label: '中',
component: asyncLoader(() => import('./Middle'))
},
{
w: 4,
h: 2,
label: '大',
component: asyncLoader(() => import('./Large'))
}
]
} as Widget

View File

@ -0,0 +1,137 @@
import { defineStore } from "pinia";
import { reactive } from "vue";
import { v4 as uuid } from "uuid"
import dayjs from "dayjs";
export const DEFALUT_DATA = {
content: `
FatfoxTab新标签页平台!
使
## **1. **
BUG反馈
--
## **2. **
FatfoxTab新标签页共分为四个模式
///
## **3. **
15
线
线
## **4. **
PC游戏-
## **5. AI助手**
免费使用:每天30次免费聊天沿
结果增强:通过小助手
AI对话指令模板
访FatfoxTab新标签页后 ~
## **6. /**
## **7. **
1.
2.
3.//
4.
5.
6.便
7./FatfoxTab账号
8.
9.-
10.
11.CPU使
12.
13.
...
`,
title: "FatFoxTab指南",
date: 1730877843004,
id: "defautId"
};
export type NotepadItem = {
title: string
content: string
id: string
date: number
pin: boolean
}
export default defineStore("notepad", () => {
const state = reactive({
list: [DEFALUT_DATA] as NotepadItem[]
})
const addNewNote = () => {
state.list.unshift({
id: uuid(),
title: '',
date: dayjs().valueOf(),
content: '',
pin: false
})
}
return {
state,
addNewNote
}
}, {
persist: true
})

View File

@ -54,7 +54,7 @@ export default defineStore("work", () => {
if (!state.isStart) {
return 0
}
return dayjs(state.beginTime).add(15, 'minute').diff(dayjs(time.date), 'second')
return dayjs(state.beginTime).add(1, 'minute').diff(dayjs(time.date), 'second')
})
const stopTomatoTime = () => {
@ -96,6 +96,12 @@ export default defineStore("work", () => {
togglePlay()
}
watch(remainingTime, (val) => {
if (val <= 0) {
stopTomatoTime()
}
})
const openShowModel = ref<undefined | null | TomatoTarget>()
const openFullscreen = ref(false)
const todayHour = computed(() => {
@ -111,6 +117,7 @@ export default defineStore("work", () => {
return state.list.filter(val => dayjs(val.finishTime).isSame(dayjs().subtract(1, 'day'), 'day') && val.isCompleted).length
})
return {
state,
openShowModel,

12
tailwind.config.cjs Normal file
View File

@ -0,0 +1,12 @@
/* eslint-disable no-undef */
/* Copyright 2021, Milkdown by Mirone. */
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
darkMode: 'class',
theme: {
extend: {}
},
plugins: [require('@tailwindcss/typography')]
}

View File

@ -1,10 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
darkMode: 'selector',
theme: {
fontFamily: {
'din': 'DIN-Black'
}
}
}