Vue3 + TypeScript + Element Plus 开启边框 > 调整列宽(拖动表头)> 保存列宽(本地存储)> 加载列宽(读取本地数据)

发布于:2025-06-14 ⋅ 阅读:(23) ⋅ 点赞:(0)

Vue3 + TypeScript + Element Plus 开启边框 > 调整列宽(拖动表头)> 保存列宽(本地存储)> 加载列宽(读取本地数据)

初始效果:

1、开启边框,点击【设置列宽】

 2、调整列宽,在表头的纵向边框处,按住拖动

3、保存列宽,列宽数据保存在本地 localStorage

4、打开页面,加载列宽 ,从本地 localStorage 中读取列宽数据

技术栈:

1、涉及表格属性:border、 @header-dragend

2、涉及表格列属性:prop、width

3、本地存储 localStorage

4、相关逻辑:存储表格列宽 localStorage.setItem、加载表格列宽 localStorage.getItem

相关代码:Reagent.vue

<script setup lang="ts" name="Reagent">
......

// 边框标识
const isBorder = ref(false);

// 设置列宽
const onSetColumnWidthClick = () => {
  isBorder.value = !isBorder.value;
};

// 存储表格列宽
const saveColumnWidth = (newWidth: number, oldWidth: number, column: TableColumnCtx<IReagent>, event: MouseEvent) => {
  let prop = column.property;
  localStorage.setItem(`reagent_colWidth_${prop}`, newWidth.toString());
};

// 加载表格列宽
const loadColumnWidth = (prop: string, defaultValue: number) => {
  let colWidth = localStorage.getItem(`reagent_colWidth_${prop}`);
  return colWidth ? parseInt(colWidth, 10) : defaultValue;
};

......
</script>

<template>
  ......

          <BasePreventReClickButton class="header-btn" type="primary" plain :onClick="onSetColumnWidthClick" :delay="0">
            设置列宽
          </BasePreventReClickButton>
  ......
        
      <el-table
        ref="table"
        :data="store.reagentPageList"
        v-loading="store.loading"
        :border="isBorder"
        highlight-current-row
        stripe
        :show-summary="false"
        style="width: 100%; height: 100%"
        @header-dragend="saveColumnWidth">
        <el-table-column
          type="index"
          prop="index"
          label="序号"
          :width="loadColumnWidth(`index`, 60)"
          fixed="left"
          header-align="center"
          align="center" />
        <el-table-column
          prop="reagentName"
          label="名称 规格"
          min-width="200"
          fixed="left"
          header-align="left"
          sortable
          show-overflow-tooltip>
          <template #default="scope">
            <span class="material-name">{{ scope.row.reagentName }}</span>
            <span class="material-spec">{{ scope.row.reagentSpec }}</span>
          </template>
        </el-table-column>
        <el-table-column
          prop="batchNo"
          label="批号"
          :width="loadColumnWidth(`batchNo`, 120)"
          header-align="left"
          show-overflow-tooltip />
        <el-table-column
          prop="validityDate"
          label="有效期至"
          :width="loadColumnWidth(`validityDate`, 110)"
          header-align="center"
          align="center"
          show-overflow-tooltip />
        <el-table-column
          prop="amount"
          :width="loadColumnWidth(`amount`, 80)"
          header-align="center"
          align="center"
          resizable
          show-overflow-tooltip>
          <template #header>
            <div class="custom-table-column-header-amount-unit-div">库存数量</div>
          </template>
          <template #default="scope">
            <div class="custom-table-row-default-amount-unit-div">
              <span>{{ scope.row.amount }}</span>
              <span>{{ scope.row.reagentUnit }}</span>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="操作" header-align="center" align="center" fixed="right" width="150">
          <template #default="scope">
            <el-button
              class="table-btn"
              type="primary"
              size="default"
              text
              @click="onModifyClick(scope.$index, scope.row)"
              >查改</el-button
            >
            <el-button class="table-btn" type="warning" size="default" text @click="onLogoutClick(scope.row.id)"
              >注销</el-button
            >
            <el-button class="table-btn" type="primary" size="default" text @click="onTransactionsClick(scope.row.id)"
              >明细</el-button
            >
          </template>
        </el-table-column>
      </el-table>

  ......
