MatchMaster/assets/coin_madness/CoinMadnessPanel.ts

146 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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; }
}