uni app 表格封装

<template>
  <view
    class="lh-table"
    :style="{
      height: processedRows && processedRows.length ? props.height : 'auto',
    }"
  >
    <scroll-view
      v-if="processedRows.length"
      ref="scrollView"
      class="table"
      scroll-x
      scroll-y
      :style="{ height: processedRows.length ? props.height : 'auto' }"
      :scroll-into-view="scrollIntoView"
      @scrolltolower="scroll"
    >
      <view class="scrollView">
        <view class="table-info">
			<!-- 固定列 -->
          <view ref="scrollViewContent1" class="fixed-cloumn">
            <view class="column" style="display: flex;">
              <view
                v-if="props.showChecker"
                class="column"
                style="width: 60rpx"
              >
                <view
                  :style="props.headerStyle"
                  class="column-title"
                  style="z-index: 10"
                >
                  <checkbox
                    v-if="!props.radio && props.showCheckerAll"
                    @tap="checkAll"
                    :checked="state.checkAll"
                    :class="state.checkAllClass"
                    style="width: 34rpx; height: 34rpx"
                  ></checkbox>
                </view>
                <view
                  v-for="(row, ind) in processedRows"
                  :key="ind"
                  class="column-row column-order"
                  :class="{
                    'child-task-row': row.isChild,
                    'row-even': ind % 2 === 1,
                    'row-odd': ind % 2 === 0,
                  }"
                >
                  <checkbox
                    @tap="changeCheckbox(row, ind)"
                    :class="{ radio: props.radio }"
                    :checked="row[props.checkedStr]"
                    :disabled="Number(row.examineState) === 2"
                    style="width: 34rpx; height: 34rpx"
                  ></checkbox>
                </view>
              </view>
              <view v-if="props.showIndex" class="column" style="width: 60rpx">
                <view :style="props.headerStyle" class="column-title"
                  >序号</view
                >
                <view
                  @tap="tapRow(ind, row)"
                  v-for="(row, ind) in processedRows"
                  :key="ind"
                  class="column-row column-order"
                  :class="{
                    'child-task-row': row.isChild,
                    'row-even': ind % 2 === 1,
                    'row-odd': ind % 2 === 0,
                  }"
                >
                  {{ ind + 1 }}
                </view>
              </view>
              <!-- 固定列需指定宽度 -->
              <view
                v-for="(col, i) in fixedColumnsArray"
                :key="i"
                class="column fixedBox"
                :style="{
                  width: col.width || '80rpx',
                  '--column-width': col.width || '80rpx',
                }"
              >
                <view
                  :style="props.headerStyle"
                  class="column-title"
                  @tap="onFixedHeaderChange(col)"
                  :class="{
                    'title-left': col.align === 'left',
                    'title-right': col.align === 'right',
                    'title-center': col.align === 'center' || !col.align,
                  }"
                >
                  {{ col.title }}
                </view>
                <view
                  v-for="(row, ind) in processedRows"
                  :key="ind"
                  class="column-row"
                  :class="{
                    'child-task-row': row.isChild,
                    'row-even': ind % 2 === 1,
                    'row-odd': ind % 2 === 0,
                  }"
                  :style="{
                    textAlign: col.align || 'center',
                    justifyContent: getJustifyContent(col.align),
                  }"
                  @tap="handleRowTap(row, ind)"
                >
                  <slot :row="row" :name="col.prop" :rowIndex="ind">
                    <uni-tooltip
                      v-if="col.tooltip"
                      :content="row[col.prop]"
                      placement="bottom"
                    >
                      <view
                        :style="{ width: col.width || 'auto' }"
                        class="tooltip"
                        >{{ row[col.prop] }}</view
                      >
                    </uni-tooltip>
                    <view v-else>
                      {{ row[col.prop] }}
                    </view>
                  </slot>
                </view>
              </view>
            </view>
          </view>
		  <!-- 内容列 -->
          <view ref="scrollViewContent2" class="arrange-column">
            <view
              v-for="(col, index) in fixedRowsFalse"
              :key="index"
              class="column"
              :class="{ 'has-child': col && col.hasChild }"
              :id="'column' + index"
              :scrollIntoView="scrollIntoView"
              :style="{ '--column-width': col.width || 'max-content' }"
            >
              <view>
                <view
                  :style="props.headerStyle"
                  class="column-title"
                  @tap="onSortChange(col, index)"
                  :class="{
                    'title-left': col.align === 'left',
                    'title-right': col.align === 'right',
                    'title-center': col.align === 'center' || !col.align,
                  }"
                >
                  {{ col.title }}
                  <view class="arrow-box" v-if="props.isSort">
                    <text
                      class="arrow up"
                      :class="{
                        active: sortMode === 1 && clickedHeaderIndex === index,
                      }"
                    ></text>
                    <text
                      class="arrow down"
                      :class="{
                        active: sortMode === 2 && clickedHeaderIndex === index,
                      }"
                    ></text>
                  </view>
                </view>
                <view v-if="processedRows.length">
                  <view
                    v-for="(row, ind) in processedRows"
                    :key="ind"
                    class="column-row"
                    :class="{
                      'child-task-row': row.isChild,
                      'row-even': ind % 2 === 1,
                      'row-odd': ind % 2 === 0,
                    }"
                    :style="{
                      textAlign: col.align || 'center',
                      whiteSpace: col.width ? 'normal' : 'nowrap',
                      overflow:(col.slot && col.formate)?'hidden':'none',
                      justifyContent: getJustifyContent(col.align)
                    }"
                    @tap="handleRowTap(row, ind)"
                  >
                    <view v-if="col.slot">
                      <slot :row="row" :name="col.prop" :rowIndex="ind"></slot>
                    </view>
                    <view v-else-if="col.formate">
                      <uni-tooltip
                        v-if="col.tooltip"
                        :content="col.formate(row[col.prop], row, ind)"
                        placement="bottom"
                      >
                        <view
                          :style="{ width: col.width || 'auto' }"
                          class="tooltip"
                          >{{ col.formate(row[col.prop], row, ind) }}</view
                        >
                      </uni-tooltip>
                      <text v-else>
                        {{ col.formate(row[col.prop], row, ind) }}
                      </text>
                    </view>
                    <view v-else>
                      <uni-tooltip
                        v-if="col.tooltip"
                        :content="row[col.prop]"
                        placement="bottom"
                      >
                        <view
                          :style="{ width: col.width || 'auto' }"
                          class="tooltip"
                          >{{ row[col.prop] }}</view
                        >
                      </uni-tooltip>
                      <text v-else>
                        {{ row[col.prop] }}
                      </text>
                    </view>
                  </view>
                </view>
              </view>
            </view>
            <view v-if="props.showOper" class="oper-column">
              <view class="column">
                <view :style="props.headerStyle" class="column-title"
                  >操作</view >
                <view
                  v-for="(row, ind) in processedRows"
                  :key="ind"
                  class="column-row"
                  :class="{
                    'child-task-row': row.isChild,
                    'row-even': ind % 2 === 1,
                    'row-odd': ind % 2 === 0,
                  }"
                >
                  <slot :row="row" :rowIndex="ind" name="operCol"></slot>
                </view>
              </view>
            </view>
          </view>
        </view>
        <uni-load-more
          v-if="processedRows.length"
          :status="props.loading ? 'loading' : 'noMore'"
          :content-text="{
            contentdown: '加载中...',
            contentrefresh: '加载中...',
            contentnomore: '没有更多了',
          }"
        ></uni-load-more>
      </view>
    </scroll-view>
    <uni-load-more
      v-else
      :status="props.loading ? 'loading' : 'noMore'"
      :content-text="{
        contentdown: '加载中...',
        contentrefresh: '加载中...',
        contentnomore: '暂无数据',
      }"
    ></uni-load-more>
    <view v-if="props.showFooter" class="footer">
      <slot name="footer">
        <view>计划放点击翻页</view>
      </slot>
    </view>
  </view>
