谢亚昕 3 months ago
commit
7f4415cf0e
  1. 3
      .gitignore
  2. 24
      .prettierrc
  3. 12
      internal/tsconfig/package.json
  4. 17
      internal/tsconfig/tsconfig.json
  5. 38
      package.json
  6. 135
      packages/.vitepress/config.ts
  7. 182
      packages/.vitepress/getSideBar.ts
  8. 62
      packages/.vitepress/modules.ts
  9. 86
      packages/.vitepress/plugins/markdownTransform.ts
  10. 19
      packages/.vitepress/theme/index.js
  11. 117
      packages/.vitepress/theme/page404.vue
  12. 29
      packages/.vitepress/theme/useLangs.js
  13. 13
      packages/browser/global.d.ts
  14. 19
      packages/browser/package.json
  15. 45
      packages/browser/src/check/docs/demo.vue
  16. 10
      packages/browser/src/check/docs/index.md
  17. 77
      packages/browser/src/check/ie.ts
  18. 56
      packages/browser/src/check/index.ts
  19. 30
      packages/browser/src/event/__tests__/index.ts
  20. 31
      packages/browser/src/event/docs/demo.vue
  21. 20
      packages/browser/src/event/docs/index.md
  22. 29
      packages/browser/src/event/index.ts
  23. 3
      packages/browser/src/index.ts
  24. 7
      packages/browser/src/readme.md
  25. 15
      packages/browser/src/scrollTo/docs/demo.vue
  26. 11
      packages/browser/src/scrollTo/docs/index.md
  27. 69
      packages/browser/src/scrollTo/index.ts
  28. 8
      packages/browser/tsconfig.json
  29. 1
      packages/core/global.d.ts
  30. 14
      packages/core/package.json
  31. 29
      packages/core/src/date/format/docs/demo.vue
  32. 9
      packages/core/src/date/format/docs/index.md
  33. 25
      packages/core/src/date/format/index.ts
  34. 13
      packages/core/src/date/friendly/docs/demo.vue
  35. 9
      packages/core/src/date/friendly/docs/index.md
  36. 57
      packages/core/src/date/friendly/index.ts
  37. 3
      packages/core/src/date/index.ts
  38. 10
      packages/core/src/date/parse/docs/demo.vue
  39. 9
      packages/core/src/date/parse/docs/index.md
  40. 9
      packages/core/src/date/parse/index.ts
  41. 1
      packages/core/src/index.ts
  42. 9
      packages/core/src/readme.md
  43. 7
      packages/core/tsconfig.json
  44. 11
      packages/guide/introduction.md
  45. 49
      packages/index.md
  46. 12
      packages/node/package.json
  47. 0
      packages/node/src/index.ts
  48. 7
      packages/node/tsconfig.json
  49. 12
      packages/shared/package.json
  50. 28
      packages/shared/src/index.ts
  51. 7
      packages/shared/tsconfig.json
  52. 0
      packages/vue3/global.d.ts
  53. 24
      packages/vue3/package.json
  54. 3
      packages/vue3/src/hook/createContextComponent/docs/ThemeContext.ts
  55. 32
      packages/vue3/src/hook/createContextComponent/docs/demo.vue
  56. 13
      packages/vue3/src/hook/createContextComponent/docs/index.md
  57. 35
      packages/vue3/src/hook/createContextComponent/index.ts
  58. 83
      packages/vue3/src/hook/echarts/docs/demo.vue
  59. 5
      packages/vue3/src/hook/echarts/docs/index.md
  60. 69
      packages/vue3/src/hook/echarts/index.ts
  61. 4
      packages/vue3/src/hook/index.ts
  62. 12
      packages/vue3/src/hook/useCallDelay/docs/demo.vue
  63. 7
      packages/vue3/src/hook/useCallDelay/docs/index.md
  64. 76
      packages/vue3/src/hook/useCallDelay/index.ts
  65. 7
      packages/vue3/src/hook/useCherry/docs/demo.vue
  66. 5
      packages/vue3/src/hook/useCherry/docs/index.md
  67. 3
      packages/vue3/src/hook/useCherry/index.ts
  68. 7
      packages/vue3/src/hook/useCherry/type.ts
  69. 28
      packages/vue3/src/hook/useCherry/useInjector.ts
  70. 15
      packages/vue3/src/hook/useCherry/useProvider.ts
  71. 10
      packages/vue3/src/hook/useCherry/useProviders.ts
  72. 1
      packages/vue3/src/index.ts
  73. 7
      packages/vue3/src/readme.md
  74. 7
      packages/vue3/tsconfig.json
  75. 12
      packages/xyx-utils/package.json
  76. 8
      packages/xyx-utils/src/README.md
  77. 0
      packages/xyx-utils/src/index.ts
  78. 7
      packages/xyx-utils/tsconfig.json
  79. 4691
      pnpm-lock.yaml
  80. 5
      pnpm-workspace.yaml
  81. BIN
      public/favicon.png
  82. 21
      readme.md
  83. 33
      scripts/build-one.mts
  84. 19
      scripts/build.mts
  85. 24
      vitest.config.ts

3
.gitignore

