组件封装

This commit is contained in:
李远 2026-03-11 18:37:41 +08:00
parent db9b98af8e
commit 2fded9a646
6 changed files with 451 additions and 4 deletions

View File

@ -0,0 +1,116 @@
<template>
<div class="pagination-container">
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="pageSizes"
:total="total" :disabled="disabled" :background="background" :small="small"
:hide-on-single-page="hideOnSinglePage" :layout="layout" @size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</div>
</template>
<script setup lang="ts">
//
const props = defineProps({
//
modelValue: {
type: Number,
required: true,
default: 1
},
//
pageSizeValue: {
type: Number,
required: true,
default: 10
},
//
total: {
type: Number,
required: true,
default: 0
},
//
pageSizes: {
type: Array,
default: () => [10, 20, 50, 100]
},
//
disabled: {
type: Boolean,
default: false
},
//
background: {
type: Boolean,
default: true
},
//
small: {
type: Boolean,
default: false
},
//
hideOnSinglePage: {
type: Boolean,
default: true
},
// Element Plus
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
}
})
//
const emit = defineEmits([
// v-model
'update:modelValue',
// v-model
'update:pageSizeValue',
// 便
'pagination-change'
])
// props
const currentPage = props.modelValue
// props
const pageSize = props.pageSizeValue
//
watch(
() => currentPage,
(newVal) => {
emit('update:modelValue', newVal)
emit('pagination-change', { currentPage: newVal, pageSize })
}
)
//
watch(
() => pageSize,
(newVal) => {
emit('update:pageSizeValue', newVal)
emit('pagination-change', { currentPage, pageSize: newVal })
}
)
// Element Plus
const handleSizeChange = (val: number) => {
emit('update:pageSizeValue', val)
emit('pagination-change', { currentPage, pageSize: val })
}
// Element Plus
const handleCurrentChange = (val: number) => {
emit('update:modelValue', val)
emit('pagination-change', { currentPage: val, pageSize })
}
</script>
<style scoped lang="scss">
.pagination-container {
position: fixed;
bottom: 20px;
right: 20px;
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<div class="search-container">
<el-form :model="searchForm" inline @submit.prevent>
<!-- 动态生成的表单项 -->
<template v-for="item in searchOptions" :key="item.prop">
<el-form-item :label="item.label" :prop="item.prop" v-if="!item.slot"
:style="{ minWidth: item.minWidth }">
<!-- 输入框 -->
<el-input v-if="item.type === 'input'" v-model="searchForm[item.prop]"
:placeholder="item.placeholder || `请输入${item.label}`" :clearable="item.clearable ?? true"
:disabled="item.disabled" />
<!-- 选择器 -->
<el-select v-else-if="item.type === 'select'" v-model="searchForm[item.prop]"
:placeholder="item.placeholder || `请选择${item.label}`" :clearable="item.clearable ?? true"
:disabled="item.disabled">
<el-option v-for="option in item.options" :key="option.value" :label="option.label"
:value="option.value" />
</el-select>
<!-- 日期选择器单个/范围 -->
<el-date-picker v-else-if="item.type === 'date'" v-model="searchForm[item.prop]"
:type="item.dateType || 'date'" :range-separator="item.rangeSeparator || '至'"
:start-placeholder="item.startPlaceholder || '开始日期'"
:end-placeholder="item.endPlaceholder || '结束日期'"
:value-format="item.valueFormat || 'YYYY-MM-DD'" :clearable="item.clearable ?? true"
:disabled="item.disabled" />
<!-- 单选框组 -->
<el-radio-group v-else-if="item.type === 'radio'" v-model="searchForm[item.prop]"
:disabled="item.disabled">
<el-radio v-for="option in item.options" :key="option.value" :label="option.value">
{{ option.label }}
</el-radio>
</el-radio-group>
<!-- 复选框组 -->
<el-checkbox-group v-else-if="item.type === 'checkbox'" v-model="searchForm[item.prop]"
:disabled="item.disabled">
<el-checkbox v-for="option in item.options" :key="option.value" :label="option.value">
{{ option.label }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 自定义插槽项 -->
<el-form-item :label="item.label" :prop="item.prop" v-else>
<slot :name="item.prop" :form="searchForm" />
</el-form-item>
</template>
<!-- 额外插槽用于插入配置外的自定义项 -->
<slot name="extra" :form="searchForm" />
<!-- 操作按钮 -->
<el-form-item>
<el-button type="primary" @click="$emit('search')">
<el-icon>
<Search />
</el-icon>
{{ buttonConfig.searchText || '查询' }}
</el-button>
<el-button @click="$emit('reset')">
{{ buttonConfig.resetText || '重置' }}
</el-button>
<!-- 按钮区插槽 -->
<slot name="button" :form="searchForm" />
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
//
interface SearchOption {
prop: string; //
label: string; //
type: 'input' | 'select' | 'date' | 'radio' | 'checkbox'; //
slot?: boolean; //
placeholder?: string; //
clearable?: boolean; //
disabled?: boolean; //
options?: { label: string; value: any }[]; // //
dateType?: 'date' | 'daterange' | 'datetime' | 'datetimerange'; //
rangeSeparator?: string; //
startPlaceholder?: string; //
endPlaceholder?: string; //
valueFormat?: string; //
width?: string; //
minWidth?: string; //
}
//
interface ButtonConfig {
searchText?: string;
resetText?: string;
}
// Props
const props = defineProps({
searchForm: {
type: Object,
required: true,
description: '搜索表单数据对象'
},
searchOptions: {
type: Array as () => SearchOption[],
required: true,
description: '搜索项配置数组'
},
buttonConfig: {
type: Object as () => ButtonConfig,
default: () => ({}),
description: '按钮文本配置'
}
})
//
defineEmits(['search', 'reset'])
</script>
<style scoped lang="scss">
.search-container {
margin-bottom: 20px;
.el-form{
display: flex;
width: 100%;
.el-form-item{
flex: 1 1 auto;
}
}
:deep(.el-form-item) {
margin-bottom: 0;
margin-right: 16px;
}
:deep(.el-button) {
margin-left: 8px;
}
}
</style>

View File

@ -97,13 +97,13 @@ const staticMenuList = ref<ElMenuItem[]>(
{
id: 3,
label: '账户管理',
path: '3', // el-sub-menu index="3"
path: 'account', // el-sub-menu index="account"
icon: 'UserFilled', // <UserFilled />
children: [
{
id: 31,
label: '客户管理',
path: '3-1', // index="3-1"
path: '/account/client', // index="client"
icon: ''
},
{

View File

@ -78,6 +78,33 @@ const routes = [
}
]
},
//模板配置
{
path: 'template',
name: 'Template',
meta: {
title: '模板配置', // 左侧菜单显示的标题
},
children: []
},
//账户管理
{
path: 'account',
name: 'Account',
meta: {
title: '账户管理', // 左侧菜单显示的标题
},
children: [
{
path: 'client',
name: 'Client',
component: () => import('@/views/account/client/index.vue'),
meta: {
title: '客户账户', // 左侧菜单显示的标题
}
},
]
},
]
},
{

View File

@ -1,5 +1,135 @@
<template>
<div>
<h1>客户管理</h1>
<div class="client">
<!-- 动态搜索组件 -->
<ClientSearch :search-form="searchForm" :search-options="clientSearchOptions"
:button-config="{ searchText: '搜索', resetText: '重置' }" @search="handleSearch" @reset="handleReset">
<!-- 这里可以插入自定义插槽如果需要 -->
</ClientSearch>
<!-- 下方的表格区域与你的图片对应 -->
<el-table :data="tableData" border>
<el-table-column prop="username" label="用户名" />
<el-table-column prop="registerStartTime" label="注册开始时间" />
<el-table-column prop="registerEndTime" label="注册结束时间" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '开启' : '关闭' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default>
<el-button link type="primary">搜索</el-button>
<el-button link type="primary">重置</el-button>
<el-button link type="primary">新增用户</el-button>
</template>
</el-table-column>
</el-table>
</div>
<ClientPagination v-model="currentPage" v-model:page-size-value="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</template>
<script lang="ts" setup>
//
import ClientSearch from '@/components/common/Search.vue'
import ClientTable from '@/components/common/Table.vue'
import ClientPagination from '@/components/common/Pagination.vue'
//
import { clientSearchOptions } from './options'
//
const searchForm = ref({
username: '', //
registerTime: [], // daterange
status: '' // /
})
//
const tableData = ref([
{ username: 'admin', registerStartTime: '2026-01-01', registerEndTime: '2026-01-02', status: 1 },
{ username: 'test', registerStartTime: '2026-02-01', registerEndTime: '2026-02-02', status: 0 }
])
// 3.
const handleSearch = () => {
console.log('发起查询,参数:', searchForm)
// registerTime start end
const params = {
...searchForm.value,
registerStartTime: searchForm.value.registerTime[0] || '',
registerEndTime: searchForm.value.registerTime[1] || ''
}
delete params.registerTime //
//
// api.getUserList(params).then(res => {
// tableData.value = res.list
// })
}
// 4.
const handleReset = () => {
//
searchForm.value.username = ''
searchForm.value.registerTime = []
searchForm.value.status = ''
//
handleSearch()
}
//
handleSearch()
//
const currentPage = ref(1) //
const pageSize = ref(10) //
const total = ref(0) //
//
onMounted(() => {
fetchData()
})
//
const fetchData = async () => {
try {
//
// const res = await api.getList({ page: currentPage.value, size: pageSize.value })
//
const res = {
list: [/* 业务数据 */],
total: 100 //
}
tableData.value = res.list
total.value = res.total
} catch (error) {
console.error('数据请求失败:', error)
}
}
//
const handlePaginationChange = ({ currentPage: page, pageSize: size }) => {
//
currentPage.value = page
pageSize.value = size
//
fetchData()
}
</script>

View File

@ -0,0 +1,31 @@
export const clientSearchOptions = [
{
prop: 'username',
label: '用户名',
type: 'input' as const,
placeholder: '请输入用户名',
minWidth: '120px'
},
{
prop: 'registerTime',
label: '注册开始时间', // 标签与图片一致
type: 'date' as const,
dateType: 'daterange' as const, // 时间范围选择器
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
valueFormat: 'YYYY-MM-DD',
minWidth: '200px'
},
{
prop: 'status',
label: '状态',
type: 'select' as const,
placeholder: '选择状态',
width: '120px',
options: [
{ label: '全部', value: '' },
{ label: '开启', value: 1 },
{ label: '关闭', value: 0 }
],
}
]