前端新手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

相关推荐
开心工作室_kaic11 分钟前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿30 分钟前
webWorker基本用法
前端·javascript·vue.js
cy玩具1 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161772 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test2 小时前
js下载excel示例demo
前端·javascript·excel
Yaml42 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事2 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶2 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo2 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v2 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript