|
@@ -1,37 +1,26 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="app-container">
|
|
<div class="app-container">
|
|
|
- <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
|
|
|
|
- <el-form-item label="播放方案名称" prop="planName">
|
|
|
|
|
|
|
+ <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="90px">
|
|
|
|
|
+ <el-form-item label="方案名称" prop="planName">
|
|
|
<el-input
|
|
<el-input
|
|
|
v-model="queryParams.planName"
|
|
v-model="queryParams.planName"
|
|
|
- placeholder="请输入播放方案名称"
|
|
|
|
|
|
|
+ placeholder="请输入方案名称"
|
|
|
clearable
|
|
clearable
|
|
|
|
|
+ style="width: 200px"
|
|
|
@keyup.enter="handleQuery"
|
|
@keyup.enter="handleQuery"
|
|
|
/>
|
|
/>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="循环方式:loop循环播放,once播放一次" prop="loopMode">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="queryParams.loopMode"
|
|
|
|
|
- placeholder="请输入循环方式:loop循环播放,once播放一次"
|
|
|
|
|
- clearable
|
|
|
|
|
- @keyup.enter="handleQuery"
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="默认方案标识:0否,1是" prop="defaultFlag">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="queryParams.defaultFlag"
|
|
|
|
|
- placeholder="请输入默认方案标识:0否,1是"
|
|
|
|
|
- clearable
|
|
|
|
|
- @keyup.enter="handleQuery"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <el-form-item label="循环方式" prop="loopMode">
|
|
|
|
|
+ <el-select v-model="queryParams.loopMode" placeholder="请选择循环方式" clearable style="width: 160px">
|
|
|
|
|
+ <el-option label="循环播放" value="loop" />
|
|
|
|
|
+ <el-option label="播放一次" value="once" />
|
|
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
- <el-form-item label="素材数量" prop="assetCount">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="queryParams.assetCount"
|
|
|
|
|
- placeholder="请输入素材数量"
|
|
|
|
|
- clearable
|
|
|
|
|
- @keyup.enter="handleQuery"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <el-form-item label="播放状态" prop="status">
|
|
|
|
|
+ <el-select v-model="queryParams.status" placeholder="请选择播放状态" clearable style="width: 160px">
|
|
|
|
|
+ <el-option label="当前播放" value="1" />
|
|
|
|
|
+ <el-option label="备用方案" value="0" />
|
|
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
<el-form-item>
|
|
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
@@ -81,23 +70,62 @@
|
|
|
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
|
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
|
|
</el-row>
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ v-if="currentPlayingCount > 1"
|
|
|
|
|
+ title="检测到存在多条当前播放方案,请联系后端检查播放状态唯一性规则。"
|
|
|
|
|
+ type="warning"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ class="play-status-alert"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
<el-table v-loading="loading" :data="planList" @selection-change="handleSelectionChange">
|
|
<el-table v-loading="loading" :data="planList" @selection-change="handleSelectionChange">
|
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
|
- <el-table-column label="主键ID" align="center" prop="id" />
|
|
|
|
|
- <el-table-column label="播放方案名称" align="center" prop="planName" />
|
|
|
|
|
- <el-table-column label="循环方式:loop循环播放,once播放一次" align="center" prop="loopMode" />
|
|
|
|
|
- <el-table-column label="默认方案标识:0否,1是" align="center" prop="defaultFlag" />
|
|
|
|
|
- <el-table-column label="素材数量" align="center" prop="assetCount" />
|
|
|
|
|
- <el-table-column label="启用状态:0停用,1启用" align="center" prop="status" />
|
|
|
|
|
- <el-table-column label="备注" align="center" prop="remark" />
|
|
|
|
|
- <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
|
|
|
|
|
|
+ <el-table-column label="方案名称" align="left" prop="planName" min-width="180" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="循环方式" align="center" prop="loopMode" width="110">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-tag v-if="scope.row.loopMode === 'loop'" type="success">循环播放</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="scope.row.loopMode === 'once'" type="info">播放一次</el-tag>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="素材数" align="center" prop="assetCount" width="90">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-tag type="primary">{{ scope.row.assetCount || 0 }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="播放状态" align="center" prop="status" width="110">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['base:plan:edit']">修改</el-button>
|
|
|
|
|
- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['base:plan:remove']">删除</el-button>
|
|
|
|
|
|
|
+ <el-tag v-if="String(scope.row.status) === '1'" type="success">当前播放</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="String(scope.row.status) === '0'" type="info">备用方案</el-tag>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" align="center" width="240" fixed="right" class-name="small-padding fixed-width">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-button link type="primary" icon="View" @click="handlePreview(scope.row)">预览</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="String(scope.row.status) !== '1'"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="success"
|
|
|
|
|
+ icon="CircleCheck"
|
|
|
|
|
+ @click="handleStatusChange(scope.row, '1')"
|
|
|
|
|
+ v-hasPermi="['base:plan:edit']"
|
|
|
|
|
+ >启用播放</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="String(scope.row.status) === '1'"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="warning"
|
|
|
|
|
+ icon="CircleClose"
|
|
|
|
|
+ @click="handleStatusChange(scope.row, '0')"
|
|
|
|
|
+ v-hasPermi="['base:plan:edit']"
|
|
|
|
|
+ >设为备用</el-button>
|
|
|
|
|
+ <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['base:plan:edit']">编辑</el-button>
|
|
|
|
|
+ <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['base:plan:remove']">删除</el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
<pagination
|
|
<pagination
|
|
|
v-show="total>0"
|
|
v-show="total>0"
|
|
|
:total="total"
|
|
:total="total"
|
|
@@ -107,71 +135,118 @@
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
<!-- 添加或修改播放方案对话框 -->
|
|
<!-- 添加或修改播放方案对话框 -->
|
|
|
- <el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
|
|
|
|
- <el-form ref="planRef" :model="form" :rules="rules" label-width="100px">
|
|
|
|
|
|
|
+ <el-dialog :title="title" v-model="open" width="960px" append-to-body>
|
|
|
|
|
+ <el-form ref="planRef" :model="form" :rules="rules" label-width="110px">
|
|
|
<el-row>
|
|
<el-row>
|
|
|
<el-col :span="24">
|
|
<el-col :span="24">
|
|
|
- <el-form-item label="播放方案名称" prop="planName">
|
|
|
|
|
- <el-input v-model="form.planName" placeholder="请输入播放方案名称" />
|
|
|
|
|
|
|
+ <el-form-item label="方案名称" prop="planName">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.planName"
|
|
|
|
|
+ maxlength="100"
|
|
|
|
|
+ show-word-limit
|
|
|
|
|
+ placeholder="请输入方案名称"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="循环方式:loop循环播放,once播放一次" prop="loopMode">
|
|
|
|
|
- <el-input v-model="form.loopMode" placeholder="请输入循环方式:loop循环播放,once播放一次" />
|
|
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item label="循环方式" prop="loopMode">
|
|
|
|
|
+ <el-radio-group v-model="form.loopMode">
|
|
|
|
|
+ <el-radio label="loop">循环播放</el-radio>
|
|
|
|
|
+ <el-radio label="once">播放一次</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="默认方案标识:0否,1是" prop="defaultFlag">
|
|
|
|
|
- <el-input v-model="form.defaultFlag" placeholder="请输入默认方案标识:0否,1是" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-col>
|
|
|
|
|
- <el-col :span="24">
|
|
|
|
|
- <el-form-item label="素材数量" prop="assetCount">
|
|
|
|
|
- <el-input v-model="form.assetCount" placeholder="请输入素材数量" />
|
|
|
|
|
|
|
+ <el-col :span="12">
|
|
|
|
|
+ <el-form-item label="播放状态" prop="status">
|
|
|
|
|
+ <el-radio-group v-model="form.status">
|
|
|
|
|
+ <el-radio label="1">当前播放</el-radio>
|
|
|
|
|
+ <el-radio label="0">备用方案</el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ <div class="form-tip">同一时间仅允许一套方案作为当前播放方案,建议新增方案先保存为备用方案。</div>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="24">
|
|
<el-col :span="24">
|
|
|
<el-form-item label="备注" prop="remark">
|
|
<el-form-item label="备注" prop="remark">
|
|
|
- <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
|
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.remark"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :rows="3"
|
|
|
|
|
+ maxlength="500"
|
|
|
|
|
+ show-word-limit
|
|
|
|
|
+ placeholder="请输入备注"
|
|
|
|
|
+ />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
</el-row>
|
|
</el-row>
|
|
|
- <el-divider content-position="center">播放方案素材明细信息</el-divider>
|
|
|
|
|
|
|
+ <el-divider content-position="center">素材编排</el-divider>
|
|
|
<el-row :gutter="10" class="mb8">
|
|
<el-row :gutter="10" class="mb8">
|
|
|
<el-col :span="1.5">
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="primary" icon="Plus" @click="handleAddRobotOpsPlayPlanItem">添加</el-button>
|
|
|
|
|
|
|
+ <el-button type="primary" icon="Plus" @click="handleSelectAsset">选择素材</el-button>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
<el-col :span="1.5">
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="danger" icon="Delete" @click="handleDeleteRobotOpsPlayPlanItem">删除</el-button>
|
|
|
|
|
|
|
+ <el-button type="danger" icon="Delete" @click="handleDeleteRobotOpsPlayPlanItem">移除素材</el-button>
|
|
|
</el-col>
|
|
</el-col>
|
|
|
</el-row>
|
|
</el-row>
|
|
|
- <el-table :data="robotOpsPlayPlanItemList" @selection-change="handleRobotOpsPlayPlanItemSelectionChange" ref="robotOpsPlayPlanItem">
|
|
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ :data="robotOpsPlayPlanItemList"
|
|
|
|
|
+ @selection-change="handleRobotOpsPlayPlanItemSelectionChange"
|
|
|
|
|
+ ref="robotOpsPlayPlanItem"
|
|
|
|
|
+ empty-text="请点击「选择素材」添加播放内容"
|
|
|
|
|
+ >
|
|
|
<el-table-column type="selection" width="50" align="center" />
|
|
<el-table-column type="selection" width="50" align="center" />
|
|
|
- <el-table-column label="序号" width="60">
|
|
|
|
|
- <template #default="{ $index }">
|
|
|
|
|
- {{ $index + 1 }}
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column label="素材ID,关联robot_ops_media_asset.id" prop="assetId" width="150">
|
|
|
|
|
|
|
+ <el-table-column label="素材" min-width="260">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <el-input v-model="scope.row.assetId" placeholder="请输入素材ID,关联robot_ops_media_asset.id" />
|
|
|
|
|
|
|
+ <div class="asset-info">
|
|
|
|
|
+ <div class="asset-thumb">
|
|
|
|
|
+ <image-preview
|
|
|
|
|
+ v-if="getAssetThumb(scope.row)"
|
|
|
|
|
+ :src="getAssetThumb(scope.row)"
|
|
|
|
|
+ :width="54"
|
|
|
|
|
+ :height="54"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div v-else class="video-thumb">
|
|
|
|
|
+ <el-icon><VideoPlay /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="asset-meta">
|
|
|
|
|
+ <div class="asset-name">{{ scope.row.assetName || '未命名素材' }}</div>
|
|
|
|
|
+ <div class="asset-sub">
|
|
|
|
|
+ <el-tag v-if="scope.row.assetType === 'image'" size="small" type="success">图片</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="scope.row.assetType === 'video'" size="small" type="warning">视频</el-tag>
|
|
|
|
|
+ <el-tag v-else size="small" type="info">未知</el-tag>
|
|
|
|
|
+ <span class="asset-id">ID: {{ scope.row.assetId }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="播放顺序,数字越小越靠前" prop="playOrder" width="150">
|
|
|
|
|
|
|
+ <el-table-column label="顺序" prop="playOrder" width="140" align="center">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <el-input v-model="scope.row.playOrder" placeholder="请输入播放顺序,数字越小越靠前" />
|
|
|
|
|
|
|
+ <el-button link type="primary" :disabled="scope.$index === 0" @click="moveItem(scope.$index, -1)">上移</el-button>
|
|
|
|
|
+ <el-button link type="primary" :disabled="scope.$index === robotOpsPlayPlanItemList.length - 1" @click="moveItem(scope.$index, 1)">下移</el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="停留时长,图片必填,视频可为空" prop="staySeconds" width="150">
|
|
|
|
|
|
|
+ <el-table-column label="停留时长" prop="staySeconds" width="160" align="center">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <el-input v-model="scope.row.staySeconds" placeholder="请输入停留时长,图片必填,视频可为空" />
|
|
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-if="scope.row.assetType === 'image'"
|
|
|
|
|
+ v-model="scope.row.staySeconds"
|
|
|
|
|
+ :min="1"
|
|
|
|
|
+ :max="3600"
|
|
|
|
|
+ :step="1"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ style="width: 120px"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span v-else class="text-muted">视频播完切换</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column label="转场方式,预留字段:none无转场,fade淡入淡出" prop="transitionType" width="150">
|
|
|
|
|
|
|
+ <el-table-column label="文件信息" min-width="160">
|
|
|
<template #default="scope">
|
|
<template #default="scope">
|
|
|
- <el-select v-model="scope.row.transitionType" placeholder="请选择转场方式,预留字段:none无转场,fade淡入淡出">
|
|
|
|
|
- <el-option label="请选择字典生成" value="" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
|
|
+ <div class="asset-file-info">
|
|
|
|
|
+ <div>{{ scope.row.resolution || '-' }}</div>
|
|
|
|
|
+ <div v-if="scope.row.assetType === 'video'">时长:{{ formatDuration(scope.row.durationSeconds) }}</div>
|
|
|
|
|
+ <div v-else>图片停留:{{ scope.row.staySeconds || '-' }} 秒</div>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
@@ -183,11 +258,184 @@
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 选择素材弹窗 -->
|
|
|
|
|
+ <el-dialog title="选择素材" v-model="assetSelectOpen" width="900px" append-to-body @closed="selectedAssets = []">
|
|
|
|
|
+ <el-form :model="assetQueryParams" :inline="true" label-width="80px">
|
|
|
|
|
+ <el-form-item label="素材名称">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="assetQueryParams.assetName"
|
|
|
|
|
+ placeholder="请输入素材名称"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ @keyup.enter="handleAssetQuery"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="素材类型">
|
|
|
|
|
+ <el-select v-model="assetQueryParams.assetType" placeholder="请选择素材类型" clearable style="width: 140px">
|
|
|
|
|
+ <el-option label="图片" value="image" />
|
|
|
|
|
+ <el-option label="视频" value="video" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" icon="Search" @click="handleAssetQuery">搜索</el-button>
|
|
|
|
|
+ <el-button icon="Refresh" @click="resetAssetQuery">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ title="仅显示启用状态的素材;已停用素材不能被新播放方案选择。"
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ show-icon
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ class="asset-select-tip"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table v-loading="assetLoading" :data="assetList" @selection-change="handleAssetSelectionChange">
|
|
|
|
|
+ <el-table-column type="selection" width="50" align="center" />
|
|
|
|
|
+ <el-table-column label="缩略图" width="90" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <image-preview
|
|
|
|
|
+ v-if="getAssetThumb(scope.row)"
|
|
|
|
|
+ :src="getAssetThumb(scope.row)"
|
|
|
|
|
+ :width="48"
|
|
|
|
|
+ :height="48"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div v-else class="video-thumb small">
|
|
|
|
|
+ <el-icon><VideoPlay /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="素材名称" prop="assetName" min-width="180" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="类型" prop="assetType" width="90" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-tag v-if="scope.row.assetType === 'image'" size="small" type="success">图片</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="scope.row.assetType === 'video'" size="small" type="warning">视频</el-tag>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="分辨率" prop="resolution" width="120" align="center">
|
|
|
|
|
+ <template #default="scope">{{ scope.row.resolution || '-' }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="视频时长" prop="durationSeconds" width="120" align="center">
|
|
|
|
|
+ <template #default="scope">{{ formatDuration(scope.row.durationSeconds) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="assetTotal > 0"
|
|
|
|
|
+ :total="assetTotal"
|
|
|
|
|
+ v-model:page="assetQueryParams.pageNum"
|
|
|
|
|
+ v-model:limit="assetQueryParams.pageSize"
|
|
|
|
|
+ @pagination="getAssetList"
|
|
|
|
|
+ />
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <el-button type="primary" @click="confirmSelectAsset">确 定</el-button>
|
|
|
|
|
+ <el-button @click="handleCancelSelectAsset">取 消</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 播放方案预览弹窗 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ :title="previewPlan?.planName ? '播放方案预览 - ' + previewPlan.planName : '播放方案预览'"
|
|
|
|
|
+ v-model="previewOpen"
|
|
|
|
|
+ width="960px"
|
|
|
|
|
+ append-to-body
|
|
|
|
|
+ @closed="resetPreview"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div v-if="previewPlan" class="plan-preview">
|
|
|
|
|
+ <div class="preview-header">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div class="preview-title">{{ previewPlan.planName || '-' }}</div>
|
|
|
|
|
+ <div class="preview-sub">
|
|
|
|
|
+ <el-tag v-if="previewPlan.loopMode === 'loop'" type="success">循环播放</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="previewPlan.loopMode === 'once'" type="info">播放一次</el-tag>
|
|
|
|
|
+ <el-tag v-if="String(previewPlan.status) === '1'" type="warning">当前播放</el-tag>
|
|
|
|
|
+ <el-tag v-else type="info">备用方案</el-tag>
|
|
|
|
|
+ <span>素材数:{{ previewItems.length }}</span>
|
|
|
|
|
+ <span v-if="previewItems.length">当前:{{ previewIndex + 1 }} / {{ previewItems.length }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="preview-body">
|
|
|
|
|
+ <div class="preview-main">
|
|
|
|
|
+ <div class="preview-stage">
|
|
|
|
|
+ <div v-if="currentPreviewItem && currentPreviewItem.fileUrl">
|
|
|
|
|
+ <img
|
|
|
|
|
+ v-if="currentPreviewItem.assetType === 'image'"
|
|
|
|
|
+ :src="currentPreviewItem.fileUrl"
|
|
|
|
|
+ class="preview-media preview-image"
|
|
|
|
|
+ @error="handlePreviewMediaError"
|
|
|
|
|
+ />
|
|
|
|
|
+ <video
|
|
|
|
|
+ v-else-if="currentPreviewItem.assetType === 'video'"
|
|
|
|
|
+ ref="previewVideoRef"
|
|
|
|
|
+ :src="currentPreviewItem.fileUrl"
|
|
|
|
|
+ class="preview-media preview-video"
|
|
|
|
|
+ controls
|
|
|
|
|
+ @error="handlePreviewMediaError"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div v-else class="preview-empty">暂不支持该素材类型预览</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-else-if="currentPreviewItem && !currentPreviewItem.fileUrl" class="preview-empty">素材文件地址为空,无法预览</div>
|
|
|
|
|
+ <div v-else class="preview-empty">暂无可预览素材</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="currentPreviewItem" class="preview-current-info">
|
|
|
|
|
+ <div class="preview-current-name">{{ currentPreviewItem.assetName || '未命名素材' }}</div>
|
|
|
|
|
+ <div class="preview-current-desc">
|
|
|
|
|
+ <el-tag v-if="currentPreviewItem.assetType === 'image'" size="small" type="success">图片</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="currentPreviewItem.assetType === 'video'" size="small" type="warning">视频</el-tag>
|
|
|
|
|
+ <span v-if="currentPreviewItem.assetType === 'image'">停留 {{ currentPreviewItem.staySeconds || '-' }} 秒</span>
|
|
|
|
|
+ <span v-else-if="currentPreviewItem.assetType === 'video'">视频播完切换</span>
|
|
|
|
|
+ <span v-if="currentPreviewItem.resolution">分辨率:{{ currentPreviewItem.resolution }}</span>
|
|
|
|
|
+ <span v-if="currentPreviewItem.durationSeconds">时长:{{ formatDuration(currentPreviewItem.durationSeconds) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="preview-list">
|
|
|
|
|
+ <div class="preview-list-title">播放清单</div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(item, index) in previewItems"
|
|
|
|
|
+ :key="item.assetId || index"
|
|
|
|
|
+ class="preview-item"
|
|
|
|
|
+ :class="{ active: index === previewIndex }"
|
|
|
|
|
+ @click="setPreviewIndex(index)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="preview-item-index">{{ index + 1 }}</div>
|
|
|
|
|
+ <div class="preview-item-thumb">
|
|
|
|
|
+ <image-preview
|
|
|
|
|
+ v-if="getAssetThumb(item)"
|
|
|
|
|
+ :src="getAssetThumb(item)"
|
|
|
|
|
+ :width="44"
|
|
|
|
|
+ :height="44"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div v-else class="video-thumb small">
|
|
|
|
|
+ <el-icon><VideoPlay /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="preview-item-meta">
|
|
|
|
|
+ <div class="preview-item-name">{{ item.assetName || '未命名素材' }}</div>
|
|
|
|
|
+ <div class="preview-item-desc">
|
|
|
|
|
+ <el-tag v-if="item.assetType === 'image'" size="small" type="success">图片</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="item.assetType === 'video'" size="small" type="warning">视频</el-tag>
|
|
|
|
|
+ <el-tag v-if="String(item.assetStatus) === '0'" size="small" type="danger">已停用</el-tag>
|
|
|
|
|
+ <span v-if="item.assetType === 'image'">停留 {{ item.staySeconds || '-' }} 秒</span>
|
|
|
|
|
+ <span v-else>视频播完切换</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="preview-actions">
|
|
|
|
|
+ <el-button :disabled="previewIndex <= 0" @click="setPreviewIndex(previewIndex - 1)">上一个</el-button>
|
|
|
|
|
+ <el-button :disabled="previewIndex >= previewItems.length - 1" @click="setPreviewIndex(previewIndex + 1)">下一个</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup name="Plan">
|
|
<script setup name="Plan">
|
|
|
import { listPlan, getPlan, delPlan, addPlan, updatePlan } from "@/api/base/plan"
|
|
import { listPlan, getPlan, delPlan, addPlan, updatePlan } from "@/api/base/plan"
|
|
|
|
|
+import { listMediAasset } from "@/api/base/mediAasset"
|
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
const { proxy } = getCurrentInstance()
|
|
|
|
|
|
|
@@ -203,6 +451,44 @@ const multiple = ref(true)
|
|
|
const total = ref(0)
|
|
const total = ref(0)
|
|
|
const title = ref("")
|
|
const title = ref("")
|
|
|
|
|
|
|
|
|
|
+const previewOpen = ref(false)
|
|
|
|
|
+const previewPlan = ref(null)
|
|
|
|
|
+const previewItems = ref([])
|
|
|
|
|
+const previewIndex = ref(0)
|
|
|
|
|
+const previewVideoRef = ref(null)
|
|
|
|
|
+const currentPreviewItem = computed(() => {
|
|
|
|
|
+ return previewItems.value[previewIndex.value] || null
|
|
|
|
|
+})
|
|
|
|
|
+const currentPlayingCount = computed(() => {
|
|
|
|
|
+ return planList.value.filter(item => String(item.status) === '1').length
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+function pausePreviewVideo() {
|
|
|
|
|
+ const video = previewVideoRef.value
|
|
|
|
|
+ if (video && typeof video.pause === 'function') {
|
|
|
|
|
+ video.pause()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function setPreviewIndex(index) {
|
|
|
|
|
+ if (index < 0 || index >= previewItems.value.length) return
|
|
|
|
|
+ pausePreviewVideo()
|
|
|
|
|
+ previewIndex.value = index
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const assetSelectOpen = ref(false)
|
|
|
|
|
+const assetLoading = ref(false)
|
|
|
|
|
+const assetList = ref([])
|
|
|
|
|
+const selectedAssets = ref([])
|
|
|
|
|
+const assetTotal = ref(0)
|
|
|
|
|
+const assetQueryParams = reactive({
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ assetName: undefined,
|
|
|
|
|
+ assetType: undefined,
|
|
|
|
|
+ status: '1'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
const data = reactive({
|
|
const data = reactive({
|
|
|
form: {},
|
|
form: {},
|
|
|
queryParams: {
|
|
queryParams: {
|
|
@@ -210,37 +496,68 @@ const data = reactive({
|
|
|
pageSize: 10,
|
|
pageSize: 10,
|
|
|
planName: undefined,
|
|
planName: undefined,
|
|
|
loopMode: undefined,
|
|
loopMode: undefined,
|
|
|
- defaultFlag: undefined,
|
|
|
|
|
- assetCount: undefined,
|
|
|
|
|
status: undefined,
|
|
status: undefined,
|
|
|
},
|
|
},
|
|
|
rules: {
|
|
rules: {
|
|
|
planName: [
|
|
planName: [
|
|
|
- { required: true, message: "播放方案名称不能为空", trigger: "blur" }
|
|
|
|
|
|
|
+ { required: true, message: "方案名称不能为空", trigger: "blur" },
|
|
|
|
|
+ { max: 100, message: "方案名称不能超过 100 字", trigger: "blur" }
|
|
|
],
|
|
],
|
|
|
loopMode: [
|
|
loopMode: [
|
|
|
- { required: true, message: "循环方式:loop循环播放,once播放一次不能为空", trigger: "blur" }
|
|
|
|
|
- ],
|
|
|
|
|
- defaultFlag: [
|
|
|
|
|
- { required: true, message: "默认方案标识:0否,1是不能为空", trigger: "blur" }
|
|
|
|
|
- ],
|
|
|
|
|
- assetCount: [
|
|
|
|
|
- { required: true, message: "素材数量不能为空", trigger: "blur" }
|
|
|
|
|
|
|
+ { required: true, message: "请选择循环方式", trigger: "change" }
|
|
|
],
|
|
],
|
|
|
status: [
|
|
status: [
|
|
|
- { required: true, message: "启用状态:0停用,1启用不能为空", trigger: "change" }
|
|
|
|
|
|
|
+ { required: true, message: "请选择播放状态", trigger: "change" }
|
|
|
],
|
|
],
|
|
|
|
|
+ remark: [
|
|
|
|
|
+ { max: 500, message: "备注不能超过 500 字", trigger: "blur" }
|
|
|
|
|
+ ]
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const { queryParams, form, rules } = toRefs(data)
|
|
const { queryParams, form, rules } = toRefs(data)
|
|
|
|
|
|
|
|
|
|
+function getAssetThumb(row) {
|
|
|
|
|
+ if (row.thumbnailUrl) return row.thumbnailUrl
|
|
|
|
|
+ if (row.assetType === 'image' && row.fileUrl) return row.fileUrl
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatDuration(seconds) {
|
|
|
|
|
+ const value = Number(seconds)
|
|
|
|
|
+ if (!value) return '-'
|
|
|
|
|
+ const m = Math.floor(value / 60)
|
|
|
|
|
+ const s = value % 60
|
|
|
|
|
+ return m > 0 ? `${m}分${s}秒` : `${s}秒`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function moveItem(index, direction) {
|
|
|
|
|
+ const targetIndex = index + direction
|
|
|
|
|
+ if (targetIndex < 0 || targetIndex >= robotOpsPlayPlanItemList.value.length) return
|
|
|
|
|
+ const list = robotOpsPlayPlanItemList.value
|
|
|
|
|
+ const temp = list[index]
|
|
|
|
|
+ list[index] = list[targetIndex]
|
|
|
|
|
+ list[targetIndex] = temp
|
|
|
|
|
+ refreshPlayOrder()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function refreshPlayOrder() {
|
|
|
|
|
+ robotOpsPlayPlanItemList.value.forEach((item, index) => {
|
|
|
|
|
+ item.playOrder = index + 1
|
|
|
|
|
+ item.transitionType = item.transitionType || 'none'
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/** 查询播放方案列表 */
|
|
/** 查询播放方案列表 */
|
|
|
function getList() {
|
|
function getList() {
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
listPlan(queryParams.value).then(response => {
|
|
listPlan(queryParams.value).then(response => {
|
|
|
- planList.value = response.rows
|
|
|
|
|
- total.value = response.total
|
|
|
|
|
|
|
+ planList.value = response.rows || []
|
|
|
|
|
+ total.value = response.total || 0
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ planList.value = []
|
|
|
|
|
+ total.value = 0
|
|
|
|
|
+ }).finally(() => {
|
|
|
loading.value = false
|
|
loading.value = false
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
@@ -256,10 +573,9 @@ function reset() {
|
|
|
form.value = {
|
|
form.value = {
|
|
|
id: null,
|
|
id: null,
|
|
|
planName: null,
|
|
planName: null,
|
|
|
- loopMode: null,
|
|
|
|
|
- defaultFlag: null,
|
|
|
|
|
- assetCount: null,
|
|
|
|
|
- status: null,
|
|
|
|
|
|
|
+ loopMode: 'loop',
|
|
|
|
|
+ assetCount: 0,
|
|
|
|
|
+ status: '0',
|
|
|
remark: null,
|
|
remark: null,
|
|
|
createBy: null,
|
|
createBy: null,
|
|
|
createTime: null,
|
|
createTime: null,
|
|
@@ -273,6 +589,9 @@ function reset() {
|
|
|
/** 搜索按钮操作 */
|
|
/** 搜索按钮操作 */
|
|
|
function handleQuery() {
|
|
function handleQuery() {
|
|
|
queryParams.value.pageNum = 1
|
|
queryParams.value.pageNum = 1
|
|
|
|
|
+ if (queryParams.value.planName) {
|
|
|
|
|
+ queryParams.value.planName = queryParams.value.planName.trim()
|
|
|
|
|
+ }
|
|
|
getList()
|
|
getList()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -293,7 +612,10 @@ function handleSelectionChange(selection) {
|
|
|
function handleAdd() {
|
|
function handleAdd() {
|
|
|
reset()
|
|
reset()
|
|
|
open.value = true
|
|
open.value = true
|
|
|
- title.value = "添加播放方案"
|
|
|
|
|
|
|
+ title.value = "新增播放方案"
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ proxy.$refs.robotOpsPlayPlanItem?.clearSelection?.()
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 修改按钮操作 */
|
|
/** 修改按钮操作 */
|
|
@@ -301,39 +623,93 @@ function handleUpdate(row) {
|
|
|
reset()
|
|
reset()
|
|
|
const _id = row.id || ids.value
|
|
const _id = row.id || ids.value
|
|
|
getPlan(_id).then(response => {
|
|
getPlan(_id).then(response => {
|
|
|
- form.value = response.data
|
|
|
|
|
- robotOpsPlayPlanItemList.value = response.data.robotOpsPlayPlanItemList
|
|
|
|
|
|
|
+ const data = response.data || {}
|
|
|
|
|
+ form.value = {
|
|
|
|
|
+ ...data,
|
|
|
|
|
+ loopMode: data.loopMode || 'loop',
|
|
|
|
|
+ status: data.status != null ? String(data.status) : '0'
|
|
|
|
|
+ }
|
|
|
|
|
+ const itemList = data.robotOpsPlayPlanItemList || data.itemList || []
|
|
|
|
|
+ robotOpsPlayPlanItemList.value = itemList.map((item, index) => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ playOrder: item.playOrder || index + 1,
|
|
|
|
|
+ transitionType: item.transitionType || 'none'
|
|
|
|
|
+ }))
|
|
|
open.value = true
|
|
open.value = true
|
|
|
- title.value = "修改播放方案"
|
|
|
|
|
|
|
+ title.value = "编辑播放方案"
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ proxy.$refs.robotOpsPlayPlanItem?.clearSelection?.()
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 提交按钮 */
|
|
/** 提交按钮 */
|
|
|
function submitForm() {
|
|
function submitForm() {
|
|
|
proxy.$refs["planRef"].validate(valid => {
|
|
proxy.$refs["planRef"].validate(valid => {
|
|
|
- if (valid) {
|
|
|
|
|
- form.value.robotOpsPlayPlanItemList = robotOpsPlayPlanItemList.value
|
|
|
|
|
- if (form.value.id != null) {
|
|
|
|
|
- updatePlan(form.value).then(() => {
|
|
|
|
|
- proxy.$modal.msgSuccess("修改成功")
|
|
|
|
|
- open.value = false
|
|
|
|
|
- getList()
|
|
|
|
|
- })
|
|
|
|
|
- } else {
|
|
|
|
|
- addPlan(form.value).then(() => {
|
|
|
|
|
- proxy.$modal.msgSuccess("新增成功")
|
|
|
|
|
- open.value = false
|
|
|
|
|
- getList()
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ if (!valid) return
|
|
|
|
|
+ if (!robotOpsPlayPlanItemList.value.length) {
|
|
|
|
|
+ proxy.$modal.msgWarning('请至少选择一个素材')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const item of robotOpsPlayPlanItemList.value) {
|
|
|
|
|
+ if (!item.assetId) {
|
|
|
|
|
+ proxy.$modal.msgWarning('存在未选择素材的明细,请检查')
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
|
|
+ if (item.assetType === 'image' && (!item.staySeconds || Number(item.staySeconds) <= 0)) {
|
|
|
|
|
+ proxy.$modal.msgWarning('图片素材必须填写停留时长')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (String(form.value.status) === '1') {
|
|
|
|
|
+ proxy.$modal.confirm('设为当前播放后,其他播放方案应自动变为备用方案,确认继续吗?').then(() => {
|
|
|
|
|
+ doSubmitForm()
|
|
|
|
|
+ }).catch(() => {})
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
|
|
+ doSubmitForm()
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function doSubmitForm() {
|
|
|
|
|
+ refreshPlayOrder()
|
|
|
|
|
+ const submitItemList = robotOpsPlayPlanItemList.value.map(item => ({
|
|
|
|
|
+ id: item.id,
|
|
|
|
|
+ planId: item.planId,
|
|
|
|
|
+ assetId: item.assetId,
|
|
|
|
|
+ playOrder: item.playOrder,
|
|
|
|
|
+ staySeconds: item.assetType === 'image' ? item.staySeconds : null,
|
|
|
|
|
+ transitionType: item.transitionType || 'none'
|
|
|
|
|
+ }))
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ id: form.value.id,
|
|
|
|
|
+ planName: form.value.planName,
|
|
|
|
|
+ loopMode: form.value.loopMode,
|
|
|
|
|
+ status: String(form.value.status || '0'),
|
|
|
|
|
+ remark: form.value.remark,
|
|
|
|
|
+ robotOpsPlayPlanItemList: submitItemList,
|
|
|
|
|
+ itemList: submitItemList
|
|
|
|
|
+ }
|
|
|
|
|
+ if (form.value.id != null) {
|
|
|
|
|
+ updatePlan(payload).then(() => {
|
|
|
|
|
+ proxy.$modal.msgSuccess("修改成功")
|
|
|
|
|
+ open.value = false
|
|
|
|
|
+ getList()
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ addPlan(payload).then(() => {
|
|
|
|
|
+ proxy.$modal.msgSuccess("新增成功")
|
|
|
|
|
+ open.value = false
|
|
|
|
|
+ getList()
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/** 删除按钮操作 */
|
|
/** 删除按钮操作 */
|
|
|
function handleDelete(row) {
|
|
function handleDelete(row) {
|
|
|
const _ids = row.id || ids.value
|
|
const _ids = row.id || ids.value
|
|
|
- proxy.$modal.confirm('是否确认删除播放方案编号为"' + _ids + '"的数据项?').then(function() {
|
|
|
|
|
|
|
+ proxy.$modal.confirm('确认删除选中的播放方案吗?如删除的是当前播放方案,可能导致车端无可播放方案,请谨慎操作。').then(function() {
|
|
|
return delPlan(_ids)
|
|
return delPlan(_ids)
|
|
|
}).then(() => {
|
|
}).then(() => {
|
|
|
getList()
|
|
getList()
|
|
@@ -341,40 +717,389 @@ function handleDelete(row) {
|
|
|
}).catch(() => {})
|
|
}).catch(() => {})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** 播放方案素材明细添加按钮操作 */
|
|
|
|
|
-function handleAddRobotOpsPlayPlanItem() {
|
|
|
|
|
- let obj = {}
|
|
|
|
|
- obj.assetId = undefined
|
|
|
|
|
- obj.playOrder = undefined
|
|
|
|
|
- obj.staySeconds = undefined
|
|
|
|
|
- obj.transitionType = undefined
|
|
|
|
|
- robotOpsPlayPlanItemList.value.push(obj)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
/** 播放方案素材明细删除按钮操作 */
|
|
/** 播放方案素材明细删除按钮操作 */
|
|
|
function handleDeleteRobotOpsPlayPlanItem() {
|
|
function handleDeleteRobotOpsPlayPlanItem() {
|
|
|
- if (checkedRobotOpsPlayPlanItem.value.length == 0) {
|
|
|
|
|
- proxy.$modal.msgError("请先选择要删除的播放方案素材明细数据")
|
|
|
|
|
- } else {
|
|
|
|
|
- const robotOpsPlayPlanItems = robotOpsPlayPlanItemList.value
|
|
|
|
|
- const checkedRobotOpsPlayPlanItems = checkedRobotOpsPlayPlanItem.value
|
|
|
|
|
- robotOpsPlayPlanItemList.value = robotOpsPlayPlanItems.filter(function(item) {
|
|
|
|
|
- return checkedRobotOpsPlayPlanItems.indexOf(item.index) == -1
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ if (checkedRobotOpsPlayPlanItem.value.length === 0) {
|
|
|
|
|
+ proxy.$modal.msgError("请先选择要移除的素材")
|
|
|
|
|
+ return
|
|
|
}
|
|
}
|
|
|
|
|
+ robotOpsPlayPlanItemList.value = robotOpsPlayPlanItemList.value.filter(item => {
|
|
|
|
|
+ return !checkedRobotOpsPlayPlanItem.value.includes(item)
|
|
|
|
|
+ })
|
|
|
|
|
+ checkedRobotOpsPlayPlanItem.value = []
|
|
|
|
|
+ proxy.$refs.robotOpsPlayPlanItem?.clearSelection?.()
|
|
|
|
|
+ refreshPlayOrder()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 复选框选中数据 */
|
|
/** 复选框选中数据 */
|
|
|
function handleRobotOpsPlayPlanItemSelectionChange(selection) {
|
|
function handleRobotOpsPlayPlanItemSelectionChange(selection) {
|
|
|
- checkedRobotOpsPlayPlanItem.value = selection.map(item => item.index)
|
|
|
|
|
|
|
+ checkedRobotOpsPlayPlanItem.value = selection
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** 导出按钮操作 */
|
|
/** 导出按钮操作 */
|
|
|
function handleExport() {
|
|
function handleExport() {
|
|
|
proxy.download('base/plan/export', {
|
|
proxy.download('base/plan/export', {
|
|
|
...queryParams.value
|
|
...queryParams.value
|
|
|
- }, `plan_${new Date().getTime()}.xlsx`)
|
|
|
|
|
|
|
+ }, `播放方案_${new Date().getTime()}.xlsx`)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 预览 */
|
|
|
|
|
+function handlePreview(row) {
|
|
|
|
|
+ if (!row || !row.id) {
|
|
|
|
|
+ proxy.$modal.msgWarning('请选择要预览的播放方案')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ getPlan(row.id).then(response => {
|
|
|
|
|
+ const data = response.data || {}
|
|
|
|
|
+ const itemList = data.robotOpsPlayPlanItemList || data.itemList || []
|
|
|
|
|
+ previewPlan.value = data
|
|
|
|
|
+ previewItems.value = itemList
|
|
|
|
|
+ .map((item, index) => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ playOrder: item.playOrder || index + 1,
|
|
|
|
|
+ transitionType: item.transitionType || 'none'
|
|
|
|
|
+ }))
|
|
|
|
|
+ .sort((a, b) => Number(a.playOrder || 0) - Number(b.playOrder || 0))
|
|
|
|
|
+ previewIndex.value = 0
|
|
|
|
|
+ previewOpen.value = true
|
|
|
|
|
+ if (!previewItems.value.length) {
|
|
|
|
|
+ proxy.$modal.msgWarning('该播放方案暂无素材明细')
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ proxy.$modal.msgError('获取播放方案详情失败,无法预览')
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetPreview() {
|
|
|
|
|
+ pausePreviewVideo()
|
|
|
|
|
+ previewPlan.value = null
|
|
|
|
|
+ previewItems.value = []
|
|
|
|
|
+ previewIndex.value = 0
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handlePreviewMediaError() {
|
|
|
|
|
+ proxy.$modal.msgWarning('素材文件暂无法预览,请检查文件地址是否有效。')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/** 状态变更 */
|
|
|
|
|
+function handleStatusChange(row, status) {
|
|
|
|
|
+ const actionText = status === '1' ? '启用播放' : '设为备用'
|
|
|
|
|
+ proxy.$modal.msgWarning('播放方案\u201c' + actionText + '\u201d接口暂未单独接入,建议后端提供独立状态接口后再联调,避免通过完整编辑接口覆盖素材明细。')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 选择素材 */
|
|
|
|
|
+function handleSelectAsset() {
|
|
|
|
|
+ selectedAssets.value = []
|
|
|
|
|
+ assetSelectOpen.value = true
|
|
|
|
|
+ getAssetList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 获取素材列表 */
|
|
|
|
|
+function getAssetList() {
|
|
|
|
|
+ assetLoading.value = true
|
|
|
|
|
+ listMediAasset(assetQueryParams).then(response => {
|
|
|
|
|
+ assetList.value = response.rows || []
|
|
|
|
|
+ assetTotal.value = response.total || 0
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ assetList.value = []
|
|
|
|
|
+ assetTotal.value = 0
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ assetLoading.value = false
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 重置素材查询 */
|
|
|
|
|
+function resetAssetQuery() {
|
|
|
|
|
+ assetQueryParams.pageNum = 1
|
|
|
|
|
+ assetQueryParams.assetName = undefined
|
|
|
|
|
+ assetQueryParams.assetType = undefined
|
|
|
|
|
+ assetQueryParams.status = '1'
|
|
|
|
|
+ getAssetList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 素材选择变化 */
|
|
|
|
|
+function handleAssetSelectionChange(selection) {
|
|
|
|
|
+ selectedAssets.value = selection
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 素材搜索 */
|
|
|
|
|
+function handleAssetQuery() {
|
|
|
|
|
+ assetQueryParams.pageNum = 1
|
|
|
|
|
+ if (assetQueryParams.assetName) {
|
|
|
|
|
+ assetQueryParams.assetName = assetQueryParams.assetName.trim()
|
|
|
|
|
+ }
|
|
|
|
|
+ getAssetList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 确认选择素材 */
|
|
|
|
|
+function confirmSelectAsset() {
|
|
|
|
|
+ if (!selectedAssets.value.length) {
|
|
|
|
|
+ proxy.$modal.msgWarning('请选择素材')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const existsIds = robotOpsPlayPlanItemList.value.map(item => Number(item.assetId))
|
|
|
|
|
+ let addCount = 0
|
|
|
|
|
+ let skipCount = 0
|
|
|
|
|
+ selectedAssets.value.forEach(asset => {
|
|
|
|
|
+ if (existsIds.includes(Number(asset.id))) {
|
|
|
|
|
+ skipCount++
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ robotOpsPlayPlanItemList.value.push({
|
|
|
|
|
+ assetId: asset.id,
|
|
|
|
|
+ assetName: asset.assetName,
|
|
|
|
|
+ assetType: asset.assetType,
|
|
|
|
|
+ fileUrl: asset.fileUrl,
|
|
|
|
|
+ thumbnailUrl: asset.thumbnailUrl,
|
|
|
|
|
+ durationSeconds: asset.durationSeconds,
|
|
|
|
|
+ resolution: asset.resolution,
|
|
|
|
|
+ assetStatus: asset.status,
|
|
|
|
|
+ playOrder: robotOpsPlayPlanItemList.value.length + 1,
|
|
|
|
|
+ staySeconds: asset.assetType === 'image' ? 10 : null,
|
|
|
|
|
+ transitionType: 'none'
|
|
|
|
|
+ })
|
|
|
|
|
+ existsIds.push(Number(asset.id))
|
|
|
|
|
+ addCount++
|
|
|
|
|
+ })
|
|
|
|
|
+ refreshPlayOrder()
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ selectedAssets.value = []
|
|
|
|
|
+ })
|
|
|
|
|
+ assetSelectOpen.value = false
|
|
|
|
|
+ if (addCount > 0 && skipCount > 0) {
|
|
|
|
|
+ proxy.$modal.msgSuccess('已添加 ' + addCount + ' 个素材,跳过 ' + skipCount + ' 个重复素材')
|
|
|
|
|
+ } else if (addCount > 0) {
|
|
|
|
|
+ proxy.$modal.msgSuccess('已添加 ' + addCount + ' 个素材')
|
|
|
|
|
+ } else if (skipCount > 0) {
|
|
|
|
|
+ proxy.$modal.msgWarning('所选素材已全部存在,请勿重复添加')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleCancelSelectAsset() {
|
|
|
|
|
+ selectedAssets.value = []
|
|
|
|
|
+ assetSelectOpen.value = false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
getList()
|
|
getList()
|
|
|
</script>
|
|
</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.asset-info {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.asset-thumb {
|
|
|
|
|
+ width: 54px;
|
|
|
|
|
+ height: 54px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+}
|
|
|
|
|
+.video-thumb {
|
|
|
|
|
+ width: 54px;
|
|
|
|
|
+ height: 54px;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+}
|
|
|
|
|
+.video-thumb.small {
|
|
|
|
|
+ width: 48px;
|
|
|
|
|
+ height: 48px;
|
|
|
|
|
+ font-size: 22px;
|
|
|
|
|
+}
|
|
|
|
|
+.asset-meta {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.asset-name {
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ max-width: 170px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+}
|
|
|
|
|
+.asset-sub {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+.asset-id {
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.asset-file-info {
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+}
|
|
|
|
|
+.text-muted {
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+}
|
|
|
|
|
+.asset-select-tip {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.form-tip {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+}
|
|
|
|
|
+.play-status-alert {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.plan-preview {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-title {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-sub {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-body {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: minmax(0, 1fr) 320px;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-stage {
|
|
|
|
|
+ min-height: 420px;
|
|
|
|
|
+ background: #111827;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-media {
|
|
|
|
|
+ max-width: 100%;
|
|
|
|
|
+ max-height: 420px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-image {
|
|
|
|
|
+ object-fit: contain;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-video {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 420px;
|
|
|
|
|
+ background: #000;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-empty {
|
|
|
|
|
+ color: #cbd5e1;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-list {
|
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ max-height: 420px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-list-title {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ padding: 8px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-item:hover {
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-item.active {
|
|
|
|
|
+ background: #ecf5ff;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-item-index {
|
|
|
|
|
+ width: 22px;
|
|
|
|
|
+ height: 22px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #e4e7ed;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-item.active .preview-item-index {
|
|
|
|
|
+ background: #409eff;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-item-thumb {
|
|
|
|
|
+ width: 44px;
|
|
|
|
|
+ height: 44px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-item-meta {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-item-name {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-item-desc {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-main {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-current-info {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ padding: 10px 12px;
|
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #f8fafc;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-current-name {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+.preview-current-desc {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|