【后台管理系统】-【组件封装】

目录

组件封装

搜索组件

给页面写一个配置文件,将配置文件传入组件,可直接生成页面,以下面页面为例,

新建src/views/main/system/department/config/search.config.ts:

typescript 复制代码
const searchConfig = {
  formItems: [
    {
      type: 'input',// el组件类型
      prop: 'name',
      label: '部门名称',
      placeholder: '请输入查询的部门名称',
      initialValue: 'bbb'// 默认值
    },
    {
      type: 'input',
      prop: 'leader',
      label: '部门领导',
      placeholder: '请输入查询的领导名称'
    },
    {
      type: 'date-picker',
      prop: 'createAt',
      label: '创建时间'
    }
  ]
}

export default searchConfig

在department.vue文件中引入配置文件并传给search子组件:

html 复制代码
<page-search
      :search-config="searchConfig"
      @query-click="handleQueryClick"
      @reset-click="handleResetClick"
/>
<script>
import contentConfig from './config/content.config'
</script>

search子组件使用defineProps接收配置文件中的数据,并遍历展示

html 复制代码
<template>
  <div class="search">
    <!-- 1.输入搜索关键字的表单 -->
    <el-form
      :model="searchForm"
      ref="formRef"
      :label-width="searchConfig.labelWidth ?? '80px'"
      size="large"
    >
      <el-row :gutter="20">
        <template v-for="item in searchConfig.formItems" :key="item.prop">
        <!-- span的值也可以配置 -->
          <el-col :span="8">
          <!-- :label="item.label" :prop="item.prop"可以写成v-bind="item"来绑定所有属性 -->
            <el-form-item :label="item.label" :prop="item.prop">
            <!-- 也可以用动态组件 -->
            <!-- <component :is="'el-${item.type}'" xxx></component>-->
              <template v-if="item.type === 'input'">
                <el-input
                  v-model="searchForm[item.prop]"
                  :placeholder="item.placeholder"
                />
              </template>
              <template v-if="item.type === 'date-picker'">
                <el-date-picker
                  v-model="searchForm[item.prop]"
                  type="daterange"
                  range-separator="-"
                  start-placeholder="开始时间"
                  end-placeholder="结束时间"
                />
              </template>
              <template v-if="item.type === 'select'">
                <el-select
                  v-model="searchForm[item.prop]"
                  :placeholder="item.placeholder"
                  style="width: 100%"
                >
                  <template v-for="option in item.options" :key="option.value">
                    <el-option :label="option.label" :value="option.value" />
                  </template>
                </el-select>
              </template>
            </el-form-item>
          </el-col>
        </template>
      </el-row>
    </el-form>

    <!-- 2.重置和搜索的按钮 -->
    <div class="btns">
      <el-button icon="Refresh" @click="handleResetClick">重置</el-button>
      <el-button icon="Search" type="primary" @click="handleQueryClick"
        >查询</el-button
      >
    </div>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'
import type { ElForm } from 'element-plus'

// 定义自定义事件/接收的属性,从伏组件
interface IProps {
  searchConfig: {
    labelWidth?: string
    formItems: any[]
  }
}
const emit = defineEmits(['queryClick', 'resetClick'])
const props = defineProps<IProps>()

// 定义form的数据
const initialForm: any = {}
for (const item of props.searchConfig.formItems) {
  initialForm[item.prop] = item.initialValue ?? ''
}
const searchForm = reactive(initialForm)

// 重置操作
const formRef = ref<InstanceType<typeof ElForm>>()
function handleResetClick() {
  // 1.form中的数据全部重置
  formRef.value?.resetFields()

  // 2.将事件出去, content内部重新发送网络请求
  emit('resetClick')
}

function handleQueryClick() {
  emit('queryClick', searchForm)
}
</script>

