CarouselConfig.vue 9.63 KB
<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>