@ -0,0 +1,3 @@
node_modules
dist
packages/\.vitepress/cache/*

24
.prettierrc

@ -0,0 +1,24 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"singleAttributePerLine": false,
"bracketSameLine": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 120,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 4,
"trailingComma": "es5",
"useTabs": false,
"embeddedLanguageFormatting": "auto",
"vueIndentScriptAndStyle": false,
"experimentalTernaries": false,
"parser": "typescript"
}

12
internal/tsconfig/package.json

@ -0,0 +1,12 @@
{
"name": "tsconfig",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

17
internal/tsconfig/tsconfig.json

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "ES6",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"types": ["vitest/globals"],
"paths": {
"@xyx-utils/vue3": ["../../packages/vue3/src"],
"@xyx-utils/shared": ["../../packages/shared/src"],
"@xyx-utils/browser": ["../../packages/browser/src"],
"@xyx-utils/core": ["../../packages/core/src"],
"@xyx-utils/node": ["../../packages/node/src"],
"xyx-utils": ["../../packages/xyx-utils/src"]
}
}
}

38
package.json

@ -0,0 +1,38 @@
{
"private": true,
"type": "module",
"name": "xyx-utils",
"version": "1.0.0",
"description": "",
"scripts": {
"build": "tsx scripts/build.mts",
"build-one": "tsx scripts/build-one.mts build",
"dev": "tsx scripts/build-one.mts dev",
"docs:dev": "vitepress dev packages",
"docs:build": "vitepress build packages",
"docs:serve": "vitepress serve packages",
"test": "vitest",
"coverage": "vitest run --coverage"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^22.10.5",
"@vitepress-demo-preview/component": "^2.3.2",
"@vitepress-demo-preview/plugin": "^1.2.3",
"@vueuse/core": ">=10.0.0",
"fast-glob": "^3.3.3",
"fs-extra": "^11.2.0",
"gray-matter": "^4.0.3",
"jsdom": "^26.0.0",
"lodash-es": "^4.17.21",
"naive-ui": "^2.41.0",
"tsconfig": "workspace: *",
"tsx": "^4.19.2",
"unbuild": "^3.2.0",
"vitepress": "^1.5.0",
"vitest": "^2.1.8",
"vue": "^3.5.13"
}
}

135
packages/.vitepress/config.ts

@ -0,0 +1,135 @@
import { containerPreview, componentPreview } from "@vitepress-demo-preview/plugin";
import path, { resolve } from "path";
import { defineConfig } from "vitepress";
import { getSidebar, getNav } from "./modules";
import { MarkdownTransform } from "./plugins/markdownTransform";
let oldSidebar: string;
// let isRestarting: boolean = false;
const src = "src"
const userConfig = defineConfig({
lang: "zh-CN",
// lastUpdated: true,
head: [["link", { rel: "icon", href: "/favicon.png" }]],
title: "XYX UTILS",
description: "为自己构建适合的工具函数",
themeConfig: {
siteTitle: "XYX-UTILS",
footer: {
message: "Released under the MIT License.",
copyright: `Copyright © ${new Date().getFullYear()}-present NPMRUN`,
},
socialLinks: [{ icon: "github", link: "https://github.com/npmrun/xyx-utils" }],
// https://juejin.cn/post/7227358177489961018#heading-5
// sidebar,
},
markdown: {
theme: {
light: "vitesse-light",
dark: "vitesse-dark",
},
config(md) {
md.use(containerPreview);
md.use(componentPreview);
},
},
vite: {
resolve: {
alias: {
"@xyx-utils/vue3": resolve(process.cwd(), "packages/vue3/" + src),
"@xyx-utils/shared": resolve(process.cwd(), "packages/shared/" + src),
"@xyx-utils/browser": resolve(process.cwd(), "packages/browser/" + src),
"@xyx-utils/core": resolve(process.cwd(), "packages/core/" + src),
"@xyx-utils/node": resolve(process.cwd(), "packages/node/" + src),
"xyx-utils": resolve(process.cwd(), "packages/xyx-utils/" + src),
},
},
server: {
port: 1234,
},
publicDir: path.resolve(__dirname, "../../public"),
plugins: [
MarkdownTransform(),
{
name: "refresh-tree",
enforce: "post",
config(config) {
let curSidebar = getSidebar();
// @ts-ignore
config.vitepress.site.themeConfig.sidebar = curSidebar;
// @ts-ignore
config.vitepress.site.themeConfig.nav = getNav();
oldSidebar = JSON.stringify(curSidebar);
return config;
},
async handleHotUpdate(ctx) {
const { file, read, server, modules } = ctx;
if (file.endsWith(".md")) {
let curSidebar = getSidebar();
if (JSON.stringify(curSidebar) !== oldSidebar) {
if (userConfig.themeConfig) {
userConfig.themeConfig.sidebar = curSidebar;
oldSidebar = JSON.stringify(curSidebar);
}
server.moduleGraph.onFileChange("/@siteData");
}
// const mod = server.moduleGraph.getModuleById(
// '/@siteData'
// )
// if (!mod) return
// if (userConfig.themeConfig) {
// let curSidebar = getSidebar()
// if (JSON.stringify(curSidebar) !== oldSidebar) {
// userConfig.themeConfig.sidebar = curSidebar
// // server.ws.send({
// // type: 'custom',
// // event: '/@siteData',
// // data: {
// // default: userConfig
// // }
// // })
// server.ws.send({
// type: 'update',
// updates: [
// {
// acceptedPath: mod.url,
// path: mod.url,
// timestamp: Date.now(),
// type: 'js-update'
// }
// ]
// })
// }
}
},
configureServer(server) {
// const { moduleGraph, watcher, ws, restart } = server
// function reload() {
// let curSidebar = getSidebar()
// if (JSON.stringify(curSidebar) !== oldSidebar) {
// console.log("侧边栏更新");
// if (isRestarting) {
// return
// }
// isRestarting = true
// restart().then(() => {
// setTimeout(() => {
// isRestarting = false
// }, 0);
// })
// }
// }
// watcher
// .add(["**/*.md"])
// .on('add', reload)
// .on('change', reload)
// .on('unlink', reload)
},
},
],
},
});
export default userConfig;

182
packages/.vitepress/getSideBar.ts

@ -0,0 +1,182 @@
import startCase from 'lodash-es/startCase';
import sortBy from 'lodash-es/sortBy';
import remove from 'lodash-es/remove';
import path, { sep } from 'path';
import glob from 'fast-glob';
import fs from 'fs-extra';
import grayMatter from "gray-matter";
type Sidebar = SidebarGroup[] | SidebarMulti;
interface SidebarMulti {
[path: string]: SidebarGroup[]
}
interface SidebarGroup {
text: string
items: SidebarItem[]
collapsible?: boolean
collapsed?: boolean
}
interface SidebarItem {
text: string
link: string
}
interface Options {
startsDirs?: Array<string>, // Directoty path to ignore from being captured.
ignoreDirectory?: Array<string>, // Directoty path to ignore from being captured.
ignoreMDFiles?: Array<string>, // File path to ignore from being captured.
}
// handle md file name
const getName = (path: string) => {
let name = path.split(sep).pop() || path;
const argsIndex = name.lastIndexOf('--');
if (argsIndex > -1) {
name = name.substring(0, argsIndex);
}
// "001.guide" or "001-guide" or "001_guide" or "001 guide" -> "guide"
name = name.replace(/^\d+[.\-_ ]?/, '');
return startCase(name);
};
// handle dir name
const getDirName = (path: string) => {
let name = path.split(sep).shift() || path;
name = name.replace(/^\d+[.\-_ ]?/, '');
return startCase(name);
};
// Load all MD files in a specified directory
const getChildren = function (parentPath: string, ignoreMDFiles: Array<string> = []) {
const pattern = '/**/*.md';
const files = glob.sync(parentPath + pattern, {ignore: ["**/node_modules/**", "**/dist/**"]}).map((path) => {
let end = -3
const newPath = path.slice(parentPath.length + 1, end);
if (ignoreMDFiles?.length && ignoreMDFiles.findIndex(item => newPath.endsWith(item)) !== -1) {
return undefined;
}
return { path: newPath };
});
remove(files, file => file === undefined);
// Return the ordered list of files, sort by 'path'
return sortBy(files, ['path']).map(file => file?.path || '');
};
// Return sidebar config for given baseDir.
function side(baseDir: string, options?: Options) {
const mdFiles = getChildren(baseDir, options?.ignoreMDFiles);
const sidebars: Sidebar = [];
const dirs = options?.startsDirs ?? []
mdFiles.forEach((item) => {
if (options?.ignoreDirectory?.length
&& options?.ignoreDirectory.findIndex(one => item.includes(one)) !== -1) {
return;
}
let index = dirs.findIndex(text => item.startsWith(text))
if (index != -1) {
let curDir = dirs[index]
const filePath = path.resolve(baseDir, item)
let p = filePath + ".md"
const {
data: { title, first, name },
} = grayMatter(fs.readFileSync(p, "utf8"));
const [pkg, _name, i] = item.split('/').slice(-3)
let _title = title ?? _name
// @ts-ignore
const sidebarItemIndex = sidebars.findIndex(sidebar => sidebar._realtext === curDir);
if (sidebarItemIndex !== -1) {
if (first!=undefined) {
sidebars[sidebarItemIndex].items.splice(first,0,{
text: _title,
link: '/' + item.replace('index', ''),
})
if (name) {
sidebars[sidebarItemIndex].text = name
}
}else{
sidebars[sidebarItemIndex].items.push({
text: _title,
link: '/' + item.replace('index', ''),
});
}
sidebars[sidebarItemIndex].items.sort((a,b)=>{
// @ts-ignore
return a._sort - b._sort
})
} else {
sidebars.push({
text: name || curDir,
// @ts-ignore
_sort: index,
// @ts-ignore
_realtext: curDir,
items: [{
text: _title,
link: '/' + item.replace('index', ''),
}],
});
}
}
})
sidebars.sort((a,b)=>{
// @ts-ignore
return a._sort - b._sort
})
// const sidebars: Sidebar = [];
// // strip number of folder's name
// mdFiles.forEach((item) => {
// const dirName = getDirName(item);
// if (options?.ignoreDirectory?.length
// && options?.ignoreDirectory.findIndex(one => item.includes(one)) !== -1) {
// return;
// }
// const mdFileName = getName(item);
// const filePath = path.resolve(baseDir, item)
// const {
// data: { title },
// content,
// } = grayMatter(fs.readFileSync(filePath+".md", "utf8"));
// console.log(item);
// const sidebarItemIndex = sidebars.findIndex(sidebar => sidebar.text === mdFileName);
// if (sidebarItemIndex !== -1) {
// sidebars[sidebarItemIndex].items.push({
// text: title ?? mdFileName,
// link: '/'+item,
// });
// } else {
// sidebars.push({
// text: dirName,
// items: [{
// text: title ?? mdFileName,
// link: '/'+item,
// }],
// });
// }
// });
// console.info('sidebar is create:', JSON.stringify(sidebars));
return sidebars;
}
/**
* Returns `sidebar` configuration for VitePress calculated using structure of directory and files in given path.
* @param {String} rootDir - Directory to get configuration for.
* @param {Options} options - Option to create configuration.
*/
export const getSideBar = (rootDir = './', options?: Options) => side(rootDir, options);