</template>

<script setup>
import {
  ref,
  reactive,
  getCurrentInstance,
  onMounted,
  watch,
  nextTick,
  computed,
} from "vue";
const instance = getCurrentInstance();
const emits = defineEmits([
  "tapOper",
  "tabRow",
  "loadMore",
  "changeRadiobox",
  "changeCheckbox",
  "checkAll",
  "refresh",
  "cancelCheckbox",
  "toggleExpand",
]);
const props = defineProps({
  // 判断是否显示序号
  showIndex: {
    type: Boolean,
    default: false,
  },
  // 用于控制是否显示页脚。默认为false
  showFooter: {
    type: Boolean,
    default: false,
  },
  // 用于控制是否显示操作按钮
  showOper: {
    type: Boolean,
    default: false,
  },
  // 用于控制是否显示复选框等
  showChecker: {
    type: Boolean,
    default: false,
  },
  // 是否展示多选全选按钮
  showCheckerAll: {
    type: Boolean,
    default: true,
  },
  // 接口判断选中的字段名
  checkedStr: {
    type: String,
    default: "selected",
  },
  // 默认多选,打开单选
  radio: {
    type: Boolean,
    default: false,
  },
  // 判断是否显示序号
  showIndex: {
    type: Boolean,
    default: false,
  },
  // 用于定义表格的列信息。默认为空数组
  columns: {
    type: Array,
    default: () => [],
  },
  // 用于定义表格的行数据。默认为空数组,初始20条
  rows: {
    type: Array,
    default: () => [],
  },
  // 用于定义表格头部的样式
  headerStyle: {
    type: String,
    default: "",
  },
  // 用于定义表格或组件的高度
  height: {
    type: String,
    default: "",
  },
  // 是否显示加载状态
  loading: {
    type: Boolean,
    default: false,
  },
  //滚动到的指定位置
  scrollIntoView: {
    type: String,
    default: "column0",
  },
  // 是否排序
  isSort: {
    type: Boolean,
    default: false,
  },
  // 是否显示子项
  showChildTasks: {
    type: Boolean,
    default: true,
  },
  // 子项字段配置
  childConfig: {
    type: Object,
    default: () => ({
      childListField: "childList", // 子项列表字段名
      childCountField: "childCount", // 子项数量字段名
      parentIdField: "id", // 父项ID字段名
    }),
  },
  // 是否启用折叠功能
  enableCollapse: {
    type: Boolean,
    default: true,
  },
  // 默认折叠状态
  defaultCollapsed: {
    type: Boolean,
    default: false,
  },
});
const clickedHeaderIndex = ref(0); //排序下标
const clickedHeaderCode = ref(""); //排序对应的表头code
const sortMode = ref("");
const onSortChange = (column, index) => {
  clickedHeaderIndex.value = index;
  // 展示下标 上箭头/下箭头
  sortMode.value = Number(sortMode.value) + 1;
  sortMode.value = sortMode.value < 3 ? sortMode.value : "";
  emits("onSortChange", { column, sortMode: sortMode.value, index });
};

