index.vue 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  1. <template>
  2. <div class="app-container">
  3. <el-row :gutter="10" class="mb8">
  4. <el-col :span="1.5">
  5. <el-button
  6. type="primary"
  7. plain
  8. icon="el-icon-plus"
  9. size="mini"
  10. @click="handleAdd"
  11. v-hasPermi="['live:live:add']"
  12. >新增</el-button>
  13. </el-col>
  14. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  15. </el-row>
  16. <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
  17. <el-form-item label="直播名称" prop="liveName">
  18. <el-input
  19. v-model="queryParams.liveName"
  20. placeholder="请输入直播名称"
  21. clearable
  22. size="small"
  23. @keyup.enter.native="handleQuery"
  24. />
  25. </el-form-item>
  26. <el-form-item label="直播状态" prop="liveStatus">
  27. <el-select
  28. v-model="queryParams.status"
  29. placeholder="请选择直播状态"
  30. clearable
  31. size="small"
  32. >
  33. <el-option label="待直播" value="1"></el-option>
  34. <el-option label="直播中" value="2"></el-option>
  35. <el-option label="已结束" value="3"></el-option>
  36. <el-option label="直播回放中" value="4"></el-option>
  37. </el-select>
  38. </el-form-item>
  39. <el-form-item label="公司名称" prop="companyName">
  40. <el-input
  41. v-model="queryParams.companyName"
  42. placeholder="请输入公司名称"
  43. clearable
  44. size="small"
  45. />
  46. </el-form-item>
  47. <el-form-item label="直播类型" prop="liveType">
  48. <el-select
  49. v-model="queryParams.liveType"
  50. placeholder="请选择直播类型"
  51. clearable
  52. size="small"
  53. >
  54. <el-option label="直播" value="1"></el-option>
  55. <el-option label="录播" value="2"></el-option>
  56. <el-option label="直播回放" value="3"></el-option>
  57. </el-select>
  58. </el-form-item>
  59. <el-form-item label="开始时间" prop="startTimeS">
  60. <el-date-picker
  61. v-model="queryParams.startTimeS"
  62. type="datetime"
  63. placeholder="选择起始时间"
  64. value-format="yyyy-MM-dd HH:mm:ss"
  65. size="small"
  66. ></el-date-picker>
  67. </el-form-item>
  68. <!-- 开始时间-范围结束 -->
  69. <el-form-item label="结束时间" prop="endTimeE">
  70. <el-date-picker
  71. v-model="queryParams.endTimeE"
  72. type="datetime"
  73. placeholder="选择结束时间"
  74. value-format="yyyy-MM-dd HH:mm:ss"
  75. size="small"
  76. :disabled="!queryParams.startTimeS"
  77. ></el-date-picker>
  78. </el-form-item>
  79. <el-form-item label="上下架" prop="isShow">
  80. <el-select
  81. v-model="queryParams.isShow"
  82. placeholder="请选择上下架状态"
  83. clearable
  84. size="small"
  85. >
  86. <el-option label="上架" value="1"></el-option>
  87. <el-option label="下架" value="2"></el-option>
  88. </el-select>
  89. </el-form-item>
  90. <!-- 审核状态 -->
  91. <el-form-item label="审核状态" prop="isAudit">
  92. <el-select
  93. v-model="queryParams.isAudit"
  94. placeholder="请选择审核状态"
  95. clearable
  96. size="small"
  97. >
  98. <el-option label="审核未通过" value="0"></el-option>
  99. <el-option label="审核通过" value="1"></el-option>
  100. </el-select>
  101. </el-form-item>
  102. <el-form-item>
  103. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  104. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  105. <el-button icon="el-icon-download" size="mini" v-hasPermi="['live:live:export']" @click="handleExport">导出</el-button>
  106. </el-form-item>
  107. </el-form>
  108. <div class="selection-toolbar">
  109. <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
  110. {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
  111. </el-checkbox>
  112. <!-- <el-button plain size="mini" @click="handleShelf" v-hasPermi="['live:live:operation']">上架</el-button>-->
  113. <!-- <el-button plain size="mini" @click="handleUnshelf" v-hasPermi="['live:live:operation']">下架</el-button>-->
  114. <!-- <el-button plain size="mini" @click="handleDeleteSelected" v-hasPermi="['live:live:operation']">删除</el-button>-->
  115. <!-- <el-dropdown>-->
  116. <!-- <el-button plain size="mini">-->
  117. <!-- 更多操作<i class="el-icon-arrow-down el-icon&#45;&#45;right"></i>-->
  118. <!-- </el-button>-->
  119. <!-- <el-dropdown-menu slot="dropdown">-->
  120. <!-- <el-dropdown-item>操作一</el-dropdown-item>-->
  121. <!-- <el-dropdown-item>操作二</el-dropdown-item>-->
  122. <!-- </el-dropdown-menu>-->
  123. <!-- </el-dropdown>-->
  124. </div>
  125. <el-table ref="liveTable" v-loading="loading" :data="liveList" @selection-change="handleSelectionChange">
  126. <el-table-column type="selection" width="55"></el-table-column>
  127. <el-table-column label="直播封面" align="center" prop="liveImgUrl" width="180">
  128. <template slot-scope="scope">
  129. <el-image style="width: 90px;height: 90px;" :src="scope.row.liveImgUrl" mode="aspectFill" :preview-src-list="[scope.row.liveImgUrl]" />
  130. </template>
  131. </el-table-column>
  132. <el-table-column label="直播名称" align="center" prop="liveName" />
  133. <el-table-column label="显示类型" align="center" prop="showType">
  134. <template slot-scope="scope">
  135. <el-tag v-if="scope.row.showType == 1">横屏</el-tag>
  136. <el-tag v-if="scope.row.showType == 2">竖屏</el-tag>
  137. </template>
  138. </el-table-column>
  139. <el-table-column label="直播状态" align="center" prop="status">
  140. <template slot-scope="scope">
  141. <el-tag v-if="scope.row.status == 1">待直播</el-tag>
  142. <el-tag v-if="scope.row.status == 2">直播中</el-tag>
  143. <el-tag v-if="scope.row.status == 3">已结束</el-tag>
  144. <el-tag v-if="scope.row.status == 4">直播回放中</el-tag>
  145. </template>
  146. </el-table-column>
  147. <el-table-column label="公司名称" align="center" prop="companyName" >
  148. <template slot-scope="scope">
  149. <el-tag v-if="scope.row.companyName">{{ scope.row.companyName }}</el-tag>
  150. <el-tag v-else>总台</el-tag>
  151. </template>
  152. </el-table-column>
  153. <el-table-column label="直播类型" align="center" prop="liveType">
  154. <template slot-scope="scope">
  155. <el-tag type="danger" v-if="scope.row.liveType == 1">直播</el-tag>
  156. <el-tag type="success" v-if="scope.row.liveType == 2">录播</el-tag>
  157. <el-tag v-if="scope.row.liveType == 3">直播回放</el-tag>
  158. </template>
  159. </el-table-column>
  160. <el-table-column label="开始时间" align="center" prop="startTime" width="180" />
  161. <el-table-column label="结束时间" align="center" prop="finishTime" width="180" />
  162. <el-table-column label="上下架" align="center" prop="isShow">
  163. <template slot-scope="scope">
  164. <el-tag v-if="scope.row.isShow == 1">上架</el-tag>
  165. <el-tag type="danger" v-if="scope.row.isShow == 2">下架</el-tag>
  166. </template>
  167. </el-table-column>
  168. <el-table-column label="审核状态" align="center" prop="isAudit">
  169. <template slot-scope="scope">
  170. <el-tag type="danger" v-if="scope.row.isAudit == 0">审核未通过</el-tag>
  171. <el-tag v-if="scope.row.isAudit == 1">审核通过</el-tag>
  172. </template>
  173. </el-table-column>
  174. <el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
  175. <template slot-scope="scope">
  176. <!-- <el-button-->
  177. <!-- v-if="scope.row.companyName != '总台' || scope.row.status != 2"-->
  178. <!-- size="mini"-->
  179. <!-- type="text"-->
  180. <!-- icon="el-icon-edit"-->
  181. <!-- @click="handleUpdate(scope.row)"-->
  182. <!-- v-hasPermi="['live:live:edit', 'live:live:operation']"-->
  183. <!-- >修改</el-button>-->
  184. <el-button
  185. v-if="scope.row.companyName == '总台'"
  186. size="mini"
  187. type="text"
  188. style="color: #00CC66"
  189. icon="el-icon-edit"
  190. @click="handleView(scope.row)"
  191. v-hasPermi="['live:live:edit']"
  192. >查看</el-button>
  193. <!-- <el-button-->
  194. <!-- size="mini"-->
  195. <!-- type="text"-->
  196. <!-- icon="el-icon-setting"-->
  197. <!-- @click="handleConfig(scope.row)"-->
  198. <!-- v-hasPermi="['live:config:list']"-->
  199. <!-- >配置</el-button>-->
  200. <el-button
  201. size="mini"
  202. type="text"
  203. icon="el-icon-service"
  204. @click="handleManage(scope.row)"
  205. v-hasPermi="['live:console:list']"
  206. >进入直播间</el-button>
  207. <el-dropdown trigger="hover">
  208. <el-button size="mini" type="text" icon="el-icon-more">
  209. 更多
  210. </el-button>
  211. <el-dropdown-menu slot="dropdown">
  212. <!-- 结束按钮 -->
  213. <el-dropdown-item
  214. v-if="scope.row.status == 2 && scope.row.liveType == 1"
  215. @click.native="showLivingUrl(scope.row)"
  216. >
  217. <i class="el-icon-switch-button"></i> 推流码
  218. </el-dropdown-item>
  219. <!-- 去直播按钮 -->
  220. <!-- <el-dropdown-item-->
  221. <!-- v-if="scope.row.status != 2"-->
  222. <!-- @click.native="handleStart(scope.row)"-->
  223. <!-- v-hasPermi="['live:live:operation']"-->
  224. <!-- >-->
  225. <!-- <i class="el-icon-monitor"></i> 去直播-->
  226. <!-- </el-dropdown-item>-->
  227. <!-- 进入直播间按钮 -->
  228. <!-- <el-dropdown-item-->
  229. <!-- v-if="scope.row.status == 2"-->
  230. <!-- @click.native="handleEnded(scope.row)"-->
  231. <!-- v-hasPermi="['live:live:operation']"-->
  232. <!-- >-->
  233. <!-- <i class="el-icon-service"></i> 结束-->
  234. <!-- </el-dropdown-item>-->
  235. <!-- <el-dropdown-item-->
  236. <!-- @click.native="handleCopy(scope.row)"-->
  237. <!-- >-->
  238. <!-- <i class="el-icon-service"></i> 复制直播间-->
  239. <!-- </el-dropdown-item>-->
  240. <el-dropdown-item
  241. @click.native="handleLink(scope.row)"
  242. >
  243. <i class="el-icon-service"></i> 查看小程序链接
  244. </el-dropdown-item>
  245. <el-dropdown-item
  246. v-if="scope.row.liveCodeUrl == null"
  247. @click.native="handleGenerateCode(scope.row)"
  248. >
  249. <i class="el-icon-service"></i> 生成二维码
  250. </el-dropdown-item>
  251. <el-dropdown-item
  252. v-if="scope.row.liveCodeUrl != null"
  253. @click.native="handleCheckCode(scope.row)"
  254. >
  255. <i class="el-icon-service"></i> 查看二维码
  256. </el-dropdown-item>
  257. </el-dropdown-menu>
  258. </el-dropdown>
  259. </template>
  260. </el-table-column>
  261. </el-table>
  262. <el-dialog
  263. title="直播二维码"
  264. :visible.sync="qrcodeDialogVisible"
  265. width="500px"
  266. :close-on-click-modal="true"
  267. :show-close="true"
  268. top="10vh"
  269. >
  270. <div class="qrcode-img-container">
  271. <img
  272. :src="currentQrcodeUrl"
  273. alt="直播二维码"
  274. class="qrcode-img"
  275. @error="handleImgError"
  276. >
  277. </div>
  278. </el-dialog>
  279. <pagination
  280. v-show="total>0"
  281. :total="total"
  282. :page.sync="queryParams.pageNum"
  283. :limit.sync="queryParams.pageSize"
  284. @pagination="getList"
  285. />
  286. <!-- 添加或修改直播对话框 -->
  287. <el-dialog :title="title" :visible.sync="open" width="900px" append-to-body>
  288. <el-form ref="form" :model="form" :rules="rules" label-width="80px">
  289. <el-form-item label="直播名称" prop="liveName">
  290. <el-input v-model="form.liveName" placeholder="请输入直播名称" :disabled="isViewOnly"/>
  291. </el-form-item>
  292. <el-form-item label="显示类型" prop="showType">
  293. <el-radio-group v-model="form.showType" :disabled="isViewOnly">
  294. <el-radio :label="1">横屏</el-radio>
  295. <el-radio :label="2">竖屏</el-radio>
  296. </el-radio-group>
  297. </el-form-item>
  298. <el-form-item label="直播类型" prop="liveType">
  299. <el-radio-group v-model="form.liveType" :disabled="isViewOnly">
  300. <!-- <el-radio :label="1">直播</el-radio>-->
  301. <el-radio :label="2">录播</el-radio>
  302. </el-radio-group>
  303. </el-form-item>
  304. <!-- <el-form-item label="直播达人" prop="talentId">-->
  305. <!-- <el-select filterable v-model="form.talentId" placeholder="请选择达人">-->
  306. <!-- <el-option-->
  307. <!-- v-for="item in talentList"-->
  308. <!-- :key="item.talentId"-->
  309. <!-- :label="item.nickName"-->
  310. <!-- :value="item.talentId">-->
  311. <!-- </el-option>-->
  312. <!-- </el-select>-->
  313. <!-- </el-form-item>-->
  314. <el-form-item label="直播描述" prop="liveDesc">
  315. <Editor ref="myeditor" :height="300" @on-text-change="updateText" />
  316. <!-- <Editor v-model="form.liveDesc" :height="300" placeholder="直播描述" />-->
  317. </el-form-item>
  318. <el-form-item label="录播视屏" prop="videoUrl" v-if="form.liveType == 2" >
  319. <video-upload :fileKey.sync="form.fileKey" :fileSize.sync="form.fileSize"
  320. :videoUrl.sync="form.videoUrl" :fileName.sync="form.fileName" :line_1.sync="form.lineOne"
  321. :uploadType.sync="form.uploadType" :isTranscode.sync="form.isTranscode"
  322. :isViewOnly="isViewOnly"
  323. ref="videoUpload"
  324. :transcodeFileKey.sync="form.transcodeFileKey" @video-duration="handleVideoDuration"
  325. @change="handleVideoChange"></video-upload>
  326. </el-form-item>
  327. <!-- <video-upload-->
  328. <!-- v-if="form.liveType == 2"-->
  329. <!-- :type = "1"-->
  330. <!-- :isPrivate = "isPrivate"-->
  331. <!-- :fileKey.sync = "form.fileKey"-->
  332. <!-- :fileSize.sync = "form.fileSize"-->
  333. <!-- :videoUrl.sync="videoUrl"-->
  334. <!-- :fileName.sync="form.fileName"-->
  335. <!-- :line_2.sync="form.lineTwo"-->
  336. <!-- :line_1.sync="form.lineOne"-->
  337. <!-- :thumbnail.sync="form.thumbnail"-->
  338. <!-- :uploadType.sync="form.uploadType"-->
  339. <!-- :isTranscode.sync="form.isTranscode"-->
  340. <!-- :transcodeFileKey.sync="form.transcodeFileKey"-->
  341. <!-- @video-duration="handleVideoDuration"-->
  342. <!-- @change="handleVideoChange"-->
  343. <!-- ref="videoUpload"-->
  344. <!-- append-to-body-->
  345. <!-- />-->
  346. <el-form-item label="开始时间" prop="startTime">
  347. <el-date-picker size="small"
  348. v-model="form.startTime"
  349. @change="timeChange"
  350. type="datetime"
  351. :disabled="isViewOnly"
  352. format="yyyy-MM-dd HH:mm:ss"
  353. value-format="yyyy-MM-dd HH:mm:ss"
  354. :picker-options="{
  355. timePickerOptions: {
  356. selectableRange: '00:00:00 - 23:59:59',
  357. format: 'HH:mm:ss'
  358. }
  359. }"
  360. placeholder="选择开始时间">
  361. </el-date-picker>
  362. </el-form-item>
  363. <el-form-item label="结束时间" prop="finishTime" v-loading="timeLoading">
  364. <el-date-picker size="small"
  365. v-model="form.finishTime"
  366. type="datetime"
  367. :disabled="isViewOnly"
  368. format="yyyy-MM-dd HH:mm:ss"
  369. value-format="yyyy-MM-dd HH:mm:ss"
  370. :picker-options="{
  371. timePickerOptions: {
  372. selectableRange: '00:00:00 - 23:59:59',
  373. format: 'HH:mm:ss'
  374. }
  375. }"
  376. placeholder="视屏播放结束">
  377. </el-date-picker>
  378. </el-form-item>
  379. <el-form-item label="直播封面" prop="liveImgUrl">
  380. <image-upload v-model="form.liveImgUrl" :limit="1" :disabled="isViewOnly"/>
  381. </el-form-item>
  382. <el-form-item label="上下架" prop="isShow">
  383. <el-radio-group v-model="form.isShow" :disabled="isViewOnly">
  384. <el-radio :label="1">上架</el-radio>
  385. <el-radio :label="2">下架</el-radio>
  386. </el-radio-group>
  387. </el-form-item>
  388. </el-form>
  389. <div slot="footer" class="dialog-footer">
  390. <el-button type="primary" @click="submitForm" v-show="!isViewOnly">确 定</el-button>
  391. <el-button @click="cancel">取 消</el-button>
  392. </div>
  393. </el-dialog>
  394. <el-dialog
  395. title="提示"
  396. :visible.sync="rtmpUrlVisible"
  397. width="30%"
  398. >
  399. <div>服务器地址:{{serverName}}</div>
  400. <div>推流码:{{livingCode}}</div>
  401. <span slot="footer" class="dialog-footer">
  402. <el-button type="primary" @click="rtmpUrlVisible = false">确 定</el-button>
  403. </span>
  404. </el-dialog>
  405. </div>
  406. </template>
  407. <script>
  408. import {
  409. listLive,
  410. getLive,
  411. delLive,
  412. addLive,
  413. updateLive,
  414. exportLive,
  415. selectCompanyTalent,
  416. handleShelfOrUn,
  417. handleDeleteSelected,
  418. finishLive,
  419. startLive, copyLive,generateCode
  420. } from "@/api/live/live";
  421. import Editor from '@/components/Editor/wang';
  422. import user from '@/store/modules/user';
  423. import VideoUpload from "@/components/LiveVideoUpload/single.vue";
  424. export default {
  425. name: "Live",
  426. components: { Editor,VideoUpload },
  427. data() {
  428. return {
  429. // 是否只读
  430. isViewOnly:false,
  431. baseUrl: process.env.VUE_APP_BASE_API,
  432. uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
  433. isPrivate:null,
  434. // 遮罩层
  435. loading: true,
  436. // 导出遮罩层
  437. exportLoading: false,
  438. // 选中数组
  439. ids: [],
  440. // 非单个禁用
  441. single: true,
  442. // 非多个禁用
  443. multiple: true,
  444. timeLoading: false,
  445. // 显示搜索条件
  446. showSearch: true,
  447. // 总条数
  448. total: 0,
  449. // 直播表格数据
  450. liveList: [],
  451. // 达人列表
  452. talentList: [{talentId:111,nickName:"测试达人"},{talentId:222,nickName:"测试达人2"}],
  453. // 弹出层标题
  454. title: "",
  455. // 是否显示弹出层
  456. open: false,
  457. liveDesc:null,
  458. // 查询参数
  459. queryParams: {
  460. pageNum: 1,
  461. pageSize: 10,
  462. liveName: null,
  463. liveDesc: null,
  464. showType: null,
  465. status: null,
  466. anchorId: null,
  467. startTimeS: null,
  468. endTimeE: null,
  469. liveType: null,
  470. startTime: null,
  471. finishTime: null,
  472. liveImgUrl: null,
  473. liveConfig: null,
  474. isShow: null,
  475. isDel: null,
  476. qwQrCode: null,
  477. rtmpUrl: null,
  478. },
  479. // 表单参数
  480. form: {
  481. uploadType: 1,
  482. isTranscode:0,
  483. transcodeFileKey:null,
  484. videoUrl: null,
  485. fileKey: null,
  486. fileName: null,
  487. fileSize: null,
  488. lineOne: null,
  489. },
  490. // 表单校验
  491. rules: {
  492. liveName: [
  493. { required: true, message: "不能为空", trigger: "burl" }
  494. ],
  495. showType: [
  496. { required: true, message: "不能为空", trigger: "burl" }
  497. ],
  498. liveType: [
  499. { required: true, message: "不能为空", trigger: "burl" }
  500. ],
  501. startTime: [
  502. { required: true, message: "不能为空", trigger: "burl" }
  503. ],
  504. liveImgUrl: [
  505. { required: true, message: "不能为空", trigger: "burl" }
  506. ],
  507. isShow: [
  508. { required: true, message: "不能为空", trigger: "change" }
  509. ],
  510. talentId: [
  511. { required: true, message: "不能为空", trigger: "change" }
  512. ]
  513. },
  514. multipleSelection: [],
  515. allChecked: false,
  516. isIndeterminate: false,
  517. rtmpUrlVisible:false,
  518. serverName: '',
  519. livingCode:'',
  520. videoUrl: "",
  521. qrcodeDialogVisible: false, // 弹窗显示状态:默认隐藏
  522. currentQrcodeUrl: "", // 当前要展示的二维码地址
  523. defaultImg: "https://via.placeholder.com/400x400?text=二维码加载失败" // 占位图
  524. };
  525. },
  526. created() {
  527. this.getList();
  528. },
  529. watch: {
  530. 'form.startTime': {
  531. handler(newVal) {
  532. // 1. 若 startTime 为空,直接返回(避免无效处理)
  533. if (!newVal) return;
  534. // 2. 将字符串时间转为 Date 对象(处理 "yyyy-MM-dd HH:mm:ss" 格式)
  535. const timeObj = new Date(newVal);
  536. // 兼容时间解析失败的情况(如格式错误)
  537. if (isNaN(timeObj.getTime())) return;
  538. // 3. 强制将秒数设为 1 并补零(即 01 秒)
  539. timeObj.setSeconds(1); // 固定秒数为 1
  540. const formattedSeconds = this.pad(timeObj.getSeconds()); // 补零为 "01"
  541. // 4. 重新拼接完整的时间字符串(保持原格式:yyyy-MM-dd HH:mm:ss)
  542. const year = timeObj.getFullYear();
  543. const month = this.pad(timeObj.getMonth() + 1); // 月份从 0 开始,需 +1
  544. const day = this.pad(timeObj.getDate());
  545. const hours = this.pad(timeObj.getHours());
  546. const minutes = this.pad(timeObj.getMinutes());
  547. // 5. 更新 form.startTime,完成格式强制修正
  548. this.form.startTime = `${year}-${month}-${day} ${hours}:${minutes}:${formattedSeconds}`;
  549. },
  550. immediate: true, // 初始化时立即执行一次(确保初始值也符合格式)
  551. deep: false // startTime 是字符串,无需深度监听
  552. }
  553. },
  554. computed:{
  555. companyUserId() {
  556. return this.$store.state.user.user.userId
  557. },
  558. companyId() {
  559. return this.$store.state.user.user.companyId
  560. },
  561. },
  562. methods: {
  563. handleLink(row){
  564. if (!row.liveId) {
  565. this.$message.error('获取直播间id失败');
  566. return
  567. }
  568. if (!this.companyUserId) {
  569. this.$message.error('获取销售id失败');
  570. return
  571. }
  572. if (!this.companyId) {
  573. this.$message.error('获取销售公司失败');
  574. return
  575. }
  576. const companyUrl = "/pages_course/living?companyId=" + this.companyId + "&companyUserId=" + this.companyUserId + "&liveId=" + row.liveId
  577. this.$alert(companyUrl, '小程序跳转连接', {
  578. confirmButtonText: '确定',
  579. });
  580. },
  581. beforeAvatarUpload(file) {
  582. const isLt1M = file.size / 1024 / 1024 < 1;
  583. if (!isLt1M) {
  584. this.$message.error('上传图片大小不能超过 1MB!');
  585. }
  586. return isLt1M;
  587. },
  588. handleAvatarSuccess(res, file) {
  589. if(res.code==200){
  590. this.form.thumbnail=res.url;
  591. this.$forceUpdate()
  592. }
  593. else{
  594. this.msgError(res.msg);
  595. }
  596. },
  597. handleShelf(){
  598. if (this.multipleSelection.length > 0) {
  599. var liveList = []
  600. this.multipleSelection.forEach(item => {
  601. liveList.push(item.liveId);
  602. })
  603. handleShelfOrUn({"liveIds":liveList,"isShow":1}).then(res=>{
  604. if (res.code == 200) {
  605. this.getList();
  606. this.$refs.liveTable.clearSelection();
  607. } else {
  608. this.$message.error(res.msg);
  609. }
  610. })
  611. } else {
  612. this.$message.info("请选择上架直播!")
  613. }
  614. },
  615. handleUnshelf(){
  616. if (this.multipleSelection.length > 0) {
  617. var liveList = []
  618. this.multipleSelection.forEach(item => {
  619. console.log(typeof(item.liveId))
  620. console.log(item.liveId)
  621. liveList.push(item.liveId);
  622. })
  623. handleShelfOrUn({"liveIds":liveList,"isShow":2}).then(res=>{
  624. if (res.code == 200) {
  625. this.getList();
  626. this.$refs.liveTable.clearSelection();
  627. } else {
  628. this.$message.error(res.msg);
  629. }
  630. })
  631. } else {
  632. this.$message.info("请选择下架直播!")
  633. }
  634. },
  635. handleDeleteSelected(){
  636. if (this.multipleSelection.length > 0) {
  637. var liveList = []
  638. this.multipleSelection.forEach(item => {
  639. liveList.push(item.liveId);
  640. })
  641. handleDeleteSelected({"liveIds":liveList}).then(res=>{
  642. if (res.code == 200) {
  643. this.getList();
  644. this.$refs.liveTable.clearSelection();
  645. } else {
  646. this.$message.error(res.msg);
  647. }
  648. })
  649. } else {
  650. this.$message.info("请选择被删除的直播!")
  651. }
  652. },
  653. // 全选或取消全选
  654. toggleSelectAll(val) {
  655. this.checked = val; // 更新 checkbox 的状态
  656. if (val) {
  657. // 如果 checkbox 被选中,则全选
  658. this.toggleSelection(this.liveList);
  659. } else {
  660. // 如果 checkbox 被取消选中,则取消全选
  661. this.toggleSelection();
  662. }
  663. },
  664. toggleSelection(rows) {
  665. if (rows && !this.isIndeterminate) {
  666. rows.forEach(row => {
  667. this.$refs.liveTable.toggleRowSelection(row);
  668. });
  669. } else {
  670. this.$refs.liveTable.clearSelection();
  671. }
  672. },
  673. // 多选框选中数据
  674. handleSelectionChange(val) {
  675. this.multipleSelection = val;
  676. // 根据选择项的数量更新 checkbox 的状态
  677. this.allChecked = val.length === this.liveList.length;
  678. this.isIndeterminate = val.length > 0 && val.length < this.liveList.length;
  679. },
  680. /** 查询直播列表 */
  681. getList() {
  682. this.loading = true;
  683. listLive(this.queryParams).then(response => {
  684. this.liveList = response.rows;
  685. this.total = response.total;
  686. this.loading = false;
  687. });
  688. },
  689. handleVideoDuration(duration) {
  690. this.form.duration = duration;
  691. },
  692. handleVideoChange(videoUrl,lineOne){
  693. this.videoUrl = videoUrl;
  694. this.form.videoUrl = videoUrl;
  695. },
  696. changeDuration(e){
  697. this.form.duration = e.duration;
  698. this.form.durationTime = e.time;
  699. this.$forceUpdate();
  700. this.timeChange();
  701. },
  702. timeChange(){
  703. if(!this.form.startTime) return;
  704. if(!this.form.duration) return;
  705. const startDateTime = new Date(this.form.startTime);
  706. // 将视频时长(秒)加到开始时间
  707. const endDateTime = new Date(startDateTime.getTime() + this.form.duration * 1000); // 毫秒为单位
  708. // 格式化为年月日 时分秒
  709. this.form.finishTime = this.formatDate(endDateTime);
  710. this.$forceUpdate();
  711. },
  712. // 将秒数转换为时分秒
  713. secondsToTime(seconds) {
  714. const hours = Math.floor(seconds / 3600);
  715. const minutes = Math.floor((seconds % 3600) / 60);
  716. const secs = Math.floor(seconds % 60);
  717. return `${this.pad(hours)}:${this.pad(minutes)}:${this.pad(secs)}`;
  718. },
  719. // 补零处理
  720. pad(number) {
  721. return number < 10 ? `0${number}` : number;
  722. },
  723. // 格式化日期为 年月日 时分秒
  724. formatDate(date) {
  725. const year = date.getFullYear();
  726. const month = (date.getMonth() + 1).toString().padStart(2, '0');
  727. const day = date.getDate().toString().padStart(2, '0');
  728. const hours = date.getHours().toString().padStart(2, '0');
  729. const minutes = date.getMinutes().toString().padStart(2, '0');
  730. const seconds = date.getSeconds().toString().padStart(2, '0');
  731. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  732. },
  733. // 取消按钮
  734. cancel() {
  735. this.open = false;
  736. this.reset();
  737. },
  738. // 表单重置
  739. reset() {
  740. this.form = {
  741. showType: 1,
  742. liveType: 2,
  743. isShow: 1,
  744. isTranscode:0,
  745. uploadType:1,
  746. transcodeFileKey:null
  747. };
  748. this.videoUrl = "";
  749. this.resetForm("form");
  750. },
  751. /** 搜索按钮操作 */
  752. handleQuery() {
  753. this.queryParams.pageNum = 1;
  754. this.getList();
  755. },
  756. /** 重置按钮操作 */
  757. resetQuery() {
  758. this.resetForm("queryForm");
  759. this.queryParams.status = null;
  760. this.handleQuery();
  761. },
  762. /** 新增按钮操作 */
  763. handleAdd() {
  764. this.isViewOnly = false
  765. this.reset();
  766. this.open = true;
  767. setTimeout(() => {
  768. this.$refs.myeditor.setText("");
  769. this.$refs.videoUpload.reset();
  770. }, 100);
  771. this.title = "添加直播间";
  772. },
  773. updateText(text){
  774. setTimeout(() => {
  775. if (this.isViewOnly) {
  776. this.$refs.myeditor.setText(this.liveDesc);
  777. this.form.liveDesc=this.liveDesc;
  778. } else {
  779. this.form.liveDesc=text;
  780. }
  781. }, 1);
  782. },
  783. /** 修改按钮操作 */
  784. handleUpdate(row) {
  785. this.isViewOnly = false
  786. this.reset();
  787. const liveId = row.liveId || this.ids
  788. getLive(liveId).then(response => {
  789. this.form = response.data;
  790. if(this.form.duration){
  791. this.form.durationTime = this.secondsToTime(this.form.duration)
  792. }
  793. this.liveDesc = this.form.liveDesc
  794. setTimeout(() => {
  795. if(this.form.liveDesc==null){
  796. this.$refs.myeditor.setText("");
  797. }else{
  798. this.$refs.myeditor.setText(this.form.liveDesc);
  799. }
  800. this.form.videoUrl = row.videoUrl
  801. }, 1);
  802. this.open = true;
  803. this.title = "修改直播间";
  804. });
  805. },
  806. /** 修改按钮操作 */
  807. handleView(row) {
  808. this.isViewOnly = true
  809. this.reset();
  810. const liveId = row.liveId || this.ids
  811. getLive(liveId).then(response => {
  812. this.form = response.data;
  813. if(this.form.duration){
  814. this.form.durationTime = this.secondsToTime(this.form.duration)
  815. }
  816. this.liveDesc = this.form.liveDesc
  817. setTimeout(() => {
  818. if(this.form.liveDesc==null){
  819. this.$refs.myeditor.setText("");
  820. }else{
  821. this.$refs.myeditor.setText(this.form.liveDesc);
  822. }
  823. this.form.videoUrl = row.videoUrl
  824. }, 1);
  825. this.open = true;
  826. this.title = "查看直播间";
  827. });
  828. },
  829. /** 提交按钮 */
  830. submitForm() {
  831. if(this.form.liveId != null) { this.videoUrl = this.form.videoUrl; }
  832. if(this.form.liveType==2 && this.videoUrl.length == 0) {return this.$message.error("请上传视频");}
  833. this.$refs["form"].validate(valid => {
  834. if (valid) {
  835. this.form.videoUrl = this.videoUrl;
  836. if (this.form.liveId != null) {
  837. updateLive(this.form).then(response => {
  838. this.msgSuccess("修改成功");
  839. this.open = false;
  840. this.getList();
  841. });
  842. } else {
  843. addLive(this.form).then(response => {
  844. this.msgSuccess("新增成功");
  845. this.open = false;
  846. this.getList();
  847. });
  848. }
  849. }
  850. });
  851. },
  852. /** 删除按钮操作 */
  853. handleDelete(row) {
  854. const liveIds = row.liveId || this.ids;
  855. this.$confirm('是否确认删除直播编号为"' + liveIds + '"的数据项?', "警告", {
  856. confirmButtonText: "确定",
  857. cancelButtonText: "取消",
  858. type: "warning"
  859. }).then(function() {
  860. return delLive(liveIds);
  861. }).then(() => {
  862. this.getList();
  863. this.msgSuccess("删除成功");
  864. }).catch(() => {});
  865. },
  866. handleConfig(row) {
  867. console.info(row)
  868. this.$router.push('/live/liveConfig/' + row.liveId)
  869. },
  870. handleManage(row) {
  871. const routeUrl = this.$router.resolve({
  872. path: `/live/liveConsole/` + row.liveId
  873. }).href;
  874. window.open(routeUrl, '_blank')
  875. // this.$router.push('/live/liveConsole/' + row.liveId)
  876. },
  877. handleEnded(row){
  878. this.$confirm('是否确认关闭直播间?', "警告", {
  879. confirmButtonText: "确定",
  880. cancelButtonText: "取消",
  881. type: "warning"
  882. }).then(() => {
  883. finishLive({"liveId":row.liveId}).then(response=>{this.getList()})
  884. }).catch(() => {});
  885. },
  886. handleCopy(row){
  887. this.$confirm('是否确认复制直播间?', "警告", {
  888. confirmButtonText: "确定",
  889. cancelButtonText: "取消",
  890. type: "warning"
  891. }).then(() => {
  892. this.loading = true;
  893. copyLive({"liveId":row.liveId}).then(response=>{this.getList();this.loading = false;})
  894. }).catch(() => {});
  895. },
  896. handleGenerateCode(row){
  897. this.$confirm('是否确认生成直播间小程序二维码?', "警告", {
  898. confirmButtonText: "确定",
  899. cancelButtonText: "取消",
  900. type: "warning"
  901. }).then(() => {
  902. generateCode({"liveId":row.liveId}).then(response=>{
  903. if(response.code == 200){
  904. row.liveCodeUrl = response.data.liveCodeUrl
  905. }
  906. })
  907. }).catch(() => {});
  908. },
  909. // 查看二维码图片
  910. handleCheckCode(row) {
  911. // 先校验图片地址是否存在
  912. if (!row.liveCodeUrl) {
  913. this.$message.warning("二维码图片地址不存在,请稍后重试");
  914. return;
  915. }
  916. // 赋值当前图片地址 + 打开弹窗
  917. this.currentQrcodeUrl = row.liveCodeUrl;
  918. this.qrcodeDialogVisible = true;
  919. },
  920. // 2. 图片加载失败:替换为占位图
  921. handleImgError(e) {
  922. e.target.src = this.defaultImg;
  923. },
  924. handleStart(row){
  925. if(row.isShow == 2){
  926. this.$message.error("直播间已下架")
  927. return
  928. }
  929. this.$confirm('是否确认开启直播间?', "警告", {
  930. confirmButtonText: "确定",
  931. cancelButtonText: "取消",
  932. type: "warning"
  933. }).then(() => {
  934. startLive({"liveId":row.liveId}).then(response=>{this.getList()})
  935. }).catch(() => {});
  936. },
  937. showLivingUrl(row){
  938. this.serverName=''
  939. this.livingCode=''
  940. this.rtmpUrlVisible = true
  941. this.serverName = row.rtmpUrl.slice(0,row.rtmpUrl.lastIndexOf('/') + 1)
  942. this.livingCode = row.rtmpUrl.slice(row.rtmpUrl.lastIndexOf('/') + 1)
  943. },
  944. /** 导出按钮操作 */
  945. handleExport() {
  946. const queryParams = this.queryParams;
  947. this.$confirm('是否确认导出所有直播数据项?', "警告", {
  948. confirmButtonText: "确定",
  949. cancelButtonText: "取消",
  950. type: "warning"
  951. }).then(() => {
  952. this.exportLoading = true;
  953. return exportLive(queryParams);
  954. }).then(response => {
  955. this.download(response.msg);
  956. this.exportLoading = false;
  957. }).catch(() => {});
  958. }
  959. }
  960. };
  961. </script>
  962. <style scoped>
  963. .selection-toolbar {
  964. display: flex;
  965. align-items: center;
  966. margin-bottom: 10px;
  967. padding-left: 10px;
  968. }
  969. .selection-toolbar .el-checkbox {
  970. margin-right: 10px;
  971. }
  972. /* 调整 checkbox 内部输入框的对齐 */
  973. .selection-toolbar .el-checkbox .el-checkbox__inner {
  974. top: 8px; /* 根据实际需求调整 */
  975. }
  976. /* 图片容器:居中 + 内边距 */
  977. .qrcode-img-container {
  978. text-align: center;
  979. padding: 20px 0;
  980. }
  981. /* 图片样式:自适应弹窗宽度,避免溢出 */
  982. .qrcode-img {
  983. max-width: 100%; /* 最大宽度不超过容器 */
  984. max-height: 400px; /* 最大高度限制,避免过高 */
  985. cursor: pointer; /* 鼠标悬浮变指针,提示可点击 */
  986. transition: all 0.3s ease; /* 过渡动画,hover 更流畅 */
  987. }
  988. /* 图片 hover 效果:轻微放大 */
  989. .qrcode-img:hover {
  990. transform: scale(1.02);
  991. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); /* 加阴影增强层次感 */
  992. }
  993. /* 底部提示文字样式 */
  994. .qrcode-footer {
  995. text-align: center !important; /* 覆盖 Element 默认样式,居中显示 */
  996. }
  997. .text-gray {
  998. color: #666;
  999. font-size: 12px;
  1000. }
  1001. </style>