<style lang="less" scoped>
.search {
  background-color: #fff;
  padding: 20px;

  .el-form-item {
    padding: 20px 30px;
    margin-bottom: 0;
  }

  .btns {
    text-align: right;
    padding: 0 50px 10px 0;

    .el-button {
      height: 36px;
    }
  }
}
</style>

table列表组件

创建src/views/main/system/department/config/content.config.ts文件,将列表标题和按钮标题写在header里,将table列表展示的列信息写在propsList中

typescript 复制代码
const contentConfig = {
  pageName: 'department',
  header: {
    title: '部门列表',
    btnTitle: '新建部门'
  },
  propsList: [
    // 1.selection 2.index
    { type: 'selection', label: '选择', width: '80px' },
    { type: 'index', label: '序号', width: '80px' },
    { type: 'normal', label: '部门名称', prop: 'name', width: '150px' },
    { type: 'normal', label: '部门领导', prop: 'leader', width: '150px' },
    { type: 'normal', label: '上级部门', prop: 'parentId', width: '150px' },
    { type: 'timer', label: '创建时间', prop: 'createAt' },
    { type: 'timer', label: '更新时间', prop: 'updateAt' },
    { type: 'handler', label: '操作', width: '150px' }
  ]
}

export default contentConfig

在department.vue文件中引入配置文件并传给page-content子组件(代码参考前面的,此处不在赘述),page-content子组件使用defineProps接收并遍历展示

html 复制代码
<template>
  <div class="content">
    <div class="header">
      <h3 class="title">{{ contentConfig?.header?.title ?? '数据列表' }}</h3>
      <el-button type="primary" @click="handleNewUserClick">
        {{ contentConfig?.header?.btnTitle ?? '新建数据' }}
      </el-button>
    </div>
    <div class="table">
      <el-table
        :data="pageList"
        border
        style="width: 100%"
        v-bind="contentConfig.childrenTree"
      >
        <template v-for="item in contentConfig.propsList" :key="item.prop">
          <template v-if="item.type === 'timer'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                {{ formatUTC(scope.row[item.prop]) }}
              </template>
            </el-table-column>
          </template>
          <template v-else-if="item.type === 'handler'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                <el-button
                  size="small"
                  icon="Edit"
                  type="primary"
                  text
                  @click="handleEditBtnClick(scope.row)"
                >
                  编辑
                </el-button>
                <el-button
                  size="small"
                  icon="Delete"
                  type="danger"
                  text
                  @click="handleDeleteBtnClick(scope.row.id)"
                >
                  删除
                </el-button>
              </template>
            </el-table-column>
          </template>
          <template v-else-if="item.type === 'custom'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                <slot
                  :name="item.slotName"
                  v-bind="scope"
                  :prop="item.prop"
                  hName="why"
                ></slot>
              </template>
            </el-table-column>
          </template>
          <template v-else>
            <el-table-column align="center" v-bind="item" />
          </template>
        </template>
      </el-table>
    </div>
    <div class="pagination">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 30]"
        layout="total, sizes, prev, pager, next, jumper"
        :total="pageTotalCount"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { storeToRefs } from 'pinia'
import useSystemStore from '@/store/main/system/system'
import { formatUTC } from '@/utils/format'
import { ref } from 'vue'

interface IProps {
  contentConfig: {
    pageName: string
    header?: {
      title?: string
      btnTitle?: string
    }
    propsList: any[]
    childrenTree?: any
  }
}

const props = defineProps<IProps>()

// 定义事件
const emit = defineEmits(['newClick', 'editClick'])

// 1.发起action,请求usersList的数据
const systemStore = useSystemStore()
const currentPage = ref(1)
const pageSize = ref(10)
fetchPageListData()

// 2.获取usersList数据,进行展示
const { pageList, pageTotalCount } = storeToRefs(systemStore)

// 3.页码相关的逻辑
function handleSizeChange() {
  fetchPageListData()
}
function handleCurrentChange() {
  fetchPageListData()
}

