fix:完善proTablev2组件

This commit is contained in:
liangdong
2026-01-13 22:35:28 +08:00
parent 772e35b35b
commit e4c5330a18
5 changed files with 185 additions and 106 deletions

View File

@@ -40,10 +40,12 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { ref, computed, onMounted, onUnmounted, nextTick } from "vue"; import { ref, computed, onMounted, onUnmounted, nextTick } from "vue";
import { ElTag, ElButton, ElText } from "element-plus"; import { ElTag, ElButton, ElText,ElTooltip } from "element-plus";
import { debounce } from "lodash-es"; import { debounce } from "lodash-es";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { DictManage } from "@/dict"; import { DictManage } from "@/dict";
import { usePermission } from "@/utils/permission";
const { checkPermission } = usePermission();
// 组件属性 // 组件属性
const props = defineProps({ const props = defineProps({
@@ -111,11 +113,20 @@ const RenderFactory = {
}, },
// 文本自动省略 // 文本自动省略
ellipsis: (scope: any, col: any) => { ellipsis: (scope: any, col: any) => {
const val = scope.rowData[col.prop] || "-"; const val = scope.rowData[col.prop] ?? "-";
return ( return (
<div class="mj-ellipsis" title={val}> <ElTooltip
effect="dark"
content={String(val)}
placement="top"
// 只有当内容不是 "-" 且达到截断条件时才显示可选优化
fallback-placements={['bottom', 'top']}
enterable={false}
>
<div class="mj-ellipsis-cell">
{val} {val}
</div> </div>
</ElTooltip>
); );
}, },
}; };
@@ -195,6 +206,37 @@ const adaptedColumns = computed(() => {
fixed: col.fixed, fixed: col.fixed,
align: col.align || "left", align: col.align || "left",
cellRenderer: (scope: any) => { cellRenderer: (scope: any) => {
const isOp = col.prop === 'actions' || col.valueType === 'actions';
const cellClass = isOp ? 'operation-column-cell' : '';
// 2. 如果是操作列,包裹一层带 class 的 div
if (isOp && col.actions) {
return (
<div class={cellClass}>
<div class="v2-operation-btns">
{col.actions.map(btn => {
// --- 权限校验开始 ---
if (btn.permission) {
const hasAuth = checkPermission(btn.permission);
if (!hasAuth) return null; // 没权限,直接不渲染
}
// 2. 现有的 show 逻辑判断(业务逻辑显隐)
const isShow = typeof btn.show === 'function' ? btn.show(scope.rowData) : true;
if (!isShow) return null;
const { onClick, label, permission, show, ...otherProps } = btn;
return (
<ElButton
{...otherProps}
onClick={() => btn.onClick(scope.rowData)}
>
{btn.label}
</ElButton>
);
})}
</div>
</div>
);
}
if (typeof col.render === "function") return col.render(scope); if (typeof col.render === "function") return col.render(scope);
if (col.valueType && RenderFactory[col.valueType]) { if (col.valueType && RenderFactory[col.valueType]) {
return RenderFactory[col.valueType](scope, col); return RenderFactory[col.valueType](scope, col);
@@ -228,7 +270,7 @@ const fetchTableData = async (isReset = false) => {
innerData.value = isReset ? records : [...innerData.value, ...records]; innerData.value = isReset ? records : [...innerData.value, ...records];
total.value = res?.total || 0; total.value = res?.total || 0;
if (innerData.value.length >= total.value || newList.length < props.pageSize) { if (innerData.value.length >= total.value || records.length < props.pageSize) {
noMore.value = true; noMore.value = true;
} }
pageNo.value++; pageNo.value++;
@@ -275,6 +317,7 @@ defineExpose({
updateRow, updateRow,
removeRow, removeRow,
addRow, addRow,
updateSize,
getCurrentParams: () => ({ getCurrentParams: () => ({
pageNo: pageNo.value - 1, // 因为 fetch 完后 pageNo 会自增,所以要减 1 才是当前页 pageNo: pageNo.value - 1, // 因为 fetch 完后 pageNo 会自增,所以要减 1 才是当前页
pageSize: props.pageSize, pageSize: props.pageSize,
@@ -368,4 +411,26 @@ defineExpose({
background-color: #f5f7fa; background-color: #f5f7fa;
} }
} }
:deep(.operation-column-cell) {
// 默认隐藏内容,但保留位置
.v2-operation-btns {
visibility: hidden;
opacity: 0;
transition: opacity 0.2s;
display: flex;
justify-content: center;
gap: 8px;
}
}
// 2. 当鼠标移入行时,显示该行内的操作按钮
:deep(.el-table-v2__row:hover) {
background-color: #f5f7fa; // 顺便加个行高亮
.operation-column-cell .v2-operation-btns {
visibility: visible;
opacity: 1;
}
}
</style> </style>

View File

@@ -45,12 +45,15 @@ export const useTableAction = (tableRef: any) => {
* @param deleteApi 删除接口函数 * @param deleteApi 删除接口函数
*/ */
const handleDelete = async ( const handleDelete = async (
id: string | number, deleteApi: (id: any) => Promise<any>,
deleteApi: (id: any) => Promise<any> ...args: any[]
) => { ) => {
try { try {
const res = await deleteApi(id); const res = await deleteApi(...args);
const id = args[0];
if(id){
tableRef.value?.removeRow(id); tableRef.value?.removeRow(id);
}
return res; return res;
} catch (error) { } catch (error) {
// 捕获取消行为或接口错误 // 捕获取消行为或接口错误

View File

@@ -86,33 +86,19 @@
<!-- Table列表 --> <!-- Table列表 -->
<CommonTable <CommonTable
ref="tableRef" ref="tableRef"
v-model:data="list" :data="list"
:columns="columns" :columns="columns"
pagination :total="total"
height="calc(100vh - 186px)"
:immediate="false"
:request-api="fetchData" :request-api="fetchData"
> >
<!-- 名称点击 --> <!-- 名称点击 -->
<template #labelName="{ row }"> <!-- <template #labelName="{ row }">
<el-button link type="primary" @click="onLevelNext(row)" v-if="!hasChild">{{ <el-button link type="primary" @click="onLevelNext(row)" v-if="!hasChild">{{
row.label row.label
}}</el-button> }}</el-button>
<span v-else>{{ row.label }}</span> <span v-else>{{ row.label }}</span>
</template> </template> -->
<!-- 状态插槽 -->
<template #status="{ row }">
<div
class="mj-status-dot"
:style="{
'--data-status-color': DictManage.statusDictColor[row.status],
}"
@click="handleDictStatus(row)"
>
{{ dicts.permission_list_enable_disable.find(item=>item.value == row.status)?.label }}
</div>
</template>
</CommonTable> </CommonTable>
<!-- 新增字段 --> <!-- 新增字段 -->
<dictFieldLevelManage <dictFieldLevelManage
@@ -133,12 +119,15 @@
</el-drawer> </el-drawer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import CommonTable from "@/components/proTable/index.vue"; import { h } from "vue";
import CommonTable from "@/components/proTable/proTablev2.vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import dictFieldLevelManage from "./dictFieldLevelManage.vue"; import dictFieldLevelManage from "./dictFieldLevelManage.vue";
import { DictManage } from "@/dict"; import { DictManage } from "@/dict";
import { useDict } from '@/hooks/useDictData'; import { useTableAction } from "@/hooks/useTableAction";
const { dicts,refresh } = useDict('permission_list_enable_disable'); import { useDict } from "@/hooks/useDictData";
const { dicts, refresh } = useDict("permission_list_enable_disable");
import { import {
getDictTypeValue, getDictTypeValue,
deleteDictTypeValue, deleteDictTypeValue,
@@ -157,38 +146,51 @@ const filterForm = reactive({
}); });
const size = ref<string>(""); //抽屉大小 const size = ref<string>(""); //抽屉大小
const tableRef = ref(null); const tableRef = ref(null);
const { handleAction, handleDelete: runDelete } = useTableAction(tableRef);
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
const parentId = ref<string>(""); const parentId = ref<string>("");
const total = ref(0); const total = ref(0);
const list = ref([]); const list = ref([]);
const hasChild = ref<boolean>(false); //是否是子级弹窗 const hasChild = ref<boolean>(false); //是否是子级弹窗
const childId = ref<string|number>(''); // 子集的id const childId = ref<string | number>(""); // 子集的id
const childModals = ref([]); //子弹窗的列表 const childModals = ref([]); //子弹窗的列表
const childModalRefs = ref({}); // 子弹窗的引用 const childModalRefs = ref({}); // 子弹窗的引用
const onCloseCallback = ref<Function | null>(null); // 关闭回调函数 const onCloseCallback = ref<Function | null>(null); // 关闭回调函数
const columns = computed(()=>[ const columns = computed(() => [
{ {
prop: "id", prop: "id",
align:'center',
label: "字典编码", label: "字典编码",
}, },
{ {
prop: "label", prop: "label",
label: "字典名称", label: "字典名称",
align: "center", align: "center",
showOverflowTooltip:true, valueType: "ellipsis",
slot: "labelName", render: ({ rowData }: any) => {
if (hasChild.value) {
return h("span", { class: "v2-cell-text" }, rowData.label);
}
return h(ElButton, { type: "primary", link:true,onClick:()=>{
onLevelNext(rowData);
} }, () => rowData.label);
}
}, },
{ {
prop: "value", prop: "value",
label: "字典值", label: "字典值",
showOverflowTooltip:true, showOverflowTooltip: true,
align: "center", align: "center",
}, },
{ {
prop: "status", prop: "status",
label: "状态", label: "状态",
align: "center", align: "center",
slot: "status", valueType: "status",
options: computed(() => dicts.value.permission_list_enable_disable),
onClick: ({ cellValue, rowData }) => {
handleDictStatus(rowData);
},
}, },
{ {
prop: "sort", prop: "sort",
@@ -199,46 +201,42 @@ const columns = computed(()=>[
prop: "updateTime", prop: "updateTime",
label: "更新时间", label: "更新时间",
align: "center", align: "center",
showOverflowTooltip: true, valueType: "date",
formatter: (val) => { format: "YYYY-MM-DD HH:mm",
return val.updateTime
? dayjs(val.updateTime).format("YYYY-MM-DD HH:mm")
: "-";
},
}, },
{ {
prop: "actions", prop: "actions",
label: "操作", label: "操作",
align: "right", align: "right",
width:200, width: 300,
actions:[ actions: [
{ {
label: "添加二级字段", label: "添加二级字段",
type: "primary", type: "primary",
link:true, link: true,
permission: ["edit"], permission: ["edit"],
show:()=>{ show: () => {
return !hasChild.value return !hasChild.value;
}, },
onClick: (row) => handleAddNext(row), onClick: (row) => handleAddNext(row),
}, },
{ {
label: "编辑", label: "编辑",
type: "primary", type: "primary",
link:true, link: true,
permission: ["edit"], permission: ["edit"],
onClick: (row) => handleEdit(row), onClick: (row) => handleEdit(row),
}, },
{ {
label: "删除", label: "删除",
type: "danger", type: "danger",
link:true, link: true,
permission: ["delete"], permission: ["delete"],
onClick: (row) => handleDelete(row), onClick: (row) => handleDelete(row),
}, },
] ],
}, },
]) ]);
// 设置子弹窗引用 // 设置子弹窗引用
const setChildModalRef = (el, key) => { const setChildModalRef = (el, key) => {
@@ -248,7 +246,9 @@ const setChildModalRef = (el, key) => {
}; };
// 点击获取二级菜单数据 // 点击获取二级菜单数据
const onLevelNext = (row) => { const onLevelNext = (row) => {
const childKey = `child-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const childKey = `child-${Date.now()}-${Math.random()
.toString(36)
.substr(2, 9)}`;
childModals.value.push({ childModals.value.push({
key: childKey, key: childKey,
data: row, data: row,
@@ -260,9 +260,9 @@ const onLevelNext = (row) => {
if (childRef) { if (childRef) {
childRef.open({ childRef.open({
...row, ...row,
parentId:parentId.value, parentId: parentId.value,
hasChild: true, hasChild: true,
onClose: () => removeChildModal(childKey) onClose: () => removeChildModal(childKey),
}); });
} }
}); });
@@ -270,7 +270,7 @@ const onLevelNext = (row) => {
// 移除当前组件 // 移除当前组件
const removeChildModal = (key: string) => { const removeChildModal = (key: string) => {
const index = childModals.value.findIndex(child => child.key === key); const index = childModals.value.findIndex((child) => child.key === key);
if (index !== -1) { if (index !== -1) {
childModals.value.splice(index, 1); childModals.value.splice(index, 1);
} }
@@ -285,9 +285,11 @@ const fetchData = async (params) => {
...params, ...params,
keyword: searchQuery.value, keyword: searchQuery.value,
...filterForm, ...filterForm,
} };
const response = hasChild.value ? await getNextDictMenu(parentId.value,childId.value,queryParams) : await getDictTypeValue(parentId.value, queryParams); const response = hasChild.value
? await getNextDictMenu(parentId.value, childId.value, queryParams)
: await getDictTypeValue(parentId.value, queryParams);
return response; return response;
} catch (error) { } catch (error) {
console.log("getTableData Error", error); console.log("getTableData Error", error);
@@ -311,20 +313,27 @@ const handleDictStatus = async (row) => {
const addFields = () => { const addFields = () => {
dictTitle.value = "新增字段"; dictTitle.value = "新增字段";
addVisible.value = true; addVisible.value = true;
Object.assign(selectItem,{ Object.assign(selectItem, {
id:null, id: null,
parentId:null, parentId: null,
label: "", label: "",
value: "", value: "",
sort: 0, sort: 0,
status:1, status: 1,
remark:'' remark: "",
}) });
}; };
// 确定刷新数据 // 确定刷新数据
const onConfirmSuccess = () => { const onConfirmSuccess = () => {
tableRef.value && tableRef.value.refresh(); if (selectItem.id) {
handleAction(Promise.resolve(true), selectItem.id, getTableData, {
showMsg: false,
silentUpdate: true,
});
} else {
tableRef.value?.refresh();
}
}; };
// 关闭popover 重置数据信息 // 关闭popover 重置数据信息
@@ -342,7 +351,7 @@ const onReset = () => {
const handleAddNext = async (item) => { const handleAddNext = async (item) => {
addVisible.value = true; addVisible.value = true;
dictTitle.value = "添加二级字段"; dictTitle.value = "添加二级字段";
Object.assign(selectItem,{},{parentId:item.id}); Object.assign(selectItem, {}, { parentId: item.id });
}; };
// 编辑当前字段 // 编辑当前字段
const handleEdit = (item) => { const handleEdit = (item) => {
@@ -358,6 +367,8 @@ const handleDelete = async (item) => {
try { try {
await deleteDictTypeValue(parentId.value, item.id); await deleteDictTypeValue(parentId.value, item.id);
tableRef.value && tableRef.value.refresh(); tableRef.value && tableRef.value.refresh();
await runDelete();
} catch (error) { } catch (error) {
console.log("fetch error", error); console.log("fetch error", error);
} }
@@ -382,7 +393,7 @@ defineExpose({
} }
await nextTick(); await nextTick();
if (tableRef.value) { if (tableRef.value) {
await tableRef.value.refresh(); await tableRef.value.updateSize();
} }
}, },
close() { close() {

View File

@@ -101,7 +101,6 @@ import { ElMessage } from "element-plus";
import { useTableAction } from "@/hooks/useTableAction"; import { useTableAction } from "@/hooks/useTableAction";
import { useDict } from "@/hooks/useDictData"; import { useDict } from "@/hooks/useDictData";
import { render } from "vue";
const { dicts, refresh } = useDict("permission_list_enable_disable"); const { dicts, refresh } = useDict("permission_list_enable_disable");
defineOptions({ name: "Dictionary" }); defineOptions({ name: "Dictionary" });
@@ -183,37 +182,29 @@ const columns = [
label: "操作", label: "操作",
align: "right", align: "right",
width: "300", width: "300",
render: ({ rowData }: any) => { actions: [
return h("div", { class: "space-x-2" }, [
h(
ElButton,
{ {
link: true, label: "编辑",
type: "primary", type: "primary",
onClick: () => handleEdit(rowData), link:true,
permission: ["edit"],
onClick: (row) => handleEdit(row),
}, },
() => "编辑"
),
h(
ElButton,
{ {
link: true, label: "字段配置",
type: "primary", type: "primary",
onClick: () => handlefieldsConfig(rowData), link:true,
permission: ["config"],
onClick: (row) => handlefieldsConfig(row),
}, },
() => "字段配置"
),
h(
ElButton,
{ {
link: true, label: "删除",
type: "danger", type: "danger",
onClick: () => handleDelete(rowData), link:true,
}, permission: ["delete"],
() => "删除" onClick: (row) => handleDelete(row),
), }
]); ]
},
}, },
]; ];
@@ -302,7 +293,7 @@ const handleDelete = async (item) => {
.then(async () => { .then(async () => {
try { try {
// await deleteDictValue(item.id); // await deleteDictValue(item.id);
await runDelete(item.id, deleteDictValue); await runDelete(deleteDictValue,item.id);
} catch (error) { } catch (error) {
console.log("fetch error", error); console.log("fetch error", error);
} }

View File

@@ -196,3 +196,12 @@ body {
height: 34px; height: 34px;
} }
} }
.mj-ellipsis-cell {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
cursor: default;
}