## 蓝鲸任务日志组件

用于查看任务执行的具体日志文件，方便排查问题。任何任务执行历史，都需要保留任务日志，比如：执行作业、安装插件等。

### 效果图

![效果图](./example.png)

### 如何使用

```shell
npm i @blueking/task-log
// vue2项目需要安装如下库
npm i @blueking/bkui-library
```

```vue
<template>
  <TaskLog
    :status="status"
    :title="title"
    :type="type"
    :data="data"
    :loading="loading"
    :height="height"
    :enable-statistics="enableStatistics"
    :enable-minimap="enableMinimap"
    :statistics="statistics"
    :rolling-loading="rollingLoading"
    :modules="modules"
    :is-custom-modules="isCustomModules"
    :enable-rolling-loading="enableRollingLoading"
    :show-step-skip-fn="showStepSkipFn"
    :show-step-retry-fn="showStepRetryFn"
    @refresh="refresh"
    @auto-refresh="autoRefresh"
    @download="download"
    @retry="handleRetry"
    @skip="handleSkip"
  />
</template>
<script lang="ts" setup>
  import { PropType } from 'vue';

  import { IStatisticsItem, IStep, LogType } from '@blueking/task-log/src/lib/@types/index';
  // vue2.7 导入方式
  import TaskLog from '@blueking/task-log/vue2';
  import '@blueking/task-log/vue2/vue2.css';
  // vue3导入方式
  // import TaskLog from '@blueking/task-log';

  defineProps({
    // 状态
    status: {
      type: String, // FAILED,HALFSUCCESS,LOADING,SUCCESS,TERMINATE,WAITING
    },
    // 标题
    title: {
      type: String,
      default: '',
    },
    // 自定义模块
    modules: {
      type: Array as PropType<IModule[]>,
      default: () => [],
    },
    // 完全自定义模块列表（不自动插入默认"日志"tab）
    isCustomModules: {
      type: Boolean,
      default: false,
    },
    // 日志类型
    type: {
      type: String as PropType<LogType>,
      default: 'default',
    },
    // 日志数据
    data: {
      type: [Array, Object] as PropType<IStep | IStep[]>,
      default: () => [],
    },
    // 日志加载loading
    loading: {
      type: Boolean,
      default: false,
    },
    // 高度
    height: {
      type: [String, Number],
      default: 560,
    },
    // 开启右侧统计状态栏
    enableStatistics: {
      type: Boolean,
      default: true,
    },
    // 是否开启 minimap
    enableMinimap: {
      type: Boolean,
      default: true,
    },
    // 自定义统计规则
    statistics: {
      type: Array as PropType<IStatisticsItem[]>,
      default: () => [],
    },
    // 滚动加载loading
    rollingLoading: {
      type: Boolean,
      default: false,
    },
    // 是否开启滚动加载
    enableRollingLoading: {
      type: [Boolean, String] as PropType<'manual' | boolean>,
      default: false,
    },
    // 跳过按钮
    showStepSkipFn: {
      type: Function,
      default: () => false,
    },
    // 重试按钮
    showStepRetryFn: {
      type: Function,
      default: () => false,
    },
  });

  const emits = defineEmits(['download', 'refresh', 'auto-refresh', 'skip', 'retry']);

  function refresh() {
    emits('refresh'); // 刷新按钮
  }

  function autoRefresh(v: boolean) {
    emits('auto-refresh', v); // 启动自动刷新
  }

  function download() {
    emits('download'); // 下载
  }

  function handleRetry(data: IStep) {
    emits('retry', data);
  }

  function handleSkip(data: IStep) {
    emits('skip', data);
  }
</script>
```

### 完整案例

