ソースを参照

1、优化直播配置页面调整
2、调整直播观看、完课奖励为多类型

yys 4 週間 前
コミット
1ef2616120

+ 15 - 4
src/views/live/liveConfig/barrage.vue

@@ -1,6 +1,14 @@
 <template>
-  <div class="app-container">
-    <el-row :gutter="10" class="mb8">
+  <div class="cfg-subpage">
+    <div class="cfg-banner">
+      <div class="cfg-banner-icon pink"><i class="el-icon-chat-dot-round"></i></div>
+      <div>
+        <div class="cfg-banner-title">弹幕脚本</div>
+        <div class="cfg-banner-desc">配置定时弹幕人物与内容,活跃直播间氛围</div>
+      </div>
+    </div>
+    <div class="cfg-panel">
+    <el-row :gutter="10" class="mb8 cfg-toolbar">
       <el-col :span="1.5">
         <el-button
           type="primary" plain icon="el-icon-refresh" size="mini" @click="handleReload" v-hasPermi="['live:task:add']"
@@ -23,7 +31,7 @@
       </el-col>
       <el-col :span="1.5">
         <el-button
-          type="success" plain icon="el-icon-download" size="mini" :loading="exportLoading" @click="handleImport" v-hasPermi="['live:task:export']"
+          type="success" plain icon="el-icon-upload2" size="mini" :loading="exportLoading" @click="handleImport" v-hasPermi="['live:task:export']"
         >导入</el-button>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -116,6 +124,7 @@
         <el-button @click="upload.open = false">取 消</el-button>
       </div>
     </el-dialog>
+    </div>
   </div>
 </template>
 
