146 lines
9.3 KiB
TypeScript
146 lines
9.3 KiB
TypeScript
const { ccclass } = cc._decorator;
|
||
declare const wx: any;
|
||
|
||
/** All view code stays in this bundle. The resident bridge injects transport. */
|
||
@ccclass('CoinMadnessPanel')
|
||
export default class CoinMadnessPanel extends cc.Component {
|
||
private api: any;
|
||
private data: any;
|
||
private body: cc.Node;
|
||
private countdown: cc.Label;
|
||
private title: cc.Label;
|
||
private message: cc.Label;
|
||
private offset = 0;
|
||
private busy = false;
|
||
private paying = false;
|
||
private alive = true;
|
||
private root: cc.Node;
|
||
|
||
initialize(data: any, api: any) {
|
||
this.api = api;
|
||
const size = cc.view.getVisibleSize();
|
||
this.node.setContentSize(size); this.node.addComponent(cc.BlockInputEvents);
|
||
this.box(this.node, 'Dim', 0, 0, size.width, size.height, new cc.Color(0, 0, 0, 175), 0);
|
||
this.root = this.box(this.node, 'Card', 0, 0, 660, 1080, new cc.Color(250, 239, 215), 32);
|
||
this.root.scale = Math.min((size.width - 36) / 660, (size.height - 90) / 1080, 1.45);
|
||
this.box(this.root, 'Header', 0, 360, 660, 360, new cc.Color(233, 126, 29), 32);
|
||
this.title = this.label(this.root, 'COIN MADNESS', 0, 474, 46, 500, cc.Color.WHITE);
|
||
this.countdown = this.label(this.root, '', 0, 418, 26, 540, new cc.Color(255, 245, 211));
|
||
this.button(this.root, '×', 278, 491, 62, 62, () => this.api.close(), new cc.Color(194, 72, 32));
|
||
for (let i = 0; i < 7; i++) {
|
||
const n = new cc.Node('Coin'); this.root.addChild(n); n.setPosition((i - 3) * 47, 330 + (i % 2) * 16);
|
||
const g = n.addComponent(cc.Graphics); g.fillColor = new cc.Color(255, 207, 55);
|
||
g.strokeColor = new cc.Color(190, 105, 16); g.lineWidth = 5; g.circle(0, 0, 31); g.fill(); g.stroke();
|
||
this.label(n, '$', 0, 0, 36, 56, new cc.Color(192, 110, 22));
|
||
}
|
||
this.body = new cc.Node('Body'); this.root.addChild(this.body);
|
||
this.message = this.label(this.root, '购买后开始计数,每段领取后进入下一段', 0, -495, 22, 600);
|
||
this.render(data); this.schedule(this.tick, 1);
|
||
}
|
||
render(data: any) {
|
||
if (!this.alive || !data) return;
|
||
this.data = data; this.offset = data.serverNow - Date.now();
|
||
this.body.children.slice().forEach(child => child.destroy());
|
||
this.body.removeAllChildren();
|
||
const state = data.state;
|
||
if (!state) { this.label(this.body, '活动暂未开放', 0, 0, 32); return; }
|
||
const config = state.config;
|
||
this.title.string = config.title || 'COIN MADNESS';
|
||
this.label(this.body, '最高可获得 ' + config.stages.reduce((n, s) => n + s.coins, 0) + ' 金币', 0, 240, 31);
|
||
const expired = Date.now() + this.offset >= config.endsAt;
|
||
let text = state.paidAt ? '已解锁 · 逐段闯关领奖' : '解锁全部任务 ¥' + (config.priceCents / 100).toFixed(2);
|
||
if (expired) text = '本期已结束';
|
||
if (state.stage >= config.stages.length) text = '全部奖励已领取';
|
||
this.button(this.body, text, 0, 166, 570, 80, () => {
|
||
if (!state.paidAt && !expired) this.purchase();
|
||
}, state.paidAt || expired ? new cc.Color(168, 157, 129) : new cc.Color(74, 162, 60));
|
||
const viewport = new cc.Node('Viewport'); this.body.addChild(viewport);
|
||
viewport.setPosition(0, -182); viewport.setContentSize(602, 550); viewport.addComponent(cc.Mask);
|
||
const content = new cc.Node('Stages'); viewport.addChild(content); content.anchorY = 1;
|
||
content.setContentSize(602, Math.max(550, config.stages.length * 112)); content.y = 275;
|
||
const scroll = viewport.addComponent(cc.ScrollView); scroll.content = content; scroll.horizontal = false;
|
||
config.stages.forEach((stage, index) => {
|
||
const complete = index < state.stage;
|
||
const current = index === state.stage;
|
||
const earned = current && state.paidAt && state.progress >= stage.wins;
|
||
const row = this.box(content, 'Stage' + index, 0, -56 - index * 112, 580, 100,
|
||
complete ? new cc.Color(225, 231, 207) : cc.Color.WHITE, 14);
|
||
this.label(row, '+' + stage.coins, -189, 7, 32, 164, new cc.Color(178, 116, 22));
|
||
this.label(row, '完成 ' + stage.wins + ' 关', 1, 22, 25, 208);
|
||
this.label(row, complete ? '已完成' : ((current && state.paidAt ? state.progress : 0) + ' / ' + stage.wins), 1, -18, 22, 200);
|
||
const status = complete ? '已领取' : expired ? '已结束' : earned ? '领取' : current && state.paidAt ? '进行中' : '未解锁';
|
||
this.button(row, status, 199, 0, 142, 62, () => {
|
||
if (earned && !expired) this.claim(index);
|
||
}, earned && !expired ? new cc.Color(74, 162, 60) : new cc.Color(165, 167, 154));
|
||
});
|
||
this.tick();
|
||
if (data.pending || data.needsSettlement) this.settle();
|
||
}
|
||
private tick() {
|
||
if (!this.data?.state) return;
|
||
const remaining = Math.max(0, Math.ceil((this.data.state.config.endsAt - Date.now() - this.offset) / 1000));
|
||
this.countdown.string = remaining ? Math.floor(remaining / 86400) + '天 ' + Math.floor(remaining / 3600) % 24
|
||
+ '时 ' + Math.floor(remaining / 60) % 60 + '分 ' + remaining % 60 + '秒' : '活动已结束';
|
||
if (!remaining && !this.busy && !this.data.state.settled) this.api.refresh();
|
||
if (this.paying && !this.busy) this.api.refresh();
|
||
if (this.data.state.paidAt) this.paying = false;
|
||
}
|
||
private claim(stage: number) { this.run('claim', { periodId: this.data.state.config.periodId, stage }); }
|
||
private settle() { this.run(this.data.pending ? 'read' : 'settle', { periodId: this.data.state.config.periodId }); }
|
||
private run(action: string, body: any) {
|
||
if (this.busy) return;
|
||
this.busy = true; this.message.string = '正在同步奖励…';
|
||
this.api.request(action, body, result => {
|
||
if (!this.alive) return;
|
||
this.busy = false;
|
||
this.message.string = result?.code === 1 ? '奖励已同步' : result?.msg || '网络异常,请重新打开活动重试';
|
||
if (result?.code === 1) this.render(result.data);
|
||
});
|
||
}
|
||
private purchase() {
|
||
if (this.busy || this.paying) return;
|
||
if (typeof wx === 'undefined' || typeof wx.requestMidasPaymentGameItem !== 'function') {
|
||
this.message.string = '当前平台暂不支持此购买方式'; return;
|
||
}
|
||
this.busy = true; this.message.string = '正在创建订单…';
|
||
const platform = wx.getSystemInfoSync().platform;
|
||
this.api.order(platform === 'ios' ? 'wx/iosorderPaySig' : 'wx/orderPaySig', { itemid: 'coin_madness', itemCount: 1 }, result => {
|
||
if (!this.alive) return;
|
||
if (result?.code !== 1) { this.busy = false; this.message.string = result?.msg || '创建订单失败'; return; }
|
||
const payment = result.data;
|
||
wx.requestMidasPaymentGameItem({ signData: payment.signData, paySig: payment.paySig, signature: payment.signature,
|
||
success: () => {
|
||
if (!this.alive) return;
|
||
this.busy = false; this.paying = true;
|
||
this.message.string = '等待服务器确认付款…'; this.api.refresh();
|
||
this.scheduleOnce(() => { this.paying = false; }, 30);
|
||
},
|
||
fail: (error: any) => { if (this.alive) { this.busy = false;
|
||
this.message.string = error?.errCode === 16 ? '当前客户端不支持此直购方式' : '支付未完成,可重试';
|
||
this.api.refresh(); } },
|
||
});
|
||
});
|
||
}
|
||
private box(parent: cc.Node, name: string, x: number, y: number, w: number, h: number, color: cc.Color, radius: number): cc.Node {
|
||
const node = new cc.Node(name); parent.addChild(node); node.setPosition(x, y); node.setContentSize(w, h);
|
||
const g = node.addComponent(cc.Graphics); g.fillColor = color;
|
||
if (radius) g.roundRect(-w / 2, -h / 2, w, h, radius); else g.rect(-w / 2, -h / 2, w, h);
|
||
g.fill(); return node;
|
||
}
|
||
private label(parent: cc.Node, text: string, x: number, y: number, size: number, width = 560, color = new cc.Color(92, 63, 37)): cc.Label {
|
||
const node = new cc.Node('Label'); parent.addChild(node); node.setPosition(x, y); node.setContentSize(width, size * 1.6);
|
||
const label = node.addComponent(cc.Label); label.string = text; label.fontSize = size; label.lineHeight = size + 6;
|
||
label.useSystemFont = true; label.fontFamily = 'Arial';
|
||
label.horizontalAlign = cc.Label.HorizontalAlign.CENTER; label.verticalAlign = cc.Label.VerticalAlign.CENTER;
|
||
label.overflow = cc.Label.Overflow.SHRINK;
|
||
// Adding an enabled Label initially sizes its empty text to zero; set bounds afterwards.
|
||
node.setContentSize(width, size * 1.6); node.color = color; return label;
|
||
}
|
||
private button(parent: cc.Node, text: string, x: number, y: number, w: number, h: number, action: () => void, color: cc.Color) {
|
||
const button = this.box(parent, 'Button', x, y, w, h, color, 14);
|
||
this.label(button, text, 0, 0, Math.min(28, h * 0.45), w - 12, cc.Color.WHITE);
|
||
button.on(cc.Node.EventType.TOUCH_END, (event: cc.Event.EventTouch) => { event.stopPropagation(); if (!this.busy) action(); });
|
||
}
|
||
onDestroy() { this.alive = false; this.unscheduleAllCallbacks(); this.api = null; this.data = null; }
|
||
}
|