From 0bfd823733bdeec35161a87fd6bdf295355c48b8 Mon Sep 17 00:00:00 2001 From: plightfield <1207120484@qq.com> Date: Mon, 9 Sep 2024 17:53:07 +0800 Subject: [PATCH] init --- .eslintrc.cjs | 15 ++ .gitignore | 35 +++ .prettierrc.json | 8 + README.md | 39 ++++ env.d.ts | 1 + index.html | 13 ++ package.json | 55 +++++ postcss.config.js | 6 + public/favicon.ico | Bin 0 -> 4286 bytes public/searchIcons/360.svg | 9 + public/searchIcons/F.svg | 3 + public/searchIcons/GitHub.svg | 3 + public/searchIcons/baidu.svg | 3 + public/searchIcons/bilibili.svg | 4 + public/searchIcons/bing.svg | 19 ++ public/searchIcons/douban.svg | 3 + public/searchIcons/google.svg | 6 + public/searchIcons/kaifa.svg | 9 + public/searchIcons/mita.jpg | Bin 0 -> 10282 bytes public/searchIcons/sougou.svg | 3 + public/searchIcons/translate.png | Bin 0 -> 845 bytes public/searchIcons/yandex.svg | 4 + public/searchIcons/zhihu.svg | 3 + src/App.vue | 41 ++++ src/GlobalModal.tsx | 80 +++++++ src/config.ts | 10 + src/db.ts | 45 ++++ src/layout/background/BackgroundPage.tsx | 5 + src/layout/background/index.tsx | 22 ++ src/layout/background/useBackgroundStore.ts | 34 +++ src/layout/header/GlobalTime.tsx | 52 +++++ src/layout/header/index.tsx | 34 +++ src/layout/header/search/SearchConfig.tsx | 65 ++++++ src/layout/header/search/SearchHistory.tsx | 40 ++++ src/layout/header/search/SearchPage.tsx | 200 ++++++++++++++++++ src/layout/header/search/SearchSuggestion.tsx | 78 +++++++ src/layout/header/search/index.tsx | 59 ++++++ .../header/search/useSearchConfigStore.ts | 113 ++++++++++ src/layout/header/search/useSearchStore.ts | 99 +++++++++ src/layout/layout.types.ts | 45 ++++ src/layout/useLayoutStore.ts | 30 +++ src/layout/utils.ts | 14 ++ src/main.css | 127 +++++++++++ src/main.ts | 22 ++ src/settings/SettingsButton.tsx | 19 ++ src/settings/SettingsOverlay.tsx | 91 ++++++++ src/settings/useSettingsStore.ts | 31 +++ src/useRouterStore.ts | 24 +++ src/user/UserPage.tsx | 5 + src/user/useUserStore.ts | 5 + src/utils/ImageUploader.tsx | 72 +++++++ src/utils/asyncLoader.tsx | 26 +++ src/utils/getFp.ts | 16 ++ src/utils/jump.ts | 75 +++++++ src/utils/upload.ts | 40 ++++ src/utils/useTimeStore.ts | 20 ++ src/utils/vOutsideClick.ts | 24 +++ tailwind.config.js | 5 + tsconfig.app.json | 14 ++ tsconfig.json | 11 + tsconfig.node.json | 19 ++ vite.config.ts | 42 ++++ 62 files changed, 1995 insertions(+) create mode 100644 .eslintrc.cjs create mode 100644 .gitignore create mode 100644 .prettierrc.json create mode 100644 README.md create mode 100644 env.d.ts create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/favicon.ico create mode 100644 public/searchIcons/360.svg create mode 100644 public/searchIcons/F.svg create mode 100644 public/searchIcons/GitHub.svg create mode 100644 public/searchIcons/baidu.svg create mode 100644 public/searchIcons/bilibili.svg create mode 100644 public/searchIcons/bing.svg create mode 100644 public/searchIcons/douban.svg create mode 100644 public/searchIcons/google.svg create mode 100644 public/searchIcons/kaifa.svg create mode 100644 public/searchIcons/mita.jpg create mode 100644 public/searchIcons/sougou.svg create mode 100644 public/searchIcons/translate.png create mode 100644 public/searchIcons/yandex.svg create mode 100644 public/searchIcons/zhihu.svg create mode 100644 src/App.vue create mode 100644 src/GlobalModal.tsx create mode 100644 src/config.ts create mode 100644 src/db.ts create mode 100644 src/layout/background/BackgroundPage.tsx create mode 100644 src/layout/background/index.tsx create mode 100644 src/layout/background/useBackgroundStore.ts create mode 100644 src/layout/header/GlobalTime.tsx create mode 100644 src/layout/header/index.tsx create mode 100644 src/layout/header/search/SearchConfig.tsx create mode 100644 src/layout/header/search/SearchHistory.tsx create mode 100644 src/layout/header/search/SearchPage.tsx create mode 100644 src/layout/header/search/SearchSuggestion.tsx create mode 100644 src/layout/header/search/index.tsx create mode 100644 src/layout/header/search/useSearchConfigStore.ts create mode 100644 src/layout/header/search/useSearchStore.ts create mode 100644 src/layout/layout.types.ts create mode 100644 src/layout/useLayoutStore.ts create mode 100644 src/layout/utils.ts create mode 100644 src/main.css create mode 100644 src/main.ts create mode 100644 src/settings/SettingsButton.tsx create mode 100644 src/settings/SettingsOverlay.tsx create mode 100644 src/settings/useSettingsStore.ts create mode 100644 src/useRouterStore.ts create mode 100644 src/user/UserPage.tsx create mode 100644 src/user/useUserStore.ts create mode 100644 src/utils/ImageUploader.tsx create mode 100644 src/utils/asyncLoader.tsx create mode 100644 src/utils/getFp.ts create mode 100644 src/utils/jump.ts create mode 100644 src/utils/upload.ts create mode 100644 src/utils/useTimeStore.ts create mode 100644 src/utils/vOutsideClick.ts create mode 100644 tailwind.config.js create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..6f40582 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,15 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + 'extends': [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript', + '@vue/eslint-config-prettier/skip-formatting' + ], + parserOptions: { + ecmaVersion: 'latest' + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3149916 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +yarn.lock +package-lock.json + +/cypress/videos/ +/cypress/screenshots/ +stats.html +.vscode + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..66e2335 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..225dd72 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# xyyd-fatfox + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vitejs.dev/config/). + +## Project Setup + +```sh +yarn +``` + +### Compile and Hot-Reload for Development + +```sh +yarn dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +yarn build +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +yarn lint +``` diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/index.html b/index.html new file mode 100644 index 0000000..a888544 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..f3f42d2 --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "xyyd-fatfox", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build --force", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "format": "prettier --write src/" + }, + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "@fingerprintjs/fingerprintjs": "^4.4.3", + "ali-oss": "^6.21.0", + "ant-design-vue": "4.x", + "dayjs": "^1.11.13", + "localforage": "^1.10.0", + "lunar-typescript": "^1.7.5", + "oh-vue-icons": "^1.0.0-rc3", + "pinia": "^2.1.7", + "pinia-plugin-persistedstate": "^3.2.3", + "uuid": "^10.0.0", + "vue": "^3.4.29" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.8.0", + "@tsconfig/node20": "^20.1.4", + "@types/ali-oss": "^6.16.11", + "@types/node": "^20.14.5", + "@types/uuid": "^10.0.0", + "@vitejs/plugin-vue": "^5.0.5", + "@vitejs/plugin-vue-jsx": "^4.0.0", + "@vue/eslint-config-prettier": "^9.0.0", + "@vue/eslint-config-typescript": "^13.0.0", + "@vue/tsconfig": "^0.5.1", + "autoprefixer": "^10.4.20", + "eslint": "^8.57.0", + "eslint-plugin-vue": "^9.23.0", + "less": "^4.2.0", + "npm-run-all2": "^6.2.0", + "postcss": "^8.4.43", + "prettier": "^3.2.5", + "rollup-plugin-visualizer": "^5.12.0", + "tailwindcss": "^3.4.10", + "terser": "^5.31.6", + "typescript": "~5.4.0", + "vite": "^5.3.1", + "vite-plugin-vue-devtools": "^7.3.1", + "vue-tsc": "^2.0.21" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..9ed16a0 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + autoprefixer: {}, + tailwindcss: {} + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/public/searchIcons/360.svg b/public/searchIcons/360.svg new file mode 100644 index 0000000..87123b9 --- /dev/null +++ b/public/searchIcons/360.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/searchIcons/F.svg b/public/searchIcons/F.svg new file mode 100644 index 0000000..ddce289 --- /dev/null +++ b/public/searchIcons/F.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/searchIcons/GitHub.svg b/public/searchIcons/GitHub.svg new file mode 100644 index 0000000..f36d57b --- /dev/null +++ b/public/searchIcons/GitHub.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/searchIcons/baidu.svg b/public/searchIcons/baidu.svg new file mode 100644 index 0000000..5eea01d --- /dev/null +++ b/public/searchIcons/baidu.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/searchIcons/bilibili.svg b/public/searchIcons/bilibili.svg new file mode 100644 index 0000000..7f17220 --- /dev/null +++ b/public/searchIcons/bilibili.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/searchIcons/bing.svg b/public/searchIcons/bing.svg new file mode 100644 index 0000000..2b9dd85 --- /dev/null +++ b/public/searchIcons/bing.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/searchIcons/douban.svg b/public/searchIcons/douban.svg new file mode 100644 index 0000000..968e7a4 --- /dev/null +++ b/public/searchIcons/douban.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/searchIcons/google.svg b/public/searchIcons/google.svg new file mode 100644 index 0000000..92a1c92 --- /dev/null +++ b/public/searchIcons/google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/searchIcons/kaifa.svg b/public/searchIcons/kaifa.svg new file mode 100644 index 0000000..449f437 --- /dev/null +++ b/public/searchIcons/kaifa.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/searchIcons/mita.jpg b/public/searchIcons/mita.jpg new file mode 100644 index 0000000000000000000000000000000000000000..29a5072a401b2d313567665b3abaf622bc6a3583 GIT binary patch literal 10282 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 JSf*|8e*xWS42=K) literal 0 HcmV?d00001 diff --git a/public/searchIcons/sougou.svg b/public/searchIcons/sougou.svg new file mode 100644 index 0000000..1ec1fb9 --- /dev/null +++ b/public/searchIcons/sougou.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/searchIcons/translate.png b/public/searchIcons/translate.png new file mode 100644 index 0000000000000000000000000000000000000000..2c9e385d2706418f37d9881f71760c98df490a0a GIT binary patch literal 845 zcmV-T1G4;yP)Px&2T4RhR7gv$md{e!P!z_`34aueGhtiBh9_uwgSahYi%(FjEV`>*sojVx>Pp;o zMjZ17+EMJH3-AfFo6dBV4A5ye9WxyjOvpW*5UBxfLeOTJ+)f=+-+WA*Vn3_3AcY~~PgKj?VgI6^E120r=FtP-H?1r#hwGXYir@s-b_ z^#2C@(>4;~MEDs)q3Wl?eSjJfZ4MR;8rSvU}I#0};;at261KXDJmM zvU}HofyrXy5K(yP2H7%B$3ikv=)nD6AR}e6h!_gt+2Fzg1Eqp0!h|~xVM$w0)}(D! zWJXkVMMbNx&7{0s-38_jF2qPQA`zqU@&y3ph3VQ{@kap=Hg0#L)YoQ~yj+dTM^{-8 zU>^a;hS>lD&@AI-zWTkjPe8IQXB#FVicIC5GcKQ8t4JUPH37E>q4MS1?;X|wx&D5( zlq@zC^vEOTE7#-?Bi;G#rvN)GXX4#UVYb+i1)nUA0-l#MQ|ULKTT+LyGjHe}Fu7IF zB89gBh^v|}5G}T}`y#H`D_^c$mJo!ud^H@PSWrd1kkX~%PlaZ~P!+9Y^e8pSVVW5{0J%M2ZQ|-w%gxQ1&KzcdzDB&$@Kp?SC z9zd6zAUI8zS@Yhqjs_ypDT0~f0{_W*)RLKCb?qHBLbZ>JF^w;)1GG&7(BZ_PqiO3Y zt(#_E@NGHm;=FU<%X*i?Q8yL`$dU853Ia=;hN*?4QMKQ#4*!s2>3AcjtgW2(|L_srg}9?+LV9&0c0I)eU>kokL94{k + + + diff --git a/public/searchIcons/zhihu.svg b/public/searchIcons/zhihu.svg new file mode 100644 index 0000000..3dea3ad --- /dev/null +++ b/public/searchIcons/zhihu.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..1ad1271 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,41 @@ + + + + diff --git a/src/GlobalModal.tsx b/src/GlobalModal.tsx new file mode 100644 index 0000000..cde59aa --- /dev/null +++ b/src/GlobalModal.tsx @@ -0,0 +1,80 @@ +import useRouterStore, { type RouteStr } from '@/useRouterStore' +import { computed, defineComponent, ref, Transition, watch } from 'vue' +import { OhVueIcon, addIcons } from 'oh-vue-icons' +import { MdClose, MdOpeninfull, MdClosefullscreen } from 'oh-vue-icons/icons' +import asyncLoader from './utils/asyncLoader' +addIcons(MdClose, MdOpeninfull, MdClosefullscreen) +const SearchPage = asyncLoader(() => import('@/layout/header/search/SearchPage')) + +const noFullList: RouteStr[] = ['global-search'] + +export default defineComponent(() => { + const router = useRouterStore() + const show = computed( + () => + router.path.startsWith('widget-') || + router.path === 'global-search' || + router.path === 'global-adder' + ) + const full = ref(false) + watch(router, () => { + full.value = false + }) + return () => ( +
+ {/* 背景遮罩 */} + + {show.value && ( +
{ + router.path = '' + }} + >
+ )} +
+ {/* 弹框主体 */} + + {show.value && ( +
+ {/* 关闭按钮 */} +
{ + router.path = '' + }} + > + +
+ {/* 全屏按钮 */} + {noFullList.includes(router.path) ? null : ( +
{ + full.value = !full.value + }} + > + +
+ )} + +
+ {router.path === 'global-search' ? : null} +
+
+ )} +
+
+ ) +}) diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..06871f7 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,10 @@ +export const aIUrl = 'https://metaso.cn/?s=uitab&referrer_s=uitab&q=' +export const translateUrl = 'https://fanyi.baidu.com/mtpe-individual/multimodal?lang=zh2en&query=' +// 获取 ossKey 地址 +export const ossKeyUrl = import.meta.env.PROD + ? 'http://192.168.110.28:8300/ossKey' + : 'http://192.168.110.28:8300/ossKey' +// 获取 oss 根目录地址 +export const ossBase = import.meta.env.PROD + ? 'http://btab.oss-cn-hangzhou.aliyuncs.com' + : 'http://btab.oss-cn-hangzhou.aliyuncs.com' diff --git a/src/db.ts b/src/db.ts new file mode 100644 index 0000000..f58dae8 --- /dev/null +++ b/src/db.ts @@ -0,0 +1,45 @@ +import { createInstance, INDEXEDDB } from 'localforage' +import { reactive, toRaw, watch, type WatchStopHandle } from 'vue' + +const db = createInstance({ + driver: INDEXEDDB, + name: 'fatfox', + version: 1.0, + storeName: 'fat_fox_key_value_pairs' +}) + +export function useForageStore( + name: string, + defaultData: T, + partialWrite: (res: T) => T = (res) => res +) { + const state = reactive(defaultData) + const writeWatch = () => + watch( + state, + (d) => { + db.setItem(name, partialWrite(toRaw(d) as T)) + }, + { deep: true } + ) + let stopWatch: WatchStopHandle = () => {} + const refresh = () => { + stopWatch() + state.loading = true + db.getItem<{ data: T }>(name).then((res) => { + if (res?.data) { + Object.assign(state, res.data) + } + state.loading = false + stopWatch = writeWatch() + }) + } + refresh() + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + refresh() + } + }) +} + +export default db diff --git a/src/layout/background/BackgroundPage.tsx b/src/layout/background/BackgroundPage.tsx new file mode 100644 index 0000000..7d5765d --- /dev/null +++ b/src/layout/background/BackgroundPage.tsx @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue' + +export default defineComponent(() => { + return () =>
this is background
+}) diff --git a/src/layout/background/index.tsx b/src/layout/background/index.tsx new file mode 100644 index 0000000..0c55939 --- /dev/null +++ b/src/layout/background/index.tsx @@ -0,0 +1,22 @@ +import { defineComponent } from 'vue' +import useBackgroundStore from './useBackgroundStore' + +export default defineComponent({ + setup() { + const background = useBackgroundStore() + return () => ( +
+ {background.resource.type === 'image' ? ( +
+ ) : ( +
+ ) + } +}) diff --git a/src/layout/background/useBackgroundStore.ts b/src/layout/background/useBackgroundStore.ts new file mode 100644 index 0000000..760a97c --- /dev/null +++ b/src/layout/background/useBackgroundStore.ts @@ -0,0 +1,34 @@ +import { defineStore } from 'pinia' +import { reactive, ref, watch } from 'vue' + +export default defineStore('background', () => { + const tag = ref(localStorage.getItem('backgroundTag') || '') + const resource = reactive({ + type: 'image', + brief: '', + url: '' + }) + const getResource = (tag: string) => { + // 默认壁纸 + if (!tag) { + return { + type: 'image', + brief: + 'https://aihlp.com.cn/admin/wallpaper/508d3994-3727-4839-bfe9-41b97ccf4fba_66a3272c874d41e5b9ec7562fe5822cd (1).jpg?x-oss-process=image/resize,w_400,h_225', + url: 'https://aihlp.com.cn/admin/wallpaper/508d3994-3727-4839-bfe9-41b97ccf4fba_66a3272c874d41e5b9ec7562fe5822cd (1).jpg' + } + } + } + watch( + tag, + (val) => { + localStorage.setItem('backgroundTag', val) + Object.assign(resource, getResource(val)) + }, + { immediate: true } + ) + return { + tag, + resource + } +}) diff --git a/src/layout/header/GlobalTime.tsx b/src/layout/header/GlobalTime.tsx new file mode 100644 index 0000000..a24b96b --- /dev/null +++ b/src/layout/header/GlobalTime.tsx @@ -0,0 +1,52 @@ +import useSettingsStore from '@/settings/useSettingsStore' +import useTimeStore from '@/utils/useTimeStore' +import { Lunar } from 'lunar-typescript' +import { computed, defineComponent, Transition } from 'vue' + +export default defineComponent({ + setup() { + const time = useTimeStore() + const text = computed(() => { + const h = time.date.getHours() + const hour = h < 10 ? '0' + h : h + const m = time.date.getMinutes() + const minute = m < 10 ? '0' + m : m + const s = time.date.getSeconds() + const second = s < 10 ? '0' + s : s + const dateStr = `${time.date.getMonth() + 1}月${time.date.getDate()}日` + return { + timeStr: `${hour}:${minute}:${second}`, + dateStr + } + }) + const info = computed(() => { + const l = Lunar.fromDate(time.date) + const day = l.getMonthInChinese() + '月' + l.getDayInChinese() + const dayWeek = l.getWeekInChinese() + return { + day, + dayWeek + } + }) + + const settings = useSettingsStore() + return () => ( +
+ + {settings.state.showDate && ( +
{text.value.timeStr}
+ )} +
+ + {settings.state.showTime && ( +
+
{text.value.dateStr}
+
星期{info.value.dayWeek}
+
{info.value.day}
+
+ )} +
+
+ ) + } +}) diff --git a/src/layout/header/index.tsx b/src/layout/header/index.tsx new file mode 100644 index 0000000..51dbded --- /dev/null +++ b/src/layout/header/index.tsx @@ -0,0 +1,34 @@ +import { defineAsyncComponent, defineComponent } from 'vue' +import Search from './search' + +const GlobalTime = defineAsyncComponent({ + loader: () => import('./GlobalTime') +}) + +export default defineComponent({ + setup() { + return () => ( + <> +
+ +
+ +
+ +
+ + ) + } +}) diff --git a/src/layout/header/search/SearchConfig.tsx b/src/layout/header/search/SearchConfig.tsx new file mode 100644 index 0000000..d8cc7d1 --- /dev/null +++ b/src/layout/header/search/SearchConfig.tsx @@ -0,0 +1,65 @@ +import { defineComponent } from 'vue' +import useSearchConfigStore from './useSearchConfigStore' +import useSearchStore from './useSearchStore' +import { OhVueIcon, addIcons } from 'oh-vue-icons' +import { FaPlus } from 'oh-vue-icons/icons' +import useRouterStore from '@/useRouterStore' + +addIcons(FaPlus) + +export default defineComponent({ + setup() { + const search = useSearchStore() + const searchConfig = useSearchConfigStore() + const router = useRouterStore() + return () => ( +
{ + search.showSearchConfig = false + }} + > + {searchConfig.defaultList + .concat(searchConfig.customList) + .filter((el) => el.show) + .map((item) => ( +
+
{ + searchConfig.current = { ...item } + search.showSearchConfig = false + }} + > +
+
+
+ {item.name} +
+
+ ))} +
+
{ + search.showSearchConfig = false + router.path = 'global-search' + }} + > + +
+
+
+ ) + } +}) diff --git a/src/layout/header/search/SearchHistory.tsx b/src/layout/header/search/SearchHistory.tsx new file mode 100644 index 0000000..0f00e30 --- /dev/null +++ b/src/layout/header/search/SearchHistory.tsx @@ -0,0 +1,40 @@ +import { defineComponent } from 'vue' +import { OhVueIcon, addIcons } from 'oh-vue-icons' +import { MdHistory, MdRemove } from 'oh-vue-icons/icons' +import useSearchConfigStore from './useSearchConfigStore' +import jump from '@/utils/jump' +addIcons(MdHistory) +addIcons(MdRemove) +export default defineComponent(() => { + const searchConfig = useSearchConfigStore() + return () => ( +
+ {searchConfig.history.map((item, idx) => ( +
{ + jump(searchConfig.current.url + item) + }} + > +
+ +
+ {item} +
+
+
{ + e.preventDefault() + e.stopPropagation() + searchConfig.removeHistory(idx) + }} + > + +
+
+ ))} +
+ ) +}) diff --git a/src/layout/header/search/SearchPage.tsx b/src/layout/header/search/SearchPage.tsx new file mode 100644 index 0000000..906f9d4 --- /dev/null +++ b/src/layout/header/search/SearchPage.tsx @@ -0,0 +1,200 @@ +import { defineComponent, reactive, ref } from 'vue' +import zhCN from 'ant-design-vue/es/locale/zh_CN' +import { + Button, + Checkbox, + ConfigProvider, + Divider, + Form, + Input, + message, + Modal +} from 'ant-design-vue' +import useSearchConfigStore from './useSearchConfigStore' +import { EditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue' +import asyncLoader from '@/utils/asyncLoader' +const ImageUploader = asyncLoader(() => import('@/utils/ImageUploader')) +const SearchItem = defineComponent({ + props: { + icon: { + type: String, + default: '' + }, + name: { + type: String, + default: '' + }, + url: { + type: String, + default: '' + }, + editable: { + type: Boolean, + default: false + }, + modelValue: { + type: Boolean, + default: false + } + }, + emits: ['update:modelValue'], + setup(props, ctx) { + const searchConfig = useSearchConfigStore() + return () => ( +
{ + searchConfig.current = { + icon: props.icon, + name: props.name, + url: props.url, + show: true + } + }} + > +
+
+
+ {props.name}{' '} + {searchConfig.current.url === props.url && ( + (当前使用) + )} +
+
+
e.stopPropagation()}> + { + const checked = e.target.checked + if (!checked && props.url === searchConfig.current.url) { + message.warning('不可隐藏当前使用搜索引擎') + } else { + ctx.emit('update:modelValue', e.target.checked) + } + }} + /> + {props.editable && ( + <> + + + + )} +
+
+ ) + } +}) + +export default defineComponent(() => { + const searchConfig = useSearchConfigStore() + const showAdder = ref(false) + const addState = reactive({ + name: '', + url: '', + icon: '' + }) + return () => ( +
e.stopPropagation()} + > + +

管理搜索引擎

+
+ 默认 + 自定义 +
+ + +
{ + console.log(res) + }} + > + + + + + + + + + + + + +
+
+
+
+ ) +}) diff --git a/src/layout/header/search/SearchSuggestion.tsx b/src/layout/header/search/SearchSuggestion.tsx new file mode 100644 index 0000000..27a8e9b --- /dev/null +++ b/src/layout/header/search/SearchSuggestion.tsx @@ -0,0 +1,78 @@ +import { defineComponent } from 'vue' +import useSearchStore from './useSearchStore' +import jump from '@/utils/jump' +import useSearchConfigStore from './useSearchConfigStore' +import { OhVueIcon, addIcons } from 'oh-vue-icons' +import { MdSearch } from 'oh-vue-icons/icons' +import { aIUrl, translateUrl } from '@/config' + +addIcons(MdSearch) + +export const Item = defineComponent({ + props: { + icon: { type: Object, default: null }, + label: { type: String, default: '' }, + path: { type: String, default: '' }, + prefix: { type: String, default: '' }, + num: { type: Number, default: 0 }, + current: { type: Number, default: -1 } + }, + setup(props) { + const searchConfig = useSearchConfigStore() + const search = useSearchStore() + return () => ( +
{ + searchConfig.addHistory(props.path) + search.searchStr = '' + jump(props.prefix + props.path) + }} + > + {props.icon} +
+ {props.label + props.path} +
+
+ ) + } +}) + +export default defineComponent(() => { + const search = useSearchStore() + const searchConfig = useSearchConfigStore() + + return () => ( +
+ } + current={search.current} + num={0} + /> + } + current={search.current} + num={1} + /> + {search.sugList.map((el, idx) => ( + } + current={search.current} + num={idx + 2} + /> + ))} +
+ ) +}) diff --git a/src/layout/header/search/index.tsx b/src/layout/header/search/index.tsx new file mode 100644 index 0000000..b7c3431 --- /dev/null +++ b/src/layout/header/search/index.tsx @@ -0,0 +1,59 @@ +import useSettingsStore from '@/settings/useSettingsStore' +import { defineComponent, Transition } from 'vue' +import useSearchStore from './useSearchStore' +import useSearchConfigStore from './useSearchConfigStore' +import SearchConfig from './SearchConfig' +import SearchHistory from './SearchHistory' +import SearchSuggestion from './SearchSuggestion' + +export default defineComponent(() => { + const settings = useSettingsStore() + const search = useSearchStore() + const searchConfig = useSearchConfigStore() + return () => ( +
+
+
{ + search.showSearchConfig = true + }} + > +
+
+
+
+ (search.searchRef = el as any)} + onContextmenu={(e) => e.stopPropagation()} + class="flex-1 h-full outline-none bg-transparent placeholder:text-slate-600 placeholder:tracking-widest text-slate-800 pr-4" + placeholder={`输入搜索 ${searchConfig.current.name}`} + /> +
+ {search.showSearchConfig && } + + {search.focus && !search.searchStr && searchConfig.history.length > 0 && } + + + {search.focus && search.searchStr && } + +
+ ) +}) diff --git a/src/layout/header/search/useSearchConfigStore.ts b/src/layout/header/search/useSearchConfigStore.ts new file mode 100644 index 0000000..211edca --- /dev/null +++ b/src/layout/header/search/useSearchConfigStore.ts @@ -0,0 +1,113 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export interface SearchInfo { + name: string + icon: string + url: string + show: boolean +} + +const defaultSearchList: SearchInfo[] = [ + { + name: '百度', + url: 'https://www.baidu.com/s?wd=', + icon: 'searchIcons/baidu.svg', + show: true + }, + { + name: '必应', + url: 'https://cn.bing.com/search?q=', + icon: 'searchIcons/bing.svg', + show: true + }, + { + name: '谷歌', + url: 'https://www.google.com/search?q=', + icon: 'searchIcons/google.svg', + show: true + }, + { + name: '360', + url: 'https://www.so.com/s?q=', + icon: 'searchIcons/360.svg', + show: true + }, + { + name: '搜狗', + url: 'https://www.sogou.com/sogou?&query=', + icon: 'searchIcons/sougou.svg', + show: true + } +] +const defaultCustomSearchList: SearchInfo[] = [ + { + name: '知乎', + url: 'https://www.zhihu.com/search?type=content&q=', + icon: 'searchIcons/zhihu.svg', + show: true + }, + { + name: 'GitHub', + url: 'https://github.com/search?q=', + icon: 'searchIcons/GitHub.svg', + show: true + }, + { + name: 'F搜', + url: 'https://fsoufsou.com/search?q=', + icon: 'searchIcons/F.svg', + show: true + }, + { + name: '豆瓣', + url: 'https://www.douban.com/search?q=', + icon: 'searchIcons/douban.svg', + show: true + }, + { + name: 'Yandex', + url: 'https://yandex.com/search/?text=', + icon: 'searchIcons/yandex.svg', + show: true + }, + { + name: '开发者', + url: 'https://kaifa.baidu.com/searchPage?wd=', + icon: 'searchIcons/kaifa.svg', + show: true + }, + { + name: 'B站', + url: 'https://search.bilibili.com/all?keyword=', + icon: 'searchIcons/bilibili.svg', + show: true + } +] +export default defineStore( + 'searchConfig', + () => { + const current = ref({ ...defaultSearchList[0] }) + const defaultList = ref(defaultSearchList) + const customList = ref(defaultCustomSearchList) + const history = ref([]) + const addHistory = (str: string) => { + history.value.unshift(str) + if (history.value.length > 10) { + history.value.pop() + } + } + const removeHistory = (idx: number) => { + history.value.splice(idx, 1) + } + return { + current, + customList, + defaultList, + history, + addHistory, + removeHistory + } + }, + { persist: true } +) diff --git a/src/layout/header/search/useSearchStore.ts b/src/layout/header/search/useSearchStore.ts new file mode 100644 index 0000000..d84c9c7 --- /dev/null +++ b/src/layout/header/search/useSearchStore.ts @@ -0,0 +1,99 @@ +import { defineStore } from 'pinia' +import { ref, watch } from 'vue' +import useSearchConfigStore from './useSearchConfigStore' +import jump from '@/utils/jump' +import { aIUrl, translateUrl } from '@/config' + +export default defineStore('search', () => { + const searchRef = ref(null) + const focus = ref(false) + watch(searchRef, (node, _, onCleanup) => { + if (!node) return + const handleFocus = () => { + focus.value = true + } + const handleBlur = () => { + focus.value = false + } + node.addEventListener('focus', handleFocus) + node.addEventListener('blur', handleBlur) + onCleanup(() => { + node.removeEventListener('focus', handleFocus) + node.removeEventListener('blur', handleBlur) + }) + }) + const showSearchConfig = ref(false) + const searchStr = ref('') + const current = ref(-1) + const sugList = ref([]) + watch( + searchStr, + (val) => { + if (!val) return + fetch( + `${import.meta.env.PROD ? 'https://suggestion.baidu.com/su' : '/baiduSuggestion'}?wd=${val}&ie=utf-8&p=3&cb=j` + ) + .then((res) => res.text()) + .then((res: string) => { + const list = res.match(/(?<=s:\[").*(?=\]\})/g)?.[0]?.split('","') + if (list) { + sugList.value = list + } + }) as Promise + }, + { + immediate: true + } + ) + const searchConfig = useSearchConfigStore() + const handle = (e: KeyboardEvent) => { + const n = sugList.value.length + if (e.key === 'ArrowDown') { + e.preventDefault() + current.value = Math.min(current.value + 1, n + 1) + } else if (e.key === 'ArrowUp') { + e.preventDefault() + current.value = Math.max(current.value - 1, 0) + } else if (e.key === 'Enter') { + e.preventDefault() + if (current.value < 0 || current.value > sugList.value.length + 2) { + // 直接回车搜索 + const str = searchStr.value + searchConfig.addHistory(str) + searchStr.value = '' + jump(searchConfig.current.url + str) + return + } + if (current.value <= 1) { + // ai 或 翻译 + searchConfig.addHistory(searchStr.value) + searchStr.value = '' + jump((current.value === 0 ? aIUrl : translateUrl) + searchStr.value) + return + } + const item = sugList.value[current.value - 2] + if (item) { + // 其他提示项 + searchConfig.addHistory(item) + searchStr.value = '' + jump(searchConfig.current.url + item) + } + } + } + watch(searchStr, (val, _, onCleanup) => { + if (!val || !searchRef.value) return + searchRef.value.addEventListener('keydown', handle) + onCleanup(() => { + current.value = -1 + searchRef.value?.removeEventListener('keydown', handle) + }) + }) + return { + searchStr, + searchRef, + focus, + showSearchConfig, + current, + sugList + } +}) diff --git a/src/layout/layout.types.ts b/src/layout/layout.types.ts new file mode 100644 index 0000000..5065080 --- /dev/null +++ b/src/layout/layout.types.ts @@ -0,0 +1,45 @@ +export enum BlockType { + Widget, + Folder, + Link +} + +export interface Block { + link: string // '' 代表小组件,id:xxx 代表文件夹,其他代表链接 + // 内容标注,小组件表示展示何种小组件 + name: string + // 标签,展示在图标下放 + label: string + // 图标 + icon: string + // 图标中的文字 + text: string + // 背景颜色 + background: string + // 文字颜色 + color: string + // 其他信息 + extra?: any +} + +export type LayoutPages = { list: Block[]; icon: string; label: string }[] + +export interface Layout { + content: [LayoutPages, LayoutPages, LayoutPages] + current: 0 | 1 | 2 // 游戏,工作,轻娱 + currentPage: number + dir: { [key: string]: Block[] } + dock: { + q: Block | null + w: Block | null + e: Block | null + r: Block | null + a: Block | null + s: Block | null + d: Block | null + f: Block | null + b: Block | null + } + simple: boolean + loading: boolean +} diff --git a/src/layout/useLayoutStore.ts b/src/layout/useLayoutStore.ts new file mode 100644 index 0000000..307acb6 --- /dev/null +++ b/src/layout/useLayoutStore.ts @@ -0,0 +1,30 @@ +import { useForageStore } from '@/db' +import { defineStore } from 'pinia' +import type { Layout } from './layout.types' + +const defaultLayout: Layout = { + content: [[], [], []], + current: 0, + currentPage: 0, + dir: {}, + dock: { + q: null, + w: null, + e: null, + r: null, + a: null, + s: null, + d: null, + f: null, + b: null + }, + simple: false, + loading: true +} + +export default defineStore('layout', () => { + const state = useForageStore('layout', defaultLayout) + return { + state + } +}) diff --git a/src/layout/utils.ts b/src/layout/utils.ts new file mode 100644 index 0000000..7c6a0e1 --- /dev/null +++ b/src/layout/utils.ts @@ -0,0 +1,14 @@ +import { BlockType, type Block } from './layout.types' + +/** + * 检查 block 类型 + * + * @export + * @param {Block} b + * @return {*} + */ +export function checkBlock(b: Block) { + if (!b.link) return BlockType.Widget + if (b.link.startsWith('id:')) return BlockType.Folder + return BlockType.Link +} diff --git a/src/main.css b/src/main.css new file mode 100644 index 0000000..00fd587 --- /dev/null +++ b/src/main.css @@ -0,0 +1,127 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-family: + -apple-system, + BlinkMacSystemFont, + Helvetica Neue, + PingFang SC, + Microsoft YaHei, + Source Han Sans SC, + Noto Sans CJK SC, + WenQuanYi Micro Hei, + sans-serif; +} +body { + /* ! 全局禁用鼠标选择,需要在其他位置放开 */ + user-select: none; +} +@keyframes loading { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} +.loading { + animation: loading 1s ease-in-out infinite; +} + +/* 修改 antd 样式 */ +.ant-btn .anticon { + position: relative; + top: -3px; +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background-color: transparent; +} + +::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +/* 背景之上阴影文字 */ +.shadow-text { + text-shadow: 0 0 0.3em rgba(0, 0, 0, 0.3); +} +.shadow-content { + backdrop-filter: blur(20px); + box-shadow: 0 0 1em 0 rgba(0, 0, 0, 0.2); +} + +/* 默认动画 */ +.v-enter-active, +.v-leave-active { + transition: opacity 0.3s ease-in-out; +} + +.v-enter-from, +.v-leave-to { + opacity: 0; +} + +/* 搜索框动画 */ +.searchContent-enter-active, +.searchContent-leave-active { + transition: + transform 0.3s ease-in-out, + opacity 0.3s ease-in-out; +} + +.searchContent-enter-from { + transform: translateY(90%); + opacity: 0; +} +.searchContent-leave-to { + transform: translateY(110%); + opacity: 0; +} + +/* 弹框内容动画 */ +.modal-enter-active, +.modal-leave-active { + transition: + transform 0.3s ease-in-out, + opacity 0.3s ease-in-out; +} + +.modal-enter-from { + transform: translate(-50%, -60%); + opacity: 0; +} +.modal-leave-to { + transform: translate(-50%, -40%); + opacity: 0; +} + +/* 设置框动画 */ +.settings-enter-active, +.settings-leave-active { + transform-origin: left bottom; + transition: + transform 0.3s ease-in-out, + bottom 0.3s ease-in-out, + opacity 0.3s ease-in-out; +} + +.settings-enter-from, +.settings-leave-to { + bottom: 0; + opacity: 0; + transform: scale(0.4); +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..e65afbc --- /dev/null +++ b/src/main.ts @@ -0,0 +1,22 @@ +import './main.css' +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import persist from 'pinia-plugin-persistedstate' + +import App from './App.vue' +import getFp from './utils/getFp' +import vOutsideClick from './utils/vOutsideClick' +import dayjs from 'dayjs' +import 'dayjs/locale/zh-cn' +dayjs.locale('zh-cn') + +const app = createApp(App) + +// ! persist 利用 localstorage,请不要在大量数据时使用 +// 大量数据(扩张内容,文件),清直接使用 ./db.ts +app.use(createPinia().use(persist)) +app.directive('outside-click', vOutsideClick) +getFp().then((fp) => { + console.info('fp:', fp) + app.mount('#app') +}) diff --git a/src/settings/SettingsButton.tsx b/src/settings/SettingsButton.tsx new file mode 100644 index 0000000..2d47141 --- /dev/null +++ b/src/settings/SettingsButton.tsx @@ -0,0 +1,19 @@ +import { defineComponent } from 'vue' +import { OhVueIcon, addIcons } from 'oh-vue-icons' +import { MdSettings } from 'oh-vue-icons/icons' +import useRouterStore from '@/useRouterStore' +addIcons(MdSettings) +export default defineComponent(() => { + const router = useRouterStore() + return () => ( +
{ + router.path = 'settings-background' + }} + > + +
+ ) +}) diff --git a/src/settings/SettingsOverlay.tsx b/src/settings/SettingsOverlay.tsx new file mode 100644 index 0000000..de3c0ba --- /dev/null +++ b/src/settings/SettingsOverlay.tsx @@ -0,0 +1,91 @@ +import useRouterStore from '@/useRouterStore' +import asyncLoader from '@/utils/asyncLoader' +import { computed, defineComponent, Transition } from 'vue' + +const ProfilePage = asyncLoader(() => import('@/user/UserPage')) +const BackgroundPage = asyncLoader(() => import('@/layout/background/BackgroundPage')) + +const SettingsTab = defineComponent({ + props: { + label: { + type: String, + required: true + }, + path: { + type: String, + required: true + } + }, + setup(props) { + const router = useRouterStore() + return () => ( +
{ + router.path = props.path as any + }} + > + {props.label} +
+ ) + } +}) + +export default defineComponent(() => { + const router = useRouterStore() + const show = computed(() => router.path.startsWith('settings-')) + return () => ( +
+ {/* 背景遮罩 */} + {show.value && ( +
{ + router.path = '' + }} + >
+ )} + {/* 弹框主体 */} + + {show.value && ( +
+
+
{ + router.path = 'settings-user' + }} + > +
+
+ + + + + + + + + +
+
+ + {router.path === 'settings-user' ? ( + + ) : router.path === 'settings-background' ? ( + + ) : null} + +
+
+ )} +
+
+ ) +}) diff --git a/src/settings/useSettingsStore.ts b/src/settings/useSettingsStore.ts new file mode 100644 index 0000000..68f5363 --- /dev/null +++ b/src/settings/useSettingsStore.ts @@ -0,0 +1,31 @@ +import { defineStore } from 'pinia' +import { computed, reactive } from 'vue' + +export type VisibleState = 'show' | 'auto' | '' + +export default defineStore( + 'settings', + () => { + const state = reactive({ + backgroundTag: '', + // 显示隐藏 + showSider: 'show' as VisibleState, + showDock: 'show' as VisibleState, + showPet: 'show' as VisibleState, + showDate: true, + showTime: true, + // 尺寸 + blockSize: 6, + blockPadding: 1, + mainWidth: 70, + blockRadius: 1, + // 搜索 + searchWidth: 30, + searchRadius: 24 + }) + return { state, blockInner: computed(() => state.blockSize - 2 * state.blockPadding) } + }, + { + persist: true + } +) diff --git a/src/useRouterStore.ts b/src/useRouterStore.ts new file mode 100644 index 0000000..47a3ee9 --- /dev/null +++ b/src/useRouterStore.ts @@ -0,0 +1,24 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export type WidgetStr = 'ai' | 'calendar' +export type GlobalStr = 'search' | 'block' | 'adder' +export type SettingStr = + | 'user' + | 'background' + | 'block' + | 'search' + | 'time' + | 'sider' + | 'ai' + | 'dock' + | 'reset' + | 'fallback' +export type RouteStr = '' | `widget-${WidgetStr}` | `global-${GlobalStr}` | `settings-${SettingStr}` + +export default defineStore('router', () => { + const path = ref('') + return { + path + } +}) diff --git a/src/user/UserPage.tsx b/src/user/UserPage.tsx new file mode 100644 index 0000000..7d8820f --- /dev/null +++ b/src/user/UserPage.tsx @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue' + +export default defineComponent(() => () => ( +
this is user
+)) diff --git a/src/user/useUserStore.ts b/src/user/useUserStore.ts new file mode 100644 index 0000000..0351b4f --- /dev/null +++ b/src/user/useUserStore.ts @@ -0,0 +1,5 @@ +import { defineStore } from 'pinia' + +export default defineStore('user', () => { + return {} +}) diff --git a/src/utils/ImageUploader.tsx b/src/utils/ImageUploader.tsx new file mode 100644 index 0000000..31991ec --- /dev/null +++ b/src/utils/ImageUploader.tsx @@ -0,0 +1,72 @@ +import { defineComponent } from 'vue' +import { OhVueIcon, addIcons } from 'oh-vue-icons' +import { MdUpload } from 'oh-vue-icons/icons' +import { message } from 'ant-design-vue' +import upload from './upload' + +addIcons(MdUpload) + +// !清 asyncLoader 加载使用 +export default defineComponent({ + props: { + ratio: { + type: Number, + default: 1 + }, + width: { + type: Number, + default: 64 + }, + value: { + type: String, + default: '' + }, + size: { + type: Number, + default: 4 + } + }, + emits: { + 'update:value': (() => true) as (val: string) => boolean + }, + setup(props, ctx) { + let input: HTMLInputElement | null = null + return () => ( +
+ (input = el as any)} + onChange={(e) => { + const file: File | undefined = (e.target as any).files?.[0] + if (!file) return + console.log(file.size, props.size) + if (file.size > props.size * 1000 * 1000) { + message.warn(`大小不得超过${props.size}mb`) + return + } + upload(file, 'test').then((res) => { + console.log(res) + ctx.emit('update:value', res) + }) + }} + /> +
{ + input?.click() + }} + > + {!props.value && } +
+
支持上传 .png, .jpeg, .jpg, .svg
+
+ ) + } +}) diff --git a/src/utils/asyncLoader.tsx b/src/utils/asyncLoader.tsx new file mode 100644 index 0000000..bbdf01e --- /dev/null +++ b/src/utils/asyncLoader.tsx @@ -0,0 +1,26 @@ +import { + type AsyncComponentLoader, + type Component, + defineAsyncComponent, + defineComponent +} from 'vue' +import { OhVueIcon, addIcons } from 'oh-vue-icons' +import { FaSpinner } from 'oh-vue-icons/icons' +addIcons(FaSpinner) +const BasicLoading = defineComponent(() => () => ( +
+
+ +
+
+)) + +export default function asyncLoader( + loader: AsyncComponentLoader, + loading?: Component +) { + return defineAsyncComponent({ + loader, + loadingComponent: loading || BasicLoading + }) +} diff --git a/src/utils/getFp.ts b/src/utils/getFp.ts new file mode 100644 index 0000000..b9ee318 --- /dev/null +++ b/src/utils/getFp.ts @@ -0,0 +1,16 @@ +import { load } from '@fingerprintjs/fingerprintjs' + +export default function getFp() { + const storedFp = localStorage.getItem('fp') + if (storedFp) { + return Promise.resolve(storedFp) + } else { + return load() + .then((fp) => fp.get()) + .then((res) => { + const fp = res.visitorId + localStorage.setItem('fp', fp) + return fp + }) + } +} diff --git a/src/utils/jump.ts b/src/utils/jump.ts new file mode 100644 index 0000000..7c5454e --- /dev/null +++ b/src/utils/jump.ts @@ -0,0 +1,75 @@ +import db from '@/db' + +interface AdverLink { + id: string + tag: string + adverLink: string +} +type AdverParams = Omit & { + adverParams: string +} + +type AdverData = { + links: AdverLink[] + params: AdverParams[] + expiration: number +} + +const fetchAdverConfig = async () => { + return Promise.allSettled([ + fetch('https://api.iyuntab.com/adverLink/params').then((res) => res.json()), + fetch('https://api.iyuntab.com/adverLink/link').then((res) => res.json()) + ]).then((res) => { + const result: AdverData = { links: [], params: [], expiration: Date.now() + 1000 * 60 * 60 * 4 } + if (res[0].status === 'fulfilled') { + result.params = res[0].value + } + if (res[1].status === 'fulfilled') { + result.links = res[1].value + } + return db.setItem('adverInfo', result).then(() => result) + }) +} +export function getAdverConfig() { + return db + .getItem<{ links: AdverLink[]; params: AdverParams[]; expiration: number }>('adverInfo') + .then((res) => { + if (!res || res.expiration < Date.now()) { + return fetchAdverConfig() + } + return Promise.resolve(res) + }) +} + +async function checkWithAdver(_url: string) { + try { + const config = await getAdverConfig() + const tag = _url.match(/(?<=http(s?):\/\/).*/g)?.[0] || _url + for (const item of config.params) { + if (tag.startsWith(item.tag)) { + const params = new URLSearchParams(item.adverParams) + const origin = new URLSearchParams(_url.includes('?') ? _url : _url + '?') + for (const key of params.keys()) { + const value = params.get(key) + if (value) { + origin.set(key, value) + } + } + return decodeURIComponent(origin.toString()) + } + } + for (const item of config.links) { + if (item.tag.startsWith(tag)) { + return item.adverLink + } + } + return _url + } catch (_) { + return _url + } +} +export default async function jump(_url: string) { + const url = _url.startsWith('http') ? _url : `https://${_url}` + const used = await checkWithAdver(url) + window.open(used, '_blank') +} diff --git a/src/utils/upload.ts b/src/utils/upload.ts new file mode 100644 index 0000000..10945d2 --- /dev/null +++ b/src/utils/upload.ts @@ -0,0 +1,40 @@ +import { ossBase, ossKeyUrl } from './../config' +import OSS from 'ali-oss' +import { v4 as uuid } from 'uuid' +export default async function upload(file: File, root: string) { + const config = await fetch(ossKeyUrl, { + method: 'POST', + headers: { + Authorization: + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NjlkZjA3Y2RhMjkyZTNiZTc0OGM5MmMifQ.9k9-C_Im2r2fONfT6rdAZDxapkqtwGtiVBSen59JDXY' + } + }).then( + (res) => + res.json() as Promise<{ + region: string + accessKeyId: string + accessKeySecret: string + securityToken: string + bucket: string + path: string + }> + ) + const ext = file.name.split('.').pop() + const path = `${config.path}/${root}/${uuid()}.${ext}` + const client = new OSS({ + region: config.region, + accessKeyId: config.accessKeyId, + accessKeySecret: config.accessKeySecret, + stsToken: config.securityToken, + bucket: config.bucket + }) + console.log(path) + const { name } = await client.put(path, file, { + mime: file.type, + headers: { + 'Content-Type': file.type + } + }) + console.log(name) + return ossBase + '/' + name +} diff --git a/src/utils/useTimeStore.ts b/src/utils/useTimeStore.ts new file mode 100644 index 0000000..7bf8b49 --- /dev/null +++ b/src/utils/useTimeStore.ts @@ -0,0 +1,20 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' + +const useTimeStore = defineStore('time', () => { + const date = ref(new Date()) + // 1 秒更新一次 + let d = 0 + const refresh = (dt: number) => { + const _d = Math.trunc(dt / 100) + if (_d !== d) { + d = _d + date.value = new Date() + } + requestAnimationFrame(refresh) + } + requestAnimationFrame(refresh) + return { date } +}) + +export default useTimeStore diff --git a/src/utils/vOutsideClick.ts b/src/utils/vOutsideClick.ts new file mode 100644 index 0000000..5ed5b8b --- /dev/null +++ b/src/utils/vOutsideClick.ts @@ -0,0 +1,24 @@ +import { type Directive } from 'vue' + +const mapping = new WeakMap void>() + +// 确认 absolute 内容层级 +export default { + mounted(el, binding) { + setTimeout(() => { + const handle = (e: Event) => { + if (el.contains(e.target as Node)) return + binding.value() + } + mapping.set(el, handle) + document.addEventListener('click', handle) + document.addEventListener('contextmenu', handle) + }, 0) + }, + beforeUnmount(el) { + const handle = mapping.get(el) + if (!handle) return + document.removeEventListener('click', handle) + document.removeEventListener('contextmenu', handle) + } +} as Directive void> diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..8367a3b --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,5 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], + darkMode: 'selector' +} diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..e14c754 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..f094063 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node20/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*" + ], + "compilerOptions": { + "composite": true, + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..34e547f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,42 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import vueDevTools from 'vite-plugin-vue-devtools' +import { visualizer } from 'rollup-plugin-visualizer' + +export default defineConfig({ + plugins: [vue(), vueJsx(), vueDevTools(), visualizer()], + server: { + host: '0.0.0.0', + port: 8100, + proxy: { + '/baiduSuggestion': { + target: 'https://suggestion.baidu.com', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/baiduSuggestion/, '/su') + } + } + }, + preview: { + host: '0.0.0.0', + port: 8200 + }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + build: { + // 禁用内联资源 + assetsInlineLimit: 0, + minify: 'terser', + terserOptions: { + compress: { + drop_debugger: true, + drop_console: ['log'] as any + } + } + } +})