您现在已经了解了 MEAN 应用程序的机制,接下来我们将对第一期文章中创建的 MEAN.JS应用程序进行定制。我们在第二期文章中对该应用程序有了一个大致的了解。在第三期文章中,我将演示该应用程序的基本 CRUD功能。您还会了解一些有关响应式 Web 设计和 Bootstrap 的内容。

本系列其余部分将要构建的应用程序被命名为 UGLI:User Group List and Information 应用程序。我从 2010年开始运营 HTML5 Denver User Group(前身是 Boulder Java User Group,更早以前是 Denver Java UserGroup),因此我是本地用户组的狂热粉丝,但是让我不解的是一直没有专门的软件来运行用户组。现在我们就要解决这个问题了。

许多用户组都在 Meetup.com 建立了一个在线主页。我使用 MEAN 和UGLI 应用程序的目标并不是要取代 Meetup.com;相反,我想与它建立更深入的集成。Meetup.com集中了运行成功的用户组所需的大部分核心功能;注册新用户,发布会议细节、处理 RSVP等等。但是对于用户组领导者来说仍然缺失一些关键功能,包括管理一组会议主持人(presenter)并链接到幻灯片(slide deck)。UGLI可以填补这方面的空缺。(参见 下载 获得完整的样例代码)。

调整标记

创建应用程序 UGLI 的第一个任务就是调整应用程序的标记(branding)。需要在应用程序的服务器端对 config 和 app 目录做一些修改;另外要对客户端的 public 目录做一些修改。

首先从 config/env/all.js 中的元数据开始。将标题修改为 HTML5 Denver(或您选择的用户组),并将描述修改为 HTML5 Denver User Group,如清单 1 所示。

清单 1. config/env/all.js