// 获取选中的数据(考虑禁用行),用于表头全选样式(含“部分选中”态)
const getCheckList = (array, checkedLabel, key = "id") => {
  const enabledRows = (array || []).filter(
    (obj) => Number(obj?.examineState) !== 2
  );
  const checkedEnabled = enabledRows.filter((obj) => obj[checkedLabel]);

  if (enabledRows.length === 0) {
    state.checkAll = false;
    state.checkAllClass = "";
    return;
  }

  if (checkedEnabled.length === enabledRows.length) {
    state.checkAll = true;
    state.checkAllClass = "";
  } else if (checkedEnabled.length === 0) {
    state.checkAll = false;
    state.checkAllClass = "";
  } else {
    state.checkAll = false;
    state.checkAllClass = "variable";
  }
};
// 折叠状态管理
const collapsedRows = ref(new Set());

// 初始化折叠状态
const initCollapseState = () => {
  if (props.defaultCollapsed && props.rows) {
    props.rows.forEach((row) => {
      if (row[props.childConfig.childCountField] > 0) {
        collapsedRows.value.add(row[props.childConfig.parentIdField]);
      }
    });
  }
};

// 切换展开/折叠状态
const toggleExpand = (row) => {
  if (!props.enableCollapse) return;

  const parentId = row[props.childConfig.parentIdField];
  if (collapsedRows.value.has(parentId)) {
    collapsedRows.value.delete(parentId);
  } else {
    collapsedRows.value.add(parentId);
  }

  emits("toggleExpand", {
    row,
    parentId,
    isCollapsed: collapsedRows.value.has(parentId),
  });
};

