基于element-plus封装table组件

当前组件已经发布到npm库中,使用 pnpm add @dripadmin/drip-table 即可安装

表格本身是透明的, 实际使用时,需要自己设置背景色.

文章目录

    • [1. 需求分析](#1. 需求分析)
    • [2. 项目初始化与架构设计](#2. 项目初始化与架构设计)
    • [3. 组件设计与实现](#3. 组件设计与实现)
    • [4. 组件功能与API文档](#4. 组件功能与API文档)
      • [4.1 DripTable 组件](#4.1 DripTable 组件)
      • [4.2 行操作工具栏配置](#4.2 行操作工具栏配置)
    • [5. 打包与发布](#5. 打包与发布)
      • [5.1 打包组件库](#5.1 打包组件库)
      • [5.2 发布到NPM](#5.2 发布到NPM)
        • [5.2.1 准备发布](#5.2.1 准备发布)
        • [5.2.2 登录NPM](#5.2.2 登录NPM)
        • [5.2.3 发布包](#5.2.3 发布包)
        • [5.2.4 版本更新](#5.2.4 版本更新)
      • [5.3 使用发布的组件](#5.3 使用发布的组件)
    • [6. 总结与进阶](#6. 总结与进阶)

1. 需求分析

Element Plus提供了功能强大的el-table组件,但在实际业务系统中,存在大量重复的增删改查的操作,

所以针对现有的el-table进行封装后,只需要提供json类数据, 即可实现表单表格的操作, 提高我们的效率,

所需求大致如下:

  • 简化配置:通过JSON配置生成复杂表格
  • 工具栏:内置表格工具栏,支持自定义按钮和操作
  • 行操作:支持行级别的操作按钮
  • 分页控制:集成分页功能
  • 自定义渲染:支持自定义列渲染
  • 国际化:支持多语言
  • 主题定制:支持自定义主题
  • TypeScript支持:完整的类型定义

整体实现的效果如下图:

2. 项目初始化与架构设计

2.1 项目初始化

首先,我们需要创建一个基于Vue3和TypeScript的组件库项目:

bash 复制代码
# 创建项目目录
mkdir drip-table
cd drip-table

# 初始化package.json
pnpm init

# 安装核心依赖
pnpm add vue element-plus -P
pnpm add typescript vite @vitejs/plugin-vue @types/node -D

2.2 目录结构设计

组件库的目录结构如下:

复制代码
drip-table/
├── packages/              # 组件源码
│   ├── components/        # 组件实现
│   │   ├── drip-table/    # 表格组件
│   │   │   ├── index.vue  # 主组件
│   │   │   ├── toolbar/   # 工具栏组件
│   │   │   └── row-toolbar/ # 行操作组件
│   │   └── drip-form/     # 表单组件(可选)
│   ├── types/             # 类型定义
│   └── index.ts           # 入口文件
├── playgrounds/           # 示例项目
│   └── drip-table-demo/   # 演示项目
├── package.json           # 包配置
├── tsconfig.json          # TypeScript配置
└── vite.lib.config.ts     # 打包配置

2.3 基础配置文件

package.json
json 复制代码
{
  "name": "drip-table",
  "version": "0.1.0",
  "description": "基于Element Plus的表格组件封装",
  "main": "dist/drip-table.umd.js",
  "module": "dist/drip-table.es.js",
  "types": "dist/types/index.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "dev": "cd playgrounds/drip-table-demo && pnpm run dev",
    "build": "vite build --config vite.lib.config.ts",
    "preview": "cd playgrounds/drip-table-demo && pnpm run preview"
  },
  "keywords": [
    "vue3",
    "element-plus",
    "table",
    "component"
  ],
  "author": "drip admin",
  "license": "MIT",
  "peerDependencies": {
    "vue": "^3.2.0",
    "element-plus": "^2.2.0"
  }
}
tsconfig.json
json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "paths": {
      "@/*": ["./packages/*"]
    }
  },
  "include": ["packages/**/*.ts", "packages/**/*.d.ts", "packages/**/*.tsx", "packages/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
vite.lib.config.ts
typescript 复制代码
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: resolve(__dirname, 'packages/index.ts'),
      name: 'DripTable',
      fileName: (format) => `drip-table.${format}.js`
    },
    rollupOptions: {
      external: ['vue', 'element-plus'],
      output: {
        globals: {
          vue: 'Vue',
          'element-plus': 'ElementPlus'
        }
      }
    }
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, 'packages')
    }
  }
});

3. 组件设计与实现

3.1 类型定义

首先,我们需要定义组件的类型:

typescript 复制代码
// packages/types/drip-table.ts
import type { CSSProperties } from "vue";

export type Align = "left" | "center" | "right";

export interface RowToolbarAction {
  label: string;
  type?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'default';
  disabled?: boolean;
  event: string;
  link?: boolean;
}

export interface DripTableRowToolBar {
  label?: string;
  width?: number | string;
  align?: Align;
  fixed?: boolean | "left" | "right";
  size?: "small" | "default" | "large";
  actions: RowToolbarAction[];
}

export interface DripTableColumn {
  label: string;
  prop?: string;
  type?: "selection" | "index" | "expand";
  width?: number | string;
  minWidth?: number | string;
  fixed?: boolean | "left" | "right";
  sortable?: boolean | "custom";
  align?: Align;
  headerAlign?: Align;
  showOverflowTooltip?: boolean;
  slot?: string;
  headerSlot?: string;
  children?: DripTableColumn[];
}

export interface DripTablePagination {
  total: number;
  pageSize: number;
  currentPage: number;
  layout?: string;
  pageSizes?: number[];
  size?: "small" | "default" | "large";
  background?: boolean;
  align?: Align;
}

export interface DripTableToolbarConfig {
  // 工具栏配置...
}

export interface DripTableProps {
  data: any[];
  columns: DripTableColumn[];
  rowKey?: string;
  pagination?: DripTablePagination;
  toolbarLeft?: DripTableToolbarConfig;
  toolbarRight?: DripTableToolbarConfig;
  rowToolbar?: DripTableRowToolBar;
  height?: string | number;
  maxHeight?: string | number;
  stripe?: boolean;
  border?: boolean;
  size?: "large" | "default" | "small";
  fit?: boolean;
  showHeader?: boolean;
  highlightCurrentRow?: boolean;
  showOverflowTooltip?: boolean;
  emptyText?: string;
  defaultExpandAll?: boolean;
  expandRowKeys?: any[];
  defaultSort?: { prop: string; order: "ascending" | "descending" };
  tooltipEffect?: "dark" | "light";
  showSummary?: boolean;
  sumText?: string;
  summaryMethod?: (data: any) => any[];
  spanMethod?: (data: any) => any;
  selectOnIndeterminate?: boolean;
  indent?: number;
  lazy?: boolean;
  load?: (row: any, treeNode: any, resolve: (data: any[]) => void) => void;
  treeProps?: { children: string; hasChildren: string };
  tableLayout?: "fixed" | "auto";
  scrollbarAlwaysOn?: boolean;
  flexible?: boolean;
}

export interface DripTableInstallOptions {
  locale?: string;
  i18n?: any;
  ssr?: boolean;
}

3.2 组件实现

主组件实现
vue 复制代码
<!-- packages/components/drip-table/index.vue -->
<template>
  <ElConfigProvider :locale="elementLocale">
    <div
      class="drip-table-wrapper"
      :style="mergedWrapperStyle"
      :id="String(tableKey)"
      :class="[wrapperClass, { 'is-maximized': isMaximized, 'hide-ui': hideUIOnMaximize }]"
    >
      <!-- 工具栏 -->
      <div v-if="showAnyToolbar" class="drip-table__toolbars">
        <div class="drip-table__toolbar--left">
          <Toolbar
            v-if="toolbarLeftCfg"
            :config="toolbarLeftCfg"
            :columns="columns"
            :data="data"
            :table-key="tableKey"
            @refresh="emit('refresh')"
            @size-change="onSizeChange"
            @columns-visibility-change="onColumnsVisibilityChange"
            @columns-order-change="onColumnsOrderChange"
            @primary-action="emit('primary-action')"
            @maximize-toggle="onToggleMaximize"
          /> 
        </div>
        <div class="drip-table__toolbar--right">
          <Toolbar
            v-if="toolbarRightCfg"
            :config="toolbarRightCfg"
            :columns="columns"
            :data="data"
            :table-key="tableKey"
            @refresh="emit('refresh')"
            @size-change="onSizeChange"
            @columns-visibility-change="onColumnsVisibilityChange"
            @columns-order-change="onColumnsOrderChange"
            @primary-action="emit('primary-action')"
            @maximize-toggle="onToggleMaximize"
          />
        </div>
      </div>

      <!-- 表格 -->
      <ElTable
        ref="tableRef"
        v-bind="tableProps"
        :data="data"
        :height="tableHeight"
        :max-height="tableMaxHeight"
        :size="tableSize"
        @selection-change="emit('selection-change', $event)"
        @sort-change="emit('sort-change', $event)"
        @cell-click="emit('cell-click', $event)"
        @row-click="emit('row-click', $event)"
      >
        <template v-for="(column, index) in visibleColumns" :key="index">
          <ElTableColumn
            v-if="!column.children || column.children.length === 0"
            :prop="column.prop"
            :label="column.label"
            :type="column.type"
            :width="column.width"
            :min-width="column.minWidth"
            :fixed="column.fixed"
            :sortable="column.sortable"
            :align="column.align"
            :header-align="column.headerAlign"
            :show-overflow-tooltip="column.showOverflowTooltip ?? showOverflowTooltip"
          >
            <template #header="headerScope">
              <slot v-if="column.headerSlot" :name="column.headerSlot" :column="column" :scope="headerScope" />
              <span v-else>{{ column.label }}</span>
            </template>
            <template #default="scope">
              <slot v-if="column.slot" :name="column.slot" :row="scope.row" :column="column" :scope="scope" />
              <span v-else>{{ column.prop ? scope.row[column.prop] : '' }}</span>
            </template>
          </ElTableColumn>
          <ElTableColumn v-else :label="column.label">
            <!-- 嵌套列 -->
            <template v-for="(child, childIndex) in column.children" :key="`${index}-${childIndex}`">
              <!-- 嵌套列实现 -->
            </template>
          </ElTableColumn>
        </template>

        <!-- 行操作工具栏 -->
        <ElTableColumn 
          v-if="rowToolbar" 
          :label="rowToolbar.label || '操作'" 
          :width="rowToolbar.width || 100"
          :align="rowToolbar.align || 'center'"
          :fixed="rowToolbar.fixed || 'right'"
        >
          <template #default="scope">
            <RowToolbar 
              :actions="rowToolbar.actions || []" 
              :row="scope.row"
              :size="rowToolbar.size || 'small'"
              @action="(eventName, row) => emit('row-action', eventName, row)"
            />
          </template>
        </ElTableColumn>
      </ElTable>

      <!-- 分页 -->
      <div v-if="hasPagination" class="drip-table__pagination" :class="paginationClass" :style="paginationMerged?.style">
        <ElPagination
          v-model:current-page="paginationState.currentPage"
          v-model:page-size="paginationState.pageSize"
          :page-sizes="paginationMerged?.pageSizes"
          :layout="paginationMerged?.layout"
          :total="paginationMerged?.total ?? 0"
          :background="paginationMerged?.background"
          :size="paginationMerged?.size"
          @size-change="onPageSizeChange"
          @current-change="onCurrentPageChange"
        />
      </div>
    </div>
  </ElConfigProvider>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick } from 'vue';
import { ElTable, ElTableColumn, ElPagination, ElConfigProvider } from 'element-plus';
import type { DripTableProps, DripTableColumn, DripTablePagination } from '@/types/drip-table';
import Toolbar from './toolbar/index.vue';
import RowToolbar from './row-toolbar/index.vue';

// 组件逻辑实现...
</script>

<style scoped>
.drip-table-wrapper {
  width: 100%;
  position: relative;
}

.drip-table__toolbars {
  display: flex;
  justify-content: space-between;
  margin-bottom: 16px;
}

.drip-table__pagination {
  margin-top: 16px;
  display: flex;
  justify-content: flex-end;
}

/* 更多样式... */
</style>
行操作工具栏组件
vue 复制代码
<!-- packages/components/drip-table/row-toolbar/index.vue -->
<template>
  <div class="drip-table-row-toolbar">
    <el-button-group v-if="group">
      <el-button
        v-for="action in props.actions"
        :key="action.event"
        :type="action.type"
        :size="props.size || 'small'"
        :link="action.link || false"
        @click="handleAction(action.event)"
      >
        {{ action.label }}
      </el-button>
    </el-button-group>
    <template v-else>
      <el-button
        v-for="action in props.actions"
        :key="action.event"
        :type="action.type"
        :size="props.size || 'small'"
        :link="action.link || false"
        @click="handleAction(action.event)"
      >
        {{ action.label }}
      </el-button>
    </template>
  </div>
</template>

<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
import type { RowToolbarAction } from '@/types/drip-table';

const props = defineProps({
  actions: {
    type: Array as () => RowToolbarAction[],
    default: () => [],
  },
  row: {
    type: Object,
    default: () => ({}),
  },
  size: {
    type: String,
    default: 'small',
  },
  group: {
    type: Boolean,
    default: false,
  },
});

const emit = defineEmits(['action']);

const handleAction = (eventName: string) => {
  emit("action", eventName, props.row);
};
</script>

<style scoped>
.drip-table-row-toolbar {
  display: flex;
  gap: 8px;
}
</style>

3.3 入口文件

typescript 复制代码
// packages/index.ts
import type { App } from 'vue';
import DripTableVue from './components/drip-table/index.vue';
import DripFormVue from './components/drip-form/index.vue';
import RowToolbarVue from './components/drip-table/row-toolbar/index.vue';
import type {
  DripTableProps,
  DripTableColumn,
  DripTablePagination,
  DripTableToolbarConfig,
  DripTableRowToolBar,
  DripTableInstallOptions,
} from './types/drip-table';
import type { DripFormConfig, DripFormItem } from './types/drip-form';

export const DripTable = Object.assign(DripTableVue, {
  install(app: App, options?: DripTableInstallOptions) {
    app.component((DripTableVue as any).name || 'DripTable', DripTableVue);
    app.component('DripTableRowToolbar', RowToolbarVue);
    app.provide('locale', options ?? { locale: null, i18n: null, ssr: false });
  },
});

export const DripForm = Object.assign(DripFormVue, {
  install(app: App) {
    app.component((DripFormVue as any).name || 'DripForm', DripFormVue);
  },
});

export const DripTableRowToolbar = RowToolbarVue;

export type {
  DripTableProps,
  DripTableColumn,
  DripTablePagination,
  DripTableToolbarConfig,
  DripTableRowToolBar,
};

export type { DripFormConfig, DripFormItem };

4. 组件功能与API文档

4.1 DripTable 组件

属性
属性名 类型 默认值 说明
data Array [] 表格数据
columns Array [] 表格列配置
rowKey String 'id' 行数据的唯一标识
pagination Object - 分页配置
toolbarLeft Object - 左侧工具栏配置
toolbarRight Object - 右侧工具栏配置
rowToolbar Object - 行操作工具栏配置
height String/Number - 表格高度
maxHeight String/Number - 表格最大高度
stripe Boolean false 是否为斑马纹表格
border Boolean false 是否带有边框
size String 'default' 表格大小
... ... ... 更多属性参考Element Plus的Table组件
事件
事件名 说明 参数
selection-change 当选择项发生变化时触发 selection
sort-change 当表格的排序条件发生变化时触发 { column, prop, order }
row-click 当某一行被点击时触发 row, column, event
row-action 当行操作按钮被点击时触发 eventName, row
refresh 当刷新按钮被点击时触发 -
page-change 当页码改变时触发 currentPage
page-size-change 当每页显示条数改变时触发 pageSize
primary-action 当主操作按钮被点击时触发 -
插槽
插槽名 说明 作用域参数
[column.slot] 自定义列内容 { row, column, scope }
[column.headerSlot] 自定义列头内容 { column, scope }

4.2 行操作工具栏配置

javascript 复制代码
// 行操作工具栏配置示例
const rowToolbar = {
  label: '操作',
  width: 220,
  align: 'center',
  fixed: 'right',
  size: 'small',
  actions: [
    { label: '新增', type: 'primary', event: 'add' },
    { label: '修改', type: 'warning', event: 'edit' },
    { label: '删除', type: 'danger', event: 'delete' }
  ]
};

5. 打包与发布

5.1 打包组件库

bash 复制代码
# 执行打包命令
pnpm run build

打包后的文件将输出到dist目录,包含以下文件:

  • drip-table.es.js - ES模块格式
  • drip-table.umd.js - UMD格式
  • types/ - TypeScript类型定义

5.2 发布到NPM

5.2.1 准备发布
  1. 确保package.json中的信息正确:
json 复制代码
{
{
 "name": "@dripadmin/drip-table",
  "version": "0.2.5",
  "description": "",
  "license": "MIT",
  "type": "module",
  "main": "dist/index.cjs",
  "module": "dist/index.mjs",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    },
    "./style.css": "./dist/style.css"
  },
  "files": [
    "dist",
    "readme.md"
  ],
  // ...其他配置
}
  1. 创建.npmignore文件,排除不需要发布的文件:

    源码和开发文件

    packages/
    playgrounds/
    node_modules/
    .vscode/
    .idea/

    配置文件

    .gitignore
    .eslintrc.js
    .prettierrc
    tsconfig.json
    vite.lib.config.ts

    其他文件

    *.log

5.2.2 登录NPM
bash 复制代码
# 登录NPM
npm login

输入用户名、密码和邮箱,如果有双因素认证,还需要输入验证码。

5.2.3 发布包
bash 复制代码
# 发布包
npm publish

如果是第一次发布,可能需要添加--access=public参数:

bash 复制代码
npm publish --access=public
5.2.4 版本更新

当需要更新版本时,修改package.json中的版本号,然后重新打包和发布:

bash 复制代码
# 更新版本号
npm version patch  # 小版本更新
npm version minor  # 中版本更新
npm version major  # 大版本更新

# 打包
pnpm run build

# 发布
npm publish

5.3 使用发布的组件

在其他项目中安装和使用:

bash 复制代码
# 安装组件
pnpm add @dripadmin/drip-table

在Vue项目中注册和使用:

在main.ts 中引入全局的样式

javascript 复制代码
// main.ts
import "@dripadmin/drip-table/style.css";

然后在业务组件中例如菜单管理中使用:

vue 复制代码
<template>
  <DripForm
    :config="formConfig"
    @submit="onFormSubmit"
    @reset="onFormReset"
    @change="onFormChange"
  />
  <DripTable
    :columns="columns"
    :data="rows"
    :pagination="pagination"
    :toolbar-right="toolbarRight"
    :elTableProps="elTableProps"
    :row-toolbar="tableRowToolbar"
    @page-size-change="onPageSizeChange"
    @page-current-change="onPageCurrentChange"
    @refresh="onRefresh"
    @row-click="onRowClick"
  >
    <template #titleHeader>
      <span>菜单名称</span>
    </template>
    <template #titleCell="{ row }">
      <span>{{ row.title }}</span>
    </template>
  </DripTable>
</template>

<script setup lang="ts">
import { onMounted, ref } from "vue";
import { DripTable,DripForm } from "@dripadmin/drip-table";
import type {
  DripTableColumn,
  DripTablePagination,
  DripTableToolbarConfig,
  DripTableRowToolBar,
  DripFormConfig,
} from "@dripadmin/drip-table";
import { getMenuListApi } from "@/api/menu_api";

// 列定义
const columns = ref<DripTableColumn[]>([
  { type: "index", label: "序", width: 60, align: "center" },
  {
    label: "菜单名称",
    prop: "title",
    slot: "titleCell",
    headerSlot: "titleHeader",
    minWidth: 160,
  },
  { label: "路径", prop: "path", minWidth: 200 },
  { label: "图标", prop: "icon", minWidth: 120 },
  { label: "类型", prop: "type", minWidth: 100, align: "center" },
  { label: "状态", prop: "status", minWidth: 100, align: "center" },
  { label: "排序", prop: "order", minWidth: 80, align: "center" },
]);

const tableRowToolbar = ref<DripTableRowToolBar>({
  actions: [
    { label: "新增", type: "primary", event: "add" },
    { label: "修改", type: "warning", event: "edit" },
    { label: "删除", type: "danger", event: "delete" },
  ],
});

// 数据与分页
const rows = ref<any[]>([]);
const pagination = ref<DripTablePagination>({
  total: 0,
  pageSize: 10,
  currentPage: 1,
});

// 工具条(右侧显示刷新/大小/列设置/最大化)
const toolbarRight = ref<DripTableToolbarConfig>({
  showRefresh: true,
  showSize: true,
  showColumnSetting: true,
  showFullscreen: true,
});

// 透传 el-table 原生属性
const elTableProps = ref<Record<string, any>>({
  border: true,
  size: "default",
});

// 加载菜单数据(兼容不同返回结构)
async function loadData() {
  const page = pagination.value.currentPage;
  const size = pagination.value.pageSize;
  const res: any = await getMenuListApi({ page, pageSize: size });
  const list =
    res?.list ?? res?.records ?? res?.rows ?? (Array.isArray(res) ? res : []);
  const total = res?.total ?? list.length ?? 0;
  rows.value = list || [];
  pagination.value.total = total || 0;
}

function onPageSizeChange(size: number) {
  pagination.value.pageSize = size;
  pagination.value.currentPage = 1;
  loadData();
}

function onPageCurrentChange(page: number) {
  pagination.value.currentPage = page;
  loadData();
}

function onRefresh() {
  pagination.value.currentPage = 1;
  loadData();
}

function onRowClick(eventName: string, row: any) {
  console.log("点击行操作:", eventName, row);
}

onMounted(() => {
  loadData();
});


const formConfig = ref<DripFormConfig>({
  items: [
    {
      type: "input",
      label: "名称",
      field: "name",
      placeholder: "输入名称",
      width: 220,
    },
    {
      type: "select",
      label: "类型",
      field: "type",
      options: [
        { label: "目录", value: "0" },
        { label: "页面", value: "1" },
        { label: "按钮", value: "2" },
        { label: "链接", value: "3" },
      ],
      width: 140,
    },
    {
      type: "select",
      label: "状态",
      field: "status",
      options: [
        { label: "启用", value: "启用" },
        { label: "停用", value: "停用" },
      ],
      width: 140,
    },
  ],
});

// 筛选条件
const filters = ref<{
  keyword: string;
  type: string | null;
  status: string | null;
}>({ keyword: "", type: null, status: null });
function onFormSubmit(values: Record<string, any>) {
  filters.value = { ...filters.value, ...values } as any;
  pagination.value.currentPage = 1;
  loadData();
}
function onFormReset(values: Record<string, any>) {
  filters.value = { keyword: "", type: null, status: null };
  pagination.value.currentPage = 1;
  loadData();
}
function onFormChange(field: string, value: any, values: Record<string, any>) {
  filters.value = { ...filters.value, ...values } as any;
}


</script>

6. 总结与进阶

该组件部分代码使用AI自动生成,再进行加工优化,基本完成了基于Element Plus的表格组件二次封装, 已经发布在NPM库中。

这个组件库可以帮助我们在项目中快速实现简单的表格查询操作,提高开发效率, 后续会继续完善.

做为学习的一部分, 会逐步应用到dripadmin项目中.


相关推荐
咚咚咚小柒4 小时前
【前端】用el-popover做通用悬停气泡(可设置弹框宽度)
前端·javascript·vue.js·elementui·html·scss
Stringzhua6 小时前
ElementUi【饿了么ui】
前端·ui·elementui
尔嵘7 小时前
vue2+elementUi实现自定义表格框选复制粘贴
前端·javascript·elementui
星光一影8 小时前
HIS系统天花板,十大核心模块,门诊/住院/医保全流程打通,医院数字化转型首选
java·spring boot·后端·sql·elementui·html·scss
博客zhu虎康1 天前
Element中 el-tree 如何隐藏 Tree 组件中的父节点 Checkbox
javascript·vue.js·elementui
我总是词不达意2 天前
vue3 + el-upload组件集成阿里云视频点播从本地上传至点播存储
前端·vue.js·阿里云·elementui
通往曙光的路上2 天前
day9_elementPlus2
javascript·vue.js·elementui
Queen_sy5 天前
vue3 el-date-picker 日期选择器校验规则-选择日期范围不能超过七天
javascript·vue.js·elementui
左手吻左脸。6 天前
解决el-select因为弹出层层级问题,不展示下拉选
javascript·vue.js·elementui