zhidao 发表于 2025-2-6 14:42:08

心得之开发设计一个篮球共享计分器小程序

序言
  作为一名篮球爱好者的程序员,在使用目前市面篮球计分器时,总觉得用的不顺手,市面基本都是单机模式,广告很多,也不便于核对和多人协同记录。因此,我决定自己设计并开发一款篮球共享计分器小程序,实现多人协同记录赛事,提供多种方便快捷的计分方式和统计数据效果。
  上效果图:
      
  
  由于上篇《开发一个题库系统App和小程序的心得》已经叙述了很多资源方案,代码方案,部署方案,以及很多基础性的功能开发设计,此系统基本相同,不再赘述,直接讲解篮球共享计分器特有功能。
 
1.    功能目标

  篮球共享计分器功能目标:

[*]实现多人实时共享记录比赛
[*]提供个人和团队数据和表现的统计数据
[*]提供单机版/共享版/私密版三种模式记录比赛
[*]提供简易/精准版模式选择
[*]保存历史比赛数据,可用于文字直播。
 
1.1     功能列表

  篮球共享计分器核心功能

[*]实现常规简易单机版计分器
[*]实现协同协同版计分器,协同实时切换简易版和精准版计分方式
[*]实现团队对比数据,可视化图表展示和对比,以及完整文字记录
[*]实现个人完整数据,以及个人完整文字记录
[*]协同版区分公开和私密赛事,公开赛事可免登录参与,私密赛事支持多种精准权限控制记录比赛
 
1.2     功能截图

  以下是App端的部分截图
       
  以下是微信小程序端的部分截图
       
2.    实现方案

 
2.1     设计方案

 
2.1.1   数据库设计

  赛事表
   
 
  队员表
   
 
  授权表
   
 
  日志表
   
  
2.1.2   移动端开发-赛事列表和录入

        
           
  
  赛事有三种添加方式:

[*]已有比赛,可以选择复制一份,会复制赛事的队员和用户权限
[*]录入比赛,自行录入比赛名称等信息,保存即可
[*](需登录)添加授权码比赛,别人创建的私密比赛,分享的授权码,可以通过授权码进行添加
 
  赛事录入细节:

[*]长按赛事明细,可操作复制/编辑/授权/重置/取消/删除(可见操作按钮跟权限有关)
[*]编辑赛事,可增加/修改/删除赛事双方队员:添加队员直接录入即可;搜索队员可查找有权限赛事的所有人员,因此同名队伍只需录入一次,下次直接搜索一次性添加即可;长按队员可编辑;删除按钮可删除。(赛事录入页面也可同样操作)
  
2.1.3   移动端开发-赛事授权(需登录)

  赛事列表中,对于公开赛事,所有人都可见也都是管理员角色;对于私密赛事,长按明细,可选择授权操作。
       
       
  有两种授权模式:

[*]生成授权码,点击观众/主队管理员/客队管理员/管理员,可生成对应赛事授权码,其他人在赛事列表页面通过添加授权码比赛的方式即可获权
[*]添加用户,点击添加用户,输入用户信息查找授权即可(为保证用户隐私,此处不支持模糊搜索)
 
  授权细节说明:

[*]赛事创建人是超级管理员,可以直接添加/修改/删除任何已授权用户
[*]只能添加/修改/删除比自己低的权限,不会出现操作的权限超越操作人的权限的情况
[*]可自己删除自己的授权,删除后赛事不可见
 
2.1.4   移动端开发-记录比赛

       
  赛事记录方法:

[*]点击【精准版】/【简易版】可以互相切换记录模式
[*]点击【用时】,可查看实时比赛数据结果
[*]点击【分享比赛】,可生成赛事二维码,让其他人参与
[*]长按任意队员,可切换队员
[*]长按日志明细,可作废或修改日志
  赛事记录说明:

[*]公开赛事记录,所有人都是管理员权限,都可以通过分享的二维码进行赛事记录。
[*]私密赛事记录,扫码进来的默认赋权观众,需要进一步授权才可以记录比赛,否则只能观看。
[*]私密赛事记录,管理员可以记录双方数据,主队管理员只能记录主队数据,客队管理员只能记录客队数据,观众只能观看。
[*]加时赛说明,默认四节比赛,如果在第四节结束,两队分数一致,自动进入加时,否则比赛结束。

 
2.1.5   移动端开发-查看结果

       
       
  
  查看结果有三种方式:

[*]列表已经结束的比赛,点击查看即可查看比赛结果
[*]记录比赛中,点击用时可以查看比赛结果
[*]通过其他人分享的比赛结果二维码可以查看比赛结果
 
  查看结果操作说明:

[*]进入结果页面,可点击上方【*** 数据】切换页签,也可以左右滑动切换页签
[*]在【**队 数据】页签,柱状图的“其他”表示整队的数据,也就是比赛记录时,选择了队伍,未选择具体人员
[*]小程序图表可能出现空白,点一下图表内部,即可正常显示,图表组件在小程序上的层级Bug问题,暂时未修复
 
