前端新手Vue3+Vite+Ts+Pinia+Sass项目指北系列文章 —— 第十一章 基础界面开发 (组件封装和使用)

前言

Vue 是前端开发中非常常见的一种框架,它的易用性和灵活性使得它成为了很多开发者的首选。而在 Vue2 版本中,组件的开发也变得非常简单,但随着 Vue3 版本的发布,组件开发有了更多的特性和优化,为我们的业务开发带来了更多便利。本文将介绍如何使用 Vue3 开发业务组件,并通过代码实例进行演示。

一、自己封装组件

1、button 代码

src 目录下创建 components 文件夹,并在该文件夹下创建 Button 文件。

Button 文件中创建 index.vue 文件和 index.ts 文件,并编写以下代码

index.vue 文件中编写以下代码

vue 复制代码
<script lang="ts" setup name="ZButton">
defineProps({
  text: {
    type: String,
    default: ''
  },
  btnSize: {
    type: String,
    default: 'default'
  },
  size: {
    type: Number,
    default: 14
  },
  type: {
    type: String,
    default: 'default'
  },
  left: {
    type: Number,
    default: 0
  },
  right: {
    type: Number,
    default: 0
  },
  disabled: {
    type: Boolean,
    default: false
  },
  loading: {
    type: Boolean,
    default: false
  }
})
</script>

<template>
  <div :type="type" :size="btnSize" :class="`z-button-${type}`" :disabled="disabled" :loading="loading" :style="{
    marginLeft: `${left}px`,
    marginRight: `${right}px`
  }">
    {{ text }}
  </div>
</template>

<style lang="scss" scoped>
.z-button-blue {
  background: #80d4fb;
  color: #fff;
  border: none;

  &:hover,
  &:focus,
  &:active {
    background: #80d4fb80;
    color: #fff;
  }

  .anticon {
    color: #fff;
  }
}

.z-button-warn {
  background: #ec622b;
  color: #fff;
  border: none;
  outline: none;

  &:hover,
  &:focus,
  &:active {
    background-color: #ec622b80;
    color: #fff;
  }

  .anticon {
    color: #fff;
  }
}
</style>

index.ts 文件中编写以下代码

typescript 复制代码
import ZButton from "./index.vue";

export default ZButton

2、button 使用组件

我们在 home 页面导入组件来进行测试

vue 复制代码
<script setup lang="ts">
import { useRouter } from 'vue-router'
import useUserStore from '@/store/modules/user'
import ZButton from '@/components/Button/index' // 新增

const router = useRouter()
const userStore = useUserStore()

function goRouter(path: string): void {
  router.push(path)
}

function getUserInfo(): void {
  console.log(userStore.userInfo, 'userStore.userInfo')
}
</script>

<template>
  <div class="flex-c flex-align h-100">
    <el-button type="primary" @click="goRouter('/news')">
      go news
    </el-button>
    <el-button type="primary" @click="goRouter('/user')">
      go user
    </el-button>
    <el-button @click="getUserInfo">
      get user info
    </el-button>
    <el-button type="primary" @click="goRouter('/table')">
      go table
    </el-button>
    <!-- 新增 -->
    <z-button type="blue" text="测试blue" :left="10"></z-button>
    <!-- 新增 -->
    <z-button type="warn" text="测试warn" :left="10"></z-button>
  </div>
</template>

3、button 效果图

二、基于 Element-Plus 封装组件

1、table 代码

components 文件夹下创建 Table 文件。

Table 文件中创建 index.vueindex.tstypes.ts 文件,并编写以下代码

index.vue 文件中编写以下代码

vue 复制代码
<script setup lang="ts" name="ZTable">
import { ref, computed, watch, nextTick, defineExpose } from 'vue'
import { ElTable } from 'element-plus'
import { ZTableOptions } from './types'

const props = withDefaults(
  defineProps<{
    // 表格配置选项
    propList: ZTableOptions[]
    // 表格数据
    data: any[]
    // 表格高度
    height?: string | number
    maxHeight?: string | number
    // 显示复选框
    showSelectColumn?: boolean
    // 显示复选框
    showExpand?: boolean
    // 显示序号
    showIndexColumn?: boolean
    // 显示操作column
    operation?: boolean
    // 操作column 宽度
    operationWidth?: string
    moreOperationsPopoverWidth?: string
    // 加载状态
    loading?: boolean
    // 加载文案
    loadingText?: string
    // 加载图标名
    elementLoadingSpinner?: string
    // 是否显示分页
    pagination?: boolean
    // 显示分页的对齐方式
    paginationAlign?: 'left' | 'center' | 'right'
    pageInfo?: any
    // 显示分页数据多少条的选项
    pageSizes?: number[]
    // 数据总条数
    total?: number
    emptyImg?: boolean
  }>(),
  {
    propList: () => [],
    height: '100%',
    operation: true,
    operationWidth: '240px',
    moreOperationsPopoverWidth: '160px',
    paginationAlign: 'right',
    pageInfo: () => ({ page: 1, size: 10 }),
    pageSizes: () => [10, 15, 20, 30],
    total: 0,
    emptyImg: true
  }
)

