NavigationView 是官方根据Container控件扩展而来的,由一个导航栏和一个card组成,具备导航和返回时自动销毁当前界面的功能,非常适合新手使用。

其中导航栏的代码如下:

 Ext.define('Ext.navigation.Bar', {
extend: 'Ext.TitleBar',
requires: ['Ext.Button', 'Ext.Spacer'],
isToolbar: true,
config: {
baseCls: Ext.baseCSSPrefix + 'toolbar',
cls: Ext.baseCSSPrefix + 'navigation-bar',
ui: 'dark',
title: null,
defaultType: 'button',
layout: {
type: 'hbox'
},
defaultBackButtonText: 'Back',
animation: {
duration: 300
},
useTitleForBackButtonText: null,
view: null,
android2Transforms: false,
backButton: {
align: 'left',
ui: 'back',
hidden: true
}
},
platformConfig: [{
theme: ['Blackberry'],
animation: false
}],
constructor: function(config) {
config = config || {};
if (!config.items) {
config.items = []
}
this.backButtonStack = [];
this.activeAnimations = [];
this.callParent([config])
},
applyBackButton: function(config) {
return Ext.factory(config, Ext.Button, this.getBackButton())
},
updateBackButton: function(newBackButton, oldBackButton) {
if (oldBackButton) {
this.remove(oldBackButton)
}
if (newBackButton) {
this.add(newBackButton);
newBackButton.on({
scope: this,
tap: this.onBackButtonTap
})
}
},
onBackButtonTap: function() {
this.fireEvent('back', this)
},
updateView: function(newView) {
var me = this,
backButton = me.getBackButton(),
innerItems,
i,
backButtonText,
item,
title,
titleText;
me.getItems();
if (newView) {
innerItems = newView.getInnerItems();
for (i = 0; i < innerItems.length; i++) {
item = innerItems[i];
title = (item.getTitle) ? item.getTitle() : item.config.title;
me.backButtonStack.push(title || '&nbsp;')
}
titleText = me.getTitleText();
if (titleText === undefined) {
titleText = ''
}
me.setTitle(titleText);
backButtonText = me.getBackButtonText();
if (backButtonText) {
backButton.setText(backButtonText);
backButton.show()
}
}
},
onViewAdd: function(view, item) {
var me = this,
backButtonStack = me.backButtonStack,
hasPrevious, title;
me.endAnimation();
title = (item.getTitle) ? item.getTitle() : item.config.title;
backButtonStack.push(title || '&nbsp;');
hasPrevious = backButtonStack.length > 1;
me.doChangeView(view, hasPrevious, false)
},
onViewRemove: function(view) {
var me = this,
backButtonStack = me.backButtonStack,
hasPrevious;
me.endAnimation();
backButtonStack.pop();
hasPrevious = backButtonStack.length > 1;
me.doChangeView(view, hasPrevious, true)
},
doChangeView: function(view, hasPrevious, reverse) {
var me = this,
leftBox = me.leftBox,
leftBoxElement = leftBox.element,
titleComponent = me.titleComponent,
titleElement = titleComponent.element,
backButton = me.getBackButton(),
titleText = me.getTitleText(),
backButtonText = me.getBackButtonText(),
animation = me.getAnimation() && view.getLayout().getAnimation(),
animated = animation && animation.isAnimation && view.isPainted(),
properties,
leftGhost,
titleGhost,
leftProps,
titleProps;
if (animated) {
leftGhost = me.createProxy(leftBox.element);
leftBoxElement.setStyle('opacity', '0');
backButton.setText(backButtonText);
backButton[hasPrevious ? 'show': 'hide']();
titleGhost = me.createProxy(titleComponent.element.getParent());
titleElement.setStyle('opacity', '0');
me.setTitle(titleText);
properties = me.measureView(leftGhost, titleGhost, reverse);
leftProps = properties.left;
titleProps = properties.title;
me.isAnimating = true;
me.animate(leftBoxElement, leftProps.element);
me.animate(titleElement, titleProps.element,
function() {
titleElement.setLeft(properties.titleLeft);
me.isAnimating = false;
me.refreshTitlePosition()
});
if (Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms()) {
leftGhost.ghost.destroy();
titleGhost.ghost.destroy()
} else {
me.animate(leftGhost.ghost, leftProps.ghost);
me.animate(titleGhost.ghost, titleProps.ghost,
function() {
leftGhost.ghost.destroy();
titleGhost.ghost.destroy()
})
}
} else {
if (hasPrevious) {
backButton.setText(backButtonText);
backButton.show()
} else {
backButton.hide()
}
me.setTitle(titleText)
}
},
measureView: function(oldLeft, oldTitle, reverse) {
var me = this,
barElement = me.element,
newLeftElement = me.leftBox.element,
titleElement = me.titleComponent.element,
minOffset = Math.min(barElement.getWidth() / 3, 200),
newLeftWidth = newLeftElement.getWidth(),
barX = barElement.getX(),
barWidth = barElement.getWidth(),
titleX = titleElement.getX(),
titleLeft = titleElement.getLeft(),
titleWidth = titleElement.getWidth(),
oldLeftX = oldLeft.x,
oldLeftWidth = oldLeft.width,
oldLeftLeft = oldLeft.left,
useLeft = Ext.browser.is.AndroidStock2 && !this.getAndroid2Transforms(),
newOffset,
oldOffset,
leftAnims,
titleAnims,
omega,
theta;
theta = barX - oldLeftX - oldLeftWidth;
if (reverse) {
newOffset = theta;
oldOffset = Math.min(titleX - oldLeftWidth, minOffset)
} else {
oldOffset = theta;
newOffset = Math.min(titleX - barX, minOffset)
}
if (useLeft) {
leftAnims = {
element: {
from: {
left: newOffset,
opacity: 1
},
to: {
left: 0,
opacity: 1
}
}
}
} else {
leftAnims = {
element: {
from: {
transform: {
translateX: newOffset
},
opacity: 0
},
to: {
transform: {
translateX: 0
},
opacity: 1
}
},
ghost: {
to: {
transform: {
translateX: oldOffset
},
opacity: 0
}
}
}
}
theta = barX - titleX + newLeftWidth;
if ((oldLeftLeft + titleWidth) > titleX) {
omega = barX - titleX - titleWidth
}
if (reverse) {
titleElement.setLeft(0);
oldOffset = barX + barWidth - titleX - titleWidth;
if (omega !== undefined) {
newOffset = omega
} else {
newOffset = theta
}
} else {
newOffset = barX + barWidth - titleX - titleWidth;
if (omega !== undefined) {
oldOffset = omega
} else {
oldOffset = theta
}
newOffset = Math.max(titleLeft, newOffset)
}
if (useLeft) {
titleAnims = {
element: {
from: {
left: newOffset,
opacity: 1
},
to: {
left: titleLeft,
opacity: 1
}
}
}
} else {
titleAnims = {
element: {
from: {
transform: {
translateX: newOffset
},
opacity: 0
},
to: {
transform: {
translateX: titleLeft
},
opacity: 1
}
},
ghost: {
to: {
transform: {
translateX: oldOffset
},
opacity: 0
}
}
}
}
return {
left: leftAnims,
title: titleAnims,
titleLeft: titleLeft
}
},
animate: function(element, config, callback) {
var me = this,
animation;
element.setLeft(0);
config = Ext.apply(config, {
element: element,
easing: 'ease-in-out',
duration: me.getAnimation().duration || 250,
preserveEndState: true
});
animation = new Ext.fx.Animation(config);
animation.on('animationend',
function() {
if (callback) {
callback.call(me)
}
},
me);
Ext.Animator.run(animation);
me.activeAnimations.push(animation)
},
endAnimation: function() {
var activeAnimations = this.activeAnimations,
animation, i, ln;
if (activeAnimations) {
ln = activeAnimations.length;
for (i = 0; i < ln; i++) {
animation = activeAnimations[i];
if (animation.isAnimating) {
animation.stopAnimation()
} else {
animation.destroy()
}
}
this.activeAnimations = []
}
},
refreshTitlePosition: function() {
if (!this.isAnimating) {
this.callParent()
}
},
getBackButtonText: function() {
var text = this.backButtonStack[this.backButtonStack.length - 2],
useTitleForBackButtonText = this.getUseTitleForBackButtonText();
if (!useTitleForBackButtonText) {
if (text) {
text = this.getDefaultBackButtonText()
}
}
return text
},
getTitleText: function() {
return this.backButtonStack[this.backButtonStack.length - 1]
},
beforePop: function(count) {
count--;
for (var i = 0; i < count; i++) {
this.backButtonStack.pop()
}
},
doSetHidden: function(hidden) {
if (!hidden) {
this.element.setStyle({
position: 'relative',
top: 'auto',
left: 'auto',
width: 'auto'
})
} else {
this.element.setStyle({
position: 'absolute',
top: '-1000px',
left: '-1000px',
width: this.element.getWidth() + 'px'
})
}
},
createProxy: function(element) {
var ghost, x, y, left, width;
ghost = element.dom.cloneNode(true);
ghost.id = element.id + '-proxy';
element.getParent().dom.appendChild(ghost);
ghost = Ext.get(ghost);
x = element.getX();
y = element.getY();
left = element.getLeft();
width = element.getWidth();
ghost.setStyle('position', 'absolute');
ghost.setX(x);
ghost.setY(y);
ghost.setHeight(element.getHeight());
ghost.setWidth(width);
return {
x: x,
y: y,
left: left,
width: width,
ghost: ghost
}
}
});