```vue
<template>
  <div class="task-log">
    <div class="flex items-center mb-[16px]">
      <bk-select
        v-model="logConfig.status"
        :clearable="false"
        :list="statusList"
        prefix="状态"
      />
      <bk-input
        class="ml-[8px] !w-[210px]"
        v-model="logConfig.title"
        prefix="标题"
        clearable
      />
      <bk-input
        class="ml-[8px] !w-[210px]"
        v-model="count"
        :min="0"
        prefix="数据量"
        suffix="条"
        type="number"
        @change="handleAddData"
      />
      <bk-select
        class="ml-[8px]"
        v-model="logConfig.type"
        :clearable="false"
        :list="typeList"
        prefix="日志类型"
      />
      <bk-checkbox
        class="ml-[8px]"
        v-model="logConfig.enableStatistics"
        >统计搜索</bk-checkbox
      >
      <bk-checkbox
        class="ml-[8px]"
        v-model="logConfig.enableMinimap"
        >启用 MiniMap</bk-checkbox
      >
      <bk-checkbox
        class="ml-[8px]"
        @change="handleClearData"
        >清空数据</bk-checkbox
      >
      <bk-radio-group
        class="ml-[8px]"
        v-model="logConfig.enableRollingLoading"
      >
        <bk-radio :label="true">滚动加载</bk-radio>
        <bk-radio label="manual">滚动加载(手动)</bk-radio>
      </bk-radio-group>
    </div>
    <BkTaskLog
      :data="stepData"
      :enable-rolling-loading="logConfig.enableRollingLoading"
      :enable-statistics="logConfig.enableStatistics"
      :enable-minimap="logConfig.enableMinimap"
      :loading="logConfig.loading"
      :modules="logConfig.modules"
      :rolling-loading="logConfig.rollingLoading"
      :statistics="logConfig.statistics"
      :status="logConfig.status"
      :title="logConfig.title"
      :type="logConfig.type"
      :show-step-skip-fn="showStepSkipFn"
      :show-step-retry-fn="showStepRetryFn"
      @download="download"
      @load-more="loadMore"
      @refresh="handleAddData"
      @skip="handleSkip"
      @retry="handleRetry"
    >
      <template #content="{ activeModule }">
        <div class="text-[#fff] h-[360px] flex items-center justify-center">自定义模块: {{ activeModule }}</div>
      </template>
    </BkTaskLog>
  </div>
</template>
<script lang="ts" setup>
  import { onBeforeMount, ref } from 'vue';

  import { Message } from 'bkui-vue';

  import { IModule, IStatisticsItem, IStep, LogType } from '../src/lib/@types';
  import { Status } from '../src/lib/common/constants';
  import BkTaskLog from '../src/lib/index';

  const statusList = ref(Object.keys(Status).map(key => ({ label: key, value: key })));
  const typeList = ref([
    {
      label: '默认',
      value: 'default',
    },
    {
      label: '日志步骤',
      value: 'multi-task-anchor',
    },
    {
      label: '日志步骤（不带锚点）',
      value: 'multi-task',
    },
  ]);

  interface ILogProps {
    status: Status;
    title: string;
    type: LogType;
    loading: boolean;
    enableStatistics: boolean;
    enableMinimap: boolean;
    statistics: Array<IStatisticsItem>;
    modules: IModule[];
    enableRollingLoading: 'manual' | boolean;
    rollingLoading: boolean;
  }

  const count = ref(100);
  const stepData = ref<IStep[]>([
    {
      id: '1',
      name: '1. 创建计算平台数据开发任务',
      status: 'SUCCESS',
      startTime: '2023-09-20 18:17:35',
      endTime: '2023-09-21 18:17:30',
      data: [
        { log: '[2023-09-20 18:17:35 INFO] 开始 初始化进程状态.', timestamp: '2023-09-20 18:17:35', level: 'ERROR' },
        { log: '[2023-09-20 18:17:35 INFO] 初始化进程状态 成功', groupStart: true, timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 18:17:35 INFO] 开始 更新插件部署状态为UNKNOWN.', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 18:17:35 INFO] 更新插件部署状态为UNKNOWN 成功', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 18:17:35 INFO] 开始 下发脚本.', groupEnd: true, timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:38 INFO] 开始 初始化进程状态.', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:39 INFO] 初始化进程状态 成功', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:39 INFO] 开始 切换订阅启用状态.', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:39 INFO] 策略 -> 1597「奇怪的策略」切换为：启用', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:39 INFO] 切换订阅启用状态 成功', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:39 INFO] 开始 更新插件部署状态为UNKNOWN.', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:39 INFO] 更新插件部署状态为UNKNOWN 成功', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:39 INFO] 开始 下发脚本.', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:45 INFO] 下发脚本 成功', timestamp: '2023-09-20 18:17:35' },
        { log: '[2023-09-20 22:47:46 INFO] 开始 启动进程.' },
      ],
    },
    {
      id: '2',
      name: '2. 发布API',
      status: 'WAITING',
      data: [],
    },
  ]);

  const logConfig = ref<ILogProps>({
    status: Status.SUCCESS,
    title: '安装插件',
    type: 'default',
    enableStatistics: true,
    enableMinimap: true,
    enableRollingLoading: false,
    loading: false,
    rollingLoading: false,
    modules: [
      {
        text: '模块A',
        value: 'moduleA',
      },
      {
        text: '模块B',
        value: 'moduleB',
      },
    ],
    statistics: [
      {
        id: 'agent',
        regex: 'agent',
        desc: '点击搜索agent',
      },
    ],
  });

  const download = () => {
    Message({
      theme: 'warning',
      message: '请配置下载链接',
    });
  };

  const showStepSkipFn = (data: IStep) => {
    return data.status === 'WAITING';
  };

  const showStepRetryFn = (data: IStep) => {
    return data.status === 'FAILED';
  };

  const handleRetry = (data: IStep) => {
    console.log(data);
  };

  const handleSkip = (data: IStep) => {
    console.log(data);
  };

  const loadMore = () => {
    logConfig.value.rollingLoading = true;
    setTimeout(() => {
      const newData = new Array(count.value).fill(undefined).map(() => ({
        log: '[2023-09-20 22:47:39 INFO] 开始 切换订阅启用状态.',
        timestamp: '2023-09-20 18:17:35',
      }));
      stepData.value[0].data.push(...newData);
      logConfig.value.rollingLoading = false;
    }, 2000);
  };

  const handleClearData = (v: boolean) => {
    if (v) {
      stepData.value[0].data = [];
    } else {
      handleAddData();
    }
  };

  const handleAddData = () => {
    const newData = new Array(count.value).fill(undefined).map(() => ({
      log: '[2023-09-20 22:47:39 INFO] 开始 切换订阅启用状态.',
      timestamp: '2023-09-20 18:17:35',
    }));

    stepData.value[0].data = newData;
    loading();
  };

  const loading = () => {
    logConfig.value.loading = true;

    setTimeout(() => {
      logConfig.value.loading = false;
    }, 2000);
  };

  onBeforeMount(() => {
    new Array(count.value).fill(undefined).forEach(() => {
      stepData.value[0].data.push({
        log: '[2023-09-20 22:47:39 INFO] 开始 切换订阅启用状态.',
        timestamp: '2023-09-20 18:17:35',
      });
    });
    loading();
  });
</script>
```

