当前位置:   article > 正文

Vite + Vue3 + Ts 《企业级项目》二次封装 el-table、el-pagination、el-tooltip、el-dialog_vue3+ts el-table

vue3+ts el-table

 ​前期回顾    ​      

Vite项目,vite + vue3 + ts + vuex + vue-router + axios + scss + 自动导入api(就是用v3不需要引入api直接使用)_0.活在风浪里的博客-CSDN博客_vue3+vite​webpack回顾 ​ (移动端打包)一步一步,一步 从代码到,打包成为手机App,上传至nginx服务器 (Vue项目)_0.活在风浪里的博客-CSDN博客_移动端打包一步一步,一步 从代码到,打包成为手机App,上传至ngnix服务器 (Vue项目) https://blog.csdn.net/m0_57904695/article/details/122500485?ops_request_misc=%257B%2522request%255Fid%2522%253A%25221656https://blog.csdn.net/m0_57904695/article/details/125487996?spm=1001.2014.3001.5501

目录

封装的功能有哪些?

最简单示例

类似这样:

Vue3 表格封装

目录结构:

子组件:newTable

抽离的 emits.ts

抽离的  props.ts

global.d.ts

全局css

newForm

页面使用:

效果: 

el-tooltip 

子组件:

父组件:

el-dialog

子组件:

全局动画弹框css

父组件:


 封装了一个子组件(table、分页),在不同页面引入

封装的功能有哪些?

 分页、表格排序、文字居中、溢出隐藏、操作列、开关、宽、最小宽、type类型(selection/index/expand)、格式化 、不同页面不同操作列、vuex、vue持久化插件、

说思路:data数据请求接口拿到,表头数据一般也是后台接口,如没,前台可自定义自己写

最简单示例

  1. //lable指定表头名 //prop指定每一项数据
  2. <el-table :data="tableData">
  3. <el-table-column
  4. :label="item.label"
  5. :prop="item.prop"
  6. :key="index" v-for="(item,index) in tableHeader"
  7. >
  8. </el-table-column>
  9. </el-table>
  10. //表头
  11. tableHeader:[
  12. {label:'姓名' , prop : 'uname'},
  13. {label:'年龄' , prop : 'age'},
  14. {label:'性别' , prop : 'sex'},
  15. ],
  16. //表数据
  17. tableData:[
  18. {uname:"小明",age:'20',sex:'男'},
  19. {uname:"小黑",age:'18',sex:'男'},
  20. ]

类似这样:

 念及此!那开始正文

Vue3 表格封装

目录结构:

