ant-design-vue中tree增删改

1. 使用背景

新项目中使用了ant-design-vue组件库.该组件库完全根基数据双向绑定的模式实现.只有表单组件提供少量的方法.所以,在使用ant-design-vue时,一定要从改变数据的角度去切换UI显示效果.然而,在树形控件a-tree的使用上,单从数据驱动上去考虑,感体验效果实在不好.

2. 当前痛点

通过阅读官方帮助文档,针对树形控件数据绑定.需要将数据构造成一个包含children,title,key属性的大对象.这样一个对象,要么通过后端构造好这样的json对象,要么就是后端给前端一个json数组,前端根据上下级关系构建这么一个树形对象.数据绑定好,就可以成功的渲染成我们想要的UI效果了.可痛点在哪里呢?

  • 树形加载成功后,我要向当前的树形添加一个同级以及下级节点该如何操作(增)
  • 树形加载成功后,我要修改任意一个树形节点该如何操作(改)
  • 树形加载成功后,我要删除一个树形节点该如何操作(删)

以上操作,都要求不重新加载树形控件条件下完成.经过测试整理出了三个可行方案

  1. 数据驱动
  2. 作用域插槽
  3. 节点事件

3. 数据驱动实现树形节点增删改

我们可以在帮助文档中找到名为selectedKeys(.sync)属性,sync表示该属性支持双向操作.但是,这里仅仅获取的是一个key值,并不是需要的绑定对象.所以,需要通过这key值找到这个对象.需要找这个对象就相当恶心了

  1. 如果后端返回是构建好的数据,需要遍历这个树形数据中找到和这个key值对应的对象.我能想到的就是通过顶层节点递归查找.可是控件都渲染完成了,都知道每个节点的数据.我为什要重新查找一遍呢???
  2. 如果后端返回的仅仅是一个数组,这个刚才有提到需要重新构建这部分数据为对象.这样查找这个对象又分两种情况
    a. 如果列表数据和构建后树形对象采用克隆的方式,也就是列表中对象的地址和树形中相同key值对象的地址不同.需要通过方法1遍历重新构造后的树形数据
    b. 如果列表数据中的对象和构建后对应的节点是相同的对象地址.可以直接查找这个列表数据得到对应的对象.

所以,恶心的地方就在于构建好一个树,我又得遍历这个树查找某个节点,或者采用方案b这种空间换时间的做法

这里我们假设数据,已经是构建成树形的数据格式.要实现数据驱动的首要任务需要完成两个核心方法

  1. 根据当前节点key值查找节点对象getTreeDataByKey
  2. 根据当前节点key值查找父级节点children集合getTreeParentChilds

两个方法代码分别如下

  1. // author:herbert date:20201024 qq:464884492
  2. // 根据key获取与之相等的数据对象
  3. getTreeDataByKey(childs = [], findKey) {
  4. let finditem = null;
  5. for (let i = 0, len = childs.length; i < len; i++) {
  6. let item = childs[i]
  7. if (item.key !== findKey && item.children && item.children.length > 0) {
  8. finditem = this.getTreeDataByKey(item.children, findKey)
  9. }
  10. if (item.key == findKey) {
  11. finditem = item
  12. }
  13. if (finditem != null) {
  14. break
  15. }
  16. }
  17. return finditem
  18. },
  19. // author:herbert date:20201024 qq:464884492
  20. // 根据key获取父级节点children数组
  21. getTreeParentChilds(childs = [], findKey) {
  22. let parentChilds = []
  23. for (let i = 0, len = childs.length; i < len; i++) {
  24. let item = childs[i]
  25. if (item.key !== findKey && item.children && item.children.length > 0) {
  26. parentChilds = this.getTreeParentChilds(item.children, findKey)
  27. }
  28. if (item.key == findKey) {
  29. parentChilds = childs
  30. }
  31. if (parentChilds.length > 0) {
  32. break
  33. }
  34. }
  35. return parentChilds
  36. },
3.1 添加同级节点

