Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/rzx007/nav
Browse files Browse the repository at this point in the history
  • Loading branch information
rzx007 committed Jan 23, 2024
2 parents 3a60310 + a3f9744 commit 096ec85
Show file tree
Hide file tree
Showing 11 changed files with 968 additions and 714 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:

# 打包静态文件
- name: Build
run: pnpm install && pnpm run build
run: pnpm install --no-frozen-lockfile && pnpm run build

# 部署
- name: Deploy
Expand Down
34 changes: 34 additions & 0 deletions docs/.vitepress/components/Comment.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!-- 基于giscus的评论 -->
<!-- 参考: https://tricker39.github.io/programming-knowledge/home/giscus -->
<template>
<div class="comment">
<component
:is="'script'"
src="https://giscus.app/client.js"
data-repo="rzx007/nav"
data-repo-id="R_kgDOKFJWVg"
data-category="Show and tell"
data-category-id="DIC_kwDOKFJWVs4CclyE"
data-mapping="pathname"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="bottom"
:data-theme="!isDark ? 'light' : 'dark'"
data-lang="zh-CN"
crossorigin="anonymous"
data-loading="eager"
async
></component>
</div>
</template>
<script setup lang="ts">
import { useData } from 'vitepress'
// 获取当前配色方案
const { isDark } = useData()
</script>
<style lang="scss" scoped>
.comment {
margin-top: 16px;
}
</style>
5 changes: 4 additions & 1 deletion docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ export default defineConfig({
/* 主题配置 */
themeConfig: {
i18nRouting: false,


search: {
provider: 'local'
},
logo: '/logo.png',

nav,
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/configs/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const sidebar: DefaultTheme.Config['sidebar'] = {
{ text: '显示或隐藏表列', link: '/html-dom/show-or-hide-table-columns' },
{ text: '复制高亮代码到剪贴板', link: '/html-dom/copy-highlighted-code-to-the-clipboard' },
{ text: '创建自定义滚动条', link: '/html-dom/create-a-custom-scrollbar' },
{ text: '数组树形数据相互转换', link: '/html-dom/tree-conver' },
{ text: '基于流式数据的类似 chatgpt 的打字机式输出', link: '/html-dom/server-sent-events' },
{ text: '已同步方式实现事件监听', link: '/html-dom/async-addEventListener' },
{ text: '使用 vue 指令实现一个元素平滑上升的效果', link: '/html-dom/vue-slide-smooth' },
Expand Down
11 changes: 7 additions & 4 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { h, watch } from 'vue'
import { useData, EnhanceAppContext } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import Comment from '../components/Comment.vue'

import './styles/index.scss'

Expand All @@ -18,7 +19,9 @@ export default {
props.class = frontmatter.value.layoutClass
}

return h(DefaultTheme.Layout, props)
return h(DefaultTheme.Layout, props, {
'doc-after': () => h(Comment),
})
},
enhanceApp({ router }: EnhanceAppContext) {
if (typeof window !== 'undefined') {
Expand All @@ -27,12 +30,12 @@ export default {
() =>
updateHomePageStyle(
/* /vitepress-nav-template/ 是为了兼容 GitHub Pages */
location.pathname === '/' || location.pathname === '/vitepress-nav-template/'
location.pathname === '/' || location.pathname === '/vitepress-nav-template/',
),
{ immediate: true }
{ immediate: true },
)
}
}
},
}

if (typeof window !== 'undefined') {
Expand Down
45 changes: 45 additions & 0 deletions docs/code/tree/arrayToTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
interface Node {
id: string; // 节点的唯一标识符
children?: Array<Node>; // 节点的子节点数组
pid?: string; // 节点的父节点标识符
}
interface Options {
idKey?: string; // 自定义 id 字段的名称
pidKey?: string; // 自定义 pid 字段的名称
childrenKey?: string; // 自定义 children 字段的名称
}

export const arrayToTree = (
array: Array<Node | undefined>,
options: Options = {}
) => {
if (!Array.isArray(array)) {
throw new Error('The first argument must be an array.');
}
// 解构 options 对象来获取自定义字段名称或默认值
const { idKey = 'id', pidKey = 'pid', childrenKey = 'children' } = options;
// 创建节点 id 到节点的映射
const map = array.reduce((acc: Record<string, Node>, node: any) => {
acc[node[idKey]] = { ...node, [childrenKey]: [] }; // 初始化节点并添加到映射中
return acc;
}, {});

Object.values(map).forEach((node: any) => {
// 遍历所有节点
const parentId = node[pidKey];
if (parentId) {
// 如果存在父节点
const parent: any = map[parentId]; // 获取当前节点的父节点
if (!parent[childrenKey]) {
// 如果父节点没有 children 属性
parent[childrenKey] = []; // 初始化 children 属性
}
parent[childrenKey].push(node); // 将当前节点添加到父节点的 children 数组中
}
});

// 找到所有根节点(没有父节点的节点)并构建树
const tree = Object.values(map).filter((node: any) => !node[pidKey]);

return tree;
};
18 changes: 18 additions & 0 deletions docs/code/tree/generateTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
interface Item {
// Add any existing properties
children?: Item[];
// Define the new property
id: string;
}
export const generateTree = (depth: number, width: number): Item => {
const node: Item = {
id: Math.floor(Math.random() * 1000000).toString()
};
if (depth > 0) {
node.children = [];
for (let i = 0; i < width; i++) {
node.children.push(generateTree(depth - 1, width));
}
}
return node;
};
86 changes: 86 additions & 0 deletions docs/code/tree/treeToArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 节点类型 Node Type
interface Node {
[key: string]: any;
children?: Array<Node>;
}

// 入参类型
interface TreeToArrayOptions {
// 子节点的键名称,默认为'children'
childrenKey?: string;
// 需要忽略的字段名列表,默认为空列表
ignoreFields?: Array<string>;
// 需要添加的字段名及其对应属性值的计算方式列表,默认为空列表
addFields?: Array<{ fieldName: string; callback: (item: Node) => any }>;
// 子节点是否需要父节点的id,默认为true
needParentId?: boolean;
}

// 根据树状结构以数组形式返回所有节点(包括子孙节点)
export const treeToArray = (
tree: Array<Node>,
options: TreeToArrayOptions = {}
): Array<Node> => {
const {
// 子节点的键名称
childrenKey = 'children',
// 要忽略的字段名列表
ignoreFields = [],
// 需要添加的字段名及其对应属性值的计算方式列表
addFields = [],
// 子节点是否需要父节点的id,默认为true
needParentId = true
} = options;
const nodes: Array<Node> = [];
// stack用于迭代树结构中的子节点和子孙子节点
const stack: Array<{
// 正在处理的节点,包括该节点的信息、父节点的id、以及该节点的所有子节点
node: Node | null;
children: Array<Node>;
parentId: string | null;
}> = [];
// 将整个树的所有节点压入栈,其中root节点的parentId为空
stack.push({
node: null,
children: tree,
parentId: null
});
while (stack.length) {
const { node, children, parentId } = stack.pop()!;
if (node) {
// 存储该节点的所有属性到newNode中,除去childrenKey所指定的子节点信息
const { [childrenKey]: subChildren, ...rest } = node;
const newNode = { ...rest };
if (needParentId) {
// 如果needParentId为true,则将父节点的id添加到该节点的属性中
newNode['parentId'] = parentId;
}
if (addFields.length) {
// 如果需要添加属性值,则遍历addFields列表,计算对应的属性值
for (let i = 0; i < addFields.length; i++) {
newNode[addFields[i].fieldName] = addFields[i].callback(node);
}
}
if (ignoreFields.length) {
// 如果需要忽略某些属性,则遍历ignoreFields列表,将对应的属性从newNode中删除
for (let i = 0; i < ignoreFields.length; i++) {
delete newNode[ignoreFields[i]];
}
}
// 将newNode存入nodes数组中
nodes.push(newNode);
}
if (children) {
// 将该节点的所有子节点压入栈中,继续循环直到stack为空
for (let i = children.length - 1; i >= 0; i--) {
stack.push({
node: children[i],
children: children[i][childrenKey] || [],
parentId: node?.id || ''
});
}
}
}
// 返回以数组形式储存的所有节点(包括子孙节点)
return nodes;
};
130 changes: 130 additions & 0 deletions docs/html-dom/tree-conver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# 树形数据相互转换

## 把树形数据转换成扁平数组

::: details 查看代码
<<< @/code/tree/treeToArray.ts{ts}
:::

### 参数

接受两个参数:

- **Tree**: 树形结构数组

- **Options**: 一个可选的参数对象,用于配置转换方法的具体行为

| 属性 | 描述 | 类型 | 默认值 |
| ------------ | ------------------------------------------------ | ----------------------------------------------- | ---------- |
| addFields | 需要添加的字段名称及其对应的属性值计算方法的列表 | [{ fieldName: string;callback: (item) => any }] | [] |
| childrenKey | 子节点的键名 | string | 'children' |
| ignoreFields | 要忽略的字段名称列表 | string[] | [] |
| needParentId | 是否添加节点信息的父节点 ID | boolean | true |

### 示例

```javascript
const treeArray = [
{
id: '1',
name: 'Node 1',
list: [
{
id: '2',
name: 'Node 2',
list: [
{
id: '3',
name: 'Node 3'
}
]
},
{
id: '4',
name: 'Node 4'
}
]
}
]
const calculateDepth = (node) => {
let depth = 0
let parent = node
while (parent) {
depth++
parent = parent['parentId'] && treeArray.find((n) => n.id === parent['parentId'])
}
return depth
}
const options = {
childrenKey: 'list',
ignoreFields: [],
addFields: [
{
fieldName: 'hasChildren', // 添加新字段 'hasChildren',用于判断是否有子节点
callback: (node) => Boolean(node['children'])
},
{
fieldName: 'depth', // 添加新字段 'depth',用于记录节点深度
callback: calculateDepth
}
],
needParentId: true
}

const flatArray = treeToArray(treeArray, options)

console.log(flatArray)
```
结果如下:
```json
[
{ "id": "1", "name": "Node 1", "parentId": "", "hasChildren": false, "depth": 1 },
{ "id": "2", "name": "Node 2", "parentId": "1", "hasChildren": false, "depth": 1 },
{ "id": "3", "name": "Node 3", "parentId": "2", "hasChildren": false, "depth": 1 },
{ "id": "4", "name": "Node 4", "parentId": "1", "hasChildren": false, "depth": 1 }
]
```

## 把扁平数组转换成树形数据

::: details 查看代码
<<< @/code/tree/arrayToTree.ts{ts}
:::

### 参数

它接受两个参数:

- **Array**: 扁平的节点数组
- **Options**: 一个可选的参数对象,用于配置转换方法的具体行为

| 参数 | 描述 | 类型 | 默认值 |
| ----------- | ---------------------------- | ------ | ---------- |
| childrenKey | 自定义节点 children 字段名称 | string | 'children' |
| idKey | 自定义节点 ID 字段名称 | string | 'id' |
| pidKey | 自定义节点父 ID 字段名称 | string | 'pid' |

### 示例

```javascript
const flatArray = [
{ uid: '1', name: 'node1', pid: null },
{ uid: '2', name: 'node2', pid: '1' },
{ uid: '3', name: 'node3', pid: '1' },
{ uid: '4', name: 'node4', pid: '2' },
{ uid: '5', name: 'node5', pid: '2' },
{ uid: '6', name: 'node6', pid: '3' }
]

const options = {
idKey: 'id',
pidKey: 'pid',
childrenKey: 'children'
}

const treeArray = arrayToTree(flatArray, options)
```

### 生成随机树形数据

<<< @/code/tree/generateTree.ts{ts}
Loading

0 comments on commit 096ec85

Please sign in to comment.