|
@@ -111,46 +111,59 @@
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 新增/编辑弹窗 -->
|
|
|
|
|
- <el-dialog v-model="dialogVisible" :title="dialogTitle" width="550px" destroy-on-close>
|
|
|
|
|
- <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
|
|
|
|
|
- <el-form-item :label="t('Stream SN')" prop="streamSn">
|
|
|
|
|
- <el-input v-model="form.streamSn" placeholder="自动生成" disabled />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item :label="t('名称')" prop="name">
|
|
|
|
|
- <el-input v-model="form.name" placeholder="请输入名称" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item :label="t('LSS')" prop="lssId">
|
|
|
|
|
- <el-select v-model="form.lssId" placeholder="请选择 LSS" clearable filterable style="width: 100%">
|
|
|
|
|
- <el-option v-for="lss in lssOptions" :key="lss.id" :label="`${lss.lssId} / ${lss.name}`" :value="lss.id" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item :label="t('摄像头')" prop="cameraId">
|
|
|
|
|
- <el-select v-model="form.cameraId" placeholder="请选择摄像头" clearable filterable style="width: 100%">
|
|
|
|
|
- <el-option
|
|
|
|
|
- v-for="camera in cameraOptions"
|
|
|
|
|
- :key="camera.id"
|
|
|
|
|
- :label="`${camera.cameraId} / ${camera.name}`"
|
|
|
|
|
- :value="camera.id"
|
|
|
|
|
- />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item :label="t('推流方式')" prop="streamMethod">
|
|
|
|
|
- <el-select v-model="form.streamMethod" placeholder="请选择推流方式" style="width: 100%">
|
|
|
|
|
- <el-option label="ffmpeg" value="ffmpeg" />
|
|
|
|
|
- <el-option label="obs" value="obs" />
|
|
|
|
|
- <el-option label="gstreamer" value="gstreamer" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item :label="t('命令模板')" prop="commandTemplate">
|
|
|
|
|
- <el-input v-model="form.commandTemplate" type="textarea" :rows="4" placeholder="请输入命令模板" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <el-button @click="dialogVisible = false">{{ t('取消') }}</el-button>
|
|
|
|
|
- <el-button type="primary" :loading="submitLoading" @click="handleSubmit">{{ t('确定') }}</el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
|
|
+ <!-- 新增/编辑抽屉 -->
|
|
|
|
|
+ <el-drawer
|
|
|
|
|
+ v-model="drawerVisible"
|
|
|
|
|
+ :title="drawerTitle"
|
|
|
|
|
+ direction="rtl"
|
|
|
|
|
+ size="500px"
|
|
|
|
|
+ destroy-on-close
|
|
|
|
|
+ class="stream-drawer"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="drawer-content">
|
|
|
|
|
+ <div class="drawer-body">
|
|
|
|
|
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="80px" label-position="left">
|
|
|
|
|
+ <el-form-item label="name:" prop="name">
|
|
|
|
|
+ <el-input v-model="form.name" placeholder="live-stream name" style="width: 200px" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="LSS ID:" prop="lssId">
|
|
|
|
|
+ <el-select v-model="form.lssId" placeholder="请选择" clearable filterable style="width: 200px">
|
|
|
|
|
+ <el-option v-for="lss in lssOptions" :key="lss.id" :label="lss.lssId" :value="lss.id" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="设备ID:" prop="cameraId">
|
|
|
|
|
+ <el-select v-model="form.cameraId" placeholder="请选择" clearable filterable style="width: 260px">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="camera in cameraOptions"
|
|
|
|
|
+ :key="camera.id"
|
|
|
|
|
+ :label="camera.cameraId"
|
|
|
|
|
+ :value="camera.id"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="命令模板:" prop="commandTemplate">
|
|
|
|
|
+ <div class="command-textarea-wrapper">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="form.commandTemplate"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :rows="10"
|
|
|
|
|
+ placeholder="请输入运行参数内容"
|
|
|
|
|
+ maxlength="1000"
|
|
|
|
|
+ show-word-limit
|
|
|
|
|
+ class="command-textarea"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="drawer-footer">
|
|
|
|
|
+ <el-button @click="drawerVisible = false">{{ t('取消') }}</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
|
|
|
|
+ {{ isEdit ? t('更新') : t('添加') }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-drawer>
|
|
|
|
|
|
|
|
<!-- 命令模板查看弹窗 -->
|
|
<!-- 命令模板查看弹窗 -->
|
|
|
<el-dialog v-model="templateDialogVisible" :title="t('命令模板')" width="600px">
|
|
<el-dialog v-model="templateDialogVisible" :title="t('命令模板')" width="600px">
|
|
@@ -172,10 +185,10 @@ import {
|
|
|
updateLiveStream,
|
|
updateLiveStream,
|
|
|
startLiveStream,
|
|
startLiveStream,
|
|
|
stopLiveStream,
|
|
stopLiveStream,
|
|
|
- getLssOptions,
|
|
|
|
|
getCameraOptions
|
|
getCameraOptions
|
|
|
} from '@/api/live-stream'
|
|
} from '@/api/live-stream'
|
|
|
-import type { LiveStreamDTO, LssDTO, CameraInfoDTO, StreamMethod } from '@/types'
|
|
|
|
|
|
|
+import { listAllLssNodes } from '@/api/lss'
|
|
|
|
|
+import type { LiveStreamDTO, LssNodeDTO, CameraInfoDTO, StreamMethod } from '@/types'
|
|
|
import dayjs from 'dayjs'
|
|
import dayjs from 'dayjs'
|
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
|
|
|
@@ -192,13 +205,13 @@ const loading = ref(false)
|
|
|
const submitLoading = ref(false)
|
|
const submitLoading = ref(false)
|
|
|
const actionLoading = ref<Record<number, boolean>>({})
|
|
const actionLoading = ref<Record<number, boolean>>({})
|
|
|
const streamList = ref<LiveStreamDTO[]>([])
|
|
const streamList = ref<LiveStreamDTO[]>([])
|
|
|
-const dialogVisible = ref(false)
|
|
|
|
|
|
|
+const drawerVisible = ref(false)
|
|
|
const templateDialogVisible = ref(false)
|
|
const templateDialogVisible = ref(false)
|
|
|
const formRef = ref<FormInstance>()
|
|
const formRef = ref<FormInstance>()
|
|
|
const currentTemplate = ref('')
|
|
const currentTemplate = ref('')
|
|
|
|
|
|
|
|
// 下拉选项
|
|
// 下拉选项
|
|
|
-const lssOptions = ref<LssDTO[]>([])
|
|
|
|
|
|
|
+const lssOptions = ref<LssNodeDTO[]>([])
|
|
|
const cameraOptions = ref<CameraInfoDTO[]>([])
|
|
const cameraOptions = ref<CameraInfoDTO[]>([])
|
|
|
|
|
|
|
|
// 排序状态
|
|
// 排序状态
|
|
@@ -240,7 +253,7 @@ const form = reactive<{
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const isEdit = computed(() => !!form.id)
|
|
const isEdit = computed(() => !!form.id)
|
|
|
-const dialogTitle = computed(() => (isEdit.value ? t('编辑 LiveStream') : t('新增 LiveStream')))
|
|
|
|
|
|
|
+const drawerTitle = computed(() => (isEdit.value ? t('编辑live-stream') : t('新增live-stream')))
|
|
|
|
|
|
|
|
const rules: FormRules = {
|
|
const rules: FormRules = {
|
|
|
name: [{ required: true, message: t('请输入名称'), trigger: 'blur' }],
|
|
name: [{ required: true, message: t('请输入名称'), trigger: 'blur' }],
|
|
@@ -517,19 +530,15 @@ async function getList() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function loadOptions() {
|
|
async function loadOptions() {
|
|
|
- const useMockData = true
|
|
|
|
|
-
|
|
|
|
|
- if (useMockData) {
|
|
|
|
|
- lssOptions.value = generateMockLssOptions()
|
|
|
|
|
- cameraOptions.value = generateMockCameraOptions()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
try {
|
|
try {
|
|
|
- const [lssRes, cameraRes] = await Promise.all([getLssOptions(), getCameraOptions()])
|
|
|
|
|
|
|
+ // 获取 LSS 节点列表
|
|
|
|
|
+ const lssRes = await listAllLssNodes()
|
|
|
if (lssRes.success && lssRes.data) {
|
|
if (lssRes.success && lssRes.data) {
|
|
|
- lssOptions.value = lssRes.data
|
|
|
|
|
|
|
+ lssOptions.value = lssRes.data.list || []
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 获取摄像头列表(如需要可后续添加)
|
|
|
|
|
+ const cameraRes = await getCameraOptions()
|
|
|
if (cameraRes.success && cameraRes.data) {
|
|
if (cameraRes.success && cameraRes.data) {
|
|
|
cameraOptions.value = cameraRes.data
|
|
cameraOptions.value = cameraRes.data
|
|
|
}
|
|
}
|
|
@@ -568,7 +577,7 @@ function handleAdd() {
|
|
|
streamMethod: 'ffmpeg',
|
|
streamMethod: 'ffmpeg',
|
|
|
commandTemplate: ''
|
|
commandTemplate: ''
|
|
|
})
|
|
})
|
|
|
- dialogVisible.value = true
|
|
|
|
|
|
|
+ drawerVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function handleEdit(row: LiveStreamDTO) {
|
|
function handleEdit(row: LiveStreamDTO) {
|
|
@@ -581,7 +590,7 @@ function handleEdit(row: LiveStreamDTO) {
|
|
|
streamMethod: row.streamMethod,
|
|
streamMethod: row.streamMethod,
|
|
|
commandTemplate: row.commandTemplate || ''
|
|
commandTemplate: row.commandTemplate || ''
|
|
|
})
|
|
})
|
|
|
- dialogVisible.value = true
|
|
|
|
|
|
|
+ drawerVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function showCommandTemplate(row: LiveStreamDTO) {
|
|
function showCommandTemplate(row: LiveStreamDTO) {
|
|
@@ -649,7 +658,7 @@ async function handleSubmit() {
|
|
|
})
|
|
})
|
|
|
if (res.success) {
|
|
if (res.success) {
|
|
|
ElMessage.success(t('修改成功'))
|
|
ElMessage.success(t('修改成功'))
|
|
|
- dialogVisible.value = false
|
|
|
|
|
|
|
+ drawerVisible.value = false
|
|
|
getList()
|
|
getList()
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
@@ -662,7 +671,7 @@ async function handleSubmit() {
|
|
|
})
|
|
})
|
|
|
if (res.success) {
|
|
if (res.success) {
|
|
|
ElMessage.success(t('新增成功'))
|
|
ElMessage.success(t('新增成功'))
|
|
|
- dialogVisible.value = false
|
|
|
|
|
|
|
+ drawerVisible.value = false
|
|
|
getList()
|
|
getList()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -818,4 +827,76 @@ onMounted(() => {
|
|
|
max-height: 400px;
|
|
max-height: 400px;
|
|
|
overflow: auto;
|
|
overflow: auto;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// 抽屉样式
|
|
|
|
|
+.stream-drawer {
|
|
|
|
|
+ :deep(.el-drawer__header) {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+ padding: 16px 20px;
|
|
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-drawer__body) {
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.drawer-content {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.drawer-body {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 18px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-form-item__label) {
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-textarea-wrapper {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-textarea__inner) {
|
|
|
|
|
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ background-color: #fafafa;
|
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
|
+
|
|
|
|
|
+ &:focus {
|
|
|
|
|
+ border-color: #409eff;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.drawer-footer {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ padding: 12px 20px;
|
|
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+
|
|
|
|
|
+ .el-button--primary {
|
|
|
|
|
+ background-color: #409eff;
|
|
|
|
|
+ border-color: #409eff;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover,
|
|
|
|
|
+ &:focus {
|
|
|
|
|
+ background-color: #66b1ff;
|
|
|
|
|
+ border-color: #66b1ff;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|