添加同级节点,需要把新增加的数据,添加到当前选中节点的父级的children数组中.所以,添加节点的难点在如何找到当前选中节点的绑定对象的父级对象.页面代码如下

  1. <!-- author:herbert date:20201030 qq:464884492-->
  2. <a-card style="width: 450px;height:550px;float: left;">
  3. <div slot="title">
  4. <h2>树形操作(纯数据驱动)<span style="color:blue">@herbert</span></h2>
  5. <div>
  6. <a-button @click="dataDriveAddSame">添加同级</a-button>
  7. <a-divider type="vertical" />
  8. <a-button @click="dataDriveAddSub">添加下级</a-button>
  9. <a-divider type="vertical" />
  10. <a-button @click="dataDriveModify">修改</a-button>
  11. <a-divider type="vertical" />
  12. <a-button @click="dataDriveDelete">删除</a-button>
  13. </div>
  14. </div>
  15. <a-tree :tree-data="treeData" :defaultExpandAll="true"
  16. :selectedKeys.sync="selectKeys" showLine />
  17. <img src="./assets/gamelogo.png" width="100%" style="margin-top:20px" />
  18. </a-card>

从页面代码中可以看出,再树上绑定了两个属性tree-data,selectedKeys,这里我们就可以通过selectedKeys绑定值,获取到树形当前选择的key值.然后使用方法getTreeParentChilds就可以实现同级添加.所以,对用的dataDriveAddSame代码实现如下

  1. // author:herbert date:20201030 qq:464884492
  2. dataDriveAddSame() {
  3. let parentChilds = this.getTreeParentChilds(this.treeData, this.selectKeys[0])
  4. parentChilds.forEach(item => console.log(item.title));
  5. parentChilds.push({
  6. title: '地心侠士,会玩就停不下来',
  7. key: new Date().getTime()
  8. })
  9. },
3.2 添加下级

有了上边的基础,添加下级就很简单了.唯一需要注意的地方就是获取到的对象children属性可能不存在,此时我们需要$set方式添加属性dataDriveAddSub代码实现如下

  1. // author:herbert date:20201030 qq:464884492
  2. dataDriveAddSub() {
  3. let selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
  4. if (!selectItem.children) {
  5. this.$set(selectItem, "children", [])
  6. }
  7. selectItem.children.push({
  8. title: 地心侠士,值得你来玩,
  9. key: new Date().getTime()
  10. })
  11. this.$forceUpdate()
  12. },
3.3 修改节点

能获取到绑定对象,修改节点值也变得简单了,同添加下级一样使用getTreeDataByKey获取当前对象,然后直接修改值就是了.dataDriveModify代码实现如下

  1. // author:herbert date:20201030 qq:464884492
  2. dataDriveModify() {
  3. let selectItem = this.getTreeDataByKey(this.treeData, this.selectKeys[0])
  4. selectItem.title = '扫码下方二维码,开始地心探险之旅'
  5. },
3.4 删除节点

删除和添加同级一样,需要找到父级节点children数组,已经当前对象在父级数组中对应的索引.dataDriveDelete代码实现如下

  1. // author:herbert date:20201030 qq:464884492
  2. dataDriveDelete() {
  3. let parentChilds = this.getTreeParentChilds(this.treeData, this.selectKeys[0])
  4. let delIndex = parentChilds.findIndex(item => item.key == this.selectKeys[0])
  5. parentChilds.splice(delIndex, 1)
  6. },

4. 通过插槽方式树形节点增删改

ant-tree的api中,树形节点属性title类型可以是字符串,也可以是插槽[string|slot|slot-scope],我么这里需要拿到操作对象,这里使用作用域插槽,对应的页面代码如下

  1. <!-- author:herbert date:20201030 qq:464884492-->
  2. <a-card style="width: 450px;height:550px;float: left;">
  3. <div slot="title">
  4. <h2>树形操作(采用作用域插槽)</h2>
  5. <div>
  6. 采用作用域插槽,操作按钮统一放置到树上<span style="color:blue">@小院不小</span>
  7. </div>
  8. </div>
  9. <a-tree ref="tree1" :tree-data="treeData1" :defaultExpandAll="true" :selectedKeys.sync="selectKeys1" showLine blockNode>
  10. <template v-slot:title="nodeData">
  11. <span>{{nodeData.title}}</span>
  12. <a-button-group style="float:right">
  13. <a-button size="small" @click="slotAddSame(nodeData)" icon="plus-circle" title="添加同级"></a-button>
  14. <a-button size="small" @click="slotAddSub(nodeData)" icon="share-alt" title="添加下级"></a-button>
  15. <a-button size="small" @click="slotModify(nodeData)" icon="form" title="修改"></a-button>
  16. <a-button size="small" @click="slotDelete(nodeData)" icon="close-circle" title="删除"></a-button>
  17. </a-button-group>
  18. </template>
  19. </a-tree>
  20. <img src="./assets/gamelogo.png" width="100%" style="margin-top:20px" />
  21. </a-card>