62
packages/.vitepress/modules.ts

@ -0,0 +1,62 @@
import { getSideBar } from "./getSideBar";
function getTree(name: string[]) {
const result: any[] = [
{
text: "<- 总目录",
link: "/guide/introduction",
},
];
result.push(
...(getSideBar("./packages", {
startsDirs: name,
ignoreMDFiles: ["CHANGELOG"],
ignoreDirectory: ["node_modules", "dist"],
}) ?? [])
);
return result;
}
export const getNav = () => [
// {
// text: "naive-ui",
// link: "https://www.naiveui.com/",
// },
];
export const getAllModule = () => [
{
text: "工具模块",
items: [
{
text: "通用",
link: "/core/src/readme",
},
{
text: "浏览器",
link: "/browser/src/readme",
},
{
text: "node",
link: "/node/src/readme",
},
],
},
{
text: 'vue3',
link: '/vue3/src/readme',
},
];
export const getSidebar = () => ({
"/guide/": [
{
text: "总目录",
items: getAllModule(),
},
],
"/browser/": getTree(["browser/src"]),
"/node/": getTree(["node/src"]),
"/core/": getTree(["core/src"]),
"/vue3/": getTree(["vue3/src"]),
});

86
packages/.vitepress/plugins/markdownTransform.ts

@ -0,0 +1,86 @@
// https://github.com/vueuse/vueuse/blob/main/packages/.vitepress/plugins/markdownTransform.ts
import path from "path";
import fs from "fs-extra";
import fg from "fast-glob";
import grayMatter from "gray-matter";
export function MarkdownTransform(): any {
return {
name: 'niu-tools-md-transform',
enforce: 'pre',
async transform(code, id) {
if (!id.match(/\.md\b/))
return null
const [pkg, _name, i] = id.split('/').slice(-3)
const oneDir = path.parse(id).dir.endsWith("docs") ? path.parse(path.parse(id).dir).dir : path.parse(id).dir
const oneName = oneDir.split("/").slice(-1)
const allFiles = fg.sync('**/*.ts', { cwd: oneDir, ignore: ["**/*.test.ts", "docs", "test", "**/__tests__/**/*"] })
if (_name !== "packages" && i === "index.md") {
const matter = grayMatter(fs.readFileSync(id, "utf8"));
const { data, content } = matter
if (data.category) {
code = grayMatter.stringify(`${data.category ? `分类:\`${data.category}\`` : ''}` + content, data)
}
let source = ""
let rawcodeArray = (Array.from(code.matchAll(/<\!--code\:(.*?)\:code-->/g) ?? []) as any).map(([_, name]) => name)
for (let i = 0; i < allFiles.length; i++) {
const file = allFiles[i];
const p = path.resolve(oneDir, file)
let name = `${oneName}/${file}`
let str = ''
if (fs.pathExistsSync(p)) {
let rawcode = fs.readFileSync(p, "utf8")
if (rawcodeArray.length) {
for (let i = 0; i < rawcodeArray.length; i++) {
const symbol = rawcodeArray[i];
let startLen = `//${symbol}===== Start`.length
let startIndex = rawcode.indexOf(`//${symbol}===== Start`)
let endIndex = rawcode.indexOf(`//${symbol}===== End`)
// console.log(`<\!--code\:${symbol}\:code-->`);
if (startIndex !== -1 && endIndex !== -1) {
code = code.replace(`<\!--code\:${symbol}\:code-->`, `:::: details ${symbol}源码\n\`\`\`ts` + rawcode.slice(startIndex + startLen, endIndex) + "\`\`\`\n ::::")
}
}
}
str = `::: details ${name.replace(/\\/g, "/")}源码 \n\`\`\`ts\ ${rawcode}\n\`\`\` \n:::\n`
}
code = code.replace('$' + name + '$', str)
source += str
}
if (source) code += `\n ## 源码 \n :::: details 查看源码 \n ${source} \n :::: \n`
}
// if (!i.startsWith("index.md")) {
// const frontmatterEnds = code.indexOf('---\n\n')
// const firstHeader = code.search(/\n#{2,6}\s.+/)
// const sliceIndex = firstHeader < 0 ? frontmatterEnds < 0 ? 0 : frontmatterEnds + 4 : firstHeader
// code = code.slice(0, sliceIndex) + "\n# " + _name + "\n" + code.slice(sliceIndex)
// console.log(code, frontmatterEnds);
// code = code
// .replace(/(# \w+?)\n/, `$1\n\n<div>aaa</div>\n`)
// .replace(/## (Components?(?:\sUsage)?)/i, '## $1\n<LearnMoreComponents />\n\n')
// .replace(/## (Directives?(?:\sUsage)?)/i, '## $1\n<LearnMoreDirectives />\n\n')
// code += `\n# 哈哈`
// }
return code
},
}
}
// [...`fsd
// /**-----start:main-----**/
// sadada
// /**-----end:mai1n-----**/
// sadasdad
// /**-----start:test-----**/
// sadada
// /**-----end:test-----**/
// sadasdad`.matchAll(/\/\*\*-----start:(.*?)-----\*\*\/\n(.*?)\n\/\*\*-----end:(.*?)-----\*\*\//g)]

19
packages/.vitepress/theme/index.js

@ -0,0 +1,19 @@
import DefaultTheme from 'vitepress/theme'
import { NaiveUIContainer } from '@vitepress-demo-preview/component'
import '@vitepress-demo-preview/component/dist/style.css'
import naive from 'naive-ui'
import page404 from './page404.vue'
import { h } from 'vue'
export default {
...DefaultTheme,
enhanceApp ({ app, router, siteData }) {
app.use(naive)
app.component('demo-preview', NaiveUIContainer)
},
Layout () {
return h(DefaultTheme.Layout, null, {
'not-found': () => h(page404)
})
}
}

117
packages/.vitepress/theme/page404.vue

@ -0,0 +1,117 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { withBase, useData } from 'vitepress'
import { useLangs } from './useLangs'
const { site, theme, lang } = useData()
const { localeLinks } = useLangs({ removeCurrent: false })
const root = ref('/')
onMounted(() => {
const path = window.location.pathname
.replace(site.value.base, '')
.replace(/(^.*?\/).*$/, '/$1')
if (localeLinks.value.length) {
root.value =
localeLinks.value.find(({ link }) => link.startsWith(path))?.link ||
localeLinks.value[0].link
}
})
</script>
<template>
<div class="NotFound">
<p class="code">{{ theme.notFound?.code ?? '404' }}</p>
<h1 class="title">{{ theme.notFound?.title ?? '页面未找到' }}</h1>
<div class="divider" />
<blockquote class="quote">
{{
theme.notFound?.quote ??
"生大材,不遇其时,其势定衰。生平庸,不化其势,其性定弱。"
}}
</blockquote>
<div class="action">
<a
class="link"
href="javascript:history.go(-1)"
:aria-label="'前往上一页'"
>
{{ '前往上一页' }}
</a>
<a
class="link"
:href="withBase(root)"
:aria-label="theme.notFound?.linkLabel ?? '回到最初'"
>
{{ theme.notFound?.linkText ?? '回到最初' }}
</a>
</div>
</div>
</template>
<style scoped>
.NotFound {
padding: 64px 24px 96px;
text-align: center;
}
@media (min-width: 768px) {
.NotFound {
padding: 96px 32px 168px;
}
}
.code {
line-height: 64px;
font-size: 64px;
font-weight: 600;
}
.title {
padding-top: 12px;
letter-spacing: 2px;
line-height: 20px;
font-size: 20px;
font-weight: 700;
}
.divider {
margin: 24px auto 18px;
width: 64px;
height: 1px;
background-color: var(--vp-c-divider);
}
.quote {
margin: 0 auto;
max-width: 256px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
}
.action {
padding-top: 20px;
display: flex;
justify-content: center;
gap: 12px;
}
.link {
display: inline-block;
border: 1px solid var(--vp-c-brand-1);
border-radius: 16px;
padding: 3px 16px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand-1);
transition: border-color 0.25s, color 0.25s;
}
.link:hover {
border-color: var(--vp-c-brand-2);
color: var(--vp-c-brand-2);
}
</style>