// 4.定义函数, 用于发送网络请求
function fetchPageListData(formData: any = {}) {
  // 1.获取offset/size
  const size = pageSize.value
  const offset = (currentPage.value - 1) * size
  const pageInfo = { size, offset }

  // 2.发起网络请求
  const queryInfo = { ...pageInfo, ...formData }
  systemStore.postPageListAction(props.contentConfig.pageName, queryInfo)
}

// 5.删除/新建/编辑的操作
function handleDeleteBtnClick(id: number) {
  systemStore.deletePageByIdAction(props.contentConfig.pageName, id)
}
function handleNewUserClick() {
  emit('newClick')
}
function handleEditBtnClick(itemData: any) {
  emit('editClick', itemData)
}

defineExpose({ fetchPageListData })
</script>

<style lang="less" scoped>
.content {
  margin-top: 20px;
  padding: 20px;
  background-color: #fff;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  margin-bottom: 10px;

  .title {
    font-size: 22px;
  }
}

.table {
  :deep(.el-table__cell) {
    padding: 12px 0;
  }

  .el-button {
    margin-left: 0;
    padding: 5px 8px;
  }
}

.pagination {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
}
</style>

content组件自定义插槽定制

目前的设计不支持定制化,就是在子组件中,对propsList的类型判断不够全,确实也无法判断全,因为就算把所有的el组件都判断一遍,也会有用户自定义组件,所以本节讨论如何对table列表的某一列进行定制化。在content.config.ts中添加自定义的内容:

typescript 复制代码
const contentConfig = {
  pageName: 'department',
  header: {
    title: '部门列表',
    btnTitle: '新建部门'
  },
  propsList: [
    // 1.selection 2.index
    { type: 'selection', label: '选择', width: '80px' },
    { type: 'index', label: '序号', width: '80px' },

    { type: 'normal', label: '部门名称', prop: 'name', width: '150px' },
    { type: 'normal', label: '部门领导', prop: 'leader', width: '150px' },
    { type: 'normal', label: '上级部门', prop: 'parentId', width: '150px' },

    {
      type: 'custom',
      label: '部门领导',
      prop: 'leader',
      width: '150px',
      slotName: 'leader'
    },
    {
      type: 'custom',
      label: '上级部门',
      prop: 'parentId',
      width: '150px',
      slotName: 'parent'
    },

    { type: 'timer', label: '创建时间', prop: 'createAt' },
    { type: 'timer', label: '更新时间', prop: 'updateAt' },

    { type: 'handler', label: '操作', width: '150px' }
  ]
}

export default contentConfig

在page-content子组件遍历propsList的时候,增加对custom类型的判断,既然要自定义,那肯定得使用el-table-column的默认插槽插入自定义的内容,但是这个自定义的东西是不确定的(不固定的),所以在el-table-column的默认插槽中再插入一个插槽,可能表格中有很多可以自定义的列,就会有多个插槽,所以这个插槽得是具名插槽,不然在使用的时候根本不知道数据要插入哪个插槽,且具名插槽的名字由配置来决定:

html 复制代码
<template v-else-if="item.type === 'custom'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                <slot
                  :name="item.slotName"
                ></slot>
              </template>
            </el-table-column>
          </template>

父组件使用:

  1. contentConfig是引入的content.config.ts配置文件
  2. 通过<template #leader>制定插入的内容
html 复制代码
<page-content
      :content-config="contentConfig"
      ref="contentRef"
      @new-click="handleNewClick"
      @edit-click="handleEditClick"
    >
      <template #leader>哈哈哈</template>
      <template #parent>呵呵呵</span></template>
    </page-content>

但是现在父组件传入的东西只能是写死的,父组件如果想根据这行数据来插入内容应该怎么办?在page-content子组件中将scope传给插槽,父组件使用插槽时接收数据。page-content子组件代码:

html 复制代码
<template v-else-if="item.type === 'custom'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                <slot
                  :name="item.slotName"
                  v-bind="scope"
                ></slot>
              </template>
            </el-table-column>
          </template>