// 折叠相关方法
const expandAll = () => {
  collapsedRows.value.clear();
};

const collapseAll = () => {
  if (props.rows) {
    props.rows.forEach((row) => {
      if (row[props.childConfig.childCountField] > 0) {
        collapsedRows.value.add(row[props.childConfig.parentIdField]);
      }
    });
  }
};

const expandRow = (parentId) => {
  collapsedRows.value.delete(parentId);
};

const collapseRow = (parentId) => {
  collapsedRows.value.add(parentId);
};

const getCollapsedRows = () => {
  return Array.from(collapsedRows.value);
};

defineExpose({
  getCheckList,
  onSortChange,
  expandAll,
  collapseAll,
  expandRow,
  collapseRow,
  getCollapsedRows,
  toggleExpand,
}); //抛出才去得到
const arrangeColumnListWidth = ref(0);
const arrangeColumnWidth = ref(0);

const extractNumberFromString = (input) => {
  // 使用正则表达式匹配数字部分
  const match = input.match(/\d+/);
  // 如果匹配成功,则返回匹配到的数字,否则返回null
  return match ? parseInt(match[0], 10) : null;
};
const state = reactive({
  checkAll: false,
  checkAllClass: "",
  height: `calc(${props.height} ? '60px' : '1px') - ${
    props.showFooter ? "80px" : "1px"
  }`,
});

const isCheckboxDisabled = (row) => false;

// 纵向滚动条事件
const scroll = (e) => {
  // console.log('开始gun')
  if (e.detail.direction === "bottom") {
    emits("loadMore");
    console.log("滑动到底部");
    return;
  }
};

// 点击单元格事件
const tapRow = (ind, row) => {
  emits("tapRow", {
    index: ind,
    data: row,
  });
};

// 处理行点击事件
const handleRowTap = (row, ind) => {
  // 如果启用了折叠功能,且当前行有子项且不是子项本身,则切换折叠状态
  if (props.enableCollapse && !row.isChild && row.hasChild) {
    toggleExpand(row);
  }
  // 无论是否折叠,都触发普通的行点击事件
  tapRow(ind, row);
};

const onFixedHeaderChange = (column) => {
  emits("onFixedHeaderChange", { column });
};

// 根据对齐方式获取justify-content值
const getJustifyContent = (align) => {
  switch (align) {
    case "left":
      return "flex-start";
    case "right":
      return "flex-end";
    case "center":
    default:
      return "center";
  }
};

// 全选(考虑禁用行)
const checkAll = () => {
  state.checkAllClass = "";
  // 如果当前是半选(variable),点击后应切换为全选
  if (state.checkAllClass === "variable") {
    state.checkAll = true;
  } else {
    state.checkAll = !state.checkAll;
  }
  props.rows.forEach((row) => {
    if (Number(row.examineState) !== 2) {
      row[props.checkedStr] = state.checkAll;
    }
  });
  emits("checkAll", { data: processedRows.value, selected: state.checkAll });
};

// 选中
const changeCheckbox = (row, ind) => {
  if (Number(row.examineState) === 2) return;
  row[props.checkedStr] = !row[props.checkedStr];
  // 单选
  if (props.radio) {
    props.rows.forEach((v, i) => {
      v[props.checkedStr] = ind === i;
    });
    emits("changeRadiobox", { index: ind, data: row });
  } else {
    // 多选
    const allCountLength = processedRows.value.length;
    const checkedCount = processedRows.value.filter(
      (row) => row[props.checkedStr]
    );
    const checkedCountLength = checkedCount.length;
    if (allCountLength == checkedCountLength) {
      state.checkAllClass = "";
      state.checkAll = true;
    }
    if (!checkedCountLength) {
      state.checkAllClass = "";
      state.checkAll = false;
    }
    if (checkedCountLength && checkedCountLength < allCountLength) {
      state.checkAll = false;
      state.checkAllClass = "variable";
    }
    if (!row[props.checkedStr]) {
      //取消选中
      emits("cancelCheckbox", row);
    }
    emits("changeCheckbox", checkedCount);
  }
};
// 除固定列内容
const fixedRowsFalse = computed(() => {
  return props.columns.filter((col) => !col.fixed);
});
const fixedColumnsArray = computed(() => {
  return props.columns.filter((col) => col.fixed);
});
// 处理子项数据的计算属性
const processedRows = computed(() => {
	// 每次变化都更新状态
	getCheckList(props.rows, props.checkedStr);
  if (!props.showChildTasks || !Array.isArray(props.rows)) {
    return props.rows || [];
  }
  const result = [];
  const { childListField, childCountField, parentIdField } = props.childConfig;

  props.rows.forEach((item) => {
    // 添加父任务
    result.push({
      ...item,
      hasChild: (item[childCountField] || 0) > 0,
      isChild: false,
      _raw: item,
    });

    // 如果有子任务且显示子任务,且父项未折叠,添加子任务
    if (
      item[childListField] &&
      Array.isArray(item[childListField]) &&
      item[childListField].length > 0 &&
      !collapsedRows.value.has(item[parentIdField])
    ) {
      item[childListField].forEach((child) => {
        result.push({
          ...child,
          hasChild: (child[childCountField] || 0) > 0,
          isChild: true,
          parentId: item[parentIdField],
          _raw: child,
        });
      });
    }
  });
  return result;
});

// 监听数据变化,重置折叠状态
watch(
  () => props.rows,
  (newRows) => {
    if (newRows && props.defaultCollapsed) {
      initCollapseState();
    }
  },
  { deep: true }
);

// 初始化折叠状态
onMounted(() => {
  initCollapseState();
});
</script>
<style>
</style>
<style lang="scss" scoped>
::v-deep .uni-scroll-view-content {
  display: inline-flex;
}
.lh-table {
  flex: 1;
  width: 100%;
  overflow: hidden;
}

.fixed-cloumn {
  position: sticky;
  left: -3rpx;
  z-index: 10;
  display: inline-block;
  background: #fff;
  border-left: 2rpx solid #fff;
  border-right: 2rpx solid #fff;
  .fixedBox {
    background: #fff;
    z-index: 99;
    width: var(--column-width, auto);
    box-sizing: border-box;
  }
  .column-title {
    z-index: 99;
    background: linear-gradient(#ececfc, #ffffff) !important;
    border-top: 2rpx solid #ffffff;
    border-bottom: none !important;
    width: 100%;
    box-sizing: border-box;
  }
  // 移除固定列内部的 nth-child 斑马纹,统一用 row-even/row-odd 控制

  // 固定列子项样式
  .column-row.child-task-row {
    background-color: #f8f9fa !important;
    border-left: 8rpx solid #e9ecef;
    padding-left: 20rpx;
    font-size: 22rpx;
    color: #6c757d;
    min-height: 80rpx;
    position: relative;

    &::before {
      content: "";
      position: absolute;
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      width: 0;
      height: 0;
      border-left: 8rpx solid #dee2e6;
      border-top: 6rpx solid transparent;
      border-bottom: 6rpx solid transparent;
    }

    &:hover {
      background-color: #e9ecef !important;
      border-left-color: #adb5bd;
    }
  }
}

.oper-column {
  position: sticky;
  right: 0;
  z-index: 10;
  display: inline-block;
  background: #fff;
  .column {
    border: 2rpx solid #fff !important;
    border-top: none !important;
  }
  .column-title {
    z-index: 99;
    background: linear-gradient(#ececfc, #ffffff) !important;
    border-top: 2rpx solid #ffffff;
    border-bottom: none !important;
  }

  // 移除操作列内部的 nth-child 斑马纹,统一用 row-even/row-odd 控制

  // 操作列子项样式
  .column-row.child-task-row {
    background-color: #f8f9fa !important;
    border-left: 8rpx solid #e9ecef;
    padding-left: 20rpx;
    font-size: 22rpx;
    color: #6c757d;
    min-height: 80rpx;
    position: relative;

    &::before {
      content: "";
      position: absolute;
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      width: 0;
      height: 0;
      border-left: 8rpx solid #dee2e6;
      border-top: 6rpx solid transparent;
      border-bottom: 6rpx solid transparent;
    }

    &:hover {
      background-color: #e9ecef !important;
      border-left-color: #adb5bd;
    }
  }
}

.footer {
  height: 80px;
  padding: 16rpx;
  font-size: 18px;
}

.table {
  width: 100%;
  box-sizing: border-box;
  white-space: nowrap;
  display: flex;
  padding: 13rpx;
  .table-info {
    display: flex;
    flex: 1;
  }
  .arrange-column {
    flex: 1;
    display: inline-flex;
    border-right: 2rpx solid #fff;
    .arrange-column-box {
      display: flex;
      flex: 1;
      // overflow-x: scroll;
    }
  }

  .column {
    display: inline-block;
    // border-left: 1px solid #eee;
    flex-grow: 1;
    width: max-content;
    background: #fff;
    // 移除列内 nth-child 斑马纹,避免与全局行级类冲突
  }
  .column:first-child {
    border-left: none;
  }

  .column.has-child {
    flex: none;
    flex-shrink: 0;
  }

  .oper:nth-child(n + 2) {
    margin-left: 16rpx;
  }
}

.column-row {
  text-align: center;
  padding: 10rpx;
  box-sizing: border-box;
  text-overflow: ellipsis;
  min-width: 60rpx;
  font-size: 24rpx;
  color: #000000;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: nowrap;
  min-height: 94rpx;
  background-color: #fff;
  border-bottom: 2rpx solid rgba(123, 123, 123, 0.2);
  // overflow: hidden;
  width: 100%;
  word-break: break-all;
  white-space: nowrap;
  position: relative;
  &.disabled-row {
    opacity: 0.6;
  }

  // 子项样式优化
  &.child-task-row {
    background-color: #f8f9fa !important;
    border-left: 8rpx solid #e9ecef;
    padding-left: 20rpx;
    font-size: 22rpx;
    color: #6c757d;
    min-height: 80rpx;
    position: relative;

    // 子项左侧缩进指示器
    &::before {
      content: "";
      position: absolute;
      left: 0;
      top: 50%;
      transform: translateY(-50%);
      width: 0;
      height: 0;
      border-left: 8rpx solid #dee2e6;
      border-top: 6rpx solid transparent;
      border-bottom: 6rpx solid transparent;
    }

    // 子项悬停效果
    &:hover {
      background-color: #e9ecef !important;
      border-left-color: #adb5bd;
    }

    // 子项内容样式
    .child-content {
      display: flex;
      align-items: center;
      width: 100%;

      // 子项图标
      .child-icon {
        width: 24rpx;
        height: 24rpx;
        margin-right: 8rpx;
        color: #6c757d;
      }

      // 子项文本
      .child-text {
        flex: 1;
        text-align: left;
      }
    }
  }
}

.column-title {
  height: 64rpx !important;
  line-height: 40rpx;
  font-weight: 500;
  color: #868686;
  position: sticky;
  position: -webkit-sticky;
  top: -2rpx !important;
  font-size: 24rpx;
  padding: 10rpx;
  z-index: 9;
  background: linear-gradient(#ececfc, #ffffff);
  border-top: 2rpx solid #ffffff;
  border-bottom: none !important;
  text-align: center;
  overflow: hidden;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  word-break: break-all;
  white-space: nowrap;

  &.title-left {
    justify-content: flex-start;
    text-align: left;
  }

  &.title-right {
    justify-content: flex-end;
    text-align: right;
  }

  &.title-center {
    justify-content: center;
    text-align: center;
  }
}

::v-deep .uni-checkbox-input {
  width: 32rpx;
  height: 32rpx;
  box-sizing: border-box;
  border: 1px solid #7b7b7b;
  background-color: transparent;
}

::v-deep .uni-checkbox-input svg {
  color: #e70c0c;

  path {
    fill: #e70c0c !important;
  }
}

.radio {
  ::v-deep .uni-checkbox-input {
    border-radius: 50%;
  }
}

::v-deep .variable .uni-checkbox-input::after {
  height: 30rpx;
  content: "-";
  font-size: 28px;
  line-height: 20rpx;
  color: #e70c0c;
  display: inline-block;
}

.no_more {
  text-align: center;
  font-size: 24rpx;
  font-weight: 500;
  color: #868686;
  padding: 0 40rpx;
}

.loading-state {
  text-align: center;
  padding: 40rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;

  .loading-spinner {
    width: 40rpx;
    height: 40rpx;
    border: 4rpx solid #f3f3f3;
    border-top: 4rpx solid #e70c0c;
    border-radius: 50%;
    animation: spin 1s linear infinite;
    margin-bottom: 16rpx;
  }

  text {
    font-size: 24rpx;
    color: #868686;
  }
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.state-more {
  padding: 10rpx;
  flex-direction: row;
  font-size: 24rpx;
  color: #868686;
  text-align: center;
  .loading-spinner {
    width: 25rpx;
    height: 25rpx;
    margin-bottom: 0rpx;
    margin-right: 16rpx;
  }
}

// 排序图标
.arrow-box {
  position: relative;
  bottom: 0rpx;
  right: 8rpx;
  top: -5rpx;
  display: inline-block;
}
.arrow {
  display: block;
  position: relative;
  width: 10px;
  height: 8px;
  // border: 1px red solid;
  left: 5px;
  overflow: hidden;
  cursor: pointer;
}
.down {
  top: 3px;
}
.down::after {
  content: "";
  width: 8px;
  height: 8px;
  position: absolute;
  left: 2px;
  top: -5px;
  transform: rotate(45deg);
  background-color: #ccc;
}
.down.active::after {
  background-color: #e70c0c;
}
.up::after {
  content: "";
  width: 8px;
  height: 8px;
  position: absolute;
  left: 2px;
  top: 5px;
  transform: rotate(45deg);
  background-color: #ccc;
}
.up.active::after {
  background-color: #e70c0c;
}
.scrollView {
  width: 100%;
  display: inline-table;
  box-sizing: border-box;
}

// 展示省略号
.tooltip {
  white-space: nowrap; /* 禁止换行 */
  overflow: hidden; /* 隐藏溢出内容 */
  text-overflow: ellipsis;
}

// 子项层级样式增强
.child-task-row {
  // 子项缩进层级样式
  &.level-1 {
    padding-left: 40rpx;
    border-left-width: 8rpx;
  }

  &.level-2 {
    padding-left: 60rpx;
    border-left-width: 12rpx;
  }

  &.level-3 {
    padding-left: 80rpx;
    border-left-width: 16rpx;
  }

  // 子项状态样式
  &.status-active {
    border-left-color: #28a745;
    background-color: #f8fff9 !important;
  }

  &.status-pending {
    border-left-color: #ffc107;
    background-color: #fffdf8 !important;
  }

  &.status-completed {
    border-left-color: #6c757d;
    background-color: #f8f9fa !important;
  }

  &.status-error {
    border-left-color: #dc3545;
    background-color: #fff8f8 !important;
  }
}
.row-even {
  background-color: rgba(190, 190, 190, 0.2) !important;
}
.row-odd {
  background-color: #fff !important;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值