### 属性

| 名称                 | 描述                                                        | 类型            | 可选值                                               | 默认值      |
| -------------------- | ----------------------------------------------------------- | --------------- | ---------------------------------------------------- | ----------- |
| status               | 状态                                                        | 枚举            | FAILED,HALFSUCCESS,LOADING,SUCCESS,TERMINATE,WAITING | --          |
| title                | 标题                                                        | string          | --                                                   | --          |
| modules              | 自定义模块                                                  | Array           | --                                                   | []          |
| isCustomModules      | 完全自定义模块列表，启用后不再自动在第一项插入默认"日志"tab | boolean         | true,false                                           | false       |
| type                 | 类型                                                        | 枚举            | 'default'， 'multi-task'， 'multi-task-anchor'       | default     |
| data                 | 数据                                                        | [Array, Object] | --                                                   | --          |
| loading              | 是否加载中                                                  | boolean         | true,false                                           | false       |
| height               | 高度                                                        | number          | --                                                   | --          |
| statistics           | 自定义右侧统计规则                                          | Array           | --                                                   | []          |
| enableStatistics     | 是否开启右侧统计状态栏                                      | boolean         | true,false                                           | false       |
| enableMinimap        | 是否开启 minimap                                            | boolean         | true,false                                           | true        |
| enableRollingLoading | 是否开启滚动加载                                            | boolean         | true,false                                           | false       |
| rollingLoading       | 滚动加载loading                                             | boolean         | true,false                                           | false       |
| showStepSkipFn       | 控制跳过按钮显示和隐藏的回调，回调参数为对应步骤的数据      | function        | --                                                   | () => false |
| showStepRetryFn      | 控制重试按钮显示和隐藏的回调，回调参数为对应步骤的数据      | function        | --                                                   | () => false |

### 事件

| 名称         | 描述         | 参数       |
| ------------ | ------------ | ---------- |
| download     | 日志下载     | --         |
| refresh      | 刷新         | --         |
| auto-refresh | 开启自动刷新 | true/false |
| load-more    | 加载更多     | --         |
| skip         | 跳过         | --         |
| retry        | 重试         | --         |
