初始化
parent
de5d8c6208
commit
bbb43faaa3
@ -0,0 +1,14 @@
|
||||
import axios from '@/utils/axios';
|
||||
import type { ApiResponse } from '@/api/types';
|
||||
export async function getTableOptionsVOList(options?: { [key: string]: any }) {
|
||||
return axios<ApiResponse<any[]>>('/tableInfo/get/list', {
|
||||
method: 'GET',
|
||||
...(options || {})
|
||||
});
|
||||
}
|
||||
export async function getTableInfoVO(options?: { [key: string]: any }) {
|
||||
return axios<ApiResponse<any[]>>('/tableInfo/get/vo', {
|
||||
method: 'GET',
|
||||
...(options || {})
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
export interface ApiResponse<T = any> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
import type { DataGridSchema } from './type';
|
||||
import type { PropType } from 'vue';
|
||||
const props = defineProps({
|
||||
// 表格的列规范
|
||||
columns: {
|
||||
type: Array as PropType<DataGridSchema>,
|
||||
required: true
|
||||
},
|
||||
// 表格的数据源
|
||||
tableData: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
spanMethod: {
|
||||
type: Function as PropType<
|
||||
(
|
||||
row: any,
|
||||
column: any,
|
||||
rowIndex: number,
|
||||
columnIndex: number
|
||||
) => { rowspan: number; colspan: number } | null
|
||||
>,
|
||||
default: undefined
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['on-action']);
|
||||
const handleActionClick = (actionKey: string, row: any) => {
|
||||
emit('on-action', {
|
||||
actionKey,
|
||||
row
|
||||
});
|
||||
};
|
||||
// 状态字典映射
|
||||
const getStatusConfig = (statusValue: string | number) => {
|
||||
const dict: Record<string, { label: string; elType: string }> = {
|
||||
normal: { label: '正常', elType: 'success' }, // 绿色
|
||||
fault: { label: '故障', elType: 'warning' }, // 黄色
|
||||
alarm: { label: '报警', elType: 'danger' }, // 红色
|
||||
running: { label: '运行', elType: 'primary' }, // 蓝色
|
||||
turnOn: { label: '开启', elType: 'info' }, // 灰色
|
||||
turnOff: { label: '关闭', elType: 'info' }, // 灰色
|
||||
no_action: { label: '未动作', elType: 'info' } // 灰色
|
||||
};
|
||||
return dict[statusValue] || { label: '未知', elType: 'info' };
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="standard-data-grid">
|
||||
<el-table :data="tableData" border :span-method="spanMethod">
|
||||
<template v-for="(item, index) in columns" :key="item.key">
|
||||
<el-table-column
|
||||
v-if="item.children && item.children.length > 0"
|
||||
:label="item.title"
|
||||
:prop="item.key"
|
||||
:align="item.align ?? 'center'"
|
||||
>
|
||||
<el-table-column
|
||||
v-for="subItem in item.children"
|
||||
:key="subItem.key"
|
||||
:prop="subItem.key"
|
||||
:label="subItem.title"
|
||||
:align="subItem.align ?? 'center'"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div v-if="subItem.type === 'action'" class="action-btn-group">
|
||||
<template v-for="btn in subItem.actions" :key="btn.actionKey">
|
||||
<el-button
|
||||
v-if="!btn.showOn || btn.showOn.values.includes(scope.row[btn.showOn.key])"
|
||||
:type="btn.btnType || 'primary'"
|
||||
size="small"
|
||||
@click="handleActionClick(btn.actionKey, scope.row)"
|
||||
>
|
||||
{{ btn.label }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-else
|
||||
:label="item.title"
|
||||
:prop="item.key"
|
||||
:align="item.align"
|
||||
:width="item.width"
|
||||
:min-width="item.minWidth"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span v-if="item.type === 'text'">
|
||||
{{ scope.row[item.key] ?? '--' }}
|
||||
</span>
|
||||
<span v-else-if="item.type === 'number'">
|
||||
{{ scope.row[item.key] }}<span class="unit">{{ item.unit }}</span>
|
||||
</span>
|
||||
<span v-else-if="item.type === 'status'">
|
||||
<el-tag :type="getStatusConfig(scope.row[item.key]).elType">
|
||||
{{ getStatusConfig(scope.row[item.key]).label }}
|
||||
</el-tag>
|
||||
</span>
|
||||
<div v-else-if="item.type === 'action'" class="action-btn-group">
|
||||
<template v-for="btn in item.actions" :key="btn.actionKey">
|
||||
<el-button
|
||||
v-if="!btn.showOn || btn.showOn.values.includes(scope.row[btn.showOn.key])"
|
||||
:type="btn.btnType || 'primary'"
|
||||
:disabled="
|
||||
!btn.disabledOn || btn.disabledOn.values.includes(scope.row[btn.disabledOn.key])
|
||||
"
|
||||
@click="handleActionClick(btn.actionKey, scope.row)"
|
||||
>
|
||||
{{ btn.label }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
item.horizontalMergeRule &&
|
||||
scope.row[item.horizontalMergeRule.matchCondition.key] ===
|
||||
item.horizontalMergeRule.matchCondition.value
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-if="item?.horizontalMergeRule.renderConfig?.type === 'action'"
|
||||
class="merged-action-cell"
|
||||
>
|
||||
<el-button
|
||||
v-for="btn in item.horizontalMergeRule.renderConfig.actions"
|
||||
:key="btn.actionKey"
|
||||
:type="btn.btnType"
|
||||
@click="handleActionClick(btn.actionKey, scope.row)"
|
||||
>
|
||||
{{ btn.label }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 表格列的基本规范
|
||||
*/
|
||||
interface BaseColumn {
|
||||
// 表头的标题
|
||||
title: string;
|
||||
// 数据源对应的字段名
|
||||
key: string;
|
||||
// 列的宽度
|
||||
width?: number | string;
|
||||
// 单元格的显示位置
|
||||
align?: 'left' | 'center' | 'right';
|
||||
// 固定列
|
||||
fixed?: 'left' | 'right';
|
||||
// 最小宽度,适应不同大小的屏幕
|
||||
minWidth?: number | string;
|
||||
// 标记这个列是否需要纵向合并
|
||||
mergeRow?: boolean;
|
||||
// 标记这个列是否需要横向合并
|
||||
horizontalMergeRule?: {
|
||||
matchCondition: { key: string; value: string | boolean };
|
||||
mergeKeys: string[];
|
||||
renderConfig?: {
|
||||
type: string;
|
||||
actions?: ActionButton[];
|
||||
};
|
||||
};
|
||||
// 多级表头
|
||||
children?: TableColumnSchema[];
|
||||
// 标记合并行时的参考列
|
||||
mergeBy?: string;
|
||||
}
|
||||
// 纯文本列
|
||||
export interface TextColumn extends BaseColumn {
|
||||
type: 'text';
|
||||
}
|
||||
// 状态列
|
||||
export interface StatusColumn extends BaseColumn {
|
||||
type: 'status';
|
||||
dictCode?: string;
|
||||
}
|
||||
// 数值列
|
||||
export interface NumberColumn extends BaseColumn {
|
||||
type: 'number';
|
||||
// 单位后缀
|
||||
unit?: string;
|
||||
}
|
||||
// 操作列
|
||||
export interface ActionColumn extends BaseColumn {
|
||||
type: 'action';
|
||||
// 操作列包含按钮列表
|
||||
actions: ActionButton[];
|
||||
}
|
||||
// 按钮定义的schema
|
||||
export interface ActionButton {
|
||||
// 按钮的文字
|
||||
label: string;
|
||||
//抛出给外层的事件名
|
||||
actionKey: string;
|
||||
// 按钮的视觉类型
|
||||
btnType?: 'primary' | 'default' | 'dashed' | 'danger' | 'link' | 'text';
|
||||
// 根据状态值控制显示
|
||||
showOn?: {
|
||||
key: string; // 依赖哪一列数据
|
||||
values: any[]; // 当值等于数组中的哪些项时,才显示按钮
|
||||
};
|
||||
// 根据状态来控制禁用
|
||||
disabledOn?: {
|
||||
key: string;
|
||||
values: any[];
|
||||
};
|
||||
isRound?: boolean;
|
||||
}
|
||||
export type TableColumnSchema = TextColumn | StatusColumn | NumberColumn | ActionColumn;
|
||||
export type DataGridSchema = TableColumnSchema[];
|
||||
@ -0,0 +1,118 @@
|
||||
// 提取叶子节点列,解决多级表头索引对齐问题
|
||||
const getLeafColumns = (columns: any[]) => {
|
||||
let leaves: any[] = [];
|
||||
columns.forEach((col) => {
|
||||
if (col.children && col.children.length > 0) {
|
||||
leaves.push(...getLeafColumns(col.children));
|
||||
} else {
|
||||
leaves.push(col);
|
||||
}
|
||||
});
|
||||
return leaves;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param data 表格的数据源
|
||||
* @param rowMergeConfigs 纵向合并配置 [{ key: 'xxx', mergeBy: 'yyy' }]
|
||||
* @param rawColumns 表格列配置
|
||||
*/
|
||||
export const createRowSpanMethod = (
|
||||
data: any[],
|
||||
rawColumns: any[],
|
||||
rowMergeConfigs: { key: string; mergeBy: string }[]
|
||||
) => {
|
||||
// 1. 拍平获取物理叶子列
|
||||
const columns = getLeafColumns(rawColumns);
|
||||
const rowSpanInfos: Record<string, number[]> = {};
|
||||
rowMergeConfigs.forEach((config) => {
|
||||
const { key, mergeBy } = config;
|
||||
rowSpanInfos[key] = [];
|
||||
let position = 0;
|
||||
data.forEach((item, index) => {
|
||||
if (index === 0) {
|
||||
rowSpanInfos[key].push(1);
|
||||
position = 0;
|
||||
} else {
|
||||
// 如果当前行数据和上一行数据相等,则当前行不显示
|
||||
if (
|
||||
item[mergeBy] === data[index - 1][mergeBy] &&
|
||||
!item.colMergeConfig &&
|
||||
!data[index - 1].colMergeConfig
|
||||
) {
|
||||
rowSpanInfos[key][position] += 1;
|
||||
rowSpanInfos[key].push(0);
|
||||
} else {
|
||||
rowSpanInfos[key].push(1);
|
||||
position = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return ({ row, column, rowIndex, columnIndex }: any) => {
|
||||
// 优先处理横向合并
|
||||
const ruleColumn = columns.find(
|
||||
(col) =>
|
||||
col.horizontalMergeRule &&
|
||||
row[col.horizontalMergeRule.matchCondition.key] ===
|
||||
col.horizontalMergeRule.matchCondition.value
|
||||
);
|
||||
if (ruleColumn) {
|
||||
const rule = ruleColumn.horizontalMergeRule;
|
||||
const targetKeys = rule.mergeKeys;
|
||||
const validIndices = targetKeys
|
||||
.map((key) => columns.findIndex((item) => item.key === key))
|
||||
.filter((index) => index != -1);
|
||||
|
||||
if (validIndices.length > 0) {
|
||||
const minIndex = Math.min(...validIndices);
|
||||
const maxIndex = Math.max(...validIndices);
|
||||
const actualColSpan = maxIndex - minIndex + 1;
|
||||
if (columnIndex == minIndex) {
|
||||
return {
|
||||
rowspan: 1,
|
||||
colspan: actualColSpan
|
||||
};
|
||||
}
|
||||
if (columnIndex > minIndex && columnIndex <= maxIndex) {
|
||||
return {
|
||||
rowspan: 0,
|
||||
colspan: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// 再处理纵向合并
|
||||
const mergeTarget = rowMergeConfigs.find((c) => c.key === column.property);
|
||||
if (mergeTarget) {
|
||||
const rowspan = rowSpanInfos[column.property][rowIndex];
|
||||
const colspan = rowspan > 0 ? 1 : 0;
|
||||
return { rowspan, colspan };
|
||||
}
|
||||
return { rowspan: 1, colspan: 1 };
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 通用数组聚合标记函数
|
||||
* @param {Array} list - 原始数组
|
||||
* @param {String} groupKey - 按照哪个字段进行分组统计 (例如 'deviceName')
|
||||
* @param {String} flagName - 生成的布尔值标记名称 (例如 'isMultiNode')
|
||||
*/
|
||||
export const injectMultiNodeFlag = (list: any[], groupKey: string, flagName: string) => {
|
||||
const countMap = {} as Record<string, number>;
|
||||
// 统计
|
||||
list.forEach((item) => {
|
||||
const val = item[groupKey];
|
||||
if (val) countMap[val] = (countMap[val] || 0) + 1;
|
||||
});
|
||||
|
||||
// 映射
|
||||
return list.map((item) => {
|
||||
const val = item[groupKey];
|
||||
if (!val) return item; // 非目标数据,直接返回
|
||||
return {
|
||||
...item,
|
||||
[flagName]: countMap[val] > 1 // 动态注入标记
|
||||
};
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,52 @@
|
||||
import { computed, type Ref, unref } from 'vue';
|
||||
import { createRowSpanMethod } from '@/components/custom-components/table-vue/utils/tableUtils';
|
||||
/**
|
||||
* 只能表格合并 hook
|
||||
* @param tableData 表格数据源
|
||||
* @param tableColumns 表格列配置
|
||||
*/
|
||||
export function useTableSpan(tableData: Ref<any[]>, tableColumns: any[] | Ref<any[]>) {
|
||||
const flatColumns = computed(() => {
|
||||
const columns = unref(tableColumns); // 兼容普通对象和ref对象
|
||||
const flatten = (cols: any[]): any[] => {
|
||||
let result: any[] = [];
|
||||
cols.forEach((col) => {
|
||||
result.push(col);
|
||||
if (col.children && col.children.length > 0) {
|
||||
result.push(...flatten(col.children));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
return flatten(columns);
|
||||
});
|
||||
//自动提取需要纵向合并的列配置
|
||||
const rowMergeConfigs = computed(() => {
|
||||
return flatColumns.value
|
||||
.filter((col) => col.mergeRow)
|
||||
.map((col) => {
|
||||
return {
|
||||
key: col.key,
|
||||
mergeBy: col.mergeBy || col.key
|
||||
};
|
||||
});
|
||||
});
|
||||
// 判断表格需不需要开启合并引擎
|
||||
const needSpan = computed(() => {
|
||||
// 有任何列配置了纵向合并
|
||||
const hasRowMerge = rowMergeConfigs.value.length;
|
||||
// 有任何列配置了横向合并
|
||||
const hasColMerge = flatColumns.value.some((col) => !!col.horizontalMergeRule);
|
||||
return hasRowMerge || hasColMerge;
|
||||
});
|
||||
// 动态返回合并函数
|
||||
const spanMethod = computed(() => {
|
||||
if (!needSpan.value) {
|
||||
return undefined;
|
||||
}
|
||||
return createRowSpanMethod(unref(tableData), unref(tableColumns), rowMergeConfigs.value);
|
||||
});
|
||||
return {
|
||||
spanMethod
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
import axios from 'axios';
|
||||
const myAxios = axios.create({
|
||||
baseURL: 'http://localhost:8080',
|
||||
timeout: 60000,
|
||||
withCredentials: true
|
||||
});
|
||||
export default myAxios;
|
||||
Loading…
Reference in New Issue