LiveBoard.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <template>
  2. <div class="live-board-wrapper" :class="{ selected }" :style="{
  3. width: width + 'px',
  4. height: height + 'px',
  5. position: 'relative',
  6. display: 'flex',
  7. alignItems: 'center',
  8. justifyContent: 'center',
  9. background: '#f8fafc',
  10. boxSizing: 'border-box',
  11. border: selected ? '2px solid #409eff' : '1px solid #ddd',
  12. borderRadius: props.borderRadius + 'px'
  13. }">
  14. <div class="live-board-content">
  15. <div class="live-icon">
  16. <svg width="32" height="32" viewBox="0 0 32 32" fill="none">
  17. <rect x="3" y="3" width="26" height="26" rx="6" fill="#f3f6fa" stroke="#e67e22" stroke-width="2" />
  18. <path d="M12 11v10l8-5-8-5z" fill="#e67e22" />
  19. </svg>
  20. </div>
  21. <div class="live-info" v-if="liveUrl || selected">
  22. <div class="live-url" :title="liveUrl"><span class="label">直播地址:</span>{{ liveUrl || '未设置' }}</div>
  23. <div class="live-audio"><span class="label">音频:</span>{{ playAudio ? '开' : '关' }}</div>
  24. </div>
  25. </div>
  26. <template v-if="selected">
  27. <div v-for="dir in ['nw', 'ne', 'sw', 'se']" :key="dir" class="resize-handle" :class="'resize-' + dir"
  28. @mousedown.stop="onResizeMouseDown(dir, $event)" />
  29. </template>
  30. </div>
  31. </template>
  32. <script setup lang="ts">
  33. import { computed, withDefaults, defineEmits } from 'vue';
  34. const emit = defineEmits(['resize']);
  35. interface Props {
  36. width?: number;
  37. height?: number;
  38. liveUrl?: string;
  39. playAudio?: boolean;
  40. selected?: boolean;
  41. borderRadius?: number;
  42. }
  43. const props = withDefaults(defineProps<Props>(), {
  44. width: 200,
  45. height: 120,
  46. liveUrl: '',
  47. playAudio: true,
  48. selected: false,
  49. borderRadius: 0
  50. });
  51. const selected = computed(() => !!props.selected);
  52. const width = computed(() => props.width);
  53. const height = computed(() => props.height);
  54. const liveUrl = computed(() => props.liveUrl);
  55. const playAudio = computed(() => props.playAudio);
  56. let startX = 0,
  57. startY = 0,
  58. startW = 0,
  59. startH = 0;
  60. function onResizeMouseDown(dir: string, e: MouseEvent) {
  61. e.stopPropagation();
  62. startX = e.clientX;
  63. startY = e.clientY;
  64. startW = props.width;
  65. startH = props.height;
  66. function onMouseMove(ev: MouseEvent) {
  67. let dx = ev.clientX - startX;
  68. let dy = ev.clientY - startY;
  69. let newW = startW;
  70. let newH = startH;
  71. if (dir.includes('e')) newW = Math.max(40, startW + dx);
  72. if (dir.includes('s')) newH = Math.max(40, startH + dy);
  73. if (dir.includes('w')) newW = Math.max(40, startW - dx);
  74. if (dir.includes('n')) newH = Math.max(40, startH - dy);
  75. emit('resize', { width: newW, height: newH });
  76. }
  77. function onMouseUp() {
  78. window.removeEventListener('mousemove', onMouseMove);
  79. window.removeEventListener('mouseup', onMouseUp);
  80. }
  81. window.addEventListener('mousemove', onMouseMove);
  82. window.addEventListener('mouseup', onMouseUp);
  83. }
  84. function select() {
  85. // 交由父组件处理选中
  86. }
  87. </script>
  88. <style scoped>
  89. .live-board-wrapper {
  90. user-select: none;
  91. -webkit-user-select: none;
  92. -moz-user-select: none;
  93. -ms-user-select: none;
  94. background: #f8fafc;
  95. transition: all 0.2s;
  96. position: relative;
  97. box-sizing: border-box;
  98. display: flex;
  99. align-items: center;
  100. justify-content: center;
  101. }
  102. .live-board-content {
  103. display: flex;
  104. flex-direction: column;
  105. align-items: center;
  106. justify-content: center;
  107. gap: 10px;
  108. }
  109. .live-icon {
  110. flex-shrink: 0;
  111. display: flex;
  112. align-items: center;
  113. justify-content: center;
  114. }
  115. .live-info {
  116. display: flex;
  117. flex-direction: column;
  118. align-items: flex-start;
  119. justify-content: center;
  120. gap: 2px;
  121. font-size: 15px;
  122. color: #222;
  123. }
  124. .live-url,
  125. .live-audio {
  126. white-space: nowrap;
  127. overflow: hidden;
  128. text-overflow: ellipsis;
  129. max-width: 180px;
  130. }
  131. .label {
  132. color: #e67e22;
  133. font-weight: bold;
  134. margin-right: 4px;
  135. }
  136. .live-board-wrapper.selected {
  137. border-color: #409eff;
  138. }
  139. .live-icon {
  140. margin-right: 10px;
  141. }
  142. .live-info {
  143. font-size: 13px;
  144. color: #e67e22;
  145. margin-top: 4px;
  146. text-align: center;
  147. }
  148. .live-url {
  149. font-size: 13px;
  150. color: #e67e22;
  151. white-space: nowrap;
  152. overflow: hidden;
  153. text-overflow: ellipsis;
  154. max-width: 120px;
  155. }
  156. .live-audio {
  157. font-size: 12px;
  158. color: #888;
  159. margin-top: 4px;
  160. }
  161. .resize-handle {
  162. width: 8px;
  163. height: 8px;
  164. background: #fff;
  165. border: 1.5px solid #e67e22;
  166. border-radius: 50%;
  167. position: absolute;
  168. z-index: 2;
  169. }
  170. .resize-nw {
  171. top: -4px;
  172. left: -4px;
  173. cursor: nw-resize;
  174. }
  175. .resize-ne {
  176. top: -4px;
  177. right: -4px;
  178. cursor: ne-resize;
  179. }
  180. .resize-sw {
  181. bottom: -4px;
  182. left: -4px;
  183. cursor: sw-resize;
  184. }
  185. .resize-se {
  186. bottom: -4px;
  187. right: -4px;
  188. cursor: se-resize;
  189. }
  190. .live-icon {
  191. margin-right: 10px;
  192. }
  193. .live-info {
  194. flex: 1;
  195. min-width: 0;
  196. display: flex;
  197. flex-direction: column;
  198. align-items: flex-start;
  199. justify-content: center;
  200. }
  201. .live-url {
  202. font-size: 13px;
  203. color: #e67e22;
  204. white-space: nowrap;
  205. overflow: hidden;
  206. text-overflow: ellipsis;
  207. max-width: 120px;
  208. }
  209. .live-audio {
  210. font-size: 12px;
  211. color: #888;
  212. margin-top: 4px;
  213. }
  214. .resize-handle {
  215. width: 8px;
  216. height: 8px;
  217. background: #fff;
  218. border: 1.5px solid #e67e22;
  219. border-radius: 50%;
  220. position: absolute;
  221. z-index: 2;
  222. }
  223. .resize-nw {
  224. top: -4px;
  225. left: -4px;
  226. cursor: nw-resize;
  227. }
  228. .resize-ne {
  229. top: -4px;
  230. right: -4px;
  231. cursor: ne-resize;
  232. }
  233. .resize-sw {
  234. bottom: -4px;
  235. left: -4px;
  236. cursor: sw-resize;
  237. }
  238. .resize-se {
  239. bottom: -4px;
  240. right: -4px;
  241. cursor: se-resize;
  242. }
  243. </style>