29
packages/.vitepress/theme/useLangs.js

@ -0,0 +1,29 @@
import { computed } from 'vue';
import { useData } from 'vitepress';
export function useLangs ({ removeCurrent = true, correspondingLink = false } = {}) {
const { site, localeIndex, page, theme } = useData();
const currentLang = computed(() => ({
label: site.value.locales[localeIndex.value]?.label,
link: site.value.locales[localeIndex.value]?.link ||
(localeIndex.value === 'root' ? '/' : `/${localeIndex.value}/`)
}));
const localeLinks = computed(() => Object.entries(site.value.locales).flatMap(([key, value]) => removeCurrent && currentLang.value.label === value.label
? []
: {
text: value.label,
link: normalizeLink(value.link || (key === 'root' ? '/' : `/${key}/`), theme.value.i18nRouting !== false && correspondingLink, page.value.relativePath.slice(currentLang.value.link.length - 1), !site.value.cleanUrls)
}));
return { localeLinks, currentLang };
}
function normalizeLink (link, addPath, path, addExt) {
return addPath
? link.replace(/\/$/, '') +
ensureStartingSlash(path
.replace(/(^|\/)index\.md$/, '$1')
.replace(/\.md$/, addExt ? '.html' : ''))
: link;
}
function ensureStartingSlash (path) {
return /^\//.test(path) ? path : `/${path}`;
}

13
packages/browser/global.d.ts

@ -0,0 +1,13 @@
declare const __DEV__: boolean;
declare const wx: any;
declare const plus: any;
interface Math {
easeInOutQuad: (t: any, b: any, c: any, d: any) => any;
}
interface Window {
webkitRequestAnimationFrame: ()=>void
mozRequestAnimationFrame: ()=>void
}

19
packages/browser/package.json

@ -0,0 +1,19 @@
{
"name": "@xyx-utils/browser",
"version": "1.0.0",
"description": "",
"main": "dist/index.mjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"files": [
"dist"
],
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@xyx-utils/core": "workspace: *",
"@xyx-utils/shared": "workspace: *"
}
}

45
packages/browser/src/check/docs/demo.vue

@ -0,0 +1,45 @@
<template>
<div>
<cop info="getExplorerInfo" :text="ExplorerInfo"></cop>
<cop info="getExplorerInfo" :text="IeInfo === -1 ? '这不是IE' : '这是IE' + IeInfo"></cop>
<cop info="isHtml5Plus" :text="html5PlusInfo ? '这是html5Plus环境' : '这不是html5Plus环境'"></cop>
<cop info="isWxMp" :text="wxMpInfo ? '这是微信小程序' : '这不是微信小程序'"></cop>
<cop info="isWeChat" :text="weChatInfo ? '这是微信浏览器' : '这不是微信浏览器'"></cop>
<cop info="isMobile" :text="mobileInfo ? '这是手机' : '这不是手机'"></cop>
<cop info="isWeCom" :text="isWeCom ? '这是企业微信' : '这不是企业微信'"></cop>
<cop info="isAlipay" :text="isAlipay ? '这是支付宝' : '这不是支付宝'"></cop>
<cop info="isDingTalk" :text="isDingTalk ? '这是钉钉' : '这不是钉钉'"></cop>
</div>
</template>
<script lang="ts" setup>
import { getExplorerInfo, IEVersion } from '@xyx-utils/browser/check'
import { isWeCom, isAlipay, isDingTalk, isHtml5Plus, isMobile, isWeChat, isWxMp } from '@xyx-utils/browser'
import { defineComponent, h, ref } from 'vue'
const cop = defineComponent({
props: ['info', 'text'],
setup(props: any, ctx) {
return () => h('div', [
h('code', [props.info]),
h('span', ["检测结果:" + props.text]),
])
},
})
const ExplorerInfo = getExplorerInfo()
const IeInfo = IEVersion()
const html5PlusInfo = isHtml5Plus()
const mobileInfo = isMobile()
const weChatInfo = isWeChat()
const wxMpInfo = ref<boolean>();
; (async () => {
try {
await isWxMp()
wxMpInfo.value = true
} catch (error) {
wxMpInfo.value = false
}
})();
</script>
<style lang="less" scoped></style>

10
packages/browser/src/check/docs/index.md

@ -0,0 +1,10 @@
---
title: 平台检测
---
这是一个平台信息检测工具
## Demo
<preview path="./demo.vue" title="@xyx-utils/browser" description="用于平台的判断"></preview>

77
packages/browser/src/check/ie.ts

@ -0,0 +1,77 @@
export function IEVersion() {
var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1; //判断是否IE<11浏览器
var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器
var isIE11 = userAgent.indexOf("Trident") > -1 && userAgent.indexOf("rv:11.0") > -1;
if (isIE) {
var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
reIE.test(userAgent);
var fIEVersion = parseFloat(RegExp["$1"]);
if (fIEVersion == 7) {
return 7;
} else if (fIEVersion == 8) {
return 8;
} else if (fIEVersion == 9) {
return 9;
} else if (fIEVersion == 10) {
return 10;
} else {
return 6; //IE版本<=7
}
} else if (isEdge) {
return "edge"; //edge
} else if (isIE11) {
return 11; //IE11
} else {
return -1; //不是ie浏览器
}
}
export function getExplorerInfo() {
//判断浏览器版本
var userAgent = navigator.userAgent;
var info;
var isOpera = userAgent.indexOf("Opera") > -1; //判断是否Opera浏览器
var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera; //判断是否IE浏览器
var isEdge = userAgent.toLowerCase().indexOf("edge") > -1 && !isIE; //判断是否IE的Edge浏览器
var isIE11 = userAgent.toLowerCase().indexOf("trident") > -1 && userAgent.indexOf("rv") > -1;
var tempArray: RegExpExecArray;
if (/[Ff]irefox(\/\d+\.\d+)/.test(userAgent)) {
tempArray = /([Ff]irefox)\/(\d+\.\d+)/.exec(userAgent) as RegExpExecArray;
info = tempArray[1] + tempArray[2];
} else if (isIE) {
var version = "";
var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
reIE.test(userAgent);
var fIEVersion = parseFloat(RegExp["$1"]);
if (fIEVersion == 7) {
version = "IE7";
} else if (fIEVersion == 8) {
version = "IE8";
} else if (fIEVersion == 9) {
version = "IE9";
} else if (fIEVersion == 10) {
version = "IE10";
} else {
version = "0";
}
info = version;
} else if (isEdge) {
info = "Edge";
} else if (isIE11) {
info = "IE11";
} else if (/[Cc]hrome\/\d+/.test(userAgent)) {
tempArray = /([Cc]hrome)\/(\d+)/.exec(userAgent) as RegExpExecArray;
info = tempArray[1] + tempArray[2];
} else if (/[Vv]ersion\/\d+\.\d+\.\d+(\.\d)* *[Ss]afari/.test(userAgent)) {
tempArray = /[Vv]ersion\/(\d+\.\d+\.\d+)(\.\d)* *([Ss]afari)/.exec(userAgent) as RegExpExecArray;
info = tempArray[3] + tempArray[1];
} else if (/[Oo]pera.+[Vv]ersion\/\d+\.\d+/.test(userAgent)) {
tempArray = /([Oo]pera).+[Vv]ersion\/(\d+)\.\d+/.exec(userAgent) as RegExpExecArray;
info = tempArray[1] + tempArray[2];
} else {
info = "unknown";
}
return info;
}

56
packages/browser/src/check/index.ts

@ -0,0 +1,56 @@
const UA = navigator.userAgent.toLowerCase();
export * from "./ie"
/** 是不是企业微信 */
export const isWeCom: boolean = Boolean(UA) && (UA as string).indexOf("wxwork") > 0;
/** 是不是支付宝 */
export const isAlipay: boolean = Boolean(UA) && (UA as string).indexOf("alipay") > 0;
/** 是不是钉钉 */
export const isDingTalk: boolean = Boolean(UA) && (UA as string).indexOf("dingtalk") > 0;
/**
*
*/
export function isWeChat() {
var ua = navigator.userAgent.toLowerCase();
if (ua && ua.match(/MicroMessenger/i)?.toString() == "micromessenger") {
return true;
} else {
return false;
}
}
export function isMobile() {
let flag = navigator.userAgent.match(
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
);
return flag;
}
export function isWxMp() {
return new Promise<number>((resolve, reject) => {
if (isWeChat()) {
wx.miniProgram.getEnv((res: any) => {
if (res.miniprogram) {
resolve(0);
} else {
reject(1);
}
});
} else {
reject(2);
}
});
}
export function isHtml5Plus() {
if (typeof plus === "object") {
return true;
} else {
return false;
}
}

30
packages/browser/src/event/__tests__/index.ts

@ -0,0 +1,30 @@
import { on, off } from "@xyx-utils/browser";
describe("测试 on 与 off", () => {
it("触发多次click事件", () => {
const mockListener = vi.fn();
const element = document.createElement("div");
on(element, "click", mockListener);
const event = new MouseEvent("click");
element.dispatchEvent(event);
element.dispatchEvent(event);
expect(mockListener).toBeCalledTimes(2);
});
it("仅触发一次click事件", () => {
const mockListener = vi.fn();
const element = document.createElement("div");
on(element, "click", mockListener);
const event = new MouseEvent("click");
element.dispatchEvent(event);
off(element, "click", mockListener);
element.dispatchEvent(event);
element.dispatchEvent(event);
expect(mockListener).toBeCalledTimes(1);
});
});

31
packages/browser/src/event/docs/demo.vue

@ -0,0 +1,31 @@
<template>
<div>
<n-space style="margin-bottom: 10px;">
<n-button @click="onBindEvent">绑定点击事件</n-button>
<n-button @click="onRemoveEvent">移除点击事件</n-button>
</n-space>
<n-tag><span ref="targetRef">{{ isBind ? '已绑定,点击测试' : '未绑定' }}</span></n-tag>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { on, off } from "@xyx-utils/browser/event";
const targetRef = ref<HTMLElement>();
const isBind = ref(false)
function clickNode() {
alert("click");
}
function onBindEvent() {
on(targetRef.value, "click", clickNode);
isBind.value = true
}
function onRemoveEvent() {
off(targetRef.value, "click", clickNode);
isBind.value = false
}
</script>
<style lang="less" scoped></style>

20
packages/browser/src/event/docs/index.md

@ -0,0 +1,20 @@
---
title: 事件绑定
---
## Demo
<preview path="./demo.vue" title="元素事件事件监听" description="监听与取消元素的原生事件"></preview>
## on
监听元素原生事件
<!--code:on:code-->
## off
取消监听事件
<!--code:off:code-->

29
packages/browser/src/event/index.ts

@ -0,0 +1,29 @@
//on===== Start
function on(ele: HTMLElement, event: keyof HTMLElementEventMap, fn: EventListenerOrEventListenerObject) {
if (ele.addEventListener) {
ele.addEventListener(event, fn, false);
// @ts-ignore
} else if (ele.attachEvent) {
// @ts-ignore
ele!.attachEvent("on" + event, fn);
} else {
ele["on" + event] = fn;
}
}
//on===== End
//off===== Start
function off(el: HTMLElement, event: keyof HTMLElementEventMap, fn: EventListenerOrEventListenerObject) {
if (el.removeEventListener) {
el.removeEventListener(event, fn, false);
// @ts-ignore
} else if (el.detachEvent) {
// @ts-ignore
el.detachEvent("on" + event, fn.bind(el));
} else {
el["on" + event] = null;
}
}
//off===== End
export { on, off };

3
packages/browser/src/index.ts

@ -0,0 +1,3 @@
export * from "./event";
export * from "./check";
export * from "./scrollTo";

7
packages/browser/src/readme.md

@ -0,0 +1,7 @@
---
title: 导览
first: 0
name: 浏览器
---
浏览器相关工具函数,主要包括元素的事件处理

15
packages/browser/src/scrollTo/docs/demo.vue

@ -0,0 +1,15 @@
<template>
<n-space style="position: fixed;top: 200px;">
<n-button @click="onBindEvent(1000)">点击滚动到1000位置</n-button>
<n-button @click="onBindEvent(0)">点击滚动到顶部</n-button>
</n-space>
</template>
<script lang="ts" setup>
import { scrollTo } from "@xyx-utils/browser/scrollTo";
function onBindEvent(height) {
scrollTo(height, 1000)
}
</script>
<style lang="less" scoped></style>

11
packages/browser/src/scrollTo/docs/index.md

@ -0,0 +1,11 @@
---
title: 滚动位置
---
## Demo
<preview path="./demo.vue" title="滚动位置" description="滚动到指定的位置"></preview>
## 2000px高的空文档
<div style="height: 2000px" />

69
packages/browser/src/scrollTo/index.ts

@ -0,0 +1,69 @@
Math.easeInOutQuad = function (t, b, c, d) {
t /= d / 2;
if (t < 1) {
return (c / 2) * t * t + b;
}
t--;
return (-c / 2) * (t * (t - 2) - 1) + b;
};
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
var requestAnimFrame = (function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
}
);
})();
/**
* Because it's so fucking difficult to detect the scrolling element, just move them all
* @param {number} amount
*/
function move(amount) {
document.documentElement.scrollTop = amount;
(document.body.parentNode as HTMLElement).scrollTop = amount;
document.body.scrollTop = amount;
}
function position() {
return (
document.documentElement.scrollTop ||
(document.body.parentNode as HTMLElement).scrollTop ||
document.body.scrollTop
);
}
/**
* @param {number} to
* @param {number} duration
* @param {Function} callback
*/
export function scrollTo(to: number, duration: number, callback?: Function) {
const start = position();
const change = to - start;
const increment = 20;
let currentTime = 0;
duration = typeof duration === "undefined" ? 500 : duration;
var animateScroll = function () {
// increment the time
currentTime += increment;
// find the value with the quadratic in-out easing function
var val = Math.easeInOutQuad(currentTime, start, change, duration);
// move the document.body
move(val);
// do the animation unless its over
if (currentTime < duration) {
requestAnimFrame(animateScroll);
} else {
if (callback && typeof callback === "function") {
// the animation is done so lets callback
callback();
}
}
};
animateScroll();
}

8
packages/browser/tsconfig.json

@ -0,0 +1,8 @@
{
"extends": "tsconfig/tsconfig.json",
"include": [
"src",
"docs",
"global.d.ts",
]
}

1
packages/core/global.d.ts

@ -0,0 +1 @@
declare const __DEV__: boolean;

14
packages/core/package.json

@ -0,0 +1,14 @@
{
"name": "@xyx-utils/core",
"version": "1.0.0",
"description": "",
"main": "dist/index.mjs",
"scripts": {
},
"files": [
"dist"
],
"keywords": [],
"author": "",
"license": "ISC"
}

29
packages/core/src/date/format/docs/demo.vue

@ -0,0 +1,29 @@
<template>
<div>
<div>yyyy-MM-dd hh:mm:ss ---> {{ date }}</div>
<div>yyyy-MM-dd ---> {{ date2 }}</div>
<div>hh:mm:ss ---> {{ date3 }}</div>
<div>h:m:s ---> {{ date4 }}</div>
</div>
</template>
<script lang="ts" setup>
import { formatDateByFmt } from "@xyx-utils/core/date/format";
import { onScopeDispose, ref } from "vue";
const date = ref(formatDateByFmt(new Date(), "yyyy-MM-dd hh:mm:ss"))
const date2 = ref(formatDateByFmt(new Date(), "yyyy-MM-dd"))
const date3 = ref(formatDateByFmt(new Date(), "hh:mm:ss"))
const date4 = ref(formatDateByFmt(new Date(), "h:m:s"))
const timeId = setInterval(() => {
date.value = formatDateByFmt(new Date(), "yyyy-MM-dd hh:mm:ss")
date2.value = formatDateByFmt(new Date(), "yyyy-MM-dd")
date3.value = formatDateByFmt(new Date(), "hh:mm:ss")
date4.value = formatDateByFmt(new Date(), "h:m:s")
}, 1000)
onScopeDispose(() => {
clearInterval(timeId)
})
</script>
<style lang="less" scoped></style>

