初始提交:项目迁移,前端(管理端)
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { nextTick, ref } from "vue";
|
||||
import { useCore } from "../../../hooks";
|
||||
|
||||
export function useData({ config, Table }: { config: ClTable.Config; Table: Vue.Ref<any> }) {
|
||||
const { mitt, crud } = useCore();
|
||||
|
||||
// 列表数据
|
||||
const data = ref<obj[]>([]);
|
||||
|
||||
// 设置数据
|
||||
function setData(list: obj[]) {
|
||||
data.value = list;
|
||||
}
|
||||
|
||||
// 监听刷新
|
||||
mitt.on("crud.refresh", ({ list }: ClCrud.Response["page"]) => {
|
||||
data.value = list;
|
||||
|
||||
// 显示选中行
|
||||
nextTick(() => {
|
||||
crud.selection.forEach((e) => {
|
||||
const d = list.find((a) => a[config.rowKey] == e[config.rowKey]);
|
||||
|
||||
if (d) {
|
||||
Table.value.toggleRowSelection(d, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
setData
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { CloseBold, Search } from "@element-plus/icons-vue";
|
||||
import { h } from "vue";
|
||||
import { useCrud } from "../../../hooks";
|
||||
import { renderNode } from "../../../utils/vnode";
|
||||
|
||||
export function renderHeader(item: ClTable.Column, { scope, slots }: any) {
|
||||
const crud = useCrud();
|
||||
|
||||
const slot = slots[`header-${item.prop}`];
|
||||
|
||||
if (slot) {
|
||||
return slot({
|
||||
scope
|
||||
});
|
||||
}
|
||||
|
||||
if (!item.search || !item.search.component) {
|
||||
return item.label;
|
||||
}
|
||||
|
||||
// 显示输入框
|
||||
function show(e: MouseEvent) {
|
||||
item.search.isInput = true;
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// 隐藏输入框
|
||||
function hide() {
|
||||
if (item.search.value !== undefined) {
|
||||
item.search.value = undefined;
|
||||
refresh();
|
||||
}
|
||||
|
||||
item.search.isInput = false;
|
||||
}
|
||||
|
||||
// 刷新
|
||||
function refresh(params?: any) {
|
||||
const { value } = item.search;
|
||||
|
||||
crud.value?.refresh({
|
||||
page: 1,
|
||||
[item.prop]: value === "" ? undefined : value,
|
||||
...params
|
||||
});
|
||||
}
|
||||
|
||||
// 文字
|
||||
const text = (
|
||||
<div class="cl-table-header__search-label" onClick={show}>
|
||||
<el-icon size={14}>{item.search.icon?.() ?? <Search />}</el-icon>
|
||||
|
||||
{item.renderLabel ? item.renderLabel(scope) : item.label}
|
||||
</div>
|
||||
);
|
||||
|
||||
// 输入框
|
||||
const input = h(renderNode(item.search.component, { prop: item.prop }), {
|
||||
clearable: true,
|
||||
modelValue: item.search.value,
|
||||
onVnodeMounted(vn) {
|
||||
// 默认聚焦
|
||||
vn.component?.exposed?.focus?.();
|
||||
},
|
||||
onInput(val: any) {
|
||||
item.search.value = val;
|
||||
},
|
||||
onChange(val: any) {
|
||||
item.search.value = val;
|
||||
|
||||
// 更改时刷新列表
|
||||
if (item.search.refreshOnChange) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div class={["cl-table-header__search", { "is-input": item.search.isInput }]}>
|
||||
<div class="cl-table-header__search-inner">{item.search.isInput ? input : text}</div>
|
||||
|
||||
{item.search.isInput && (
|
||||
<div class="cl-table-header__search-close" onClick={hide}>
|
||||
<el-icon>
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { debounce, last } from "lodash-es";
|
||||
import { nextTick, onActivated, onMounted, ref } from "vue";
|
||||
import { addClass, removeClass } from "../../../utils";
|
||||
import { mitt } from "../../../utils/mitt";
|
||||
|
||||
// 表格高度
|
||||
export function useHeight({ config, Table }: { Table: Vue.Ref<any>; config: ClTable.Config }) {
|
||||
// 最大高度
|
||||
const maxHeight = ref(0);
|
||||
|
||||
// 计算表格最大高度
|
||||
const update = debounce(async () => {
|
||||
await nextTick();
|
||||
|
||||
let vm = Table.value;
|
||||
|
||||
if (vm) {
|
||||
while (!vm.$parent?.$el.className?.includes("cl-crud")) {
|
||||
vm = vm.$parent;
|
||||
}
|
||||
|
||||
if (vm) {
|
||||
const p = vm.$parent.$el;
|
||||
|
||||
await nextTick();
|
||||
|
||||
// 高度
|
||||
let h = 0;
|
||||
|
||||
// 表格下间距
|
||||
if (vm.$el.className.includes("cl-row")) {
|
||||
h += 10;
|
||||
}
|
||||
|
||||
// 上高度
|
||||
h += vm.$el.offsetTop;
|
||||
|
||||
// 获取下高度
|
||||
let n = vm.$el.nextSibling;
|
||||
|
||||
// 集合
|
||||
const arr = [vm.$el];
|
||||
|
||||
while (n) {
|
||||
if (n.offsetHeight > 0) {
|
||||
h += n.offsetHeight || 0;
|
||||
arr.push(n);
|
||||
|
||||
if (n.className.includes("cl-row--last")) {
|
||||
h += 10;
|
||||
}
|
||||
}
|
||||
|
||||
n = n.nextSibling;
|
||||
}
|
||||
|
||||
// 移除 cl-row--last
|
||||
arr.forEach((e) => {
|
||||
removeClass(e, "cl-row--last");
|
||||
});
|
||||
|
||||
// 最后一个可视元素
|
||||
const z = last(arr);
|
||||
|
||||
// 去掉 cl-row 下间距高度
|
||||
if (z?.className.includes("cl-row")) {
|
||||
addClass(z, "cl-row--last");
|
||||
h -= 10;
|
||||
}
|
||||
|
||||
// 上间距
|
||||
h += parseInt(window.getComputedStyle(p).paddingTop, 10);
|
||||
|
||||
// 设置最大高度
|
||||
if (config.autoHeight) {
|
||||
maxHeight.value = p.clientHeight - h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 窗口大小改变事件
|
||||
mitt.on("resize", () => {
|
||||
update();
|
||||
});
|
||||
|
||||
onMounted(function () {
|
||||
update();
|
||||
});
|
||||
|
||||
onActivated(function () {
|
||||
update();
|
||||
});
|
||||
|
||||
return {
|
||||
maxHeight,
|
||||
calcMaxHeight: update
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { inject, reactive, ref } from "vue";
|
||||
import { useConfig } from "../../../hooks";
|
||||
import { getValue, mergeConfig } from "../../../utils";
|
||||
import type { TableInstance } from "element-plus";
|
||||
|
||||
export function useTable(props: any) {
|
||||
const { style } = useConfig();
|
||||
|
||||
const Table = ref<TableInstance>();
|
||||
|
||||
// 配置
|
||||
const config = reactive<ClTable.Config>(mergeConfig(props, inject("useTable__options") || {}));
|
||||
|
||||
// 列表项动态处理
|
||||
config.columns = (config.columns || []).map((e) => getValue(e));
|
||||
|
||||
// 自动高度
|
||||
config.autoHeight = config.autoHeight ?? style.table.autoHeight;
|
||||
|
||||
// 右键菜单
|
||||
config.contextMenu = config.contextMenu ?? style.table.contextMenu;
|
||||
|
||||
// 事件
|
||||
if (!config.on) {
|
||||
config.on = {};
|
||||
}
|
||||
|
||||
// 参数
|
||||
if (!config.props) {
|
||||
config.props = {};
|
||||
}
|
||||
|
||||
return { Table, config };
|
||||
}
|
||||
|
||||
export * from "./data";
|
||||
export * from "./height";
|
||||
export * from "./op";
|
||||
export * from "./render";
|
||||
export * from "./row";
|
||||
export * from "./selection";
|
||||
export * from "./sort";
|
||||
export * from "./header";
|
||||
@@ -0,0 +1,69 @@
|
||||
import { nextTick, ref } from "vue";
|
||||
import { useCore } from "../../../hooks";
|
||||
import { isArray, isBoolean } from "lodash-es";
|
||||
|
||||
export function useOp({ config }: { config: ClTable.Config }) {
|
||||
const { mitt } = useCore();
|
||||
|
||||
// 是否可见,用于解决一些显示隐藏的副作用
|
||||
const visible = ref(true);
|
||||
|
||||
// 重新构建
|
||||
async function reBuild(cb?: fn) {
|
||||
visible.value = false;
|
||||
|
||||
await nextTick();
|
||||
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
|
||||
visible.value = true;
|
||||
|
||||
await nextTick();
|
||||
|
||||
mitt.emit("resize");
|
||||
}
|
||||
|
||||
// 显示列
|
||||
function showColumn(prop: string | string[], status?: boolean) {
|
||||
const keys = isArray(prop) ? prop : [prop];
|
||||
|
||||
// 多级表头
|
||||
function deep(list: ClTable.Column[]) {
|
||||
list.forEach((e) => {
|
||||
if (e.prop && keys.includes(e.prop)) {
|
||||
e.hidden = isBoolean(status) ? !status : false;
|
||||
}
|
||||
|
||||
if (e.children) {
|
||||
deep(e.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deep(config.columns);
|
||||
}
|
||||
|
||||
// 隐藏列
|
||||
function hideColumn(prop: string | string[]) {
|
||||
showColumn(prop, false);
|
||||
}
|
||||
|
||||
// 设置列
|
||||
function setColumns(list: ClTable.Column[]) {
|
||||
if (list) {
|
||||
reBuild(() => {
|
||||
config.columns.splice(0, config.columns.length, ...list);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
visible,
|
||||
reBuild,
|
||||
showColumn,
|
||||
hideColumn,
|
||||
setColumns
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { getCurrentInstance } from "vue";
|
||||
import { useConfig } from "../../../hooks";
|
||||
import { uniqueFns } from "../../../utils";
|
||||
|
||||
export function usePlugins() {
|
||||
const that: any = getCurrentInstance();
|
||||
const { style } = useConfig();
|
||||
|
||||
// 插件创建
|
||||
function create(plugins: ClTable.Plugin[] = []) {
|
||||
// 执行
|
||||
uniqueFns([...(style.table.plugins || []), ...plugins]).forEach((p) => {
|
||||
p({
|
||||
exposed: that.exposed
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
create
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
import { h, useSlots } from "vue";
|
||||
import { useCore, useBrowser, useConfig } from "../../../hooks";
|
||||
import { assign, cloneDeep, isArray, isEmpty, isObject, isString, orderBy } from "lodash-es";
|
||||
import { deepFind, getValue } from "../../../utils";
|
||||
import { renderNode } from "../../../utils/vnode";
|
||||
import { renderHeader } from "./header";
|
||||
|
||||
// 渲染
|
||||
export function useRender() {
|
||||
const browser = useBrowser();
|
||||
const slots = useSlots();
|
||||
const { crud } = useCore();
|
||||
const { style } = useConfig();
|
||||
|
||||
// 渲染列
|
||||
function renderColumn(columns: ClTable.Column[]) {
|
||||
const arr = columns.map((e) => {
|
||||
const d = getValue(e);
|
||||
|
||||
if (!d.orderNum) {
|
||||
d.orderNum = 0;
|
||||
}
|
||||
|
||||
return d;
|
||||
});
|
||||
|
||||
return orderBy(arr, "orderNum", "asc")
|
||||
.map((item, index) => {
|
||||
if (item.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ElTableColumn = (
|
||||
<el-table-column
|
||||
key={`cl-table-column__${index}`}
|
||||
align={style.table.column.align}
|
||||
header-align={style.table.column.headerAlign}
|
||||
minWidth={style.table.column.minWidth}
|
||||
/>
|
||||
);
|
||||
|
||||
// 操作按钮
|
||||
if (item.type === "op") {
|
||||
const props = assign(
|
||||
{
|
||||
label: crud.dict.label.op,
|
||||
width: style.table.column.opWidth,
|
||||
fixed: browser.isMini ? null : "right"
|
||||
},
|
||||
item
|
||||
);
|
||||
|
||||
return h(ElTableColumn, props, {
|
||||
default: (scope: any) => {
|
||||
return (
|
||||
<div class="cl-table__op">
|
||||
{renderOpButtons(item.buttons, { scope })}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 多选,序号
|
||||
else if (["selection", "index"].includes(item.type)) {
|
||||
return h(ElTableColumn, item);
|
||||
}
|
||||
// 默认
|
||||
else {
|
||||
function deep(item: ClTable.Column) {
|
||||
if (item.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const props: obj = cloneDeep(item);
|
||||
|
||||
// Cannot set property children of #<Element>
|
||||
delete props.children;
|
||||
|
||||
return h(ElTableColumn, props, {
|
||||
header(scope: any) {
|
||||
return renderHeader(item, { scope, slots });
|
||||
},
|
||||
default(scope: any) {
|
||||
if (item.children) {
|
||||
return item.children.map(deep);
|
||||
}
|
||||
|
||||
// 使用插槽
|
||||
const slot = slots[`column-${item.prop}`];
|
||||
|
||||
if (slot) {
|
||||
return slot({
|
||||
scope,
|
||||
item
|
||||
});
|
||||
} else {
|
||||
// 绑定值
|
||||
let value = scope.row[item.prop];
|
||||
|
||||
// 格式化
|
||||
if (item.formatter) {
|
||||
value = item.formatter(
|
||||
scope.row,
|
||||
scope.column,
|
||||
value,
|
||||
scope.$index
|
||||
);
|
||||
|
||||
if (isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义渲染
|
||||
if (item.render) {
|
||||
return item.render(
|
||||
scope.row,
|
||||
scope.column,
|
||||
value,
|
||||
scope.$index
|
||||
);
|
||||
}
|
||||
// 自定义渲染2
|
||||
else if (item.component) {
|
||||
return renderNode(item.component, {
|
||||
prop: item.prop,
|
||||
scope: scope.row,
|
||||
_data: {
|
||||
column: scope.column,
|
||||
index: scope.$index,
|
||||
row: scope.row
|
||||
}
|
||||
});
|
||||
}
|
||||
// 字典状态
|
||||
else if (item.dict) {
|
||||
return renderDict(value, item);
|
||||
}
|
||||
// 空数据
|
||||
else if (isEmpty(value)) {
|
||||
return scope.emptyText;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return deep(item);
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
// 渲染操作按钮
|
||||
function renderOpButtons(buttons: any, { scope }: any) {
|
||||
const list = getValue(buttons || ["edit", "delete"], { scope }) as ClTable.OpButton;
|
||||
|
||||
return list.map((vnode) => {
|
||||
if (vnode === "info") {
|
||||
return (
|
||||
<el-button
|
||||
plain
|
||||
size={style.size}
|
||||
v-show={crud.getPermission("info")}
|
||||
onClick={(e: MouseEvent) => {
|
||||
crud.rowInfo(scope.row);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{crud.dict.label?.info}
|
||||
</el-button>
|
||||
);
|
||||
} else if (vnode === "edit") {
|
||||
return (
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
size={style.size}
|
||||
v-show={crud.getPermission("update")}
|
||||
onClick={(e: MouseEvent) => {
|
||||
crud.rowEdit(scope.row);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{crud.dict.label?.update}
|
||||
</el-button>
|
||||
);
|
||||
} else if (vnode === "delete") {
|
||||
return (
|
||||
<el-button
|
||||
text
|
||||
type="danger"
|
||||
size={style.size}
|
||||
v-show={crud.getPermission("delete")}
|
||||
onClick={(e: MouseEvent) => {
|
||||
crud.rowDelete(scope.row);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{crud.dict.label?.delete}
|
||||
</el-button>
|
||||
);
|
||||
} else {
|
||||
if (typeof vnode === "object") {
|
||||
if (vnode.hidden) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return renderNode(vnode, {
|
||||
scope,
|
||||
slots,
|
||||
custom(vnode) {
|
||||
return (
|
||||
<el-button
|
||||
text
|
||||
type={vnode.type}
|
||||
{...vnode?.props}
|
||||
onClick={(e: MouseEvent) => {
|
||||
vnode.onClick({ scope });
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{vnode.label}
|
||||
</el-button>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染字典
|
||||
function renderDict(value: any, item: ClTable.Column) {
|
||||
// 选项列表
|
||||
const list = cloneDeep(item.dict || []) as DictOptions;
|
||||
|
||||
// 字符串分隔符
|
||||
const separator = item.dictSeparator === undefined ? "," : item.dictSeparator;
|
||||
|
||||
// 设置颜色
|
||||
if (item.dictColor) {
|
||||
list.forEach((e, i) => {
|
||||
if (!e.color) {
|
||||
e.color = style.colors[i];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定值
|
||||
let values: any[] = [];
|
||||
|
||||
// 格式化值
|
||||
if (isArray(value)) {
|
||||
values = value;
|
||||
} else if (isString(value)) {
|
||||
if (separator) {
|
||||
values = value.split(separator);
|
||||
} else {
|
||||
values = [value];
|
||||
}
|
||||
} else {
|
||||
values = [value];
|
||||
}
|
||||
|
||||
// 返回值
|
||||
const result = values
|
||||
.filter((e) => e !== undefined && e !== null && e !== "")
|
||||
.map((v) => {
|
||||
const d = deepFind(v, list, { allLevels: item.dictAllLevels }) || {
|
||||
label: v,
|
||||
value: v
|
||||
};
|
||||
|
||||
return {
|
||||
...d,
|
||||
children: []
|
||||
};
|
||||
});
|
||||
|
||||
// 格式化返回
|
||||
if (item.dictFormatter) {
|
||||
return item.dictFormatter(result);
|
||||
} else {
|
||||
// tag 返回
|
||||
return result.map((e) => {
|
||||
return h(
|
||||
<el-tag disable-transitions style="margin: 2px; border: 0" />,
|
||||
{
|
||||
type: e.type,
|
||||
closable: e.closable,
|
||||
hit: e.hit,
|
||||
color: e.color,
|
||||
size: e.size,
|
||||
effect: e.effect || "dark",
|
||||
round: e.round
|
||||
},
|
||||
{
|
||||
default: () => <span>{e.label}</span>
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 插槽 empty
|
||||
function renderEmpty(emptyText: string) {
|
||||
return (
|
||||
<div class="cl-table__empty">
|
||||
{slots.empty ? (
|
||||
slots.empty()
|
||||
) : (
|
||||
<el-empty image-size={100} description={emptyText}></el-empty>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 插槽 append
|
||||
function renderAppend() {
|
||||
return <div class="cl-table__append">{slots.append && slots.append()}</div>;
|
||||
}
|
||||
|
||||
return {
|
||||
renderColumn,
|
||||
renderEmpty,
|
||||
renderAppend
|
||||
};
|
||||
}
|
||||
130
cool-admin-vue/packages/crud/src/components/table/helper/row.ts
Normal file
130
cool-admin-vue/packages/crud/src/components/table/helper/row.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { isEmpty, isFunction } from "lodash-es";
|
||||
import { useCore } from "../../../hooks";
|
||||
import { ContextMenu } from "../../context-menu";
|
||||
|
||||
// 单元行事件
|
||||
export function useRow({
|
||||
Table,
|
||||
config,
|
||||
Sort
|
||||
}: {
|
||||
Table: Vue.Ref<any>;
|
||||
config: ClTable.Config;
|
||||
Sort: {
|
||||
defaultSort: {
|
||||
prop?: string;
|
||||
order?: string;
|
||||
};
|
||||
changeSort(prop: string, order: string): void;
|
||||
};
|
||||
}) {
|
||||
const { crud } = useCore();
|
||||
|
||||
// 右键菜单
|
||||
function onRowContextMenu(row: obj, column: obj, event: PointerEvent) {
|
||||
// 菜单按钮
|
||||
const buttons = config.contextMenu;
|
||||
// 是否开启
|
||||
const enable = !isEmpty(buttons);
|
||||
|
||||
if (enable) {
|
||||
// 高亮
|
||||
Table.value.setCurrentRow(row);
|
||||
|
||||
// 解析按钮
|
||||
const list = buttons
|
||||
.map((e) => {
|
||||
switch (e) {
|
||||
case "refresh":
|
||||
return {
|
||||
label: crud.dict.label.refresh,
|
||||
callback(done: fn) {
|
||||
crud.refresh();
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "edit":
|
||||
case "update":
|
||||
return {
|
||||
label: crud.dict.label.update,
|
||||
hidden: !crud.getPermission("update"),
|
||||
callback(done: fn) {
|
||||
crud.rowEdit(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "delete":
|
||||
return {
|
||||
label: crud.dict.label.delete,
|
||||
hidden: !crud.getPermission("delete"),
|
||||
callback(done: fn) {
|
||||
crud.rowDelete(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "info":
|
||||
return {
|
||||
label: crud.dict.label.info,
|
||||
hidden: !crud.getPermission("info"),
|
||||
callback(done: fn) {
|
||||
crud.rowInfo(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "check":
|
||||
return {
|
||||
label: crud.selection.find((e) => e.id == row.id)
|
||||
? crud.dict.label.deselect
|
||||
: crud.dict.label.select,
|
||||
hidden: !config.columns.find((e) => e.type === "selection"),
|
||||
callback(done: fn) {
|
||||
Table.value.toggleRowSelection(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "order-desc":
|
||||
return {
|
||||
label: `${column.label} - ${crud.dict.label.desc}`,
|
||||
hidden: !column.sortable,
|
||||
callback(done: fn) {
|
||||
Sort.changeSort(column.property, "desc");
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "order-asc":
|
||||
return {
|
||||
label: `${column.label} - ${crud.dict.label.asc}`,
|
||||
hidden: !column.sortable,
|
||||
callback(done: fn) {
|
||||
Sort.changeSort(column.property, "asc");
|
||||
done();
|
||||
}
|
||||
};
|
||||
default:
|
||||
if (isFunction(e)) {
|
||||
return e(row, column, event);
|
||||
} else {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter((e) => Boolean(e) && !e.hidden);
|
||||
|
||||
// 打开菜单
|
||||
if (!isEmpty(list)) {
|
||||
ContextMenu.open(event, {
|
||||
list
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 回调
|
||||
if (config.onRowContextmenu) {
|
||||
config.onRowContextmenu(row, column, event);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
onRowContextMenu
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useCore } from "../../../hooks";
|
||||
|
||||
export function useSelection({ emit }: { emit: Vue.Emit }) {
|
||||
const { crud } = useCore();
|
||||
|
||||
// 选择项发生变化
|
||||
function onSelectionChange(selection: any[]) {
|
||||
crud.selection.splice(0, crud.selection.length, ...selection);
|
||||
emit("selection-change", crud.selection);
|
||||
}
|
||||
|
||||
return {
|
||||
selection: crud.selection,
|
||||
onSelectionChange
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { useCore } from "../../../hooks";
|
||||
|
||||
// 排序
|
||||
export function useSort({
|
||||
config,
|
||||
Table,
|
||||
emit
|
||||
}: {
|
||||
config: ClTable.Config;
|
||||
Table: Vue.Ref<any>;
|
||||
emit: Vue.Emit;
|
||||
}) {
|
||||
const { crud } = useCore();
|
||||
|
||||
// 设置默认排序Ï
|
||||
const defaultSort = (function () {
|
||||
let { prop, order } = config.defaultSort || {};
|
||||
|
||||
const item = config.columns.find((e) =>
|
||||
["desc", "asc", "descending", "ascending"].find((a) => a == e.sortable)
|
||||
);
|
||||
|
||||
if (item) {
|
||||
prop = item.prop;
|
||||
order = ["descending", "desc"].find((a) => a == item.sortable)
|
||||
? "descending"
|
||||
: "ascending";
|
||||
}
|
||||
|
||||
if (order && prop) {
|
||||
crud.params.order = ["descending", "desc"].includes(order) ? "desc" : "asc";
|
||||
crud.params.prop = prop;
|
||||
|
||||
return {
|
||||
prop,
|
||||
order
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
})();
|
||||
|
||||
// 排序监听
|
||||
function onSortChange({ prop, order }: { prop: string | undefined; order: string }) {
|
||||
if (config.sortRefresh) {
|
||||
if (order === "descending") {
|
||||
order = "desc";
|
||||
}
|
||||
|
||||
if (order === "ascending") {
|
||||
order = "asc";
|
||||
}
|
||||
|
||||
if (!order) {
|
||||
prop = undefined;
|
||||
}
|
||||
|
||||
crud.refresh({
|
||||
prop,
|
||||
order,
|
||||
page: 1
|
||||
});
|
||||
}
|
||||
|
||||
emit("sort-change", { prop, order });
|
||||
}
|
||||
|
||||
// 改变排序
|
||||
function changeSort(prop: string, order: string) {
|
||||
if (order === "desc") {
|
||||
order = "descending";
|
||||
}
|
||||
|
||||
if (order === "asc") {
|
||||
order = "ascending";
|
||||
}
|
||||
|
||||
Table.value?.sort(prop, order);
|
||||
}
|
||||
|
||||
return {
|
||||
defaultSort,
|
||||
onSortChange,
|
||||
changeSort
|
||||
};
|
||||
}
|
||||
165
cool-admin-vue/packages/crud/src/components/table/index.tsx
Normal file
165
cool-admin-vue/packages/crud/src/components/table/index.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import { defineComponent, h } from "vue";
|
||||
import {
|
||||
useRow,
|
||||
useHeight,
|
||||
useRender,
|
||||
useSort,
|
||||
useData,
|
||||
useSelection,
|
||||
useOp,
|
||||
useTable
|
||||
} from "./helper";
|
||||
import { useCore, useProxy, useElApi, useConfig } from "../../hooks";
|
||||
import { usePlugins } from "./helper/plugins";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-table",
|
||||
|
||||
props: {
|
||||
// 列配置
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 是否自动计算高度
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: null
|
||||
},
|
||||
// 固定高度
|
||||
height: null,
|
||||
// 右键菜单
|
||||
contextMenu: {
|
||||
type: [Array, Boolean],
|
||||
default: null
|
||||
},
|
||||
// 默认排序
|
||||
defaultSort: Object,
|
||||
// 排序后是否刷新
|
||||
sortRefresh: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 空数据显示文案
|
||||
emptyText: String,
|
||||
// 当前行的 key
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: "id"
|
||||
}
|
||||
},
|
||||
|
||||
emits: ["selection-change", "sort-change"],
|
||||
|
||||
setup(props, { emit, expose }) {
|
||||
const { crud } = useCore();
|
||||
const { style } = useConfig();
|
||||
const { Table, config } = useTable(props);
|
||||
const plugin = usePlugins();
|
||||
|
||||
// 排序
|
||||
const Sort = useSort({ config, emit, Table });
|
||||
|
||||
// 行
|
||||
const Row = useRow({
|
||||
config,
|
||||
Table,
|
||||
Sort
|
||||
});
|
||||
|
||||
// 高度
|
||||
const Height = useHeight({ config, Table });
|
||||
|
||||
// 数据
|
||||
const Data = useData({ config, Table });
|
||||
|
||||
// 多选
|
||||
const Selection = useSelection({ emit });
|
||||
|
||||
// 操作
|
||||
const Op = useOp({ config });
|
||||
|
||||
// 方法
|
||||
const ElTableApi = useElApi(
|
||||
[
|
||||
"clearSelection",
|
||||
"getSelectionRows",
|
||||
"toggleRowSelection",
|
||||
"toggleAllSelection",
|
||||
"toggleRowExpansion",
|
||||
"setCurrentRow",
|
||||
"clearSort",
|
||||
"clearFilter",
|
||||
"doLayout",
|
||||
"sort",
|
||||
"scrollTo",
|
||||
"setScrollTop",
|
||||
"setScrollLeft",
|
||||
"updateKeyChildren"
|
||||
],
|
||||
Table
|
||||
);
|
||||
|
||||
const ctx = {
|
||||
Table,
|
||||
config,
|
||||
columns: config.columns,
|
||||
...Selection,
|
||||
...Data,
|
||||
...Sort,
|
||||
...Row,
|
||||
...Height,
|
||||
...Op,
|
||||
...ElTableApi
|
||||
};
|
||||
|
||||
useProxy(ctx);
|
||||
expose(ctx);
|
||||
plugin.create(config.plugins);
|
||||
|
||||
return () => {
|
||||
const { renderColumn, renderAppend, renderEmpty } = useRender();
|
||||
|
||||
return (
|
||||
ctx.visible.value &&
|
||||
h(
|
||||
<el-table class="cl-table" ref={Table} v-loading={crud.loading} />,
|
||||
{
|
||||
...config.on,
|
||||
...config.props,
|
||||
|
||||
// config
|
||||
maxHeight: config.autoHeight ? ctx.maxHeight.value : null,
|
||||
height: config.autoHeight ? config.height : null,
|
||||
rowKey: config.rowKey,
|
||||
|
||||
// ctx
|
||||
defaultSort: ctx.defaultSort,
|
||||
data: ctx.data.value,
|
||||
onRowContextmenu: ctx.onRowContextMenu,
|
||||
onSelectionChange: ctx.onSelectionChange,
|
||||
onSortChange: ctx.onSortChange,
|
||||
|
||||
// style
|
||||
size: style.size,
|
||||
border: style.table.border,
|
||||
highlightCurrentRow: style.table.highlightCurrentRow,
|
||||
resizable: style.table.resizable,
|
||||
stripe: style.table.stripe,
|
||||
},
|
||||
{
|
||||
default() {
|
||||
return renderColumn(ctx.columns);
|
||||
},
|
||||
empty() {
|
||||
return renderEmpty(config.emptyText || crud.dict.label.empty);
|
||||
},
|
||||
append() {
|
||||
return renderAppend();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user