|
|
@@ -2,259 +2,304 @@
|
|
|
<div class="console">
|
|
|
|
|
|
<div class="left-panel">
|
|
|
- <h2>学员列表</h2>
|
|
|
- <div class="search">
|
|
|
- <input type="text" placeholder="搜索用户昵称" v-model="searchKeyword">
|
|
|
- <button @click="searchUsers()">搜索</button>
|
|
|
+ <h2 class="panel-title">学员列表</h2>
|
|
|
+ <div class="search-bar">
|
|
|
+ <el-input
|
|
|
+ v-model="searchKeyword"
|
|
|
+ placeholder="搜索用户昵称"
|
|
|
+ size="small"
|
|
|
+ clearable
|
|
|
+ @keyup.enter.native="searchUsers"
|
|
|
+ />
|
|
|
+ <el-button type="primary" size="small" @click="searchUsers">搜索</el-button>
|
|
|
</div>
|
|
|
- <el-tabs class="live-console-tab-right" v-model="tabLeft.activeName" @tab-click="handleUserClick" :stretch="true">
|
|
|
+ <el-tabs class="console-tabs" v-model="tabLeft.activeName" @tab-click="handleUserClick" :stretch="true">
|
|
|
<el-tab-pane :label="alLabel" name="al">
|
|
|
- <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_al" style="height: 800px; width: 100%;">
|
|
|
- <el-row class='scrollbar-demo-item' v-for="u in alDisplayList" :key="u.userId">
|
|
|
- <el-col :span="20">
|
|
|
- <el-row type="flex" align="middle">
|
|
|
- <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
|
|
|
- <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
|
|
|
- <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
|
|
|
- </el-row>
|
|
|
- </el-col>
|
|
|
- <el-col :span="4" >
|
|
|
- <el-popover
|
|
|
- width="100"
|
|
|
- trigger="click">
|
|
|
- <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
- <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
|
|
|
- <i class="el-icon-more" slot="reference"></i>
|
|
|
- </el-popover>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
+ <el-scrollbar class="panel-scroll" ref="manageLeftRef_al">
|
|
|
+ <div class="user-list-item" v-for="u in alDisplayList" :key="u.userId">
|
|
|
+ <el-avatar :size="36" :src="u.avatar" class="user-avatar" />
|
|
|
+ <div class="user-meta">
|
|
|
+ <div class="user-name">{{ u.nickName }}</div>
|
|
|
+ <div class="user-id">{{ u.userId }}</div>
|
|
|
+ </div>
|
|
|
+ <el-popover width="120" trigger="click" popper-class="user-action-popover">
|
|
|
+ <a class="action-link" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
+ <a class="action-link" @click="blockUser(u)">拉黑</a>
|
|
|
+ <i class="el-icon-more user-more-btn" slot="reference"></i>
|
|
|
+ </el-popover>
|
|
|
+ </div>
|
|
|
</el-scrollbar>
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane :label="onlineLabel" name="online">
|
|
|
- <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_online" style="height: 800px; width: 100%;">
|
|
|
- <el-row class='scrollbar-demo-item' v-for="u in onlineDisplayList" :key="u.userId">
|
|
|
- <el-col :span="20">
|
|
|
- <el-row type="flex" align="middle">
|
|
|
- <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
|
|
|
- <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
|
|
|
- <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
|
|
|
- </el-row>
|
|
|
- </el-col>
|
|
|
- <el-col :span="4" >
|
|
|
- <el-popover
|
|
|
- width="100"
|
|
|
- trigger="click">
|
|
|
- <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
- <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
|
|
|
- <i class="el-icon-more" slot="reference"></i>
|
|
|
- </el-popover>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
+ <el-scrollbar class="panel-scroll" ref="manageLeftRef_online">
|
|
|
+ <div class="user-list-item" v-for="u in onlineDisplayList" :key="u.userId">
|
|
|
+ <el-avatar :size="36" :src="u.avatar" class="user-avatar" />
|
|
|
+ <div class="user-meta">
|
|
|
+ <div class="user-name">{{ u.nickName }}</div>
|
|
|
+ <div class="user-id">{{ u.userId }}</div>
|
|
|
+ </div>
|
|
|
+ <el-popover width="120" trigger="click" popper-class="user-action-popover">
|
|
|
+ <a class="action-link" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
+ <a class="action-link" @click="blockUser(u)">拉黑</a>
|
|
|
+ <i class="el-icon-more user-more-btn" slot="reference"></i>
|
|
|
+ </el-popover>
|
|
|
+ </div>
|
|
|
</el-scrollbar>
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane :label="offlineLabel" name="offline">
|
|
|
- <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_offline" style="height: 800px; width: 100%;">
|
|
|
- <el-row class='scrollbar-demo-item' v-for="u in offlineDisplayList" :key="u.userId">
|
|
|
- <el-col :span="20">
|
|
|
- <el-row type="flex" align="middle">
|
|
|
- <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
|
|
|
- <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
|
|
|
- <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
|
|
|
- </el-row>
|
|
|
- </el-col>
|
|
|
- <el-col :span="4" >
|
|
|
- <el-popover
|
|
|
- width="100"
|
|
|
- trigger="click">
|
|
|
- <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
- <i class="el-icon-more" slot="reference"></i>
|
|
|
- <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
|
|
|
- </el-popover>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
+ <el-scrollbar class="panel-scroll" ref="manageLeftRef_offline">
|
|
|
+ <div class="user-list-item" v-for="u in offlineDisplayList" :key="u.userId">
|
|
|
+ <el-avatar :size="36" :src="u.avatar" class="user-avatar" />
|
|
|
+ <div class="user-meta">
|
|
|
+ <div class="user-name">{{ u.nickName }}</div>
|
|
|
+ <div class="user-id">{{ u.userId }}</div>
|
|
|
+ </div>
|
|
|
+ <el-popover width="120" trigger="click" popper-class="user-action-popover">
|
|
|
+ <a class="action-link" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
+ <a class="action-link" @click="blockUser(u)">拉黑</a>
|
|
|
+ <i class="el-icon-more user-more-btn" slot="reference"></i>
|
|
|
+ </el-popover>
|
|
|
+ </div>
|
|
|
</el-scrollbar>
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane :label="silencedUserLabel" name="silenced">
|
|
|
- <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_silenced" style="height: 800px; width: 100%;">
|
|
|
- <el-row class='scrollbar-demo-item' v-for="u in silencedDisplayList" :key="u.userId">
|
|
|
- <el-col :span="20">
|
|
|
- <el-row type="flex" align="middle">
|
|
|
- <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
|
|
|
- <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
|
|
|
- <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
|
|
|
- </el-row>
|
|
|
- </el-col>
|
|
|
- <el-col :span="4" >
|
|
|
- <el-popover
|
|
|
- width="100"
|
|
|
- trigger="click">
|
|
|
- <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
- <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
|
|
|
- <i class="el-icon-more" slot="reference"></i>
|
|
|
- </el-popover>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
+ <el-scrollbar class="panel-scroll" ref="manageLeftRef_silenced">
|
|
|
+ <div class="user-list-item" v-for="u in silencedDisplayList" :key="u.userId">
|
|
|
+ <el-avatar :size="36" :src="u.avatar" class="user-avatar" />
|
|
|
+ <div class="user-meta">
|
|
|
+ <div class="user-name">{{ u.nickName }}</div>
|
|
|
+ <div class="user-id">{{ u.userId }}</div>
|
|
|
+ </div>
|
|
|
+ <el-popover width="120" trigger="click" popper-class="user-action-popover">
|
|
|
+ <a class="action-link" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
+ <a class="action-link" @click="blockUser(u)">拉黑</a>
|
|
|
+ <i class="el-icon-more user-more-btn" slot="reference"></i>
|
|
|
+ </el-popover>
|
|
|
+ </div>
|
|
|
</el-scrollbar>
|
|
|
</el-tab-pane>
|
|
|
</el-tabs>
|
|
|
</div>
|
|
|
|
|
|
<div class="middle-panel">
|
|
|
- <h2>消息管理</h2>
|
|
|
-
|
|
|
+ <h2 class="panel-title">消息管理</h2>
|
|
|
|
|
|
- <div class="discussion-messages">
|
|
|
- <h3>讨论区消息</h3>
|
|
|
- <div class="message-settings">
|
|
|
- <label>
|
|
|
- <input type="checkbox" v-model="globalVisible" @change="globalVisibleChange">
|
|
|
- 全局用户自见
|
|
|
- </label>
|
|
|
+ <div class="panel-card discussion-messages">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3 class="section-title">讨论区消息</h3>
|
|
|
+ <el-checkbox v-model="globalVisible" @change="globalVisibleChange">全局用户自见</el-checkbox>
|
|
|
</div>
|
|
|
<div class="message-container" @click="handleMessageBoxClick">
|
|
|
- <el-scrollbar class="custom-scrollbar" style="height: 500px; width: 100%;" ref="manageRightRef">
|
|
|
- <el-row v-for="m in msgList" :key="m.msgId">
|
|
|
- <el-row v-if="m.userId === userId && m.msgId == null" style="padding: 8px 0" type="flex" align="top" justify="end">
|
|
|
- <div style="display: flex;justify-content: flex-end">
|
|
|
- <div style="display: flex;justify-content: flex-end;flex-direction: column;max-width: 200px;align-items: flex-end">
|
|
|
- <div style="font-size: 12px; color: #999; margin-bottom: 3px;">{{ m.nickName }}</div>
|
|
|
- <div style="white-space: normal; word-wrap: break-word;width: 100%; background-color: #e6f7ff; padding: 8px; border-radius: 5px;font-size: 14px;">{{ m.msg }}</div>
|
|
|
+ <el-scrollbar class="msg-scroll" ref="manageRightRef">
|
|
|
+ <div v-for="m in msgList" :key="m.msgId" class="msg-item">
|
|
|
+ <div v-if="m.userId === userId && m.msgId == null" class="msg-row msg-row--self">
|
|
|
+ <div class="msg-content msg-content--self">
|
|
|
+ <div class="msg-nickname">{{ m.nickName }}</div>
|
|
|
+ <div class="msg-bubble msg-bubble--self">{{ m.msg }}</div>
|
|
|
</div>
|
|
|
- <el-avatar :src="m.avatar" style="margin-left: 10px; margin-right: 10px;"/>
|
|
|
+ <el-avatar :size="32" :src="m.avatar" class="msg-avatar" />
|
|
|
</div>
|
|
|
- </el-row>
|
|
|
- <el-row v-else style="margin-top: 5px" type="flex" align="top" >
|
|
|
- <el-col :span="3" style="margin-left: 10px"><el-avatar :src="m.avatar"/></el-col>
|
|
|
- <el-col :span="15">
|
|
|
- <el-row style="margin-left: 10px">
|
|
|
- <el-col><div style="font-size: 12px; color: #999; margin-bottom: 3px;">{{ m.nickName }}</div></el-col>
|
|
|
- <el-col :span="24" style="max-width: 200px;">
|
|
|
- <div style="white-space: normal; word-wrap: break-word;background-color: #f0f2f5; padding: 8px; border-radius: 5px;font-size: 14px;width: 100%;">
|
|
|
- {{ m.msg }}
|
|
|
- </div>
|
|
|
- </el-col>
|
|
|
- <el-col>
|
|
|
- <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
- <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="blockUser(m)">拉黑</a>
|
|
|
- <a v-if="m.singleVisible === 1" style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="singleVisible(m)">解除用户自见</a>
|
|
|
- <a v-else style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="singleVisible(m)">用户自见</a>
|
|
|
- <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="deleteMsg(m)">删除</a>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- </el-row>
|
|
|
- <!-- 底部留白 -->
|
|
|
- <div style="height: 20px;"></div>
|
|
|
+ <div v-else class="msg-row">
|
|
|
+ <el-avatar :size="32" :src="m.avatar" class="msg-avatar" />
|
|
|
+ <div class="msg-content">
|
|
|
+ <div class="msg-nickname">{{ m.nickName }}</div>
|
|
|
+ <div class="msg-bubble">{{ m.msg }}</div>
|
|
|
+ <div class="msg-actions">
|
|
|
+ <a class="action-link" @click.stop="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
|
|
|
+ <a class="action-link" @click.stop="blockUser(m)">拉黑</a>
|
|
|
+ <a class="action-link" @click.stop="singleVisible(m)">{{ m.singleVisible === 1 ? '解除用户自见' : '用户自见' }}</a>
|
|
|
+ <a class="action-link" @click.stop="deleteMsg(m)">删除</a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="scroll-bottom-space"></div>
|
|
|
</el-scrollbar>
|
|
|
- <!-- 加载最新消息按钮 -->
|
|
|
<el-button
|
|
|
v-if="showLoadLatestBtn"
|
|
|
class="load-latest-btn"
|
|
|
type="primary"
|
|
|
- size="small"
|
|
|
+ size="mini"
|
|
|
@click.stop="loadLatestMessages"
|
|
|
:disabled="isLoadingLatest"
|
|
|
:loading="isLoadingLatest"
|
|
|
- icon="el-icon-refresh">
|
|
|
- 加载最新消息
|
|
|
- </el-button>
|
|
|
+ icon="el-icon-refresh"
|
|
|
+ >加载最新消息</el-button>
|
|
|
</div>
|
|
|
- <!-- <div class="message-list">-->
|
|
|
- <!-- <div class="message-item" v-for="msg in msgList" :key="msg.id">-->
|
|
|
- <!-- <div class="message-avatar">-->
|
|
|
- <!-- <img :src="msg.avatar" alt="用户头像">-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- <div class="message-content">-->
|
|
|
- <!-- <div class="message-user">{{ msg.user }}</div>-->
|
|
|
- <!-- <div class="message-text">{{ msg.text }}</div>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- <div class="message-actions">-->
|
|
|
- <!--<!– <button @click="toggleVisible(msg)">–>-->
|
|
|
- <!--<!– {{ msg.isVisible ? '仅用户自见' : '全局可见' }}–>-->
|
|
|
- <!--<!– </button>–>-->
|
|
|
- <!-- <button @click="deleteMessage(msg)">删除</button>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </div>-->
|
|
|
- <!-- </div>-->
|
|
|
</div>
|
|
|
- <div class="system-messages">
|
|
|
- <h3>系统消息</h3>
|
|
|
- <textarea placeholder="输入系统消息" v-model="newMsg"></textarea>
|
|
|
- <div class="message-actions">
|
|
|
- <button @click="sendMessage">发送消息</button>
|
|
|
- <button @click="sendPopMessage">弹窗消息</button>
|
|
|
- <button @click="showTopMsgDialog">顶部消息</button>
|
|
|
- </div>
|
|
|
+
|
|
|
+ <div class="panel-card ops-panel">
|
|
|
+ <el-tabs v-model="opsTabActive" class="ops-tabs">
|
|
|
+ <el-tab-pane label="营销" name="marketing">
|
|
|
+ <div class="marketing-panel" v-loading="marketingLoading">
|
|
|
+ <div class="marketing-toolbar">
|
|
|
+ <span class="toolbar-label">显示购物车</span>
|
|
|
+ <el-switch v-model="showCart" @change="showCartChange" />
|
|
|
+ <el-alert
|
|
|
+ class="marketing-tip"
|
|
|
+ title="置顶商品弹窗优先展示时,其他同位置弹窗将不再展示"
|
|
|
+ type="info"
|
|
|
+ :closable="false"
|
|
|
+ show-icon
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <el-radio-group v-model="marketingCategory" size="mini" class="marketing-categories" @change="clearMarketingSelection">
|
|
|
+ <el-radio-button v-for="cat in marketingCategories" :key="cat.key" :label="cat.key">{{ cat.label }}</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ <el-scrollbar class="marketing-scroll">
|
|
|
+ <!-- 商品:图片卡片 -->
|
|
|
+ <div class="marketing-cards" v-if="marketingCategory === 'goods' && currentMarketingList.length">
|
|
|
+ <div
|
|
|
+ v-for="item in currentMarketingList"
|
|
|
+ :key="getMarketingItemKey(item)"
|
|
|
+ class="marketing-card"
|
|
|
+ :class="{ selected: isMarketingItemSelected(item) }"
|
|
|
+ @click="selectMarketingItem(item)"
|
|
|
+ >
|
|
|
+ <div class="card-image-wrap">
|
|
|
+ <img :src="item.imgUrl || item.image" class="card-image" />
|
|
|
+ <div class="card-tags">
|
|
|
+ <el-tag v-if="item.isShow" type="warning" size="mini" effect="dark">展示中</el-tag>
|
|
|
+ <el-tag v-if="item.status == 1" type="success" size="mini" effect="dark">已上架</el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="card-overlay">
|
|
|
+ <el-button size="mini" round :disabled="item.status == 1" @click.stop="handleGoodsShelf(item, 1)">上架</el-button>
|
|
|
+ <el-button size="mini" type="danger" round :disabled="item.status == 0" @click.stop="handleGoodsShelf(item, 0)">下架</el-button>
|
|
|
+ <el-button size="mini" type="primary" round @click.stop="handleGoodsShowToggle(item)">{{ item.isShow ? '收起' : '展示' }}</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="card-info">
|
|
|
+ <div class="card-title" :title="item.productName">{{ item.productName }}</div>
|
|
|
+ <div class="card-bottom">
|
|
|
+ <span class="card-price" v-if="item.price">¥{{ item.price }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 优惠券 / 核销券 / 红包 / 抽奖:列表 -->
|
|
|
+ <div class="marketing-list" v-else-if="marketingCategory !== 'goods' && currentMarketingList.length">
|
|
|
+ <div
|
|
|
+ v-for="item in currentMarketingList"
|
|
|
+ :key="getMarketingItemKey(item)"
|
|
|
+ class="marketing-list-item"
|
|
|
+ :class="{ selected: isMarketingItemSelected(item) }"
|
|
|
+ @click="selectMarketingItem(item)"
|
|
|
+ >
|
|
|
+ <div class="list-main">
|
|
|
+ <div class="list-title" :title="getMarketingItemTitle(item)">{{ getMarketingItemTitle(item) }}</div>
|
|
|
+ <div class="list-meta">
|
|
|
+ <span v-if="getMarketingItemPrice(item)" class="list-price">¥{{ getMarketingItemPrice(item) }}</span>
|
|
|
+ <span v-if="getMarketingListMeta(item)">{{ getMarketingListMeta(item) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="list-tags">
|
|
|
+ <el-tag v-if="(marketingCategory === 'coupon' || marketingCategory === 'verifyCoupon') && item.isShow" type="warning" size="mini">展示中</el-tag>
|
|
|
+ <el-tag v-if="(marketingCategory === 'lottery' || marketingCategory === 'red') && isRedLotteryRunning(item)" type="success" size="mini">进行中</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="list-actions" @click.stop>
|
|
|
+ <template v-if="marketingCategory === 'lottery'">
|
|
|
+ <el-button size="mini" type="primary" @click="handleLotteryStatusChange(item, '1')">开始</el-button>
|
|
|
+ <el-button size="mini" @click="handleLotteryStatusChange(item, '3')">暂停</el-button>
|
|
|
+ <el-button size="mini" type="danger" @click="handleLotteryStatusChange(item, '2')">结算</el-button>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="marketingCategory === 'red'">
|
|
|
+ <el-button size="mini" type="primary" @click="handleRedStatusChange(item, '1')">开始</el-button>
|
|
|
+ <el-button size="mini" @click="handleRedStatusChange(item, '3')">暂停</el-button>
|
|
|
+ <el-button size="mini" type="danger" @click="handleRedStatusChange(item, '2')">结算</el-button>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="marketingCategory === 'coupon'">
|
|
|
+ <el-button size="mini" @click="openCouponBindDialog(item)">绑定商品</el-button>
|
|
|
+ <el-button size="mini" type="primary" @click="handleCouponShowToggle(item)">{{ item.isShow ? '收起' : '展示' }}</el-button>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="marketingCategory === 'verifyCoupon'">
|
|
|
+ <el-button size="mini" type="primary" @click="handleCouponShowToggle(item)">{{ item.isShow ? '收起' : '展示' }}</el-button>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-empty v-else description="暂无数据" :image-size="60" />
|
|
|
+ </el-scrollbar>
|
|
|
+ <div class="panel-footer">
|
|
|
+ <el-button size="mini" @click="handleActivityRecord">活动记录</el-button>
|
|
|
+ <el-button size="mini" @click="handleDistributionRecord">发放记录</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="系统消息" name="systemMsg">
|
|
|
+ <div class="system-msg-panel">
|
|
|
+ <el-input
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="输入系统消息"
|
|
|
+ v-model="newMsg"
|
|
|
+ resize="none"
|
|
|
+ />
|
|
|
+ <div class="panel-footer">
|
|
|
+ <el-button type="primary" size="mini" @click="sendMessage">发送消息</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="right-panel">
|
|
|
- <h2>运营工具</h2>
|
|
|
- <div class="live-player">
|
|
|
- <h3>直播观看</h3>
|
|
|
+ <h2 class="panel-title">运营工具</h2>
|
|
|
+ <div class="panel-card live-player">
|
|
|
+ <h3 class="section-title">直播观看</h3>
|
|
|
<LivePlayer ref="livePlayer" :videoParam="videoParam" />
|
|
|
</div>
|
|
|
|
|
|
- <div class="automation">
|
|
|
- <h3>运营自动化</h3>
|
|
|
- <div class="timeline">
|
|
|
- <h4>时间轴设置</h4>
|
|
|
- <div class="timeline-items">
|
|
|
- <div class="timeline-item" v-for="item in timelineItems.slice(0,2)" :key="item.time">
|
|
|
- <div class="time">{{ formatDate(item.absValue) }}</div>
|
|
|
- <div class="action">{{ item.taskName }}</div>
|
|
|
- <button class="delete" @click="removeTimelineItem(item)">删除</button>
|
|
|
+ <div class="panel-card automation">
|
|
|
+ <h3 class="section-title">运营自动化</h3>
|
|
|
+ <p class="section-subtitle">时间轴设置</p>
|
|
|
+ <div class="timeline-items">
|
|
|
+ <div class="timeline-item" v-for="item in timelineItems.slice(0, 2)" :key="item.id || item.absValue">
|
|
|
+ <div class="timeline-info">
|
|
|
+ <div class="timeline-time">{{ formatDate(item.absValue) }}</div>
|
|
|
+ <div class="timeline-action">{{ item.taskName }}</div>
|
|
|
</div>
|
|
|
+ <el-button type="text" size="mini" class="action-link" @click="removeTimelineItem(item)">删除</el-button>
|
|
|
</div>
|
|
|
- <!-- <button class="add" @click="addTimelineItem">添加时间节点</button>-->
|
|
|
+ <el-empty v-if="!timelineItems.length" description="暂无任务" :image-size="48" />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="watermark">
|
|
|
- <h3>直播氛围自动化</h3>
|
|
|
- <div class="watermark-settings">
|
|
|
- <textarea :disabled="autoWatermark" v-model="watermarkTemplate" placeholder="水军弹幕内容模板,每行一条"></textarea>
|
|
|
- <div class="watermark-options">
|
|
|
- <label>
|
|
|
- 发送间隔:
|
|
|
- <input type="number" :disabled="autoWatermark" v-model.number="interval" min="1">
|
|
|
- 秒
|
|
|
- </label>
|
|
|
- <label>
|
|
|
- <input type="checkbox" v-model="autoWatermark" @change="changeAutoWatermark">
|
|
|
- 启用水军自动化
|
|
|
- </label>
|
|
|
- </div>
|
|
|
+ <div class="panel-card watermark">
|
|
|
+ <h3 class="section-title">直播氛围自动化</h3>
|
|
|
+ <el-input
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ :disabled="autoWatermark"
|
|
|
+ v-model="watermarkTemplate"
|
|
|
+ placeholder="水军弹幕内容模板,每行一条"
|
|
|
+ resize="none"
|
|
|
+ />
|
|
|
+ <div class="watermark-options">
|
|
|
+ <span class="toolbar-label">发送间隔</span>
|
|
|
+ <el-input-number v-model="interval" :min="1" :disabled="autoWatermark" size="mini" controls-position="right" />
|
|
|
+ <span class="toolbar-label">秒</span>
|
|
|
+ <el-checkbox v-model="autoWatermark" @change="changeAutoWatermark">启用水军自动化</el-checkbox>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 顶部消息对话框 -->
|
|
|
- <el-dialog title="发送顶部消息" :visible.sync="topMsgDialogVisible" width="500px">
|
|
|
- <el-form :model="topMsgForm" label-width="100px">
|
|
|
- <el-form-item label="消息内容">
|
|
|
- <el-input
|
|
|
- type="textarea"
|
|
|
- v-model="topMsgForm.msg"
|
|
|
- placeholder="请输入消息内容"
|
|
|
- :rows="3"
|
|
|
- ></el-input>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="显示时长">
|
|
|
- <el-input-number
|
|
|
- v-model="topMsgForm.duration"
|
|
|
- :min="1"
|
|
|
- :max="60"
|
|
|
- placeholder="请输入显示时长(分钟)"
|
|
|
- ></el-input-number>
|
|
|
- <span style="margin-left: 10px;">分钟</span>
|
|
|
+ <el-dialog title="绑定商品" :visible.sync="couponBindDialogVisible" width="480px" append-to-body>
|
|
|
+ <el-form label-width="80px">
|
|
|
+ <el-form-item label="直播商品">
|
|
|
+ <el-select v-model="couponBindForm.goodsId" placeholder="请选择商品" filterable style="width: 100%;">
|
|
|
+ <el-option
|
|
|
+ v-for="g in bindGoodsOptions"
|
|
|
+ :key="g.goodsId"
|
|
|
+ :label="g.productName"
|
|
|
+ :value="g.goodsId"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
- <div slot="footer" class="dialog-footer">
|
|
|
- <el-button @click="topMsgDialogVisible = false">取 消</el-button>
|
|
|
- <el-button type="primary" @click="sendTopMessage">确 定</el-button>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="couponBindDialogVisible = false">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmCouponBind">确 定</el-button>
|
|
|
</div>
|
|
|
</el-dialog>
|
|
|
|
|
|
@@ -265,8 +310,12 @@
|
|
|
import LivePlayer from './LivePlayer.vue';
|
|
|
import {blockUser, changeUserStatus, getLiveUserTotals, dashBoardWatchUserList} from '@/api/live/liveWatchUser'
|
|
|
import { listLiveSingleMsg,delLiveMsg } from '@/api/live/liveMsg'
|
|
|
-import { getLive } from '@/api/live/live'
|
|
|
+import { getLive, updateShowCart } from '@/api/live/live'
|
|
|
import { consoleList } from '@/api/live/task'
|
|
|
+import { listLiveGoods, handleShelfOrUn, handleIsShowChange as handleGoodsIsShowChange } from '@/api/live/liveGoods'
|
|
|
+import { listLiveCoupon, handleIsShowChange as handleCouponIsShowChange, updateLiveCouponBind } from '@/api/live/liveCoupon'
|
|
|
+import { listLiveLotteryConf, updateLiveLotteryConf } from '@/api/live/liveLotteryConf'
|
|
|
+import { listLiveRedConf, updateLiveRedConf } from '@/api/live/liveRedConf'
|
|
|
import ScreenScale from './ScreenScale.vue'; // 路径根据实际位置调整
|
|
|
|
|
|
|
|
|
@@ -401,11 +450,6 @@ export default {
|
|
|
newMsg:'',
|
|
|
autoMsgTimer: null,
|
|
|
checkInterval: 2000, // 检查间隔(1分钟,可根据需求调整)
|
|
|
- topMsgDialogVisible: false,
|
|
|
- topMsgForm: {
|
|
|
- msg: '',
|
|
|
- duration: 5
|
|
|
- },
|
|
|
// 消息滚动控制
|
|
|
isAutoScrollEnabled: true, // 是否启用自动滚动
|
|
|
autoScrollTimer: null, // 自动滚动定时器
|
|
|
@@ -416,7 +460,29 @@ export default {
|
|
|
pageNum: 1,
|
|
|
pageSize: 30,
|
|
|
liveId: null
|
|
|
- }
|
|
|
+ },
|
|
|
+ opsTabActive: 'marketing',
|
|
|
+ marketingCategory: 'goods',
|
|
|
+ marketingCategories: [
|
|
|
+ { key: 'goods', label: '商品' },
|
|
|
+ { key: 'coupon', label: '优惠券' },
|
|
|
+ { key: 'verifyCoupon', label: '核销券' },
|
|
|
+ { key: 'red', label: '红包' },
|
|
|
+ { key: 'lottery', label: '抽奖' }
|
|
|
+ ],
|
|
|
+ marketingLoading: false,
|
|
|
+ showCart: true,
|
|
|
+ goodsLiveList: [],
|
|
|
+ couponLiveList: [],
|
|
|
+ lotteryList: [],
|
|
|
+ redList: [],
|
|
|
+ couponTypeOptions: [],
|
|
|
+ lotteryStatusOptions: [],
|
|
|
+ redStatusOptions: [],
|
|
|
+ selectedMarketingItem: null,
|
|
|
+ couponBindDialogVisible: false,
|
|
|
+ couponBindForm: { couponId: null, goodsId: null },
|
|
|
+ bindGoodsOptions: []
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -437,10 +503,26 @@ export default {
|
|
|
},
|
|
|
silencedUserLabel() {
|
|
|
return `禁言(${this.userTotal.silenced})`;
|
|
|
+ },
|
|
|
+ currentMarketingList() {
|
|
|
+ if (this.marketingCategory === 'goods') {
|
|
|
+ return this.goodsLiveList;
|
|
|
+ }
|
|
|
+ if (this.marketingCategory === 'lottery') {
|
|
|
+ return this.lotteryList;
|
|
|
+ }
|
|
|
+ if (this.marketingCategory === 'red') {
|
|
|
+ return this.redList;
|
|
|
+ }
|
|
|
+ if (this.marketingCategory === 'coupon') {
|
|
|
+ return this.couponLiveList.filter(item => !this.isVerifyCouponType(item));
|
|
|
+ }
|
|
|
+ return this.couponLiveList.filter(item => this.isVerifyCouponType(item));
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
if(!this.liveId) return
|
|
|
+ this.loadMarketingDicts()
|
|
|
this.getList()
|
|
|
this.handleUserClick({name:'al'})
|
|
|
this.connectWebSocket()
|
|
|
@@ -457,6 +539,295 @@ export default {
|
|
|
this.initScrollListeners();
|
|
|
},
|
|
|
methods: {
|
|
|
+ loadMarketingDicts() {
|
|
|
+ this.getDicts('store_coupon_type').then(response => {
|
|
|
+ this.couponTypeOptions = response.data || [];
|
|
|
+ });
|
|
|
+ this.getDicts('sys_live_lottery_status').then(response => {
|
|
|
+ this.lotteryStatusOptions = response.data || [];
|
|
|
+ });
|
|
|
+ this.getDicts('sys_live_red_status').then(response => {
|
|
|
+ this.redStatusOptions = response.data || [];
|
|
|
+ });
|
|
|
+ },
|
|
|
+ loadMarketingData() {
|
|
|
+ if (!this.liveId) return;
|
|
|
+ this.marketingLoading = true;
|
|
|
+ const query = { liveId: this.liveId, pageNum: 1, pageSize: 100 };
|
|
|
+ Promise.all([
|
|
|
+ listLiveGoods(query),
|
|
|
+ listLiveCoupon(query),
|
|
|
+ listLiveLotteryConf(query),
|
|
|
+ listLiveRedConf(query)
|
|
|
+ ]).then(([goodsRes, couponRes, lotteryRes, redRes]) => {
|
|
|
+ if (goodsRes.code === 200) {
|
|
|
+ this.goodsLiveList = (goodsRes.rows || []).map(item => ({
|
|
|
+ ...item,
|
|
|
+ isShow: item.isShow === 1 || item.isShow === true
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ if (couponRes.code === 200) {
|
|
|
+ this.couponLiveList = (couponRes.rows || []).map(item => ({
|
|
|
+ ...item,
|
|
|
+ isShow: item.isShow === 1 || item.isShow === true
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ if (lotteryRes.code === 200) {
|
|
|
+ this.lotteryList = lotteryRes.rows || [];
|
|
|
+ }
|
|
|
+ if (redRes.code === 200) {
|
|
|
+ this.redList = redRes.rows || [];
|
|
|
+ }
|
|
|
+ }).finally(() => {
|
|
|
+ this.marketingLoading = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ clearMarketingSelection() {
|
|
|
+ this.selectedMarketingItem = null;
|
|
|
+ },
|
|
|
+ isVerifyCouponType(item) {
|
|
|
+ const label = this.selectDictLabel(this.couponTypeOptions, item.type) || '';
|
|
|
+ return label.includes('核销') || label.includes('代金券');
|
|
|
+ },
|
|
|
+ getMarketingItemKey(item) {
|
|
|
+ if (this.marketingCategory === 'goods') return `goods-${item.goodsId}`;
|
|
|
+ if (this.marketingCategory === 'lottery') return `lottery-${item.lotteryId}`;
|
|
|
+ if (this.marketingCategory === 'red') return `red-${item.redId}`;
|
|
|
+ return `coupon-${item.id || item.couponId}`;
|
|
|
+ },
|
|
|
+ getMarketingItemTitle(item) {
|
|
|
+ if (this.marketingCategory === 'lottery') {
|
|
|
+ return item.desc || `抽奖 #${item.lotteryId}`;
|
|
|
+ }
|
|
|
+ if (this.marketingCategory === 'red') {
|
|
|
+ return item.desc || `红包 #${item.redId}`;
|
|
|
+ }
|
|
|
+ return item.productName || item.title || '-';
|
|
|
+ },
|
|
|
+ getMarketingItemPrice(item) {
|
|
|
+ if (this.marketingCategory === 'goods') {
|
|
|
+ return item.price;
|
|
|
+ }
|
|
|
+ if (this.marketingCategory === 'coupon' || this.marketingCategory === 'verifyCoupon') {
|
|
|
+ return item.couponPrice;
|
|
|
+ }
|
|
|
+ return '';
|
|
|
+ },
|
|
|
+ getMarketingListMeta(item) {
|
|
|
+ if (this.marketingCategory === 'lottery') {
|
|
|
+ return this.selectDictLabel(this.lotteryStatusOptions, item.lotteryStatus) || '';
|
|
|
+ }
|
|
|
+ if (this.marketingCategory === 'red') {
|
|
|
+ return this.selectDictLabel(this.redStatusOptions, item.redStatus) || '';
|
|
|
+ }
|
|
|
+ const typeLabel = this.selectDictLabel(this.couponTypeOptions, item.type);
|
|
|
+ const parts = [];
|
|
|
+ if (item.useMinPrice) {
|
|
|
+ parts.push(`满${item.useMinPrice}可用`);
|
|
|
+ }
|
|
|
+ if (this.marketingCategory === 'coupon' && item.productName) {
|
|
|
+ parts.push(`已绑:${item.productName}`);
|
|
|
+ }
|
|
|
+ if (typeLabel) {
|
|
|
+ parts.push(typeLabel);
|
|
|
+ }
|
|
|
+ return parts.join(' · ');
|
|
|
+ },
|
|
|
+ isRedLotteryRunning(item) {
|
|
|
+ const status = this.marketingCategory === 'red' ? item.redStatus : item.lotteryStatus;
|
|
|
+ return status + '' === '1' || status + '' === '3';
|
|
|
+ },
|
|
|
+ isMarketingItemSelected(item) {
|
|
|
+ if (!this.selectedMarketingItem) return false;
|
|
|
+ return this.getMarketingItemKey(item) === this.getMarketingItemKey(this.selectedMarketingItem);
|
|
|
+ },
|
|
|
+ selectMarketingItem(item) {
|
|
|
+ this.selectedMarketingItem = item;
|
|
|
+ },
|
|
|
+ ensureSocket() {
|
|
|
+ if (!this.socket) {
|
|
|
+ this.$message.error('请从直播间开启 WebSocket 连接');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+ handleGoodsShelf(row, status) {
|
|
|
+ handleShelfOrUn({ goodsIds: [row.goodsId], status, liveId: this.liveId }).then(res => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ row.status = status;
|
|
|
+ this.$message.success(status === 1 ? '上架成功' : '下架成功');
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ handleGoodsShowToggle(row) {
|
|
|
+ this.toggleGoodsShow(row, !row.isShow);
|
|
|
+ },
|
|
|
+ handleCouponShowToggle(row) {
|
|
|
+ this.toggleCouponShow(row, !row.isShow);
|
|
|
+ },
|
|
|
+ openCouponBindDialog(row) {
|
|
|
+ this.couponBindForm = { couponId: row.id, goodsId: row.goodsId || null };
|
|
|
+ listLiveGoods({ liveId: this.liveId, pageNum: 1, pageSize: 100 }).then(res => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.bindGoodsOptions = res.rows || [];
|
|
|
+ this.couponBindDialogVisible = true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ confirmCouponBind() {
|
|
|
+ if (!this.couponBindForm.goodsId) {
|
|
|
+ this.$message.warning('请选择商品');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ updateLiveCouponBind({
|
|
|
+ goodsId: this.couponBindForm.goodsId,
|
|
|
+ couponId: this.couponBindForm.couponId,
|
|
|
+ liveId: this.liveId
|
|
|
+ }).then(res => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ this.$message.success('绑定成功');
|
|
|
+ this.couponBindDialogVisible = false;
|
|
|
+ this.loadMarketingData();
|
|
|
+ } else {
|
|
|
+ this.$message.error(res.msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ toggleGoodsShow(row, targetStatus) {
|
|
|
+ if (!this.ensureSocket()) return;
|
|
|
+ handleGoodsIsShowChange({
|
|
|
+ goodsIds: [row.goodsId],
|
|
|
+ isShow: targetStatus,
|
|
|
+ liveId: this.liveId
|
|
|
+ }).then(res => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ row.isShow = res.isShow;
|
|
|
+ if (res.msg === '目前仅支持单一物品展示') {
|
|
|
+ this.$message.error(res.msg);
|
|
|
+ }
|
|
|
+ this.socket.send(JSON.stringify({
|
|
|
+ cmd: 'goods',
|
|
|
+ data: { liveId: this.liveId, goodsId: row.goodsId, status: targetStatus ? 1 : 0 }
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ toggleCouponShow(row, targetStatus) {
|
|
|
+ if (!this.ensureSocket()) return;
|
|
|
+ handleCouponIsShowChange({
|
|
|
+ couponId: row.id,
|
|
|
+ isShow: targetStatus,
|
|
|
+ liveId: this.liveId
|
|
|
+ }).then(res => {
|
|
|
+ if (res.code === 200) {
|
|
|
+ row.isShow = targetStatus;
|
|
|
+ if (res.msg === '目前仅支持单一优惠券推送') {
|
|
|
+ this.$message.error(res.msg);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.socket.send(JSON.stringify({
|
|
|
+ cmd: 'coupon',
|
|
|
+ data: {
|
|
|
+ liveId: this.liveId,
|
|
|
+ couponIssueId: row.id,
|
|
|
+ status: targetStatus ? 1 : 0,
|
|
|
+ goodsId: row.goodsId,
|
|
|
+ couponName: row.title,
|
|
|
+ couponPrice: row.couponPrice,
|
|
|
+ useMinPrice: row.useMinPrice,
|
|
|
+ couponTime: row.couponTime
|
|
|
+ }
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ handleLotteryStatusChange(row, status) {
|
|
|
+ if (!this.ensureSocket()) return;
|
|
|
+ if (row.lotteryStatus + '' === '2') {
|
|
|
+ this.$message.error('已结束的抽奖不能进行操作');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (status === '1' && row.lotteryStatus + '' !== '0' && row.lotteryStatus + '' !== '3') {
|
|
|
+ this.$message.error('只能对未开始或暂停的抽奖进行开始操作');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (status === '2' && row.lotteryStatus + '' !== '1' && row.lotteryStatus + '' !== '3') {
|
|
|
+ this.$message.error('只能对进行中或暂停的抽奖进行结算');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (status === '3' && row.lotteryStatus + '' !== '1') {
|
|
|
+ this.$message.error('只能对进行中的抽奖执行暂停操作');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const param = {
|
|
|
+ lotteryId: row.lotteryId,
|
|
|
+ lotteryStatus: status,
|
|
|
+ duration: row.duration,
|
|
|
+ liveId: this.liveId,
|
|
|
+ status
|
|
|
+ };
|
|
|
+ updateLiveLotteryConf(param).then(response => {
|
|
|
+ if (response.code === 200) {
|
|
|
+ this.socket.send(JSON.stringify({ cmd: 'lottery', data: param }));
|
|
|
+ this.$message.success('操作成功');
|
|
|
+ this.loadMarketingData();
|
|
|
+ } else {
|
|
|
+ this.$message.error(response.msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ handleRedStatusChange(row, status) {
|
|
|
+ if (!this.ensureSocket()) return;
|
|
|
+ if (row.redStatus + '' === '2') {
|
|
|
+ this.$message.error('已结束的红包不能进行操作');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (status === '1' && row.redStatus + '' !== '0' && row.redStatus + '' !== '3') {
|
|
|
+ this.$message.error('只能对未开始或暂停的红包进行开始操作');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (status === '2' && row.redStatus + '' !== '1' && row.redStatus + '' !== '3') {
|
|
|
+ this.$message.error('只能对进行中或暂停的红包进行结算');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (status === '3' && row.redStatus + '' !== '1') {
|
|
|
+ this.$message.error('只能对进行中的红包执行暂停操作');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const param = {
|
|
|
+ redId: row.redId,
|
|
|
+ redStatus: status,
|
|
|
+ totalLots: row.totalLots,
|
|
|
+ totalSend: row.totalSend,
|
|
|
+ redType: row.redType,
|
|
|
+ redNum: row.redNum,
|
|
|
+ liveId: this.liveId,
|
|
|
+ duration: row.duration,
|
|
|
+ status
|
|
|
+ };
|
|
|
+ updateLiveRedConf(param).then(response => {
|
|
|
+ if (response.code === 200) {
|
|
|
+ this.socket.send(JSON.stringify({ cmd: 'red', data: param }));
|
|
|
+ this.$message.success('操作成功');
|
|
|
+ this.loadMarketingData();
|
|
|
+ } else {
|
|
|
+ this.$message.error(response.msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ handleActivityRecord() {
|
|
|
+ if (!this.timelineItems.length) {
|
|
|
+ this.$message.info('暂无活动记录');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const names = this.timelineItems.map(item => `${this.formatDate(item.absValue)} ${item.taskName}`).join('\n');
|
|
|
+ this.$alert(names, '活动记录', { confirmButtonText: '确定' });
|
|
|
+ },
|
|
|
+ handleDistributionRecord() {
|
|
|
+ this.$message.info('发放记录请在直播配置-优惠券/红包配置中查看');
|
|
|
+ },
|
|
|
// 点击消息框
|
|
|
handleMessageBoxClick() {
|
|
|
// 点击消息框时,停止自动滚动
|
|
|
@@ -582,6 +953,28 @@ export default {
|
|
|
}
|
|
|
this.socket.send(JSON.stringify(msg))
|
|
|
},
|
|
|
+ showCartChange(val) {
|
|
|
+ const status = val ? 1 : 0
|
|
|
+ updateShowCart({ liveId: this.liveId, showCart: status }).then(res => {
|
|
|
+ if (res.code !== 200) {
|
|
|
+ this.$message.error(res.msg || '更新购物车显示状态失败')
|
|
|
+ this.showCart = !val
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
|
+ this.socket.send(JSON.stringify({
|
|
|
+ liveId: this.liveId,
|
|
|
+ userId: '9999',
|
|
|
+ userType: 0,
|
|
|
+ cmd: 'showCart',
|
|
|
+ status
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ this.showCart = !val
|
|
|
+ this.$message.error('更新购物车显示状态失败')
|
|
|
+ })
|
|
|
+ },
|
|
|
changeAutoWatermark( val){
|
|
|
this.watermarkList = this.watermarkTemplate
|
|
|
.split('\n')
|
|
|
@@ -633,62 +1026,6 @@ export default {
|
|
|
this.socket.send(JSON.stringify(msg))
|
|
|
this.watermarkIndex += 1
|
|
|
},
|
|
|
- // 弹窗消息
|
|
|
- sendPopMessage() {
|
|
|
- // 发送前简单校验
|
|
|
- if (this.newMsg.trim() === '') {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- let msg = {
|
|
|
- msg: this.newMsg,
|
|
|
- liveId: this.liveId,
|
|
|
- userId: this.userId,
|
|
|
- userType: 1,
|
|
|
- cmd: 'sendPopMsg',
|
|
|
- avatar: this.$store.state.user.user.avatar,
|
|
|
- nickName: this.$store.state.user.user.nickName,
|
|
|
- }
|
|
|
-
|
|
|
- this.socket.send(JSON.stringify(msg))
|
|
|
-
|
|
|
- this.newMsg = '';
|
|
|
- },
|
|
|
- // 显示顶部消息对话框
|
|
|
- showTopMsgDialog() {
|
|
|
- this.topMsgForm.msg = '';
|
|
|
- this.topMsgForm.duration = 5;
|
|
|
- this.topMsgDialogVisible = true;
|
|
|
- },
|
|
|
- // 发送顶部消息
|
|
|
- sendTopMessage() {
|
|
|
- // 发送前简单校验
|
|
|
- if (this.topMsgForm.msg.trim() === '') {
|
|
|
- this.$message.warning('请输入消息内容');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!this.topMsgForm.duration || this.topMsgForm.duration < 1) {
|
|
|
- this.$message.warning('请输入有效的显示时长');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- let msg = {
|
|
|
- msg: this.topMsgForm.msg,
|
|
|
- duration: this.topMsgForm.duration,
|
|
|
- liveId: this.liveId,
|
|
|
- userId: this.userId,
|
|
|
- userType: 1,
|
|
|
- cmd: 'sendTopMsg',
|
|
|
- avatar: this.$store.state.user.user.avatar,
|
|
|
- nickName: this.$store.state.user.user.nickName,
|
|
|
- }
|
|
|
-
|
|
|
- this.socket.send(JSON.stringify(msg))
|
|
|
-
|
|
|
- this.topMsgDialogVisible = false;
|
|
|
- this.$message.success('顶部消息发送成功');
|
|
|
- },
|
|
|
sendMessage() {
|
|
|
// 发送前简单校验
|
|
|
if (this.newMsg.trim() === '') {
|
|
|
@@ -826,6 +1163,7 @@ export default {
|
|
|
}
|
|
|
this.$nextTick(() => {
|
|
|
this.globalVisible = res.data.globalVisible
|
|
|
+ this.showCart = res.data.showCart == null || res.data.showCart === 1
|
|
|
this.$refs.livePlayer.initPlayer()
|
|
|
})
|
|
|
this.loading = false
|
|
|
@@ -1104,6 +1442,7 @@ export default {
|
|
|
// this.handleClick({name:'online'})
|
|
|
this.loadMsgList()
|
|
|
this.loadLiveTask()
|
|
|
+ this.loadMarketingData()
|
|
|
},
|
|
|
formatDate(value) {
|
|
|
// 检查值是否存在且为日期类型(或可转换为日期)
|
|
|
@@ -1494,296 +1833,510 @@ export default {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+/* 布局 */
|
|
|
.console {
|
|
|
display: flex;
|
|
|
height: 100vh;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #f5f7fa;
|
|
|
}
|
|
|
|
|
|
-.left-panel, .middle-panel, .right-panel {
|
|
|
- padding: 20px;
|
|
|
+.left-panel,
|
|
|
+.middle-panel,
|
|
|
+.right-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 16px;
|
|
|
box-sizing: border-box;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.left-panel {
|
|
|
- width: 30%;
|
|
|
- background: #f8fafc;
|
|
|
- border-right: 1px solid #e2e8f0;
|
|
|
+ width: 28%;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
}
|
|
|
|
|
|
.middle-panel {
|
|
|
- width: 40%;
|
|
|
- background: #f8fafc;
|
|
|
- border-right: 1px solid #e2e8f0;
|
|
|
+ width: 42%;
|
|
|
+ border-right: 1px solid #ebeef5;
|
|
|
+ gap: 12px;
|
|
|
}
|
|
|
|
|
|
.right-panel {
|
|
|
width: 30%;
|
|
|
- background: #f8fafc;
|
|
|
+ overflow-y: auto;
|
|
|
+ gap: 12px;
|
|
|
}
|
|
|
|
|
|
-.search {
|
|
|
- margin: 10px 0;
|
|
|
+/* 通用标题与卡片 */
|
|
|
+.panel-title {
|
|
|
+ margin: 0 0 12px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
-.search input {
|
|
|
- width: 70%;
|
|
|
- padding: 8px;
|
|
|
- border: 1px solid #cbd5e1;
|
|
|
- border-radius: 4px;
|
|
|
+.section-title {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
}
|
|
|
|
|
|
-.search button {
|
|
|
- padding: 8px 15px;
|
|
|
- background: #3b82f6;
|
|
|
- color: #fff;
|
|
|
- border: none;
|
|
|
- border-radius: 4px;
|
|
|
- cursor: pointer;
|
|
|
+.section-subtitle {
|
|
|
+ margin: 0 0 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
}
|
|
|
|
|
|
-.tabs {
|
|
|
+.section-header {
|
|
|
display: flex;
|
|
|
- margin: 10px 0;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
-.tabs button {
|
|
|
- padding: 8px 15px;
|
|
|
- border: 1px solid #e2e8f0;
|
|
|
+.panel-card {
|
|
|
background: #fff;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 12px;
|
|
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.toolbar-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+.action-link {
|
|
|
+ display: inline-block;
|
|
|
+ margin-right: 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #f56c6c;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
-.tabs button.active {
|
|
|
- background: #3b82f6;
|
|
|
- color: #fff;
|
|
|
- border-color: #3b82f6;
|
|
|
+.action-link:hover {
|
|
|
+ opacity: 0.85;
|
|
|
}
|
|
|
|
|
|
-.user-list {
|
|
|
- max-height: 600px;
|
|
|
- overflow-y: auto;
|
|
|
+.panel-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ margin-top: 10px;
|
|
|
+ padding-top: 10px;
|
|
|
+ border-top: 1px solid #ebeef5;
|
|
|
}
|
|
|
|
|
|
-.user-item {
|
|
|
+/* 学员列表 */
|
|
|
+.search-bar {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-bar .el-input {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.console-tabs {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.console-tabs ::v-deep .el-tabs__content {
|
|
|
+ height: calc(100% - 40px);
|
|
|
+}
|
|
|
+
|
|
|
+.console-tabs ::v-deep .el-tab-pane {
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-scroll {
|
|
|
+ height: calc(100vh - 180px);
|
|
|
+}
|
|
|
+
|
|
|
+.user-list-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- padding: 10px;
|
|
|
- border-bottom: 1px solid #e2e8f0;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 10px 8px;
|
|
|
+ border-bottom: 1px solid #f2f6fc;
|
|
|
}
|
|
|
|
|
|
-.user-item img {
|
|
|
- width: 40px;
|
|
|
- height: 40px;
|
|
|
- border-radius: 50%;
|
|
|
- margin-right: 10px;
|
|
|
+.user-list-item:hover {
|
|
|
+ background: #f5f7fa;
|
|
|
}
|
|
|
|
|
|
-.user-info {
|
|
|
+.user-meta {
|
|
|
flex: 1;
|
|
|
+ min-width: 0;
|
|
|
}
|
|
|
|
|
|
.user-name {
|
|
|
- font-weight: bold;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #303133;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
-.user-status {
|
|
|
+.user-id {
|
|
|
+ margin-top: 2px;
|
|
|
font-size: 12px;
|
|
|
- color: #64748b;
|
|
|
+ color: #909399;
|
|
|
}
|
|
|
|
|
|
-.online {
|
|
|
- color: #10b981;
|
|
|
+.user-more-btn {
|
|
|
+ color: #909399;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 4px;
|
|
|
}
|
|
|
|
|
|
-.offline {
|
|
|
- color: #94a3b8;
|
|
|
+.user-more-btn:hover {
|
|
|
+ color: #409eff;
|
|
|
}
|
|
|
|
|
|
-.user-actions {
|
|
|
+/* 讨论区消息 */
|
|
|
+.discussion-messages {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
|
|
|
-.user-actions button {
|
|
|
- padding: 5px 10px;
|
|
|
- margin-left: 5px;
|
|
|
- border: none;
|
|
|
- border-radius: 4px;
|
|
|
- cursor: pointer;
|
|
|
+.message-container {
|
|
|
+ position: relative;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
}
|
|
|
|
|
|
-.block {
|
|
|
- background: #ef4444;
|
|
|
- color: #fff;
|
|
|
+.msg-scroll {
|
|
|
+ height: calc(100vh - 520px);
|
|
|
+ min-height: 180px;
|
|
|
}
|
|
|
|
|
|
-.unblock {
|
|
|
- background: #10b981;
|
|
|
- color: #fff;
|
|
|
+.msg-item + .msg-item {
|
|
|
+ margin-top: 8px;
|
|
|
}
|
|
|
|
|
|
-.mute {
|
|
|
- background: #f59e0b;
|
|
|
- color: #fff;
|
|
|
+.msg-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8px;
|
|
|
}
|
|
|
|
|
|
-.unmute {
|
|
|
- background: #3b82f6;
|
|
|
- color: #fff;
|
|
|
+.msg-row--self {
|
|
|
+ justify-content: flex-end;
|
|
|
}
|
|
|
|
|
|
-.system-messages, .discussion-messages {
|
|
|
- margin: 20px 0;
|
|
|
- background: #fff;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
|
+.msg-content {
|
|
|
+ max-width: 72%;
|
|
|
}
|
|
|
|
|
|
-.system-messages textarea {
|
|
|
- width: 100%;
|
|
|
- height: 100px;
|
|
|
- border: 1px solid #e2e8f0;
|
|
|
- border-radius: 4px;
|
|
|
- padding: 8px;
|
|
|
- box-sizing: border-box;
|
|
|
+.msg-content--self {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-end;
|
|
|
}
|
|
|
|
|
|
-.message-actions {
|
|
|
- margin-top: 10px;
|
|
|
+.msg-nickname {
|
|
|
+ margin-bottom: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
}
|
|
|
|
|
|
-.message-actions button {
|
|
|
- padding: 5px 10px;
|
|
|
- margin-right: 5px;
|
|
|
- background: #3b82f6;
|
|
|
- color: #fff;
|
|
|
- border: none;
|
|
|
+.msg-bubble {
|
|
|
+ padding: 8px 10px;
|
|
|
border-radius: 4px;
|
|
|
- cursor: pointer;
|
|
|
+ background: #f4f4f5;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.5;
|
|
|
+ color: #303133;
|
|
|
+ word-break: break-word;
|
|
|
}
|
|
|
|
|
|
-.message-list {
|
|
|
- max-height: 300px;
|
|
|
- overflow-y: auto;
|
|
|
- margin-top: 10px;
|
|
|
+.msg-bubble--self {
|
|
|
+ background: #ecf5ff;
|
|
|
}
|
|
|
|
|
|
-.message-item {
|
|
|
- display: flex;
|
|
|
+.msg-actions {
|
|
|
+ margin-top: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.msg-avatar {
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.scroll-bottom-space {
|
|
|
+ height: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.load-latest-btn {
|
|
|
+ position: absolute;
|
|
|
+ right: 12px;
|
|
|
+ bottom: 12px;
|
|
|
+ z-index: 2;
|
|
|
+}
|
|
|
+
|
|
|
+/* 营销 / 系统消息 */
|
|
|
+.ops-panel {
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.ops-tabs ::v-deep .el-tabs__header {
|
|
|
margin-bottom: 10px;
|
|
|
- padding-bottom: 10px;
|
|
|
- border-bottom: 1px solid #e2e8f0;
|
|
|
}
|
|
|
|
|
|
-.message-avatar img {
|
|
|
- width: 30px;
|
|
|
- height: 30px;
|
|
|
- border-radius: 50%;
|
|
|
- margin-right: 10px;
|
|
|
+.ops-tabs ::v-deep .el-tabs__content {
|
|
|
+ overflow: visible;
|
|
|
}
|
|
|
|
|
|
-.message-content {
|
|
|
- flex: 1;
|
|
|
+.marketing-panel,
|
|
|
+.system-msg-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
|
|
|
-.message-user {
|
|
|
- font-weight: bold;
|
|
|
+.marketing-toolbar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ flex-wrap: wrap;
|
|
|
}
|
|
|
|
|
|
-.message-text {
|
|
|
- font-size: 14px;
|
|
|
- color: #64748b;
|
|
|
+.marketing-tip {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 200px;
|
|
|
+ padding: 4px 8px;
|
|
|
}
|
|
|
|
|
|
-.message-actions button {
|
|
|
- padding: 3px 8px;
|
|
|
+.marketing-tip ::v-deep .el-alert__title {
|
|
|
font-size: 12px;
|
|
|
- background: #3b82f6;
|
|
|
- color: #fff;
|
|
|
- border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.marketing-categories {
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.marketing-scroll {
|
|
|
+ height: 150px;
|
|
|
+}
|
|
|
+
|
|
|
+.marketing-cards {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 8px;
|
|
|
+ padding-right: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.marketing-card {
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
cursor: pointer;
|
|
|
+ background: #fff;
|
|
|
+ transition: border-color 0.2s;
|
|
|
}
|
|
|
|
|
|
-.live-player, .automation, .watermark {
|
|
|
- margin: 20px 0;
|
|
|
- background: #fff;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
|
+.marketing-card:hover,
|
|
|
+.marketing-card.selected {
|
|
|
+ border-color: #409eff;
|
|
|
}
|
|
|
|
|
|
-.timeline-items {
|
|
|
- margin: 10px 0;
|
|
|
+.card-image-wrap {
|
|
|
+ position: relative;
|
|
|
+ height: 72px;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #f5f7fa;
|
|
|
}
|
|
|
|
|
|
-.timeline-item {
|
|
|
+.card-image {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.card-tags {
|
|
|
+ position: absolute;
|
|
|
+ top: 6px;
|
|
|
+ left: 6px;
|
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-overlay {
|
|
|
+ position: absolute;
|
|
|
+ inset: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
align-items: center;
|
|
|
- padding: 8px 0;
|
|
|
- border-bottom: 1px solid #e2e8f0;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 4px;
|
|
|
+ padding: 6px;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.2s;
|
|
|
}
|
|
|
|
|
|
-.delete {
|
|
|
- background: #ef4444;
|
|
|
- color: #fff;
|
|
|
- border: none;
|
|
|
- border-radius: 4px;
|
|
|
- padding: 3px 8px;
|
|
|
- cursor: pointer;
|
|
|
+.marketing-card:hover .card-overlay {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.card-info {
|
|
|
+ padding: 6px 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-title {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #303133;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.card-bottom {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-price {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #f56c6c;
|
|
|
}
|
|
|
|
|
|
-.add {
|
|
|
- background: #10b981;
|
|
|
- color: #fff;
|
|
|
- border: none;
|
|
|
+.marketing-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+ padding-right: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.marketing-list-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 10px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
border-radius: 4px;
|
|
|
- padding: 8px 15px;
|
|
|
+ background: #fff;
|
|
|
cursor: pointer;
|
|
|
+ transition: border-color 0.2s;
|
|
|
}
|
|
|
|
|
|
-.watermark-settings textarea {
|
|
|
- width: 100%;
|
|
|
- height: 100px;
|
|
|
- border: 1px solid #e2e8f0;
|
|
|
- border-radius: 4px;
|
|
|
- padding: 8px;
|
|
|
- box-sizing: border-box;
|
|
|
+.marketing-list-item:hover,
|
|
|
+.marketing-list-item.selected {
|
|
|
+ border-color: #409eff;
|
|
|
+ background: #f5f9ff;
|
|
|
}
|
|
|
|
|
|
-.watermark-options {
|
|
|
- margin-top: 10px;
|
|
|
+.list-main {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
}
|
|
|
|
|
|
-.watermark-options label {
|
|
|
- display: block;
|
|
|
- margin-bottom: 5px;
|
|
|
+.list-title {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #303133;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
-/* 隐藏 el-scrollbar 的横向滚动条 */
|
|
|
-.el-scrollbar__wrap {
|
|
|
- overflow-x: hidden !important;
|
|
|
+
|
|
|
+.list-meta {
|
|
|
+ margin-top: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
-.custom-scrollbar .el-scrollbar__wrap {
|
|
|
- overflow-x: hidden !important;
|
|
|
+
|
|
|
+.list-price {
|
|
|
+ margin-right: 6px;
|
|
|
+ color: #f56c6c;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.list-tags {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 4px;
|
|
|
+ margin-top: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.list-actions {
|
|
|
+ display: flex;
|
|
|
+ flex-shrink: 0;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 4px;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧运营工具 */
|
|
|
+.live-player,
|
|
|
+.automation,
|
|
|
+.watermark {
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-items {
|
|
|
+ margin-top: 4px;
|
|
|
}
|
|
|
-.scrollbar-demo-item{
|
|
|
+
|
|
|
+.timeline-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- justify-content: center;
|
|
|
- height: 50px;
|
|
|
- margin: 10px;
|
|
|
- text-align: center;
|
|
|
- border-radius: 4px;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 8px 0;
|
|
|
+ border-bottom: 1px solid #f2f6fc;
|
|
|
}
|
|
|
-.message-container {
|
|
|
- position: relative;
|
|
|
+
|
|
|
+.timeline-time {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
}
|
|
|
-.load-latest-btn {
|
|
|
- position: absolute;
|
|
|
- bottom: 20px;
|
|
|
- right: 20px;
|
|
|
- z-index: 10;
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
|
+
|
|
|
+.timeline-action {
|
|
|
+ margin-top: 2px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.watermark-options {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ margin-top: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 滚动条 */
|
|
|
+::v-deep .el-scrollbar__wrap {
|
|
|
+ overflow-x: hidden !important;
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<style>
|
|
|
+.user-action-popover .action-link {
|
|
|
+ display: block;
|
|
|
+ margin: 0 0 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #f56c6c;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.user-action-popover .action-link:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
}
|
|
|
</style>
|