资讯详情 Sencha Ext JS 7.8 后台管理系统开发:从安装到组件封装的完整实践
📅 2026/10/14 4:02:45
简介面向具备 JavaScript 基础、希望构建企业级应用的开发者《Sencha Ext JS 7.8入门指南》以安装、项目创建、基本配置和视图/组件使用为主线配合数据网格、柱状图、表单捕获等示例系统讲解 Ext JS 核心概念与 MVC 架构。从获取许可证、安装 Sencha Command、npm 环境管理到 IDE 选择与项目结构配置均有覆盖适合前端团队作为快速上手企业级框架的参考读物。资源包内为 1 份 PDF 电子文档容量约 2.65MB内容按目录模块组织便于按章节查阅和反复对照练习。目前已有 191 人学习下载文档除基础概念外还提供详细的代码示例、操作步骤和项目构建说明其中 Trial 与 Commercial 许可项目的创建流程分别给出模板选择与构建步骤清晰便于按自身授权情况直接落地。同时推荐官方文档、视频教程和社区支持等扩展资源可帮助读者从零掌握数据网格、图表、表单等常用组件的用法并在实际项目中灵活应用。1. Sencha Ext JS 7.8 不是给个人站长的框架先想清楚这三个问题我第一次见到 Ext JS 7.8 的工程第一反应是“这都什么年代了还有人用这套”。后来在某公司接手一个数据管理平台——左侧菜单、主从表联动、几十个字段的动态表单、还有一堆汇总图表——用通用前端框架从零拼组件少说一个月而 Ext JS 的 Grid、Form、Chart 是开箱即用的。这个框架的典型战场是后台管理系统和数据密集型应用不是个人博客也不是营销页。决定要不要入坑先看三件事团队能不能接受商业授权成本业务是不是以表格、表单、复杂交互为主有没有人愿意啃 Sencha Cmd 这条工具链。这篇笔记就把从安装 SDK、用模板生成工程到手动创建和封装组件的全过程过一遍你跟着跑通一个最小应用再判断值不值得投入。2. 安装与首次启动把 Sencha Cmd 和 SDK 配到能生成项目2.1 先分清 Sencha Cmd、SDK 和 Workspace 的分工很多人拿到 Ext JS 的第一个动作是下载 zip 解压扔进项目里这个思路在其他框架下没问题在 Ext JS 这里会立刻卡住。Ext JS 的工程不是“引入一个 js 文件就能跑”的形态至少在 7.8 这个时代不是。它的类系统按命名空间动态加载主题是 Sass 编译产物连 index.html 都是靠 Microloader 生成的清单来驱动这三样东西都要靠 Sencha Cmd 这根指挥棒。Cmd 是 Java 写的命令行工具链负责脚手架生成、依赖解析、Sass 主题编译和产物打包。SDK 是框架本体包含类库、主题源文件和自带示例。Workspace 是包在应用外面的容器一个 workspace 可以装多个应用共享同一份 SDK 和公共组件包。关系可以简化成Cmd 是工具SDK 是原料workspace 是车间。为什么不能绕过 Cmd技术上可以但你会同时丢掉浏览器端的动态类加载清单、主题编译管线、生产构建的压缩与指纹。更现实的是社区里几乎所有关于 Ext JS 的排错方案都是建立在 Cmd 工程之上的脱离 Cmd 等于把自己丢进黑匣子。提示安装前先确认授权。Ext JS 是商业授权框架试用版会在界面上带水印团队评估时把授权费用算进成本别等项目跑了一半才补。2.2 下载与 PATH 配置三行命令验证环境下载环节没什么可炫技的去 Sencha 官方下载页拿 Sencha Cmd 安装包和 Ext JS 7.8 SDK 压缩包。Cmd 装完是一个独立目录内部按版本号分层Windows 安装包通常会自动写 PATHmacOS 和 Linux 下需要手动加。版本号以实际下载到的为准不要照抄别人的路径不同年份的 Cmd 目录命名规则不完全一样。# macOS / Linux 下把 Sencha Cmd 加入 PATH版本号按实际安装目录写 export PATH$HOME/bin/Sencha/Cmd/8.0.0.314:$PATH # 验证 Cmd 是否可用 sencha which # Cmd 依赖 Java 运行时顺手确认一下 java -version逻辑说明export只对当前终端会话生效建议写入~/.bashrc或~/.zshrc避免每次开新终端都补一遍。sencha which会输出当前生效的 Cmd 版本号如果提示 command not found八成是 PATH 写错了先检查路径里有没有多写或少写一层目录。java -version是确认 JVM 存在新版 Cmd 安装包会自带运行时但如果你装的是早期的 7.x Cmd系统里没有 Java 会在后续任何子命令上报错。参数说明Cmd 目录下的版本号子目录每次升级都会变PATH 里写死版本号是常见做法但升级后要记得同步修改否则你会对着旧版本排查一个已经修掉的问题。命令作用失败时看什么sencha which输出 Cmd 版本号PATH 配置错误sencha -sdk 路径 generate app生成应用骨架SDK 路径指向错误sencha app watch启动开发服务器并增量编译端口被占用、主题编译报错2.3 生成 Workspace 与应用一条命令链跑出第一个页面环境就绪后按下面顺序执行。我习惯先建 workspace再在 workspace 里生成应用这样以后加第二个应用时主题和公共包都不用重新折腾。# 1. 初始化工作区路径不存在会自动创建 sencha workspace init /data/workspace/myent-demo # 2. 进入工作区用 SDK 生成一个叫 MyEnt 的应用 cd /data/workspace/myent-demo sencha -sdk /opt/Sencha/ext-7.8.0 generate app MyEnt ./apps/myent-admin # 3. 进入应用目录启动开发模式 cd apps/myent-admin sencha app watch逻辑说明workspace init会生成 workspace.json 和 packages 目录generate app从 SDK 的模板把一套标准工程拷到./apps/myent-admin包括 app.json、app 目录、sass 目录和 index.html。sencha app watch是开发模式的核心命令它起一个本地静态服务器默认端口 1841同时监听源码变化做增量编译改一行保存浏览器刷新就能看到效果。参数说明-sdk后面跟的是 SDK 解压后的根目录里面应该有 ext-all.js 和 packages 目录不要指到 ext 子目录。MyEnt是应用名同时也是命名空间前缀它决定后面所有类的包名比如MyEnt.view.main.Main起名时别用带横杠的名字。./apps/myent-admin是应用目录多个应用就并列放在 apps 下。浏览器打开 http://localhost:1841看到带主题的空白主界面就说明安装链路通了。如果 watch 起不来十有八九是 SDK 路径写错或 Java 环境有问题把终端里的错误日志往上翻它比浏览器控制台更早暴露问题。3. 模板与工程骨架app.json、启动链和 MVVM 三件套3.1 app.json先读懂这几个配置项再动模板generate app生成的应用根目录下有个 app.json它就是整个工程的配置中心。模板长什么样、主题是哪套、依赖哪些本地包全在这里声明。先看一份最小化的{ name: MyEnt, namespace: MyEnt, type: modern, framework: ext, theme: theme-triton, requires: [], sdk: /opt/Sencha/ext-7.8.0, output: { base: ${workspace.dir}/build/${build.id}, page: index.html } }逻辑说明name是应用名也是产物文件名前缀namespace决定类的根命名空间type声明界面风格取向7.x 统一后的现代应用用 modern老项目迁移才用 classictheme指定主题requires里放需要引用的本地 package 名sdk指向 SDK 根目录生成工程时会被自动填入绝对路径。参数说明theme常见值有 theme-triton、theme-material、theme-classic 等。换主题只要改这里再重启 watchCmd 会重新编译 Sass不需要手改任何 CSS。output.base一般保持默认生产构建会用到。配置项作用容易踩的坑name应用名与产物前缀不要用横杠影响目录命名typemodern / classic7.x 新工程一律 moderntheme主题包名拼写错误不会立刻报错而是样式丢失requires本地 package 依赖漏写后 xtype 一直 UnknownsdkSDK 绝对路径换机器后要改建议写成环境变量3.2 启动链index.html 为什么是空的打开生成工程里的 index.html会发现 body 是空的。这不是模板没生成好而是 Ext JS 7.x 的标准做法界面全部运行时渲染入口只留一个引导脚本。!DOCTYPE html html head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 titleMyEnt/title script srcbootstrap.js>Ext.application({ name: MyEnt, extend: MyEnt.Application, requires: [MyEnt.view.main.Main], mainView: MyEnt.view.main.Main });逻辑说明这是整个应用的入口配置对象。extend指向应用主类requires声明首屏依赖mainView指定主视图的类名应用启动后会自动创建它并挂到视口上。Application 类里还有一个 launch 方法Ext.define(MyEnt.Application, { extend: Ext.app.Application, launch: function () { // 应用完全就绪后执行适合读取本地缓存、注册全局事件 Ext.GlobalEvents.fireEvent(app-ready); } });launch在首屏数据渲染完成后触发适合做不阻塞的初始化。耗时逻辑比如批量请求不要放在 launch 里会明显拖慢首屏。3.3 ViewModel 与 ViewController模板里最容易被忽略的绑定关系模板生成的主界面自带 MVVM 三件套视图类负责结构ViewModel 负责数据状态ViewController 负责交互逻辑。它们靠 alias 关联视图里写controller: main框架就去查 alias 为controller.main的类。下面这个例子是手动新建一个工作台视图完整演示三者关系。Ext.define(MyEnt.view.dashboard.Main, { extend: Ext.panel.Panel, xtype: dashboard-main, controller: dashboard, viewModel: { type: dashboard }, title: 工作台, tbar: [{ xtype: button, text: 刷新数据, handler: onRefreshClick }], items: [{ xtype: label, bind: {statusText} }] });对应的 ViewModel 类Ext.define(MyEnt.view.dashboard.MainViewModel, { extend: Ext.app.ViewModel, alias: viewmodel.dashboard, data: { statusText: 就绪 }, formulas: { welcome: function (get) { return get(user.name) 欢迎回来; } } });对应的 ViewController 类Ext.define(MyEnt.view.dashboard.MainViewController, { extend: Ext.app.ViewController, alias: controller.dashboard, onRefreshClick: function () { this.getViewModel().set(statusText, 刷新中……); } });逻辑说明bind: {statusText}是单向绑定语法把ViewModel.data.statusText同步到 label 的 text。按钮 handler 写成字符串onRefreshClick框架会自动在对应 ViewController 里找同名方法这是模板工程默认的行为约定比手写匿名函数更好排查。参数说明ViewModel 类的命名必须是“视图类名 ViewModel”且和视图类放在同一个包路径下ViewController 同理。alias 的前缀viewmodel.和controller.是硬约定写错不会报编译错误只会让绑定或事件静默失效这是新手最容易踩的翻车点。提示formulas 里的函数每次依赖项变化时重新计算适合做基于原始数据的派生展示不要在 data 里直接放需要计算的字段。4. 组件创建从 xtype 到自定义组件的完整路径4.1 为什么推荐声明式 xtype而不是到处 new页面里写new Ext.Button({ renderTo: ... })能用但这是老写法有三个问题组件被立即实例化无法享受框架的延迟渲染需要手动管理销毁否则页面跳转后组件还挂在 DOM 上renderTo 把渲染位置写死组件一旦被放进容器里就会渲染到错误位置。正确姿势是让容器通过 xtype 在渲染阶段按需创建组件。// 不推荐立即实例化 手动挂载 var btn new Ext.Button({ text: 旧写法, renderTo: Ext.getBody() }); // 推荐声明式交给容器在渲染时创建 Ext.create(Ext.panel.Panel, { title: 示例面板, items: [ { xtype: button, text: 新写法, handler: function () { /* ... */ } } ] });逻辑说明Ext.create适合创建“根组件”比如一个需要挂到 body 上的 Window而 items 数组里的子组件一律写 xtype 字符串。框架在父容器渲染时才实例化子组件父容器销毁时统一销毁这套生命周期是 Ext JS 组件体系的地基也是它与直接操作 DOM 的框架最本质的区别。参数说明xtype 是组件类的短名称Ext.Button的 xtype 是buttonExt.panel.Panel的 xtype 是panel。框架内部维护一张 xtype 到类的映射表自定义组件注册后同样能用这套机制。4.2 组合一个真实查询面板表单、Store 与 Grid 联动查询面板是后台系统里最常见的组件组合。拆成三块表单收集条件、Store 负责请求数据、Grid 负责展示。先建 StoreExt.define(MyEnt.store.OrderStore, { extend: Ext.data.Store, alias: store.orders, fields: [orderNo, amount, status, createDate], proxy: { type: ajax, url: /api/orders, reader: { type: json, rootProperty: data } }, autoLoad: false });逻辑说明alias: store.orders让视图里可以用store: { type: orders }引用它。fields 定义记录字段必须和接口返回的 JSON 字段对应。proxy 配置请求方式rootProperty: data告诉 Reader 从返回 JSON 的 data 字段里取数组这是后端常见的分页包装格式。参数说明autoLoad设为 false 是故意不自动加载。查询面板应该在用户点击查询后才发起请求避免页面一打开就无差别请求全量数据。接着建视图把表单和表格放进一个容器Ext.define(MyEnt.view.order.OrderView, { extend: Ext.container.Container, xtype: order-view, controller: order, layout: vbox, items: [{ xtype: form, reference: searchForm, layout: hbox, bodyPadding: 10, items: [{ xtype: textfield, name: orderNo, fieldLabel: 订单号, margin: 0 10 0 0 }, { xtype: datefield, name: beginDate, fieldLabel: 开始日期, margin: 0 10 0 0 }, { xtype: button, text: 查询, handler: onSearchClick }] }, { xtype: ordergrid, reference: resultGrid, flex: 1 }] });订单表格Ext.define(MyEnt.view.order.OrderGrid, { extend: Ext.grid.Panel, xtype: ordergrid, store: { type: orders }, columns: [ { text: 订单号, dataIndex: orderNo, width: 140 }, { text: 金额, dataIndex: amount, width: 100, align: right, renderer: usMoney }, { text: 状态, dataIndex: status, width: 80 } ] });逻辑说明columns 里 text 是表头dataIndex 对应 Store 字段名。renderer 可以是函数也可以是内置格式化字符串usMoney会把数字渲染成带货币符号的格式。reference: resultGrid是给组件起一个可供控制器查询的引用名配合getReferences()使用。控制器里的查询逻辑Ext.define(MyEnt.view.order.OrderViewController, { extend: Ext.app.ViewController, alias: controller.order, onSearchClick: function () { var grid this.getReferences(resultGrid); var form this.getReferences(searchForm); var store grid.getStore(); var values form.getValues(); store.getProxy().setExtraParam(orderNo, values.orderNo); store.getProxy().setExtraParam(beginDate, values.beginDate); store.load(); } });逻辑说明setExtraParam会把参数附加到 AJAX 请求的 query string 上这是查询类场景的标准做法比手动拼接 URL 可靠。form.getValues()返回所有带 name 字段的值对象name 属性在这一步是刚需。4.3 封装自定义组件config 的 getter/setter 与混入业务里经常要复用“带徽标的卡片”这类小组件。封装它的标准姿势是用 config 声明对外接口Ext.define(MyEnt.view.common.BadgeCard, { extend: Ext.panel.Panel, xtype: badgecard, layout: fit, config: { badgeText: 新 }, initComponent: function () { var me this; me.items [{ xtype: container, cls: badge-card-body, items: [{ xtype: label, cls: badge-tag, text: me.getBadgeText() }] }]; me.callParent(); }, updateBadgeText: function (value) { var tag this.el this.el.down(.badge-tag); if (tag) { tag.setHtml(value); } } });逻辑说明config声明了组件对外暴露的配置项框架会自动生成 getBadgeText / setBadgeText 两个方法并在 setter 触发时调用updateBadgeText钩子。外部代码执行setBadgeText(已读)就能更新界面这是 Ext JS 组件封装的标配模式。参数说明initComponent里先组装 items 再callParent是 Ext JS 5 之后的推荐顺序顺序反了可能导致 items 被父类覆盖。updateBadgeText里用this.el.down(.badge-tag)定位内部元素组件没渲染完成时 el 不存在所以要加判空。自定义 xtype 必须全小写类名用“命名空间.模块.功能”三段式避免和 SDK 内置类冲突。多个组件共用行为时用混入Ext.define(MyEnt.mixin.Resetable, { mixinId: resetable, resetForm: function () { this.getForm().reset(); } }); // 在表单组件里混入 Ext.define(MyEnt.view.order.SearchForm, { extend: Ext.form.Panel, mixins: [MyEnt.mixin.Resetable], xtype: searchform });mixinId是混入的短标识把混入类加进 mixins 数组后resetForm()就出现在组件实例上。适合封装一批组件共用的行为比如重置、导出、校验提示。5. 安装与组件阶段最常翻车的 5 个现场避坑排查5.1 页面白屏但控制台没报错现象打开 dev server 地址一片白F12 控制台干净得像没运行过。原因这类白屏多半不是代码问题而是 Microloader 的引导文件缺失。最常见的是手工改过 index.html把 bootstrap.js 删了或改了>find node_modules -type f -name theme-*.css | head -20拿到实际路径后把 import 语句写到入口文件所有代码之前避免样式加载晚于组件渲染导致闪烁。6. 进阶把业务组件抽成 Sencha Package在多个应用里复用当你手上有两个以上 Ext JS 应用并且都在用同一批业务组件时把组件抽成 package 是迟早的事。我见过一个同事把同一个查询面板在三个项目里复制了三份后来一个字段改动要同步三处改漏一处线上才暴露这就是没做 package 的代价。生成 package 在 workspace 根目录下执行sencha package generate my-widgets ./packages/my-widgets生成后的结构package.json 记录包名和版本号src 目录放组件类sass 目录放主题定制。把 BadgeCard 这类通用组件移进包的 src 里类名改成myWidgets.view.common.BadgeCard之类的命名空间然后在应用的 app.json 的 requires 数组里加上包名{ requires: [my-widgets] }重启sencha app watch应用里就能直接使用 my-widgets 里声明的 xtype。验证是否生效有个笨但有效的办法在组件类里暂时写一行 console.log 看会不会打印或者在生产构建后搜索自定义 xtype 是否进了产物sencha app build --production grep -r badgecard build/production/MyEnt/ | head我自己的习惯是一个新组件先在应用里写、跑通、踩完坑再抽进 package。抽包前先把组件的 config 接口想清楚接口丑的组件抽出去只会把坑带进更多工程。包内组件尽量不要直接依赖某个应用的 ViewModel 数据数据从配置项传进去这样复用面最大。这套路径跑顺之后新项目起步就是从 packages 里拖组件而不是重新写一遍。如果你正卡在“每个后台项目都要重写一遍表单和表格”这个循环里值得花一个迭代把第一版 package 抽出来后续的收益是持续吃到的。希望帮到你。本文还有配套的精品资源点击获取