父组件接收数据:#leader="scope",并且可以通过添加class的方式,给这一列添加样式。

html 复制代码
<page-content
      :content-config="contentConfig"
      ref="contentRef"
      @new-click="handleNewClick"
      @edit-click="handleEditClick"
    >
      <template #leader="scope">
        <span class="leader">哈哈哈: {{ scope.row.leader }}</span>
      </template>
      <template #parent="scope">
        <span class="parent">呵呵呵: {{ scope.row.parent }}</span>
      </template>
    </page-content>

<style scoped>
.leader {
  color: red;
}

.parent {
  color: blue;
}
</style>

如果直接写scope.row.leader,这个leader也是写死的,如果希望从配置中读取,取prop的值,那么page-content子组件需要将prop数据传给插槽(类似于父组件给子组件传值),代码如下:

html 复制代码
<template v-else-if="item.type === 'custom'">
            <el-table-column align="center" v-bind="item">
              <template #default="scope">
                <slot
                  :name="item.slotName"
                  v-bind="scope"
                  :prop="item.prop"
                ></slot>
              </template>
            </el-table-column>
          </template>

父组件在使用插槽时,直接从传过来的scope.prop中获取,因为传过来的所有东西都在scope上面,而item.prop是传到了prop上,所以通过scope.prop获取

html 复制代码
<page-content
      :content-config="contentConfig"
      ref="contentRef"
      @new-click="handleNewClick"
      @edit-click="handleEditClick"
    >
      <template #leader="scope">
        <span class="leader">哈哈哈: {{ scope.row[scope.prop] }}</span>
      </template>
      <template #parent="scope">
        <span class="parent">呵呵呵: {{ scope.row[scope.prop] }}</span>
      </template>
    </page-content>

modal组件

javascript 复制代码
const initialData: any = {}
for (const item of props.modalConfig.formItems) {
  initialData[item.prop] = item.initialValue ?? ''
}
const formData = reactive<any>(initialData)

如果像以前一样,在定义formData的时候就给他设置默认值,默认值是设置不上去的,这是因为一开始modal组件是不展示的,所以在modal弹出来之后再通过遍历modalConfig.formItems来设置默认值

javascript 复制代码
function setModalVisible(isNew: boolean = true, itemData?: any) {
  dialogVisible.value = true
  isNewRef.value = isNew
  if (!isNew && itemData) {
    // 编辑数据
    for (const key in formData) {
      formData[key] = itemData[key]
    }
    editData.value = itemData
  } else {
    // 新建数据
    for (const key in formData) {
      const item = props.modalConfig.formItems.find((item) => item.prop === key)
      formData[key] = item ? item.initialValue : ''
    }
    editData.value = null
  }
}

动态获取options数据

相关推荐
m0_7482487738 分钟前
【前端 Uniapp】使用Vant打造Uniapp项目(避坑版)
前端·uni-app
深海的鲸同学 luvi1 小时前
高德地图离线加载解决方案(内网部署)+本地地图瓦片加载
前端·javascript·html5
码字哥2 小时前
EasyExcel设置表头上面的那种大标题(前端传递来的大标题)
java·服务器·前端
GIS好难学4 小时前
《Vue进阶教程》第六课:computed()函数详解(上)
前端·javascript·vue.js
nyf_unknown4 小时前
(css)element中el-select下拉框整体样式修改
前端·css
m0_548514774 小时前
前端打印功能(vue +springboot)
前端·vue.js·spring boot
执键行天涯4 小时前
element-plus中的resetFields()方法
前端·javascript·vue.js
一个努力学习的小男孩4 小时前
【自学】Vues基础
vue.js
Days20504 小时前
uniapp小程序增加加载功能
开发语言·前端·javascript
喵喵酱仔__4 小时前
vue 给div增加title属性
前端·javascript·vue.js