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">
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 dayjs from "dayjs";
import { DictManage } from "@/dict";
import { usePermission } from "@/utils/permission";
const { checkPermission } = usePermission();
// 组件属性
const props = defineProps({
@@ -111,11 +113,20 @@ const RenderFactory = {
},
// 文本自动省略
ellipsis: (scope: any, col: any) => {
const val = scope.rowData[col.prop] || "-";
const val = scope.rowData[col.prop] ?? "-";
return (
<div class="mj-ellipsis" title={val}>
{val}
</div>
<ElTooltip
effect="dark"
content={String(val)}
placement="top"
// 只有当内容不是 "-" 且达到截断条件时才显示可选优化
fallback-placements={['bottom', 'top']}
enterable={false}
>
<div class="mj-ellipsis-cell">
{val}
</div>
</ElTooltip>
);
},
};
@@ -195,6 +206,37 @@ const adaptedColumns = computed(() => {
fixed: col.fixed,
align: col.align || "left",
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 (col.valueType && RenderFactory[col.valueType]) {
return RenderFactory[col.valueType](scope, col);
@@ -228,7 +270,7 @@ const fetchTableData = async (isReset = false) => {
innerData.value = isReset ? records : [...innerData.value, ...records];
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;
}
pageNo.value++;
@@ -275,6 +317,7 @@ defineExpose({
updateRow,
removeRow,
addRow,
updateSize,
getCurrentParams: () => ({
pageNo: pageNo.value - 1, // 因为 fetch 完后 pageNo 会自增,所以要减 1 才是当前页
pageSize: props.pageSize,
@@ -368,4 +411,26 @@ defineExpose({
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>

View File

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

View File

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

View File

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

View File

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