CarouselConfig.vue
9.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<template>
<el-dialog
title="轮播策略配置"
:visible.sync="visible"
width="650px"
append-to-body
@close="resetForm"
>
<el-form ref="form" :model="form" label-width="120px" :rules="rules">
<!-- 1. 轮播范围 -->
<el-form-item label="轮播范围" prop="sourceType">
<el-radio-group v-model="form.sourceType">
<el-radio label="all_online">所有在线设备 (自动同步)</el-radio>
<!-- 【修改】开放手动选择 -->
<el-radio label="custom">手动选择设备</el-radio>
</el-radio-group>
<!-- 【新增】手动选择的树形控件 -->
<div v-show="form.sourceType === 'custom'" class="device-select-box">
<el-input
placeholder="搜索设备名称(仅搜索已加载节点)"
v-model="filterText"
size="mini"
style="margin-bottom: 5px;">
</el-input>
<el-tree
ref="deviceTree"
:props="treeProps"
:load="loadNode"
lazy
show-checkbox
node-key="code"
height="250px"
style="height: 250px; overflow-y: auto; border: 1px solid #dcdfe6; border-radius: 4px; padding: 5px;"
></el-tree>
</div>
<div class="tip-text">
<i class="el-icon-info"></i>
{{ form.sourceType === 'all_online'
? '将自动从左侧设备列表中筛选状态为"在线"的设备进行循环播放。'
: '请勾选上方需要轮播的设备或通道。勾选父级设备代表选中其下所有通道。'
}}
<div style="margin-top: 5px; font-weight: bold; color: #E6A23C;">⚠️ 为保证播放流畅,轮播间隔建议设置为45秒以上</div>
</div>
</el-form-item>
<!-- 2. 分屏布局 (保持不变) -->
<el-form-item label="分屏布局" prop="layout">
<!-- ... 保持不变 ... -->
<el-select v-model="form.layout" placeholder="请选择布局" style="width: 100%">
<el-option label="四分屏 (2x2)" value="4"></el-option>
<el-option label="九分屏 (3x3)" value="9"></el-option>
<el-option label="十六分屏 (4x4)" value="16"></el-option>
<el-option label="二十五分屏 (5x5)" value="25"></el-option>
<el-option label="三十六分屏 (6x6)" value="36"></el-option>
<el-option label="1+9 异形屏" value="1+9"></el-option>
<el-option label="1+11 异形屏" value="1+11"></el-option>
</el-select>
</el-form-item>
<!-- ... 其它配置保持不变 ... -->
<el-form-item label="轮播间隔" prop="interval">
<el-input-number v-model="form.interval" :min="30" :step="5" step-strictly controls-position="right"></el-input-number>
<span class="unit-text">秒</span>
<div style="font-size: 12px; color: #909399; margin-top: 5px;">
提示:为保证播放流畅,最小间隔30秒,建议设置45秒以上
</div>
</el-form-item>
<!-- 执行模式保持不变 -->
<el-form-item label="执行模式" prop="runMode">
<el-radio-group v-model="form.runMode">
<el-radio label="manual">手动控制 (立即开始,手动停止)</el-radio>
<el-radio label="schedule">定时计划 (自动启停)</el-radio>
</el-radio-group>
</el-form-item>
<!-- 时段选择保持不变 -->
<transition name="el-zoom-in-top">
<div v-if="form.runMode === 'schedule'" class="schedule-box">
<el-form-item label="生效时段" prop="timeRange" label-width="80px" style="margin-bottom: 0">
<el-time-picker
is-range
v-model="form.timeRange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="HH:mm:ss"
style="width: 100%"
>
</el-time-picker>
</el-form-item>
</div>
</transition>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="visible = false">关 闭</el-button>
<el-button type="primary" @click="handleSave">确认并启动</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
name: "CarouselConfig",
// 【新增】接收父组件传来的设备树数据
props: {
deviceTreeData: {
type: Array,
default: () => []
}
},
data() {
// 定义一个自定义校验函数
const validateTimeRange = (rule, value, callback) => {
if (!value || value.length !== 2) {
return callback(new Error('请选择生效时段'));
}
// 1. 辅助函数:将 HH:mm:ss 转为秒
const toSeconds = (str) => {
const [h, m, s] = str.split(':').map(Number);
return h * 3600 + m * 60 + s;
};
const start = toSeconds(value[0]);
const end = toSeconds(value[1]);
let duration = end - start;
// 处理跨天情况 (例如 23:00 到 01:00)
if (duration < 0) {
duration += 24 * 3600;
}
// 2. 核心校验:时长必须大于间隔
if (duration < this.form.interval) {
return callback(new Error(`时段跨度(${duration}s) 不能小于 轮播间隔(${this.form.interval}s)`));
}
callback();
};
return {
visible: false,
filterText: '',
form: {
sourceType: 'all_online',
layout: '16',
interval: 60, // 默认60秒
runMode: 'manual',
timeRange: ['08:00:00', '18:00:00'],
selectedDevices: [] // 存储选中的设备
},
treeProps: {
label: 'name',
children: 'children',
isLeaf: (data) => data.type === '5' // 假设 type 5 是通道(叶子)
},
rules: { interval: [
{ required: true, message: '间隔不能为空' },
{ type: 'number', min: 30, message: '间隔最少为30秒,以确保视频流有足够时间加载' }
],
timeRange: [
{ required: true, validator: validateTimeRange, trigger: 'change' } // 使用自定义校验
]
}
};
},
watch: {
// 监听搜索框
filterText(val) {
// 添加安全检查,防止树未挂载时报错
if (this.$refs.deviceTree) {
this.$refs.deviceTree.filter(val);
}
}
},
methods: {
loadNode(node, resolve) {
// 1. 根节点:直接返回 props 中的 deviceTreeData
if (node.level === 0) {
return resolve(this.deviceTreeData);
}
// 2. 非根节点
const data = node.data;
// 如果已经有子节点(可能在左侧列表已经加载过),直接返回
if (data.children && data.children.length > 0) {
return resolve(data.children);
} else {
// 其他情况(如已经是通道)
resolve([]);
}
},
open(currentConfig) {
this.visible = true;
if (currentConfig) {
this.form = { ...currentConfig };
// 如果是手动模式,需要回显选中状态
if (this.form.sourceType === 'custom' && this.form.selectedDevices) {
this.$nextTick(() => {
// 只勾选叶子节点,element-ui会自动勾选父节点
const keys = this.form.selectedDevices.map(d => d.code);
this.$refs.deviceTree.setCheckedKeys(keys);
})
}
}
},
async handleSave() {
console.log('🔴 [DEBUG] handleSave 被调用');
this.$refs.form.validate(async valid => {
console.log('🔴 [DEBUG] 表单校验结果:', valid);
if (valid) {
console.log('[CarouselConfig] 校验通过,准备保存配置');
const config = { ...this.form };
console.log('🔴 [DEBUG] 配置对象创建完成:', config);
if (config.sourceType === 'custom') {
console.log('🔴 [DEBUG] 进入自定义模式分支');
// 添加安全检查
if (!this.$refs.deviceTree) {
console.error('🔴 [DEBUG] deviceTree 未找到');
this.$message.error("设备树未加载完成,请稍后再试");
return;
}
console.log('🔴 [DEBUG] 准备获取选中节点');
// 获取所有勾选的节点(包括设备和通道)
const checkedNodes = this.$refs.deviceTree.getCheckedNodes();
console.log(`[CarouselConfig] 选中节点数: ${checkedNodes.length}`);
console.log('🔴 [DEBUG] 选中节点:', checkedNodes);
// 校验
if (checkedNodes.length === 0) {
console.warn('🔴 [DEBUG] 未选中任何节点');
this.$message.warning("请至少选择一个设备或通道!");
return;
}
config.selectedNodes = checkedNodes;
}
console.log('[CarouselConfig] 准备发送配置:', config);
console.log('🔴 [DEBUG] 即将发送 save 事件');
// 让出主线程,避免阻塞UI
await this.$nextTick();
console.log('🔴 [DEBUG] nextTick 完成');
this.$emit('save', config);
console.log('🔴 [DEBUG] save 事件已发送');
this.visible = false;
console.log('🔴 [DEBUG] 对话框已关闭');
} else {
console.warn('[CarouselConfig] 表单校验失败');
}
});
console.log('🔴 [DEBUG] handleSave 执行完毕(validate 是异步的)');
},
resetForm() {
this.filterText = '';
}
}
};
</script>
<style scoped>
.device-select-box {
margin-top: 10px;
}
/* 其他样式保持不变 */
</style>