可以看出他是继承于一个TitleBar,中间为标题,左侧有一个默认的返回按钮这些代码看似复杂,其实逻辑很简单。

他的主要作用就是监听返回按钮,为其添加一个back自定义事件。并且通过this.backButtonStack这个数组来储存标题显示记录。

在返回时动态更新标题栏,并且有切换的动画效果。由于标题长短不一,所以整个导航栏的代码大部分都是来处理切换动画了。

实际上它的核心方法只有:

constructor:进行配置的初始化处理

applyBackButton:动态创建返回按钮

updateBackButton:动态更新返回按钮,并且添加监听(触发onBackButtonTap)

onBackButtonTap:将返回按钮的点击转换为自定义事件back

updateView:更新导航栏按钮,标题。NavigationView中更新视图时会触发它

onViewAdd:添加新的历史记录。NavigationView中添加新的视图时会触发它

onViewRemove:移除当前的历史记录,NavigationView中移除视图时会触发它

doChangeView:视图改变后处理标题,返回按钮。大部分代码其实是处理切换动画效果

getBackButtonText:获取返回按钮的text值,如果useTitleForBackButtonText为ture就需要它来处理

getTitleText:获取最后一个标题

beforePop:点返回按钮时处理this.backButtonStack这个数组

我们如果想要重写它可以注意这些方法,其他的都是用来处理动画效果的。个人觉得没必要有这些方法,会影响性能