4.1 添加同级

采用插槽的方式拿到对象,其实是当前节点对应的属性值,并且是一个浅复制的副本.在源码vc-tree\src\TreeNode.jsx中的renderSelector可以找到如下一段代码

  1. const currentTitle = title;
  2. let $title = currentTitle ? (
  3. <span class={`${prefixCls}-title`}>
  4. {typeof currentTitle === 'function'
  5. ? currentTitle({ ...this.$props, ...this.$props.dataRef }, h)
  6. : currentTitle}
  7. </span>
  8. ) : (
  9. <span class={`${prefixCls}-title`}>{defaultTitle}</span>
  10. );

从这段代码,可以看到一个dataRef.但是在官方的帮助文档中完全没有这个属性的介绍.不知道者算不算给愿意看源码的同学的一种福利.不管从代码层面,还是调试结果看.通过作用域得到的对象,没有父级属性所以不能实现同级添加.slotAddSame代码如下

  1. // author:herbert date:20201030 qq:464884492
  2. slotAddSame(nodeItem) {
  3. console.log(nodeItem)
  4. this.$warn({ content: "采用插槽方式,找不到父级对象,添加失败!不要想了,去玩地心侠士吧" })
  5. },
4.2 添加下级

虽然得到了对象,但是只是一个副本.所以设置children也是没用的!!

  1. // author:herbert date:20201030 qq:464884492
  2. slotAddSub(nodeItem) {
  3. if (!nodeItem.children) {
  4. console.log('其实这个判断没有用,这里仅仅是一个副本')
  5. this.$set(nodeItem, "children", [])
  6. }
  7. nodeItem.children.push({
  8. title: this.addSubTitle,
  9. key: new Date().getTime(),
  10. scopedSlots: { title: 'title' },
  11. children: []
  12. })
  13. },
4.3 修改节点

修改一样也不能实现,不过上边有提到dataRef,这里简单使用下,可以实现修改title值.

  1. // author:herbert date:20201030 qq:464884492
  2. slotModify(nodeItem) {
  3. console.log(nodeItem)
  4. console.log('nodeItem仅仅时渲染Treenode属性的一个浅复制的副本,直接修改Title没有用')
  5. nodeItem.title = '这里设置是没有用的,去玩游戏休息一会吧'
  6. // 这里可以借助dataRef 更新
  7. nodeItem.dataRef.title = nodeItem.title
  8. },
4.4 删除节点

很明显,删除也是不可以的.

  1. // author:herbert date:20201030 qq:464884492
  2. slodDelete(nodeItem) {
  3. console.log(nodeItem)
  4. this.$warn({ content: "采用插槽方式,找不到父级对象,删除失败!很明显,还是去玩地心侠士吧" })
  5. delete nodeItem.dataRef
  6. },

5. 树形事件结合dataRef实现

上边通过插槽方式,仅仅实现了修改功能.特别失望有没有.不过从设计的角度去考虑,给你对象仅仅是帮助你做自定义渲染,就好多了.接续读官方Api找到事件其中的select事件提供的值,又给了我们很大的发挥空间.到底有多大呢,我们去源码看看.首先我们找到触发select事件代码在components\vc-tree\src\TreeNode.jsx文件中,具体代码如下

  1. onSelect(e) {
  2. if (this.isDisabled()) return;
  3. const {
  4. vcTree: { onNodeSelect },
  5. } = this;
  6. e.preventDefault();
  7. onNodeSelect(e, this);
  8. },