子组件:newTable

  1. <template>
  2. <div class="container">
  3. <el-card shadow="hover">
  4. <el-scrollbar max-height="300px" v-if="isShowSearchRegion" class="wrap">
  5. <slot name="search"></slot>
  6. </el-scrollbar>
  7. <el-table
  8. v-bind="$attrs"
  9. stripe
  10. style="width: 100%"
  11. :data="tableData"
  12. :border="tableBorder"
  13. :height="excludeSearchAreaAfterTableHeight"
  14. >
  15. <template #empty>
  16. <el-empty :image-size="emptyImgSize" description="暂无数据" />
  17. </template>
  18. <el-table-column
  19. type="index"
  20. label="序号"
  21. min-width="60"
  22. :index="orderHandler"
  23. align="center"
  24. />
  25. <el-table-column
  26. v-for="item in tableHeader"
  27. v-bind="item"
  28. :key="item.prop"
  29. >
  30. <template #default="{ row }" v-if="item.slotKey">
  31. <template v-if="item.slotKey.includes('default')">
  32. <el-link
  33. type="primary"
  34. :underline="false"
  35. @click="handleEdit(row)"
  36. >编辑</el-link
  37. >
  38. <el-popconfirm
  39. title="确定删除吗?"
  40. @confirm="handleDelete(row.id)"
  41. >
  42. <template #reference>
  43. <el-link class="ml15" type="danger" :underline="false"
  44. >删除</el-link
  45. >
  46. </template>
  47. </el-popconfirm>
  48. </template>
  49. <slot
  50. v-for="slot in item.slotKey.split(',')"
  51. :name="slot"
  52. :row="row"
  53. ></slot>
  54. </template>
  55. </el-table-column>
  56. </el-table>
  57. <!-- 分页 -->
  58. <el-pagination
  59. v-if="paginationFlag"
  60. background
  61. :page-sizes="pageSizesArr"
  62. :current-page="pageNum"
  63. :page-size="pageSize"
  64. :layout="layout"
  65. :total="total"
  66. @size-change="handleSizeChange"
  67. @current-change="handleCurrentChange"
  68. ></el-pagination>
  69. </el-card>
  70. </div>
  71. </template>
  72. <script setup lang="ts">
  73. import { onMounted, ref } from "vue";
  74. import myEmits from "./newTableConfig/emits";
  75. import myProps from "./newTableConfig/props";
  76. const emits = defineEmits(myEmits);
  77. const props = defineProps(myProps);
  78. // 序号根据数据长度计算
  79. const orderHandler = (index: number) => {
  80. const { pageNum, pageSize } = props;
  81. // 第0条 * 每页条数 + 当前索引+1
  82. return (pageNum - 1) * pageSize + index + 1;
  83. };
  84. // 页数改变
  85. const handleSizeChange = (val: number | string) =>
  86. emits("handleSizeChange", val);
  87. // 当前页改变
  88. const handleCurrentChange = (val: number | string) =>
  89. emits("handleCurrentChange", val);
  90. // 编辑、删除
  91. const handleEdit = (row: object) => emits("handleEdit", row);
  92. const handleDelete = (id: number) => emits("handleDelete", id);
  93. // 搜索区域高度及默认值
  94. const Height = ref();
  95. // 减去搜索区域高度后的table
  96. const excludeSearchAreaAfterTableHeight = ref("calc(100vh - 165px)");
  97. // 获取表格高度-动态计算搜索框高度(onMounted、resize,208是已知的面包屑tebView高度)
  98. const updateHeight = () => {
  99. let wrapEl = document.querySelector(".wrap") as HTMLDivElement | null;
  100. if (!wrapEl) return;
  101. Height.value = wrapEl.getBoundingClientRect().height;
  102. if (props.isShowSearchRegion) {
  103. excludeSearchAreaAfterTableHeight.value = `calc(100vh - ${
  104. 165 + Height.value
  105. }px)`;
  106. }
  107. };
  108. onMounted(() => {
  109. // 表格下拉动画
  110. const tableContainer = <HTMLElement>document.querySelector(".container");
  111. setTimeout(() => {
  112. if (tableContainer) tableContainer.style.transform = "translateY(0)";
  113. updateHeight();
  114. }, 300);
  115. });
  116. window.addEventListener("resize", updateHeight);
  117. </script>
  118. <style scoped lang="scss">
  119. .container {
  120. padding: 15px;
  121. transform: translateY(-100%);
  122. transition: transform 0.5s;
  123. // background-color: #870404;
  124. background-color: #f8f8f8;
  125. .el-scrollbar {
  126. // border: 1px solid pink;
  127. min-height: 100px;
  128. width: 100%;
  129. height: fit-content;
  130. }
  131. .el-card {
  132. width: 100%;
  133. height: 100%;
  134. }
  135. .el-pagination {
  136. margin-left: 15%;
  137. height: 35px;
  138. margin-top: 16px;
  139. }
  140. }
  141. // 穿透父组件
  142. :deep(.el-link) {
  143. padding-left: 10px;
  144. }
  145. </style>

抽离的 emits.ts

  1. export default [
  2. // 这里是自定义事件 emits
  3. 'handleSizeChange',
  4. 'handleCurrentChange',
  5. 'handleEdit',
  6. 'handleDelete',
  7. ];

抽离的  props.ts

  1. export default {
  2. // 表头数据
  3. tableHeader: {
  4. type: Array as () => TableHeader[],
  5. default: function () {
  6. return [];
  7. },
  8. },
  9. // 表格显示的数据
  10. tableData: {
  11. default: function () {
  12. return [];
  13. },
  14. },
  15. // 边框
  16. tableBorder: {
  17. type: Boolean,
  18. default: false,
  19. },
  20. // 总页数
  21. total: {
  22. type: Number,
  23. default: 0,
  24. },
  25. // 分页的页容量数组
  26. pageSizesArr: {
  27. type: Array as () => number[],
  28. default() {
  29. return [10, 20, 30, 50];
  30. },
  31. },
  32. // 分页的布局
  33. layout: {
  34. type: String,
  35. default: 'total, sizes, prev, pager, next, jumper',
  36. },
  37. // 分页是否显示
  38. paginationFlag: {
  39. type: Boolean,
  40. default: true,
  41. },
  42. // 当前页
  43. pageNum: {
  44. type: Number,
  45. default: 1,
  46. },
  47. // 页容量
  48. pageSize: {
  49. type: Number,
  50. default: 10,
  51. },
  52. // empty的图片大小
  53. emptyImgSize: {
  54. type: Number,
  55. default: 200,
  56. },
  57. // 搜索区域是否显示
  58. isShowSearchRegion: {
  59. type: Boolean,
  60. default: true,
  61. },
  62. // 是否展示连续序号
  63. isSerialNo: {
  64. type: Boolean,
  65. default: true,
  66. },
  67. };

global.d.ts

  1. // new-table
  2. //表头数据类型定义
  3. declare interface TableHeader<T = any> {
  4. label: string;
  5. prop: string;
  6. align?: string;
  7. overHidden?: boolean;
  8. minWidth?: string;
  9. sortable?: boolean;
  10. type?: string;
  11. fixed?: string;
  12. width?: string;
  13. isActionColumn?: boolean;
  14. isCustomizeColumn?: boolean;
  15. slotKey?: string;
  16. }
  17. /*
  18. 允许任何字符串作为索引
  19. 不然会报错, 使用动态属性名,需要使用索引签名
  20. */
  21. declare type SearchFormType = {
  22. [key: string]: string;
  23. };
  24. declare type FormOptions = {
  25. type: string;
  26. props: {
  27. label: string;
  28. placeholder: string;
  29. type: string;
  30. clearable: boolean;
  31. };
  32. vm: string;
  33. selectOptions?: [
  34. {
  35. value: string | number;
  36. label: string;
  37. }
  38. ];
  39. cascaderOptions?: any;
  40. };

全局css

  1. /* 全局样式 个人不允许修改全局样式【重要!!!】*/
  2. /* 开关样式
  3. ------------------------------------------------ */
  4. .el-switch__label--left {
  5. position: relative;
  6. left: 45px;
  7. color: #fff;
  8. z-index: -1111;
  9. }
  10. .el-switch__core {
  11. width: 50px !important;
  12. }
  13. .el-switch__label--right {
  14. position: relative;
  15. right: 46px;
  16. color: #fff;
  17. z-index: -1111;
  18. }
  19. .el-switch__label--right.is-active {
  20. z-index: 1111;
  21. color: #fff !important;
  22. }
  23. .el-switch__label--left.is-active {
  24. z-index: 1111;
  25. color: #fff !important;
  26. }
  27. /* 表格样式
  28. ------------------------------------------------ */
  29. .el-table thead tr,
  30. .el-table tbody tr>td {
  31. padding: 0 !important;
  32. height: 75px;
  33. line-height: 75px;
  34. color: #909399;
  35. }
  36. .el-table thead tr>th {
  37. background-color: var(--el-color-info-light-8) !important;
  38. }