const ZTableRef = ref<InstanceType<typeof ElTable>>()
const tablePropList: any = ref([])

watch(
  () => props.propList,
  (list) => {
    tablePropList.value = []
    nextTick(() => {
      tablePropList.value = JSON.parse(JSON.stringify(list))
    })
  },
  {
    immediate: true
  }
)

// 表格分页的排列方式
const justifyContent = computed(() => {
  if (props.paginationAlign === 'left') return 'flex-start'
  else if (props.paginationAlign === 'right') return 'flex-end'
  else return 'center'
})

const emits = defineEmits([
  'row-click',
  'select-rows',
  'page-change',
  'sort-change',
  'operation-click'
])

const handleOperationClick = (row: any, code: string, index: number) => {
  emits('operation-click', code, row, index)
}
const selectable = (row: any, index: any) => {
  return !row.noSelectable
}
const handleRowClick = (row: any, column: any) => {
  if (column?.label == '操作') return
  emits('row-click', row, column)
}
const handleSelectionChange = (list: any) => {
  emits('select-rows', list)
}
const handleSizeChange = (size: number) => {
  emits('page-change', { page: 1, size })
}
const handleCurrentChange = (page: number) => {
  emits('page-change', { ...props.pageInfo, page })
}
const changeTableSort = (value: any) => {
  emits('sort-change', value)
}
const toggleSelection = (rows?: any) => {
  if (rows) {
    rows.forEach((row: any) => {
      ZTableRef.value!.toggleRowSelection(row, true)
    })
  } else {
    ZTableRef.value!.clearSelection()
  }
}

defineExpose({
  toggleSelection
})
</script>

<template>
  <div class="z-table">
    <el-table :data="data" :height="height" :max-height="maxHeight" ref="ZTableRef" v-loading="loading"
      :element-loading-text="loadingText" :element-loading-spinner="elementLoadingSpinner" stripe
      @sort-change="changeTableSort" @row-click="handleRowClick" @selection-change="handleSelectionChange"
      v-bind="$attrs">
      <template #empty v-if="emptyImg">
        <div class="empty-box">
          <el-empty></el-empty>
        </div>
      </template>
      <el-table-column type="expand" v-if="showExpand">
        <template #default="scope">
          <slot name="baseExpandSlot" :row="scope.row"></slot>
        </template>
      </el-table-column>
      <el-table-column v-if="showSelectColumn" type="selection" :selectable="selectable" fixed="left" align="center"
        width="55"></el-table-column>
      <el-table-column v-if="showIndexColumn" fixed="left" type="index" label="序号" align="center"
        width="55"></el-table-column>
      <template v-for="propItem in tablePropList" :key="propItem.prop">
        <template v-if="propItem.visible !== false">
          <template v-if="!propItem.slotName">
            <el-table-column v-bind="propItem"></el-table-column>
          </template>
          <template v-else>
            <el-table-column v-bind="propItem">
              <template #default="scope">
                <slot :name="propItem.slotName" :format="propItem.dateFormat" :row="scope.row" :prop="propItem.prop"
                  :index="scope.$index">
                </slot>
              </template>
            </el-table-column>
          </template>
        </template>
      </template>
      <el-table-column v-if="operation" label="操作" :width="operationWidth" fixed="right">
        <template #default="scope">
          <template v-if="scope.row.operations">
            <div class="operations-wrap">
              <template v-for="(o, i) in scope.row.operations" :key="o.code">
                <el-button v-if="i < 3" text type="primary" size="small" :disabled="o.disabled"
                  @click="handleOperationClick(scope.row, o.code, scope.$index)">
                  {{ o.name }}
                </el-button>
              </template>
              <el-popover placement="bottom-end" :width="moreOperationsPopoverWidth"
                v-if="scope.row.operations.length > 3">
                <template #reference>
                  <el-icon color="#26A5FF" class="more-dot">
                    <MoreFilled />
                  </el-icon>
                </template>
                <div class="more-operations">
                  <template v-for="(o, i) in scope.row.operations" :key="o.code">
                    <el-button v-if="i > 2" text type="primary" size="small" :disabled="o.disabled" @click="
                      handleOperationClick(scope.row, o.code, scope.$index)
                      ">
                      {{ o.name }}
                    </el-button>
                  </template>
                </div>
              </el-popover>
            </div>
          </template>
        </template>
      </el-table-column>
    </el-table>
    <div v-if="pagination" class="pagination" :style="{ justifyContent }">
      <el-pagination small :current-page="pageInfo.page" :page-sizes="pageSizes" :page-size="pageInfo.size"
        layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange"
        @current-change="handleCurrentChange"></el-pagination>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.operations-wrap {
  .el-button+.el-button {
    margin-left: 25px;
  }

  .more-dot {
    position: relative;
    top: 0.3em;
    margin-left: 25px;
    font-size: 20px;
    cursor: pointer;
  }
}