9
packages/core/src/date/format/docs/index.md

@ -0,0 +1,9 @@
---
title: 日期->格式化
---
## Demo
<preview path="./demo.vue" title="时间" description="时间按指定格式输出"></preview>

25
packages/core/src/date/format/index.ts

@ -0,0 +1,25 @@
function padLeftZero(str) {
if (typeof str !== "string") return str;
// return ("00" + str).substr(str.length);
return str.padStart(2, "0");
}
export function formatDateByFmt(date, fmt) {
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
}
const o = {
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"h+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
};
for (const k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
const str = o[k] + "";
fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str));
}
}
return fmt;
}

13
packages/core/src/date/friendly/docs/demo.vue

@ -0,0 +1,13 @@
<template>
<div>
<div> {{ date }}</div>
</div>
</template>
<script lang="ts" setup>
import { formatTimeFriendly } from "@xyx-utils/core/date/friendly";
import { ref } from "vue";
const date = ref(formatTimeFriendly("2025-01-10 16:30:00", new Date()))
</script>
<style lang="less" scoped></style>

9
packages/core/src/date/friendly/docs/index.md

@ -0,0 +1,9 @@
---
title: 日期->美化
---
## Demo
<preview path="./demo.vue" title="时间" description="时间按指定格式输出"></preview>

57
packages/core/src/date/friendly/index.ts

@ -0,0 +1,57 @@
/**
*
* @param str yyyy-MM-dd HH:mm:ss
* @param now GMT时间undefined使
*/
export function formatTimeFriendly(str, now) {
var currentTime = new Date(now);
var arr = str.split(/\s+/gi);
var temp = 0,
arr1,
arr2,
oldTime,
delta;
var getIntValue = function (ss, defaultValue) {
try {
return parseInt(ss, 10);
} catch (e) {
return defaultValue;
}
};
var getWidthString = function (num) {
return num < 10 ? "0" + num : num;
};
if (arr.length >= 2) {
arr1 = arr[0].split(/[\/\-]/gi);
arr2 = arr[1].split(":");
oldTime = new Date();
oldTime.setYear(getIntValue(arr1[0], currentTime.getFullYear()));
oldTime.setMonth(getIntValue(arr1[1], currentTime.getMonth() + 1) - 1);
oldTime.setDate(getIntValue(arr1[2], currentTime.getDate()));
oldTime.setHours(getIntValue(arr2[0], currentTime.getHours()));
oldTime.setMinutes(getIntValue(arr2[1], currentTime.getMinutes()));
oldTime.setSeconds(getIntValue(arr2[2], currentTime.getSeconds()));
delta = currentTime.getTime() - oldTime.getTime();
if (delta <= 6000) {
return "1分钟内";
} else if (delta < 60 * 60 * 1000) {
return Math.floor(delta / (60 * 1000)) + "分钟前";
} else if (delta < 24 * 60 * 60 * 1000) {
return Math.floor(delta / (60 * 60 * 1000)) + "小时前";
} else if (delta < 3 * 24 * 60 * 60 * 1000) {
return Math.floor(delta / (24 * 60 * 60 * 1000)) + "天前";
} else if (currentTime.getFullYear() != oldTime.getFullYear()) {
return [
getWidthString(oldTime.getFullYear()),
getWidthString(oldTime.getMonth() + 1),
getWidthString(oldTime.getDate()),
].join("-");
} else {
return [getWidthString(oldTime.getMonth() + 1), getWidthString(oldTime.getDate())].join("-");
}
}
return "";
}

3
packages/core/src/date/index.ts

@ -0,0 +1,3 @@
export * from "./format"
export * from "./parse"
export * from "./friendly"

10
packages/core/src/date/parse/docs/demo.vue

@ -0,0 +1,10 @@
<template>
<div>
fdsf
</div>
</template>
<script lang="ts" setup>
import { onScopeDispose, ref } from "vue";
</script>
<style lang="less" scoped></style>

9
packages/core/src/date/parse/docs/index.md

@ -0,0 +1,9 @@
---
title: 日期->转化
---
## Demo
<preview path="./demo.vue" title="时间" description="时间按指定格式输出"></preview>

9
packages/core/src/date/parse/index.ts

@ -0,0 +1,9 @@
function padLeftZero(str) {
if(typeof str !== "string") return str
// return ("00" + str).substr(str.length);
return str.padStart(2, "0");
}
export function parse(date, fmt) {
}

1
packages/core/src/index.ts

@ -0,0 +1 @@
export * from "./date";

9
packages/core/src/readme.md

@ -0,0 +1,9 @@
---
title: 导览
first: 0
name: 通用
---
# @xyx-utils/core
asd

7
packages/core/tsconfig.json

@ -0,0 +1,7 @@
{
"extends": "tsconfig/tsconfig.json",
"include": [
"src",
"global.d.ts",
]
}

11
packages/guide/introduction.md

@ -0,0 +1,11 @@
---
title: 导览
first: true
name: 导览
---
# XYX UTILS
## 概述
该库主要用于自己的编程工具库,该库分为`core`,`browser`,`node`等针对一些平台的特殊工具方法。

49
packages/index.md

@ -0,0 +1,49 @@
---
layout: home
hero:
name: XYX UTILS
text: 自用工具库
tagline: 构建常用的函数,封装自己的解决方法。<br />未必是最好的,但是胜在全。
image:
src: /favicon.png
alt: VitePress
actions:
- theme: brand
text: 开始
link: /guide/introduction
- theme: alt
text: 查看GitHub
link: https://github.com/npmrun/xyx-utils
features:
- icon: ⚡️
title: vite
details: 快速开发,本地飞速。
- icon: 🖖
title: pure
details: 纯粹就是工具类,别的什么都都不提供。
- icon: 🛠️
title: utils
details: 通用工具库,针对所有情况,方便记录与挑选。
---
<style>
.VPFeatures.VPHomeFeatures .items .item{
width: 100%;
}
@media (min-width: 640px){
.VPFeatures.VPHomeFeatures .items .item{
width: calc(100% / 2);
}
}
@media (min-width: 768px){
.VPFeatures.VPHomeFeatures .items .item{
width: calc(100% / 3);
}
}
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe, #41d1ff);
}
</style>

12
packages/node/package.json

