feat: BMS 项目初始化(前端+后端)
- 前端 bms-front:基于 RuoYi-Vue3,封装 BasicForm/BasicTable 公共组件,替换 18 个业务页面表格 - 后端 bms-back:基于 RuoYi-Vue,包含 system/quartz/generator 等模块 - 项目标题由若依管理系统改为 BMS
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
<template>
|
||||
<div class="basic-table">
|
||||
<!-- 搜索栏 -->
|
||||
<basic-form
|
||||
v-if="searchColumns.length"
|
||||
ref="searchFormRef"
|
||||
:model-value="queryParams"
|
||||
:schemas="searchColumns"
|
||||
:rules="searchRules"
|
||||
:label-width="searchLabelWidth"
|
||||
:size="size"
|
||||
inline
|
||||
v-show="showSearch"
|
||||
>
|
||||
<template v-for="name in searchSlotNames" :key="name" #[toFormSlot(name)]>
|
||||
<slot :name="name" />
|
||||
</template>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
<slot name="search-extra" />
|
||||
</basic-form>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<el-row v-if="showToolbar" :gutter="10" class="mb8">
|
||||
<slot name="toolbar" :selection="selectionList" :ids="selectionIds" />
|
||||
<el-col :span="1.5" v-if="!$slots.toolbar">
|
||||
<el-button type="primary" plain icon="Plus" @click="$emit('add')" v-if="showAdd">新增</el-button>
|
||||
</el-col>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearchInternal"
|
||||
:columns="toolbarColumns"
|
||||
:search="searchColumns.length > 0"
|
||||
@query-table="getList"
|
||||
/>
|
||||
</el-row>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
:row-key="rowKey"
|
||||
:border="border"
|
||||
:stripe="stripe"
|
||||
:size="size"
|
||||
:height="height"
|
||||
:max-height="maxHeight"
|
||||
:default-sort="defaultSort"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-click="(row, col, e) => $emit('row-click', row, col, e)"
|
||||
@sort-change="(c) => $emit('sort-change', c)"
|
||||
>
|
||||
<!-- 多选列 -->
|
||||
<el-table-column v-if="selection" type="selection" width="55" align="center" />
|
||||
|
||||
<!-- 序号列 -->
|
||||
<el-table-column v-if="showIndex" type="index" label="序号" width="55" align="center" />
|
||||
|
||||
<!-- 动态列 -->
|
||||
<el-table-column
|
||||
v-for="col in visibleColumns"
|
||||
:key="col.key"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
:min-width="col.minWidth"
|
||||
:align="col.align || 'center'"
|
||||
:fixed="col.fixed"
|
||||
:sortable="col.sortable"
|
||||
:show-overflow-tooltip="col.showOverflowTooltip !== false"
|
||||
>
|
||||
<template #header="scope">
|
||||
<slot :name="`header-${col.prop}`" v-bind="scope">{{ col.label }}</slot>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<slot :name="`col-${col.prop}`" v-bind="scope" :row="scope.row">
|
||||
<!-- 字典列 -->
|
||||
<dict-tag
|
||||
v-if="col.dict"
|
||||
:options="getDictOptions(col.dict)"
|
||||
:value="scope.row[col.prop]"
|
||||
/>
|
||||
<!-- 时间格式化 -->
|
||||
<span v-else-if="col.parseTime">{{ parseTime(scope.row[col.prop], col.parseTime === true ? undefined : col.parseTime) }}</span>
|
||||
<!-- 自定义格式化 -->
|
||||
<span v-else-if="col.formatter">{{ col.formatter(scope.row, col, scope.row[col.prop], scope.$index) }}</span>
|
||||
<!-- 默认显示 -->
|
||||
<span v-else>{{ scope.row[col.prop] }}</span>
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 空数据 -->
|
||||
<template #empty>
|
||||
<slot name="empty"><el-empty /></slot>
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
:page-sizes="pageSizes"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 公共表格组件 BasicTable
|
||||
* 集成搜索栏 + 工具栏 + 表格 + 分页,基于 columns/searchColumns 配置驱动,内置复用 BasicForm、RightToolbar、Pagination、DictTag。
|
||||
*
|
||||
* 用法示例:
|
||||
* <basic-table
|
||||
* ref="tableRef"
|
||||
* :columns="columns"
|
||||
* :search-columns="searchColumns"
|
||||
* :query="listPost"
|
||||
* @add="handleAdd"
|
||||
* >
|
||||
* <template #toolbar="{ selection, ids }">
|
||||
* <el-button type="primary" icon="Plus" @click="handleAdd">新增</el-button>
|
||||
* </template>
|
||||
* <template #col-status="{ row }">
|
||||
* <dict-tag :options="sys_normal_disable" :value="row.status" />
|
||||
* </template>
|
||||
* </basic-table>
|
||||
*
|
||||
* column 配置:
|
||||
* {
|
||||
* label: '岗位名称', // 列标题
|
||||
* prop: 'postName', // 字段名
|
||||
* width: 120, // 列宽
|
||||
* minWidth: 120,
|
||||
* align: 'center', // 对齐(默认 center)
|
||||
* fixed: 'left'|'right',
|
||||
* sortable: true,
|
||||
* showOverflowTooltip: true, // 默认 true
|
||||
* dict: 'sys_normal_disable', // 用字典标签渲染
|
||||
* parseTime: true | 'YYYY-MM-DD', // 时间格式化(true 用默认格式)
|
||||
* formatter: (row,col,val,index)=> ..., // 自定义格式化
|
||||
* show: true, // 是否显示(参与显隐列)
|
||||
* showInToolbar: true, // 是否在显隐列工具栏中显示(默认 true)
|
||||
* visible: true // 初始是否可见(显隐列控制)
|
||||
* }
|
||||
*
|
||||
* searchColumns 配置:同 BasicForm 的 schemas。
|
||||
*/
|
||||
|
||||
defineOptions({ name: 'BasicTable' })
|
||||
|
||||
const props = defineProps({
|
||||
// 列配置数组
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 搜索表单配置(同 BasicForm schemas)
|
||||
searchColumns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 搜索表单校验规则
|
||||
searchRules: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 搜索表单标签宽度
|
||||
searchLabelWidth: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 列表加载函数:(params) => Promise<{rows, total}> | {rows, total}
|
||||
query: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
// 额外固定查询参数(每次请求合并传入,不参与搜索表单重置)
|
||||
extraParams: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 初始查询参数(搜索字段的默认值)
|
||||
defaultParams: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 是否显示多选列
|
||||
selection: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示序号列
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示工具栏
|
||||
showToolbar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示默认新增按钮(无 toolbar 插槽时生效)
|
||||
showAdd: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示搜索栏(可双向控制)
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 行 key
|
||||
rowKey: {
|
||||
type: [String, Function],
|
||||
default: undefined
|
||||
},
|
||||
// 边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 斑马纹
|
||||
stripe: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 表格/表单尺寸(large/default/small)
|
||||
size: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
// 表格高度
|
||||
height: { type: [String, Number], default: undefined },
|
||||
maxHeight: { type: [String, Number], default: undefined },
|
||||
// 默认排序(透传 el-table default-sort,如 { prop: 'createTime', order: 'descending' })
|
||||
defaultSort: {
|
||||
type: Object,
|
||||
default: undefined
|
||||
},
|
||||
// 每页条数可选项
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default: () => [10, 20, 30, 50]
|
||||
},
|
||||
// 默认每页条数
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
// 是否在挂载后自动加载
|
||||
autoLoad: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['add', 'update:showSearch', 'row-click', 'sort-change', 'selection-change'])
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const tableRef = ref()
|
||||
const searchFormRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const selectionList = ref([])
|
||||
const showSearchInternal = ref(props.showSearch)
|
||||
|
||||
// 查询参数:搜索字段 + 分页
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: props.pageSize,
|
||||
...props.defaultParams
|
||||
})
|
||||
|
||||
watch(() => props.showSearch, (v) => (showSearchInternal.value = v))
|
||||
watch(showSearchInternal, (v) => emit('update:showSearch', v))
|
||||
|
||||
// ====== 列显隐状态 ======
|
||||
// 内部维护列状态(带 key/visible),供 RightToolbar 控制显隐
|
||||
const tableColumns = ref([])
|
||||
function initColumns() {
|
||||
tableColumns.value = props.columns.map((col, index) => ({
|
||||
...col,
|
||||
key: col.key || col.prop || index,
|
||||
visible: col.visible !== false && col.show !== false
|
||||
}))
|
||||
}
|
||||
initColumns()
|
||||
watch(
|
||||
() => props.columns,
|
||||
() => initColumns(),
|
||||
{ deep: false }
|
||||
)
|
||||
|
||||
// 实际渲染的列(visible !== false)
|
||||
const visibleColumns = computed(() => tableColumns.value.filter((c) => c.visible !== false))
|
||||
|
||||
// 参与显隐工具栏的列(有 label 且未禁用工具栏显示)
|
||||
const toolbarColumns = computed(() =>
|
||||
tableColumns.value.filter((c) => c.label && c.showInToolbar !== false)
|
||||
)
|
||||
|
||||
// ====== 字典加载(用于表格列 dict 渲染) ======
|
||||
const dictMap = reactive({})
|
||||
const dictTypes = computed(() => {
|
||||
const set = new Set()
|
||||
props.columns.forEach((c) => c.dict && set.add(c.dict))
|
||||
props.searchColumns.forEach((c) => c.dict && set.add(c.dict))
|
||||
return [...set]
|
||||
})
|
||||
watch(
|
||||
dictTypes,
|
||||
(types) => {
|
||||
if (!types.length) return
|
||||
const dictRefs = proxy.useDict(...types)
|
||||
types.forEach((t) => {
|
||||
const d = dictRefs[t]
|
||||
dictMap[t] = isRef(d) ? d : ref(d)
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
function getDictOptions(type) {
|
||||
const d = dictMap[type]
|
||||
return isRef(d) ? d.value : d || []
|
||||
}
|
||||
|
||||
// ====== 搜索栏插槽透传:search-xxx -> form-xxx ======
|
||||
const slots = useSlots()
|
||||
const searchSlotNames = computed(() =>
|
||||
Object.keys(slots).filter((n) => n.startsWith('search-') && n !== 'search-extra')
|
||||
)
|
||||
function toFormSlot(name) {
|
||||
return 'form-' + name.slice('search-'.length)
|
||||
}
|
||||
|
||||
// ====== 数据加载 ======
|
||||
async function getList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = { ...queryParams, ...props.extraParams }
|
||||
const res = await props.query(params)
|
||||
tableData.value = res?.rows || []
|
||||
total.value = res?.total || 0
|
||||
} catch (e) {
|
||||
console.error('BasicTable 加载失败', e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
function resetQuery() {
|
||||
searchFormRef.value?.resetFields()
|
||||
Object.keys(props.defaultParams || {}).forEach((k) => {
|
||||
queryParams[k] = props.defaultParams[k]
|
||||
})
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 选中变化
|
||||
function handleSelectionChange(val) {
|
||||
selectionList.value = val
|
||||
emit('selection-change', val)
|
||||
}
|
||||
const selectionIds = computed(() =>
|
||||
selectionList.value.map((r) => (props.rowKey ? r[props.rowKey] : r.id)).filter((v) => v !== undefined)
|
||||
)
|
||||
|
||||
// ====== 暴露方法 ======
|
||||
function refresh() {
|
||||
getList()
|
||||
}
|
||||
function reload() {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
function getSelection() {
|
||||
return selectionList.value
|
||||
}
|
||||
function getSelectionIds() {
|
||||
return selectionIds.value
|
||||
}
|
||||
function clearSelection() {
|
||||
tableRef.value?.clearSelection()
|
||||
}
|
||||
function toggleRowSelection(row, selected) {
|
||||
tableRef.value?.toggleRowSelection(row, selected)
|
||||
}
|
||||
function getTableRef() {
|
||||
return tableRef.value
|
||||
}
|
||||
function getSearchFormRef() {
|
||||
return searchFormRef.value
|
||||
}
|
||||
function getQueryParams() {
|
||||
return { ...queryParams, ...props.extraParams }
|
||||
}
|
||||
function setQueryParams(p) {
|
||||
Object.assign(queryParams, p)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
refresh,
|
||||
reload,
|
||||
getList,
|
||||
handleQuery,
|
||||
resetQuery,
|
||||
getSelection,
|
||||
getSelectionIds,
|
||||
clearSelection,
|
||||
toggleRowSelection,
|
||||
getTableRef,
|
||||
getSearchFormRef,
|
||||
getQueryParams,
|
||||
setQueryParams
|
||||
})
|
||||
|
||||
if (props.autoLoad) {
|
||||
onMounted(getList)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.basic-table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user