'use strict';module.exports = {app: {title: 'HTML5 Denver',description: 'HTML5 Denver User Group',keywords: 'MongoDB, Express, AngularJS, Node.js'},

config/env/development.js 中的标题也需要修改,如清单 2 所示。上篇文章中我们已经了解到 development.js 和 all.js 会在运行时合并。

清单 2. config/env/development.js

'use strict';module.exports = {db: 'mongodb://localhost/test-dev',app: {title: 'HTML5 Denver'},

接下来,修改导航栏左上角显示的品牌。为此,需要编辑
public/modules/core/views/header.client.view.html。在大概第 9 列的地方找到 anchor 标记和 navbar-brand 类,将 body 修改为 HTML5 Denver,如清单 3 所示。

清单 3. public/modules/core/views/header.client.view.html

<div class="container" data-ng-controller="HeaderController"><div class="navbar-header"><button class="navbar-toggle" type="button" data-ng-click="toggleCollapsibleMenu()"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a href="/#!/" class="navbar-brand">HTML5 Denver</a></div><!-- ...snip... -->
</div>

要验证所做的修改,请在命令行输入 mongod 启动 MongoDB,然后输入 grunt 启动应用程序。在浏览器中查看 Web 应用程序,看看标记是否显示在菜单和标题栏中。

要完成标记修改,需要替换 public/modules/core/views/home.client.view.html中的标准文本(boilerplate),该文本显示在主页的正文中。创建一个名为 home.client.view.html.original的副本,这样就可以在稍后往回引用(如果需要的话)。

该文件利用 Bootstrap框架的功能,确保您的网站从一开始就面向移动应用。在继续之前,需要了解 Bootstrap 提供的 12 列的网格布局。

了解 Bootstrap 和响应式 Web 设计

查看任意硬拷贝新闻或杂志,您都会看到其中使用了列。有时,一副图片或标题因为某种设计风格而需跨越多个列,但是一个基本的柱状布局构成了几乎所有打印页面的基础。

Web 页面也是如此。例如,访问 TIME网站。您看到它的布局也是基于列的。但是,当您将浏览器窗口的宽度从全屏缩小到非常窄的时候,注意会发生什么。可见列的数量将随着窗口变小而减少,并随着窗口增大而增多。

这种效应被称为 响应式Web 设计,因为 Web 页面会 响应 并调整设计来适应设备所要求的屏幕尺寸。现代 Web开发人员构建的网站可以无缝地支持从最小的手持设备到拥有最大屏幕的台式机或壁挂屏幕等各种设备。分别使用 http://m.* 和http://www.* URL 为智能手机、平板电脑、笔记本等创建专门的、离散的网站,这种做法早已过时。

响应式 Web 设计 并不是 一个全能的解决方案;相反,它是“一个外观要求可以适应所有设备的网站”。您不需要选择用户访问网站所使用的设备类型,因此您的设计具备内置的灵活性,可以相应地进行自我调整。

许多流行网站(包括 Facebook 和 Instagram)更多地是通过移动设备而不是传统计算机来进行访问的。Twitter的用户群绝大多数是移动用户。Twitter 规范了其响应式 Web 设计策略并实现了与 Bootstrap 相同的开源化。Bootstrap 有12 列的布局,可以根据您用来定义列的 CSS 类进行缩小或放大。

请注意,MEAN.JS 应用程序中对 MongoDB、Express、AngularJS 和 Node.js 使用了四个列的布局,如图 1 所示。

图 1. Bootstrap 的列布局示例

现在查看 public/modules/core/views/home.client.view.html 中的源代码,如清单 4 所示,看看Bootstrap 的 12 列布局是什么样子的。

清单 4. public/modules/core/views/home.client.view.html

<div class="row"><div class="col-md-3"><h2><strong>M</strong>ongoDB</h2></div><div class="col-md-3"><h2><strong>E</strong>xpress</h2></div><div class="col-md-3"><h2><strong>A</strong>ngularJS</h2></div><div class="col-md-3"><h2><strong>N</strong>ode.js</h2></div>
</div>

如果您向一个父 div 添加 class="row",那么您可以向子div 添加 class="col-_xx_-_N_"属性来将它们分成几个列。_N_ 值必须介于 1 和 12之间,_xx_ 值取决于您希望优化布局的设备的尺寸:

  • xs 适用于极小设备(低于 768 像素宽)
  • sm 用于小型设备(768 和 991 像素之间)
  • md 适合中型设备(992 和 1,199 像素之间)
  • lg 适合大型设备(1,200 像素或更高)

查看 Bootstrap CSS 文档的 网格系统小节,了解有关的更多信息。

由于清单 4 中的每个列针对中型(md)设备进行了优化,因此如果在屏幕宽度低于 992 像素的设备上访问该页面,列将垂直堆叠而不是水平堆叠。将您的浏览器窗口变得足够窄来触发这一更改,如图 2 所示。

图 2. 移动设备上的响应式 Web 设计示例

现在,可以使用我们已经需到的知识,使用特定于 UGLI 的文本替换 home.client.view.html 中的标准文本。

首先,从 W3C HTML5 徽标页面 下载256 像素的 HTML5 徽标,并将其复制到public/modules/core/img/brand/HTML5_Logo_256.png。然后使用清单 5 中的源代码替换public/modules/core/views/home.client.view.html 中现有的 HTML。

清单 5. public/modules/core/views/home.client.view.html

<section data-ng-controller="HomeController"><div class="jumbotron text-center"><div class="row"><div class="col-md-4"><img alt="HTML5" class="img-responsive center-block" src="modules/core/img/brand/HTML5_Logo_256.png" /></div><div class="col-md-8"><h1>The HTML story is still being written.</h1> <h2><em>Come hear the latest chapter at the HTML5 Denver User Group.</em></h2></div></div></div>
</section>

在较宽的浏览器窗口中查看网站时,HTML5 徽标会出现在文本旁边,如图 3 所示。

图 3. 新的 UGLI 主页

当您将浏览器窗口变得足够窄时,徽标会出现在文本的上方,如图 4 所示。

图 4. 新的 UGLI 主页,它会出现在移动设备上

使用 Bootstrap 可以轻松地让您的网站对移动应用程序变得更友好,我在为客户构建每个新网站时都使用 Bootstrap 作为基础技术。

现在我们将要在 MEAN 堆栈中处理 CRUD。

基础 CRUD

Meetup.com 可以帮助我很好地管理用户组活动。但是,在某个活动结束后,就时间方面而言,该活动的重要性不如当天晚上的谈话。

换句话说,这个网站的一个用户用例就是:“下次会议要讨论什么?”Meetup.com 可以很好地满足这种用户用例。

第二个用户用例(“向我显示与 MEAN 堆栈有关的所有谈话,不管是什么时候发生的” )正是我准备通过 UGLI应用程序解决的用例。要实现这个用例,必须围绕一个新的名为 Talk 的模型对象创建一个 CRUD
基础架构。幸运的是,可以使用一个 Yeoman 生成器来实现这个基础架构。

在应用程序的根目录,输入 yo meanjs:crud-module talks。响应提示:

  1. 选择所有四个补充文件夹(css、img、directives 和 filters)。
  2. 回答 Yes,将 CRUD 模块链接添加到菜单。3. 当生成器询问要使用哪个菜单时,接受默认设置(topbar)。

清单 6 显示了交互式命令行序列。

清单 6. 使用 Yeoman 生成器生成一个新的 CRUD

模块

$ yo meanjs:crud-module talks
[?] Which supplemental folders would you like to include in your angular module?
css, img, directives, filters
[?] Would you like to add the CRUD module links to a menu? Yes
[?] What is your menu identifier? topbarcreate app/controllers/talks.server.controller.jscreate app/models/talk.server.model.jscreate app/routes/talks.server.routes.jscreate app/tests/talk.server.model.test.jscreate public/modules/talks/config/talks.client.routes.jscreate public/modules/talks/controllers/talks.client.controller.jscreate public/modules/talks/services/talks.client.service.jscreate public/modules/talks/tests/talks.client.controller.test.jscreate public/modules/talks/config/talks.client.config.jscreate public/modules/talks/views/create-talk.client.view.htmlcreate public/modules/talks/views/edit-talk.client.view.htmlcreate public/modules/talks/views/list-talks.client.view.htmlcreate public/modules/talks/views/view-talk.client.view.htmlcreate public/modules/talks/talks.client.module.js

在清单 6 中,请注意,生成器创建了服务器端基础架构(保存在 app 目录中):路由、一个控制器、一个模型和一个单元测试。它还在public/modules/talks 目录下构建了所有客户端工件。

您稍后将向 Talk 对象添加一些自定义字段。在此之前,在浏览器中访问网站,查看默认情况下会得到哪些内容。

单击右上角的 Signin 链接,输入本系列早些时候创建的用户名和密码,或者单击Signup 并创建一组新的凭证。

完成登录后,可以在左上角看到一个 Talks 菜单。从菜单中选择 New Talk打开一个 HTML 表单,其中提供了一个独立的 Name 字段,如图 5 所示。

图 5. 自定义之前的 New Talk 表单

这是一个良好的开端,但是要捕捉 Talk 的所有属性,您需要的不仅仅是一个简单文本。

添加新字段实现持久性

要向 Talk 添加新字段,必须编辑 6 个文件 — 四个用于显示,两个用于持久性:

  • app/models/talk.server.model.js
  • public/modules/controllers/talks.client.controller.js
  • public/modules/talks/views/create-talk.client.view.html
  • public/modules/talks/views/edit-talk.client.view.html
  • public/modules/talks/views/view-talk.client.view.html
  • public/modules/talks/views/list-talks.client.view.html

首先要处理持久性。解决方案一半用在服务器端,另一半用在客户端。

服务器端模型(在 app/models/talk.server.model.js中定义)是应用程序的原型。您将在其中命名字段,提供数据类型,验证规则等等。

客户端控制器(在 public/modules/controllers/talks.client.controller.js中定义)收集来自用户的数据输入,并通过 HTTP 请求将数据推到服务器。控制器还通过连接获得 JSON 数据,并提供给视图以用于演示。

此架构的一个有趣之处是对象模型永远不会离开服务器。对象是来自客户机的数据的具体化实现,并在 HTTP 响应中序列化到 JSON。

该应用程序有两个控制器(一个位于服务器端,另一个位于客户端),但是我们只关心客户端控制器。服务器端控制器只是将进入的 JSON推入到模型对象。因此在向模型添加额外字段时不需要对服务器端控制器做任何调整。客户端控制器要进行一些调整来容纳新的字段。

打开 app/models/talk.server.model.js,向服务器端模型添加新的字段,如清单 7 所示。您可以看到展开的
name 字段(如 图 5
所示),同时还定义了两个元数据字段:createduser

清单 7. app/models/talk.server.model.js

/*** Talk Schema*/
var TalkSchema = new Schema({name: {type: String,default: '',required: 'Please fill Talk name',trim: true},created: {type: Date,default: Date.now},user: {type: Schema.ObjectId,ref: 'User'}
});

这个基于 JSON 的模式无需多加解释。在定义新字段时,您可以指定数据类型、默认值和错误消息,以显示给必要的字段。您还可以做出许多其他优化。查看 Mongoosedocumentation,获得有关的更多信息。

descriptionpresenterslidesUrl添加新字段,如清单 8 所示。在本例中,descriptionpresenter都是必要字段。slidesUrl 字段是可选字段。

清单 8. app/models/talk.server.model.js

/*** Talk Schema*/
var TalkSchema = new Schema({name: {type: String,default: '',required: 'Please fill Talk name',trim: true},description: {type: String,default: '',required: 'Please fill Talk description',trim: true},  presenter: {type: String,default: '',required: 'Please fill Talk presenter',trim: true},slidesUrl: {type: String,default: '',trim: true},created: {type: Date,default: Date.now},user: {type: Schema.ObjectId,ref: 'User'}
});

此时,您的服务器端后端已经准备好接收新字段。现在您需要处理客户端控制器。打开public/modules/controllers/talks.client.controller.js,添加新的字段,如清单 9 所示。

清单 9. public/modules/controllers/talks.client.controller.js

// Create new Talk
$scope.create = function() {// Create new Talk objectvar talk = new Talks ({name: this.name,description: this.description,presenter: this.presenter,slidesUrl: this.slidesUrl});// Redirect after savetalk.$save(function(response) {$location.path('talks/' + response._id);}, function(errorResponse) {$scope.error = errorResponse.data.message;});// Clear form fieldsthis.name = '';this.description = '';this.presenter = '';this.slidesUrl = '';
};

$scope.create 函数中,表格字段将被聚集到一个 JSON对象,并被发送给服务器,以便实现持久存储。从模型向控制器添加相应的字段后,您就实现了持久存储。

现在我们要将注意力转移到演示层,这样用户就可以查看新字段并进行交互。

添加新字段以进行显示

查看 public/modules/talks/views/。有四个字段与 CRUD 生命周期有关:

  • create-talk.client.view.html
  • edit-talk.client.view.html
  • view-talk.client.view.html
  • list-talks.client.view.html

打开 create-talk.client.view.html,如清单 10 所示。

清单 10. 生成的

<section data-ng-controller="TalksController"><div class="page-header"><h1>New Talk</h1></div><div class="col-md-12"><form class="form-horizontal" data-ng-submit="create()" novalidate><fieldset><div class="form-group"><label class="control-label" for="name">Name</label><div class="controls"><input type="text" data-ng-model="name" id="name" class="form-control" placeholder="Name" required></div></div><div class="form-group"><input type="submit" class="btn btn-default"></div><div data-ng-show="error" class="text-danger"><strong data-ng-bind="error"></strong></div></fieldset></form></div>
</section>

将与 Name 有关的代码块复制三次,以便支持DescriptionPresenterslidesUrl,如清单 11 所示。我将 Description 字段设置为textarea,而不是一个简单的文本字段。同样,我从 slidesUrl 字段移除了required 属性,并将 input typetext修改为 url

清单 11. 更新

create-talk.client.view.html

<section data-ng-controller="TalksController"><div class="page-header"><h1>New Talk</h1></div><div class="col-md-12"><form class="form-horizontal" data-ng-submit="create()" novalidate><fieldset><div class="form-group"><label class="control-label" for="name">Name</label><div class="controls"><input type="text" data-ng-model="name" id="name" class="form-control" placeholder="Name" required></div></div><div class="form-group"><label class="control-label" for="description">Description</label><div class="controls"><textarea data-ng-model="description" id="description" class="form-control" placeholder="Description" required></textarea></div></div><div class="form-group"><label class="control-label" for="presenter">Presenter</label><div class="controls"><input type="text" data-ng-model="presenter" id="presenter" class="form-control" placeholder="Presenter" required></div></div><div class="form-group"><label class="control-label" for="slidesUrl">Slides</label><div class="controls"><input type="url" data-ng-model="slidesUrl" id="slidesUrl" class="form-control" placeholder="Slides Url"></div></div>                        <div class="form-group"><input type="submit" class="btn btn-default"></div><div data-ng-show="error" class="text-danger"><strong data-ng-bind="error"></strong></div></fieldset></form></div>
</section>

在 Web 浏览器中,您新修改的 New Talk 页面应当类似图 6 所示。

图 6. 自定义后的 New Talk 表单

如果对所做的更改感到满意,请打开 edit-talk.client.view.html 并执行相应的更改,如清单 12 所示。

清单 12. edit-talk.client.view.html

<div class="col-md-12"><form class="form-horizontal" data-ng-submit="update()" novalidate><fieldset><div class="form-group"><label class="control-label" for="name">Name</label><div class="controls"><input type="text" data-ng-model="talk.name" id="name" class="form-control" placeholder="Name" required></div></div><div class="form-group"><label class="control-label" for="description">Description</label><div class="controls"><textarea data-ng-model="talk.description" id="description" class="form-control" placeholder="Description" required></textarea></div></div><div class="form-group"><label class="control-label" for="presenter">Presenter</label><div class="controls"><input type="text" data-ng-model="talk.presenter" id="name" class="form-control" placeholder="Presenter" required></div></div><div class="form-group"><label class="control-label" for="slidesUrl">Slides</label><div class="controls"><input type="url" data-ng-model="talk.slidesUrl" id="name" class="form-control" placeholder="Slides Url"></div></div><div class="form-group"><input type="submit" value="Update" class="btn btn-default"></div><div data-ng-show="error" class="text-danger"><strong data-ng-bind="error"></strong></div></fieldset></form>
</div>

请注意,用于编辑的 HTML 与之前修改的创建表单稍微有些不同。在编辑时,您已经有了一个 Talk 对象,因此data-ng-model 属性将以完全限定的方式引用字段,比如用 talk.name 而不是
name。在 Web 浏览器中查看修改,如图 7 所示。

图 7. 自定义后的 Edit Talk 表单

view-talk.client.view.html 页面是对象的只读视图。用户在保存新的 Talk,更新现有的Talk 或从列表页面中选择 Talk 后将来到该视图。如清单 13 所示做出修改。

清单 13. edit-talk.client.view.html

<div class="page-header"><h1 data-ng-bind="talk.name"></h1><h2><em>by {{talk.presenter}} <span ng-if="talk.slidesUrl !== '' ">[<a href="{{talk.slidesUrl}}">slides</a>]</span></em></h2><p>{{talk.description}}</p>
</div>

前面提到 slidesUrl 是可选字段。在视图页面中,您将使用 ng-if指令有条件地显示字段(如果已填充)。在浏览器中查看页面,检查这一行为,如图 8 所示。

图 8. 自定义后的 View Talk 表单

List 视图是最后一个需要做出调整的视图。打开 list-talks.client.view.html 并如清单 14 所示进行修改。

清单 14. list-talks.client.view.html

<div class="list-group"><a data-ng-repeat="talk in talks" data-ng-href="#!/talks/{{talk._id}}" class="list-group-item"><h4 class="list-group-item-heading" data-ng-bind="talk.name"></h4><p><em>by {{talk.presenter}}</em></p></a>
</div>

请注意,这里使用 data-ng-repeat 指令显示了服务器返回的 talk 列表中的每个talk。在浏览器中查看结果,如图 9 所示。

图 9. 自定义后的 List Talks 表单

结束语

此时,您已经了解了 MEAN 堆栈交互的各个方面。您使用 Bootstrap 的响应式 Web 设计功能确保您的网站能够适应所有设备,而不仅限于传统的有 101 个键和鼠标的传统台式机。您已经领略了使用 Yeoman 生成器向应用程序添加新 CRUD模块的强大之处及其便利性。该生成器将原始工件放到正确的目录中,您只需要对它们进行自定义即可。

下载

范例代码:wa-mean3src.zip

原文出处:精通 MEAN: 使用 MEAN 和 UGLI CRUD 实现响应式 Web 设计

【全栈开发】精通 MEAN: 使用 MEAN 和 UGLI CRUD 实现响应式 Web 设计相关推荐

  1. 《响应式Web设计全流程解析》一1.2 静态设计稿舒适区

    本节书摘来异步社区<响应式Web设计全流程解析>一书中的第1章,第1.2节,作者: [美]Stephen Hay 译者: 余果 , 等 责编: 赵轩,更多章节内容可以访问云栖社区" ...

  2. 【全栈开发】精通 MEAN: MEAN 堆栈

    在 2002 年的一本著作中,David Weinberger 将发展迅速的 Web 内容描述成一个 小块松散组合(Small Pieces Loosely Joined).这个比喻让我印象深刻,因为 ...

  3. 什么是全栈工程师,为什么全栈开发用Python,Python web全栈开发到底有多高薪?

    我们经常听到全栈工程师这个词语.那么很多小伙伴还是不明所以,什么是全栈工程师?为什么全栈开发用Python?Python web全栈开发到底有多高薪? 一.什么是Python web全栈工程师? 全栈 ...

  4. 【哈士奇赠书活动 - 18期】-〖Flask Web全栈开发实战〗

    文章目录 ⭐️ 赠书活动 - <Flask Web全栈开发实战> ⭐️ 编辑推荐 ⭐️ 内容提要 ⭐️ 赠书活动 → 获奖名单 ⭐️ 赠书活动 - <Flask Web全栈开发实战& ...

  5. 响应式HTML网页开发,web开发中的响应式网页设计

    概念 响应式网页设计最初是由 Ethan Marcotte 提出的一个概念:为什么一定要为每个用户群各自打造一套设计和开发方案?Web设计应该做到根据不同设备环境自动响应及调整.当然响应式Web设计不 ...

  6. 前端web:响应式Web开发优缺点总结

    因为越来越多的智能移动设备(mobile,tabletdevice)加入到互联网中来,移动互联网不再是独立的小网络了,而是成为了Internet的重要组成部分.响应式网络设计(RWD/AWD)的出现, ...

  7. python 3.x 全栈开发从入门到精通_GitHub - cxinping/PythonFullStack: 《Python 3 全栈开发从入门到精通》配套代码...

    <Python 3 全栈开发从入门到精通> 为什么要编写本书? 本书作者均来自开发和教育第一线,具备丰富的实际研发和培训经验.在对学校和企业的培训中,针对学校和企业的实际开发需要,定制了全 ...

  8. mybatis mapper.xml dtd_全栈开发踩坑之路4-用MyBatis实现服务

    1.前言 上一篇文章介绍了如何设计后端的Mysql数据库:Alex Wang:全栈开发踩坑之路3-MySql数据库设计,本文介绍如何用MyBatis实现后端服务. 本后端项目的Github地址(撰写中 ...

  9. 前端开发者正在被迫成为全栈开发人员

    当我在 WordPress 主题中发现 style.css 文件时,我就对前端开发一见钟情了.对我来说那就是(现在依然是)所有奇迹的源泉.我可以(并且能做到!)更改其中的几行内容,就完全改变网站的外观 ...

最新文章

  1. 【ArrayList】为什么java.util.concurrent 包里没有并发的ArrayList实现?
  2. Delphi调用java开发的WebService,传入参数出错
  3. javascript-XMLHttpRequest
  4. WPF使用X:Static做多语言支持
  5. 2021-春季学习-智能车技术创新与实践-Lesson 1
  6. 160个Crackme039
  7. js中text方法是啥意识_一盏茶的时间,快速捕获JS中常用的方法(细心整理,持续更新ing)...
  8. mysql 协议还原_mysql备份还原方案xtrabackup
  9. UG NX 12 视图操作
  10. PLC编程过程中需要注意的事项
  11. GBDT算法(最简单)
  12. 二维旋转矩阵公式推导
  13. 计算机外存储器有哪四个,计算机外存储器有哪些
  14. 安装spark的详细步骤
  15. 企鹅撞冰块Java游戏_亲子桌面游戏玩具 拯救企鹅敲打冰块玩法
  16. COMSOL电化学专题培训通知
  17. 背篼酥课堂-GPS定位(一) nodemcu 解析gps
  18. re模块剩余和subprocess模块
  19. 学生机房管理服务器系统设计,中小学机房管理系统设计与实现
  20. 哈希表链地址法解决冲突

热门文章

  1. concourse(concourse ci)
  2. flutter录音_Flutter 实现的仿微信语音录制功能
  3. Drm 例程2 双dumb buffer显示
  4. Java + OpenCV 实现素描特效(JavaCV)
  5. CAD中如何进行日照分析?CAD日照分析教程
  6. 数字化工厂建设的探索实践
  7. 推荐几款音频转文字软件给你
  8. 微软拟建统一通信平台 实施大范围战略扩张
  9. linux系统密码管理,Linux使用密码管理工具pass管理密码的方法
  10. Linux 中如何使用 Aria2 下载文件