@ -0,0 +1,12 @@
{
"name": "@xyx-utils/node",
"version": "1.0.0",
"description": "",
"main": "dist/index.mjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

0
packages/node/src/index.ts

7
packages/node/tsconfig.json

@ -0,0 +1,7 @@
{
"extends": "tsconfig/tsconfig.json",
"include": [
"src",
"global.d.ts",
]
}

12
packages/shared/package.json

@ -0,0 +1,12 @@
{
"name": "@xyx-utils/shared",
"version": "1.0.0",
"description": "",
"main": "dist/index.mjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

28
packages/shared/src/index.ts

@ -0,0 +1,28 @@
/**
githb - Search
https://www.bing.com/search?pglt=43&q=githb&cvid=dda7fe6cf86a45a78b0f414fc4ed093a&aqs=edge..69i57j69i60j69i65.614j0j1&FORM=ANSPA1&PC=CNNDDB&mkt=zh-CN
npmrun/art-theme: Beautiful blog template with Astro.
https://github.com/npmrun/art-theme
vueuse/is.ts at main · vueuse/vueuse
https://github.com/vueuse/vueuse/blob/main/packages/shared/utils/is.ts
vueuse/vueuse · Discussions · GitHub
https://github.com/vueuse/vueuse/discussions
vitepress ReferenceError: navigator is not define - Search
https://www.bing.com/search?pglt=43&q=vitepress+ReferenceError%3A+navigator+is+not+define&cvid=e945f87f90b14affb3f3f0282262f14f&aqs=edge..69i57.2203j0j1&FORM=ANNTA1&PC=U531&mkt=zh-CN
Build: navigator is not defined · Issue #1690 · vuejs/vitepress
https://github.com/vuejs/vitepress/issues/1690
Code Tools | Code Tools
http://localhost:4173/core/browser/check/
vitepress渲染需要区分浏览器环境还是node环境
*/
export const isClient = typeof window !== 'undefined'
export const defaultWindow = /* #__PURE__ */ isClient ? window : undefined

7
packages/shared/tsconfig.json

@ -0,0 +1,7 @@
{
"extends": "tsconfig/tsconfig.json",
"include": [
"src",
"global.d.ts",
]
}

0
packages/vue3/global.d.ts

24
packages/vue3/package.json

@ -0,0 +1,24 @@
{
"type": "module",
"name": "@xyx-utils/vue3",
"version": "1.0.0",
"description": "",
"main": "dist/index.mjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"peerDependencies": {
"vue": ">=3.0.0"
},
"optionalDependencies": {
"@vueuse/core": ">=10.0.0",
"echarts": ">=4.0.0"
},
"devDependencies": {
"@types/echarts": "^4.9.22",
"echarts": "^4.9.0"
}
}

3
packages/vue3/src/hook/createContextComponent/docs/ThemeContext.ts

@ -0,0 +1,3 @@
import { createContextComponent } from '@xyx-utils/vue3';
export const ThemeContext = createContextComponent("light")

32
packages/vue3/src/hook/createContextComponent/docs/demo.vue

@ -0,0 +1,32 @@
<script lang="ts">
import { createContextComponent } from '@xyx-utils/vue3';
export const ThemeContext = createContextComponent("light")
</script>
<script setup lang="ts">
import { defineComponent, h, ref } from 'vue';
const Consumer = defineComponent({
setup() {
return () => h("div", {}, [h(ThemeContext.Consumer, {}, { default(theme) { return theme } })])
}
})
const theme = ref('light');
function change() {
if (theme.value === "light") {
theme.value = "dark"
} else {
theme.value = "light"
}
}
</script>
<template>
<n-button @click="change">change</n-button>
<ThemeContext.Provider :value="theme">
<Consumer></Consumer>
</ThemeContext.Provider>
</template>

13
packages/vue3/src/hook/createContextComponent/docs/index.md

@ -0,0 +1,13 @@
---
title: createContextComponent
category: Provide/Inject
---
<!-- https://juejin.cn/post/7249624871722221623?searchId=20230919163100C73475DBCFBAE86BA7DD#heading-17 -->
> 作者:JiangHong
> 链接:https://juejin.cn/post/7249624871722221623
> 来源:稀土掘金
> 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
<preview path="./demo.vue" title="@niu-tools/vue3" description="createContext"></preview>

35
packages/vue3/src/hook/createContextComponent/index.ts

@ -0,0 +1,35 @@
import { defineComponent, provide, computed, inject, type PropType, type ComputedRef } from 'vue';
export function createContextComponent<T>(defaultValue: T) {
const KEY = Symbol('CREATE_CONTEXT_KEY');
const Provider = defineComponent({
props: {
value: {
type: [Object, Number, String, Boolean, null, undefined, Function] as PropType<T>,
required: true,
},
},
setup(props, ctx) {
provide(
KEY,
computed(() => props.value || defaultValue),
);
return () => ctx.slots.default?.();
},
});
const useContext = () => inject<ComputedRef<T>>(KEY) || computed(() => defaultValue);
const Consumer = defineComponent({
setup(props, ctx) {
const value = useContext();
return () => ctx.slots.default?.(value.value);
},
});
return {
Provider,
Consumer,
useContext
};
}

83
packages/vue3/src/hook/echarts/docs/demo.vue

@ -0,0 +1,83 @@
<script setup lang="ts">
import { useEChart } from '@xyx-utils/vue3'
import { useResizeObserver } from '@vueuse/core'
import { onMounted, ref } from 'vue'
const data: number[][] = []
for (let i = 0; i <= 100; i++) {
let theta = (i / 100) * 360
let r = 5 * (1 + Math.sin((theta / 180) * Math.PI))
data.push([r, theta])
}
function getOptions(data) {
return {
title: {
text: 'Two Value-Axes in Polar',
},
legend: {
data: ['line'],
},
polar: {},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
},
},
angleAxis: {
type: 'value',
startAngle: 0,
},
radiusAxis: {},
series: [
{
coordinateSystem: 'polar',
name: 'line',
type: 'line',
data: data,
},
],
}
}
function handleClick(num: number) {
const data: number[][] = []
for (let i = 0; i <= num; i++) {
let theta = (i / 100) * 360
let r = 5 * (1 + Math.sin((theta / 180) * Math.PI))
data.push([r, theta])
}
setOption(getOptions(data))
}
const targetEl = ref<HTMLElement | null>(null)
const { init, clear, dispose, resize, setOption } = useEChart({
el: targetEl,
option: getOptions(data),
})
onMounted(() => {
useResizeObserver(targetEl, () => {
resize()
})
})
</script>
<template>
<div>
<n-space>
<n-button @click="handleClick(50)">50数据</n-button>
<n-button @click="handleClick(100)">100数据</n-button>
<n-button @click="clear">清除图表</n-button>
<n-button @click="dispose">销毁图表</n-button>
<n-button @click="init">初始化图表</n-button>
</n-space>
<div
style="height: 500px; resize: horizontal; overflow: auto"
ref="targetEl"
></div>
</div>
</template>

5
packages/vue3/src/hook/echarts/docs/index.md

@ -0,0 +1,5 @@
---
title: hook->useEchart
---
<preview path="./demo.vue" title="@xyx-utils/vue3" description="useEchart"></preview>

69
packages/vue3/src/hook/echarts/index.ts

@ -0,0 +1,69 @@
import { ref, Ref, unref } from "vue";
import * as echarts from "echarts";
import { useEventListener, tryOnMounted, tryOnBeforeUnmount } from "@vueuse/core";
export function useEChart(opts: { el: Ref<HTMLElement | null>; option?: any; cloneNode?: boolean }) {
const originOption = opts.option ?? {};
let myChart: ReturnType<typeof echarts.init> | null = null;
let pureEl: Node | undefined;
let curOption = ref(originOption);
const stop = useEventListener(window, "resize", () => {
if (!myChart) return;
myChart?.resize();
});
function clear() {
if (!myChart) return;
myChart?.clear();
}
function dispose() {
if (!myChart) return;
if (myChart.isDisposed()) return;
// curOption.value = originOption
myChart?.dispose();
if (pureEl) {
const el = unref(opts.el);
el?.replaceWith(pureEl);
opts.el.value = pureEl as any;
pureEl = undefined;
}
myChart = null;
}
function resize() {
myChart?.resize();
}
tryOnMounted(init);
tryOnBeforeUnmount(() => {
stop();
dispose();
myChart = null;
pureEl = undefined;
curOption.value = undefined;
});
function setOption(option: object | null = null) {
if (option) curOption.value = option;
myChart && myChart.setOption(curOption.value);
}
function init() {
if (myChart) return;
const el = unref(opts.el);
if (!el) {
return;
}
if (opts.cloneNode) {
pureEl = el.cloneNode(true);
}
myChart = echarts.init(el as HTMLDivElement);
setOption();
}
return {
init,
dispose,
clear,
resize,
setOption,
};
}

4
packages/vue3/src/hook/index.ts

@ -0,0 +1,4 @@
export * from "./echarts";
export * from "./useCallDelay";
export * from "./useCherry";
export * from "./createContextComponent";

12
packages/vue3/src/hook/useCallDelay/docs/demo.vue

@ -0,0 +1,12 @@
<template>
<div>{{text}}</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useCallDelay } from "@xyx-utils/vue3"
const text = ref("value")
useCallDelay(()=>{
text.value = "wait done"
}, 2000)
</script>

7
packages/vue3/src/hook/useCallDelay/docs/index.md

@ -0,0 +1,7 @@
---
title: hook->useCallDelay
---
该函数用于延迟调用,提供了可配置选项
<preview path="./demo.vue" title="基本使用" description="普通调用"></preview>