2.1.6   移动端开发-单机版计分器

    
 
  操作说明:

[*]点击【单机版】可清空重置数据
[*]长按【犯规】或【队伍暂停】可减少次数
[*]打开页面后可离线操作,离开页面或应用,数据缓存在本地,并不会消失,只能记录一个赛事,清空重置后,数据清除不可找回
 
2.2     开发方案

 
2.2.1   后端开发-框架Springboot

  基于若依plus,在其基础上增加ruoyi-race模块,单独存放赛事相关功能,其中四个controller分别是:
    
  Race:赛事功能
  Log:日志功能
  Member:成员功能
  User:授权用户功能
  Message:SSE发送消息功能(已弃用)
  SSE:SSE连续功能(已弃用)
  WxToken:获取小程序Token和生成小程序带参数二维码功能
  
  SSE在使用时,微信小程序和APP不能很好的支持EventSource前端组件,长连接也存在兼容问题,效果都不好,所以弃用,使用WebSocket
    
        在整合的框架中,使用的是Tio引擎的Websocket,增加两种赛事的消息类型,初始化消息和常规消息:
        初始化消息:读取并回执当前某一赛事最新完整赛事数据
        常规消息:发送记录赛事时的各种指令,更新赛事,记录日志,并回执更新后的完整赛事数据

 
2.2.2   移动端开发-框架Uniapp