NavigationView代码如下:

 Ext.define('Ext.navigation.View', {
extend: 'Ext.Container',
alternateClassName: 'Ext.NavigationView',
xtype: 'navigationview',
requires: ['Ext.navigation.Bar'],
config: {
baseCls: Ext.baseCSSPrefix + 'navigationview',
navigationBar: {
docked: 'top'
},
defaultBackButtonText: 'Back',
useTitleForBackButtonText: false,
layout: {
type: 'card',
animation: {
duration: 300,
easing: 'ease-out',
type: 'slide',
direction: 'left'
}
}
},
platformConfig: [{
theme: ['Blackberry'],
navigationBar: {
splitNavigation: true
}
}],
initialize: function() {
var me = this,
navBar = me.getNavigationBar();
if (navBar) {
navBar.on({
back: me.onBackButtonTap,
scope: me
});
me.relayEvents(navBar, 'rightbuttontap');
me.relayEvents(me, {
add: 'push',
remove: 'pop'
})
}
var layout = me.getLayout();
if (layout && !layout.isCard) {
Ext.Logger.error('The base layout for a NavigationView must always be a Card Layout')
}
},
applyLayout: function(config) {
config = config || {};
return config
},
onBackButtonTap: function() {
this.pop();
this.fireEvent('back', this)
},
push: function(view) {
return this.add(view)
},
pop: function(count) {
if (this.beforePop(count)) {
return this.doPop()
}
},
beforePop: function(count) {
var me = this,
innerItems = me.getInnerItems();
if (Ext.isString(count) || Ext.isObject(count)) {
var last = innerItems.length - 1,
i;
for (i = last; i >= 0; i--) {
if ((Ext.isString(count) && Ext.ComponentQuery.is(innerItems[i], count)) || (Ext.isObject(count) && count == innerItems[i])) {
count = last - i;
break
}
}
if (!Ext.isNumber(count)) {
return false
}
}
var ln = innerItems.length,
toRemove;
if (!Ext.isNumber(count) || count < 1) {
count = 1
}
count = Math.min(count, ln - 1);
if (count) {
me.getNavigationBar().beforePop(count);
toRemove = innerItems.splice( - count, count - 1);
for (i = 0; i < toRemove.length; i++) {
this.remove(toRemove[i])
}
return true
}
return false
},
doPop: function() {
var me = this,
innerItems = this.getInnerItems();
me.remove(innerItems[innerItems.length - 1]);
if (innerItems.length < 3 && this.$backButton) {
this.$backButton.hide()
}
if (this.$titleContainer) {
if (!this.$titleContainer.setTitle) {
Ext.Logger.error('You have selected to display a title in a component that does not support titles in NavigationView. Please remove the `title` configuration from your NavigationView item, or change it to a component that has a `setTitle` method.')
}
var item = innerItems[innerItems.length - 2];
this.$titleContainer.setTitle((item.getTitle) ? item.getTitle() : item.config.title)
}
return this.getActiveItem()
},
getPreviousItem: function() {
var innerItems = this.getInnerItems();
return innerItems[innerItems.length - 2]
},
updateUseTitleForBackButtonText: function(useTitleForBackButtonText) {
var navigationBar = this.getNavigationBar();
if (navigationBar) {
navigationBar.setUseTitleForBackButtonText(useTitleForBackButtonText)
}
},
updateDefaultBackButtonText: function(defaultBackButtonText) {
var navigationBar = this.getNavigationBar();
if (navigationBar) {
navigationBar.setDefaultBackButtonText(defaultBackButtonText)
}
},
applyNavigationBar: function(config) {
if (!config) {
config = {
hidden: true,
docked: 'top'
}
}
if (config.title) {
delete config.title;
Ext.Logger.warn("Ext.navigation.View: The 'navigationBar' configuration does not accept a 'title' property. You set the title of the navigationBar by giving this navigation view's children a 'title' property.")
}
config.view = this;
config.useTitleForBackButtonText = this.getUseTitleForBackButtonText();
if (config.splitNavigation) {
this.$titleContainer = this.add({
docked: 'top',
xtype: 'titlebar',
ui: 'light',
title: this.$currentTitle || ''
});
var containerConfig = (config.splitNavigation === true) ? {}: config.splitNavigation;
this.$backButtonContainer = this.add(Ext.apply({
xtype: 'toolbar',
docked: 'bottom'
},
containerConfig));
this.$backButton = this.$backButtonContainer.add({
xtype: 'button',
text: 'Back',
hidden: true,
ui: 'back'
});
this.$backButton.on({
scope: this,
tap: this.onBackButtonTap
});
config = {
hidden: true,
docked: 'top'
}
}
return Ext.factory(config, Ext.navigation.Bar, this.getNavigationBar())
},
updateNavigationBar: function(newNavigationBar, oldNavigationBar) {
if (oldNavigationBar) {
this.remove(oldNavigationBar, true)
}
if (newNavigationBar) {
this.add(newNavigationBar)
}
},
applyActiveItem: function(activeItem, currentActiveItem) {
var me = this,
innerItems = me.getInnerItems();
me.getItems();
if (!me.initialized) {
activeItem = innerItems.length - 1
}
return this.callParent([activeItem, currentActiveItem])
},
doResetActiveItem: function(innerIndex) {
var me = this,
innerItems = me.getInnerItems(),
animation = me.getLayout().getAnimation();
if (innerIndex > 0) {
if (animation && animation.isAnimation) {
animation.setReverse(true)
}
me.setActiveItem(innerIndex - 1);
me.getNavigationBar().onViewRemove(me, innerItems[innerIndex], innerIndex)
}
},
doRemove: function() {
var animation = this.getLayout().getAnimation();
if (animation && animation.isAnimation) {
animation.setReverse(false)
}
this.callParent(arguments)
},
onItemAdd: function(item, index) {
if (item && item.getDocked() && item.config.title === true) {
this.$titleContainer = item
}
this.doItemLayoutAdd(item, index);
var navigaitonBar = this.getInitialConfig().navigationBar;
if (!this.isItemsInitializing && item.isInnerItem()) {
this.setActiveItem(item);
if (navigaitonBar) {
this.getNavigationBar().onViewAdd(this, item, index)
}
if (this.$backButtonContainer) {
this.$backButton.show()
}
}
if (item && item.isInnerItem()) {
this.updateTitleContainerTitle((item.getTitle) ? item.getTitle() : item.config.title)
}
if (this.initialized) {
this.fireEvent('add', this, item, index)
}
},
updateTitleContainerTitle: function(title) {
if (this.$titleContainer) {
if (!this.$titleContainer.setTitle) {
Ext.Logger.error('You have selected to display a title in a component that does not support titles in NavigationView. Please remove the `title` configuration from your NavigationView item, or change it to a component that has a `setTitle` method.')
}
this.$titleContainer.setTitle(title)
} else {
this.$currentTitle = title
}
},
reset: function() {
return this.pop(this.getInnerItems().length)
}
});