从代码中可以看到TreeNodeonSelect其实是调用Tree中的onNodeSelected方法,我们到components\vc-tree\src\Tree.jsx找到对应的代码如下

  1. onNodeSelect(e, treeNode) {
  2. let { _selectedKeys: selectedKeys } = this.$data;
  3. const { _keyEntities: keyEntities } = this.$data;
  4. const { multiple } = this.$props;
  5. const { selected, eventKey } = getOptionProps(treeNode);
  6. const targetSelected = !selected;
  7. // Update selected keys
  8. if (!targetSelected) {
  9. selectedKeys = arrDel(selectedKeys, eventKey);
  10. } else if (!multiple) {
  11. selectedKeys = [eventKey];
  12. } else {
  13. selectedKeys = arrAdd(selectedKeys, eventKey);
  14. }
  15.  
  16. // [Legacy] Not found related usage in doc or upper libs
  17. const selectedNodes = selectedKeys
  18. .map(key => {
  19. const entity = keyEntities.get(key);
  20. if (!entity) return null;
  21.  
  22. return entity.node;
  23. })
  24. .filter(node => node);
  25.  
  26. this.setUncontrolledState({ _selectedKeys: selectedKeys });
  27.  
  28. const eventObj = {
  29. event: 'select',
  30. selected: targetSelected,
  31. node: treeNode,
  32. selectedNodes,
  33. nativeEvent: e,
  34. };
  35. this.__emit('update:selectedKeys', selectedKeys);
  36. this.__emit('select', selectedKeys, eventObj);
  37. },

结合两个方法,从Tree节点eventObj对象中可以知道组件select不仅把Tree节点渲染TreeNode缓存数据selectedNodes以及对应实实在在的TreeNode节点node,都提供给了调用方.有了这个node属性,我们就可以拿到对应节点的上下级关系

接下来我们说说这个再帮助文档上没有出现的dataRef是个什么鬼.
找到文件components\tree\Tree.jsx在对应的render函数中我们可以知道Tree需要向vc-tree组件传递一个treeData属性,我们最终使用的传递节点数据也是这个属性名.两段关键代码如下

  1. render(){
  2. ...
  3. let treeData = props.treeData || treeNodes;
  4. if (treeData) {
  5. treeData = this.updateTreeData(treeData);
  6. }
  7. ...
  8. if (treeData) {
  9. vcTreeProps.props.treeData = treeData;
  10. }
  11. return <VcTree {...vcTreeProps} />;
  12. }

从上边代码可以看到,组件底层调用方法updateTreeData对我们传入的数据做了处理,这个方法关键代码如下

  1. updateTreeData(treeData) {
  2. const { $slots, $scopedSlots } = this;
  3. const defaultFields = { children: 'children', title: 'title', key: 'key' };
  4. const replaceFields = { ...defaultFields, ...this.$props.replaceFields };
  5. return treeData.map(item => {
  6. const key = item[replaceFields.key];
  7. const children = item[replaceFields.children];
  8. const { on = {}, slots = {}, scopedSlots = {}, class: cls, style, ...restProps } = item;
  9. const treeNodeProps = {
  10. ...restProps,
  11. icon: $scopedSlots[scopedSlots.icon] || $slots[slots.icon] || restProps.icon,
  12. switcherIcon:
  13. $scopedSlots[scopedSlots.switcherIcon] ||
  14. $slots[slots.switcherIcon] ||
  15. restProps.switcherIcon,
  16. title:
  17. $scopedSlots[scopedSlots.title] ||
  18. $slots[slots.title] ||
  19. restProps[replaceFields.title],
  20. dataRef: item,
  21. on,
  22. key,
  23. class: cls,
  24. style,
  25. };
  26. if (children) {
  27. // herbert 20200928 添加属性只能操作叶子节点
  28. if (this.onlyLeafEnable === true) {
  29. treeNodeProps.disabled = true;
  30. }
  31. return { ...treeNodeProps, children: this.updateTreeData(children) };
  32. }
  33. return treeNodeProps;
  34. });
  35. },
  36. }

从这个方法中我们看到,在treeNodeProps属性找到了dataRef属性,它的值就是我们传入treeData中的数据项,所以这个属性是支持双向绑定的哦.这个treeNodeProps最终会渲染到components\vc-tree\src\TreeNode.jsx,组件中去.