76
packages/vue3/src/hook/useCallDelay/index.ts

@ -0,0 +1,76 @@
import { computed, ref, watchEffect } from "vue";
interface IOptions{
lazy?: boolean
debounce?: boolean
throttle?: boolean
}
/**
*
* @param callback
* @param delay
* @param options
* @returns
*/
export function useCallDelay(callback: Function, delay: number = 200, options?: IOptions) {
let timeID = ref<ReturnType<typeof setTimeout>>()
let last: number
function cancel() {
if (timeID.value) {
clearTimeout(timeID.value)
timeID.value = undefined
}
}
function run(this: void, ...argu: any[]) {
if (options?.debounce) {
if (timeID.value) {
cancel()
}
timeID.value = setTimeout(() => {
timeID.value = undefined
callback.apply(this, argu)
}, delay)
}
if (options?.throttle) {
const now = +new Date()
if (last && now - last < delay) {
cancel()
timeID.value = setTimeout(() => {
timeID.value = undefined
last = now
callback.apply(this, argu)
}, delay)
} else {
last = now
callback.apply(this, argu)
}
}
if (!options?.throttle && !options?.debounce) {
timeID.value = setTimeout(() => {
timeID.value = undefined
callback.apply(this, argu)
}, delay)
}
}
watchEffect(onCleanup => {
if (!delay && delay !== 0) {
return
}
if (!options?.lazy) {
run()
}
onCleanup(cancel)
})
const isWaiting = computed(() => {
return !!timeID.value
})
return {
run,
cancel,
timeID,
isWaiting,
}
}

7
packages/vue3/src/hook/useCherry/docs/demo.vue

@ -0,0 +1,7 @@
<template>
<div>11</div>
</template>
<script setup lang="ts">
</script>

5
packages/vue3/src/hook/useCherry/docs/index.md

@ -0,0 +1,5 @@
---
title: hook->useCherry
---
asd

3
packages/vue3/src/hook/useCherry/index.ts

@ -0,0 +1,3 @@
export { useInjector } from "./useInjector"
export { useProvider } from "./useProvider"
export { useProviders } from "./useProviders"

7
packages/vue3/src/hook/useCherry/type.ts

@ -0,0 +1,7 @@
//定义一个用于状态共享的hook函数的标准接口
export interface FunctionalStore<T extends object> {
(...args: any[]): T;
token?: symbol;
root?: T;
}

28
packages/vue3/src/hook/useCherry/useInjector.ts

@ -0,0 +1,28 @@
//对原生inject进行封装
import { inject } from "vue";
import { FunctionalStore } from "./type";
type InjectType = 'root' | 'optional';
//接收第二个参数,'root'表示直接全局使用;optional表示可选注入,防止父组件的provide并未传入相关hook
export function useInjector<T extends object>(func: FunctionalStore<T>, type?: InjectType) {
const token = func.token!;
const root = func.root;
switch (type) {
case 'optional':
return (inject<T>(token) || func.root || null) as T;
case 'root':
if (!func.root) func.root = func();
return func.root as T;
default:
const data = inject<T>(token)
if (data) {
return data as T;
};
if (root) return func.root as T;
throw new Error(`状态钩子函数${func.name}未在上层组件通过调用useProvider提供`);
}
}

15
packages/vue3/src/hook/useCherry/useProvider.ts

@ -0,0 +1,15 @@
import { provide } from "vue";
import { FunctionalStore } from "./type";
//对原生provide进行封装
//由于inject函数只会从父组件开始查找,所以useProvider默认返回hook函数的调用结果,以防同组件层级需要使用
export function useProvider<T extends object>(func: FunctionalStore<T>): T {
!func.token && (func.token = Symbol('functional store'));
const depends = func();
provide(func.token, depends);
return depends;
}

10
packages/vue3/src/hook/useCherry/useProviders.ts

@ -0,0 +1,10 @@
import { provide } from "vue";
import { FunctionalStore } from "./type";
// 可以一次传入多个hook函数, 统一管理
export function useProviders(...funcs: FunctionalStore<any>[]) {
funcs.forEach(func => {
!func.token && (func.token = Symbol('functional store'));
provide(func.token, func());
});
}

1
packages/vue3/src/index.ts

@ -0,0 +1 @@
export * from "./hook"

7
packages/vue3/src/readme.md

@ -0,0 +1,7 @@
---
title: 导览
first: 0
name: vue3
---
vue3

7
packages/vue3/tsconfig.json

@ -0,0 +1,7 @@
{
"extends": "tsconfig/tsconfig.json",
"include": [
"src",
"global.d.ts",
]
}

12
packages/xyx-utils/package.json

@ -0,0 +1,12 @@
{
"name": "xyx-utils",
"version": "1.0.0",
"description": "",
"main": "dist/index.mjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

8
packages/xyx-utils/src/README.md

@ -0,0 +1,8 @@
---
title: 导览
first: 0
name: xyx-utils
---
asd

0
packages/xyx-utils/src/index.ts

7
packages/xyx-utils/tsconfig.json

@ -0,0 +1,7 @@
{
"extends": "tsconfig/tsconfig.json",
"include": [
"src",
"global.d.ts",
]
}

4691
pnpm-lock.yaml

File diff suppressed because it is too large

5
pnpm-workspace.yaml

@ -0,0 +1,5 @@
packages:
- 'internal/**/*'
- 'packages/**/*'
- '!packages/.vitepress'
- '!**/dist/**'

BIN
public/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

21
readme.md

@ -0,0 +1,21 @@
## 开发
**开发新模块**
1. 在`packages\.vitepress\config.ts`增加`startsDirs`和`alias`。
2. 在`internal\tsconfig\tsconfig.json`增加路径别名
3. 在`vitest.config.ts`修改别名
**本地调试工具**
- yalc
据说是本地最好的link方案,有没有吹牛自己试试。
**版本管理**
- changeset
可以尝试尝试

33
scripts/build-one.mts

@ -0,0 +1,33 @@
import { resolve } from "node:path";
import fg from "fast-glob";
import { build } from "unbuild";
const [mode, dir] = process.argv.slice(2);
if (dir) {
const isDev = mode === "dev";
buildOne(dir, isDev);
}
export function buildOne(dir: string, isDev: boolean = false) {
const rootDir = resolve("packages/" + dir);
const files = fg.sync(["src/**/*.ts"], { cwd: rootDir, ignore: ["**/__tests__/**/*", "**/docs/**/*"] });
return build(rootDir, false, {
rootDir: rootDir,
entries: files,
declaration: !isDev,
replace: {
__DEV__: String(isDev),
},
watch: isDev,
rollup: {
emitCJS: !isDev,
output: {
preserveModules: true,
preserveModulesRoot: "src",
},
},
});
}

19
scripts/build.mts

@ -0,0 +1,19 @@
import { resolve } from "node:path";
import fg from "fast-glob";
import { buildOne } from "./build-one.mts";
const dirs = fg.sync(["*"], {
cwd: resolve("packages"),
onlyDirectories: true,
ignore: [".vitepress", "guide"],
});
for (let i = 0; i < dirs.length; i++) {
const dir = dirs[i];
console.log(`开始构建${dir}`);
try {
await buildOne(dir);
} catch (error) {
console.error(error);
}
}

24
vitest.config.ts

@ -0,0 +1,24 @@
import { resolve } from "path";
import { configDefaults, defineConfig } from "vitest/config";
const src = "src";
export default defineConfig({
test: {
alias: {
"@xyx-utils/vue3": resolve(process.cwd(), "packages/vue3/" + src),
"@xyx-utils/shared": resolve(process.cwd(), "packages/shared/" + src),
"@xyx-utils/browser": resolve(process.cwd(), "packages/browser/" + src),
"@xyx-utils/core": resolve(process.cwd(), "packages/core/" + src),
"@xyx-utils/node": resolve(process.cwd(), "packages/node/" + src),
"xyx-utils": resolve(process.cwd(), "packages/xyx-utils/" + src),
},
globals: true,
environment: "jsdom",
exclude: [...configDefaults.exclude],
include: [
"packages/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
"packages/**/__tests__/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
],
},
});
Loading…
Cancel
Save