这些代码就是核心了,作用如下:

initialize:初始化,为导航栏的返回事件添加监听(触发onBackButtonTap方法),为add和romove方法添加监听

onBackButtonTap:点击返回按钮时触发,触发pop方法,并且添加自定义事件。

push:其实就是调用add方法,添加新视图

pop:会调用beforePop方法和doPop方法

beforePop:有时候不止移除一项,这里的逻辑很复杂

doPop:移除card中最后一项

getPreviousItem:获取倒数第二项

updateUseTitleForBackButtonText:作用顾名思义

updateDefaultBackButtonText:同上

applyNavigationBar:创建导航栏而已,别怕代码多

updateNavigationBar:更新导航栏

applyActiveItem:为什么card始终显示最后一项,就是因为重写了它

doResetActiveItem:点击返回按钮时反转切换动画的

doRemove:调用remove方法后会触发也是反转切换动画

onItemAdd:项第一次被添加到card中触发,一系列的逻辑处理

updateTitleContainerTitle:顾名思义

reset:清空所有项,不过这里逻辑比较复杂。会调用pop方法

http://www.cnblogs.com/mlzs/p/3376399.html这里我有对代码进行一些注释,可以参考一下。

下面说说用法,个人推荐先创建一个视图继承它,如下:

 Ext.define('app.view.Main', {
extend: 'Ext.NavigationView',
xtype: 'main',
config: {
navigationBar: {
backButton: {
iconCls: 'arrow_left',
ui: '',
cls: 'back'
}
},
cls: 'cardPanel'
}
});