</template>

完整代码:Reagent.vue

<script setup lang="ts" name="Reagent">
import BasePreventReClickButton from "@/components/base/BasePreventReClickButton.vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox, type TableColumnCtx } from "element-plus";
import { isEqual } from "lodash-es";
import { nextTick, onMounted, ref } from "vue";
import ReagentApplyDialog from "./reagent/comps/ReagentApplyDialog.vue";
import ReagentInDialog from "./reagent/comps/ReagentInDialog.vue";
import ReagentInfoDialog from "./reagent/comps/ReagentInfoDialog.vue";
import ReagentOutDialog from "./reagent/comps/ReagentOutDialog.vue";
import ReagentTransactionsDrawer from "./reagent/comps/ReagentTransactionsDrawer.vue";
import useReagentStore from "./reagent/stores";
import type { IReagent } from "./reagent/types";

const store = useReagentStore();
// 当前表格选择行
const currentSelectedRow = ref<IReagent>({
  id: 0,
  reagentCategory: "",
  reagentNo: "",
  reagentName: "",
  reagentSpec: "",
  reagentUnit: ""
});
// 是否新增
const isNew = ref(true);
// 试剂耗材入库模态框实例对象
const reagentInDialogRef = ref<InstanceType<typeof ReagentInDialog> | null>(null);
// 试剂耗材申领模态框实例对象
const reagentApplyDialogRef = ref<InstanceType<typeof ReagentApplyDialog> | null>(null);
// 试剂耗材出库模态框实例对象
const reagentOutDialogRef = ref<InstanceType<typeof ReagentOutDialog> | null>(null);
// 试剂耗材信息模态框实例对象
const reagentInfoDialogRef = ref<InstanceType<typeof ReagentInfoDialog> | null>(null);
// 试剂耗材流转记录抽屉实例对象
const reagentTransactionsDrawerRef = ref<InstanceType<typeof ReagentTransactionsDrawer> | null>(null);
// 边框标识
const isBorder = ref(false);

// 入库
const onInClick = () => {
  isNew.value = true;
  // 打开试剂耗材入库模态框
  reagentInDialogRef.value?.openDialog();
};

// 申领
const onReceiveClick = () => {
  //  打开试剂耗材申领模态框
  reagentApplyDialogRef.value?.openDialog();
};

// 出库
const onOutClick = () => {
  // 打开试剂耗材出库模态框
  reagentOutDialogRef.value?.openDialog();
};

// 明细查询
const onDetailClick = async () => {
  await reagentTransactionsDrawerRef.value?.openDrawerByQuery();
};

// 刷新数据
const onRefreshClick = async () => {
  await store.fetchReagentPageList();
};

// 查询
const onQueryClick = async () => {
  // 分页页数及显示数量变动监听标识,设置为 false,后面更改 page,不会触发分页监听
  store.onPageOrSizeChangeValid = false;
  // 重置当前页码为 1
  store.queryObj.pageHelper.page = 1;
  // 刷新数据
  await onRefreshClick();
};

// 查改
const onModifyClick = async (index: number, row: IReagent) => {
  isNew.value = false;
  currentSelectedRow.value = row;
  await nextTick();
  reagentInfoDialogRef.value?.openDialog();
};

// 注销
const onLogoutClick = async (id: number) => {
  try {
    await ElMessageBox.confirm("确定注销吗?", "询问", {
      cancelButtonText: "取消",
      confirmButtonText: "确定",
      type: "warning"
    });
    await store.fetchLogoutReagent(id);
    ElMessage.success("注销成功!");
    // 刷新数据
    await onRefreshClick();
  } catch (error) {}
};

// 明细(流转明细)
const onTransactionsClick = async (id: number) => {
  await reagentTransactionsDrawerRef.value?.openDrawerByTransactions(id);
};

// 更新试剂
const handleUpdateReagent = async (reagent: IReagent) => {
  // 两个对象不相同,需要更新数据;如果两个对象相同(所有属性值都相同),不需要更新数据
  if (!isEqual(currentSelectedRow.value, reagent)) {
    try {
      // 发送网络请求,更新数据
      await store.fetchUpdateReagent(reagent);
    } catch (error) {
      return;
    }

    // 使用浅拷贝,复制对象引用,同步更新页面数据
    Object.assign(currentSelectedRow.value, reagent);
  }
};

// 改变页码、显示数量,重新获取数据
const onPageOrSizeChange = async (currentPage: number, pageSize: number) => {
  if (!store.onPageOrSizeChangeValid) {
    return;
  }
  store.queryObj.pageHelper.page = currentPage;
  store.queryObj.pageHelper.size = pageSize;
  // 刷新数据
  await onRefreshClick();
};

// 设置列宽
const onSetColumnWidthClick = () => {
  isBorder.value = !isBorder.value;
};

// 存储表格列宽
const saveColumnWidth = (newWidth: number, oldWidth: number, column: TableColumnCtx<IReagent>, event: MouseEvent) => {
  let prop = column.property;
  localStorage.setItem(`reagent_colWidth_${prop}`, newWidth.toString());
};

// 加载表格列宽
const loadColumnWidth = (prop: string, defaultValue: number) => {
  let colWidth = localStorage.getItem(`reagent_colWidth_${prop}`);
  return colWidth ? parseInt(colWidth, 10) : defaultValue;
};

onMounted(async () => {
  // 刷新数据
  await onRefreshClick();
});
</script>

<template>
  <el-container class="container">
    <el-header class="header">
      <!-- 标题 -->
      <div class="header-title">试剂耗材管理</div>
      <!-- 操作栏 -->
      <div class="header-operation">
        <div>
          <BasePreventReClickButton class="header-btn" type="primary" plain :onClick="onInClick">
            入库
          </BasePreventReClickButton>
          <BasePreventReClickButton class="header-btn" type="primary" plain :onClick="onReceiveClick" :delay="0">
            申领
          </BasePreventReClickButton>
          <BasePreventReClickButton class="header-btn" type="primary" plain :onClick="onOutClick" :delay="0">
            出库
          </BasePreventReClickButton>
          <BasePreventReClickButton class="header-btn" type="primary" plain :onClick="onDetailClick" :delay="0">
            明细查询
          </BasePreventReClickButton>
          <BasePreventReClickButton class="header-btn" type="primary" plain :onClick="onRefreshClick" :delay="0">
            刷新数据
          </BasePreventReClickButton>
          <BasePreventReClickButton class="header-btn" type="primary" plain :onClick="onSetColumnWidthClick" :delay="0">
            设置列宽
          </BasePreventReClickButton>
        </div>
        <div class="query-div">
          <el-input v-model="store.queryObj.reagentName" placeholder="请输入试剂关键字进行查询" clearable>
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
          <BasePreventReClickButton class="query-btn" type="primary" plain :onClick="onQueryClick" :delay="100">
            查询
          </BasePreventReClickButton>
        </div>
      </div>
    </el-header>
    <el-main class="main">
      <!-- 展示区 -->
      <el-table
        ref="table"
        :data="store.reagentPageList"
        v-loading="store.loading"
        :border="isBorder"
        highlight-current-row
        stripe
        :show-summary="false"
        style="width: 100%; height: 100%"
        @header-dragend="saveColumnWidth">
        <el-table-column
          type="index"
          prop="index"
          label="序号"
          :width="loadColumnWidth(`index`, 60)"
          fixed="left"
          header-align="center"
          align="center" />
        <el-table-column
          prop="reagentName"
          label="名称 规格"
          min-width="200"
          fixed="left"
          header-align="left"
          sortable
          show-overflow-tooltip>
          <template #default="scope">
            <span class="material-name">{{ scope.row.reagentName }}</span>
            <span class="material-spec">{{ scope.row.reagentSpec }}</span>
          </template>
        </el-table-column>
        <el-table-column
          prop="batchNo"
          label="批号"
          :width="loadColumnWidth(`batchNo`, 120)"
          header-align="left"
          show-overflow-tooltip />
        <el-table-column
          prop="validityDate"
          label="有效期至"
          :width="loadColumnWidth(`validityDate`, 110)"
          header-align="center"
          align="center"
          show-overflow-tooltip />
        <el-table-column
          prop="amount"
          :width="loadColumnWidth(`amount`, 80)"
          header-align="center"
          align="center"
          resizable
          show-overflow-tooltip>
          <template #header>
            <div class="custom-table-column-header-amount-unit-div">库存数量</div>
          </template>
          <template #default="scope">
            <div class="custom-table-row-default-amount-unit-div">
              <span>{{ scope.row.amount }}</span>
              <span>{{ scope.row.reagentUnit }}</span>
            </div>
          </template>
        </el-table-column>
        <!-- <el-table-column prop="total" label="余额" width="110" header-align="right" align="right" show-overflow-tooltip>
          <template #default="scope">
            {{ formatMoney(scope.row.total, "¥", 2) }}
          </template>
        </el-table-column> -->
        <el-table-column label="操作" header-align="center" align="center" fixed="right" width="150">
          <template #default="scope">
            <el-button
              class="table-btn"
              type="primary"
              size="default"
              text
              @click="onModifyClick(scope.$index, scope.row)"
              >查改</el-button
            >
            <el-button class="table-btn" type="warning" size="default" text @click="onLogoutClick(scope.row.id)"
              >注销</el-button
            >
            <el-button class="table-btn" type="primary" size="default" text @click="onTransactionsClick(scope.row.id)"
              >明细</el-button
            >
          </template>
        </el-table-column>
      </el-table>
    </el-main>
    <el-footer class="footer">
      <!-- 分页 -->
      <el-pagination
        :total="store.total"
        :page-sizes="[20, 50, 100, 200, 500]"
        v-model:page-size="store.queryObj.pageHelper.size"
        v-model:current-page="store.queryObj.pageHelper.page"
        background
        layout="total, sizes, prev, pager, next, jumper"
        :small="false"
        @change="onPageOrSizeChange" />
    </el-footer>
  </el-container>
  <div>
    <!-- 试剂耗材入库模态框 -->
    <ReagentInDialog ref="reagentInDialogRef" :is-new="isNew" />
    <!-- 试剂耗材申领模态框 -->
    <ReagentApplyDialog ref="reagentApplyDialogRef" />
    <!-- 试剂耗材出库模态框 -->
    <ReagentOutDialog ref="reagentOutDialogRef" :is-new="isNew" @refresh="onRefreshClick" />
    <!-- 试剂耗材修改模态框 -->
    <ReagentInfoDialog
      ref="reagentInfoDialogRef"
      :reagent-info="currentSelectedRow"
      @update-reagent="handleUpdateReagent" />
    <!-- 试剂耗材流转记录抽屉 -->
    <ReagentTransactionsDrawer ref="reagentTransactionsDrawerRef" />
  </div>
</template>

<style scoped lang="scss">
// 选择 container 所有直接子元素(不包括孙级)
.container > * {
  margin: 0;
  padding: 0;
}
.container {
  height: 100%;
  border: 1px solid #ebeef5;
  .header {
    height: auto;
    .header-title {
      margin: 10px 10px 20px 10px;
      font-size: 18px;
    }
    .header-operation {
      margin: 10px;
      display: flex;
      justify-content: space-between;
      .query-div {
        display: flex;
        justify-content: flex-end;
        // 设置固定宽度,避免由于宽度自适应,导致页面跳动
        width: 300px;
        // 因为点击查询按钮时,会显示加载动画,撑大了原来的按钮宽度,导致页面跳动,所以设置宽度为100px,预留足够的空间,避免页面跳动
        .query-btn {
          width: 100px;
        }
      }
    }
  }
  .footer {
    height: auto;
    padding: 0 10px;
  }
}
.table-btn {
  margin: 0;
  padding: 0 5px;
}
.custom-table-column-header-amount-unit-div {
  text-align: justify;
  text-align-last: justify;
}
.custom-table-row-default-amount-unit-div {
  display: flex;
  justify-content: space-between;
}
</style>