diff --git a/src/api/proto.js b/src/api/proto.js index b88b066..7ff0152 100644 --- a/src/api/proto.js +++ b/src/api/proto.js @@ -3585,7 +3585,7 @@ export const api = $root.api = (() => { * Properties of a ReqKickOutTable. * @memberof api * @interface IReqKickOutTable - * @property {number|null} [playerId] ReqKickOutTable playerId + * @property {number|Long|null} [playerId] ReqKickOutTable playerId */ /** @@ -3605,11 +3605,11 @@ export const api = $root.api = (() => { /** * ReqKickOutTable playerId. - * @member {number} playerId + * @member {number|Long} playerId * @memberof api.ReqKickOutTable * @instance */ - ReqKickOutTable.prototype.playerId = 0; + ReqKickOutTable.prototype.playerId = $util.Long ? $util.Long.fromBits(0,0,false) : 0; /** * Creates a new ReqKickOutTable instance using the specified properties. @@ -3636,7 +3636,7 @@ export const api = $root.api = (() => { if (!writer) writer = $Writer.create(); if (message.playerId != null && Object.hasOwnProperty.call(message, "playerId")) - writer.uint32(/* id 1, wireType 0 =*/8).int32(message.playerId); + writer.uint32(/* id 1, wireType 0 =*/8).int64(message.playerId); return writer; }; @@ -3672,7 +3672,7 @@ export const api = $root.api = (() => { let tag = reader.uint32(); switch (tag >>> 3) { case 1: { - message.playerId = reader.int32(); + message.playerId = reader.int64(); break; } default: @@ -3711,8 +3711,8 @@ export const api = $root.api = (() => { if (typeof message !== "object" || message === null) return "object expected"; if (message.playerId != null && message.hasOwnProperty("playerId")) - if (!$util.isInteger(message.playerId)) - return "playerId: integer expected"; + if (!$util.isInteger(message.playerId) && !(message.playerId && $util.isInteger(message.playerId.low) && $util.isInteger(message.playerId.high))) + return "playerId: integer|Long expected"; return null; }; @@ -3729,7 +3729,14 @@ export const api = $root.api = (() => { return object; let message = new $root.api.ReqKickOutTable(); if (object.playerId != null) - message.playerId = object.playerId | 0; + if ($util.Long) + (message.playerId = $util.Long.fromValue(object.playerId)).unsigned = false; + else if (typeof object.playerId === "string") + message.playerId = parseInt(object.playerId, 10); + else if (typeof object.playerId === "number") + message.playerId = object.playerId; + else if (typeof object.playerId === "object") + message.playerId = new $util.LongBits(object.playerId.low >>> 0, object.playerId.high >>> 0).toNumber(); return message; }; @@ -3747,9 +3754,16 @@ export const api = $root.api = (() => { options = {}; let object = {}; if (options.defaults) - object.playerId = 0; + if ($util.Long) { + let long = new $util.Long(0, 0, false); + object.playerId = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.playerId = options.longs === String ? "0" : 0; if (message.playerId != null && message.hasOwnProperty("playerId")) - object.playerId = message.playerId; + if (typeof message.playerId === "number") + object.playerId = options.longs === String ? String(message.playerId) : message.playerId; + else + object.playerId = options.longs === String ? $util.Long.prototype.toString.call(message.playerId) : options.longs === Number ? new $util.LongBits(message.playerId.low >>> 0, message.playerId.high >>> 0).toNumber() : message.playerId; return object; }; @@ -3782,6 +3796,181 @@ export const api = $root.api = (() => { return ReqKickOutTable; })(); + api.ResKickOutTable = (function() { + + /** + * Properties of a ResKickOutTable. + * @memberof api + * @interface IResKickOutTable + */ + + /** + * Constructs a new ResKickOutTable. + * @memberof api + * @classdesc Represents a ResKickOutTable. + * @implements IResKickOutTable + * @constructor + * @param {api.IResKickOutTable=} [properties] Properties to set + */ + function ResKickOutTable(properties) { + if (properties) + for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Creates a new ResKickOutTable instance using the specified properties. + * @function create + * @memberof api.ResKickOutTable + * @static + * @param {api.IResKickOutTable=} [properties] Properties to set + * @returns {api.ResKickOutTable} ResKickOutTable instance + */ + ResKickOutTable.create = function create(properties) { + return new ResKickOutTable(properties); + }; + + /** + * Encodes the specified ResKickOutTable message. Does not implicitly {@link api.ResKickOutTable.verify|verify} messages. + * @function encode + * @memberof api.ResKickOutTable + * @static + * @param {api.IResKickOutTable} message ResKickOutTable message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ResKickOutTable.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + return writer; + }; + + /** + * Encodes the specified ResKickOutTable message, length delimited. Does not implicitly {@link api.ResKickOutTable.verify|verify} messages. + * @function encodeDelimited + * @memberof api.ResKickOutTable + * @static + * @param {api.IResKickOutTable} message ResKickOutTable message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + ResKickOutTable.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a ResKickOutTable message from the specified reader or buffer. + * @function decode + * @memberof api.ResKickOutTable + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {api.ResKickOutTable} ResKickOutTable + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ResKickOutTable.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + let end = length === undefined ? reader.len : reader.pos + length, message = new $root.api.ResKickOutTable(); + while (reader.pos < end) { + let tag = reader.uint32(); + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a ResKickOutTable message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof api.ResKickOutTable + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {api.ResKickOutTable} ResKickOutTable + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + ResKickOutTable.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a ResKickOutTable message. + * @function verify + * @memberof api.ResKickOutTable + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + ResKickOutTable.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + return null; + }; + + /** + * Creates a ResKickOutTable message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof api.ResKickOutTable + * @static + * @param {Object.} object Plain object + * @returns {api.ResKickOutTable} ResKickOutTable + */ + ResKickOutTable.fromObject = function fromObject(object) { + if (object instanceof $root.api.ResKickOutTable) + return object; + return new $root.api.ResKickOutTable(); + }; + + /** + * Creates a plain object from a ResKickOutTable message. Also converts values to other types if specified. + * @function toObject + * @memberof api.ResKickOutTable + * @static + * @param {api.ResKickOutTable} message ResKickOutTable + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + ResKickOutTable.toObject = function toObject() { + return {}; + }; + + /** + * Converts this ResKickOutTable to JSON. + * @function toJSON + * @memberof api.ResKickOutTable + * @instance + * @returns {Object.} JSON object + */ + ResKickOutTable.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for ResKickOutTable + * @function getTypeUrl + * @memberof api.ResKickOutTable + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + ResKickOutTable.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/api.ResKickOutTable"; + }; + + return ResKickOutTable; + })(); + api.ReqGameAction = (function() { /** @@ -4369,7 +4558,8 @@ export const api = $root.api = (() => { * @property {number|null} [tableNo] ResGameFullStatus tableNo * @property {number|null} [gameStage] ResGameFullStatus gameStage * @property {number|null} [chip] ResGameFullStatus chip - * @property {number|null} [RoundTimes] ResGameFullStatus RoundTimes + * @property {number|null} [roundTimes] ResGameFullStatus roundTimes + * @property {number|Long|null} [playerId] ResGameFullStatus playerId * @property {Array.|null} [publicCard] ResGameFullStatus publicCard * @property {Array.|null} [players] ResGameFullStatus players */ @@ -4424,12 +4614,20 @@ export const api = $root.api = (() => { ResGameFullStatus.prototype.chip = 0; /** - * ResGameFullStatus RoundTimes. - * @member {number} RoundTimes + * ResGameFullStatus roundTimes. + * @member {number} roundTimes * @memberof api.ResGameFullStatus * @instance */ - ResGameFullStatus.prototype.RoundTimes = 0; + ResGameFullStatus.prototype.roundTimes = 0; + + /** + * ResGameFullStatus playerId. + * @member {number|Long} playerId + * @memberof api.ResGameFullStatus + * @instance + */ + ResGameFullStatus.prototype.playerId = $util.Long ? $util.Long.fromBits(0,0,false) : 0; /** * ResGameFullStatus publicCard. @@ -4479,8 +4677,10 @@ export const api = $root.api = (() => { writer.uint32(/* id 3, wireType 0 =*/24).int32(message.gameStage); if (message.chip != null && Object.hasOwnProperty.call(message, "chip")) writer.uint32(/* id 4, wireType 0 =*/32).int32(message.chip); - if (message.RoundTimes != null && Object.hasOwnProperty.call(message, "RoundTimes")) - writer.uint32(/* id 5, wireType 0 =*/40).int32(message.RoundTimes); + if (message.roundTimes != null && Object.hasOwnProperty.call(message, "roundTimes")) + writer.uint32(/* id 5, wireType 0 =*/40).int32(message.roundTimes); + if (message.playerId != null && Object.hasOwnProperty.call(message, "playerId")) + writer.uint32(/* id 6, wireType 0 =*/48).int64(message.playerId); if (message.publicCard != null && message.publicCard.length) for (let i = 0; i < message.publicCard.length; ++i) $root.api.Card.encode(message.publicCard[i], writer.uint32(/* id 9, wireType 2 =*/74).fork()).ldelim(); @@ -4538,7 +4738,11 @@ export const api = $root.api = (() => { break; } case 5: { - message.RoundTimes = reader.int32(); + message.roundTimes = reader.int32(); + break; + } + case 6: { + message.playerId = reader.int64(); break; } case 9: { @@ -4600,9 +4804,12 @@ export const api = $root.api = (() => { if (message.chip != null && message.hasOwnProperty("chip")) if (!$util.isInteger(message.chip)) return "chip: integer expected"; - if (message.RoundTimes != null && message.hasOwnProperty("RoundTimes")) - if (!$util.isInteger(message.RoundTimes)) - return "RoundTimes: integer expected"; + if (message.roundTimes != null && message.hasOwnProperty("roundTimes")) + if (!$util.isInteger(message.roundTimes)) + return "roundTimes: integer expected"; + if (message.playerId != null && message.hasOwnProperty("playerId")) + if (!$util.isInteger(message.playerId) && !(message.playerId && $util.isInteger(message.playerId.low) && $util.isInteger(message.playerId.high))) + return "playerId: integer|Long expected"; if (message.publicCard != null && message.hasOwnProperty("publicCard")) { if (!Array.isArray(message.publicCard)) return "publicCard: array expected"; @@ -4644,8 +4851,17 @@ export const api = $root.api = (() => { message.gameStage = object.gameStage | 0; if (object.chip != null) message.chip = object.chip | 0; - if (object.RoundTimes != null) - message.RoundTimes = object.RoundTimes | 0; + if (object.roundTimes != null) + message.roundTimes = object.roundTimes | 0; + if (object.playerId != null) + if ($util.Long) + (message.playerId = $util.Long.fromValue(object.playerId)).unsigned = false; + else if (typeof object.playerId === "string") + message.playerId = parseInt(object.playerId, 10); + else if (typeof object.playerId === "number") + message.playerId = object.playerId; + else if (typeof object.playerId === "object") + message.playerId = new $util.LongBits(object.playerId.low >>> 0, object.playerId.high >>> 0).toNumber(); if (object.publicCard) { if (!Array.isArray(object.publicCard)) throw TypeError(".api.ResGameFullStatus.publicCard: array expected"); @@ -4691,7 +4907,12 @@ export const api = $root.api = (() => { object.tableNo = 0; object.gameStage = 0; object.chip = 0; - object.RoundTimes = 0; + object.roundTimes = 0; + if ($util.Long) { + let long = new $util.Long(0, 0, false); + object.playerId = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.playerId = options.longs === String ? "0" : 0; } if (message.inGame != null && message.hasOwnProperty("inGame")) object.inGame = message.inGame; @@ -4701,8 +4922,13 @@ export const api = $root.api = (() => { object.gameStage = message.gameStage; if (message.chip != null && message.hasOwnProperty("chip")) object.chip = message.chip; - if (message.RoundTimes != null && message.hasOwnProperty("RoundTimes")) - object.RoundTimes = message.RoundTimes; + if (message.roundTimes != null && message.hasOwnProperty("roundTimes")) + object.roundTimes = message.roundTimes; + if (message.playerId != null && message.hasOwnProperty("playerId")) + if (typeof message.playerId === "number") + object.playerId = options.longs === String ? String(message.playerId) : message.playerId; + else + object.playerId = options.longs === String ? $util.Long.prototype.toString.call(message.playerId) : options.longs === Number ? new $util.LongBits(message.playerId.low >>> 0, message.playerId.high >>> 0).toNumber() : message.playerId; if (message.publicCard && message.publicCard.length) { object.publicCard = []; for (let j = 0; j < message.publicCard.length; ++j) diff --git a/src/api/websocket.js b/src/api/websocket.js index 488862b..09dac75 100644 --- a/src/api/websocket.js +++ b/src/api/websocket.js @@ -1,18 +1,24 @@ import proto from '@/api/proto'; import { fetchOpMap } from '@/api/api' +import { getStorage } from '@/utils/storage'; +import { showToast } from '@/utils/application'; const { api } = proto const websocket = { conn: null, - state: 0, // 0未初始,1打开,2关闭 TODO 未连接,连接中 方法等待队列 + status: 0, // 0未初始,1打开,2已认证,3关闭 TODO 未连接,连接中 方法等待队列 seq: 1, // 消息序号 offset: 0, opFail: 0, opSuccess: 0, opPathMap: {}, nameOpMap: {}, + // 消息监听列表 + msgListener: {}, // {op: func} + waitInitCalls: [], init(token, { offset, opFail, opSuccess, opPathMap, nameOpMap }) { + // TODO 连接状态判定 this.offset = offset || 0; this.opFail = opFail; @@ -27,17 +33,28 @@ const websocket = { console.log(this) ws.binaryType = 'arraybuffer' ws.onopen = (e) => { + this.status = 1; // 连接后立即认证连接 const req = api.ReqIdentity.create({ token }) // const reqBuffer = api.ReqIdentity.encode(req).finish() this.send(req, res => { - console.log('call1:', res) + this.status = 2; + console.log('identity success:', res) + + // 依次发送等待连接的消息队列 + for (let i = 0; i < this.waitInitCalls.length; i++) { + if (this.waitInitCalls[i]) { + this.waitInitCalls[i](); + this.waitInitCalls[i] = null; + } + } }, err => { - console.log('call2:', err) + showToast({title: err}) + console.error('连接认证失败:', err) }) } ws.onmessage = (e) => { - console.log('msg:', e.data) + // console.log('msg:', e.data) const wrap = api.ProtoWrap.decode(new Uint8Array(e.data)) // 解码响应体 const opPath = this.opPathMap[wrap.op] @@ -65,19 +82,23 @@ const websocket = { // 找消息类型的listen执行 const listener = this.msgListener[wrap.op]; if (listener) { - listener(res) + listener(res); return; } console.log('no handler msg:', wrap.op, res) } + ws.onerror = (a, b, c) => { - console.log('error:', a, b, c) + this.status = 3; + console.log('ws error:', a, b, c) // var interval = setInterval(() => { // this.reconnect(); // }, Math.random() * 3); } + ws.onclose = e => { - console.log('close', e) + this.status = 3; + console.log('ws close', e) } }, reconnect() { @@ -85,6 +106,13 @@ const websocket = { }, waitCall: {}, // 等待响应的 callback 列表 send(msg, resCall, errCall) { + window.msg = msg; + // 不是认证消息且连接未认证,将请求放到等待队列 + if (msg.__proto__.constructor !== api.ReqIdentity && this.status !== 2) { + this.waitInitCalls.push(() => this.send(msg, resCall, errCall)); + return; + } + // 序列化请求消息... let typeUrl = msg.constructor.getTypeUrl(); const idx = typeUrl.lastIndexOf("/"); typeUrl = idx === -1 ? typeUrl : typeUrl.substring(idx + 1); @@ -103,15 +131,37 @@ const websocket = { } // 定时检查是否超时... }, +} - msgListener: {}, - // 监听消息类型 - listen(type, call) { +export default websocket - }, +export const addMsgListen = (type, call) => { + if (websocket.status !== 2) { + websocket.waitInitCalls.push(() => addMsgListen(type, call)); + return; + } + console.log('add listen...', type) + const op = websocket.nameOpMap[type]; + if (op === undefined) { + console.error(`not found listen msg type [${type}] for all type:`, websocket.nameOpMap); + return; + } + websocket.msgListener[op] = call; } -export default websocket +// 监听消息类型 +export const removeMsgListener = type => { + if (websocket.status !== 2) { + websocket.waitInitCalls.push(() => removeMsgListener(type)); + return; + } + const op = websocket.nameOpMap[type]; + if (op === undefined) { + console.error(`when remove, not found listen msg type [${type}] for all type:`, websocket.nameOpMap); + return -1; + } + websocket.msgListener[op] = null; +} export const sendPromise = function(msg) { return new Promise((resolve, reject) => { @@ -120,8 +170,12 @@ export const sendPromise = function(msg) { } ;(async function() { - const token = "15PohpTztq4YkMmErI4H71kJWX2pPqnvOZar9HKOfg1KEy0fd8fUNxnBIwuMDcKQEBPG7b9UC7ja_8FtwvYjnA==" + // const token = "15PohpTztq4YkMmErI4H71kJWX2pPqnvOZar9HKOfg1KEy0fd8fUNxnBIwuMDcKQEBPG7b9UC7ja_8FtwvYjnA==" + const token = getStorage('_t') + // console.log('token', token) const opMap = await fetchOpMap() websocket.init(token, opMap.data) + + window.websocket = websocket; })() diff --git a/src/api/wsapi.js b/src/api/wsapi.js index 5636b97..317d69d 100644 --- a/src/api/wsapi.js +++ b/src/api/wsapi.js @@ -9,3 +9,5 @@ export const reqLobbyView = () => sendPromise(api.ReqLobbyView.create()); export const reqGameFullStatus = () => sendPromise(api.ReqGameFullStatus.create({})); export const reqJoinTable = tableNo => sendPromise(api.ReqJoinTable.create({tableNo})); + +export const reqKickOutTable = playerId => sendPromise(api.ReqKickOutTable.create({playerId})); diff --git a/src/assets/icon/refresh-fill.svg b/src/assets/icon/refresh-fill.svg new file mode 100644 index 0000000..c370ac3 --- /dev/null +++ b/src/assets/icon/refresh-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/title.jsx b/src/components/title.jsx index c9f37ee..4c606cb 100644 --- a/src/components/title.jsx +++ b/src/components/title.jsx @@ -8,14 +8,15 @@ import { useSelector } from 'react-redux' function Title(props) { const {username, nickname, avatar} = useSelector(state => state.user); + const { leftSolt, leftIcon, onLeftClick, rightSolt } = props; return ( {/* */} - navigateBack()} /> + {leftSolt || } {props.title} - + {rightSolt || } {props.topNull ? '' : } diff --git a/src/pages/lobby/lobby.jsx b/src/pages/lobby/lobby.jsx index 405591d..0f78b55 100644 --- a/src/pages/lobby/lobby.jsx +++ b/src/pages/lobby/lobby.jsx @@ -14,7 +14,9 @@ import Title from '@/components/title' import coin1 from '@/assets/coin-1.png' import robotIcon from '@/assets/icon/robot-1.svg' import plusIcon from '@/assets/icon/plus-2.svg'; -import classNames from 'classnames'; +import refreshIcon from '@/assets/icon/refresh-fill.svg'; + +import cnames from 'classnames'; const {ReqLobbyView} = proto.api; @@ -24,6 +26,7 @@ class Lobby extends Component { this.state = { tables: [], defaultAvatar: "https://img0.baidu.com/it/u=2477829979,2171947490&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500", + lobbyLoading: false, } this.circleEle = [1,2,3,4,5,6,7]; this.circleElePos = [{x:16,y:8},{x:70,y:-3},{x:110,y:28},{x:115,y:70},{x:80,y:105},{x:20,y:100},{x:-2,y:60}]; @@ -31,16 +34,8 @@ class Lobby extends Component { componentDidMount() { setTimeout(() => { - reqLobbyView() - .then(res => { - console.log('lobby res:', res) - this.setState({tables: res.tables}) - }) - .catch(err => { - console.log('lobby err:', err) - }) + this.handleReqLobbyView(); }, 1500) - // fetchLobbyView() // .then(res => { // console.log('lobby', res) @@ -51,6 +46,28 @@ class Lobby extends Component { // }) } + handleReqLobbyView = () => { + if (this.state.lobbyLoading) { + showToast({title: '加载中...'}) + return + } + this.setState({lobbyLoading: true}, () => { + setTimeout(() => { + reqLobbyView() + .then(res => { + console.log('lobby res:', res) + this.setState({tables: res.tables}) + }) + .catch(err => { + console.log('lobby err:', err) + }) + .finally(() => { + this.setState({lobbyLoading: false}) + }) + }, 1000) + }) + } + async handleJoinTable(table) { try { const res = await reqJoinTable(table.tableNo) @@ -68,7 +85,12 @@ class Lobby extends Component { // console.log('state', this.state) return ( - + <Title title={"Lobby!!"} bgColor={true} leftSolt={ + <Image className={cnames("refresh-icon", {'refresh-icon-rotate': this.state.lobbyLoading})} + src={refreshIcon} + onClick={this.handleReqLobbyView} + /> + }/> <View className="celling"></View> <View className="tab"> { @@ -84,7 +106,7 @@ class Lobby extends Component { top: Taro.pxTransform(this.circleElePos[j].y) }}> <Image - className={classNames("circle-avatar", {'circle-icon': player.robot || !player.id})} + className={cnames("circle-avatar", {'circle-icon': player.robot || !player.id})} src={player.robot ? robotIcon : !player.id ? plusIcon : (player.avatar || this.state.defaultAvatar)} /> </View> diff --git a/src/pages/lobby/lobby.scss b/src/pages/lobby/lobby.scss index d08fe35..279d13c 100644 --- a/src/pages/lobby/lobby.scss +++ b/src/pages/lobby/lobby.scss @@ -1,4 +1,19 @@ +.refresh-icon { + width: 38px; + height: 38px; + border-radius: 50%; +} +.refresh-icon-rotate { + animation: rotate360 1s infinite; + transform-origin: center center; +} +@keyframes rotate360 { + to { + transform: rotate(360deg); + } +} + .celling { height: 43px; } diff --git a/src/pages/table/table.jsx b/src/pages/table/table.jsx index a02bba3..b3fe59d 100644 --- a/src/pages/table/table.jsx +++ b/src/pages/table/table.jsx @@ -3,7 +3,8 @@ import { View, Text, Image, Input, Button } from '@tarojs/components' import { connect } from 'react-redux' import cnames from 'classnames' -import { reqGameFullStatus } from '@/api/wsapi' +import { reqGameFullStatus, reqKickOutTable } from '@/api/wsapi' +import { addMsgListen, removeMsgListener } from '@/api/websocket' import { ifEmpty } from '@/utils/validator' import './table.scss' @@ -13,13 +14,16 @@ import coinIcon from '@/assets/icon/coin-1.svg' import coin2Icon from '@/assets/icon/coin-2.svg' import plus1Icon from '@/assets/icon/plus-1.svg' import sub1Icon from '@/assets/icon/sub-1.svg' +import closeIcon from '@/assets/icon/close-1.svg' + import Taro from '@tarojs/taro' -import { showToast } from '@/utils/application' +import { showToast, redirectTo } from '@/utils/application' +import { isIn } from '@/utils/collect' function PlayerStatus() { return ( <View> - <Text>+20</Text> + <Text>Ready</Text> </View> ) } @@ -32,7 +36,12 @@ function cardDot(card, cardBackCode) { } function cardSuit(card) { - return card.suit || 'D'; + return (card && card.suit) || 'D'; +} + +// 是否空白牌 +function existsCard(card) { + return !!card && !!card.suit; } class Table extends Component { @@ -43,56 +52,113 @@ class Table extends Component { tableNo: 0, stage: 0, // 游戏阶段: 1等待玩家准备;2下大盲注;3下小盲注;4发公共牌;5发第四张公共牌,轮流下注;6发第五张公共牌,轮流下注,7比牌结算 chip: 0, - RoundTimes: 0, + roundTimes: 0, + playerId: 0, players: [], publicCard: [], } } + + componentWillUnmount() { + // 取消监听消息 + removeMsgListener('api.ResGameFullStatus') + removeMsgListener('api.ResKickOutTable') + } + componentDidMount() { - console.log('table mount') - setTimeout(() => { + // setTimeout(() => { + // 监听状态变化消息 + addMsgListen('api.ResGameFullStatus', res => this.handleResGameFullStatus(res)); + addMsgListen('api.ResKickOutTable', res => { + console.log('listener level table', res); + redirectTo({url: '/pages/lobby/lobby', complete() { + showToast({title: '您被请出牌桌'}) + }}); + }); + + // 查询当前牌桌状态 reqGameFullStatus() .then(res => { - this.setState({ - inGame: !!res.inGame, - tableNo: res.tableNo, - stage: res.gameStage, - chip: res.chip || 0, - RoundTimes: res.RoundTimes || 0, - - players: res.players, - publicCard: res.publicCard, - }) - console.log('res game', res, res.publicCard[0].dot, ifEmpty(res.publicCard[0].dot, -3)) - window.publicCard = res.publicCard + this.handleResGameFullStatus(res); }) .catch(err => { - showToast({title: err}) + showToast({title: err}); + if (err.code === 402) { + setTimeout(() => redirectTo({url: '/pages/lobby/lobby'}), 800); + } }) - }, 2000) + // }, 2000) + } + + handleResGameFullStatus = res => { + console.log('res game...++', this, res) + this.setState({ + playerId: res.playerId, + inGame: !!res.inGame, + tableNo: res.tableNo, + stage: res.gameStage, + chip: res.chip || 0, + roundTimes: res.roundTimes || 0, + + players: res.players, + publicCard: res.publicCard, + }) + // console.log('res game', res, res.publicCard[0].dot, ifEmpty(res.publicCard[0].dot, -3)) + window.publicCard = res.publicCard + } + + // 踢人 + handleKickoutPlayer(player) { + console.log('kickout', player) + Taro.showModal({ + title: '提示', + content: `将玩家[${player.username}]移出牌桌?`, + success: res => { + if (!res.confirm) { + return; + } + reqKickOutTable(player.id) + .then(_ => { + showToast({title: `[${player.username}]已离开牌桌`}) + }) + .catch(err => { + showToast({title: err}) + }) + } + }) } render() { const { username, nickname, avatar } = this.props.user; // useSelector(state => state.user); // const players = [1, 2, 3, 4, 5, 6]; - const playerRows = this.state.players.reduce((arr, player, idx) => { + const players = this.state.players.map((player, index) => ({player, index})); + const {playerId, tableNo, stage, chip, roundTimes} = this.state; + + // 从玩家中找到自己 TODO 弄到 state, 避免每次render计算 + // console.log('props', this.props); + const otherPlayers = players.filter(({player}) => player.id !== playerId); + const self = players.find(({player}) => player.id === playerId); + const {index: selfIndex , player: selfPlayer} = self || {player: {handCard: []}}; + // console.log(otherPlayers, self) + + const playerRows = otherPlayers.reduce((arr, player, idx) => { const row = parseInt(idx / 2); let rowArr = arr[row] || []; - rowArr[idx % 2] = { index: idx, player }; + rowArr[idx % 2] = player; arr[row] = rowArr; return arr; }, []); return ( <View className="tab"> - <Title title="GAME" /> + <Title title={`GAME #${tableNo}`} /> { playerRows.map((row, i) => ( <View key={i} className="playerRow"> { - row.map((player, j) => ( - <View key={j} className="player-wrap"> + row.map(({index, player}, j) => ( + <View key={j} className="player-wrap" onClick={(p => {console.log(p)}).bind(this, player)}> { j % 2 === 0 && <View className={cnames('player-state-wrap', {'state-left': j % 2 === 0})}> @@ -100,20 +166,29 @@ class Table extends Component { </View> } <View className="player-bar"> - <View className={cnames("player-no", { "player-no-running": player.index === 0 })}><Text>#{player.index}</Text></View> - <View className="card1"><Card w={38} h={48} dot={-1} suit={2} /></View> - <View className="card2"><Card w={38} h={48} dot={-1} suit={3} /></View> - <View className={cnames("player", {'player-wait': player.index !== 0})}> - <View className="avatar-wrap"><Image className="avatar" src={avatar} /></View> - <View className="player-name"><Text>Player{player.item}</Text></View> + <View className={cnames("player-no", { "player-no-running": player.index === 0 })}><Text>#{index}</Text></View> + { + stage === 1 && selfPlayer.master && player.id + ? <View className="kickout" onClick={this.handleKickoutPlayer.bind(this, player)}><Image className="kickout-icon" src={closeIcon} /></View> + : null + } + {existsCard(player.handCard[0]) && <View className="card1"><Card w={38} h={48} dot={cardDot(player.handCard[0], player.status === 7 ? -2 : -1)} suit={cardSuit(player.handCard[0])} /></View>} + {existsCard(player.handCard[1]) && <View className="card2"><Card w={38} h={48} dot={cardDot(player.handCard[0], player.status === 7 ? -2 : -1)} suit={cardSuit(player.handCard[1])} /></View>} + + <View className={cnames("player", {'player-wait': !player.id || isIn(player.status, 3, 7)})}> + <View className="avatar-wrap">{!player.id ? null : <Image className="avatar" src={player.avatar || avatar} />}</View> + <View className="player-name"><Text>{player.username}</Text></View> </View> - <View className="wallet-wrap"> - <View className={cnames("wallet", {'wallet-wait': player.index !== 0})}> - <View className="wallet-icon"><Image className="wallet-icon-img" src={coinIcon} /></View> - <View className="wallet-amount"><Text>3,000,000</Text></View> + { + !player.id ? null : + <View className="wallet-wrap"> + <View className={cnames("wallet", {'wallet-wait': isIn(player.status, 3, 7)})}> + <View className="wallet-icon"><Image className="wallet-icon-img" src={coinIcon} /></View> + <View className="wallet-amount"><Text>{player.chip}</Text></View> + </View> + <View className="pct"><Text>0%</Text></View> </View> - <View className="pct"><Text>25%</Text></View> - </View> + } </View> </View> )) @@ -135,7 +210,7 @@ class Table extends Component { <Image className="d-wallet-icon-img" src={coinIcon} /> <Text className="d-wallet-icon-text">POT</Text> </View> - <View className="d-wallet-amount">22,088,000</View> + <View className="d-wallet-amount">{chip}</View> </View> </View> {/* <View className="chatbox" style={{ display: 'none' }}> @@ -152,12 +227,12 @@ class Table extends Component { <View className="self-box"> <View className="self-wrap"> - <View className={cnames("player-no", { "player-no-running": true })}><Text>#7</Text></View> - <View className="hand-card1"><Card w={38} h={48} dot={12} suit={3} /></View> - <View className="hand-card2"><Card w={38} h={48} dot={12} suit={3} /></View> + <View className={cnames("player-no", { "player-no-running": true })}><Text>#{selfIndex}</Text></View> + {existsCard(selfPlayer.handCard[0]) && <View className="hand-card1"><Card w={38} h={48} dot={cardDot(selfPlayer.handCard[0], -1)} suit={cardSuit(selfPlayer.handCard[0])} /></View>} + {existsCard(selfPlayer.handCard[1]) && <View className="hand-card2"><Card w={38} h={48} dot={cardDot(selfPlayer.handCard[1], -1)} suit={cardSuit(selfPlayer.handCard[0])} /></View>} <View className="hand-five"> { - [1, 2, 3, 4, 5].map((_, i) => ( + existsCard(selfPlayer.handCard[0]) && [1, 2, 3, 4, 5].map((_, i) => ( <View key={i} className="hand-five-item"><Card w={28} h={36} dot={12} suit={2} /></View> )) } @@ -165,13 +240,13 @@ class Table extends Component { <View className='u-opt' style={{ display: 'flex' }}> <View> <View className="s-info-wrap"> - <View className="s-avatar"><Image className="s-avatar-img" src={avatar} /></View> - <View className="s-username"><Text>Prometheus</Text></View> + <View className="s-avatar"><Image className="s-avatar-img" src={selfPlayer.avatar || avatar} /></View> + <View className="s-username"><Text>{selfPlayer.username}</Text></View> </View> <View className="s-wallet-wrap"> <View className="s-w-amount-wrap"> <View className="s-w-amount-icon"><Image className="s-w-amount-icon-img" src={coin2Icon} /></View> - <View className="s-w-amount"><Text>3,000,000</Text></View> + <View className="s-w-amount"><Text>{selfPlayer.chip}</Text></View> </View> <View className="s-w-pct"><Text>0%</Text></View> </View> @@ -182,11 +257,25 @@ class Table extends Component { <View className="amount-sub"><Image className="amount-sub-icon" src={sub1Icon} /></View> <View className="amount-plus"><Image className="amount-plus-icon" src={plus1Icon} /></View> </View> - <View className="ctl-opt-wrap" style={{ marginTop: Taro.pxTransform(10) }}> - <View className="ctl-opt"><Button className="ctl-opt-btn-1">让牌</Button></View> - <View className="ctl-opt"><Button className="ctl-opt-btn-2">弃牌</Button></View> - <View className="ctl-opt"><Button className="ctl-opt-btn-3">跟注</Button></View> - </View> + { + stage === 1 ? ( + // 是房主: 关闭牌桌, 开始游戏 + selfPlayer.master ? <View className="ctl-opt-wrap"> + <View className="ctl-opt"><Button className={cnames("ctl-opt-btn-4", {'ctl-opt-btn-disable': selfPlayer.status === 2})}>关闭牌桌</Button></View> + <View className="ctl-opt"><Button className={cnames("ctl-opt-btn-6", {'ctl-opt-btn-disable': selfPlayer.status === 2})}>开始游戏</Button></View> + </View> + // 是玩家:退出, 准备 + : <View className="ctl-opt-wrap"> + <View className="ctl-opt"><Button style={{display: selfPlayer.status === 2 ? 'none' : 'block'}} className={cnames("ctl-opt-btn-4", {'ctl-opt-btn-disable': selfPlayer.status === 2})}>退出</Button></View> + <View className="ctl-opt"><Button className={cnames("ctl-opt-btn-5", {'ctl-opt-btn-5-ready': selfPlayer.status === 2})}>{selfPlayer.status === 2 ? '已准备' : '准备'}</Button></View> + </View> + ) + : <View className="ctl-opt-wrap"> + <View className="ctl-opt"><Button className="ctl-opt-btn-1">让牌</Button></View> + <View className="ctl-opt"><Button className="ctl-opt-btn-2">弃牌</Button></View> + <View className="ctl-opt"><Button className="ctl-opt-btn-3">跟注</Button></View> + </View> + } </View> </View> </View> diff --git a/src/pages/table/table.scss b/src/pages/table/table.scss index 8ac54a3..afd741e 100644 --- a/src/pages/table/table.scss +++ b/src/pages/table/table.scss @@ -52,6 +52,18 @@ background-color: #EE914E; } + .kickout { + position: absolute; + z-index: 99; + width: 24px; + height: 24px; + border-radius: 50%; + } + .kickout-icon { + top: 5px; + left: 125px; + } + .card1, .card2 { position: absolute; @@ -85,6 +97,10 @@ .avatar-wrap { margin: 10px 0 10px 4px; + height: 50px; + width: 50px; + border-radius: 5px; + background-color: #D8D8D8; } .avatar { @@ -352,6 +368,7 @@ display: flex; width: 170px; justify-content: space-between; + margin-top: 10px; } .amount-input { @@ -390,7 +407,10 @@ .ctl-opt-btn-1, .ctl-opt-btn-2, - .ctl-opt-btn-3 { + .ctl-opt-btn-3, + .ctl-opt-btn-4, + .ctl-opt-btn-5, + .ctl-opt-btn-6 { width: 53.25px; height: 24px; border-radius: 8px; @@ -415,4 +435,30 @@ background-color: #FF4400; color: #F1D09F; } + + .ctl-opt-btn-4, + .ctl-opt-btn-5, + .ctl-opt-btn-6 { + width: 79.2px; + } + .ctl-opt-btn-4 { + background-color: #F1D09F; + color: #D05959; + } + .ctl-opt-btn-5 { + background-color: #48C25D; + color: #FFF; + } + .ctl-opt-btn-5-ready { + background-color: #6AAD76; + color: #FFF; + } + .ctl-opt-btn-6 { + background-color: #FF4400; + color: #F1D09F; + } + .ctl-opt-btn-disable { + background-color: #A8A29D; + color: #FFF; + } } diff --git a/src/utils/collect.js b/src/utils/collect.js new file mode 100644 index 0000000..5f6c93e --- /dev/null +++ b/src/utils/collect.js @@ -0,0 +1,9 @@ + +export const isIn = (value, ...values) => { + for (let i = 0; i < values.length; i++) { + if (values[i] === value) { + return true; + } + } + return false; +}