完整项目
1. 设置 npm 镜像仓库
依赖 node10.x 及以上版本,我们默认您已经安装 node 环境,如果您本地没有安装 node 环境,请安装
外部用户
外部用户需要登录 npm 仓库,请联系客服索取 npm 账户信息,需要登录后才可以安装脚手架
npm login -registry=https://artifact.iquantex.com/repository/npm-local/ -scope=@gza
- Username: 【npm user】
- Password: 【npm password】
- Email: 【your email】如果您是首次使用 npm ,我们建议您设置 npm 国内镜像,否则可能会导致安装依赖很慢甚至失败
npm config set registry=https://registry.npm.taobao.org
内部用户
内部用户(宽拓员工)使用公司内部的 npm 源,无需执行以上登录操作
npm config set registry=http://10.116.18.70:8081/content/groups/npm-quantex-group
2. 全局安装 @gza/create-godzilla-app
不推荐用 yarn 安装,目前框架暂没有去验证 yarn 模式下是否可用
npm i -g @gza/create-godzilla-app@latest #
3. 使用gza
命令创建项目
创建项目
方法一:可直接使用 gza init [project name] 初始化 UI 应用
gza init godzilla-portal # 初始化UI应用,`godzilla-portal` is your project name
cd godzilla-portal
方法二:先创建应用目录,再使用 gza init 初始化
mkdir godzilla-portal #新建目录,`godzilla-portal` is your project name
cd godzilla-portal
gza init方法一和方法二都需要执行完2.配置项目的【配置 layout】的内容之后,再执行下面的命令,启动项目。
npm start
打开浏览器 http://127.0.0.1:8888 输入用户名密码(任意)即可进入系统
注意:在 MacOS 环境,如果出现如下错误,则参考此链接 https://github.com/schnerd/d3-scale-cluster/issues/7 修复
用 Create Godzilla APP 创建的 UI 项目,最开始只有 pages 目录
// 目录结构
app
-- pages
-- globals.d.ts
config
-- config.ts
package.json
... 其它配置文件随着项目的复杂度增加,会需要更多的目录,目前框架约定支持如下其它目录
// 目录结构
app
-- pages
-- commonents // 公共业务组件
-- layouts // 界面外观配置
-- utils // 公共 utils
-- styles // 公共样式
-- images // images 目录
config
-- config.ts
package.json
... 其它配置文件
- 配置项目
配置 layout
新增配置文件 app/layouts/index.ts,需要注意的是路径和文件名不可以自定义。 开发环境时,提供两种获取数据的方式,有两种设计方案,分别是: 方案一,一般是服务端没有提供接口,也没有提供接口文档的情况下,直接使用 Promise.resolve() 返回定义的数据; 方案二,当使用 API 调用请求方法时,可分为两种场景:一种已经提供了接口文档,但没有提供接口;另一种是已经提供接口或者是两者都提供了 具体案例如下:
- 方案一 使用方案一时不会依赖 mock 服务,开发人员使用 Promise.resolve() 返回按照约定的自定义的数据,以完成一些简单的功能。
import defaultLayouts from '@/containers/defaultLayouts';
import { cloneDeep } from 'lodash';
import { Util } from '@gza/quantex-utils';
// 这里需要先深拷贝一份配置
const layouts = cloneDeep(defaultLayouts);
/*
* 假设需要登录 GZA UI 系统,可以使用以下数据格式,所有字段不可更改:
* {
* code: "用户编码",
* name: "用户名",
* token : token
* }
*/
layouts.login.login = async (params) => {
params.loginPassword = Util.sha256Encode(params.loginPassword);
return Promise.resolve({
code: 200,
data: {
code: 'test',
name: 'test',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6I...6MTU4NzQzNTc2NX0.I5McYxR4y0luSTWeaFGDmV',
},
msg: '操作成功!',
});
};
// 此处只做覆盖,不做任何逻辑
layouts.login.logout = async () => {
return Promise.resolve();
};
// 此处只做覆盖,不做任何逻辑
layouts.login.getMicroApp = () => {
return Promise.resolve({ code: 200, data: { list: [] } });
};
/*
* GZA UI 系统菜单数据,可以使用以下格式,所有字段不可更改:
* 若是一级菜单需要按照以下格式,且父子菜单ID需要一致:
* {
* id: 子菜单ID,
* pId: 父菜单ID,
* icon: 菜单图标, (一级菜单需要)
* appId: APP ID,
* name: 菜单名称
* }
* 若不是是一级菜单需要按照以下格式:
* {
* id: 子菜单ID,
* pId: 父菜单ID,
* appId: APP ID,
* name: 菜单名称
* url: 菜单地址, (子菜单需要,appId/路径,路径是相对于 app/pages 的路径)
* }
*/
layouts.main.getUserMenu = () => {
return Promise.resolve({
code: 200,
data: {
list: [
{
id: 10,
pId: 10,
icon: 'chanpinguanli',
appId: 'portal',
name: '应用管理',
},
{
id: 11,
pId: 10,
appId: 'portal',
name: '应用管理',
url: 'portal/system/Application',
},
{
id: 12,
pId: 10,
appId: 'portal',
name: '菜单管理',
url: 'portal/system/Menu',
},
],
},
msg: '操作成功!',
});
};
export default layouts;方案二 使用 Util 工具库封装的 API 功能实现接口请求功能,使用方案二的场景有两种,分别是: 场景一,假设只提供了接口文档,未提供接口,开发人员可以依赖 mock 服务,实现接口功能,在本地按照约定创建数据。 场景二,假设已经提供接口或者是两者都提供,此时不需要依赖 mock 服务,直接从后端获取数据。
有两点需要注意: 一是两种场景的使用方式是一样的,因此以下只以一种场景做演示; 二是 mock 服务的优先级比后台优先级高,在使用重名接口的调用时 mock 优先访问,所以当使用场景二的时候,需要将 app/pages/_mock.(js|ts) 文件删除
假设开发人员的使用场景是场景一,那在开发之前需要在 app/pages 目录先建立 _mock.(ts|js) 文件,并添加接口数据,若是场景二,可直接跳过这一步。按照约定数据格式如下:
// app/pages/_mock.js
module.exports = {
'POST /auth/api/users/login': {
code: 200,
data: {
code: 'a',
roleCodes: 'admin',
loginTime: 1587435765100,
loginName: 'a',
name: 'a',
roleNames: '超级管理员',
exp: 1587522165,
token: 'eyJhbGciOiLo...uSTWeaFGDmVLEh8mIramo7HlWPwiWtgI',
},
msg: '操作成功!',
},
'PUT /auth/api/users/logout': {
code: 200,
data: {},
msg: '退出成功!',
},
'GET /auth/api/users/:code/menus': {
code: 200,
data: {
list: [
{
id: 10,
pId: 10,
icon: 'chanpinguanli',
appId: 'portal',
name: '菜单名称',
},
{
id: 11,
pId: 10,
appId: 'portal',
name: '应用管理',
url: 'portal/system/Application',
},
{
id: 12,
pId: 10,
appId: 'portal',
name: '菜单管理',
url: 'portal/system/Menu',
},
],
},
msg: '操作成功!',
},
};菜单规则:
- 设置一级菜单时,id 和 pId 需设置一致,且数值唯一。
- 设置 icon 时,是一级菜单需提供 icon 值,反之不设值。
- 设置 url 时,不是一级菜单需提供 url 值(格式 appID/路径,路径是相对于 app/pages 目录,如: portal/system/ServiceProxy ),反之不设值。
- 设置 appId 时,值必须和 app/config/config.ts 文件的属性 APP_ID 的值一致,且不可以设置为关键字 portal。
两种场景的代码格式是一样的,因此这里不做区分。
import defaultLayouts from '@/containers/defaultLayouts';
import { cloneDeep } from 'lodash';
import { Util, API } from '@gza/quantex-utils';
import appWindow from '@/common/appWindow';
// 这里需要先深拷贝一份配置
const layouts = cloneDeep(defaultLayouts);
// 两种场景直接使用 Util 工具库的 API 类生成一个实例,根据接口的请求类型调用对应请求方法即可获取到数据
// 比如 /auth/api/users/login 是一个 post 方法,直接使用 new API('auth').post('/api/users/login',params) 即可实现功能
// 场景一时,API 会获取本地的 mock 数据。场景二时,API获取后端数据。 但要注意,使用场景二的时候一定要删除 app/pages 下的 _mock.(ts|js) 文件
layouts.login.login = async (params) => {
params.loginPassword = Util.sha256Encode(params.loginPassword);
return new API('auth').post('/api/users/login', params);
};
layouts.login.logout = async () => {
return new API('auth').put('/api/users/logout');
};
// 此处只做覆盖,不做任何逻辑
layouts.login.getMicroApp = () => {
return Promise.resolve({ code: 200, data: { list: [] } });
};
layouts.main.getUserMenu = () => {
const userInfo = appWindow.userLocalStore.getItem('userInfo');
return new API('auth').get('/api/users/{code}/menus', {
code: userInfo.code,
});
};
框架默认的config.ts文件配置
module.exports = {
/** webpack配置修改 */
webpackConfig: {
// config 是项目的 webpack 配置,可修改
// 如果你对 webpack 不了解,我们不建议修改
chainWebpack: undefined,
/** webpack 别名 */
resolveAlias: {},
/** 打包文件复制 */
copyWebpack: [],
},
/** API配置 */
apiConfig: {
// 启动 mock 调试
isDebug: true,
// 代理服务,即访问接口域名会被替换成 proxyServer,isDebug 为 true 时有效(默认 /)
// http://godzilla-midway.sz.iquantex.com/auth/api/v1/users/login?_t=1596162460875
// 代理后 => /auth/api/v1/users/login?_t=1596162460875
proxyServer: '/',
// 基本路径,接口访问域名
base: '',
// 域,一般用于项目中有多个接口访问域名时
domain: {},
// url路径前缀
urlPrefix: '',
},
/** YAPI配置,只在开发环境时有效 */
yapiConfig: {
// 启动使用
enable: false,
// yapi网站路径,如:https://yapi.iquantex.com
host: '',
// 需要 mock 的项目 token
token: '',
// 启动自动更新接口功能
autoUpdate: false,
// 间隔时间,默认五分钟
interval: 5 * 60 * 1000,
},
/** webpack环境配置 */
definePlugin: {
// 正式环境是否只允许 SSO 登录(默认 true)
ENABLE_SSO: true,
// 是否支持系统自带的websocket(默认false)
ENABLE_WEBSOCKET: false,
// 应用ID
APP_ID: JSON.stringify('portal'),
// 应用名称
PROJECT_NAME: JSON.stringify('Godzilla Portal'),
},
/** 系统配置 */
systemConfig: {
// dev server 端口(默认8888)
devServerPort: 8888,
// 主题开发dev server端口(默认9999)
devThemeServerPort: 9999,
// 脚手架开发(默认false,即普通开发)
isScaffoldApp: false,
// 是否是门户入口(默认false)
isPortal: false,
// npm scope
npmScope: '',
// Godzilla框架License
godzillaLicenseKey: '',
// Ag-Grid组件License,详见:https://www.ag-grid.com/license-pricing.php
agGridLicenseKey: '',
},
/** 插件配置 */
pluginConfig: [],
/** 主题配置 */
themeConfig: {
defaultTheme: '',
mainTheme: '',
themes: [],
},
/** electron配置 */
electronConfig: {
// 启用Electron客户端(默认false)
enableElectron: false,
webSecurity:'',
autoUpdate:'',
},
};自定义配置 app/config/config.ts
// 注意!!修改该配置文件不会热更新,需要重新执行npm start
const npmScope = '@gza'; // 私有npm依赖的scope
const isDev = process.env.NODE_ENV === 'development';
const config:IConfig = {
webpackConfig: {
resolveAlias: {
'quantex-utils': `${npmScope}/quantex-utils`,
'quantex-design': `${npmScope}/quantex-design`,
'quantex-scripts': `${npmScope}/quantex-scripts`,
},
chainWebpack: webpackConfig => {}, // webpack配置修改
},
systemConfig: {
isPortal: true, // 是否是Portal应用(主应用)
devServerPort: 8888, // dev server 端口
enableElectron: false, // 是否使用Electron客户端
devThemeServerPort: 9999, // 主题开发dev server端口
isScaffoldApp: false, // 是否是脚手架
copyWebpack: [], // 详细使用参考: https://www.npmjs.com/package/copy-webpack-plugin
godzillaLicenseKey:'', // godzilla license
agGridLicenseKey:'', // ag grid license
},
apiConfig: {
isDebug: isDev,
proxyServer: '/',
base: isDev ? 'http://127.0.0.1:8889' : '', // 基本路径,接口访问域名
domain: {
'/auth': 'http://127.0.0.1:8080/auth' // 例如这里配置/auth接口转发到http://127.0.0.1:8080/auth服务
},
},
definePlugin: {
ENABLE_SSO: false, // 正式环境是否只允许SSO登录
APP_ID: JSON.stringify('pro'), // 应用ID
ENABLE_WEBSOCKET: false, // 是否支持websocket
PROJECT_NAME: JSON.stringify('Godzilla Scaffold') // 应用名称
},
themeConfig: {
defaultTheme: 'themeDark',
mainTheme: 'themeDark',
themes: [
{
name: '深色',
id: 'themeDark',
},
{
name: '浅色',
id: 'themeWhite',
},
],
},
pluginConfig: [ // 插件配置
[
'metrics', // 埋点插件
{
enable: true,
domain: 'http://10.16.18.166:32143',
url: '/api/v1/metrics',
method: 'POST',
env: 'DEV', // ST、UAT、PRD等
reportInterval: 10 * 1000, // 一分钟上报一次
package: '@gza/quantex-plugin-metrics',
},
],
[
'theme-dark', // 主题插件
{
enable: true,
package: '@gza/quantex-plugin-theme-dark',
},
]
],
};
打包和部署
打包
在项目根目录下执行命令即可打包您的 UI 应用
npm run dist
打包成功之后,会在根目录生成 dist 文件夹,里面就是构建打包好的文件,通常是 .js、.css、index.html 等静态文件,然后就可以将这些静态资源文件拷贝到 CDN、nginx 等静态资源服务器部署应用。
方案一
部署参考
请确保 config/config.ts 中 apiConfig.base 在正式环境(NODE_ENV=production)下为空
{
// ... 其它配置项
"apiConfig": {
"base": isDev ? "http://127.0.0.1:8889" : "" // 请注意这里,如果不是本地开发模式,则需要设置为空
}
}nginx.conf 配置参考,需要处理 history router 问题
http {
# ... 其它配置项
server {
# ... 其它配置项
location / {
root /usr/share/nginx/html; # 这里是nginx存放静态资源的目录,请根据自己环境配置
index index.html index.htm;
try_files $uri $uri/ @router;
}
location @router {
# 处理history router 参考:https://blog.csdn.net/qq_35267557/article/details/81182097
rewrite ^.*$ /index.html last;
}
#特殊处理部分svg资源问题
location ~ \/app\/.*\.svg$ {
rewrite ^\/app\/.*\/(.*)\.svg$ /assets/css/font/$1.svg last;
}
}
}方案二
Nginx(或者其它类似服务) 作为总网关,是客户端的唯一流量入口,所有的资源都在 Nginx 服务进行处理,如果是静态资源,则会转发到 CDN,如果是 Restful 接口,则会转发到 web 服务。
优点:
1. 部署简单、传统,稳定性高
2. 跟现有网关集成程度高,无需改造。
缺点:
1. 遵循 api 接口规范,所有的 api 都必须带上一个统一的前缀(例如:/api-gateway),这样在 Nginx 才好识别
2. 不支持动态新增子应用,每次新增子应用,需要修改 Nginx 配置并重启服务
3. 灵活度不是特别高,不过一般不需要那么高的灵活度配置参考
请确保 config/config.ts 中 apiConfig.base 在正式环境(NODE_ENV=production)时设置一个统一前缀
{
// ... 其它配置项
"apiConfig": {
"base": isDev ? "http://127.0.0.1:8889" : "/api-gateway" // 请注意这里,如果不是本地开发模式,需要设置一个api前缀,用于在nginx中识别并转发,这样业务代码就不需要单独增加前缀了
}
}nginx.conf 配置参考,除了需要处理 history router 问题,还要处理其它子系统的资源转发
http {
# ... 其它配置项
server {
# ... 其它配置项
location / {
root /usr/share/nginx/html; # 这里是nginx存放静态资源的目录,请根据自己环境配置
index index.html index.htm;
try_files $uri $uri/ @router;
}
location @router {
# 处理history router 参考:https://blog.csdn.net/qq_35267557/article/details/81182097
rewrite ^.*$ /index.html last;
}
#特殊处理部分svg资源问题
location ~ \/app\/.*\.svg$ {
rewrite ^\/app\/.*\/(.*)\.svg$ /assets/css/font/$1.svg last;
}
# 这里的`portal`就是应用ID,下同
location /portal/ {
rewrite ^(/portal)(.*$) $2 last;
}
# 子系统静态资源转发,这里只是示例,需根据实际情况修改
location ^~ /godzillaPro/ {
# 注意路径后面的`/`不能去掉
proxy_pass http://godzilla-pro.sz.iquantex.com/;
}
#websocket 如果有websocket需求,则需要自己配置
location /api-gateway/msgcenter/ {
proxy_pass http://192.168.1.95:8889/msgcenter/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# websock 代理设置
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# websock 超时设置
proxy_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
}
}
}