viewer.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <template>
  2. <u-popup
  3. :show="show"
  4. @close="handleClose"
  5. @open="handleOpen"
  6. round="20rpx"
  7. bgColor="#ffffff"
  8. zIndex="10077"
  9. >
  10. <view class="viewer-list-popup">
  11. <view class="popup-header fs32">在线观众</view>
  12. <scroll-view
  13. v-if="Array.isArray(viewers)"
  14. scroll-y
  15. class="scroll-content"
  16. :style="{ height: scrollHeight + 'px' }"
  17. @scrolltolower="handleScrollToLower"
  18. >
  19. <view
  20. class="viewer-item x-f mb20 mt20"
  21. v-for="(item, index) in viewers"
  22. :key="getViewerKey(item, index)"
  23. >
  24. <!-- <view
  25. class="rank-number"
  26. :style="getRankStyle(index)"
  27. >
  28. {{ index + 1 }}
  29. </view> -->
  30. <view
  31. class="rank-number"
  32. :style="{
  33. color: index < 3 ? ['#FF3B30', '#FF9500', '#FFCC00'][index] : '#8E8E93',
  34. fontWeight: index < 3 ? 'bold' : 'normal',
  35. width: '50rpx'
  36. }"
  37. >
  38. {{ index + 1 }}
  39. </view>
  40. <!-- 用户头像 -->
  41. <view class="avatar-container">
  42. <u-avatar
  43. v-if="item.avatar"
  44. :src="item.avatar"
  45. :size="36"
  46. ></u-avatar>
  47. <view
  48. v-else
  49. class="default-avatar"
  50. :style="{ backgroundColor: getUserRandomColor(item.userId) }"
  51. >
  52. <text class="avatar-text">{{ getNicknameInitial(item.nickName) }}</text>
  53. </view>
  54. </view>
  55. <text class="nickname ml16 f30">{{ item.nickName || '未命名' }}</text>
  56. </view>
  57. <!-- 加载状态 -->
  58. <view v-if="loading" class="loading-text">
  59. <u-loading-icon size="16"></u-loading-icon>
  60. 加载中...
  61. </view>
  62. <!-- 无数据提示 -->
  63. <view v-if="!loading && viewers.length === 0" class="empty-text">
  64. 暂无在线观众
  65. </view>
  66. </scroll-view>
  67. </view>
  68. </u-popup>
  69. </template>
  70. <script>
  71. export default {
  72. // name: 'Viewer',
  73. props: {
  74. // 是否显示弹窗
  75. show: {
  76. type: Boolean,
  77. default: false
  78. },
  79. // 观众列表数据
  80. viewers: {
  81. type: Array,
  82. default: () => []
  83. },
  84. // 是否正在加载
  85. loading: {
  86. type: Boolean,
  87. default: false
  88. },
  89. // 滚动区域高度
  90. scrollHeight: {
  91. type: Number,
  92. default: 400
  93. }
  94. },
  95. data() {
  96. return {
  97. // 本地状态可以根据需要添加
  98. };
  99. },
  100. methods: {
  101. /**
  102. * 处理关闭事件
  103. */
  104. handleClose() {
  105. this.$emit('close');
  106. },
  107. /**
  108. * 处理打开事件
  109. */
  110. handleOpen() {
  111. this.$emit('open');
  112. },
  113. /**
  114. * 处理滚动到底部事件
  115. */
  116. handleScrollToLower() {
  117. this.$emit('scrolltolower');
  118. },
  119. /**
  120. * 生成观众项的唯一key
  121. */
  122. getViewerKey(item, index) {
  123. return item.userId ? `viewer_${item.userId}` : `viewer_${index}`;
  124. },
  125. /**
  126. * 获取排名样式
  127. */
  128. getRankStyle(index) {
  129. const rankColors = {
  130. 0: '#FF3B30', // 第一名
  131. 1: '#FF9500', // 第二名
  132. 2: '#FFCC00' // 第三名
  133. };
  134. return {
  135. color: rankColors[index] || '#8E8E93',
  136. fontWeight: index < 3 ? 'bold' : 'normal',
  137. width: '50rpx'
  138. };
  139. },
  140. /**
  141. * 获取用户随机颜色(从父组件传入或本地实现)
  142. */
  143. getUserRandomColor(userId) {
  144. // 如果父组件传入了方法,可以使用父组件的方法
  145. // 否则这里实现一个简单的版本
  146. if (this.$parent && this.$parent.getUserRandomColor) {
  147. return this.$parent.getUserRandomColor(userId);
  148. }
  149. // 简单的本地实现
  150. if (!userId) return '#8978e2';
  151. const colorPool = [
  152. '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
  153. '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9'
  154. ];
  155. let seed = 0;
  156. for (let i = 0; i < userId.length; i++) {
  157. seed = (seed * 31 + userId.charCodeAt(i)) % 1000000;
  158. }
  159. return colorPool[seed % colorPool.length];
  160. },
  161. /**
  162. * 获取昵称首字母
  163. */
  164. getNicknameInitial(nickName) {
  165. if (!nickName || typeof nickName !== 'string') return '未';
  166. if (/^[\u4e00-\u9fa5]/.test(nickName[0])) {
  167. return nickName[0];
  168. }
  169. return nickName[0].toUpperCase();
  170. }
  171. },
  172. watch: {
  173. // 监听显示状态变化
  174. show(newVal) {
  175. if (newVal) {
  176. this.$emit('open');
  177. }
  178. }
  179. }
  180. };
  181. </script>
  182. <style scoped lang="scss">
  183. .viewer-list-popup {
  184. position: relative;
  185. height: 60vh;
  186. padding: 40rpx 0rpx;
  187. box-sizing: border-box;
  188. display: flex;
  189. flex-direction: column;
  190. .popup-header {
  191. text-align: center;
  192. font-weight: 600;
  193. margin-bottom: 20rpx;
  194. }
  195. .scroll-content {
  196. flex: 1;
  197. overflow-y: auto;
  198. padding: 0 40rpx;
  199. }
  200. .viewer-item {
  201. align-items: center;
  202. padding: 16rpx 0;
  203. .rank-number {
  204. text-align: center;
  205. margin-right: 20rpx;
  206. font-size: 28rpx;
  207. }
  208. .avatar-container {
  209. .default-avatar {
  210. width: 72rpx;
  211. height: 72rpx;
  212. border-radius: 50%;
  213. display: flex;
  214. align-items: center;
  215. justify-content: center;
  216. .avatar-text {
  217. color: #ffffff;
  218. font-size: 24rpx;
  219. font-weight: 500;
  220. }
  221. }
  222. }
  223. .nickname {
  224. flex: 1;
  225. overflow: hidden;
  226. text-overflow: ellipsis;
  227. white-space: nowrap;
  228. }
  229. }
  230. .loading-text {
  231. text-align: center;
  232. color: #999;
  233. font-size: 28rpx;
  234. padding: 40rpx;
  235. display: flex;
  236. align-items: center;
  237. justify-content: center;
  238. gap: 16rpx;
  239. }
  240. .empty-text {
  241. text-align: center;
  242. color: #999;
  243. font-size: 28rpx;
  244. padding: 80rpx 40rpx;
  245. }
  246. }
  247. </style>