.more-operations {
  display: flex;
  flex-wrap: wrap;

  .el-button {
    overflow: hidden;
    margin-left: 10px;
    height: 32px;
    border-radius: 8px;
    line-height: 32px;
  }
}

.el-loading-mask {
  z-index: 1;
}

.pagination {
  display: flex;
  margin-top: 16px;
}

.el-table__expand-column .cell {
  width: 55px;
}

.is-dark {
  max-width: 40%;
}
</style>

index.ts 文件中编写以下代码

typescript 复制代码
import ZTable from './index.vue'

export default ZTable

types.ts 文件中编写以下代码

typescript 复制代码
export interface ZTableOptions {
  // 是否可见
  visible?: boolean
  // 自定义列模板的插槽名
  slotName?: string
  // 日期格式化
  dateFormat?: string
  // 表头
  label: string
  // 字段名称
  prop?: string
  // 对应列的宽度
  width?: string | number
  minWidth?: string | number
  // 对齐方式
  align?: 'left' | 'center' | 'right'
  fixed?: true | 'left' | 'right'
  showOverflowTooltip?: boolean
  sortable?: boolean | 'custom'
}

2、table 组件使用

table 文件中下添加 index.vue 并添加对应路由文件,编写以下代码

vue 复制代码
<script lang="ts" setup>
import ZTable from '@/components/Table/index'
import { ref } from 'vue'
import { ZTableOptions } from '@/components/Table/types'

const tableData: any = ref([
  {
    fileName: '测试文件01',
    fileType: 'pdf',
    submitterName: '张三',
    submitTime: '2024-01-04 09:34:18'
  },
  {
    fileName: '测试文件02',
    fileType: 'png',
    submitterName: '李四',
    submitTime: '2024-01-04 11:26:57'
  }
])

const propList: ZTableOptions[] = [
  {
    showOverflowTooltip: true,
    label: '文件名称',
    prop: 'fileName',
    minWidth: 130,
    align: 'left'
  },
  {
    showOverflowTooltip: true,
    label: '文件类型',
    prop: 'fileType',
    minWidth: 130,
    align: 'left'
  },
  {
    label: '上传人',
    prop: 'submitterName',
    minWidth: 150,
    showOverflowTooltip: true
  },
  {
    label: '上传时间',
    prop: 'submitTime',
    minWidth: 160
  }
]
</script>

<template>
  <div>
    <z-table :propList="propList" :data="tableData" :operation="false"></z-table>
  </div>
</template>

<style scoped lang="scss">
</style>

3、table 效果图

总结

通过以上的介绍和代码实例,我们可以看到 Vue3 提供了更多的特性和优化,让我们更加方便地开发业务组件。在实际开发中,我们可以根据实际需求选择合适的组件开发方式,并通过 Vue3 的特性来提升开发效率。希望本文能够帮助到你在 Vue3 开发中的业务组件开发。上文中的配置代码可在 github 仓库中直接 copy,仓库路径:https://github.com/SmallTeddy/ProjectConstructionHub

相关推荐
是千千千熠啊几秒前
vue使用Fabric和pdfjs完成合同签章及批注
前端·vue.js
九月TTS30 分钟前
TTS-Web-Vue系列:组件逻辑分离与模块化重构
前端·vue.js·重构
我是大头鸟1 小时前
SpringMVC 内容协商处理
前端
Humbunklung1 小时前
Visual Studio 2022 中添加“高级保存选项”及解决编码问题
前端·c++·webview·visual studio
墨水白云1 小时前
nestjs[一文学懂nestjs中对npm功能包的封装,ioredis封装示例]
前端·npm·node.js
满怀10152 小时前
【Vue 3全栈实战】从响应式原理到企业级架构设计
前端·javascript·vue.js·vue
luckywuxn2 小时前
使用gitbook 工具编写接口文档或博客
前端
梅子酱~3 小时前
Vue 学习随笔系列二十三 -- el-date-picker 组件
前端·vue.js·学习
伟笑3 小时前
elementUI 循环出来的表单,怎么做表单校验?
前端·javascript·elementui
辣辣y3 小时前
React中useMemo和useCallback的作用:
前端·react