弄清楚这两个知识点后,我们要做的操作就变得简单了.事件驱动页面代码如下

  1. <!-- author:herbert date:20201101 qq:464884492 -->
  2. <a-card style="width: 450px;height:550px;float: left;">
  3. <div slot="title">
  4. <h2>树形事件(结合dataRef)<span style="color:blue">@464884492</span></h2>
  5. <div>
  6. <a-button @click="eventAddSame">添加同级</a-button>
  7. <a-divider type="vertical" />
  8. <a-button @click="eventAddSub">添加下级</a-button>
  9. <a-divider type="vertical" />
  10. <a-button @click="eventModify">修改</a-button>
  11. <a-divider type="vertical" />
  12. <a-button @click="eventDelete">删除</a-button>
  13. </div>
  14. </div>
  15. <a-tree :tree-data="treeData2" @select="onEventTreeNodeSelected" :defaultExpandAll="true" :selectedKeys.sync="selectKeys2" showLine />
  16. <img src="./assets/gamelogo.png" width="100%" style="margin-top:20px" />
  17. </a-card>

既然是通过事件驱动,我们首先得注册对应得事件,代码如下

  1. // author:herbert date:20201101 qq:464884492
  2. onEventTreeNodeSelected(seleteKeys, e) {
  3. if (e.selected) {
  4. this.eventSelectedNode = e.node
  5. return
  6. }
  7. this.eventSelectedNode = null
  8. },

在事件中,我们保存当前选择TreeNode方便后续的增加修改删除

5.1 添加同级

利用vue虚拟dom,找到父级

  1. // author:herbert date:20201101 qq:464884492
  2. eventAddSame() {
  3. // 查找父级
  4. let dataRef = this.eventSelectedNode.$parent.dataRef
  5. if (!dataRef.children) {
  6. this.$set(dataRef, 'children', [])
  7. }
  8. dataRef.children.push({
  9. title: '地心侠士好玩,值得分享',
  10. key: new Date().getTime()
  11. })
  12. },
5.2 添加下级

直接使用对象dataRef,注意children使用$set方法

  1. // author:herbert date:20201101 qq:464884492
  2. eventAddSub() {
  3. let dataRef = this.eventSelectedNode.dataRef
  4. if (!dataRef.children) {
  5. this.$set(dataRef, 'children', [])
  6. }
  7. dataRef.children.push({
  8. title: '地心侠士,还有很多bug欢迎吐槽',
  9. key: new Date().getTime(),
  10. scopedSlots: { title: 'title' },
  11. children: []
  12. })
  13. },
5.3 修改节点

修改直接修改dataRef对应的值

  1. // author:herbert date:20201101 qq:464884492
  2. eventModify() {
  3. let dataRef = this.eventSelectedNode.dataRef
  4. dataRef.title = '地心侠士,明天及改完bug'
  5. },
5.4 删除节点

通过vue虚拟dom找到父级dataRef,从children中移除选择项就可以

  1. // author:herbert date:20201101 qq:464884492
  2. eventDelete() {
  3. let parentDataRef = this.eventSelectedNode.$parent.dataRef
  4. // 判断是否是顶层
  5. const children = parentDataRef.children
  6. const currentDataRef = this.eventSelectedNode.dataRef
  7. const index = children.indexOf(currentDataRef)
  8. children.splice(index, 1)
  9. }

6. 总结

这个知识点,从demo到最终完成.前前后后花费快一个月的时间.期间查源码,做测试,很费时间.不过把这个点说清楚了,我觉得很值得!如果需要Demo源码请扫描下方的二维码,关注公众号[小院不小],回复ant-tree获取.关于ant-desgin-vue这套组件库来说相比我以前使用的easyUi组件库来说,感觉跟适合网页展示一类.做一些后台系统,需要提供大量操作,感觉还比较乏力

