Commit a93c0b6d authored by 李维's avatar 李维

feat: 初始化项目

1. 迁移自东方之星中间层
2. 增加了场景路由
parent c71b356e
No preview for this file type
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
cocos creator技术框架下的H5互动模板框架脚手架,基于cocos creator实现快速开发基于绘玩云的H5互动课件。 cocos creator技术框架下的H5互动模板框架脚手架,基于cocos creator实现快速开发基于绘玩云的H5互动课件。
[视频教程传送门](https://www.bilibili.com/video/BV1Dq4y1t7n5/)
# 使用简介 # 使用简介
## 前期准备 ## 前期准备
...@@ -51,30 +49,29 @@ npm start ...@@ -51,30 +49,29 @@ npm start
``` ```
* 打开浏览器:http://staging-teach.ireadabc.com/template_ci/debug * 打开浏览器:http://staging-teach.ireadabc.com/template_ci/debug
* [点击查看调试视频教程](https://www.bilibili.com/video/BV1Dq4y1t7n5?p=8) * 点击右上角齿轮,选择技术选型、调试模式选择“普通”
### 真机调试
* 下载模板调试专用app
安卓下载: ### 互动模板
http://download-iplayabc.oss-cn-beijing.aliyuncs.com/iDebugABC.apk
![avatar](http://staging-teach.cdn.ireadabc.com/084f2f95-8213-4c5a-8c46-b194819d7677.png)
* 找到项目根路径下 index.html 文件
* 在引入JS的位置将air.js改为air_online_open.js
* 启动本地服务
iOS下载: ```
npm start
```
由于调试APP没有上架App Store 所以需要先获取手机的UDID 发送给我们的技术支持,加入后才可以扫码下载安装 * 打开浏览器:http://staging-teach.ireadabc.com/template_ci/debug
获取UDID:https://www.pgyer.com/tools/udid * 点击右上角齿轮,选择技术选型、调试模式选择“互动”
下载iOS: https://www.pgyer.com/gS0X * 左侧老师、右侧学生
### 真机调试
有时可能需要反复调试一些功能性的问题,与原生APP相关或者手上临时没有设备,我们提供了一个网页版的调试方式 * 下载模板调试专用app
http://staging-openapi.iteachabc.com/api/courseware/v1/middle/debug http://download-iplayabc.oss-cn-beijing.aliyuncs.com/iDebugABC.apk
![avatar](http://staging-teach.cdn.ireadabc.com/084f2f95-8213-4c5a-8c46-b194819d7677.png)
* 启动本地服务 * 启动本地服务
...@@ -84,12 +81,4 @@ npm start ...@@ -84,12 +81,4 @@ npm start
* 手机和电脑连接同一个Wifi * 手机和电脑连接同一个Wifi
* 打开调试app,根据提示输入IP地址,点击开始就可以在手机上预览模板了 * 打开调试app,根据提示输入IP地址,点击开始就可以在手机上预览模板了
* 使用 console.log("==调试信息=="); 可以打印日志进行必要的调试 * 使用 this.log("==调试信息=="); 可以打印日志进行必要的调试
* 点击左上角 “logcat” 可以查看运行日志,(logcat是可以按住拖动的, 所以不用考虑UI遮挡问题) \ No newline at end of file
### 注意事项及常见问题
* 项目里所有文件及文件夹的命名方式要注意不能包含空格、汉字、减号
* 开发者新建的脚本文件(.js/.ts)的文件名必须包含项目名称,例如在 test_01 项目中添加一个脚本文件(如想命名为 hello.ts );则需要命名为 hello_test_01.ts
* 项目里尽量不要使用setTimeout、setInterval等定时器,如果使用了记得在onDestroy中释放掉(onDestroy 是指CocosCreator的生命周期钩子)
* 理论上禁止使用全局变量,因为模板到线上会进行组装,常见问题是一个模板使用多次造成全局变量被读脏
import { asyncDelay, onHomeworkFinish } from "../script/util";
import { MyCocosSceneComponent } from "../script/MyCocosSceneComponent";
const { ccclass, property } = cc._decorator;
@ccclass
export default class SceneComponent extends MyCocosSceneComponent {
addPreloadImage() {
// TODO 根据自己的配置预加载图片资源
this._imageResList.push({ url: this.data.pic_url });
this._imageResList.push({ url: this.data.pic_url_2 });
}
addPreloadAudio() {
// TODO 根据自己的配置预加载音频资源
this._audioResList.push({ url: this.data.audio_url });
}
addPreloadAnima() {
}
async onLoadEnd() {
// TODO 加载完成后的逻辑写在这里, 下面的代码仅供参考
this.initData();
this.initView();
this.initListener();
}
_cantouch = null;
initData() {
// 所有全局变量 默认都是null
this._cantouch = true;
}
initView() {
this.initBg();
this.initPic();
this.initBtn();
this.initIcon();
}
initBg() {
const bgNode = cc.find('Canvas/bg');
bgNode.scale = this._mapScaleMax;
}
pic1 = null;
pic2 = null;
initPic() {
const canvas = cc.find('Canvas');
const maxW = canvas.width * 0.7;
this.getSprNodeByUrl(this.data.pic_url, (sprNode) => {
const picNode1 = sprNode;
picNode1.scale = maxW / picNode1.width;
picNode1.baseX = picNode1.x;
canvas.addChild(picNode1);
this.pic1 = picNode1;
const labelNode = new cc.Node();
labelNode.color = cc.Color.YELLOW;
const label = labelNode.addComponent(cc.Label);
label.string = this.data.text;
label.fontSize = 60;
label.lineHeight = 60;
label.font = cc.find('Canvas/res/font/BRLNSDB').getComponent('cc.Label').font;
picNode1.addChild(labelNode);
});
this.getSprNodeByUrl(this.data.pic_url_2, (sprNode) => {
const picNode2 = sprNode;
picNode2.scale = maxW / picNode2.width;
canvas.addChild(picNode2);
picNode2.x = canvas.width;
picNode2.baseX = picNode2.x;
this.pic2 = picNode2;
const labelNode = new cc.Node();
const label = labelNode.addComponent(cc.RichText);
const size = 60
label.font = cc.find('Canvas/res/font/BRLNSDB').getComponent(cc.Label).font;
label.string = `<outline color=#751e00 width=4><size=${size}><color=#ffffff>${this.data.text}</color></size></outline>`
label.lineHeight = size;
picNode2.addChild(labelNode);
});
}
initIcon() {
const iconNode = this.getSprNode('icon');
iconNode.zIndex = 5;
iconNode.anchorX = 1;
iconNode.anchorY = 1;
iconNode.parent = cc.find('Canvas');
iconNode.x = iconNode.parent.width / 2 - 10;
iconNode.y = iconNode.parent.height / 2 - 10;
iconNode.on(cc.Node.EventType.TOUCH_START, () => {
this.playAudioByUrl(this.data.audio_url);
})
}
curPage = null;
initBtn() {
this.curPage = 0;
const bottomPart = cc.find('Canvas/bottomPart');
bottomPart.zIndex = 5; // 提高层级
bottomPart.x = bottomPart.parent.width / 2;
bottomPart.y = -bottomPart.parent.height / 2;
const leftBtnNode = bottomPart.getChildByName('btn_left');
//节点中添加了button组件 则可以添加click事件监听
leftBtnNode.on('click', () => {
if (!this._cantouch) {
return;
}
if (this.curPage == 0) {
return;
}
this.curPage = 0
this.leftMove();
this.playLocalAudio('btn');
})
const rightBtnNode = bottomPart.getChildByName('btn_right');
//节点中添加了button组件 则可以添加click事件监听
rightBtnNode.on('click', () => {
if (!this._cantouch) {
return;
}
if (this.curPage == 1) {
return;
}
this.curPage = 1
this.rightMove();
// 游戏结束时需要调用这个方法通知系统作业完成
onHomeworkFinish();
this.playLocalAudio('btn');
})
}
leftMove() {
this._cantouch = false;
const len = this.pic1.parent.width;
cc.tween(this.pic1)
.to(1, { x: this.pic1.baseX }, { easing: 'cubicInOut' })
.start();
cc.tween(this.pic2)
.to(1, { x: this.pic2.baseX }, { easing: 'cubicInOut' })
.call(() => {
this._cantouch = true;
})
.start();
}
rightMove() {
this._cantouch = false;
const len = this.pic1.parent.width;
cc.tween(this.pic1)
.to(1, { x: this.pic1.baseX - len }, { easing: 'cubicInOut' })
.start();
cc.tween(this.pic2)
.to(1, { x: this.pic2.baseX - len }, { easing: 'cubicInOut' })
.call(() => {
this._cantouch = true;
})
.start();
}
// update (dt) {},
initListener() {
}
playLocalAudio(audioName) {
const audio = cc.find(`Canvas/res/audio/${audioName}`).getComponent(cc.AudioSource);
return new Promise((resolve, reject) => {
const id = cc.audioEngine.playEffect(audio.clip, false);
cc.audioEngine.setFinishCallback(id, () => {
resolve(id);
});
})
}
}
import { defaultData } from "../script/defaultData";
export class MyCocosSceneComponent extends cc.Component {
// 生命周期 onLoad
onLoad() {
this.initSceneData();
this.initSize();
}
_imageResList = null;
_audioResList = null;
_animaResList = null;
initSceneData() {
this._imageResList = [];
this._audioResList = [];
this._animaResList = [];
}
_designSize = null; // 设计分辨率
_frameSize = null; // 屏幕分辨率
_mapScaleMin = null; // 场景中常用缩放(取大值)
_mapScaleMax = null; // 场景中常用缩放(取小值)
_cocosScale = null; // cocos 自缩放 (较少用到)
initSize() {
// 注意cc.winSize只有在适配后(修改fitHeight/fitWidth后)才能获取到正确的值,因此使用cc.getFrameSize()来获取初始的屏幕大小
let screen_size = cc.view.getFrameSize().width / cc.view.getFrameSize().height
let design_size = cc.Canvas.instance.designResolution.width / cc.Canvas.instance.designResolution.height
let f = screen_size >= design_size
cc.Canvas.instance.fitHeight = f
cc.Canvas.instance.fitWidth = !f
const frameSize = cc.view.getFrameSize();
this._frameSize = frameSize;
this._designSize = cc.view.getDesignResolutionSize();
let sx = cc.winSize.width / frameSize.width;
let sy = cc.winSize.height / frameSize.height;
this._cocosScale = Math.min(sx, sy);
sx = frameSize.width / this._designSize.width;
sy = frameSize.height / this._designSize.height;
this._mapScaleMin = Math.min(sx, sy) * this._cocosScale;
this._mapScaleMax = Math.max(sx, sy) * this._cocosScale;
cc.director['_scene'].width = frameSize.width;
cc.director['_scene'].height = frameSize.height;
}
data = null;
// 生命周期 start
start() {
if (window && (<any>window).courseware && (<any>window).courseware.getData) {
(<any>window).courseware.getData((data) => {
this.log('data:' + data);
this.data = data || this.getDefaultData();
this.data = JSON.parse(JSON.stringify(this.data));
this.preloadItem();
})
} else {
this.data = this.getDefaultData();
this.preloadItem();
}
}
getDefaultData() {
return defaultData;
}
preloadItem() {
this.addPreloadImage();
this.addPreloadAudio();
this.addPreloadAnima();
this.preload();
}
addPreloadImage() {
}
addPreloadAudio() {
}
addPreloadAnima() {
}
preload() {
const preloadArr = this._imageResList.concat(this._audioResList).concat(this._animaResList);
cc.assetManager.preloadAny(preloadArr, null, null, async (err, data) => {
if (window && window["air"]) {
// window["air"].onCourseInScreen = (next) => {
// window["air"].isCourseInScreen = true;
// this.onLoadEnd();
// next();
// };
await this.onLoadEnd();
window["air"].hideAirClassLoading();
} else {
await this.onLoadEnd();
}
cc.debug.setDisplayStats(false);
});
}
log (str) {
const node = cc.find('middleLayer');
if(node){
node.getComponent('middleLayer').log(str);
}else{
console.log(str);
}
}
async onLoadEnd() {
}
// ------------------------------------------------
getSprNode(resName) {
const sf = cc.find('Canvas/res/img/' + resName).getComponent(cc.Sprite).spriteFrame;
const node = new cc.Node();
node.addComponent(cc.Sprite).spriteFrame = sf;
return node;
}
getSpriteFrimeByUrl(url, cb) {
cc.loader.load({ url }, (err, img) => {
const spriteFrame = new cc.SpriteFrame(img)
if (cb) {
cb(spriteFrame);
}
})
}
getSprNodeByUrl(url, cb) {
const node = new cc.Node();
const spr = node.addComponent(cc.Sprite);
this.getSpriteFrimeByUrl(url, (sf) => {
spr.spriteFrame = sf;
if (cb) {
cb(node);
}
})
}
playAudioByUrl(audio_url, cb = null) {
if (audio_url) {
cc.assetManager.loadRemote(audio_url, (err, audioClip) => {
const audioId = cc.audioEngine.play(audioClip, false, 0.8);
if (cb) {
cc.audioEngine.setFinishCallback(audioId, () => {
cb();
});
}
});
}else{
cb && cb();
}
}
}
\ No newline at end of file
export const defaultData = {
"pic_url": "http://staging-teach.cdn.ireadabc.com/ed94332a503c31e0908bd4c6923a2665.png",
"pic_url_2": "http://staging-teach.cdn.ireadabc.com/5fb60317ade0195d35ad8034d5370a7f.png",
"text": "This is a test label.",
"audio_url": "http://staging-teach.cdn.ireadabc.com/f47f1d7b5c160fe1c59500d180346240.mp3"
}
\ No newline at end of file
{
"ver": "2.3.5",
"uuid": "efa5fa09-a4dd-4bfc-ab7e-17c19f85408f",
"type": "sprite",
"wrapMode": "clamp",
"filterMode": "bilinear",
"premultiplyAlpha": false,
"genMipmaps": false,
"packable": true,
"width": 366,
"height": 336,
"platformSettings": {},
"subMetas": {
"1orange": {
"ver": "1.0.4",
"uuid": "43d1e79d-6de8-4dcb-b8ce-d767df7913aa",
"rawTextureUuid": "efa5fa09-a4dd-4bfc-ab7e-17c19f85408f",
"trimType": "auto",
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": -0.5,
"trimX": 0,
"trimY": 1,
"width": 366,
"height": 335,
"rawWidth": 366,
"rawHeight": 336,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"subMetas": {}
}
}
}
\ No newline at end of file
{
"ver": "2.3.5",
"uuid": "d582359e-924e-4ee9-9964-1fc4bb417e71",
"type": "sprite",
"wrapMode": "clamp",
"filterMode": "bilinear",
"premultiplyAlpha": false,
"genMipmaps": false,
"packable": true,
"width": 61,
"height": 67,
"platformSettings": {},
"subMetas": {
"btn_right": {
"ver": "1.0.4",
"uuid": "e5a2dbaa-a677-4a32-90d7-a1b057d7fb59",
"rawTextureUuid": "d582359e-924e-4ee9-9964-1fc4bb417e71",
"trimType": "auto",
"trimThreshold": 1,
"rotated": false,
"offsetX": -0.5,
"offsetY": 0.5,
"trimX": 0,
"trimY": 0,
"width": 60,
"height": 66,
"rawWidth": 61,
"rawHeight": 67,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"subMetas": {}
}
}
}
\ No newline at end of file
{
"ver": "1.1.2",
"uuid": "3a7066b3-5a2a-4982-a364-47370eb0227b",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
\ No newline at end of file
{
"ver": "1.1.2",
"uuid": "24910381-c329-4531-ad18-764b1cad9d6b",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
\ No newline at end of file
{
"ver": "1.2.9",
"uuid": "782105f5-3081-436f-8651-71d5b076953a",
"asyncLoadAssets": false,
"autoReleaseAssets": false,
"subMetas": {}
}
\ No newline at end of file
// Learn TypeScript:
// - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
// - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
const {ccclass, property} = cc._decorator;
import Router from '../../script/router';
@ccclass
export default class NewClass extends cc.Component {
// LIFE-CYCLE CALLBACKS:
// onLoad () {}
start () {
}
onRunGameCenter() {
// 使用路由管理器运行主场景
const router = Router.getInstance();
router.navigateTo('/index', {
action: 'start',
timestamp: Date.now()
});
}
// update (dt) {}
}
{ {
"ver": "1.0.8", "ver": "1.0.8",
"uuid": "f8b451ff-857c-4ca8-9870-866bc5154a29", "uuid": "3f255ebc-9ce5-4586-9131-8313973d7aae",
"isPlugin": false, "isPlugin": false,
"loadPluginInWeb": true, "loadPluginInWeb": true,
"loadPluginInNative": true, "loadPluginInNative": true,
......
{
"ver": "1.1.2",
"uuid": "07f21694-8cca-49b4-99a7-df4679af5e42",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
\ No newline at end of file
{
"ver": "1.1.2",
"uuid": "312e0526-ac20-4b83-af6d-4e6be1ace098",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
\ No newline at end of file
{ {
"ver": "2.3.5", "ver": "2.3.5",
"uuid": "9a79969a-0506-48d4-bc98-3c05d109b027", "uuid": "29214da6-f773-456d-a74b-e18ede916e6d",
"type": "sprite", "type": "sprite",
"wrapMode": "clamp", "wrapMode": "clamp",
"filterMode": "bilinear", "filterMode": "bilinear",
"premultiplyAlpha": false, "premultiplyAlpha": false,
"genMipmaps": false, "genMipmaps": false,
"packable": true, "packable": true,
"width": 61, "width": 1920,
"height": 67, "height": 1080,
"platformSettings": {}, "platformSettings": {},
"subMetas": { "subMetas": {
"btn_left": { "bg_bg": {
"ver": "1.0.4", "ver": "1.0.4",
"uuid": "ce19457d-e8f3-4c38-ae3e-d4b99208ddb5", "uuid": "c8ab631d-9bb9-4c80-aba5-4b643cfa2e18",
"rawTextureUuid": "9a79969a-0506-48d4-bc98-3c05d109b027", "rawTextureUuid": "29214da6-f773-456d-a74b-e18ede916e6d",
"trimType": "auto", "trimType": "auto",
"trimThreshold": 1, "trimThreshold": 1,
"rotated": false, "rotated": false,
...@@ -22,10 +22,10 @@ ...@@ -22,10 +22,10 @@
"offsetY": 0, "offsetY": 0,
"trimX": 0, "trimX": 0,
"trimY": 0, "trimY": 0,
"width": 61, "width": 1920,
"height": 67, "height": 1080,
"rawWidth": 61, "rawWidth": 1920,
"rawHeight": 67, "rawHeight": 1080,
"borderTop": 0, "borderTop": 0,
"borderBottom": 0, "borderBottom": 0,
"borderLeft": 0, "borderLeft": 0,
......
{ {
"ver": "2.3.5", "ver": "2.3.5",
"uuid": "18d07592-51a9-421e-8972-0f67b68d29e1", "uuid": "4973a04b-208c-4312-9e02-15621ff127f3",
"type": "sprite", "type": "sprite",
"wrapMode": "clamp", "wrapMode": "clamp",
"filterMode": "bilinear", "filterMode": "bilinear",
"premultiplyAlpha": false, "premultiplyAlpha": false,
"genMipmaps": false, "genMipmaps": false,
"packable": true, "packable": true,
"width": 144, "width": 132,
"height": 144, "height": 132,
"platformSettings": {}, "platformSettings": {},
"subMetas": { "subMetas": {
"icon": { "btn_return": {
"ver": "1.0.4", "ver": "1.0.4",
"uuid": "6fbc30a8-3c49-44ae-8ba4-7f56f385b78a", "uuid": "a8fcc889-aaa6-4476-b09b-c97850517c48",
"rawTextureUuid": "18d07592-51a9-421e-8972-0f67b68d29e1", "rawTextureUuid": "4973a04b-208c-4312-9e02-15621ff127f3",
"trimType": "auto", "trimType": "auto",
"trimThreshold": 1, "trimThreshold": 1,
"rotated": false, "rotated": false,
"offsetX": 0, "offsetX": 0,
"offsetY": -0.5, "offsetY": -1,
"trimX": 3, "trimX": 2,
"trimY": 2, "trimY": 2,
"width": 138, "width": 128,
"height": 141, "height": 130,
"rawWidth": 144, "rawWidth": 132,
"rawHeight": 144, "rawHeight": 132,
"borderTop": 0, "borderTop": 0,
"borderBottom": 0, "borderBottom": 0,
"borderLeft": 0, "borderLeft": 0,
......
{
"ver": "1.2.9",
"uuid": "867a2d96-17d5-47c7-96e9-5c29231a4af6",
"asyncLoadAssets": false,
"autoReleaseAssets": false,
"subMetas": {}
}
\ No newline at end of file
// Learn TypeScript:
// - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
// - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
const {ccclass, property} = cc._decorator;
import Router from '../../script/router';
@ccclass
export default class NewClass extends cc.Component {
// LIFE-CYCLE CALLBACKS:
// onLoad () {}
start () {
}
onClickBack() {
// 当按钮点击或其他逻辑触发时,派发全局事件
// 第一个参数:事件名,必须和主场景监听的名字一致
// 第二个参数:可变长参数,可以传递任意数量、任意类型的参数给监听方
console.log('onClickBack');
// this.node.emit('onEventFromSubScene', "Hello Main Scene!", 100);
// 获取主场景的 middleLayer 节点并调用 handleSubSceneEvent 方法
const middleLayerNode = cc.director.getScene().getChildByName('middleLayer');
if (middleLayerNode) {
const middleLayerComp = middleLayerNode.getComponent('middleLayer');
if (middleLayerComp && typeof middleLayerComp.closeGameCenter === 'function') {
middleLayerComp.closeGameCenter();
} else {
console.error('middleLayer 组件未找到或 handleSubSceneEvent 方法不存在');
}
} else {
console.error('middleLayer 节点未找到');
}
}
// update (dt) {}
}
{ {
"ver": "1.0.8", "ver": "1.0.8",
"uuid": "c41b0e51-55d7-443c-af3a-b22c3dd9b9e5", "uuid": "ee841466-ef92-49ad-84e7-c91048b6a896",
"isPlugin": false, "isPlugin": false,
"loadPluginInWeb": true, "loadPluginInWeb": true,
"loadPluginInNative": true, "loadPluginInNative": true,
......
// Learn TypeScript:
// - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
// - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
const {ccclass, property} = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {
// LIFE-CYCLE CALLBACKS:
// onLoad () {}
start () {
}
// update (dt) {}
}
{ {
"ver": "1.0.8", "ver": "1.0.8",
"uuid": "408a67f8-65fa-4cf1-8cf2-83e20e1a0fd5", "uuid": "9d11d90b-6323-43f3-833d-7c491761e70e",
"isPlugin": false, "isPlugin": false,
"loadPluginInWeb": true, "loadPluginInWeb": true,
"loadPluginInNative": true, "loadPluginInNative": true,
......
This diff is collapsed.
{
"ver": "1.0.8",
"uuid": "28112492-beae-4e6a-a0f4-e7b31a14f575",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}
\ No newline at end of file
This diff is collapsed.
{
"ver": "1.0.8",
"uuid": "c7a121a7-92a9-440e-a7e9-d4f5141e3f45",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}
\ No newline at end of file
{
"ver": "1.1.2",
"uuid": "79a8363c-304f-444a-9628-a8619e8d52a0",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
\ No newline at end of file
{
"ver": "1.1.2",
"uuid": "97c0238b-e55f-4981-9d17-66fdc335efa4",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
\ No newline at end of file
const { ccclass, property } = cc._decorator;
import Router from '../../router';
@ccclass
export default class Empty extends cc.Component {
onLoad() {
console.log('Empty 场景加载1');
// 检查并清理 middleLayer_for_jxw_demo 场景的常驻节点
this.cleanupMiddleLayerPersistNodes();
}
/**
* 清理 middleLayer_for_jxw_demo 场景的常驻节点
*/
private cleanupMiddleLayerPersistNodes() {
console.log('🧹 检查并清理 middleLayer_for_jxw_demo 场景的常驻节点...');
try {
console.log('🧹 检查并清理 middleLayer_for_jxw_demo 场景的常驻节点...');
// 查找 middleLayer 节点
const middleLayerNode = cc.find('middleLayer');
if (middleLayerNode) {
// 检查是否为常驻节点
if (cc.game.isPersistRootNode(middleLayerNode)) {
console.log('⚠️ 发现 middleLayer 节点仍然是常驻节点,正在移除...');
cc.game.removePersistRootNode(middleLayerNode);
console.log('✅ 已移除 middleLayer 节点的常驻设置');
} else {
console.log('✅ middleLayer 节点不是常驻节点,无需处理');
}
} else {
console.log('ℹ️ 未找到 middleLayer 节点,可能已经被清理');
}
// 检查 RouterNode(通常保持常驻)
const routerNode = cc.find('RouterNode');
if (routerNode) {
const isRouterPersist = cc.game.isPersistRootNode(routerNode);
console.log(`RouterNode 常驻状态: ${isRouterPersist ? '' : ''}`);
}
} catch (error) {
console.error('❌ 清理常驻节点失败:', error);
}
}
/**
* 运行游戏中心
*/
onRunGameCenter() {
const router = Router.getInstance();
router.navigateTo('/index', {
params: {
source: 'empty',
timestamp: Date.now()
}
}).then(success => {
if (success) {
console.log('成功导航到主场景');
} else {
console.error('导航到主场景失败');
}
});
}
/**
* 返回按钮点击事件
*/
onReturnClick() {
const router = Router.getInstance();
router.navigateTo('/index', {
params: {
source: 'empty',
action: 'return'
}
});
}
}
\ No newline at end of file
{
"ver": "1.0.8",
"uuid": "6f6bcd57-2891-423d-89c0-251d5e1ebb44",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}
\ No newline at end of file
{
"ver": "1.1.2",
"uuid": "a93ca14d-9947-49cc-a67a-6aa514c79463",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
\ No newline at end of file
const { ccclass, property } = cc._decorator;
import Router from '../../router';
@ccclass
export default class GameCenter extends cc.Component {
onLoad() {
console.log('GameCenter 场景加载');
}
/**
* 返回主场景
*/
onReturnToMain() {
const router = Router.getInstance();
router.navigateTo('/index', {
params: {
source: 'gamecenter',
timestamp: Date.now()
}
}).then(success => {
if (success) {
console.log('成功返回主场景');
} else {
console.error('返回主场景失败');
}
});
}
/**
* 进入空页面
*/
onEnterEmpty() {
const router = Router.getInstance();
router.navigateTo('/empty', {
params: {
source: 'gamecenter',
timestamp: Date.now()
}
});
}
}
\ No newline at end of file
{
"ver": "1.0.8",
"uuid": "eeca0181-fada-4660-ab19-5ae955941b39",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}
\ No newline at end of file
{
"ver": "1.1.2",
"uuid": "2bf53579-cc34-4abe-b8fb-32b7ed0aa07e",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
\ No newline at end of file
# 🛣️ 路由系统
一个为 Cocos Creator 设计的前端风格路由管理系统,支持路径导航、场景切换和路由守卫。
## ✨ 特性
- 🚀 **前端风格路由** - 使用 `/index`, `/gamecenter` 等路径
- 🎯 **场景分离** - 路径与场景名分离,支持灵活配置
- 🛡️ **简化守卫** - 轻量级的认证和权限检查
- 🔄 **冲突处理** - 智能的路由冲突解决策略
- 📱 **Cocos Creator 集成** - 完全兼容 Cocos Creator 场景系统
- 🧹 **无冗余** - 精简的代码结构,易于维护
## 🏗️ 架构
```
router/
├── index.ts # 🚪 统一入口
├── core/ # ⚙️ 核心功能
│ ├── router.ts # 🎮 路由管理器
│ └── types.ts # 📝 类型定义
├── guards/ # 🛡️ 路由守卫
│ ├── index.ts # 🚪 守卫入口
│ ├── factory.ts # 🏭 守卫工厂
│ └── gameCenterGuard.ts # 🎮 游戏中心守卫
├── config/ # ⚙️ 配置相关
│ └── defaultRoutes.ts # 🎯 默认路由配置
└── testNoBuiltin.ts # 🧪 测试文件
```
## 🚀 快速开始
### 1. 初始化路由系统
```typescript
import Router from './router/core/router';
// 获取路由实例
const router = Router.getInstance();
// 添加路由
router.addRoute({
path: '/index',
name: 'index',
sceneName: 'middleLayer_for_jxw_demo',
title: '主场景',
meta: {
requiresAuth: false,
requiresPermission: false
}
});
```
### 2. 导航到路由
```typescript
// 使用路径导航
await router.navigateTo('/index');
// 使用名称导航
await router.navigateTo('index');
// 使用场景名导航(向后兼容)
await router.navigateTo('middleLayer_for_jxw_demo');
```
### 3. 添加路由守卫
```typescript
import { SimpleGuard } from './router/guards/factory';
const guard = new SimpleGuard();
guard.setAuthStatus(true, ['admin_access']);
router.addGlobalGuard(guard);
```
## 🛡️ 路由守卫
### 简化守卫系统
系统使用简化的守卫,只保留必要的功能:
-**认证检查** - 检查用户是否已登录
-**权限检查** - 检查用户是否有访问权限
-**复杂日志** - 移除了复杂的日志记录
-**加载状态** - 移除了复杂的加载状态管理
### 守卫配置
```typescript
{
path: '/gamecenter',
name: 'gamecenter',
sceneName: 'gamecenter',
title: '游戏中心',
meta: {
requiresAuth: true, // 需要认证
permissions: ['game_access'] // 需要特定权限
}
}
```
## ⚙️ 配置选项
### 路由冲突处理
```typescript
router.addRoute(route, {
source: 'custom_config', // 来源标识
conflictStrategy: 'merge', // 冲突策略
allowOverride: true // 允许覆盖
});
```
**冲突策略选项:**
- `skip` - 跳过冲突的路由
- `override` - 覆盖现有路由
- `merge` - 合并路由配置
- `error` - 抛出错误(默认)
## 🧪 测试
### 运行测试
```typescript
// 在浏览器控制台中运行
window.testNoBuiltin.runAllTests();
// 或者运行单个测试
window.testNoBuiltin.testRouterStatus();
window.testNoBuiltin.testSimpleGuardSystem();
```
### 可用测试函数
- `testRouterStatus()` - 测试路由系统状态
- `testManualRouteAddition()` - 测试手动添加路由
- `testBatchRouteAddition()` - 测试批量添加路由
- `testSimpleGuardSystem()` - 测试简化守卫系统
## 🔧 高级功能
### 批量操作
```typescript
// 批量添加路由
const routes = [route1, route2, route3];
const result = router.addRoutes(routes, {
source: 'batch_import',
conflictStrategy: 'merge'
});
console.log(`成功: ${result.added}, 跳过: ${result.skipped}, 失败: ${result.failed}`);
```
### 路由管理
```typescript
// 检查路由是否存在
if (router.hasRoute('/gamecenter')) {
console.log('游戏中心路由已存在');
}
// 获取路由来源
const source = router.getRouteSource('/gamecenter');
console.log('路由来源:', source);
// 移除路由
router.removeRoute('/test');
```
## 📝 类型定义
### RouteConfig
```typescript
interface RouteConfig {
path: string; // 前端路径(如 /index)
name: string; // 路由名称
sceneName: string; // Cocos Creator 场景名
title: string; // 路由标题
meta?: RouteMeta; // 元数据
beforeEnter?: RouteGuardFunction; // 进入前守卫
afterEnter?: RouteGuardFunction; // 进入后守卫
beforeLeave?: RouteGuardFunction; // 离开前守卫
afterLeave?: RouteGuardFunction; // 离开后守卫
}
```
### RouteMeta
```typescript
interface RouteMeta {
requiresAuth?: boolean; // 是否需要认证
requiresPermission?: boolean; // 是否需要权限
permissions?: string[]; // 所需权限列表
keepAlive?: boolean; // 是否保持活跃
source?: string; // 路由来源
}
```
## 🚫 限制说明
- **内置路由已禁用** - 系统不会自动加载任何内置路由
- **需要手动配置** - 所有路由必须手动添加
- **简化守卫** - 移除了复杂的日志和加载状态管理
- **无自动导入** - 不支持从外部文件自动导入路由配置
## 🐛 故障排除
### 常见问题
#### 1. "路由不存在" 错误
```
router.ts:355 路由不存在: /gamecenter
```
**解决方案:**
```typescript
// 检查路由是否存在
const router = Router.getInstance();
if (!router.hasRoute('/gamecenter')) {
// 初始化默认路由
import { initializeDefaultRoutes } from './router/config/defaultRoutes';
initializeDefaultRoutes(router);
}
```
#### 2. "Cannot find module" 错误
```
load script [./authGuard] failed : Error: Cannot find module './authGuard'
```
**解决方案:**
这通常是因为旧的导入路径。确保使用正确的导入:
```typescript
// ❌ 错误的导入
import { AuthGuard } from './guards/authGuard';
// ✅ 正确的导入
import { SimpleGuard } from './guards/factory';
```
#### 3. 路由系统初始化时序问题
**解决方案:**
确保在导航之前路由系统已完全初始化:
```typescript
// 在 middleLayer.ts 中
runSubScene() {
this.scheduleOnce(() => {
const router = Router.getInstance();
// 检查路由是否存在
if (!router.hasRoute('/gamecenter')) {
initializeDefaultRoutes(router);
}
router.navigateTo('/gamecenter');
}, 0.2); // 适当的延迟
}
```
### 调试技巧
#### 1. 检查路由状态
```typescript
const router = Router.getInstance();
router.debugRoutes(); // 显示所有路由信息
```
#### 2. 运行修复测试
```typescript
// 在浏览器控制台中
window.testRouterFix.runAllFixTests();
```
#### 3. 手动初始化路由
```typescript
import { initializeDefaultRoutes } from './router/config/defaultRoutes';
const router = Router.getInstance();
initializeDefaultRoutes(router);
router.debugRoutes();
```
## 🔄 迁移指南
### 从旧版本迁移
1. **更新导入路径**
```typescript
// 旧版本
import Router from './router';
// 新版本
import Router from './router/core/router';
```
2. **简化守卫使用**
```typescript
// 旧版本
import { AuthGuard, PermissionGuard } from './router/guards';
// 新版本
import { SimpleGuard } from './router/guards/factory';
```
3. **手动添加路由**
```typescript
// 旧版本:自动加载内置路由
// 新版本:手动添加所需路由
router.addRoute({
path: '/index',
name: 'index',
sceneName: 'middleLayer_for_jxw_demo',
title: '主场景'
});
```
## 📚 示例
### 完整示例
```typescript
import Router from './router/core/router';
class GameManager {
private router: Router;
constructor() {
this.router = Router.getInstance();
this.initRoutes();
}
private initRoutes() {
// 添加主场景路由
this.router.addRoute({
path: '/index',
name: 'index',
sceneName: 'middleLayer_for_jxw_demo',
title: '主场景',
meta: { requiresAuth: false }
});
// 添加游戏中心路由
this.router.addRoute({
path: '/gamecenter',
name: 'gamecenter',
sceneName: 'gamecenter',
title: '游戏中心',
meta: {
requiresAuth: true,
permissions: ['game_access']
}
});
}
public async goToGameCenter() {
return await this.router.navigateTo('/gamecenter');
}
public async goToMain() {
return await this.router.navigateTo('/index');
}
}
```
## 🤝 贡献
欢迎提交 Issue 和 Pull Request!
## �� 许可证
MIT License
\ No newline at end of file
{
"ver": "2.0.0",
"uuid": "07ba58a3-8d89-461b-902e-b47d97277b6b",
"subMetas": {}
}
\ No newline at end of file
{
"ver": "1.1.2",
"uuid": "ea3819fe-453f-43a6-ace2-73c12855187c",
"isBundle": false,
"bundleName": "",
"priority": 1,
"compressionType": {},
"optimizeHotUpdate": {},
"inlineSpriteFrames": {},
"isRemoteBundle": {},
"subMetas": {}
}
\ No newline at end of file
import { RouteConfig } from '../core/types';
/**
* 默认路由配置
* 替代原来的内置路由
*/
export const DEFAULT_ROUTES: RouteConfig[] = [
{
path: '/',
name: 'home',
sceneName: 'middleLayer_for_jxw_demo',
title: '首页',
meta: {
requiresAuth: false,
requiresPermission: false,
keepAlive: true,
source: 'default_config'
}
},
{
path: '/index',
name: 'index',
sceneName: 'middleLayer_for_jxw_demo',
title: '主场景',
meta: {
requiresAuth: false,
requiresPermission: false,
keepAlive: true,
source: 'default_config'
}
},
{
path: '/gamecenter',
name: 'gamecenter',
sceneName: 'gamecenter',
title: '游戏中心',
meta: {
requiresAuth: false,
requiresPermission: false,
keepAlive: false,
source: 'default_config'
}
},
{
path: '/empty',
name: 'empty',
sceneName: 'empty',
title: '空页面',
meta: {
requiresAuth: false,
requiresPermission: false,
keepAlive: false,
source: 'default_config'
}
}
];
/**
* 初始化默认路由
* 在路由系统启动后调用此函数来加载默认路由
*/
export function initializeDefaultRoutes(router: any): void {
console.log('🚀 初始化默认路由...');
const result = router.addRoutes(DEFAULT_ROUTES, {
source: 'default_config',
conflictStrategy: 'error'
});
console.log(`✅ 默认路由初始化完成:`, result);
}
\ No newline at end of file
{
"ver": "1.0.8",
"uuid": "a07d8f55-e252-49fa-a882-422b49ce0576",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment