714 lines
17 KiB
Markdown
Executable File
714 lines
17 KiB
Markdown
Executable File
# chunk-vendors.4d47960d.js 详细分析
|
||
|
||
## 文件信息
|
||
- **文件名**: chunk-vendors.4d47960d.js
|
||
- **大小**: 1.18MB (1,181,721 bytes)
|
||
- **类型**: Webpack第三方依赖打包文件
|
||
- **压缩**: 高度压缩,约26行
|
||
- **模块数**: 约200+个模块
|
||
|
||
## 文件用途
|
||
|
||
这是Webpack打包的第三方依赖库文件,包含应用所需的所有外部库和polyfills。主要包括:
|
||
- uni-simple-router(完整路由库)
|
||
- 加密库(HMAC, SHA, MD5等)
|
||
- Buffer实现
|
||
- 各种Polyfills
|
||
|
||
---
|
||
|
||
## 主要库分类
|
||
|
||
### 1. uni-simple-router 路由库
|
||
|
||
这是核心依赖,占据了大部分代码量。
|
||
|
||
#### 核心模块
|
||
|
||
| 模块ID | 名称 | 功能 |
|
||
|--------|------|------|
|
||
| **607** | 主入口 | 导出createRouter, RouterMount等 |
|
||
| **366** | 常量定义 | hookToggle, navtypeToggle等 |
|
||
| **309** | 类型定义 | TypeScript类型声明 |
|
||
| **169** | 钩子调用 | loopCallHook, transitionTo等 |
|
||
| **890** | 导航跳转 | navjump, lockNavjump, createRoute |
|
||
| **845** | 页面钩子 | proxyPageHook, createFullPath |
|
||
| **99** | 查询参数 | queryPageToMap, parseQuery |
|
||
| **314** | 方法重写 | rewriteMethod |
|
||
| **963** | 路由创建 | createRouter, RouterMount |
|
||
| **809** | 原生跳转 | uniOriginJump |
|
||
| **662** | 钩子注册 | registerEachHooks |
|
||
| **460** | Mixins | initMixins, getMixins |
|
||
| **789** | 工具函数 | deepClone, getDataType等 |
|
||
| **883** | 日志工具 | err, warn, log |
|
||
| **282** | 配置常量 | baseConfig, proxyHookName |
|
||
| **801** | 路由映射 | createRouteMap |
|
||
| **814** | 加载页面 | registerLoddingPage |
|
||
| **334** | 路径获取 | getEnterPath |
|
||
| **779** | 路径匹配 | pathToRegexp |
|
||
|
||
#### 功能详解
|
||
|
||
##### 路由创建 (模块 963)
|
||
|
||
```javascript
|
||
function createRouter(options) {
|
||
return {
|
||
// 配置
|
||
options,
|
||
mount: [],
|
||
Vue: null,
|
||
routesMap: {},
|
||
|
||
// 导航方法
|
||
push(location) { ... },
|
||
replace(location) { ... },
|
||
replaceAll(location) { ... },
|
||
pushTab(location) { ... },
|
||
back(delta, options) { ... },
|
||
|
||
// 守卫
|
||
beforeEach(fn) { ... },
|
||
afterEach(fn) { ... },
|
||
|
||
// 安装
|
||
install(Vue) { ... }
|
||
};
|
||
}
|
||
```
|
||
|
||
##### 导航跳转 (模块 890)
|
||
|
||
```javascript
|
||
// 核心导航函数
|
||
function navjump(rule, router, NAVTYPE, forceNav, uniActualData, callback, passthrough) {
|
||
// 1. 处理back类型
|
||
if (NAVTYPE === "back") {
|
||
// 构建返回参数
|
||
}
|
||
|
||
// 2. 解析路由
|
||
var query = queryPageToMap(rule, router).rule;
|
||
query.type = navtypeToggle[NAVTYPE];
|
||
|
||
// 3. 处理params到query
|
||
var finalRule = paramsToQuery(router, query);
|
||
var resolvedRule = resolveQuery(finalRule, router);
|
||
|
||
// 4. H5平台特殊处理
|
||
if (router.options.platform === "h5") {
|
||
if (NAVTYPE !== "push") NAVTYPE = "replace";
|
||
|
||
if (forceNav != null) {
|
||
forceNav.next({ replace: NAVTYPE !== "push", ...resolvedRule });
|
||
} else {
|
||
router.$route[NAVTYPE](resolvedRule);
|
||
}
|
||
}
|
||
// 5. 小程序/App平台
|
||
else {
|
||
// 创建to和from
|
||
var to = createToFrom(resolvedRule, router);
|
||
var from = getCurrentRoute(router);
|
||
|
||
createFullPath(resolvedRule, from);
|
||
|
||
if (!passthrough) {
|
||
return resolvedRule;
|
||
}
|
||
|
||
// 触发守卫
|
||
transitionTo(router, resolvedRule, from, NAVTYPE, HOOKLIST, function(next) {
|
||
uni[navtypeToggle[NAVTYPE]](resolvedRule, true, next, callback);
|
||
});
|
||
}
|
||
}
|
||
|
||
// 带锁的导航
|
||
function lockNavjump(rule, router, NAVTYPE, forceNav, uniActualData) {
|
||
lockDetectWarn(router, rule, NAVTYPE, function() {
|
||
// H5不锁定
|
||
if (router.options.platform !== "h5") {
|
||
router.$lockStatus = true;
|
||
}
|
||
navjump(rule, router, NAVTYPE, void 0, forceNav, uniActualData);
|
||
}, uniActualData);
|
||
}
|
||
```
|
||
|
||
##### 路由守卫 (模块 169)
|
||
|
||
```javascript
|
||
// 守卫钩子列表
|
||
var HOOKLIST = [
|
||
// 1. 全局前置守卫
|
||
function(router, to, from, route, next) {
|
||
callHook(router.lifeCycle.routerBeforeHooks[0], to, from, router, next);
|
||
},
|
||
|
||
// 2. 组件beforeRouteLeave
|
||
function(router, to, from, route, next) {
|
||
callBeforeRouteLeave(router, to, from, next);
|
||
},
|
||
|
||
// 3. 路由beforeHooks
|
||
function(router, to, from, route, next) {
|
||
callHook(router.lifeCycle.beforeHooks[0], to, from, router, next);
|
||
},
|
||
|
||
// 4. 路由独享守卫beforeEnter
|
||
function(router, to, from, route, next) {
|
||
callHook(route.beforeEnter, to, from, router, next);
|
||
},
|
||
|
||
// 5. 路由afterHooks
|
||
function(router, to, from, route, next) {
|
||
router.$lockStatus = false;
|
||
|
||
if (router.options.platform === "h5") {
|
||
proxyH5Mount(router);
|
||
}
|
||
|
||
callHook(router.lifeCycle.afterHooks[0], to, from, router, next, false);
|
||
},
|
||
|
||
// 6. 全局后置守卫
|
||
function(router, to, from, route, next) {
|
||
router.$lockStatus = false;
|
||
|
||
if (router.options.platform === "h5") {
|
||
proxyH5Mount(router);
|
||
}
|
||
|
||
callHook(router.lifeCycle.routerAfterHooks[0], to, from, router, next, false);
|
||
}
|
||
];
|
||
|
||
// 循环调用钩子
|
||
function loopCallHook(hooks, index, next, router, to, from, NAVTYPE) {
|
||
// 如果所有钩子都执行完了
|
||
if (hooks.length - 1 < index) {
|
||
return next();
|
||
}
|
||
|
||
var hook = hooks[index];
|
||
var errorHook = ERRORHOOK[0];
|
||
|
||
// 调用钩子
|
||
hook(router, to, from, route, function(nextRule) {
|
||
// App平台tab切换特殊处理
|
||
if (router.options.platform === "app-plus") {
|
||
if (nextRule !== false &&
|
||
typeof nextRule !== "string" &&
|
||
typeof nextRule !== "object") {
|
||
tabIndexSelect(to, from);
|
||
}
|
||
}
|
||
|
||
// 1. next(false) - 终止导航
|
||
if (nextRule === false) {
|
||
if (router.options.platform === "h5") {
|
||
next(false);
|
||
}
|
||
errorHook({
|
||
type: 0,
|
||
msg: "管道函数传递 false 导航被终止!",
|
||
matTo: to,
|
||
matFrom: from,
|
||
nextTo: nextRule
|
||
}, router);
|
||
}
|
||
// 2. next(string|object) - 重定向
|
||
else if (typeof nextRule === "string" || typeof nextRule === "object") {
|
||
var redirectNAVTYPE = NAVTYPE;
|
||
var redirectRule = nextRule;
|
||
|
||
if (typeof nextRule === "object") {
|
||
var NAVTYPE_OVERRIDE = nextRule.NAVTYPE;
|
||
redirectRule = objectWithoutProperties(nextRule, ["NAVTYPE"]);
|
||
|
||
if (NAVTYPE_OVERRIDE != null) {
|
||
redirectNAVTYPE = NAVTYPE_OVERRIDE;
|
||
}
|
||
}
|
||
|
||
navjump(redirectRule, router, redirectNAVTYPE, { from, next });
|
||
}
|
||
// 3. next() - 继续
|
||
else if (nextRule == null) {
|
||
index++;
|
||
loopCallHook(hooks, index, next, router, to, from, NAVTYPE);
|
||
}
|
||
// 4. 其他 - 错误
|
||
else {
|
||
errorHook({
|
||
type: 1,
|
||
msg: "管道函数传递未知类型,无法被识别。导航被终止!",
|
||
matTo: to,
|
||
matFrom: from,
|
||
nextTo: nextRule
|
||
}, router);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 转换导航
|
||
function transitionTo(router, to, from, NAVTYPE, hooks, finalCallback) {
|
||
var formattedRoutes = forMatNextToFrom(router, to, from);
|
||
var matTo = formattedRoutes.matTo;
|
||
var matFrom = formattedRoutes.matFrom;
|
||
|
||
// H5平台
|
||
if (router.options.platform === "h5") {
|
||
loopCallHook(hooks, 0, finalCallback, router, matTo, matFrom, NAVTYPE);
|
||
}
|
||
// 小程序/App平台
|
||
else {
|
||
// 分两步执行钩子
|
||
// 第一步:前4个钩子
|
||
loopCallHook(hooks.slice(0, 4), 0, function() {
|
||
// 第二步:后2个钩子(在导航完成后)
|
||
finalCallback(function() {
|
||
loopCallHook(hooks.slice(4), 0, voidFun, router, matTo, matFrom, NAVTYPE);
|
||
});
|
||
}, router, matTo, matFrom, NAVTYPE);
|
||
}
|
||
}
|
||
```
|
||
|
||
##### 页面钩子代理 (模块 845)
|
||
|
||
```javascript
|
||
// 代理页面生命周期钩子
|
||
function proxyPageHook(vueInstance, router, mpType) {
|
||
var proxyHookDeps = router.proxyHookDeps;
|
||
var options = vueInstance.$options;
|
||
|
||
var proxyHookNames = [
|
||
"onLoad", "onShow", "onReady", "onHide",
|
||
"onUnload", "onPullDownRefresh", "onReachBottom"
|
||
];
|
||
|
||
for (var i = 0; i < proxyHookNames.length; i++) {
|
||
var hookName = proxyHookNames[i];
|
||
var hooks = options[hookName];
|
||
|
||
if (hooks) {
|
||
for (var j = 0; j < hooks.length; j++) {
|
||
// 跳过已代理的钩子
|
||
if (hooks[j].toString().includes("UNI-SIMPLE-ROUTER")) {
|
||
continue;
|
||
}
|
||
|
||
var hookId = Object.keys(proxyHookDeps.hooks).length + 1;
|
||
|
||
// 创建代理函数
|
||
var proxyHook = function() {
|
||
var args = Array.prototype.slice.call(arguments);
|
||
proxyHookDeps.resetIndex.push(hookId);
|
||
proxyHookDeps.options[hookId] = args;
|
||
};
|
||
|
||
// 保存原始钩子
|
||
var originalHook = hooks.splice(j, 1, proxyHook)[0];
|
||
|
||
// 保存钩子信息
|
||
proxyHookDeps.hooks[hookId] = {
|
||
proxyHook: proxyHook,
|
||
callHook: function(path) {
|
||
// 只在匹配路径时调用
|
||
if (router.enterPath.replace(/^\//, "") === path.replace(/^\//, "") ||
|
||
mpType === "app") {
|
||
var args = proxyHookDeps.options[hookId];
|
||
originalHook.apply(vueInstance, args);
|
||
}
|
||
},
|
||
resetHook: function() {
|
||
hooks.splice(j, 1, originalHook);
|
||
}
|
||
};
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 重置并调用页面钩子
|
||
function resetAndCallPageHook(router, path, reset) {
|
||
if (reset === void 0) reset = true;
|
||
|
||
// 解析路径
|
||
var match = path.trim().match(/^(\/?[^\?\s]+)(\?[\s\S]*$)?$/);
|
||
if (match == null) {
|
||
throw new Error("还原hook失败。请检查 【" + path + "】 路径是否正确。");
|
||
}
|
||
path = match[1];
|
||
|
||
var proxyHookDeps = router.proxyHookDeps;
|
||
var resetIndex = proxyHookDeps.resetIndex;
|
||
|
||
// 调用所有钩子
|
||
for (var i = 0; i < resetIndex.length; i++) {
|
||
var hookId = resetIndex[i];
|
||
proxyHookDeps.hooks[hookId].callHook(path);
|
||
}
|
||
|
||
// 重置钩子
|
||
if (reset) {
|
||
resetPageHook(router);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 加密库
|
||
|
||
#### HMAC (模块 0116)
|
||
|
||
```javascript
|
||
/**
|
||
* HMAC - Hash-based Message Authentication Code
|
||
*
|
||
* 算法:
|
||
* HMAC(K, m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))
|
||
*
|
||
* 其中:
|
||
* - K: 密钥
|
||
* - m: 消息
|
||
* - H: 哈希函数
|
||
* - ⊕: XOR运算
|
||
* - ||: 连接
|
||
* - ipad: 0x36重复blocksize次
|
||
* - opad: 0x5C重复blocksize次
|
||
*/
|
||
|
||
function Hmac(alg, key) {
|
||
// 1. 如果key是字符串,转为Buffer
|
||
if (typeof key === "string") {
|
||
key = Buffer.from(key);
|
||
}
|
||
|
||
// 2. 确定blocksize
|
||
var blocksize = (alg === "sha512" || alg === "sha384") ? 128 : 64;
|
||
|
||
// 3. 处理key长度
|
||
if (key.length > blocksize) {
|
||
// key太长,先hash
|
||
var hash = (alg === "rmd160") ? new rmd160() : sha(alg);
|
||
key = hash.update(key).digest();
|
||
} else if (key.length < blocksize) {
|
||
// key太短,填充0
|
||
key = Buffer.concat([key, zeroBuffer], blocksize);
|
||
}
|
||
|
||
// 4. 计算ipad和opad
|
||
var ipad = Buffer.allocUnsafe(blocksize);
|
||
var opad = Buffer.allocUnsafe(blocksize);
|
||
|
||
for (var i = 0; i < blocksize; i++) {
|
||
ipad[i] = key[i] ^ 0x36;
|
||
opad[i] = key[i] ^ 0x5C;
|
||
}
|
||
|
||
// 5. 初始化内部hash
|
||
this._hash = (alg === "rmd160") ? new rmd160() : sha(alg);
|
||
this._hash.update(ipad);
|
||
}
|
||
|
||
Hmac.prototype._final = function() {
|
||
// 1. 计算内部hash
|
||
var innerHash = this._hash.digest();
|
||
|
||
// 2. 计算外部hash
|
||
var hash = (this._alg === "rmd160") ? new rmd160() : sha(this._alg);
|
||
return hash.update(this._opad).update(innerHash).digest();
|
||
};
|
||
```
|
||
|
||
支持的算法:
|
||
- MD5
|
||
- SHA1, SHA256, SHA384, SHA512
|
||
- RIPEMD160
|
||
|
||
#### Cipher (模块 0168)
|
||
|
||
加密/解密基类,提供:
|
||
- 缓冲区管理
|
||
- 分块处理
|
||
- 填充处理
|
||
|
||
#### Buffer (模块 5f79)
|
||
|
||
Node.js Buffer在浏览器环境的实现。
|
||
|
||
---
|
||
|
||
### 3. 核心Polyfills
|
||
|
||
#### Object方法
|
||
|
||
| 模块ID | 方法 | 说明 |
|
||
|--------|------|------|
|
||
| **00ca** | getOwnPropertyNames | 获取对象自身属性名 |
|
||
| **036b** | indexOf | 数组indexOf |
|
||
| **338c** | hasOwnProperty | 检查自身属性 |
|
||
| **f660** | toIndexedObject | 转为索引对象 |
|
||
|
||
#### Array方法
|
||
|
||
| 模块ID | 方法 | 说明 |
|
||
|--------|------|------|
|
||
| **bb80** | push | 数组push |
|
||
| **00c2** | keys | 对象键名 |
|
||
|
||
#### 工具函数
|
||
|
||
| 模块ID | 功能 | 说明 |
|
||
|--------|------|------|
|
||
| **2c2e** | inherits | 继承工具 |
|
||
| **cfef** | Transform | 流转换 |
|
||
| **d3c2** | assert | 断言 |
|
||
|
||
---
|
||
|
||
### 4. uni-simple-router 详细功能
|
||
|
||
#### 路由配置选项
|
||
|
||
```javascript
|
||
{
|
||
// 平台类型
|
||
platform: "h5" | "app-plus" | "mp-weixin" | "mp-alipay" | ...,
|
||
|
||
// 路由表
|
||
routes: [
|
||
{
|
||
path: "/pages/index/index",
|
||
name: "index",
|
||
aliasPath: "/",
|
||
meta: {},
|
||
beforeEnter: function(to, from, next) {}
|
||
}
|
||
],
|
||
|
||
// H5配置
|
||
h5: {
|
||
vueRouterDev: false,
|
||
vueNext: false,
|
||
paramsToQuery: false
|
||
},
|
||
|
||
// 小程序配置
|
||
applet: {
|
||
animationDuration: 300
|
||
},
|
||
|
||
// App配置
|
||
APP: {
|
||
animation: {
|
||
animationType: "pop-in",
|
||
animationDuration: 300
|
||
},
|
||
launchedHook: function() {}
|
||
},
|
||
|
||
// 调试
|
||
debugger: false | true | { err: true, warn: true, log: false },
|
||
|
||
// 守卫
|
||
routerBeforeEach: function(to, from, next) {},
|
||
routerAfterEach: function(to, from) {},
|
||
routerErrorEach: function(error, router) {},
|
||
|
||
// 查询处理
|
||
resolveQuery: function(query) { return query; },
|
||
parseQuery: function(query) { return query; },
|
||
|
||
// 检测锁
|
||
detectBeforeLock: function(router, to, navType) {}
|
||
}
|
||
```
|
||
|
||
#### 路由对象结构
|
||
|
||
```javascript
|
||
{
|
||
name: "index", // 路由名称
|
||
path: "/pages/index/index", // 路径
|
||
fullPath: "/pages/index/index?id=1", // 完整路径
|
||
query: { id: "1" }, // 查询参数
|
||
params: {}, // 路由参数
|
||
meta: {}, // 元信息
|
||
NAVTYPE: "push", // 导航类型
|
||
BACKTYPE: "" // 返回类型
|
||
}
|
||
```
|
||
|
||
#### API方法
|
||
|
||
```javascript
|
||
// 导航方法
|
||
router.push(location)
|
||
router.replace(location)
|
||
router.replaceAll(location)
|
||
router.pushTab(location)
|
||
router.back(delta, options)
|
||
|
||
// 守卫方法
|
||
router.beforeEach(fn)
|
||
router.afterEach(fn)
|
||
|
||
// 强制触发
|
||
router.forceGuardEach(NAVTYPE, passthrough)
|
||
|
||
// Vue实例访问
|
||
this.$Router // 路由器实例
|
||
this.$Route // 当前路由
|
||
this.$AppReady // App准备完成Promise
|
||
```
|
||
|
||
#### 原生方法重写
|
||
|
||
```javascript
|
||
// 重写uni原生导航方法
|
||
uni.navigateTo(options) → router.push()
|
||
uni.redirectTo(options) → router.replace()
|
||
uni.reLaunch(options) → router.replaceAll()
|
||
uni.switchTab(options) → router.pushTab()
|
||
uni.navigateBack(options) → router.back()
|
||
```
|
||
|
||
---
|
||
|
||
## 模块依赖关系
|
||
|
||
```
|
||
607 (主入口)
|
||
├── 366 (常量)
|
||
├── 309 (类型)
|
||
├── 814 (加载页面)
|
||
└── 963 (创建路由)
|
||
├── 282 (配置)
|
||
├── 789 (工具)
|
||
├── 662 (钩子注册)
|
||
├── 460 (Mixins)
|
||
│ ├── 801 (路由映射)
|
||
│ ├── 844 (Vue路由)
|
||
│ ├── 147 (代理)
|
||
│ └── 814 (加载页面)
|
||
├── 890 (导航)
|
||
│ ├── 366 (常量)
|
||
│ ├── 99 (查询)
|
||
│ ├── 789 (工具)
|
||
│ ├── 169 (钩子调用)
|
||
│ └── 845 (页面钩子)
|
||
└── 314 (方法重写)
|
||
└── 809 (原生跳转)
|
||
```
|
||
|
||
---
|
||
|
||
## 性能优化
|
||
|
||
### 1. 代码分割
|
||
- 将第三方库独立打包
|
||
- 减少主bundle大小
|
||
- 利用浏览器缓存
|
||
|
||
### 2. 懒加载
|
||
- 页面组件按需加载
|
||
- 减少首屏加载时间
|
||
|
||
### 3. 压缩
|
||
- UglifyJS压缩
|
||
- 变量名混淆
|
||
- 移除注释和空格
|
||
|
||
---
|
||
|
||
## 建议
|
||
|
||
### 1. 优化建议
|
||
- ✅ 使用CDN加载常用库
|
||
- ✅ 启用Gzip/Brotli压缩
|
||
- ✅ 考虑按需引入uni-simple-router功能
|
||
- ✅ 分析是否所有加密库都需要
|
||
|
||
### 2. 调试建议
|
||
- 使用Source Map调试
|
||
- 启用路由debugger选项
|
||
- 查看原始源代码
|
||
|
||
### 3. 升级建议
|
||
- 检查依赖库版本
|
||
- 及时更新安全补丁
|
||
- 关注性能优化
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
`chunk-vendors.4d47960d.js` 是一个包含所有第三方依赖的大型文件:
|
||
|
||
**主要内容**:
|
||
1. ✅ uni-simple-router (约70%代码量)
|
||
2. ✅ 加密库 (HMAC, SHA, MD5等)
|
||
3. ✅ Buffer实现
|
||
4. ✅ Polyfills (Object, Array方法)
|
||
5. ✅ 工具函数
|
||
|
||
**特点**:
|
||
- 📦 高度压缩(1.18MB → 约26行)
|
||
- 🔒 变量混淆
|
||
- ⚡ 模块化设计
|
||
- 🎯 独立打包
|
||
|
||
**用途**:
|
||
- 提供路由功能
|
||
- 提供加密功能
|
||
- 兼容性支持
|
||
- 工具函数库
|
||
|
||
由于文件过大且高度压缩,完整反编译需要原始源代码和构建配置。建议查看:
|
||
- uni-simple-router GitHub仓库
|
||
- 项目的package.json
|
||
- Webpack配置文件
|
||
|
||
---
|
||
|
||
## 附录:模块ID速查表
|
||
|
||
### uni-simple-router核心模块
|
||
- 607: 主入口
|
||
- 366: 常量定义
|
||
- 963: 创建路由器
|
||
- 890: 导航跳转
|
||
- 169: 钩子调用
|
||
- 845: 页面钩子
|
||
- 789: 工具函数
|
||
- 99: 查询参数
|
||
- 314: 方法重写
|
||
- 662: 钩子注册
|
||
- 460: Mixins
|
||
- 282: 配置常量
|
||
|
||
### 加密相关
|
||
- 0116: HMAC
|
||
- 0168: Cipher
|
||
- 5f79: Buffer
|
||
- 1177: MD5
|
||
- 2d81: RMD160
|
||
- 25a4: SHA
|
||
|
||
### Polyfills
|
||
- 00c2: indexOf
|
||
- 00ca: getOwnPropertyNames
|
||
- bb80: array push
|
||
- 338c: hasOwnProperty
|
||
- f660: toIndexedObject
|
||
|
||
完整模块列表约200+个,这里仅列出主要模块。
|
||
|
||
|
||
|