commit
7f4415cf0e
85 changed files with 6771 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||||
|
node_modules |
||||
|
dist |
||||
|
packages/\.vitepress/cache/* |
@ -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" |
||||
|
} |
@ -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" |
||||
|
} |
@ -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"] |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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" |
||||
|
} |
||||
|
} |
@ -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; |
@ -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); |
@ -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"]), |
||||
|
}); |
@ -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)]
|
@ -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) |
||||
|
}) |
||||
|
} |
||||
|
} |
@ -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> |
@ -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}`; |
||||
|
} |
@ -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 |
||||
|
} |
@ -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: *" |
||||
|
} |
||||
|
} |
@ -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> |
@ -0,0 +1,10 @@ |
|||||
|
--- |
||||
|
title: 平台检测 |
||||
|
--- |
||||
|
|
||||
|
|
||||
|
这是一个平台信息检测工具 |
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
<preview path="./demo.vue" title="@xyx-utils/browser" description="用于平台的判断"></preview> |
@ -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; |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
}); |
||||
|
}); |
@ -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> |
@ -0,0 +1,20 @@ |
|||||
|
--- |
||||
|
title: 事件绑定 |
||||
|
--- |
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
<preview path="./demo.vue" title="元素事件事件监听" description="监听与取消元素的原生事件"></preview> |
||||
|
|
||||
|
## on |
||||
|
|
||||
|
监听元素原生事件 |
||||
|
|
||||
|
<!--code:on:code--> |
||||
|
|
||||
|
## off |
||||
|
|
||||
|
取消监听事件 |
||||
|
|
||||
|
<!--code:off:code--> |
||||
|
|
@ -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 }; |
@ -0,0 +1,3 @@ |
|||||
|
export * from "./event"; |
||||
|
export * from "./check"; |
||||
|
export * from "./scrollTo"; |
@ -0,0 +1,7 @@ |
|||||
|
--- |
||||
|
title: 导览 |
||||
|
first: 0 |
||||
|
name: 浏览器 |
||||
|
--- |
||||
|
|
||||
|
浏览器相关工具函数,主要包括元素的事件处理 |
@ -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> |
@ -0,0 +1,11 @@ |
|||||
|
--- |
||||
|
title: 滚动位置 |
||||
|
--- |
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
<preview path="./demo.vue" title="滚动位置" description="滚动到指定的位置"></preview> |
||||
|
|
||||
|
## 2000px高的空文档 |
||||
|
|
||||
|
<div style="height: 2000px" /> |
@ -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(); |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"extends": "tsconfig/tsconfig.json", |
||||
|
"include": [ |
||||
|
"src", |
||||
|
"docs", |
||||
|
"global.d.ts", |
||||
|
] |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
declare const __DEV__: boolean; |
@ -0,0 +1,14 @@ |
|||||
|
{ |
||||
|
"name": "@xyx-utils/core", |
||||
|
"version": "1.0.0", |
||||
|
"description": "", |
||||
|
"main": "dist/index.mjs", |
||||
|
"scripts": { |
||||
|
}, |
||||
|
"files": [ |
||||
|
"dist" |
||||
|
], |
||||
|
"keywords": [], |
||||
|
"author": "", |
||||
|
"license": "ISC" |
||||
|
} |
@ -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> |
@ -0,0 +1,9 @@ |
|||||
|
--- |
||||
|
title: 日期->格式化 |
||||
|
--- |
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
<preview path="./demo.vue" title="时间" description="时间按指定格式输出"></preview> |
||||
|
|
||||
|
|
@ -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; |
||||
|
} |
@ -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> |
@ -0,0 +1,9 @@ |
|||||
|
--- |
||||
|
title: 日期->美化 |
||||
|
--- |
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
<preview path="./demo.vue" title="时间" description="时间按指定格式输出"></preview> |
||||
|
|
||||
|
|
@ -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 ""; |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
export * from "./format" |
||||
|
export * from "./parse" |
||||
|
export * from "./friendly" |
@ -0,0 +1,10 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
fdsf |
||||
|
</div> |
||||
|
</template> |
||||
|
<script lang="ts" setup> |
||||
|
import { onScopeDispose, ref } from "vue"; |
||||
|
|
||||
|
</script> |
||||
|
<style lang="less" scoped></style> |
@ -0,0 +1,9 @@ |
|||||
|
--- |
||||
|
title: 日期->转化 |
||||
|
--- |
||||
|
|
||||
|
## Demo |
||||
|
|
||||
|
<preview path="./demo.vue" title="时间" description="时间按指定格式输出"></preview> |
||||
|
|
||||
|
|
@ -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) { |
||||
|
|
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
export * from "./date"; |
@ -0,0 +1,9 @@ |
|||||
|
--- |
||||
|
title: 导览 |
||||
|
first: 0 |
||||
|
name: 通用 |
||||
|
--- |
||||
|
|
||||
|
# @xyx-utils/core |
||||
|
|
||||
|
asd |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"extends": "tsconfig/tsconfig.json", |
||||
|
"include": [ |
||||
|
"src", |
||||
|
"global.d.ts", |
||||
|
] |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
--- |
||||
|
title: 导览 |
||||
|
first: true |
||||
|
name: 导览 |
||||
|
--- |
||||
|
|
||||
|
# XYX UTILS |
||||
|
|
||||
|
## 概述 |
||||
|
|
||||
|
该库主要用于自己的编程工具库,该库分为`core`,`browser`,`node`等针对一些平台的特殊工具方法。 |
@ -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> |
@ -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,0 +1,7 @@ |
|||||
|
{ |
||||
|
"extends": "tsconfig/tsconfig.json", |
||||
|
"include": [ |
||||
|
"src", |
||||
|
"global.d.ts", |
||||
|
] |
||||
|
} |
@ -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" |
||||
|
} |
@ -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 |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"extends": "tsconfig/tsconfig.json", |
||||
|
"include": [ |
||||
|
"src", |
||||
|
"global.d.ts", |
||||
|
] |
||||
|
} |
@ -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" |
||||
|
} |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
import { createContextComponent } from '@xyx-utils/vue3'; |
||||
|
|
||||
|
export const ThemeContext = createContextComponent("light") |
@ -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> |
@ -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> |
@ -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 |
||||
|
}; |
||||
|
} |
@ -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> |
@ -0,0 +1,5 @@ |
|||||
|
--- |
||||
|
title: hook->useEchart |
||||
|
--- |
||||
|
|
||||
|
<preview path="./demo.vue" title="@xyx-utils/vue3" description="useEchart"></preview> |
@ -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, |
||||
|
}; |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
export * from "./echarts"; |
||||
|
export * from "./useCallDelay"; |
||||
|
export * from "./useCherry"; |
||||
|
export * from "./createContextComponent"; |
@ -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> |
@ -0,0 +1,7 @@ |
|||||
|
--- |
||||
|
title: hook->useCallDelay |
||||
|
--- |
||||
|
|
||||
|
该函数用于延迟调用,提供了可配置选项 |
||||
|
|
||||
|
<preview path="./demo.vue" title="基本使用" description="普通调用"></preview> |
@ -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, |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
<template> |
||||
|
<div>11</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
|
||||
|
</script> |
@ -0,0 +1,5 @@ |
|||||
|
--- |
||||
|
title: hook->useCherry |
||||
|
--- |
||||
|
|
||||
|
asd |
@ -0,0 +1,3 @@ |
|||||
|
export { useInjector } from "./useInjector" |
||||
|
export { useProvider } from "./useProvider" |
||||
|
export { useProviders } from "./useProviders" |
@ -0,0 +1,7 @@ |
|||||
|
//定义一个用于状态共享的hook函数的标准接口
|
||||
|
export interface FunctionalStore<T extends object> { |
||||
|
(...args: any[]): T; |
||||
|
token?: symbol; |
||||
|
root?: T; |
||||
|
} |
||||
|
|
@ -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提供`); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
|
||||
|
|
@ -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()); |
||||
|
}); |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
export * from "./hook" |
@ -0,0 +1,7 @@ |
|||||
|
--- |
||||
|
title: 导览 |
||||
|
first: 0 |
||||
|
name: vue3 |
||||
|
--- |
||||
|
|
||||
|
vue3 |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"extends": "tsconfig/tsconfig.json", |
||||
|
"include": [ |
||||
|
"src", |
||||
|
"global.d.ts", |
||||
|
] |
||||
|
} |
@ -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" |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
--- |
||||
|
title: 导览 |
||||
|
first: 0 |
||||
|
name: xyx-utils |
||||
|
--- |
||||
|
|
||||
|
|
||||
|
asd |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"extends": "tsconfig/tsconfig.json", |
||||
|
"include": [ |
||||
|
"src", |
||||
|
"global.d.ts", |
||||
|
] |
||||
|
} |
File diff suppressed because it is too large
@ -0,0 +1,5 @@ |
|||||
|
packages: |
||||
|
- 'internal/**/*' |
||||
|
- 'packages/**/*' |
||||
|
- '!packages/.vitepress' |
||||
|
- '!**/dist/**' |
After Width: | Height: | Size: 618 KiB |
@ -0,0 +1,21 @@ |
|||||
|
|
||||
|
|
||||
|
## 开发 |
||||
|
|
||||
|
**开发新模块** |
||||
|
|
||||
|
1. 在`packages\.vitepress\config.ts`增加`startsDirs`和`alias`。 |
||||
|
2. 在`internal\tsconfig\tsconfig.json`增加路径别名 |
||||
|
3. 在`vitest.config.ts`修改别名 |
||||
|
|
||||
|
**本地调试工具** |
||||
|
|
||||
|
- yalc |
||||
|
|
||||
|
据说是本地最好的link方案,有没有吹牛自己试试。 |
||||
|
|
||||
|
**版本管理** |
||||
|
|
||||
|
- changeset |
||||
|
|
||||
|
可以尝试尝试 |
@ -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", |
||||
|
}, |
||||
|
}, |
||||
|
}); |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
@ -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…
Reference in new issue