核心在记录赛事使用websocket上,这里我们使用具体页面直连的方式,离开页面则关闭websocket,相关代码如下

    onUnload() {      this.timeClear()      this.socket.close(true)    },    onShow() {      this.diyApiGet('/race/race/getMoreByRaceId/' + this.setData.id).then(res => {            this.setData = res.data;            if(!this.userInfo || !this.userInfo.userId){                // 公开赛事,无用户,随机生成一个                this.userId = this.getRandom(2, 16);            }else{                this.userId = this.userInfo.userId;            }            // websocket非通讯状态,则重连            // #ifdef APP-PLUS            if(!this.socket){                this.getMemberFirstList();                this.getMemberSecondList();                this.connectWebsocket(this.userId, this.setData.id);            }            // #endif            // #ifndef APP-PLUS            if(!this.socket || this.socket.getReadyState() != 1){                this.getMemberFirstList();                this.getMemberSecondList();                this.connectWebsocket(this.userId, this.setData.id);            }            // #endif      })    },


连接websocket方法,未登录传入随机用户ID

    connectWebsocket(userId, raceId){            let url = globalData.wssUrl;            let heartbeatTimeout = 50000; // 心跳超时时间,单位:毫秒            let reconnInterval = 5000; // 重连间隔时间,单位:毫秒            let binaryType = 'blob'; // 'blob' or 'arraybuffer';//arraybuffer是字节            let paramStr = "app=urace&userId=" + userId + "&sessionId=" + raceId            let param = "";            this.socket = new TioSocket(url, paramStr, param, heartbeatTimeout, reconnInterval, binaryType);            let ws = this.socket.connect(false);                        ws.onOpen((e) => {                // 读取赛事初始信息                uni.sendSocketMessage({                  data: JSON.stringify({code:6, message: {raceId: raceId, userId: userId}})                })            this.socket.reset();            })            ws.onMessage((e) => {                let message = JSONbig.parse(e.data);                if(message.code == 2){                  let data = JSONbig.parse(message.message.content)                  if(data.msg){                        this.msg(data.msg);                        return;                  }                  if(data.data){                        this.setData = data.data;                        this.usedTime = data.data.usedTime || 0;                        if(data.data.status == 1){                            this.timeInterval();                        }else{                            this.timeClear();                        }                        if(data.data.status < 0){                            this.logList = []                        }                        this.showTitle();                  }                  if(data.log){                        if(data.log.logType == 99){                            this.getMemberFirstList();                            this.getMemberSecondList();                            this.invalidMessageLog(data.log)                        }else if(data.log.logType == 61){                            this.moveMessageLog(data.log)                        }else{                            if(data.log.logType == 34 || data.log.logType == 41 || data.log.logType == 42 || data.log.logType == 43                                 || data.log.logType == 81 || data.log.logType == 82 || data.log.logType == 83){                              if(data.log.teamIndex == 1){                                    this.getMemberFirstList();                              }                              if(data.log.teamIndex == 2){                                    this.getMemberSecondList();                              }                            }                            this.addMessageLog(data.log, true)                        }                  }                }                this.socket.reset();            })      },


赛事记录命令转译函数

export function getLogText(log, firstTeamName, secondTeamName, status){    let text = "";    if(log.teamIndex == 1){      text += " " + firstTeamName    }    if(log.teamIndex == 2){      text += " " + secondTeamName    }    if(log.memberName){      text += " " + log.memberName    }    switch(log.logType){      case -10:            text += "比赛尚未开始";            break;      case -1:            text += "比赛进行中";            break;      case -2:            text += "比赛暂停中";            break;      case -3:            if(log.period == 2){                text += "第一节已结束";            }else if(log.period == 3){                text += "第二节已结束";            }else if(log.period == 4){                text += "第三节已结束";            }else if(log.period >= 5 && log.firstTeamScore != log.secondTeamScore){                text += "比赛已结束";            }else if(log.period == 5){                text += "第四节已结束";            }else if(log.period == 6){                text += "加时赛1已结束";            }else if(log.period == 7){                text += "加时赛2已结束";            }else if(log.period == 8){                text += "加时赛3已结束";            }else if(log.period == 9){                text += "加时赛4已结束";            }else if(log.period == 10){                text += "加时赛5已结束";            }else{                text += "加时赛已结束";            }            break;      case -9:            text += "比赛已取消";            break;      case 1:            text += "比赛开始";            break;      case 2:            text += "裁判暂停";            break;      case 3:            if(log.period == 1){                text += "第一节比赛继续";            }else if(log.period == 2){                text += "第二节比赛继续";            }else if(log.period == 3){                text += "第三节比赛继续";            }else if(log.period == 4){                text += "第四节比赛继续";            }else if(log.period == 5){                text += "加时赛1继续";            }else if(log.period == 6){                text += "加时赛2继续";            }else if(log.period == 7){                text += "加时赛3继续";            }else if(log.period == 8){                text += "加时赛4继续";            }else if(log.period == 9){                text += "加时赛5继续";            }else if(log.period == 10){                text += "加时赛6继续";            }else{                text += "加时赛继续";            }            break;      case 4:            if(log.period == 2){                text += "第一节结束";            }else if(log.period == 3){                text += "第二节结束";            }else if(log.period == 4){                text += "第三节结束";            }else if(log.period >= 5 && log.firstTeamScore == log.secondTeamScore){                text += "比赛结束";            }else if(log.period == 5){                text += "第四节结束";            }else if(log.period == 6){                text += "加时赛1结束";            }else if(log.period == 7){                text += "加时赛2结束";            }else if(log.period == 8){                text += "加时赛3结束";            }else if(log.period == 9){                text += "加时赛4结束";            }else if(log.period == 10){                text += "加时赛5结束";            }else{                text += "加时赛结束";            }            break;      case 5:            text += "比赛取消";            break;      case 6:            text += " 请求暂停";            break;      case 11:            text += " 罚篮命中,得1分"            break;      case 12:            text += " 投篮命中,得2分"            break;      case 13:            text += " 投篮命中,得2分,加罚1次"            break;      case 14:            text += " 突破上篮,得2分"            break;      case 15:            text += " 突破上篮,得2分,加罚1次"            break;      case 16:            text += " 投篮命中,得3分"            break;      case 17:            text += " 投篮命中,得3分,加罚1次"            break;      case 18:            text += " 扣篮成功,得2分"            break;      case 19:            text += " 扣篮成功,得2分,加罚1次"            break;      case 20:            text += " 扣1分"            break;      case 21:            text += " 罚篮不中"            break;      case 22:            text += " 2分不中"            break;      case 23:            text += " 2分不中,被犯规,罚球2次"            break;      case 24:            text += " 3分不中"            break;      case 25:            text += " 3分不中,被犯规,罚球3次"            break;      case 26:            text += " 上篮失败"            break;      case 27:            text += " 上篮失败,被犯规,罚球2次"            break;      case 28:            text += " 扣篮失败"            break;      case 29:            text += " 扣篮失败,被犯规,罚球2次"            break;      case 31:            text += " 助攻一次"            break;      case 32:            text += " 失误,丢失球权"            break;      case 33:            text += " 抢断,获得球权"            break;      case 34:            text += " 普通犯规"            break;      case 35:            text += " 被普通犯规"            break;      case 36:            text += " 投篮被犯规,开始罚球"            break;      case 37:            text += " 盖帽"            break;      case 38:            text += " 被盖帽"            break;      case 39:            text += " 获得前场篮板"            break;      case 40:            text += " 获得后场篮板"            break;      case 41:            text += " 犯规,对手罚球"            break;      case 42:            text += " 违体犯规"            break;      case 43:            text += " 技术犯规"            break;      case 81:            text += " 上场"            break;      case 82:            text += " 下场," + log.nextMemberName + " 上场"            break;      case 83:            text += " 下场休息"            break;      default:            text += log.logType    }    return " " + text}

 
3.    总结一下

  篮球共享计分器,主要实现的是一个协同处理能力,此次小程序开发,不仅实现了App、小程序、H5的三端兼容,也实现了赛事数据的实时同步,经过测试,任意时间进入赛事的用户,都能保证比赛用时显示的同步,互相操作都能及时收到消息。
 
  不足之处,图表使用的uchart工具,兼容性还有待研究,篮球规则懂的不全面,以及更多形式的赛事记录支持等。
 
  以上分享心得只能描述个大概,个人文档和开发水平都有限,文档或有错误和不妥之处,欢迎指定!
页: [1]
查看完整版本: 心得之开发设计一个篮球共享计分器小程序