fix:完善proTablev2组件
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
// 捕获取消行为或接口错误
|
// 捕获取消行为或接口错误
|
||||||
|
|||||||
@@ -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,26 +146,35 @@ 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",
|
||||||
@@ -188,7 +186,11 @@ const columns = computed(()=>[
|
|||||||
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,18 +201,14 @@ 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: "添加二级字段",
|
||||||
@@ -218,7 +216,7 @@ const columns = computed(()=>[
|
|||||||
link: true,
|
link: true,
|
||||||
permission: ["edit"],
|
permission: ["edit"],
|
||||||
show: () => {
|
show: () => {
|
||||||
return !hasChild.value
|
return !hasChild.value;
|
||||||
},
|
},
|
||||||
onClick: (row) => handleAddNext(row),
|
onClick: (row) => handleAddNext(row),
|
||||||
},
|
},
|
||||||
@@ -236,9 +234,9 @@ const columns = computed(()=>[
|
|||||||
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,
|
||||||
@@ -262,7 +262,7 @@ const onLevelNext = (row) => {
|
|||||||
...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);
|
||||||
@@ -318,13 +320,20 @@ const addFields = () => {
|
|||||||
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 重置数据信息
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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),
|
|
||||||
},
|
|
||||||
() => "编辑"
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
ElButton,
|
|
||||||
{
|
|
||||||
link:true,
|
link:true,
|
||||||
|
permission: ["edit"],
|
||||||
|
onClick: (row) => handleEdit(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "字段配置",
|
||||||
type: "primary",
|
type: "primary",
|
||||||
onClick: () => handlefieldsConfig(rowData),
|
|
||||||
},
|
|
||||||
() => "字段配置"
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
ElButton,
|
|
||||||
{
|
|
||||||
link:true,
|
link:true,
|
||||||
|
permission: ["config"],
|
||||||
|
onClick: (row) => handlefieldsConfig(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user