newForm

  1. <template>
  2. <el-form ref="searchFormRef" :model="searchForm" size="default">
  3. <!-- 使用了不稳定的 key,可能会导致一些不可预期的行为,比如输入框失去焦点。 -->
  4. <el-row>
  5. <el-col
  6. :xs="24"
  7. :sm="24"
  8. :md="24"
  9. :lg="12"
  10. :xl="6"
  11. v-for="item in formOptions"
  12. :key="item.vm"
  13. >
  14. <el-form-item :label="item.props.label" :prop="item.vm">
  15. <el-input
  16. v-if="item.type === FormOptionsType.INPUT"
  17. v-model.lazy="searchForm[item.vm]"
  18. v-bind="item.props"
  19. class="ml10 w100"
  20. ></el-input>
  21. <el-select
  22. v-if="item.type === FormOptionsType.SELECT"
  23. v-model.lazy="searchForm[item.vm]"
  24. v-bind="item.props"
  25. class="ml10 w100"
  26. >
  27. <el-option
  28. v-for="option in item.selectOptions"
  29. :key="option.value"
  30. :label="option.label"
  31. :value="option.value"
  32. />
  33. </el-select>
  34. <el-cascader
  35. v-if="item.type === FormOptionsType.CASCADER"
  36. v-model.lazy="searchForm[item.vm]"
  37. :options="item.cascaderOptions"
  38. v-bind="item.props"
  39. class="ml10 w100"
  40. />
  41. <el-date-picker
  42. v-if="item.type === FormOptionsType.DATE_PICKER"
  43. v-model.lazy="searchForm[item.vm]"
  44. v-bind="item.props"
  45. class="ml10 w100"
  46. />
  47. </el-form-item>
  48. </el-col>
  49. <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6" class="xs-mt">
  50. <el-form-item style="margin-left: 10px">
  51. <el-button @click="onReset">
  52. <SvgIcon name="ant-ReloadOutlined"></SvgIcon>
  53. 重置
  54. </el-button>
  55. <el-button type="primary" @click="onSearch">
  56. <SvgIcon name="ant-SearchOutlined"></SvgIcon>
  57. 查询
  58. </el-button>
  59. </el-form-item>
  60. </el-col>
  61. </el-row>
  62. </el-form>
  63. </template>
  64. <script setup lang="ts" name="newForm">
  65. import { toRefs, onBeforeUnmount, ref } from 'vue';
  66. import type { PropType } from 'vue';
  67. import { type FormInstance } from 'element-plus';
  68. const searchFormRef = ref<FormInstance>();
  69. enum FormOptionsType {
  70. INPUT = 'input', // 输入框
  71. SELECT = 'select', // 下拉框
  72. CASCADER = 'cascader', // 级联选择器
  73. DATE_PICKER = 'date-picker', // 日期选择器
  74. }
  75. const props = defineProps({
  76. formOptions: {
  77. type: Array as PropType<FormOptions[]>,
  78. required: true,
  79. },
  80. searchForm: {
  81. type: Object as PropType<SearchFormType>,
  82. required: true,
  83. },
  84. });
  85. const { formOptions, searchForm } = toRefs(props);
  86. const emit = defineEmits(['reset', 'search']);
  87. const onReset = () => emit('reset');
  88. const onSearch = () => emit('search');
  89. onBeforeUnmount(() => searchFormRef.value?.resetFields());
  90. defineExpose({ searchFormRef });
  91. </script>
  92. <style scoped lang="scss">
  93. :deep(.el-form-item__label) {
  94. margin-left: 10px;
  95. }
  96. </style>

封装完成,下面在使用的页面调用

页面使用:

  1. <template>
  2. <div class="container-wrapper">
  3. <!-- 动态 page -->
  4. <new-table
  5. :tableHeader="tableHeader"
  6. :tableData="tableData"
  7. :pageNum="pageNum"
  8. :pageSize="pageSize"
  9. :total="pageTotal"
  10. @handleSizeChange="onHandleSizeChange"
  11. @handleCurrentChange="onHandleCurrentChange"
  12. @handleEdit="onHandleEdit"
  13. @handleDelete="onHandleDelete"
  14. >
  15. <template #search>
  16. <new-form
  17. :formOptions="formOptions"
  18. :searchForm="searchForm"
  19. @reset="onReset"
  20. @search="onSearch"
  21. />
  22. </template>
  23. <template #switch="{ row }">
  24. <el-switch
  25. v-model="row.fileStatus"
  26. active-text="开"
  27. inactive-text="关"
  28. :active-value="1"
  29. :inactive-value="2"
  30. active-color="#13ce66"
  31. inactive-color="#ff4949"
  32. @change="changeSwitchStatus(row.id, row.fileStatus)"
  33. />
  34. </template>
  35. </new-table>
  36. </div>
  37. </template>
  38. <script setup lang="ts" name="algorithmRegistrationQuery">
  39. import { onMounted, reactive, toRefs } from "vue";
  40. // import { getTestList } from "/@/api/encryptionAlgorithm/templateDefinition";
  41. // import { STATUS_CODE } from "/@/enum/global";
  42. const state = reactive({
  43. //表头数据
  44. // el-table-column有的属性都可以在这传
  45. tableHeader: <TableHeader[]>[
  46. { label: "姓名", prop: "uname" },
  47. { label: "年龄", prop: "age" },
  48. { label: "性别", prop: "sex", slotKey: "switch" },
  49. { label: "操作", fixed: "right", slotKey: "default" },
  50. ],
  51. //表项数据
  52. tableData: [
  53. { uname: "小帅", age: "18", sex: "男", status: false, id: 1 },
  54. { uname: "小美", age: "148", sex: "女", status: false, id: 2 },
  55. { uname: "小明", age: "12", sex: "男", status: true, id: 3 },
  56. { uname: "小红", age: "12", sex: "女", status: false, id: 4 },
  57. { uname: "小黑", age: "12", sex: "男", status: true, id: 5 },
  58. { uname: "小白", age: "12", sex: "女", status: false, id: 6 },
  59. { uname: "小黑", age: "12", sex: "男", status: true, id: 7 },
  60. { uname: "小白", age: "12", sex: "女", status: false, id: 8 },
  61. { uname: "小黑", age: "12", sex: "男", status: true, id: 9 },
  62. { uname: "小白", age: "12", sex: "女", status: false, id: 10 },
  63. { uname: "小黑", age: "12", sex: "男", status: true, id: 11 },
  64. ],
  65. formOptions: <FormOptions[]>[
  66. {
  67. type: "input",
  68. props: {
  69. label: "合规规则",
  70. placeholder: "请输入合规规则",
  71. type: "text",
  72. clearable: true,
  73. },
  74. vm: "knowledgeName",
  75. },
  76. {
  77. type: "input",
  78. props: {
  79. label: "文件数量",
  80. placeholder: "请输入文件数量",
  81. type: "text",
  82. clearable: true,
  83. },
  84. vm: "documentNumber",
  85. },
  86. // 下拉选择器
  87. {
  88. type: "select",
  89. props: {
  90. label: "所属部门",
  91. placeholder: "请选择",
  92. clearable: true,
  93. },
  94. vm: "department",
  95. selectOptions: [
  96. {
  97. label: "数据安全",
  98. value: 1,
  99. },
  100. {
  101. label: "研发",
  102. value: 2,
  103. },
  104. {
  105. label: "事业",
  106. value: 3,
  107. },
  108. ],
  109. },
  110. // 时间范围选择器
  111. {
  112. type: "date-picker",
  113. props: {
  114. label: "时间范围",
  115. type: "datetimerange", // datetimerange范围 datetime日期
  116. clearable: true,
  117. "range-separator": "-",
  118. "start-placeholder": "开始日期",
  119. "end-placeholder": "结束日期",
  120. "value-format": "YYYY-MM-DD HH:mm:ss",
  121. },
  122. vm: "createTime",
  123. },
  124. // 级联选择器
  125. {
  126. type: "cascader",
  127. props: {
  128. label: "所属部门",
  129. placeholder: "请选择",
  130. clearable: true,
  131. },
  132. vm: "cascader",
  133. cascaderOptions: [
  134. {
  135. value: "guide",
  136. label: "Guide",
  137. children: [
  138. {
  139. value: "disciplines",
  140. label: "Disciplines",
  141. children: [
  142. {
  143. value: "consistency",
  144. label: "Consistency",
  145. },
  146. ],
  147. },
  148. {
  149. value: "navigation",
  150. label: "Navigation",
  151. children: [
  152. {
  153. value: "side nav",
  154. label: "Side Navigation",
  155. },
  156. {
  157. value: "top nav",
  158. label: "Top Navigation",
  159. },
  160. ],
  161. },
  162. ],
  163. },
  164. {
  165. value: "component",
  166. label: "Component",
  167. children: [
  168. {
  169. value: "basic",
  170. label: "Basic",
  171. children: [
  172. {
  173. value: "button",
  174. label: "Button",
  175. },
  176. ],
  177. },
  178. {
  179. value: "form",
  180. label: "Form",
  181. children: [
  182. {
  183. value: "radio",
  184. label: "Radio",
  185. },
  186. {
  187. value: "checkbox",
  188. label: "Checkbox",
  189. },
  190. ],
  191. },
  192. {
  193. value: "data",
  194. label: "Data",
  195. children: [
  196. {
  197. value: "table",
  198. label: "Table",
  199. },
  200. ],
  201. },
  202. {
  203. value: "notice",
  204. label: "Notice",
  205. children: [
  206. {
  207. value: "alert",
  208. label: "Alert",
  209. },
  210. ],
  211. },
  212. {
  213. value: "navigation",
  214. label: "Navigation",
  215. children: [
  216. {
  217. value: "menu",
  218. label: "Menu",
  219. },
  220. ],
  221. },
  222. {
  223. value: "others",
  224. label: "Others",
  225. children: [
  226. {
  227. value: "dialog",
  228. label: "Dialog",
  229. },
  230. ],
  231. },
  232. ],
  233. },
  234. {
  235. value: "resource",
  236. label: "Resource",
  237. children: [
  238. {
  239. value: "axure",
  240. label: "Axure Components",
  241. },
  242. ],
  243. },
  244. ],
  245. },
  246. ],
  247. //这里允许动态属性所以可为空
  248. searchForm: <SearchFormType>{},
  249. pageNum: 1,
  250. pageSize: 10,
  251. pageTotal: 0,
  252. });
  253. const {
  254. tableHeader,
  255. tableData,
  256. formOptions,
  257. searchForm,
  258. pageNum,
  259. pageSize,
  260. pageTotal,
  261. } = toRefs(state);
  262. // 修改
  263. const onHandleEdit = (row: object) => {
  264. console.log(row);
  265. };
  266. // 删除
  267. const onHandleDelete = (row: object) => {
  268. console.log(row);
  269. };
  270. // switch
  271. const changeSwitchStatus = (id: number, status: boolean) => {
  272. console.log(id, status);
  273. };
  274. //页容量改变
  275. const onHandleSizeChange = (val: number) => {
  276. // console.log('页容量 ==>:', val);
  277. pageSize.value = val;
  278. getTableList(pageNum.value, pageSize.value);
  279. };
  280. //当前分页改变
  281. const onHandleCurrentChange = (val: number) => {
  282. // console.log('当前页
    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/701537
    推荐阅读
    相关标签