ant-design-vue中tree增删改的更多相关文章

  1. Ant Design Vue select下拉列表设置默认值

    在项目中需要为Ant Design Vue 的 select 组件设置一个默认值,如下图所示的状态下拉选择框,默认选择全部 代码如下: <a-select v-model="query ...

  2. Ant Design Vue Pro 项目实战-项目初始化(一)

    写在前面 时间真快,转眼又是新的一年.随着前后端技术的不断更新迭代,尤其是前端,在目前前后端分离开发模式这样的一个大环境下,交互性.兼容性等传统的开发模式已经显得有些吃力.之前一直用的是react,随 ...

  3. 使用ant design vue的日历组件,实现一个简单交易日与非交易日的切换

    使用ant design vue的日历组件,实现一个简单交易日与非交易日的切换 需求: 日历区分交易日.非交易日 可以切换面板查看整年交易日信息 可以在手动调整交易日.非交易日 演示实例 序--使用软 ...

  4. Vue3学习(二)之集成Ant Design Vue

    一.集成Ant Design Vue npm install ant-design-vue@2.0.0-rc.3 --save 兼容性 Ant Design Vue 2.x 支持所有的现代浏览器. 如 ...

  5. 【设计模式】【应用】使用模板方法设计模式、策略模式 处理DAO中的增删改查

    原文:使用模板方法设计模式.策略模式 处理DAO中的增删改查 关于模板模式和策略模式参考前面的文章. 分析 在dao中,我们经常要做增删改查操作,如果每个对每个业务对象的操作都写一遍,代码量非常庞大. ...

  6. thinkphp模型事件(钩子函数:模型中在增删改等操作前后自动执行的事件)

    thinkphp模型事件(钩子函数:模型中在增删改等操作前后自动执行的事件) 一.总结 1.通过模型事件(钩子函数),可以在插入更新删除等前后执行一些特定的功能 2.模型事件是写在模型里面的,控制器中 ...

  7. Ant Design框架中不同的组件访问不同的models中的数据

    Ant Design框架中不同的组件访问不同的models中的数据 本文记录了我在使用该框架的时候踩过的坑,方便以后查阅. 一.models绑定 在某个组件(控件或是页面),要想从某个models中获 ...

  8. Ant Design Pro中Transfer穿梭框的实际用法(与后端交互)

    Ant Design Pro中Transfer穿梭框的实际用法(与后端交互) 该控件的属性以及属性的作用在ADP的官方文档中都有介绍,但没有讲如何与后端交互,本文旨在讲解该控件与后端的交互. Ant ...

  9. 基于Ant Design Vue封装一个表单控件

    开源代码 https://github.com/naturefwvue/nf-vue3-ant 有缺点本来是写在最后的,但是博文写的似乎有点太长了,估计大家没时间往下看,于是就把有缺点写在前面了,不喜 ...

随机推荐

  1. 带你搭建一个简单的mybatis项目:IDEA+spring+springMVC+mybatis+Mysql

    最近小编有点闲,突发奇想想重温一下mybatis,然后在脑海中搜索了一下,纳尼,居然不太会用了,想到这里都是泪啊!!现在我所呆的的公司使用的是springboot+hebinate,编程都是使用的JP ...

  2. Android App 侧边栏菜单的简单实现

    效果图 Layout 注意事项 想要实现侧边栏,需要配合使用DrawerLayout.因为会用到嵌套布局,所以根布局不能是 ConstraintLayout,最好使用 LinearLayout 布局. ...

  3. Python练习题 045:Project Euler 017:数字英文表达的字符数累加

    本题来自 Project Euler 第17题:https://projecteuler.net/problem=17 ''' Project Euler 17: Number letter coun ...

  4. 64位Win7下H3C的iMC无法查看“网络拓扑”的解决方法、心路历程

    64位Win7下H3C的iMC无法查看"网络拓扑"的解决方法.心路历程

  5. 079 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 04 实例化对象

    079 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 04 实例化对象 本文知识点:实例化对象 说明:因为时间紧张,本人写博客过程中只是对知 ...

  6. 053 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 15 流程控制知识总结

    053 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 15 流程控制知识总结 本文知识点: 流程控制知识总结 流程控制知识总结 选择结构语句 循环结构语句 ...

  7. opencv的imread函数相对路径问题和 main 参数问题

    参考: https://blog.csdn.net/u013404374/article/details/80178822 https://blog.csdn.net/fujilove/article ...

  8. Django Croppie

    下载 Django CroppieDjango Croppie django -croppie是一个简单集成croppie.js图像cropper到django项目的应用程序. 安装 安装与pip安装 ...

  9. 使用Jest快照测试api

    你知道什么很烦人吗?API不匹配. 有一天,后台开发人员在没有通知前端开发人员的情况下更改了其中一个api."我们认为dateCreated这个名字比created_at更好,"他 ...

  10. Windows 10 如何在桌面上显示“此电脑”和“控制面板”

    新电脑安装好 Windows 10 系统,默认在桌面上是不显示 "此电脑" 和 "控制面板" 图标的. 如果是 Windows 10 家庭版,桌面一般只显示&q ...