649 lines
19 KiB
Vue
649 lines
19 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 } from "element-plus";
|
|
import { defineAsyncComponent, AsyncComponentLoader } from "vue";
|
|
import {
|
|
ButtonCustomConfig,
|
|
ConditionalType,
|
|
Dialog,
|
|
TableColumn,
|
|
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 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(() => {
|
|
nextTick(async () => {
|
|
// appStyle();
|
|
});
|
|
});
|
|
onUnmounted(() => {});
|
|
// defineExpose({
|
|
// initTable
|
|
// });
|
|
const tableShow = ref(false);
|
|
let Api: hTableAPI = null;
|
|
|
|
const init = ref(false);
|
|
const tableHeight = ref(0);
|
|
|
|
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.offsetHeight -
|
|
145 -
|
|
appB_S.value.offsetHeight +
|
|
0;
|
|
return tableHeight;
|
|
}
|
|
|
|
function intdata() {
|
|
if (!table.value.data) table.value.data = [];
|
|
if (!table.value.selectRows) table.value.selectRows = [];
|
|
if (table.value.border == null) table.value.border = true;
|
|
if (!table.value.pageData) table.value.pageData = { total: 0 };
|
|
if (table.value.operationTop === undefined) table.value.operationTop = true;
|
|
|
|
// 处理 column 的属性
|
|
for (const key in table.value.column) {
|
|
const element = table.value.column[key];
|
|
if (element.add === undefined) element.add = false;
|
|
if (element.edit === undefined) element.edit = false;
|
|
// Vue 3 中直接赋值即可,不需要 $set
|
|
if (element.valueE === undefined) element.valueE = element.multiple ? [] : "";
|
|
if (element.value === undefined) element.value = element.multiple ? [] : "";
|
|
if (!element.type) element.type = "string";
|
|
if (element.show === undefined) element.show = true;
|
|
if (element.editShow === undefined) element.editShow = true;
|
|
if (!element.setting)
|
|
element.setting = { datasource: [], mapValue: "value", maplabel: "text" };
|
|
if (!element.setting.mapValue) element.setting.mapValue = "value";
|
|
if (!element.setting.maplabel) element.setting.maplabel = "text";
|
|
|
|
if (!element.change) element.change = () => {};
|
|
}
|
|
|
|
// 处理 operationColumnData 的属性
|
|
for (const key in table.value.operationColumnData) {
|
|
const element = table.value.operationColumnData[key];
|
|
if (element.show === undefined) element.show = true;
|
|
}
|
|
}
|
|
|
|
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 (res.code === 200) {
|
|
handleReloadPaged();
|
|
ElMessage.success("删除成功");
|
|
} else {
|
|
ElMessage.error(res.message);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
// 页面完成回调
|
|
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.valueE)) {
|
|
item.valueE = [];
|
|
} else if (typeof item.valueE === "number") {
|
|
item.valueE = "";
|
|
} else if (typeof item.valueE === "boolean") {
|
|
} else {
|
|
item.valueE = "";
|
|
}
|
|
}
|
|
}
|
|
function tableClose() {
|
|
handleAddCallback();
|
|
}
|
|
function pageSizeChange(o) {
|
|
table.value.search.PageSize = o;
|
|
fetchPagedData();
|
|
}
|
|
function pageIndexChange(o) {
|
|
table.value.search.PageIndex = o - 1;
|
|
fetchPagedData();
|
|
}
|
|
// 勾选
|
|
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].value === undefined ||
|
|
table.value.column[name].value === null ||
|
|
table.value.column[name].value === ""
|
|
) {
|
|
continue;
|
|
}
|
|
table.value.column[name].value = "";
|
|
}
|
|
}
|
|
// 查询
|
|
function handleReloadPaged(reload = true) {
|
|
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].value === undefined ||
|
|
table.value.column[name].value === null ||
|
|
table.value.column[name].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].value.toString();
|
|
|
|
if (table.value.column[name].searchType != undefined) {
|
|
let v: number = table.value.column[name].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.searchCallback) {
|
|
table.value.searchCallback(table.value.search);
|
|
}
|
|
fetchPagedData();
|
|
}
|
|
// 加载前置数据(如查询条件的下拉选择数据)
|
|
async function fetchInitData() {
|
|
for (const key in table.value.column) {
|
|
const element = table.value.column[key];
|
|
if (element.type === "dropdown") {
|
|
if (!element.setting.datasource) {
|
|
// 不安全取消 存在值就不处理
|
|
// let rdata = await eval(element.setting.datasourceStr);
|
|
// element.setting.datasource = rdata.data;
|
|
// console.log(key + " " + element.setting.datasourceStr, rdata);
|
|
}
|
|
}
|
|
if (
|
|
element.custom == undefined &&
|
|
(element.type === "switch" ||
|
|
element.type === "dropdown" ||
|
|
element.type === "string" ||
|
|
element.type === undefined)
|
|
) {
|
|
if (element.type === "string" || element.type === undefined)
|
|
element.custom = (row) => row[key];
|
|
else {
|
|
element.custom = (row) => {
|
|
let sc = element.setting.datasource.find(
|
|
(s) => s[element.setting.mapValue] + "" == row[key] + ""
|
|
);
|
|
return !sc ? row[key] : sc[element.setting.maplabel];
|
|
};
|
|
}
|
|
} else if (element.custom == undefined) {
|
|
element.custom = (row) => row[key];
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
// 加载分页数据
|
|
function fetchPagedData() {
|
|
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 (res.code === 200) {
|
|
table.value.data = res.data.data.map((s, i) => {
|
|
return { ...s, customId: i };
|
|
});
|
|
table.value.pageData = res.data;
|
|
}
|
|
});
|
|
}
|
|
</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" :key="i">
|
|
<el-date-picker
|
|
v-if="o.type.trim() == 'datetime'"
|
|
v-model="o.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.value"
|
|
:multiple="o.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.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.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 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"
|
|
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>
|
|
|
|
<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"
|
|
style="width: 100%"
|
|
:row-key="rowKeyFun"
|
|
@selection-change="handleSelectionChange"
|
|
>
|
|
<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
|
|
"
|
|
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-for="(item, name, i) of tableShowColumn"
|
|
:key="i"
|
|
:prop="name"
|
|
:label="item.label"
|
|
:width="item.width"
|
|
>
|
|
<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
|
|
style="
|
|
padding-top: 15px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
"
|
|
>
|
|
<el-pagination
|
|
:current-page="table.search.PageIndex + 1"
|
|
:page-sizes="[20, 40, 80, 100]"
|
|
:page-size="table.search.PageSize"
|
|
layout="prev, pager, next,sizes, total"
|
|
:total="table.pageData.total"
|
|
@size-change="pageSizeChange"
|
|
@current-change="pageIndexChange"
|
|
/>
|
|
</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>
|