app.js中初始化它:

     launch: function () {
// Destroy the #appLoadingIndicator element
Ext.fly('appLoadingIndicator').destroy();
// Initialize the main view
Ext.Viewport.add(Ext.create('app.view.Main'));
}

单独的main控制层监听它:

         //引用
refs: {
main: 'main'
}

添加一个路由监听

         routes: {
'redirect/:view': 'showView'
}

main控制层中写一个方法:

     //展示页面
showView: function (view, isPop) {
var main = this.getMain(),
view = Ext.create(xtype);
main.push(view, params);
}

任何控制层之中都可以通过如下方法触发这个方法

 this.redirectTo('redirect/xtype');

当然这个只是简单的用法,有兴趣的可以看看http://www.cnblogs.com/mlzs/p/3498846.html,里面有免费视频听,也可以参考官方的示例。

值得注意的是,NavigationView作为一个容器,虽然是继承于Container,里面的tpl,data,html这些都是不能使用的,他的布局也不能随便更改。

sencha touch NavigationView的更多相关文章

  1. sencha touch NavigationView 源码详解(注释)

    Ext.define('Ext.navigation.View', { extend: 'Ext.Container', alternateClassName: 'Ext.NavigationView ...

  2. sencha touch NavigationView 嵌套 TabPanel 的问题

    在st2.1之中,在NavigationView视图之中在嵌套一个TabPanel会有以下问题 下面我们监控TabPanel的activate事件和activeitemchange事件 会发现当首页加 ...

  3. 跟我一起玩转Sencha Touch 移动 WebApp 开发(一)

    1.目录 移动框架简介,为什么选择Sencha Touch? 环境搭建 创建项目框架,框架文件简介 创建简单Tabpanel案例 自定义图标的方式 WebApp产品测试和发布 HTML5离线缓存 发布 ...

  4. 再探 Ext JS 6 (sencha touch/ext升级版) 变化篇 (编译命令、滚动条、控制层、模型层、路由)

    从sencha touch 2.4.2升级到ext js 6,cmd版本升级到6.0之后发生了很多变化 首先从cmd说起,cmd 6 中sencha app build package不能使用了,se ...

  5. Sencha Touch xtype对应的class

    Sencha Touch 2的有效xtype xtype Class ----------------- --------------------- actionsheet Ext.ActionShe ...

  6. sencha touch 常见问题解答(1-25)

    欢迎留言补充,持续更新中... 1.sencha touch 是什么? 答:Sencha touch框架是世界上第一个基于HTML 5的移动应用框架.它可以让你的Web应用看起来像网络应用.美丽的用户 ...

  7. Sencha Touch 实战开发培训 视频教程 第二期 第一节

    经过忙碌的准备,终于在2014.4.7晚上8:10分开课. 本来预定在8点开课的,不过电脑出了点问题,推迟了. 本期培训一共八节,前两节免费,后面的课程需要付费才可以观看. 本节内容: 了解Sench ...

  8. Sencha Touch 实战开发培训 视频教程 第二期 基础提高篇 预告

    “抛砖网”国内首家首创纯实战型培训机构,提供在线培训.技术指导及答疑! 团队通过360全方位技术培训+1度手把手技术指导,保证每一个学员能最快掌握实际工作技能: 让每一个学员都能站在我们的肩膀上,展翅 ...

  9. sencha touch Container

    Container控件是我们在实际开发中最常用的控件,大部分视图控件都是继承于Container控件,了解此控件能帮我们更好的了解sencha touch. layout是一个很重要的属性,能够帮助你 ...

随机推荐

  1. PHP中全局变量的使用global和$GLOBALS[]

    From: http://blog.csdn.net/happyqyt/article/details/7219889 用PHP开发项目,不可避免的会使用到全局变量,比如一些网站的配置信息,全站通用, ...

  2. JS性能细节学习初步总结

    1,声明变量要赋初值2,尽量避免声明全局变量,可以减少与系统的重名3,当编写大量js代码时,难免会遇到命名冲突,这是可以通过模拟命名空间方式     来避免冲突4,尽量避免使用全局变量,搜索全局变量是 ...

  3. UNIX环境编程学习笔记(27)——多线程编程(二):控制线程属性

    lienhua342014-11-09 1 线程属性概括 POSIX 线程的主要属性包括 scope 属性.detach 属性.堆栈地址.堆栈大小.优先级.在头文件 pthread.h 中定义了结构体 ...

  4. synchronized关键字的用法总结

    synchronized关键字主要有以下这3种用法: 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 修饰代 ...

  5. 8. Django系列之上传文件与下载-djang为服务端,requests为客户端

    preface 运维平台新上线一个探测功能,需要上传文件到服务器上和下载文件从服务器上,那么我们就看看requests作为客户端,django作为服务器端怎么去处理? 对于静态文件我们不建议通过dja ...

  6. Oracle12c 在 Ubuntu 12.04 ~ 18.04 的安装注意事项

    必须的注意点: 1:/bin/sh 必须指向 bash or ksh 2:/usr/lib64 可以忽略的事情: 1:gcc 版本无所谓 2:libstdc++5 无需安装 3:libaio 版本无所 ...

  7. HTML5标签canvas图像处理

    摘要: canvas可以读取图片后,使用drawImage方法在画布内进行重绘.本文介绍canvas的图像处理 drawImage drawImage() 方法在画布上绘制图像.画布或视频.drawI ...

  8. 分分钟学会GCD

    2014 什么是GCD Grand Central Dispatch (GCD)是异步运行任务的技术之中的一个.一般将应用程序中记述的线程管理用的代码在系统级中实现.因为线程管理是作为系统的一部分来实 ...

  9. 让树莓派自动上报IP地址到邮箱,二代B

    由于我使用树莓派的场景大多数是在没有显示器.只用terminal连接它的情况下,所以,它的IP地址有时会在重启之后变掉(DHCP的),导致我无法通过terminal连接上它.然后我又要很麻烦地登录路由 ...

  10. 【ArcGIS】ArcGIS Data Store配置

    一.错误提示 Unable to configure the ArcGIS Data Store with the GIS Server. Please make sure that the GIS ...