CarouselConfig.vue
8.55 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
<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">
<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"
prefix-icon="el-icon-search"
clearable
style="margin-bottom: 5px;"
@input="handleFilterInput">
</el-input>
<vue-easy-tree
ref="deviceTree"
:data="deviceTreeData"
:props="treeProps"
show-checkbox
node-key="code"
:filter-node-method="filterNode"
height="250px"
style="height: 250px; overflow-y: auto; border: 1px solid #dcdfe6; border-radius: 4px; padding: 5px;"
></vue-easy-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>
<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>
</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>
import VueEasyTree from "@wchbrad/vue-easy-tree/index";
export default {
name: "CarouselConfig",
components: {
VueEasyTree
},
props: {
deviceTreeData: {
type: Array,
default: () => []
}
},
data() {
const validateTimeRange = (rule, value, callback) => {
if (!value || value.length !== 2) return callback(new Error('请选择生效时段'));
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;
if (duration < 0) duration += 24 * 3600;
if (duration < this.form.interval) {
return callback(new Error(`时段跨度(${duration}s) 不能小于 轮播间隔(${this.form.interval}s)`));
}
callback();
};
return {
visible: false,
filterText: '',
filterTimer: null, // 【新增】用于防抖
form: {
sourceType: 'all_online',
layout: '16',
interval: 60,
runMode: 'manual',
timeRange: ['08:00:00', '18:00:00'],
selectedDevices: []
},
treeProps: {
label: 'name',
children: 'children'
},
rules: {
interval: [{ required: true, message: '间隔不能为空' }, { type: 'number', min: 30, message: '最少30秒' }],
timeRange: [{ required: true, validator: validateTimeRange, trigger: 'change' }]
}
};
},
// 【移除】移除了 watch filterText,改为在 input 事件中手动触发,控制更精准
methods: {
// 【新增】防抖输入处理 (与 DeviceList 逻辑一致)
handleFilterInput() {
if (this.filterTimer) clearTimeout(this.filterTimer);
this.filterTimer = setTimeout(() => {
if (this.$refs.deviceTree) {
this.$refs.deviceTree.filter(this.filterText);
}
}, 300);
},
// 【修改】递归过滤逻辑 (与 DeviceList 逻辑一致)
// 逻辑:如果节点名称匹配 OR 父级匹配,则显示
// 这确保了搜索时树形结构不会完全被打散,且容易定位
filterNode(value, data, node) {
if (!value) return true;
return (data.name && data.name.toUpperCase().indexOf(value.toUpperCase()) !== -1) ||
(node.parent && node.parent.data && this.filterNode(value, node.parent.data, node.parent));
},
open(currentConfig) {
this.visible = true;
if (currentConfig) {
this.form = { ...currentConfig };
// 回显选中状态
if (this.form.sourceType === 'custom' && this.form.selectedDevices) {
this.$nextTick(() => {
if (this.$refs.deviceTree) {
const keys = this.form.selectedDevices.map(d => d.code);
// setCheckedKeys 会将 key 对应的节点勾选,
// 无论该节点当前是否因为过滤而被隐藏,状态都会被正确设置
this.$refs.deviceTree.setCheckedKeys(keys);
// 打开弹窗时清空上一次的搜索
this.filterText = '';
this.$refs.deviceTree.filter('');
}
})
}
}
},
async handleSave() {
this.$refs.form.validate(async valid => {
if (valid) {
const config = { ...this.form };
if (config.sourceType === 'custom') {
if (!this.$refs.deviceTree) {
this.$message.error("组件未就绪");
return;
}
// 【关键点】getCheckedNodes(false, false)
// 第一个参数 leafOnly: false (我们需要父节点和子节点都拿到,或者根据你的业务只需要子节点)
// 第二个参数 includeHalfChecked: false
// ElementUI 的这个方法会返回所有被勾选的节点,哪怕它现在因为 filterText 而被隐藏了
// 所以"搜索后之前的选择不消失"是原生支持的,只要不重置数据源。
const checkedNodes = this.$refs.deviceTree.getCheckedNodes();
if (checkedNodes.length === 0) {
this.$message.warning("请至少选择一个设备或通道!");
return;
}
config.selectedNodes = checkedNodes;
}
await this.$nextTick();
this.$emit('save', config);
this.visible = false;
}
});
},
resetForm() {
this.filterText = '';
if (this.filterTimer) clearTimeout(this.filterTimer);
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
}
}
};
</script>
<style scoped>
.device-select-box {
margin-top: 10px;
}
.unit-text {
margin-left: 10px;
}
.tip-text {
font-size: 12px;
color: #909399;
line-height: 1.5;
margin-top: 5px;
}
</style>