@@ -432,7 +441,9 @@ export default {
 };
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+@import './cfg-common.scss';
+
 .loading-indicator {
   padding: 10px;
   text-align: center;

+ 168 - 0
src/views/live/liveConfig/cfg-common.scss

@@ -0,0 +1,168 @@
+/* live config shared styles */
+.cfg-subpage {
+  min-height: 100%;
+  padding: 4px 4px 24px;
+}
+
+.cfg-banner {
+  display: flex;
+  align-items: center;
+  gap: 14px;
+  margin-bottom: 14px;
+  padding: 16px 18px;
+  border-radius: 12px;
+  background: linear-gradient(135deg, #edf5ff 0%, #f5f9ff 55%, #eef7f3 100%);
+  border: 1px solid #e4eef8;
+}
+
+.cfg-banner-icon {
+  width: 42px;
+  height: 42px;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-size: 20px;
+  flex-shrink: 0;
+}
+
+.cfg-banner-icon.blue { background: linear-gradient(135deg, #1890ff, #36a3ff); }
+.cfg-banner-icon.cyan { background: linear-gradient(135deg, #13c2c2, #36cfc9); }
+.cfg-banner-icon.orange { background: linear-gradient(135deg, #fa8c16, #ffa940); }
+.cfg-banner-icon.gold { background: linear-gradient(135deg, #faad14, #ffc53d); }
+.cfg-banner-icon.green { background: linear-gradient(135deg, #52c41a, #73d13d); }
+.cfg-banner-icon.purple { background: linear-gradient(135deg, #722ed1, #9254de); }
+.cfg-banner-icon.indigo { background: linear-gradient(135deg, #2f54eb, #597ef7); }
+.cfg-banner-icon.teal { background: linear-gradient(135deg, #08979c, #13c2c2); }
+.cfg-banner-icon.pink { background: linear-gradient(135deg, #eb2f96, #f759ab); }
+.cfg-banner-icon.slate { background: linear-gradient(135deg, #595959, #8c8c8c); }
+
+.cfg-banner-title {
+  font-size: 17px;
+  font-weight: 700;
+  color: #1f2d3d;
+  margin-bottom: 4px;
+}
+
+.cfg-banner-desc {
+  font-size: 13px;
+  color: #6b7c93;
+  line-height: 1.5;
+}
+
+.cfg-panel {
+  background: #fff;
+  border-radius: 12px;
+  border: 1px solid #eef2f7;
+  box-shadow: 0 1px 2px rgba(31, 45, 61, 0.04);
+  padding: 16px 18px 20px;
+}
+
+.cfg-panel + .cfg-panel {
+  margin-top: 14px;
+}
+
+.cfg-section-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  margin-bottom: 14px;
+}
+
+.cfg-section-title::before {
+  content: '';
+  width: 3px;
+  height: 14px;
+  border-radius: 2px;
+  background: #1890ff;
+}
+
+.cfg-toolbar {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-bottom: 14px;
+  padding: 10px 12px;
+  background: #f8fafc;
+  border: 1px solid #eef2f7;
+  border-radius: 10px;
+}
+
+.cfg-filter {
+  margin-bottom: 4px;
+}
+
+.cfg-filter .el-form-item {
+  margin-bottom: 12px;
+}
+
+.cfg-table-wrap {
+  border-radius: 10px;
+  overflow: hidden;
+  border: 1px solid #eef2f7;
+}
+
+.cfg-table-wrap .el-table {
+  border: none !important;
+}
+
+.cfg-empty-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  min-height: 160px;
+  color: #909399;
+  font-size: 13px;
+  background: #fafbfc;
+  border: 1px dashed #d9e2ec;
+  border-radius: 10px;
+}
+
+.cfg-switch-row {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 14px 16px;
+  background: #f8fafc;
+  border: 1px solid #eef2f7;
+  border-radius: 10px;
+  margin-bottom: 14px;
+}
+
+.cfg-switch-label {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.cfg-switch-desc {
+  font-size: 12px;
+  color: #909399;
+}
+
+.cfg-upload-zone {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  gap: 12px;
+  padding: 20px;
+  background: #f8fafc;
+  border: 1px dashed #d9e2ec;
+  border-radius: 12px;
+}
+
+.cfg-id-preview {
+  width: 180px;
+  height: 180px;
+  object-fit: cover;
+  border-radius: 10px;
+  border: 1px solid #ebeef5;
+  background: #fff;
+}

+ 25 - 40
src/views/live/liveConfig/goods.vue

@@ -1,15 +1,23 @@
 <template>
-  <div class="app-container">
+  <div class="cfg-subpage">
+    <div class="cfg-banner">
+      <div class="cfg-banner-icon purple"><i class="el-icon-goods"></i></div>
+      <div>
+        <div class="cfg-banner-title">直播商品</div>
+        <div class="cfg-banner-desc">管理直播带货商品、库存、销量与上下架状态</div>
+      </div>
+    </div>
+    <div class="cfg-panel">
   <div class="el-container-md">
 
-    <div class="selection-toolbar">
+    <div class="cfg-toolbar selection-toolbar">
       <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
         {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
       </el-checkbox>
-      <el-button  plain size="mini" @click="handleShelf">上架</el-button>
-      <el-button  plain size="mini" @click="handleUnshelf">下架</el-button>
-      <el-button  plain size="mini" @click="handleDeleteSelected">删除</el-button>
-      <el-button  plain type="mini" icon="el-icon-plus" @click="handleAddLiveGoods">添加商品</el-button>
+      <el-button plain size="mini" @click="handleShelf">上架</el-button>
+      <el-button plain size="mini" @click="handleUnshelf">下架</el-button>
+      <el-button plain size="mini" @click="handleDeleteSelected">删除</el-button>
+      <el-button type="primary" plain size="mini" icon="el-icon-plus" @click="handleAddLiveGoods">添加商品</el-button>
     </div>
     <el-table
       ref="goodTable"
@@ -32,7 +40,7 @@
         <template slot-scope="scope">
           <img
             :src="scope.row.imgUrl"
-            style="display: block; max-width: 100%; width: 100px; height: 100px"
+            class="goods-img"
           />
         </template>
 
@@ -248,6 +256,7 @@
 
   </div>
   </div>
+  </div>
 </template>
 
 <script>
@@ -569,42 +578,18 @@ export default {
 </script >
 
 <style lang="scss" scoped>
-
-.selection-toolbar {
-  display: flex;
-  align-items: center;
-  margin-bottom: 10px;
-  padding-left: 10px;
-  background-color: #f0f2f5;
-}
+@import './cfg-common.scss';
 
 .selection-toolbar .el-checkbox {
-  margin-right: 10px;
-  background-color: #f0f2f5;
+  margin-right: 4px;
 }
 
-/* 调整 checkbox 内部输入框的对齐 */
-.selection-toolbar .el-checkbox .el-checkbox__inner {
-  top: 8px; /* 根据实际需求调整 */
-  background-color: #f0f2f5;
-}
-/* 1. 让组件根容器继承 app-main 背景 */
-.goods-table-container {
-  background-color: inherit; /* 继承父容器(app-main)的背景色 */
-  padding: 20px; /* 可选:保留内边距,避免内容贴边 */
-}
-
-/* 2. 让 Element UI 容器/表格匹配背景(若其有默认白色背景) */
-.el-container-md {
-  background-color: inherit; /* 取消 el-container 的默认背景 */
-}
-
-.el-table {
-  background-color: inherit; /* 取消 el-table 的默认白色背景(可选,若需要表格透明) */
-  /* 若希望表格有背景,但与 app-main 协调:可改为 app-main 背景色的相近色 */
-  /* background-color: #f8f9fa; */
-}
-::v-deep .app-main .goods-table-container {
-  background-color: #f5f7fa; /* 与 app-main 全局背景统一 */
+.goods-img {
+  display: block;
+  width: 72px;
+  height: 72px;
+  object-fit: cover;
+  border-radius: 8px;
+  border: 1px solid #eef2f7;
 }
 </style>

+ 27 - 11
src/views/live/liveConfig/idCard.vue

@@ -1,13 +1,27 @@
 <template>
-  <el-form ref="form"  label-width="80px">
-    <el-form-item>
-      <el-form-item label="上传图片" prop="materialUrl">
-        <img :src="idCardUrl" alt="身份证" style="height: 150px; width: 150px" v-if="idCardUrl != null">
+  <div class="cfg-subpage">
+    <div class="cfg-banner">
+      <div class="cfg-banner-icon slate"><i class="el-icon-user"></i></div>
+      <div>
+        <div class="cfg-banner-title">身份认证</div>
+        <div class="cfg-banner-desc">上传主播/医生身份凭证图片,用于直播间合规展示</div>
+      </div>
+    </div>
+
+    <div class="cfg-panel">
+      <div class="cfg-section-title">证件图片</div>
+      <div class="cfg-upload-zone">
+        <img
+          v-if="idCardUrl"
+          :src="idCardUrl"
+          alt="身份证"
+          class="cfg-id-preview"
+        >
         <ImageUpload @input="handleUrl" type="image" :num="10" :width="150" :height="150" />
-      </el-form-item>
-      <el-button type="primary" size="mini" @click="submit">保存</el-button>
-    </el-form-item>
-  </el-form>
+        <el-button type="primary" size="small" icon="el-icon-check" @click="submit">保存</el-button>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script>
@@ -24,9 +38,8 @@ export default {
     };
   },
   watch: {
-    // 监听路由的 query 参数变化
     '$route.query': {
-      handler(newQuery) {
+      handler() {
         this.liveId = this.$route.params.liveId
         if(this.liveId == null) {
           return;
@@ -35,7 +48,6 @@ export default {
           this.idCardUrl = res.data.idCardUrl;
         })
       },
-      // 初始化时立即执行一次
       immediate: true
     }
   },
@@ -63,3 +75,7 @@ export default {
   }
 };
 </script>
+
+<style scoped lang="scss">
+@import './cfg-common.scss';
+</style>

+ 371 - 186
src/views/live/liveConfig/index.vue

@@ -1,103 +1,78 @@
 <template>
-  <div class="live-config-container">
-    <el-card class="box-card live-config-header">
-      <div class="detail-header">
-        <div class="header-left-area">
-          <img :src="liveInfo.liveImgUrl" alt="直播封面">
-          <!-- 加载遮罩 -->
-          <div class="ss-loading-mask" style="display: none;">
-            <div class="ss-loading-spinner is-module-loading">
-              <div class="ss-loading-dot-flashing"></div>
-            </div>
-          </div>
+  <div class="live-config-page">
+    <!-- 顶部直播信息 -->
+    <div class="live-hero">
+      <div class="hero-cover">
+        <img v-if="liveInfo.liveImgUrl" :src="liveInfo.liveImgUrl" alt="直播封面">
+        <div v-else class="cover-placeholder">
+          <i class="el-icon-video-camera"></i>
         </div>
+        <span class="status-badge" :class="statusClass">{{ statusText }}</span>
+      </div>
 
-        <div class="header-middle-area">
-          <div class="live-info-title">
-            <div class="title-text">{{ liveInfo.liveName }}</div>
-          </div>
-
-          <div class="live-info-desc">
-            <div class="live-type">{{ liveInfo.liveType == 1 ? '视频直播' : '录播' }}</div>
-            <div class="line"></div>
-            <div class="live-mode">{{ liveInfo.showType == 1 ? '横屏' : '竖屏' }}</div>
-            <div class="big-screen-info">
-              <div class="dataLarge">
-                <a class="dataLarge__screen ss-popover__reference" aria-describedby="ss-popover-7614" tabindex="0" data-auth-id="818">
-                  <span class="dataLarge__screen__icon"></span>
-                  <span class="dataLarge__screen__txt">实时大屏</span>
-                </a>
-              </div>
-            </div>
-          </div>
-
-          <div class="live-info-state">
-            <div class="info-state-text live-time">直播时间:{{ liveInfo.startTime }} 至 {{ liveInfo.finishTime }}</div>
-            <div class="line"></div>
-            <div class="info-state-text live-state">{{ liveInfo.status == 3 ? '已结束' : liveInfo.status == 2 ? '直播中' : '未开始'}}</div>
-            <div class="line"></div>
-            <div class="info-state-text live-active-state">{{ liveInfo.isShow == 1 ? '上架' : '下架' }}</div>
-            <div class="line"></div>
+      <div class="hero-main">
+        <div class="hero-top">
+          <h1 class="live-title">{{ liveInfo.liveName || '直播配置' }}</h1>
+          <div class="hero-actions">
+            <el-button size="small" icon="el-icon-back" @click="goBack">返回列表</el-button>
+            <el-button size="small" type="primary" plain icon="el-icon-monitor" @click="goConsole">
+              实时大屏
+            </el-button>
           </div>
         </div>
 
-
-<!--        <div class="header-right-area">-->
-<!--          <div class="operation-wrapper">-->
-<!--            <div class="operation-item">-->
-<!--              <div class="operation-item-icon sales-icon"></div>-->
-<!--              <div class="operation-item-text">中控台</div>-->
-<!--            </div>-->
-<!--            <div class="line"></div>-->
-<!--            <div class="operation-item">-->
-<!--              <span class="operation-item-icon xe-iconfont"></span>-->
-<!--              <div class="operation-item-text">分享</div>-->
-<!--            </div>-->
-<!--            <div class="line"></div>-->
-<!--            <div class="operation-item" data-auth-id="975">-->
-<!--              <span class="operation-item-icon xe-iconfont"></span>-->
-<!--              <div class="operation-item-text">恢复直播</div>-->
-<!--            </div>-->
-<!--            <div class="line"></div>-->
-<!--            <div class="operation-item ss-popover__reference" aria-describedby="ss-popover-6482" tabindex="0">-->
-<!--              <span class="operation-item-icon xe-iconfont"></span>-->
-<!--              <div class="operation-item-text">更多</div>-->
-<!--            </div>-->
-<!--          </div>-->
-<!--        </div>-->
-      </div>
-    </el-card>
-    <el-card class="box-card live-config-left">
-      <div class="container">
-      <!-- 左边菜单 -->
-        <div class="left-menu">
-          <el-menu default-active="1" class="el-menu-vertical-demo" @select="handleSelect">
-            <el-menu-item :index="item.index" v-for="item in menuList" >
-              <span>{{item.name}}</span>
-            </el-menu-item>
-          </el-menu>
+        <div class="meta-row">
+          <span class="meta-chip">
+            <i class="el-icon-film"></i>
+            {{ liveInfo.liveType == 1 ? '视频直播' : '录播' }}
+          </span>
+          <span class="meta-chip">
+            <i class="el-icon-full-screen"></i>
+            {{ liveInfo.showType == 1 ? '横屏' : '竖屏' }}
+          </span>
+          <span class="meta-chip" :class="liveInfo.isShow == 1 ? 'is-on' : 'is-off'">
+            <i :class="liveInfo.isShow == 1 ? 'el-icon-upload2' : 'el-icon-download'"></i>
+            {{ liveInfo.isShow == 1 ? '已上架' : '已下架' }}
+          </span>
         </div>
-        <!-- 右边信息 -->
-        <div class="right-info">
-          <!-- 动态组件 -->
-          <component :is="currentComponent" :live-type="liveInfo.liveType"></component>
+
+        <div class="time-row">
+          <i class="el-icon-time"></i>
+          <span>直播时间:{{ liveInfo.startTime || '-' }} 至 {{ liveInfo.finishTime || '-' }}</span>
         </div>
       </div>
-<!--      <el-tabs tab-position="left" style="height: 200px;">-->
-<!--        <el-tab-pane label="营销内容">营销内容</el-tab-pane>-->
-<!--        <el-tab-pane label="观看奖励">观看奖励</el-tab-pane>-->
-<!--        <el-tab-pane label="答题红包">答题红包</el-tab-pane>-->
-<!--        <el-tab-pane label="答题">答题</el-tab-pane>-->
-<!--        <el-tab-pane label="直播商品">直播商品</el-tab-pane>-->
-<!--        <el-tab-pane label="观看积分">观看积分</el-tab-pane>-->
-<!--        <el-tab-pane label="身份认证">-->
-<!--            <idCard :liveId="liveId" />-->
-<!--        </el-tab-pane>-->
-<!--      </el-tabs>-->
-    </el-card>
+    </div>
 
+    <!-- 主体:侧栏 + 内容 -->
+    <div class="live-body">
+      <aside class="nav-panel">
+        <div class="nav-label">配置项</div>
+        <button
+          v-for="item in menuList"
+          :key="item.index"
+          type="button"
+          class="nav-item"
+          :class="{ active: activeMenu === item.index }"
+          @click="handleSelect(item.index)"
+        >
+          <span class="nav-icon" :class="'tone-' + item.tone">
+            <i :class="item.icon"></i>
+          </span>
+          <span class="nav-text">
+            <span class="nav-name">{{ item.name }}</span>
+            <span class="nav-desc">{{ item.desc }}</span>
+          </span>
+          <i class="el-icon-arrow-right nav-arrow"></i>
+        </button>
+      </aside>
+
+      <section class="content-panel">
+        <component :is="currentComponent" :live-type="liveInfo.liveType"></component>
+      </section>
+    </div>
   </div>
 </template>
+
 <script>
 import WatchReward from './watchReward.vue';
 import Answer from './answer.vue';
@@ -110,8 +85,7 @@ import LiveReplay from './liveReplay.vue'
 import Preview from './preview.vue'
 import LiveCoupon from './liveCoupon.vue'
 import Barrage from './barrage.vue'
-import { listLive, getLive, delLive, addLive, updateLive, exportLive,selectCompanyTalent,handleShelfOrUn,handleDeleteSelected } from "@/api/live/live";
-
+import { getLive } from "@/api/live/live";
 
 export default {
   name: 'LiveConfig',
@@ -130,27 +104,24 @@ export default {
   },
   data() {
     return {
-      activeTab: 'watchReward',
       liveId: null,
       liveInfo: {},
-      socket:null,
-      currentComponent: WatchReward,
+      socket: null,
+      activeMenu: 'watchReward',
+      currentComponent: 'WatchReward',
       liveWsUrl: process.env.VUE_APP_LIVE_WS_URL + '/app/webSocket',
-      menuList:[
-        { name: '观看奖励', label: '观看奖励', index: 'watchReward'},
-        { name: '直播预告', label: '直播预告', index: 'preview'},
-        { name: '红包配置', label: '红包配置', index: 'liveRedConf'},
-        { name: '抽奖配置', label: '抽奖配置', index: 'liveLotteryConf'},
-        { name: '优惠券配置', label: '优惠券配置', index: 'liveCoupon'},
-        // { name: '答题', label: '答题', index: 'answer'},
-        { name: '直播商品', label: '直播商品', index: 'goods'},
-        { name: '回放设置', label: '回放设置', index: 'liveReplay'},
-        // { name: '观看积分', label: '观看积分', index: 'watchScore'},
-        { name: '运营自动化', label: '运营自动化', index: 'task'},
-        { name: '弹幕脚本', label: '弹幕脚本', index: 'barrage'},
-        { name: '身份认证', label: '身份认证', index: 'idCard'},
+      menuList: [
+        { name: '观看奖励', index: 'watchReward', component: 'WatchReward', icon: 'el-icon-present', tone: 'blue', desc: '时长 / 完课奖励' },
+        { name: '直播预告', index: 'preview', component: 'Preview', icon: 'el-icon-bell', tone: 'cyan', desc: '预告信息配置' },
+        { name: '红包配置', index: 'liveRedConf', component: 'LiveRedConf', icon: 'el-icon-coin', tone: 'orange', desc: '直播红包规则' },
+        { name: '抽奖配置', index: 'liveLotteryConf', component: 'LiveLotteryConf', icon: 'el-icon-trophy', tone: 'gold', desc: '抽奖活动设置' },
+        { name: '优惠券配置', index: 'liveCoupon', component: 'LiveCoupon', icon: 'el-icon-ticket', tone: 'green', desc: '优惠券投放' },
+        { name: '直播商品', index: 'goods', component: 'Goods', icon: 'el-icon-goods', tone: 'purple', desc: '带货商品管理' },
+        { name: '回放设置', index: 'liveReplay', component: 'LiveReplay', icon: 'el-icon-video-play', tone: 'indigo', desc: '回放权限与展示' },
+        { name: '运营自动化', index: 'task', component: 'Task', icon: 'el-icon-s-operation', tone: 'teal', desc: '自动化任务' },
+        { name: '弹幕脚本', index: 'barrage', component: 'Barrage', icon: 'el-icon-chat-dot-round', tone: 'pink', desc: '弹幕脚本管理' },
+        { name: '身份认证', index: 'idCard', component: 'IdCard', icon: 'el-icon-user', tone: 'slate', desc: '观众身份校验' },
       ],
-
     }
   },
   created() {
@@ -162,6 +133,14 @@ export default {
     userId() {
       return this.$store.state.user.user.userId
     },
+    statusText() {
+      const map = { 1: '未开始', 2: '直播中', 3: '已结束' }
+      return map[this.liveInfo.status] || '未知'
+    },
+    statusClass() {
+      const map = { 1: 'pending', 2: 'living', 3: 'ended' }
+      return map[this.liveInfo.status] || 'pending'
+    }
   },
   methods: {
     connectWebSocket() {
@@ -171,147 +150,353 @@ export default {
         userId: this.userId
       })
       this.socket = this.$store.state.liveWs[this.liveId]
-      this.socket.onmessage = (event) => this.handleWsMessage(event)
+      if (this.socket) {
+        this.socket.onmessage = (event) => this.handleWsMessage(event)
+      }
     },
-    handleSelect(index){
-      this.currentComponent = index; // 切换当前显示的组件
+    handleWsMessage() {
+      // 配置页仅保持连接,消息由中控台处理
+    },
+    handleSelect(index) {
+      const item = this.menuList.find(m => m.index === index)
+      if (!item) return
+      this.activeMenu = index
+      this.currentComponent = item.component
     },
     getLiving() {
       getLive(this.liveId).then(res => {
-        this.liveInfo = res.data
+        this.liveInfo = res.data || {}
       })
     },
+    goBack() {
+      this.$router.back()
+    },
+    goConsole() {
+      this.$router.push('/live/liveConsole/' + this.liveId)
+    }
   }
 }
 </script>
 
-
 <style scoped>
-.el-tabs__header .is-top {
-  display: none !important;
-}
-.live-config-header{
-  padding: 5px;
-  border: 1px solid #ebeef5;
+.live-config-page {
+  min-height: calc(100vh - 84px);
+  padding: 16px 16px 24px;
+  background:
+    radial-gradient(ellipse 80% 50% at 0% 0%, rgba(24, 144, 255, 0.08), transparent 55%),
+    radial-gradient(ellipse 60% 40% at 100% 0%, rgba(82, 196, 26, 0.06), transparent 50%),
+    linear-gradient(180deg, #f4f7fb 0%, #eef2f7 100%);
 }
-.detail-header {
+
+/* ===== Hero ===== */
+.live-hero {
   display: flex;
-  padding: 20px;
-  border-bottom: 1px solid #ebeef5;
+  gap: 20px;
+  padding: 20px 22px;
+  margin-bottom: 16px;
+  background: rgba(255, 255, 255, 0.92);
+  border: 1px solid #e6edf5;
+  border-radius: 14px;
+  box-shadow: 0 4px 20px rgba(31, 45, 61, 0.04);
+  backdrop-filter: blur(8px);
 }
 
-.header-left-area {
-  flex: 0 0 120px;
+.hero-cover {
   position: relative;
+  flex: 0 0 120px;
+  width: 120px;
+  height: 120px;
+  border-radius: 12px;
+  overflow: hidden;
+  background: #e8eef5;
 }
 
-.header-left-area img {
-  width: 100px;
-  height: 100px;
-  border-radius: 4px;
+.hero-cover img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  display: block;
+}
+
+.cover-placeholder {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #8fa0b5;
+  font-size: 36px;
+  background: linear-gradient(135deg, #e8eef5, #f2f6fa);
+}
+
+.status-badge {
+  position: absolute;
+  left: 8px;
+  bottom: 8px;
+  padding: 2px 8px;
+  border-radius: 999px;
+  font-size: 12px;
+  font-weight: 600;
+  color: #fff;
+  line-height: 1.6;
+  backdrop-filter: blur(4px);
+}
+
+.status-badge.pending {
+  background: rgba(144, 147, 153, 0.88);
 }
 
-.header-middle-area {
+.status-badge.living {
+  background: rgba(245, 108, 108, 0.92);
+  box-shadow: 0 0 0 0 rgba(245, 108, 108, 0.5);
+  animation: living-pulse 1.8s ease-out infinite;
+}
+
+.status-badge.ended {
+  background: rgba(96, 98, 102, 0.88);
+}
+
+@keyframes living-pulse {
+  0% { box-shadow: 0 0 0 0 rgba(245, 108, 108, 0.45); }
+  70% { box-shadow: 0 0 0 8px rgba(245, 108, 108, 0); }
+  100% { box-shadow: 0 0 0 0 rgba(245, 108, 108, 0); }
+}
+
+.hero-main {
   flex: 1;
-  padding: 0 20px;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  gap: 10px;
 }
 
-.header-right-area {
-  flex: 0 0 200px;
+.hero-top {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  gap: 16px;
 }
 
-.live-info-title .title-text {
-  font-size: 20px;
-  font-weight: 500;
-  color: #303133;
+.live-title {
+  margin: 0;
+  font-size: 22px;
+  font-weight: 700;
+  color: #1f2d3d;
+  line-height: 1.3;
+  letter-spacing: 0.2px;
 }
 
-.live-info-desc {
+.hero-actions {
+  flex-shrink: 0;
   display: flex;
+  gap: 8px;
+}
+
+.meta-row {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+
+.meta-chip {
+  display: inline-flex;
   align-items: center;
-  margin-top: 10px;
+  gap: 4px;
+  padding: 4px 10px;
+  border-radius: 999px;
+  font-size: 12px;
+  color: #4a5d73;
+  background: #f0f4f9;
+  border: 1px solid #e4ebf3;
 }
 
-.live-info-desc .line {
-  width: 1px;
-  height: 12px;
-  background: #c0c4cc;
-  margin: 0 10px;
+.meta-chip i {
+  font-size: 13px;
 }
 
-.live-info-state {
+.meta-chip.is-on {
+  color: #389e0d;
+  background: #f6ffed;
+  border-color: #d9f7be;
+}
+
+.meta-chip.is-off {
+  color: #8c8c8c;
+  background: #fafafa;
+  border-color: #f0f0f0;
+}
+
+.time-row {
   display: flex;
   align-items: center;
-  margin-top: 10px;
+  gap: 6px;
+  font-size: 13px;
+  color: #6b7c93;
 }
 
-.live-info-state .line {
-  width: 1px;
-  height: 12px;
-  background: #c0c4cc;
-  margin: 0 10px;
+.time-row i {
+  color: #8fa0b5;
 }
 
-.live-state-dot {
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
+/* ===== Body ===== */
+.live-body {
+  display: flex;
+  gap: 14px;
+  align-items: stretch;
+  min-height: calc(100vh - 280px);
 }
 
-.live-state-dot-liveEnd {
-  background: #909399;
+.nav-panel {
+  flex: 0 0 240px;
+  width: 240px;
+  padding: 14px 10px;
+  background: rgba(255, 255, 255, 0.95);
+  border: 1px solid #e6edf5;
+  border-radius: 14px;
+  box-shadow: 0 2px 12px rgba(31, 45, 61, 0.03);
+  align-self: flex-start;
+  position: sticky;
+  top: 12px;
+  max-height: calc(100vh - 120px);
+  overflow-y: auto;
 }
 
-.info-state-text {
-  font-size: 14px;
-  color: #606266;
+.nav-label {
+  padding: 4px 12px 12px;
+  font-size: 12px;
+  font-weight: 600;
+  color: #8fa0b5;
+  letter-spacing: 0.8px;
+  text-transform: uppercase;
 }
 
-.operation-wrapper {
+.nav-item {
+  width: 100%;
   display: flex;
   align-items: center;
+  gap: 10px;
+  padding: 10px 10px;
+  margin-bottom: 4px;
+  border: 1px solid transparent;
+  border-radius: 10px;
+  background: transparent;
+  cursor: pointer;
+  text-align: left;
+  transition: all 0.18s ease;
+  outline: none;
 }
 
-.operation-item {
+.nav-item:hover {
+  background: #f5f8fc;
+  border-color: #e8eef5;
+}
+
+.nav-item.active {
+  background: linear-gradient(135deg, #edf5ff 0%, #f3f9ff 100%);
+  border-color: #cfe3ff;
+  box-shadow: 0 2px 8px rgba(24, 144, 255, 0.08);
+}
+
+.nav-icon {
+  flex-shrink: 0;
+  width: 34px;
+  height: 34px;
+  border-radius: 9px;
   display: flex;
-  flex-direction: column;
   align-items: center;
-  cursor: pointer;
+  justify-content: center;
+  font-size: 16px;
+  color: #fff;
 }
 
-.operation-item-text {
-  font-size: 12px;
-  color: #606266;
-  margin-top: 4px;
+.tone-blue { background: linear-gradient(135deg, #1890ff, #36a3ff); }
+.tone-cyan { background: linear-gradient(135deg, #13c2c2, #36cfc9); }
+.tone-orange { background: linear-gradient(135deg, #fa8c16, #ffa940); }
+.tone-gold { background: linear-gradient(135deg, #faad14, #ffc53d); }
+.tone-green { background: linear-gradient(135deg, #52c41a, #73d13d); }
+.tone-purple { background: linear-gradient(135deg, #722ed1, #9254de); }
+.tone-indigo { background: linear-gradient(135deg, #2f54eb, #597ef7); }
+.tone-teal { background: linear-gradient(135deg, #08979c, #13c2c2); }
+.tone-pink { background: linear-gradient(135deg, #eb2f96, #f759ab); }
+.tone-slate { background: linear-gradient(135deg, #595959, #8c8c8c); }
+
+.nav-text {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
 }
 
-.operation-wrapper .line {
-  width: 1px;
-  height: 20px;
-  background: #c0c4cc;
-  margin: 0 10px;
+.nav-name {
+  font-size: 13px;
+  font-weight: 600;
+  color: #303133;
+  line-height: 1.3;
 }
 
-.container {
-  display: flex;
-  min-height: calc(100vh - 280px);
+.nav-item.active .nav-name {
+  color: #1890ff;
 }
-.left-menu {
-  width: 200px;
-  border-right: 1px solid #e4e7ed;
-  flex-shrink: 0; /* 不收缩 */
+
+.nav-desc {
+  font-size: 11px;
+  color: #909399;
+  line-height: 1.3;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
-.right-info {
-  flex: 1;
-  padding: 20px;
-  min-height: 0;
-  overflow-y: auto;
-  max-height: calc(100vh - 280px);
+.nav-arrow {
+  flex-shrink: 0;
+  font-size: 12px;
+  color: #c0c4cc;
+  opacity: 0;
+  transform: translateX(-4px);
+  transition: all 0.18s ease;
 }
 
-.right-tabs {
-  height: 100%;
+.nav-item:hover .nav-arrow,
+.nav-item.active .nav-arrow {
+  opacity: 1;
+  transform: translateX(0);
+  color: #1890ff;
 }
 
+.content-panel {
+  flex: 1;
+  min-width: 0;
+  min-height: 520px;
+  padding: 0 4px 8px;
+  background: transparent;
+  border: none;
+  box-shadow: none;
+  overflow: auto;
+}
+
+@media (max-width: 960px) {
+  .live-body {
+    flex-direction: column;
+  }
+
+  .nav-panel {
+    flex: none;
+    width: 100%;
+    position: static;
+    max-height: none;
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+    gap: 6px;
+  }
+
+  .nav-label {
+    grid-column: 1 / -1;
+  }
+
+  .hero-top {
+    flex-direction: column;
+  }
+}
 </style>

+ 16 - 19
src/views/live/liveConfig/liveCoupon.vue

@@ -1,15 +1,21 @@
 <template>
-  <div class="app-container">
+  <div class="cfg-subpage">
+    <div class="cfg-banner">
+      <div class="cfg-banner-icon green"><i class="el-icon-ticket"></i></div>
+      <div>
+        <div class="cfg-banner-title">优惠券配置</div>
+        <div class="cfg-banner-desc">为直播间添加优惠券,并绑定对应直播商品</div>
+      </div>
+    </div>
+    <div class="cfg-panel">
   <div class="el-container-md">
 
-    <div class="selection-toolbar">
+    <div class="cfg-toolbar selection-toolbar">
       <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
         {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
       </el-checkbox>
-<!--      <el-button  plain size="mini" @click="handleShelf">上架</el-button>-->
-<!--      <el-button  plain size="mini" @click="handleUnshelf">下架</el-button>-->
-      <el-button  plain size="mini" @click="handleDeleteSelected">删除</el-button>
-      <el-button  plain type="mini" icon="el-icon-plus" @click="handleAddLiveCoupon">添加优惠券</el-button>
+      <el-button plain size="mini" @click="handleDeleteSelected">删除</el-button>
+      <el-button type="primary" plain size="mini" icon="el-icon-plus" @click="handleAddLiveCoupon">添加优惠券</el-button>
     </div>
     <el-table
       ref="couponTable"
@@ -226,6 +232,7 @@
     </el-dialog>
   </div>
   </div>
+  </div>
 </template>
 
 <script>
@@ -577,20 +584,10 @@ export default {
 };
 </script>
 
-<style scoped>
-.selection-toolbar {
-  display: flex;
-  align-items: center;
-  margin-bottom: 10px;
-  padding-left: 10px;
-}
+<style scoped lang="scss">
+@import './cfg-common.scss';
 
 .selection-toolbar .el-checkbox {
-  margin-right: 10px;
-}
-
-/* 调整 checkbox 内部输入框的对齐 */
-.selection-toolbar .el-checkbox .el-checkbox__inner {
-  top: 8px; /* 根据实际需求调整 */
+  margin-right: 4px;
 }
 </style>

+ 14 - 4
src/views/live/liveConfig/liveLotteryConf.vue

@@ -1,6 +1,14 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="cfg-subpage">
+    <div class="cfg-banner">
+      <div class="cfg-banner-icon gold"><i class="el-icon-trophy"></i></div>
+      <div>
+        <div class="cfg-banner-title">抽奖配置</div>
+        <div class="cfg-banner-desc">管理直播抽奖活动、奖品与参与规则</div>
+      </div>
+    </div>
+    <div class="cfg-panel">
+    <el-form class="cfg-filter" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
       <el-form-item label="直播间ID" prop="liveId">
         <el-input
           v-model="queryParams.liveId"
@@ -356,6 +364,7 @@
     <el-button type="primary" @click="recordShow = false">关 闭</el-button>
   </span>
     </el-dialog>
+    </div>
   </div>
 </template>
 
@@ -874,7 +883,9 @@ export default {
   }
 };
 </script>
-<style scoped>
+<style scoped lang="scss">
+@import './cfg-common.scss';
+
 .prize-card {
   margin-bottom: 20px;
 }
@@ -893,5 +904,4 @@ export default {
 .dialog-footer {
   text-align: right;
 }
-
 </style>

+ 15 - 2
src/views/live/liveConfig/liveRedConf.vue

@@ -1,6 +1,14 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="cfg-subpage">
+    <div class="cfg-banner">
+      <div class="cfg-banner-icon orange"><i class="el-icon-coin"></i></div>
+      <div>
+        <div class="cfg-banner-title">红包配置</div>
+        <div class="cfg-banner-desc">管理直播间积分红包规则、状态与发放记录</div>
+      </div>
+    </div>
+    <div class="cfg-panel">
+    <el-form class="cfg-filter" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
       <el-form-item label="红包状态" prop="redStatus">
         <el-select v-model="queryParams.redStatus" placeholder="请选择红包状态" clearable size="small">
           <el-option v-for="(item,index) in redStatusOptions" :key="item.dictValue+index" :label="item.dictLabel" :value="item.dictValue" />
@@ -192,6 +200,7 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+    </div>
   </div>
 </template>
 
@@ -516,3 +525,7 @@ export default {
   }
 };
 </script>
+
+<style scoped lang="scss">
+@import './cfg-common.scss';
+</style>

+ 126 - 286
src/views/live/liveConfig/liveReplay.vue

@@ -1,155 +1,105 @@
 <template>
-  <div class="app-container" v-loading.fullscreen.lock="loading">
-    <!-- 直播回放开关区域 -->
-
-    <div class="switch-area">
-      <span class="switch-label">直播回放:</span>
-      <el-switch
-        v-model="replayForm.isPlaybackOpen"
-        @change="handlePlaybackSwitch"
-      />
-      <span class="switch-desc" style="color: #9c9c9c;margin-left: 10px">开启回放,直播结束后学员可查看回放视频</span>
-    </div>
-
-    <!-- 回放模式区域 -->
-    <div class="playback-mode" v-if="replayForm.isPlaybackOpen">
-      <div class="mode-title">回放模式:</div>
-      <div class="mode-option">
-        <el-radio v-model="replayForm.playbackMode" label="1">模式一:以直播间全部直播视频作为回看</el-radio>
+  <div class="cfg-subpage" v-loading.fullscreen.lock="loading">
+    <div class="cfg-banner">
+      <div class="cfg-banner-icon indigo"><i class="el-icon-video-play"></i></div>
+      <div>
+        <div class="cfg-banner-title">回放设置</div>
+        <div class="cfg-banner-desc">配置直播结束后的回放权限、有效期与倍速规则</div>
       </div>
-<!--      <div class="mode-option">-->
-<!--        <el-radio v-model="playbackMode" label="2">模式二:以直播间最后一次直播视频作为回看</el-radio>-->
-<!--      </div>-->
-<!--      <div class="mode-option">-->
-<!--        <el-radio v-model="playbackMode" label="3">模式三:自主选择本直播间或素材库任一视频作为回看</el-radio>-->
-<!--      </div>-->
     </div>
 
-    <!-- 回放有效期区域 -->
-    <div class="validity-period" v-if="replayForm.isPlaybackOpen">
-      <div class="period-title">回放有效期:</div>
-      <div class="period-option">
-        <el-radio @change="updateForm" v-model="replayForm.validityType" label="permanent">永久有效</el-radio>
-      </div>
-      <div class="period-option">
-        <el-radio v-model="replayForm.validityType" label="days">
-          直播结束后
-          <el-input
-            v-model.number="replayForm.validDays"
-            type="number"
-            @change="updateForm"
-            :min="1"
-            placeholder="天数"
-          />
-          天有效
-          <el-tooltip
-            content="以直播实际结束时间为准"
-            placement="top"
-            effect="light"
-          >
-            <i class="el-icon-question" style="color: #999; margin-left: 5px" />
-          </el-tooltip>        </el-radio>
-      </div>
-      <div class="period-desc">
-        到期后,学员无法观看回放内容
+    <div class="cfg-panel">
+      <div class="cfg-switch-row">
+        <span class="cfg-switch-label">直播回放</span>
+        <el-switch v-model="replayForm.isPlaybackOpen" @change="handlePlaybackSwitch" />
+        <span class="cfg-switch-desc">开启后,直播结束学员可查看回放视频</span>
       </div>
 
-      <!-- 倍速播放区域 -->
-      <div class="speed-play">
-        <div class="speed-label">倍速播放/快进:</div>
-        <el-radio @change="updateForm" v-model="replayForm.isSpeedAllowed" label="1">允许</el-radio>
-        <el-radio @change="updateForm" v-model="replayForm.isSpeedAllowed" label="0">禁止</el-radio>
-        <span class="speed-desc">禁止时,课程未学完学员不可倍速播放、拖动进度条</span>
-        <span class="pro-tag">专业版</span>
-      </div>
-    </div>
+      <template v-if="replayForm.isPlaybackOpen">
+        <div class="cfg-section-title">回放模式</div>
+        <div class="mode-block">
+          <el-radio v-model="replayForm.playbackMode" label="1">模式一:以直播间全部直播视频作为回看</el-radio>
+        </div>
 
-    <!-- 回放内容区域 -->
-    <div class="playback-content">
-      <div>
-        <span>回放内容:</span>
-<!--        <el-button-->
-<!--          class="upload-btn"-->
-<!--          type="text"-->
-<!--          icon="el-icon-plus"-->
-<!--          @click="handleUploadVideo"-->
-<!--        >上传视频</el-button>-->
-<!--        <span class="upload-tip">上传视频大小不可超过5GB</span>-->
-      </div>
+        <div class="cfg-section-title">回放有效期</div>
+        <div class="mode-block">
+          <div class="period-option">
+            <el-radio @change="updateForm" v-model="replayForm.validityType" label="permanent">永久有效</el-radio>
+          </div>
+          <div class="period-option">
+            <el-radio v-model="replayForm.validityType" label="days">
+              直播结束后
+              <el-input
+                v-model.number="replayForm.validDays"
+                type="number"
+                @change="updateForm"
+                :min="1"
+                class="day-input"
+                placeholder="天数"
+              />
+              天有效
+              <el-tooltip content="以直播实际结束时间为准" placement="top" effect="light">
+                <i class="el-icon-question tip-icon" />
+              </el-tooltip>
+            </el-radio>
+          </div>
+          <div class="period-desc">到期后,学员无法观看回放内容</div>
+        </div>
+
+        <div class="cfg-section-title">倍速播放 / 快进</div>
+        <div class="mode-block speed-row">
+          <el-radio @change="updateForm" v-model="replayForm.isSpeedAllowed" label="1">允许</el-radio>
+          <el-radio @change="updateForm" v-model="replayForm.isSpeedAllowed" label="0">禁止</el-radio>
+          <span class="cfg-switch-desc">禁止时,课程未学完学员不可倍速播放、拖动进度条</span>
+        </div>
+      </template>
+    </div>
 
-      <!-- 视频列表 -->
-      <el-table
-        :data="videoList"
-        border
-        style="width: 100%; margin-top: 10px"
-      >
-        <el-table-column prop="videoName" label="视频名称">
-          <template slot-scope="scope">
-            <div class="video-item">
-              <div class="video-info">
-                <div class="video-name">{{ scope.row.videoName }}</div>
-              </div>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column prop="duration" label="时长" />
-        <el-table-column prop="status" label="状态">
-          <template slot-scope="scope">
-            <span v-if="scope.row.status === '回放中'">
-              <span class="status-dot" />{{ scope.row.status }}
-            </span>
-            <span v-else>{{ scope.row.status }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="updateTime" label="更新时间" />
-        <el-table-column label="视频地址" prop="videoUrl" >
-          <template slot-scope="scope">
-            <el-tooltip
-              :content="scope.row.videoUrl"
-              placement="top"
-              effect="dark"
-            >
-              <a
-                :href="scope.row.videoUrl"
-                target="_blank"
-                class="video-url-container"
-                rel="noopener noreferrer"
+    <div class="cfg-panel">
+      <div class="cfg-section-title">回放内容</div>
+      <div class="cfg-table-wrap">
+        <el-table :data="videoList" style="width: 100%">
+          <el-table-column prop="videoName" label="视频名称">
+            <template slot-scope="scope">
+              <div class="video-name">{{ scope.row.videoName }}</div>
+            </template>
+          </el-table-column>
+          <el-table-column prop="duration" label="时长" width="100" />
+          <el-table-column prop="status" label="状态" width="120">
+            <template slot-scope="scope">
+              <span v-if="scope.row.status === '回放中'" class="status-living">
+                <span class="status-dot" />{{ scope.row.status }}
+              </span>
+              <span v-else>{{ scope.row.status }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="updateTime" label="更新时间" width="180" />
+          <el-table-column label="视频地址" prop="videoUrl" min-width="200">
+            <template slot-scope="scope">
+              <el-tooltip
+                v-if="scope.row.videoUrl"
+                :content="scope.row.videoUrl"
+                placement="top"
+                effect="dark"
               >
-                {{
-                  scope.row.videoUrl.length > 32
-                    ? scope.row.videoUrl.substring(0, 32) + '...'
-                    : scope.row.videoUrl
-                }}
-                <i class="el-icon-external-link video-url-icon"></i>
-              </a>
-            </el-tooltip>
-          </template>
-        </el-table-column>
-<!--        <el-table-column label="操作">-->
-<!--          <template slot-scope="scope">-->
-<!--&lt;!&ndash;            <el-button&ndash;&gt;-->
-<!--&lt;!&ndash;              type="text"&ndash;&gt;-->
-<!--&lt;!&ndash;              size="small"&ndash;&gt;-->
-<!--&lt;!&ndash;              @click="handleCut(scope.row)"&ndash;&gt;-->
-<!--&lt;!&ndash;            >剪切</el-button>&ndash;&gt;-->
-<!--&lt;!&ndash;            <el-button&ndash;&gt;-->
-<!--&lt;!&ndash;              type="text"&ndash;&gt;-->
-<!--&lt;!&ndash;              size="small"&ndash;&gt;-->
-<!--&lt;!&ndash;              @click="handleGenerateAiSummary(scope.row)"&ndash;&gt;-->
-<!--&lt;!&ndash;            >生成AI速览</el-button>&ndash;&gt;-->
-<!--&lt;!&ndash;            <el-button&ndash;&gt;-->
-<!--&lt;!&ndash;              type="text"&ndash;&gt;-->
-<!--&lt;!&ndash;              size="small"&ndash;&gt;-->
-<!--&lt;!&ndash;              @click="handleGenerateSubtitle(scope.row)"&ndash;&gt;-->
-<!--&lt;!&ndash;            >生成字幕</el-button>&ndash;&gt;-->
-<!--&lt;!&ndash;            <el-button&ndash;&gt;-->
-<!--&lt;!&ndash;              type="text"&ndash;&gt;-->
-<!--&lt;!&ndash;              size="small"&ndash;&gt;-->
-<!--&lt;!&ndash;              @click="handlePlaybackSegment(scope.row)"&ndash;&gt;-->
-<!--&lt;!&ndash;            >回放片段</el-button>&ndash;&gt;-->
-<!--          </template>-->
-<!--        </el-table-column>-->
-      </el-table>
+                <a
+                  :href="scope.row.videoUrl"
+                  target="_blank"
+                  class="video-url-container"
+                  rel="noopener noreferrer"
+                >
+                  {{
+                    scope.row.videoUrl.length > 32
+                      ? scope.row.videoUrl.substring(0, 32) + '...'
+                      : scope.row.videoUrl
+                  }}
+                  <i class="el-icon-external-link"></i>
+                </a>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
     </div>
   </div>
 </template>
@@ -352,176 +302,66 @@ export default {
 };
 </script>
 
-<style scoped>
-.live-playback-setting {
-  font-family: "Microsoft Yahei", sans-serif;
-  color: #333;
-  background-color: #fff;
-  padding: 20px;
-  border-radius: 4px;
-  box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
-}
+<style scoped lang="scss">
+@import './cfg-common.scss';
 
-.switch-area {
-  display: flex;
-  align-items: center;
-  margin-bottom: 20px;
-}
-.switch-area .switch-label {
-  margin-right: 10px;
-  font-size: 14px;
-}
-.switch-area .switch-desc {
-  color: #999;
-  font-size: 12px;
+.mode-block {
+  padding: 12px 14px 16px;
+  margin-bottom: 16px;
+  background: #f8fafc;
+  border: 1px solid #eef2f7;
+  border-radius: 10px;
 }
 
-.playback-mode {
-  background-color: #f9fafc;
-  padding: 15px;
-  border-radius: 4px;
-  margin-bottom: 20px;
-}
-.playback-mode .mode-title {
-  font-size: 14px;
+.period-option {
   margin-bottom: 10px;
 }
-.playback-mode .mode-option {
-  display: flex;
-  align-items: center;
-  margin-bottom: 8px;
-}
-.playback-mode .mode-option .el-radio {
-  margin-left: 2%;
-}
-.playback-mode .mode-option label {
-  font-size: 13px;
-}
 
-.validity-period {
-  background-color: #f9fafc;
-  padding: 15px;
-  border-radius: 4px;
-  margin-bottom: 20px;
-}
-.validity-period .period-title {
-  font-size: 14px;
-  margin-bottom: 10px;
-}
-.validity-period .period-option {
-  display: flex;
-  align-items: center;
-  margin-bottom: 8px;
-}
-.validity-period .period-option .el-radio {
-  margin-left: 2%;
-}
-.validity-period .period-option label {
-  font-size: 13px;
-}
-.validity-period .period-desc {
-  color: #999;
-  font-size: 12px;
-  margin-top: 5px;
-  margin-left: 2%;
-}
-.validity-period .day-input {
-  width: 60px;
-  height: 28px;
-  border: 1px solid #ddd;
-  border-radius: 4px;
-  padding: 0 8px;
-  margin: 0 5px;
+.day-input {
+  width: 80px;
+  margin: 0 6px;
 }
 
-.speed-play {
-  display: flex;
-  align-items: center;
-  margin-top: 15px;
-}
-.speed-play .speed-label {
-  font-size: 13px;
-  margin-right: 10px;
-}
-.speed-play .el-radio {
-  margin-right: 5px;
-}
-.speed-play .speed-desc {
+.tip-icon {
   color: #999;
-  font-size: 12px;
-  margin-left: 10px;
-}
-
-.pro-tag {
-  display: inline-block;
-  background-color: #007bff;
-  color: #fff;
-  font-size: 10px;
-  padding: 2px 5px;
-  border-radius: 3px;
   margin-left: 5px;
-  vertical-align: middle;
 }
 
-.playback-content {
-  margin-top: 20px;
-}
-.playback-content .upload-btn {
-  display: inline-block;
-  background-color: #fff;
-  border: 1px dashed #ddd;
-  color: #007bff;
-  padding: 8px 15px;
-  border-radius: 4px;
-  cursor: pointer;
-  font-size: 13px;
-  margin-bottom: 15px;
-}
-.playback-content .upload-btn:hover {
-  border-color: #007bff;
-}
-.playback-content .upload-tip {
+.period-desc {
+  color: #909399;
   font-size: 12px;
-  color: #999;
-  margin-left: 10px;
+  margin-top: 4px;
 }
 
-.video-item {
+.speed-row {
   display: flex;
   align-items: center;
+  flex-wrap: wrap;
+  gap: 8px;
 }
-.video-cover {
-  width: 80px;
-  height: 45px;
-  border-radius: 4px;
-  margin-right: 10px;
-}
-.video-info .video-name {
+
+.video-name {
   font-size: 13px;
-  margin-bottom: 3px;
+  color: #303133;
 }
-.video-info .video-desc {
-  font-size: 12px;
-  color: #999;
-}
-.video-duration,
-.video-source,
-.video-status,
-.video-update-time {
-  color: #666;
+
+.status-living {
+  color: #1890ff;
 }
-.video-status .status-dot {
+
+.status-dot {
   display: inline-block;
   width: 8px;
   height: 8px;
-  background-color: #007bff;
+  background-color: #1890ff;
   border-radius: 50%;
   margin-right: 5px;
   vertical-align: middle;
 }
-.video-operation .el-button {
-  color: #007bff;
-  font-size: 12px;
-  margin-right: 10px;
+
+.video-url-container {
+  cursor: pointer;
+  color: #1890ff;
+  white-space: nowrap;
 }
 </style>

+ 49 - 221
src/views/live/liveConfig/preview.vue

@@ -1,27 +1,26 @@
 <template>
-  <div class="app-container" v-loading.fullscreen.lock="loading">
-    <!-- 直播回放开关区域 -->
-
-    <!-- 回放内容区域 -->
-    <div class="playback-content">
+  <div class="cfg-subpage" v-loading.fullscreen.lock="loading">
+    <div class="cfg-banner">
+      <div class="cfg-banner-icon cyan"><i class="el-icon-bell"></i></div>
       <div>
-        <span>预告内容:</span>
-        <el-button class="upload-btn" type="normal" @click="handleUploadVideo">上传视频</el-button>
-        <!--        <el-button-->
-        <!--          class="upload-btn"-->
-        <!--          type="text"-->
-        <!--          icon="el-icon-plus"-->
-        <!--          @click="handleUploadVideo"-->
-        <!--        >上传视频</el-button>-->
-        <!--        <span class="upload-tip">上传视频大小不可超过5GB</span>-->
+        <div class="cfg-banner-title">直播预告</div>
+        <div class="cfg-banner-desc">上传预告视频,开播前向用户展示预热内容</div>
+      </div>
+    </div>
+
+    <div class="cfg-panel">
+      <div class="cfg-section-title">预告内容</div>
+      <div class="cfg-toolbar">
+        <el-button type="primary" plain icon="el-icon-upload2" size="mini" @click="handleUploadVideo">上传视频</el-button>
       </div>
+
       <el-dialog title="添加直播预告" :visible.sync="open" width="900px" append-to-body>
-        <el-form ref="form" :model="form"  label-width="80px">
+        <el-form ref="form" :model="form" label-width="80px">
           <video-upload
-            :type = "1"
-            :isPrivate = "isPrivate"
-            :fileKey.sync = "form.fileKey"
-            :fileSize.sync = "form.fileSize"
+            :type="1"
+            :isPrivate="isPrivate"
+            :fileKey.sync="form.fileKey"
+            :fileSize.sync="form.fileSize"
             :videoUrl.sync="videoUrl"
             :fileName.sync="form.fileName"
             :line_2.sync="form.lineTwo"
@@ -42,38 +41,35 @@
         </div>
       </el-dialog>
 
-      <!-- 视频列表 -->
-      <el-table
-        :data="videoList"
-        border
-        style="width: 100%; margin-top: 10px"
-      >
-        <el-table-column prop="duration" label="时长" />
-        <el-table-column prop="updateTime" label="更新时间" />
-        <el-table-column label="视频地址(可点击查看)" prop="videoUrl" >
-          <template slot-scope="scope">
-            <el-tooltip
-              :content="scope.row.videoUrl"
-              placement="top"
-              effect="dark"
-            >
-              <a
-                :href="scope.row.videoUrl"
-                target="_blank"
-                class="video-url-container"
-                rel="noopener noreferrer"
+      <div class="cfg-table-wrap">
+        <el-table :data="videoList" style="width: 100%">
+          <el-table-column prop="duration" label="时长" width="120" />
+          <el-table-column prop="updateTime" label="更新时间" width="180" />
+          <el-table-column label="视频地址(可点击查看)" prop="videoUrl">
+            <template slot-scope="scope">
+              <el-tooltip
+                :content="scope.row.videoUrl"
+                placement="top"
+                effect="dark"
               >
-                {{
-                  scope.row.videoUrl.length > 32
-                    ? scope.row.videoUrl.substring(0, 32) + '...'
-                    : scope.row.videoUrl
-                }}
-                <i class="el-icon-external-link video-url-icon"></i>
-              </a>
-            </el-tooltip>
-          </template>
-        </el-table-column>
-      </el-table>
+                <a
+                  :href="scope.row.videoUrl"
+                  target="_blank"
+                  class="video-url-container"
+                  rel="noopener noreferrer"
+                >
+                  {{
+                    scope.row.videoUrl && scope.row.videoUrl.length > 32
+                      ? scope.row.videoUrl.substring(0, 32) + '...'
+                      : scope.row.videoUrl
+                  }}
+                  <i class="el-icon-external-link video-url-icon"></i>
+                </a>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
     </div>
   </div>
 </template>
@@ -274,180 +270,12 @@ export default {
 };
 </script>
 
-<style scoped>
-.live-playback-setting {
-  font-family: "Microsoft Yahei", sans-serif;
-  color: #333;
-  background-color: #fff;
-  padding: 20px;
-  border-radius: 4px;
-  box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
-}
-
-.switch-area {
-  display: flex;
-  align-items: center;
-  margin-bottom: 20px;
-}
-.switch-area .switch-label {
-  margin-right: 10px;
-  font-size: 14px;
-}
-.switch-area .switch-desc {
-  color: #999;
-  font-size: 12px;
-}
+<style scoped lang="scss">
+@import './cfg-common.scss';
 
-.playback-mode {
-  background-color: #f9fafc;
-  padding: 15px;
-  border-radius: 4px;
-  margin-bottom: 20px;
-}
-.playback-mode .mode-title {
-  font-size: 14px;
-  margin-bottom: 10px;
-}
-.playback-mode .mode-option {
-  display: flex;
-  align-items: center;
-  margin-bottom: 8px;
-}
-.playback-mode .mode-option .el-radio {
-  margin-left: 2%;
-}
-.playback-mode .mode-option label {
-  font-size: 13px;
-}
-
-.validity-period {
-  background-color: #f9fafc;
-  padding: 15px;
-  border-radius: 4px;
-  margin-bottom: 20px;
-}
-.validity-period .period-title {
-  font-size: 14px;
-  margin-bottom: 10px;
-}
-.validity-period .period-option {
-  display: flex;
-  align-items: center;
-  margin-bottom: 8px;
-}
-.validity-period .period-option .el-radio {
-  margin-left: 2%;
-}
-.validity-period .period-option label {
-  font-size: 13px;
-}
-.validity-period .period-desc {
-  color: #999;
-  font-size: 12px;
-  margin-top: 5px;
-  margin-left: 2%;
-}
-.validity-period .day-input {
-  width: 60px;
-  height: 28px;
-  border: 1px solid #ddd;
-  border-radius: 4px;
-  padding: 0 8px;
-  margin: 0 5px;
-}
-
-.speed-play {
-  display: flex;
-  align-items: center;
-  margin-top: 15px;
-}
-.speed-play .speed-label {
-  font-size: 13px;
-  margin-right: 10px;
-}
-.speed-play .el-radio {
-  margin-right: 5px;
-}
-.speed-play .speed-desc {
-  color: #999;
-  font-size: 12px;
-  margin-left: 10px;
-}
-
-.pro-tag {
-  display: inline-block;
-  background-color: #007bff;
-  color: #fff;
-  font-size: 10px;
-  padding: 2px 5px;
-  border-radius: 3px;
-  margin-left: 5px;
-  vertical-align: middle;
-}
-
-.playback-content {
-  margin-top: 20px;
-}
-.playback-content .upload-btn {
-  display: inline-block;
-  background-color: #fff;
-  border: 1px dashed #ddd;
-  color: #007bff;
-  padding: 8px 15px;
-  border-radius: 4px;
-  cursor: pointer;
-  font-size: 13px;
-  margin-bottom: 15px;
-}
-.playback-content .upload-btn:hover {
-  border-color: #007bff;
-}
-.playback-content .upload-tip {
-  font-size: 12px;
-  color: #999;
-  margin-left: 10px;
-}
-
-.video-item {
-  display: flex;
-  align-items: center;
-}
-.video-cover {
-  width: 80px;
-  height: 45px;
-  border-radius: 4px;
-  margin-right: 10px;
-}
-.video-info .video-name {
-  font-size: 13px;
-  margin-bottom: 3px;
-}
-.video-info .video-desc {
-  font-size: 12px;
-  color: #999;
-}
-.video-duration,
-.video-source,
-.video-status,
-.video-update-time {
-  color: #666;
-}
-.video-status .status-dot {
-  display: inline-block;
-  width: 8px;
-  height: 8px;
-  background-color: #007bff;
-  border-radius: 50%;
-  margin-right: 5px;
-  vertical-align: middle;
-}
-.video-operation .el-button {
-  color: #007bff;
-  font-size: 12px;
-  margin-right: 10px;
-}
 .video-url-container {
   cursor: pointer;
+  color: #1890ff;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;

+ 14 - 3
src/views/live/liveConfig/task.vue

@@ -1,6 +1,14 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="cfg-subpage">
+    <div class="cfg-banner">
+      <div class="cfg-banner-icon teal"><i class="el-icon-s-operation"></i></div>
+      <div>
+        <div class="cfg-banner-title">运营自动化</div>
+        <div class="cfg-banner-desc">配置定时推送商品、红包、抽奖与优惠券等自动化任务</div>
+      </div>
+    </div>
+    <div class="cfg-panel">
+    <el-form class="cfg-filter" :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
       <el-form-item label="直播间ID" prop="liveId">
         <el-input
           v-model="queryParams.liveId"
@@ -265,6 +273,7 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+    </div>
   </div>
 </template>
 
@@ -836,7 +845,9 @@ export default {
 };
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+@import './cfg-common.scss';
+
 .loading-indicator {
   padding: 10px;
   text-align: center;

ファイルの差分が大きいため隠しています
+ 472 - 448
src/views/live/liveConfig/watchReward.vue


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません