Learn.VideoAnalysis/VideoAnalysis/WebUI/src/components/hTable/index.vue

628 lines
18 KiB
Vue

<script setup lang="ts">
import {
ref,
unref,
watch,
reactive,
computed,
onMounted,
nextTick,
onUnmounted,
getCurrentInstance,
onBeforeMount,
PropType,
shallowRef,
} from "vue";
import { Search } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox, TableColumnCtx } from "element-plus";
import { defineAsyncComponent, AsyncComponentLoader } from "vue";
import {
ButtonCustomConfig,
ConditionalType,
Dialog,
intTableData,
TableColumn,
TableColumnEdit,
TableColumnSearch,
TableConfig,
} from "./hTable";
import hTableEdit from "./hTableEdit.vue";
import { hTableAPI } from "@/api/hTable";
import { getenum } from "@/api/enum";
//按钮权限
import { hasPerms } from "@/utils/auth";
const modules = import.meta.glob("/src/views/**/*.{vue,tsx}");
const props = defineProps({
//** 传入的表单数据 */
Row: {
type: Object as PropType<any>,
default: () => ({}),
},
tableConfig: {
type: Object as PropType<TableConfig>,
default: () => ({}),
},
});
const expands = ref<any[]>();
const table = ref<TableConfig>(props.tableConfig);
const tableShowColumn = ref<Record<string, TableColumn>>();
onBeforeMount(() => {
/* 初始化系统配置 */
nextTick(async () => {
intdata();
//过滤无法显示的列
tableShowColumn.value = Object.fromEntries(
Object.entries(table.value.column).filter(([_, s]) => s.show)
);
Api = new hTableAPI(table.value.apiUrl);
init.value = true;
tableShow.value = true;
await fetchInitData();
fetchPagedData();
});
});
onMounted(() => {
window.addEventListener("resize", appStyle);
});
onUnmounted(() => {});
// defineExpose({
// initTable
// });
const tableShow = ref(false);
let Api: hTableAPI = null;
const init = ref(false);
const tableHeight = ref(0);
let defaultOrderBy = null;
const dialog = ref<Dialog>({
visible: false,
close: true,
title: "",
width: "500px", // 弹出层宽度
custom: {
height: "85vh",
data: [],
custom: {},
component: null, //自定义弹窗的路径
visible: false, // 弹出层是否显示
},
edit: {
id: -1,
title: "", // 弹出层title
visible: false, // 弹出层是否显示
},
});
const appB = ref<HTMLElement>(null);
const appB_S = ref<HTMLElement>(null);
function appStyle() {
if (tableHeight.value !== 0) return;
tableHeight.value =
appB.value.parentElement.parentElement.parentElement.offsetHeight -
145 -
appB_S.value.offsetHeight +
0;
console.log("tableHeight.value", tableHeight.value);
return tableHeight;
}
function intdata() {
intTableData(table.value);
}
function rowKeyFun(row) {
return row.customId;
}
function execute(obj, scope, btn) {
if (Object.prototype.toString.call(obj) === "[object Function]") return obj(scope, btn);
return eval(obj);
}
function getOperationColumnWidth() {
let hFontSize = getComputedStyle(window.document.documentElement)["font-size"].replace(
"px",
""
);
let defWidth = 10 + 2 + 30;
let width = eval(
table.value.operationColumnData
.filter((s) => !s.topBtn && s.show)
.map((s) => defWidth + s.label.length * (hFontSize || 16))
.join("+")
);
width = width < 100 ? 100 : width;
width = width > 320 ? 320 : width;
return width + "px";
}
function getbtnClick(obj, row) {
row = row ? [row] : table.value.selectRows;
if (obj.btnType === "add") {
return handleAdd();
} else if (obj.btnType === "edit") {
return handleEdit(obj, row);
} else if (obj.btnType === "del") {
return handleDelete(obj, row);
} else if (obj.btnType === "custom") {
return handleCustom(obj, row, obj.custom);
} else {
return obj.click(obj, row, handleReloadPaged);
}
}
function handleAdd() {
dialog.value.edit.id = -1;
dialog.value.title = "添加";
dialog.value.custom.visible = false;
dialog.value.edit.visible = true;
dialog.value.visible = true;
dialog.value.width = "500px";
dialog.value.edit.row = null;
dialog.value.edit.tagData = null;
}
function handleEdit(obj, row) {
dialog.value.edit.id = row[0].id;
dialog.value.edit.row = row[0];
dialog.value.edit.tagData = obj.tagData;
dialog.value.title = obj.label || "修改";
dialog.value.edit.visible = true;
dialog.value.custom.visible = false;
dialog.value.visible = true;
dialog.value.width = "500px";
}
async function handleCustom(obj, row, custom: ButtonCustomConfig) {
dialog.value.custom.data = row || [];
dialog.value.custom.custom = custom || [];
// 异步加载组件
// dialog.value.custom.component = defineAsyncComponent({
// loader: () => import(/* @vite-ignore */ `../../views/${custom.src}.vue`),
// });
let r = shallowRef(null);
const module: any = await modules[`/src/views/${custom.src}.vue`](); //import(`@/views/${custom.src}.vue`);
r.value = module.default;
dialog.value.custom.component = r;
dialog.value.width = custom.width;
dialog.value.title = custom.title;
dialog.value.edit.visible = false;
dialog.value.custom.visible = true;
if (custom.height !== undefined) {
dialog.value.custom.height = custom.height;
} else {
dialog.value.custom.height = "600px";
}
dialog.value.visible = true;
}
// 删除
function handleDelete(obj, row) {
if (row.length === 0) {
ElMessage.warning("未勾选记录");
return;
}
const ids: any[] = [];
row.forEach((it) => {
ids.push(it.id);
});
ElMessageBox.confirm("此操作将永久删除勾选记录, 是否继续?")
.then(() => {
Api.delete(ids).then((res) => {
if (true) {
handleReloadPaged();
ElMessage.success("删除成功");
} else {
ElMessage.error(res.message);
}
});
})
.catch(() => {
ElMessage.info("取消删除");
});
}
// 页面完成回调
function handleAddCallback() {
Api.url = table.value.apiUrl;
dialog.value.visible = false;
dialog.value.edit.visible = false;
dialog.value.custom.visible = false;
handleResetForm();
handleReloadPaged();
}
function handleResetForm() {
for (const key in table.value.column) {
let item = table.value.column[key];
if (Array.isArray(item.edit.valueE)) {
item.edit.valueE = [];
} else if (typeof item.edit.valueE === "number") {
item.edit.valueE = "";
} else if (typeof item.edit.valueE === "boolean") {
} else {
item.edit.valueE = "";
}
}
}
function tableClose() {
handleAddCallback();
}
function paginationChange(currentPage: number, pageSize: number) {
table.value.search.PageIndex = currentPage - 1;
table.value.search.PageSize = pageSize;
fetchPagedData();
}
/**
* 排序变化时
* @param selection
*/
function sortChange(data: { column: TableColumnCtx<any>; prop: string; order: any }) {
if (data.order == undefined) {
table.value.search.OrderBy = defaultOrderBy.OrderBy;
table.value.search.OrderByType = defaultOrderBy.OrderByType;
} else {
table.value.search.OrderBy = data.prop;
table.value.search.OrderByType = data.order === "ascending" ? 1 : 0;
}
handleReloadPaged();
}
// 勾选
function handleSelectionChange(selection) {
console.log("selection-change", selection);
table.value.selectRows = selection;
}
function searchReload() {
for (let name in table.value.column) {
if (
!table.value.column[name].search ||
table.value.column[name].search.value === undefined ||
table.value.column[name].search.value === null ||
table.value.column[name].search.value === ""
) {
continue;
}
table.value.column[name].search.value = "";
}
}
// 查询
async function handleReloadPaged(reload = true) {
if (defaultOrderBy == null) {
defaultOrderBy = {
OrderBy: table.value.search.OrderBy,
OrderByType: table.value.search.OrderByType,
};
}
if (table.value.search === undefined || table.value.search.PageIndex === undefined) {
table.value.search.PageIndex = 0;
table.value.search.PageSize = 20;
}
table.value.search.Conditions = [];
for (let name in table.value.column) {
if (
!table.value.column[name].search ||
table.value.column[name].search.value === undefined ||
table.value.column[name].search.value === null ||
table.value.column[name].search.value === ""
) {
continue;
}
let data: any = { ConditionalType: 0 };
if (table.value.column[name].type === "datetime") {
// data.CSharpTypeName = 'DateTime'
data.ConditionalType = ConditionalType.LikeLeft; // '2023-10-07%'
} else if (table.value.column[name].type === "switch") {
data.CSharpTypeName = "Boolean";
} else if (table.value.column[name].type === "string") {
data.ConditionalType = ConditionalType.Like;
} else data.ConditionalType = ConditionalType.Equal;
data.FieldName = name.charAt(0).toUpperCase() + name.slice(1);
data.FieldValue = table.value.column[name].search.value.toString();
if (table.value.column[name].search.searchType != undefined) {
let v: number = table.value.column[name].search.searchType || 0;
data.ConditionalType = v;
}
table.value.search.Conditions.push(data);
}
if (!reload && table.value.search.Conditions.length > 0) {
table.value.search.PageIndex = 0;
}
//如果有展开行 则全部收回
if (table.value.expandColumn) expands.value = [];
return fetchPagedData();
}
// 加载前置数据(如查询条件的下拉选择数据)
async function fetchInitData() {
setTimeout(() => {
init.value = true;
appStyle();
}, 0);
}
function showTips(item, value) {
if (item.width == undefined) {
return false;
}
return getByteLen(item.custom(value) + "") * 16 < item.width.replace("px", "");
}
function getByteLen(str) {
let len = 0;
for (let i = 0; i < str.length; i++) {
str.charCodeAt(i) < 256 ? (len += 1) : (len += 2);
}
return len;
}
// 加载分页数据
async function fetchPagedData() {
if (table.value.searchCallback) {
if (await table.value.searchCallback(table.value.search, table.value)) return;
}
for (const iterator of table.value.search.defaultConditions) {
if (!iterator) continue;
if (!table.value.search.Conditions.find((s) => s == iterator)) {
table.value.search.Conditions.push(iterator);
}
}
Api.PageList(table.value.search).then((res) => {
if (true) {
table.value.data = res.data.map((s, i) => {
return { ...s, customId: i };
});
table.value.pageData = res;
}
});
}
</script>
<template>
<div v-if="tableShow" ref="appB" class="app-container container-h">
<div ref="appB_S" class="search-container1">
<!-- 搜索项目 -->
<el-form v-if="table.search.show" :inline="true" :model="table.search">
<el-form-item v-for="(o, n, i) in table.column" v-show="o.search.yes" :key="i">
<el-date-picker
v-if="o.type.trim() == 'datetime'"
v-model="o.search.value as Date"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:placeholder="o.label"
style="width: 100%"
/>
<el-select
v-else-if="o.type.trim() == 'dropdown'"
v-model="o.search.value"
:multiple="o.edit.multiple"
clearable
filterable
:placeholder="o.label"
style="width: 100%"
>
<el-option
v-for="item in o.setting.datasource"
:key="item.value"
autocomplete="off"
:label="item[o.setting.maplabel]"
:value="item[o.setting.mapValue]"
/>
</el-select>
<el-select
v-else-if="o.type.trim() == 'switch'"
v-model="o.search.value"
clearable
filterable
:placeholder="o.label"
style="width: 100%"
>
<el-option autocomplete="off" :label="'启用'" :value="true" />
<el-option autocomplete="off" :label="'禁用'" :value="false" />
</el-select>
<div v-else-if="o.type.trim() == 'img'" v-show="false" />
<el-input v-else v-model="o.search.value as string" :placeholder="o.label" />
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleReloadPaged(false)"
>查询</el-button
>
<el-button type="default" @click="searchReload()">重置</el-button>
</el-form-item>
</el-form>
</div>
<div v-if="table.operationTop" class="toolbar-container">
<!-- 头部按钮组 -->
<el-button
v-for="(e, i) in table.operationColumnData.filter(
(s) => s.topBtn && hasPerms(s.perms)
)"
v-show="execute(e['show'], {}, e)"
:key="i"
:type="e.btnStyle || 'info'"
@click="getbtnClick(e, null)"
>{{ e.label }}</el-button
>
</div>
<el-table
v-if="init"
fit
:height="tableHeight"
:data="table.data"
:border="table.border"
:highlight-current-row="true"
:row-key="rowKeyFun"
@selection-change="handleSelectionChange"
@sort-change="sortChange"
@expand-change="table.expandChange"
:expand-row-keys="expands"
>
<el-table-column v-if="table.selectColumn" type="selection" width="40" />
<el-table-column
v-if="
table.operationColumn &&
table.operationColumnData.filter((s) => !s.topBtn).length > 0
"
:fixed="table.operationColumnFixed"
label="操作"
:width="getOperationColumnWidth()"
>
<template v-slot="scope">
<div class="columnTemplate">
<el-button
v-for="(e, i) in table.operationColumnData.filter(
(s) => !s.topBtn && hasPerms(s.perms)
)"
v-show="execute(e['show'], scope, e)"
:key="i"
class="btn"
text
:type="e.btnStyle || 'primary'"
@click="getbtnClick(e, scope.row)"
>
{{ e.label }}</el-button
>
</div>
<!-- 行内按钮组 -->
</template>
</el-table-column>
<!-- 拓展列 -->
<el-table-column v-if="table.expandColumn" type="expand">
<template #default="props">
<slot name="expandSlot" :props="props"></slot>
</template>
</el-table-column>
<el-table-column
v-for="(item, name, i) of tableShowColumn"
:key="i"
:prop="name"
:label="item.label"
:width="item.width"
:fixed="item.fixed"
:sortable="item.search.sort ? `custom` : false"
>
<template v-slot="scope">
<div class="columnTemplate">
<!-- 暂未实现 -->
<div v-if="item.type.trim() == 'math'">
{{ item.custom(scope.row) }}
</div>
<el-image
v-else-if="item.type.trim() == 'img'"
style="width: 300px; height: 100px"
fit="fill"
:src="item.setting.imgUrl(scope.row[name], scope.row)[0]"
:preview-src-list="
Array.from(item.setting.imgUrl(scope.row[name], scope.row))
"
>
<template v-slot:error>
<div class="image-slot">
{{ item.setting.imgUrl(scope.row[name], scope.row) }}
</div>
</template>
</el-image>
<el-tooltip
v-else-if="item.type.trim() != 'img'"
popper-class="maxWidth600px"
:disabled="showTips(item, scope.row)"
:content="item.custom(scope.row) + ''"
placement="top"
effect="light"
>
<div class="tab_tip" :style="{ width: item.width }">
{{ item.custom(scope.row) }}
</div>
</el-tooltip>
</div>
</template>
</el-table-column>
</el-table>
<div
v-if="table.search.showPage"
style="
padding-top: 15px;
display: flex;
align-items: center;
justify-content: center;
"
>
<el-pagination
:page-sizes="[20, 40, 80, 100]"
layout="prev, pager, next,sizes, total"
:total="table.pageData.total"
@change="paginationChange"
/>
</div>
<div class="dialog-container">
<el-dialog
v-if="dialog.visible"
v-model="dialog.visible"
:class="dialog.title ? '' : 'noHeader'"
:title="dialog.title"
:width="dialog.width"
:close-on-click-modal="false"
:close-on-press-escape="dialog.close"
:before-close="tableClose"
class="max-w-[95vw] overflow-x-auto"
append-to-body
>
<hTableEdit
v-if="dialog.edit.visible"
:id="dialog.edit.id"
:tableData="table"
:row="dialog.edit.row"
:tagData="dialog.edit.tagData"
@handlePagedCallback="handleAddCallback"
/>
<component
:is="dialog.custom.component"
v-if="dialog.custom.visible"
:style="{ height: 'calc( ' + dialog.custom.height + ' - 84px )' }"
:iscomponent="true"
:custom="dialog.custom.custom"
:CancelCallback="handleAddCallback"
:data="dialog.custom.data"
@handlePagedCallback="handleAddCallback"
/>
</el-dialog>
</div>
</div>
</template>
<style scoped>
.columnTemplate .btn {
margin-left: 0 !important;
}
.columnTemplate {
display: flex;
flex-direction: row;
flex-flow: row wrap;
gap: 5px;
place-content: flex-start flex-start;
align-items: center;
-webkit-box-orient: horizontal;
-webkit-box-direction: normal;
}
.sty .tab_tip {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.toolbar-container {
padding-bottom: 5px;
}
.maxWidth600px {
max-width: 600px;
}
</style>