添加 export 模块,添加 crud 依赖

This commit is contained in:
icssoa 2021-04-20 14:49:07 +08:00
parent 820b9d4749
commit 450c232c0a
70 changed files with 66 additions and 5111 deletions

View File

@ -34,7 +34,7 @@
"",
"<script lang=\"ts\">",
"import { defineComponent, inject, reactive } from \"vue\";",
"import { CrudLoad, Upsert, Table } from \"/$/crud/types\";",
"import { CrudLoad, Upsert, Table } from \"cl-admin-crud-vue3/types\";",
"import { useRefs } from \"/@/core\";",
"",
"export default defineComponent({",

View File

@ -1,6 +1,6 @@
{
"name": "front-next",
"version": "0.2.7",
"version": "0.3.0",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit --skipLibCheck && vite build",
@ -11,6 +11,7 @@
"dependencies": {
"array.prototype.flat": "^1.2.4",
"axios": "^0.21.1",
"cl-admin-crud-vue3": "^0.1.3",
"clipboard": "^2.0.8",
"clone-deep": "^4.0.1",
"codemirror": "^5.60.0",

View File

@ -1,3 +1,6 @@
import Crud from "cl-admin-crud-vue3";
import "cl-admin-crud-vue3/dist/index.css";
export default {
modules: [
// 基础模块
@ -15,6 +18,7 @@ export default {
// crud 模块
{
name: "crud",
value: Crud,
options: {
crud: {
dict: {

View File

@ -63,7 +63,7 @@
<script lang="ts">
import { defineComponent, inject, onMounted, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { ContextMenu } from "/$/crud";
import { ContextMenu } from "cl-admin-crud-vue3";
import { useRefs } from "/@/core";
import { deepTree, isArray, revDeepTree, isPc } from "/@/core/utils";

View File

@ -36,7 +36,7 @@ import { useStore } from "vuex";
import { useRoute, useRouter } from "vue-router";
import { last } from "/@/core/utils";
import { useRefs } from "/@/core";
import { ContextMenu } from "/$/crud";
import { ContextMenu } from "cl-admin-crud-vue3";
export default {
name: "cl-process",

View File

@ -42,7 +42,7 @@
import { defineComponent, inject, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { useRefs } from "/@/core";
import { CrudLoad, Table } from "/$/crud/types";
import { CrudLoad, Table } from "cl-admin-crud-vue3/types";
export default defineComponent({
name: "sys-log",

View File

@ -81,7 +81,7 @@ import { useRefs } from "/@/core";
import { deepTree } from "/@/core/utils";
import { useRouter } from "vue-router";
import { defineComponent, inject, reactive } from "vue";
import { CrudLoad, Table, Upsert, RefreshOp } from "/$/crud/types";
import { CrudLoad, Table, Upsert, RefreshOp } from "cl-admin-crud-vue3/types";
export default defineComponent({
name: "sys-menu",

View File

@ -37,7 +37,7 @@
import { ElMessageBox } from "element-plus";
import { defineComponent, inject, nextTick, reactive } from "vue";
import { useRefs } from "/@/core";
import { CrudLoad, Table, Upsert } from "/$/crud/types";
import { CrudLoad, Table, Upsert } from "cl-admin-crud-vue3/types";
export default defineComponent({
name: "sys-param",

View File

@ -51,7 +51,7 @@ import { ElMessage } from "element-plus";
import { defineComponent, inject, reactive } from "vue";
import { checkPerm } from "/$/base";
import { useRefs } from "/@/core";
import { CrudLoad, RefreshOp, Table } from "/$/crud/types";
import { CrudLoad, RefreshOp, Table } from "cl-admin-crud-vue3/types";
export default defineComponent({
name: "plugin",

View File

@ -22,7 +22,7 @@
</template>
<script lang="ts">
import { CrudLoad, Table, Upsert } from "/$/crud/types";
import { CrudLoad, Table, Upsert } from "cl-admin-crud-vue3/types";
import { defineComponent, inject, reactive } from "vue";
export default defineComponent({

View File

@ -112,7 +112,7 @@
import { computed, defineComponent, inject, reactive, ref, watch } from "vue";
import { useStore } from "vuex";
import { useRefs } from "/@/core";
import { Table, Upsert } from "/$/crud/types";
import { Table, Upsert } from "cl-admin-crud-vue3/types";
export default defineComponent({
name: "sys-user",

View File

@ -57,7 +57,7 @@ import { computed, defineComponent, inject, onUnmounted, reactive, ref } from "v
import { useStore } from "vuex";
import { ElMessage } from "element-plus";
import { isEmpty } from "/@/core/utils";
import { ContextMenu } from "/$/crud";
import { ContextMenu } from "cl-admin-crud-vue3";
import { parseContent } from "../utils";
export default defineComponent({

View File

@ -1,30 +0,0 @@
import { defineComponent, inject } from "vue";
import { Crud } from "../types";
export default defineComponent({
name: "cl-add-btn",
props: {
props: Object
},
setup(props, { slots }) {
const crud = inject("crud") as Crud;
return () => {
return (
crud.getPermission("add") && (
<el-button
size="mini"
type="primary"
onClick={() => {
crud.rowAdd();
}}
{...props}>
{slots.default ? slots.default() : crud.dict.label.add}
</el-button>
)
);
};
}
});

View File

@ -1,30 +0,0 @@
import { defineComponent, inject } from "vue";
import { Crud } from "../types";
export default defineComponent({
name: "cl-adv-btn",
props: {
props: Object
},
setup(props, { slots }) {
const crud = inject("crud") as Crud;
return () => {
return (
<div class="cl-adv-btn">
<el-button
size="mini"
onClick={() => {
crud.openAdvSearch();
}}
{...props}>
<i class="el-icon-search" />
{slots.default ? slots.default() : crud.dict.label.advSearch}
</el-button>
</div>
);
};
}
});

View File

@ -1,283 +0,0 @@
import { defineComponent, inject, reactive, ref } from "vue";
import { useAction } from "./form/helper";
import { useForm, useRefs } from "../hooks/core";
import { cloneDeep, deepMerge } from "../utils";
import Parse from "../utils/parse";
import { renderNode } from "../utils/vnode";
import { Browser, Crud, Mitt } from "../types";
export default defineComponent({
name: "cl-adv-search",
props: {
// 绑定值
modelValue: {
type: Object,
default: () => {
return {};
}
},
// 表单项
items: {
type: Array,
default: () => []
},
// el-drawer 参数
props: {
type: Object,
default: () => {
return {};
}
},
// 操作按钮 ['search', 'reset', 'clear', 'close']
opList: {
type: Array,
default: () => ["close", "search"]
},
// 打开钩子 { data, { next } }
onOpen: Function,
// 关闭钩子 { done }
onClose: Function,
// 搜索钩子 { data, { next, close } }
onSearch: Function
},
emits: ["update:modelValue", "open", "opened", "close", "closed", "reset", "clear"],
setup(props, { emit }) {
const { refs, setRefs } = useRefs();
const { setFormData } = useForm(props);
// 参数注入
const crud = inject("crud") as Crud;
const mitt = inject("mitt") as Mitt;
// 表单数据
const form = setFormData();
// 是否可见
const visible = ref<boolean>(false);
// 表单配置
const conf = reactive<any>({
items: props.items,
op: {
buttons: props.opList
}
});
// 表单动作
const {
getForm,
setForm,
setData,
setOptions,
toggleItem,
hiddenItem,
showItem,
resetFields,
clearValidate
} = useAction({ conf, form, refs });
// 打开
function open() {
conf.items.map((e: any) => {
if (form[e.prop] === undefined) {
form[e.prop] = e.value;
}
});
const next = (data: any) => {
visible.value = true;
if (data) {
deepMerge(form, data);
}
emit("open", form);
};
if (props.onOpen) {
props.onOpen(form, { next });
} else {
next(null);
}
}
// 打开动画结束
function onOpened() {
emit("opened");
}
// 关闭
function close() {
const done = () => {
visible.value = false;
};
if (props.onClose) {
props.onClose(done);
} else {
done();
}
}
// 关闭回调
function onClose2() {
emit("close");
}
// 关闭动画结束
function onClosed() {
emit("closed");
}
// 重置数据
function reset() {
resetFields();
emit("reset");
}
// 清空数据
function clear() {
for (const i in form) {
form[i] = undefined;
}
clearValidate();
emit("clear");
}
// 搜素请求
function search() {
const params = cloneDeep(form);
const next = (params: any) => {
crud.refresh({
...params,
page: 1
});
close();
};
if (props.onSearch) {
props.onSearch(params, { next, close });
} else {
next(params);
}
}
// 消息事件
mitt.on("crud.openAdvSearch", open);
return {
refs,
visible,
conf,
form,
setRefs,
open,
onOpened,
close,
onClose2,
onClosed,
reset,
clear,
search,
getForm,
setForm,
setData,
setOptions,
toggleItem,
hiddenItem,
showItem
};
},
render(ctx: any) {
const browser = inject("browser") as Browser;
// 渲染表单
function renderForm() {
return (
<el-form
ref={ctx.setRefs("form")}
class="cl-form"
size="small"
label-width="100px"
model={ctx.form}
{...ctx.props}>
<el-row>
{ctx.conf.items.map((e: any) => {
return (
!Parse("hidden", {
value: e.hidden,
scope: ctx.form
}) && (
<el-col span={24} {...e}>
<el-form-item {...e}>
{renderNode(e.component, {
prop: e.prop,
scope: ctx.form,
slots: ctx.$slots
})}
</el-form-item>
</el-col>
)
);
})}
</el-row>
</el-form>
);
}
// 渲染底部
function renderFooter() {
const btns: any = {
search: "搜索",
reset: "重置",
clear: "清空",
close: "取消"
};
return ctx.opList.map((e: any) => {
if (btns[e]) {
return (
<el-button
{...{
size: ctx.props.size || "small",
type: e === "search" ? "primary" : null,
onClick: ctx[e]
}}>
{btns[e]}
</el-button>
);
} else {
return renderNode(e, {
scope: ctx.form,
slots: ctx.$slots
});
}
});
}
return (
<div class="cl-adv-search">
<el-drawer
v-model={ctx.visible}
title="高级搜索"
direction="rtl"
size={browser.isMini ? "100%" : ctx.props.size || "30%"}
{...{
onOpened: ctx.onOpened,
onClosed: ctx.onClosed,
onClose: ctx.onClose2,
...ctx.props
}}>
<div class="cl-adv-search__container">{renderForm()}</div>
<div class="cl-adv-search__footer">{renderFooter()}</div>
</el-drawer>
</div>
);
}
});

View File

@ -1,216 +0,0 @@
import { defineComponent, nextTick, onMounted, reactive, ref } from "vue";
import type { PropType } from "vue";
import { useRefs } from "../../hooks/core";
import { contains } from "../../utils";
import { ContextMenuItem, ContextMenuOptions } from "../../types";
export default defineComponent({
name: "cl-context-menu",
props: {
visible: Boolean,
options: {
type: Object as PropType<ContextMenuOptions>,
default: () => []
},
event: Object
},
setup(props) {
const { refs, setRefs }: any = useRefs();
// 菜单是否可见
const visible2 = ref<boolean>(props.visible);
// 按钮列表
const list = ref<Array<ContextMenuItem>>([]);
// 菜单样式
const style = reactive<any>({
left: 0,
top: 0
});
// 选中值
const ids = ref<string>("");
// 阻止默认事件
function stopDefault(e: any) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
}
// 解析列表
function parseList(list: Array<ContextMenuItem>) {
const deep = (list: any[]) => {
list.forEach((e: any) => {
e.showChildren = false;
if (e.children) {
deep(e.children);
}
});
};
deep(list);
return list;
}
// 关闭菜单
function close() {
visible2.value = false;
ids.value = "";
}
// 打开菜单
function open(event: any, options?: ContextMenuOptions) {
let left: number = event.pageX;
let top: number = event.pageY;
if (!options) {
options = {};
}
if (options.list) {
list.value = parseList(options.list);
}
nextTick(() => {
const { clientHeight: h1, clientWidth: w1 } = document.body;
const { clientHeight: h2, clientWidth: w2 } = refs.value["context-menu"];
if (top + h2 > h1) {
top = h1 - h2 - 5;
}
if (left + w2 > w1) {
left = w1 - w2 - 5;
}
style.left = left + "px";
style.top = top + "px";
});
// 阻止默认事件
stopDefault(event);
// 显示菜单
visible2.value = true;
return {
close
};
}
// 行点击
function rowClick(e: any, id: string) {
ids.value = id;
if (e.disabled) {
return false;
}
if (e.callback) {
return e.callback(e, () => {
close();
});
}
if (e.children) {
e.showChildren = !e.showChildren;
} else {
close();
}
}
onMounted(function () {
if (visible2.value) {
// 添加到 body 下
document.body.appendChild(refs.value["context-menu"]);
// 关闭事件
(document.documentElement || document.body).addEventListener("mousedown", (e) => {
const el = refs.value["context-menu"];
if (!contains(el, e.target) && el != e.target) {
close();
}
});
// 默认打开
open(props.event, props.options);
}
});
return {
refs,
visible2,
ids,
style,
list,
setRefs,
open,
close,
rowClick,
stopDefault
};
},
render(ctx: any) {
function deep(list: any[], pId: string, level: number) {
return (
<div class={["cl-context-menu__box", level > 1 && "is-append"]}>
{list
.filter((e) => !e.hidden)
.map((e, i) => {
const id = `${pId}-${i}`;
return (
<div
class={{
"is-active": ctx.ids.includes(id),
"is-ellipsis": e.ellipsis,
"is-disabled": e.disabled
}}>
{/* 前缀图标 */}
{e["prefix-icon"] && <i class={e["prefix-icon"]}></i>}
{/* 标题 */}
<span
onClick={() => {
ctx.rowClick(e, id);
}}>
{e.label}
</span>
{/* 后缀图标 */}
{e["suffix-icon"] && <i class={e["suffix-icon"]}></i>}
{/* 子集*/}
{e.children &&
e.showChildren &&
deep(e.children, id, level + 1)}
</div>
);
})}
</div>
);
}
return (
ctx.visible2 && (
<div
class="cl-context-menu"
ref={ctx.setRefs("context-menu")}
style={ctx.style}
onContextmenu={ctx.stopDefault}>
{ctx.$slots.default ? ctx.$slots.default() : deep(ctx.list, "0", 1)}
</div>
)
);
}
});

View File

@ -1,16 +0,0 @@
import { h, render } from "vue";
import ContextMenuConstructor from "./context-menu";
class ContextMenu {
open(event: any, options: any) {
const vm: any = h(ContextMenuConstructor, {
visible: true,
event,
options
});
render(vm, document.createElement("div"));
}
}
export default new ContextMenu();

View File

@ -1,53 +0,0 @@
import { merge } from "merge";
import { deepMerge, isFunction } from "../../utils";
export const bootstap = (crud: any, { fn }: any) => {
const { params, permission, service, refresh, id } = crud || {};
const app = {
refresh(d: any) {
return isFunction(d) ? d(params, refresh) : refresh(d);
}
};
function ctx(data: any) {
deepMerge(crud, data);
return ctx;
}
ctx.id = id;
ctx.service = function (s: any) {
if (s) {
Object.assign(crud.service, s);
crud.service.__proto__ = s.__proto__;
}
if (fn.permission) {
merge(permission, fn.permission({ permission, service, refresh }));
}
return ctx;
};
ctx.permission = function (d: any) {
if (isFunction(d)) {
merge(permission, d({ service, permission }));
} else {
merge(permission, d);
}
return ctx;
};
ctx.set = (key: string, value: any) => {
deepMerge(crud[key], value);
return ctx;
};
ctx.done = function () {};
return { ctx, app };
};

View File

@ -1,205 +0,0 @@
import { ElMessageBox, ElMessage } from "element-plus";
import { isArray, isObject, isString } from "../../utils";
import { ServiceName } from "../../types";
export function useRequest({ mitt, props, crud }: any) {
// 刷新随机值,避免脏数据
let refreshRd = 0;
// 获取权限
function getPermission(key: ServiceName): boolean {
switch (key) {
case "update":
return Boolean(crud.permission["update"]);
default:
return Boolean(crud.permission[key]);
}
}
// 根据字典替换请求参数
function paramsReplace(params: any) {
const { pagination, search, sort } = crud.dict;
const a: any = { ...params };
const b: any = { ...pagination, ...search, ...sort };
for (const i in b) {
if (a[i]) {
if (i != b[i]) {
a[`_${b[i]}`] = a[i];
delete a[i];
}
}
}
for (const i in a) {
if (i[0] === "_") {
a[i.substr(1)] = a[i];
delete a[i];
}
}
return a;
}
// 刷新请求
function refresh(newParams?: any) {
// 合并请求参数
const reqParams = paramsReplace(Object.assign(crud.params, newParams));
// Loading
crud.loading = true;
// 预防脏数据
const rd = (refreshRd = Math.random());
// 完成事件
const done = () => {
crud.loading = false;
};
// 渲染
const render = (list: Array<any>, pagination?: any) => {
mitt.emit("crud.refresh", { list, pagination });
done();
};
// 请求执行
const next = (params: any) => {
return new Promise((resolve, reject) => {
const reqName = crud.dict.api.page;
if (!crud.service[reqName]) {
done();
return reject(`Request function '${reqName}' is not fount`);
}
crud.service[reqName](params)
.then((res: any) => {
if (rd != refreshRd) {
return false;
}
if (isString(res)) {
return reject("Response error");
}
if (isArray(res)) {
render(res);
} else if (isObject(res)) {
render(res.list, res.pagination);
}
resolve(res);
done();
})
.catch((err: string) => {
ElMessage.error(err);
reject(err);
done();
console.error(err);
});
});
};
if (props.onRefresh) {
return props.onRefresh(reqParams, { next, done, render });
} else {
return next(reqParams);
}
}
// 删除请求
function rowDelete(...selection: Array<any>) {
// 获取请求方法
const reqName = crud.dict.api.delete;
const params = {
ids: selection.map((e) => e.id)
};
const next = (params: any) => {
return new Promise((resolve, reject) => {
ElMessageBox.confirm(`此操作将永久删除选中数据,是否继续?`, "提示", {
type: "warning"
})
.then((res: any) => {
if (res === "confirm") {
// 验证方法
if (!crud.service[reqName]) {
return reject(`Request function '${reqName}' is not fount`);
}
// 发送请求
crud.service[reqName](params)
.then((res: any) => {
ElMessage.success(`删除成功`);
refresh();
resolve(res);
})
.catch((err: string) => {
ElMessage.error(err);
reject(err);
});
}
})
.catch(() => null);
});
};
if (props.onDelete) {
props.onDelete(selection, { next });
} else {
next(params);
}
}
return {
rowDelete,
refresh,
getPermission,
paramsReplace
};
}
export function useMitt({ mitt }: any) {
// 打开新增
function rowAdd() {
mitt.emit("crud.add");
}
// 打开编辑
function rowEdit(data: any) {
mitt.emit("crud.edit", data);
}
// 打开追加
function rowAppend(data: any) {
mitt.emit("crud.append", data);
}
// 关闭新增、编辑弹窗
function rowClose() {
mitt.emit("crud.close");
}
// 打开高级搜索
function openAdvSearch() {
mitt.emit("crud.openAdvSearch");
}
// 关闭高级搜索
function closeAdvSearch() {
mitt.emit("crud.closeAdvSearch");
}
return {
rowAdd,
rowEdit,
rowAppend,
rowClose,
openAdvSearch,
closeAdvSearch
};
}

View File

@ -1,120 +0,0 @@
import {
defineComponent,
getCurrentInstance,
inject,
onMounted,
provide,
reactive,
ref
} from "vue";
import { useMitt, useRequest } from "./helper";
import { bootstap } from "./app";
import Mitt from "../../utils/mitt";
import { deepMerge } from "../../utils";
export default defineComponent({
name: "cl-crud",
props: {
name: String,
border: Boolean,
onDelete: Function,
onRefresh: Function
},
emits: ["load"],
setup(props, { emit }) {
const ctx = getCurrentInstance();
// 组件间通讯
const mitt = new Mitt(ctx?.uid);
// 配置
const crud = reactive<any>({
dict: {
api: {
list: "list",
add: "add",
update: "update",
delete: "delete",
info: "info",
page: "page"
},
pagination: {
page: "page",
size: "size"
},
search: {
keyWord: "keyWord",
query: "query"
},
sort: {
order: "order",
prop: "prop"
},
label: {
add: "新增",
delete: "删除",
multiDelete: "删除",
update: "编辑",
refresh: "刷新",
advSearch: "高级搜索",
saveButtonText: "保存",
closeButtonText: "关闭"
}
},
selection: [],
table: {
"context-menu": true
},
crudRef: ref<any>({}),
service: {},
loading: false,
params: {
page: 1,
size: 20
},
permission: {
update: true,
page: true,
info: true,
list: true,
add: true,
delete: true
}
});
// 集合
Object.assign(crud, useMitt({ mitt }), useRequest({ mitt, props, crud }));
// 临时处理方法
const fn: any = {
permission: null
};
// 提供
provide("crud", crud);
provide("mitt", mitt);
onMounted(() => {
// 加载完成回调
emit("load", bootstap(deepMerge(crud, inject("__crud")), { fn }));
// 监听窗口大小改变事件
window.addEventListener("resize", () => {
mitt.emit("crud.resize");
});
});
return crud;
},
render(ctx: any) {
return (
<div class={["cl-crud", { "is-border": ctx.border }]} ref="crudRef">
{ctx.$slots.default && ctx.$slots.default()}
</div>
);
}
});

View File

@ -1,139 +0,0 @@
import { getCurrentInstance, nextTick } from "vue";
export function useDialog({ props, isFullscreen }: any) {
const ctx = getCurrentInstance();
// 设置对话框样式、拖动
const setDialog = () => {
const { top = "15vh" } = props.props;
nextTick(() => {
// 获取元素
const dlg: any = document.querySelector(`.cl-dialog--${ctx?.uid}`);
const hdr: any = dlg ? dlg.querySelector(".el-dialog__header") : null;
// 设置对话框
if (dlg) {
dlg.style.left = 0;
if (isFullscreen.value) {
dlg.style.top = 0;
dlg.style.marginBottom = 0;
} else {
dlg.style.marginBottom = "160px";
dlg.style.top = top;
}
// 设置光标
hdr.style.cursor = isFullscreen.value ? "text" : "move";
}
// 设置头部
if (hdr) {
hdr.onmousedown = (e: any) => {
// 可视区域大小
const { clientWidth, clientHeight } = document.documentElement || document.body;
// Try drag
const isDrag = (() => {
if (isFullscreen.value) {
return false;
}
// 是否能拖动
if (!props.drag) {
return false;
}
// Determine height of the box is too large
let marginTop = 0;
if (["vh", "%"].some((e) => top.includes(e))) {
marginTop = clientHeight * (parseInt(top) / 100);
}
if (top.includes("px")) {
marginTop = top;
}
return dlg.clientHeight < clientHeight - marginTop;
})();
// 设置指针状态
if (!isDrag) {
return (hdr.style.cursor = "text");
} else {
hdr.style.cursor = "move";
}
// Distance
const dis = {
left: e.clientX - hdr.offsetLeft,
top: e.clientY - hdr.offsetTop
};
// Calc left and top of the box
const box = (() => {
const { left, top } =
dlg.currentStyle || window.getComputedStyle(dlg, null);
if (left.includes("%")) {
return {
top: +clientHeight * (+top.replace(/%/g, "") / 100),
left: +clientWidth * (+left.replace(/%/g, "") / 100)
};
} else {
return {
top: +top.replace(/\px/g, ""),
left: +left.replace(/\px/g, "")
};
}
})();
// Screen limit
const pad = 5;
const minLeft = -(clientWidth - dlg.clientWidth) / 2 + pad;
const maxLeft =
(dlg.clientWidth >= clientWidth / 2
? dlg.clientWidth / 2 - (dlg.clientWidth - clientWidth / 2)
: dlg.clientWidth / 2 + clientWidth / 2 - dlg.clientWidth) - pad;
const minTop = pad;
const maxTop = clientHeight - dlg.clientHeight - pad;
// Start move
document.onmousemove = function (e) {
let left = e.clientX - dis.left + box.left;
let top = e.clientY - dis.top + box.top;
if (left < minLeft) {
left = minLeft;
} else if (left >= maxLeft) {
left = maxLeft;
}
if (top < minTop) {
top = minTop;
} else if (top >= maxTop) {
top = maxTop;
}
// Set dialog top and left
dlg.style.top = top + "px";
dlg.style.left = left + "px";
};
// Clear event
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
};
};
}
});
};
return {
setDialog
};
}

View File

@ -1,296 +0,0 @@
import { defineComponent, h, inject, onMounted, ref, watch, computed } from "vue";
import { useDialog } from "./helper";
import { Browser } from "../../types";
import { isArray, isBoolean } from "../../utils";
import { renderNode } from "../../utils/vnode";
export default defineComponent({
name: "cl-dialog",
props: {
// 是否可见
modelValue: {
type: Boolean,
default: false
},
// 标题
title: {
type: String,
default: "对话框"
},
// 高度
height: String,
// 宽度
width: {
type: String,
default: "50%"
},
// 是否缓存
keepAlive: Boolean,
// 是否拖动
drag: {
type: Boolean,
default: true
},
// el-dialog 参数
props: {
type: Object,
default: () => {
return {};
}
},
// 控制按钮
controls: {
type: Array,
default: () => ["fullscreen", "close"]
},
// 是否隐藏控制按钮
hiddenControls: {
type: Boolean,
default: false
},
// 隐藏头部元素
hiddenHeader: {
type: Boolean,
default: false
}
},
emits: ["update:modelValue", "fullscreen-change", "open", "opened", "close", "closed"],
setup(props, { emit }) {
const browser = inject("browser") as Browser;
// 是否全屏
const fullscreen = ref<boolean>(props.props.fullscreen);
// 是否可见
const visible = ref<boolean>(props.modelValue);
// 缓存数
const cacheKey = ref<number>(0);
// 是否全屏
const isFullscreen = computed(() => {
return browser.isMini ? true : fullscreen.value;
});
// 对话框事件
const { setDialog } = useDialog({ isFullscreen, props });
// 监听绑定值
watch(
() => props.modelValue,
(val: boolean) => {
visible.value = val;
}
);
// 监听 fullscreen 变化
watch(
() => props.props.fullscreen,
(val: boolean) => {
fullscreen.value = val;
}
);
watch(fullscreen, (val: boolean) => {
emit("fullscreen-change", val);
});
watch(isFullscreen, setDialog);
function close() {
emit("update:modelValue", false);
}
// 关闭前
function beforeClose() {
if (props.props["before-close"]) {
props.props["before-close"](close);
} else {
close();
}
}
function onOpen() {
// 初始值
fullscreen.value = props.props.fullscreen;
// 是否缓存
if (!props.keepAlive) {
cacheKey.value += 1;
}
setDialog();
emit("open");
}
function onOpened() {
emit("opened");
}
function onClose() {
emit("close");
close();
}
function onClosed() {
emit("closed");
}
// 切换全屏
function changeFullscreen(val?: boolean) {
fullscreen.value = isBoolean(val) ? Boolean(val) : !fullscreen.value;
}
// 双击全屏
function dblClickFullscreen() {
if (isArray(props.controls) && props.controls.includes("fullscreen")) {
changeFullscreen();
}
}
onMounted(function () {
setDialog();
});
return {
visible,
fullscreen,
isFullscreen,
cacheKey,
close,
onOpen,
onOpened,
onClose,
onClosed,
changeFullscreen,
beforeClose,
dblClickFullscreen
};
},
render(ctx: any) {
const browser = inject("browser") as Browser;
// 渲染头部
function renderHeader() {
return ctx.hiddenHeader ? null : (
<div class="cl-dialog__header" onDblclick={ctx.dblClickFullscreen}>
{/* 标题 */}
<span class="cl-dialog__title">{ctx.title}</span>
{/* 控制按钮 */}
<div class="cl-dialog__controls">
{ctx.controls.map((vnode: any) => {
// 全屏按钮
if (vnode === "fullscreen") {
// 隐藏全屏
if (browser.screen === "xs") {
return null;
}
// 是否显示全屏按钮
if (ctx.isFullscreen) {
return (
<button
type="button"
class="minimize"
onClick={() => {
ctx.changeFullscreen(false);
}}>
<i class="el-icon-minus" />
</button>
);
} else {
return (
<button
type="button"
class="maximize"
onClick={() => {
ctx.changeFullscreen(true);
}}>
<i class="el-icon-full-screen" />
</button>
);
}
}
// 关闭按钮
else if (vnode === "close") {
return (
<button
type="button"
class="close"
onClick={() => {
ctx.beforeClose();
}}>
<i class="el-icon-close" />
</button>
);
} else {
return renderNode(vnode, {
slots: ctx.$slots
});
}
})}
</div>
</div>
);
}
// el-dialog 对话框
const ElDialog = (
<el-dialog
title={ctx.title}
width={ctx.width}
onOpen={ctx.onOpen}
onOpened={ctx.onOpened}
onClose={ctx.onClose}
onClosed={ctx.onClosed}
show-close={false}
v-model={ctx.visible}></el-dialog>
);
// 自定义样式
const customClass = `cl-dialog cl-dialog--${ctx.$.uid} ${ctx.props.customClass || ""}`;
// 对话框高度
const height = ctx.height ? (ctx.isFullscreen ? `calc(100vh - 46px)` : ctx.height) : null;
return (
<div>
{h(
ElDialog,
{
...ctx.props,
customClass,
fullscreen: ctx.isFullscreen
},
{
title() {
return renderHeader();
},
default() {
return (
<div
class="cl-dialog__container"
style={{ height }}
key={ctx.cacheKey}>
{ctx.$slots.default && ctx.$slots.default()}
</div>
);
},
footer() {
return (
<div class="cl-dialog__footer">
{ctx.$slots.footer && ctx.$slots.footer()}
</div>
);
}
}
)}
</div>
);
}
});

View File

@ -1,15 +0,0 @@
import { defineComponent } from "vue";
export default defineComponent({
name: "cl-error-message",
props: {
title: String
},
setup(props) {
return () => {
return <el-alert title={props.title} type="error"></el-alert>;
};
}
});

View File

@ -1,21 +0,0 @@
import { defineComponent } from "vue";
export default defineComponent({
name: "cl-filter",
props: {
label: String
},
render(ctx: any) {
return (
<div class="cl-filter">
<span class="cl-filter__label" v-show={ctx.label}>
{ctx.label}
</span>
{ctx.$slots.default ? ctx.$slots.default() : null}
</div>
);
}
});

View File

@ -1,11 +0,0 @@
import { defineComponent } from "vue";
export default defineComponent({
name: "cl-flex1",
setup(_, { slots }) {
return () => {
return <div class="cl-flex1">{slots.default ? slots.default() : null}</div>;
};
}
});

View File

@ -1,119 +0,0 @@
import { defineComponent, nextTick, onMounted, reactive, ref, watch } from "vue";
import { useRefs } from "../hooks/core";
import { isArray, isEmpty } from "../utils";
export default defineComponent({
name: "cl-form-tabs",
props: {
modelValue: [String, Number],
labels: {
type: Array,
default: () => []
},
justify: {
type: String,
default: "center"
},
color: {
type: String,
default: "#409EFF"
}
},
emits: ["update:modelValue", "change"],
setup(props, { emit }) {
const { refs, setRefs }: any = useRefs();
// 标识
const active = ref<any>("");
// 切换列表
const list = ref<any[]>([]);
// 下划线
const line = reactive<any>({
width: "",
offsetLeft: ""
});
function update(val: string | number) {
nextTick(() => {
const index = list.value.findIndex((e) => e.value === val);
const item = refs.value[`tab-${index}`];
if (item) {
// 下划线位置
line.width = item.clientWidth + "px";
line.transform = `translateX(${item.offsetLeft}px)`;
line.backgroundColor = props.color;
// 靠左位置
let left: number = item.offsetLeft + item.clientWidth / 2 - 414 / 2 + 15;
if (left < 0) {
left = 0;
}
// 设置滚动距离
refs.value.tabs.scrollLeft = left;
}
});
active.value = val;
emit("update:modelValue", val);
emit("change", val);
}
// 监听绑定值变化
watch(
() => props.modelValue,
(val: any) => {
update(val);
}
);
onMounted(function () {
if (isArray(props.labels) && props.labels.length > 0) {
list.value = props.labels;
update(isEmpty(props.modelValue) ? list.value[0].value : props.modelValue);
}
});
return {
active,
list,
line,
refs,
setRefs,
update
};
},
render(ctx: any) {
return (
<div class="cl-form-tabs">
<ul style={{ textAlign: ctx.justify }} ref={ctx.setRefs("tabs")}>
{ctx.list.map((e: any, i: number) => {
return (
<li
ref={ctx.setRefs(`tab-${i}`)}
class={{ "is-active": e.value === ctx.active }}
style={{
color: e.value === ctx.active ? ctx.color : "#444"
}}
onClick={() => {
ctx.update(e.value);
}}>
{e.label}
</li>
);
})}
{ctx.line.width && <div class="cl-form-tabs__line" style={ctx.line}></div>}
</ul>
</div>
);
}
});

View File

@ -1,131 +0,0 @@
import { dataset } from "../../utils";
import { ref } from "vue";
export function useAction({ conf, form, refs }: any) {
// 加载状态
const loading = ref<boolean>(false);
// 设置数据
function set({ prop, options, hidden, path }: any, data?: any): any {
let p: string = path || "";
if (prop) {
p = `items[prop:${prop}]`;
}
if (options) {
p += `.component.options`;
}
if (hidden) {
p += ".hidden";
}
return dataset(conf, p, data);
}
// 获取表单值
function getForm(prop: string) {
return prop ? form[prop] : form;
}
// 设置表单值
function setForm(prop: string, value: any) {
form[prop] = value;
}
// 设置路径数据
function setData(path: string, value: any) {
set({ path }, value);
}
// 设置表单项的下拉数据列表
function setOptions(prop: string, value: Array<any>) {
set({ options: true, prop }, value);
}
// 切换表单项的显示、隐藏
function toggleItem(prop: string, value?: boolean) {
if (value === undefined) {
value = set({ prop, hidden: true });
}
set({ hidden: true, prop }, !value);
}
// 对部分表单项隐藏
function hiddenItem(...props: Array<string>) {
props.forEach((prop: string) => {
set({ hidden: true, prop }, true);
});
}
// 对部分表单项显示
function showItem(...props: Array<string>) {
props.forEach((prop: string) => {
set({ hidden: true, prop }, false);
});
}
// 显示表单加载状态
function showLoading() {
loading.value = true;
}
// 隐藏表单加载状态
function hiddenLoading() {
loading.value = false;
}
// 对整个表单进行重置
function resetFields() {
if (refs.value.form) {
refs.value.form.resetFields();
}
}
// 移除表单项的校验结果
function clearValidate(props?: string | Array<any>) {
if (refs.value.form) {
return refs.value.form.clearValidate(props);
}
}
// 对部分表单字段进行校验
function validateField(props?: string | Array<any>, callback?: Function) {
if (refs.value.form) {
refs.value.form.validateField(props, callback);
}
}
// 对整个表单进行校验
function validate(callback?: Function) {
if (refs.value.form) {
refs.value.form.validate(callback);
}
}
// 是否展开表单项
function collapseItem(e: any) {
clearValidate(e.prop);
e.collapse = !e.collapse;
}
return {
loading,
showLoading,
hiddenLoading,
collapseItem,
getForm,
setForm,
setData,
setOptions,
toggleItem,
hiddenItem,
showItem,
resetFields,
clearValidate,
validateField,
validate
};
}

View File

@ -1,517 +0,0 @@
import { defineComponent, h, inject, nextTick, provide, reactive, ref, watch } from "vue";
import cloneDeep from "clone-deep";
import { useAction } from "./helper";
import { useRefs, useForm } from "../../hooks/core";
import { deepMerge, isBoolean, isEmpty, isObject, isString } from "../../utils";
import Parse from "../../utils/parse";
import { renderNode } from "../../utils/vnode";
import { Browser, Form } from "../../types";
import FormHook from "../../hooks/form";
export default defineComponent({
name: "cl-form",
props: {
modelValue: {
type: Object,
default: () => {
return {};
}
}
},
emits: ["update:modelValue"],
setup(props, { emit }) {
const { refs, setRefs }: any = useRefs();
// 设置表单值
const { setFormData } = useForm(props);
// 表单是否可见
const visible = ref<boolean>(false);
// 表单提交保存状态
const saving = ref<boolean>(false);
// 选项卡
const tabActive = ref<any>(null);
// 表单数据
const form = setFormData();
// 表单配置
const conf = reactive<Form>({
title: "自定义表单",
width: "50%",
props: {
size: "small",
labelWidth: "100px"
},
on: {},
op: {
hidden: false,
saveButtonText: "保存",
closeButtonText: "取消",
buttons: ["close", "save"]
},
dialog: {
props: {
fullscreen: false,
"close-on-click-modal": false,
"append-to-body": true
},
hiddenControls: false,
controls: ["fullscreen", "close"]
},
items: [],
_data: {}
});
// 表单动作
const {
loading,
showLoading,
hiddenLoading,
collapseItem,
getForm,
setForm,
setData,
setOptions,
toggleItem,
hiddenItem,
showItem,
resetFields,
clearValidate,
validateField,
validate
} = useAction({ conf, form, refs });
// 更新绑定值
watch(form, (val: any) => {
emit("update:modelValue", val);
});
// 提供
provide("form", form);
// 请求表单保存状态
function done() {
saving.value = false;
}
// 关闭表单
function close() {
visible.value = false;
done();
}
function onClosed() {
tabActive.value = null;
}
// 表单关闭前事件
function beforeClose() {
if (conf.on?.close) {
conf.on.close(close);
} else {
close();
}
}
// 清空表单验证
function clear() {
for (const i in form) {
delete form[i];
}
clearValidate();
}
// 表单提交
function submit(callback?: Function) {
// 验证表单
refs.value.form.validate(async (valid: boolean, error: any) => {
if (valid) {
saving.value = true;
// 拷贝表单值
const d = cloneDeep(form);
// 过滤隐藏的表单项
conf.items.forEach((e: any) => {
if (e._hidden) {
delete d[e.prop];
}
if (e.hook) {
d[e.prop] = FormHook.submit(d[e.prop], e.hook, d);
}
});
const submit = callback || conf.on?.submit;
// 提交事件
if (submit) {
submit(d, {
done,
close
});
} else {
console.error("Not found callback function");
}
} else {
// 判断是否使用form-tabs切换到对应的选项卡
const keys = Object.keys(error);
if (tabActive.value) {
const item = conf.items.find((e) => e.prop === keys[0]);
if (item) {
tabActive.value = item.group;
}
}
}
});
}
// 打开表单
function open(options?: Form) {
if (!options) {
options = {
items: []
};
}
clear();
// 合并配置
for (const i in conf) {
switch (i) {
case "items":
conf.items = cloneDeep(options.items || []);
break;
case "title":
case "width":
conf[i] = options[i];
break;
case "props":
case "on":
case "op":
case "dialog":
case "_data":
conf[i] = deepMerge(conf[i], options[i] || {});
break;
}
}
// 显示对话框
visible.value = true;
// 预设表单值
if (options?.form) {
for (const i in options.form) {
form[i] = options.form[i];
}
}
// 设置表单数据
conf.items.map((e: any) => {
if (e.prop) {
form[e.prop] = FormHook.bind(
isEmpty(form[e.prop]) ? cloneDeep(e.value) : form[e.prop],
e.hook,
form
);
}
});
// 打开回调
nextTick(() => {
if (conf.on?.open) {
conf.on.open(form, {
close,
submit,
done
});
}
});
return {
showLoading,
hiddenLoading,
collapseItem,
getForm,
setForm,
setData,
setOptions,
toggleItem,
hiddenItem,
showItem,
resetFields,
clearValidate,
validateField,
validate
};
}
// 重新绑定表单数据
function reBindForm(data: any) {
for (const i in data) {
const d: any = conf.items.find((e) => e.prop === i);
form[i] = d ? FormHook.bind(data[i], d.hook, form) : data[i];
}
}
return {
visible,
saving,
tabActive,
form,
refs,
setRefs,
conf,
loading,
open,
beforeClose,
close,
onClosed,
done,
clear,
submit,
reBindForm,
showLoading,
hiddenLoading,
collapseItem,
getForm,
setForm,
setData,
setOptions,
toggleItem,
hiddenItem,
showItem,
resetFields,
clearValidate,
validateField,
validate
};
},
render(ctx: any) {
const browser = inject("browser") as Browser;
const { props, op, title, width, dialog, _data } = ctx.conf;
// 渲染表单及表单项
const renderForm = () => {
// 表单项列表
const children = ctx.conf.items.map((e: any) => {
if (e.type == "tabs") {
return <cl-form-tabs v-model={ctx.tabActive} {...e.props}></cl-form-tabs>;
}
// 隐藏处理
e._hidden = Parse("hidden", {
value: e.hidden,
scope: ctx.form,
data: _data
});
// 分组
e._group =
isEmpty(ctx.tabActive) || isEmpty(e.group) ? true : e.group === ctx.tabActive;
// Flex handler
if (isEmpty(e.flex)) {
e._flex = true;
}
return (
!e._hidden && (
<el-col span={24} {...e}>
{e.component &&
h(
<el-form-item v-show={e._group}></el-form-item>,
{
prop: e.prop,
rules: e.rules,
...e.props
},
{
label: () => {
let d: any = {
text: "",
tip: "",
icon: ""
};
if (isString(e.label)) {
d.text = e.label;
} else if (isObject(e.label)) {
d = e.label;
}
return (
<el-tooltip
effect="dark"
placement="top"
content={d.tip}
disabled={!d.tip}>
<span>
{d.text}
{d.icon && <i class={d.icon}></i>}
</span>
</el-tooltip>
);
},
default: () => {
return (
<div>
{/* Form item */}
<div class="cl-form-item">
{["prepend", "component", "append"].map(
(name) => {
return (
e[name] && (
<div
v-show={!e.collapse}
class={[
`cl-form-item__${name}`,
{
"is-flex":
e._flex
}
]}>
{renderNode(e[name], {
prop: e.prop,
scope: ctx.form,
slots: ctx.$slots
})}
</div>
)
);
}
)}
</div>
{/* Collapse button */}
{isBoolean(e.collapse) && (
<div
class="cl-form-item__collapse"
onClick={() => {
ctx.collapseItem(e);
}}>
<el-divider content-position="center">
{e.collapse ? (
<span>
<i class="el-icon-arrow-down"></i>
</span>
) : (
<span>
<i class="el-icon-arrow-up"></i>
</span>
)}
</el-divider>
</div>
)}
</div>
);
}
}
)}
</el-col>
)
);
});
// el-form
const ElForm = (
<el-form
ref={ctx.setRefs("form")}
label-position={browser.isMini ? "top" : ""}
size="small"
label-width="100px"
disabled={ctx.saving}
model={ctx.form}></el-form>
);
return h(ElForm, props, {
default: () => {
return (
<el-row gutter={10} v-loading={ctx.loading}>
{children}
</el-row>
);
}
});
};
// 渲染表单按钮
function renderFooter() {
const { hidden, buttons, saveButtonText, closeButtonText } = op;
const { size = "small" } = props;
return hidden
? null
: buttons.map((vnode: any) => {
if (vnode == "save") {
return (
<el-button
{...{
size,
type: "success",
disabled: ctx.loading,
loading: ctx.saving,
onClick: () => {
ctx.submit();
}
}}>
{saveButtonText}
</el-button>
);
} else if (vnode == "close") {
return (
<el-button
{...{
size,
onClick: () => {
ctx.beforeClose();
}
}}>
{closeButtonText}
</el-button>
);
} else {
return renderNode(vnode, {
scope: ctx.form,
slots: ctx.$slots
});
}
});
}
return h(
<cl-dialog v-model={ctx.visible}></cl-dialog>,
{
title,
width,
...dialog,
props: {
...dialog.props,
"before-close": ctx.beforeClose
},
onClosed: ctx.onClosed
},
{
default() {
return (
<div class="cl-form">
<div class="cl-form__container">{renderForm()}</div>
<div class="cl-form__footer">{renderFooter()}</div>
</div>
);
}
}
);
}
});

View File

@ -1,39 +0,0 @@
import Crud from "./crud/index";
import AddBtn from "./add-btn";
import AdvBtn from "./adv-btn";
import AdvSearch from "./adv-search";
import Flex from "./flex1";
import Form from "./form";
import FormTabs from "./form-tabs";
import MultiDeleteBtn from "./multi-delete-btn";
import Pagination from "./pagination";
import Query from "./query";
import RefreshBtn from "./refresh-btn";
import SearchKey from "./search-key";
import Table from "./table/index";
import Upsert from "./upsert/index";
import Dialog from "./dialog";
import Filter from "./filter";
import ErrorMessage from "./error-message";
import ContextMenu from "./context-menu/context-menu";
export {
Crud,
AddBtn,
AdvBtn,
AdvSearch,
Flex,
Form,
FormTabs,
MultiDeleteBtn,
Pagination,
Query,
RefreshBtn,
SearchKey,
Table,
Upsert,
Dialog,
Filter,
ErrorMessage,
ContextMenu
};

View File

@ -1,26 +0,0 @@
import { defineComponent, inject } from "vue";
import { Crud } from "../types";
export default defineComponent({
name: "cl-multi-delete-btn",
setup(_, { slots }) {
const crud = inject("crud") as Crud;
return () => {
return (
crud.getPermission("delete") && (
<el-button
size="mini"
type="danger"
disabled={crud.selection.length === 0}
onClick={() => {
crud.rowDelete(...crud.selection);
}}>
{slots.default ? slots.default() : crud.dict.label.multiDelete}
</el-button>
)
);
};
}
});

View File

@ -1,84 +0,0 @@
import { defineComponent, h, inject, ref, watch } from "vue";
import { Crud, Mitt } from "../types";
export default defineComponent({
name: "cl-pagination",
props: {
props: {
type: Object,
default: () => {
return {};
}
}
},
setup(props) {
const crud = inject("crud") as Crud;
const mitt = inject("mitt") as Mitt;
// 总数
const total = ref<number>(0);
// 当前页码
const currentPage = ref<number>(1);
// 每页大小
const pageSize = ref<number>(20);
const onCurrentChange = (index: number) => {
crud.refresh({
page: index
});
};
const onSizeChange = (size: number) => {
crud.refresh({
page: 1,
size
});
};
const setPagination = (res: any) => {
if (res) {
currentPage.value = res.currentPage || res.page || 1;
pageSize.value = res.pageSize || res.size || 20;
total.value = res.total | 0;
crud.params.size = pageSize.value;
}
};
mitt.on("crud.refresh", ({ pagination }: any) => {
setPagination(pagination);
});
watch(() => props.props, setPagination, {
immediate: true
});
return {
total,
currentPage,
pageSize,
onCurrentChange,
onSizeChange,
setPagination
};
},
render(ctx: any) {
const ElPagination = (
<el-pagination
background
page-sizes={[10, 20, 30, 40, 50, 100]}
layout={"total, sizes, prev, pager, next, jumper"}
{...ctx.props}></el-pagination>
);
return h(ElPagination, {
onSizeChange: ctx.onSizeChange,
onCurrentChange: ctx.onCurrentChange,
total: ctx.total,
"current-page": ctx.currentPage,
"page-size": ctx.pageSize
});
}
});

View File

@ -1,118 +0,0 @@
import { defineComponent, inject, ref, watch } from "vue";
import { Crud } from "../types";
import { isArray } from "../utils";
export default defineComponent({
name: "cl-query",
props: {
modelValue: null,
list: {
type: Array,
required: true
},
field: {
type: String,
default: "query"
},
multiple: Boolean,
callback: Function
},
emits: ["update:modelValue", "change"],
setup(props, { emit }) {
const crud = inject("crud") as Crud;
const list2 = ref<Array<any>>([]);
// 更新数据列表
const update = () => {
let arr: Array<any> = [];
if (isArray(props.modelValue)) {
arr = props.modelValue;
} else {
arr = [props.modelValue];
}
if (!props.multiple) {
arr.splice(1);
}
// 默认选择
list2.value = (props.list || []).map((e: any) => {
e.active = arr.some((v) => v === e.value);
return e;
});
};
update();
// 点击选择项
const selectItem = (event: any, item: any) => {
if (item.active) {
item.active = false;
} else {
if (props.multiple) {
item.active = true;
} else {
list2.value.map((e: any) => {
e.active = e.value == item.value;
});
}
}
// 过滤未选中的
const selection = list2.value.filter((e: any) => e.active).map((e: any) => e.value);
// 处理多选情况
const value = props.multiple ? selection : selection[0];
// 请求回调
if (props.callback) {
props.callback(value);
} else {
crud.refresh({
[props.field]: value
});
emit("change", value);
}
// 阻止默认事件
event.preventDefault();
};
// 监听绑定值,更新数据列表
watch(
() => props.modelValue,
() => {
update();
}
);
return {
list2,
selectItem
};
},
render(ctx: any) {
return (
<div class="cl-query">
{ctx.list2.map((item: any, index: number) => {
return (
<button
class={{ "is-active": item.active }}
key={index}
onClick={(event) => {
ctx.selectItem(event, item);
}}>
<span>{item.label}</span>
</button>
);
})}
</div>
);
}
});

View File

@ -1,23 +0,0 @@
import { defineComponent, inject } from "vue";
import { Crud } from "../types";
export default defineComponent({
name: "cl-refresh-btn",
setup(props, { slots }) {
const crud = inject("crud") as Crud;
return () => {
return (
<el-button
size="mini"
onClick={() => {
crud.refresh();
}}
{...props}>
{slots.default ? slots.default() : crud.dict.label.refresh}
</el-button>
);
};
}
});

View File

@ -1,129 +0,0 @@
import { defineComponent, inject, ref } from "vue";
import { Crud } from "../types";
export default defineComponent({
name: "cl-search-key",
props: {
// 绑定值
modelValue: String,
// 选中字段
field: {
type: String,
default: "keyWord"
},
// 字段列表
fieldList: {
type: Array,
default: () => []
},
// 搜索时的钩子
onSearch: Function,
// 输入框占位内容
placeholder: {
type: String,
default: "请输入关键字"
}
},
emits: ["update:modelValue", "change", "field-change"],
setup(props, { emit }) {
const crud = inject("crud") as Crud;
// 选中字段
const selectField = ref<string>(props.field);
// 搜索内容
const value = ref<string>(props.modelValue || "");
// 搜索
function search() {
const params: any = {};
props.fieldList.forEach((e: any) => {
params[e.value] = null;
});
function next(newParams?: any) {
crud.refresh({
page: 1,
...params,
[selectField.value]: value.value,
...newParams
});
}
if (props.onSearch) {
props.onSearch(params, { next });
} else {
next();
}
}
// 回车搜索
function onKeydown({ keyCode }: any) {
if (keyCode === 13) {
search();
}
}
// 监听输入
function onInput(val: string) {
emit("update:modelValue", val);
emit("change", val);
}
// 监听字段选择
function onFieldChange() {
emit("field-change", selectField.value);
onInput("");
value.value = "";
}
return {
value,
selectField,
search,
onKeydown,
onInput,
onFieldChange
};
},
render(ctx: any) {
return (
<div class="cl-search-key">
<el-select
class="cl-search-key__select"
filterable
size="mini"
v-model={ctx.selectField}
v-show={ctx.fieldList.length > 0}
onChange={ctx.onFieldChange}>
{ctx.fieldList.map((e: any, i: number) => (
<el-option key={i} label={e.label} value={e.value} />
))}
</el-select>
<el-input
class="cl-search-key__input"
v-model={ctx.value}
placeholder={ctx.placeholder}
onKeydown={ctx.onKeydown}
onInput={ctx.onInput}
clearable
size="mini"
/>
<el-button
class="cl-search-key__button"
type="primary"
size="mini"
onClick={ctx.search}>
</el-button>
</div>
);
}
});

View File

@ -1,67 +0,0 @@
export function useElTableApi({ refs }: any) {
const clearSelection = () => {
if (refs.value.table) {
refs.value.table.clearSelection();
}
};
const toggleRowSelection = (row: any, selected?: boolean) => {
if (refs.value.table) {
refs.value.table.toggleRowSelection(row, selected);
}
};
const toggleAllSelection = () => {
if (refs.value.table) {
refs.value.table.toggleAllSelection();
}
};
const toggleRowExpansion = (row: any, expanded?: boolean) => {
if (refs.value.table) {
refs.value.table.toggleRowExpansion(row, expanded);
}
};
const setCurrentRow = (row: any) => {
if (refs.value.table) {
refs.value.table.setCurrentRow(row);
}
};
const clearSort = () => {
if (refs.value.table) {
refs.value.table.clearSort();
}
};
const clearFilter = (columnKey: any) => {
if (refs.value.table) {
refs.value.table.clearFilter(columnKey);
}
};
const doLayout = () => {
if (refs.value.table) {
refs.value.table.doLayout();
}
};
const sort = (prop: string, order: string) => {
if (refs.value.table) {
refs.value.table.sort(prop, order);
}
};
return {
clearSelection,
toggleRowSelection,
toggleAllSelection,
toggleRowExpansion,
setCurrentRow,
clearSort,
clearFilter,
doLayout,
sort
};
}

View File

@ -1,543 +0,0 @@
import { defineComponent, h, inject, nextTick, onMounted, ref } from "vue";
import type { PropType } from "vue";
import ContextMenu from "../context-menu/index";
import { useElTableApi } from "./helper";
import { cloneDeep, isArray, isEmpty, isFunction, isNull, isBoolean } from "../../utils";
import { renderNode } from "../../utils/vnode";
import { useRefs } from "../../hooks/core";
import { Crud, Mitt, Browser, TableColumn } from "../../types";
export default defineComponent({
name: "cl-table",
props: {
columns: {
type: Array as PropType<TableColumn[]>,
required: true,
default: () => []
},
on: {
type: Object,
default: () => {
return {};
}
},
props: {
type: Object,
default: () => {
return {};
}
},
height: Number,
// 是否自动计算表格高度
autoHeight: {
type: Boolean,
default: true
},
// 开启右键菜单
contextMenu: {
type: [Boolean, Array],
default: undefined
}
},
emits: ["selection-change"],
setup(props, { emit }) {
const { refs, setRefs }: any = useRefs();
// 参数注入
const mitt = inject("mitt") as Mitt;
const crud = inject("crud") as Crud;
// el-table api
const {
clearSelection,
toggleRowSelection,
toggleAllSelection,
toggleRowExpansion,
setCurrentRow,
clearSort,
clearFilter,
doLayout,
sort
} = useElTableApi({ refs });
// 是否可见,用于解决一些显示隐藏的副作用
const visible = ref<boolean>(true);
// 列表数据
const data = ref<Array<any>>([]);
// 最大高度
const maxHeight = ref<number>(0);
// 改变排序
function changeSort(prop: string, order: string) {
if (order === "desc") {
order = "descending";
}
if (order === "asc") {
order = "ascending";
}
sort(prop, order);
}
// 多选框选择
function onSelectionChange(selection: Array<any>) {
crud.selection.splice(0, crud.selection.length, ...selection);
emit("selection-change", selection);
}
// 排序监听
function onSortChange({ prop, order }: any) {
if (order === "descending") {
order = "desc";
}
if (order === "ascending") {
order = "asc";
}
if (!order) {
prop = null;
}
crud.refresh({
prop,
order,
page: 1
});
}
// 右键菜单
function onRowContextMenu(row: any, column: any, event: any) {
// 菜单配置
const cm: any =
isEmpty(props.contextMenu) && !isArray(props.contextMenu)
? crud.table!["context-menu"]
: props.contextMenu;
// 菜单按钮
let buttons = ["refresh", "check", "edit", "delete", "order-asc", "order-desc"];
// 是否开启
let enable = false;
if (cm) {
if (isArray(cm)) {
buttons = cm || [];
enable = Boolean(buttons.length > 0);
} else {
enable = true;
}
}
if (enable) {
// 解析按钮
const list = buttons
.map((e: any) => {
switch (e) {
case "refresh":
return {
label: "刷新",
callback(_: any, done: Function) {
crud.refresh();
done();
}
};
case "edit":
case "update":
return {
label: "编辑",
hidden: !crud.getPermission("update"),
callback(_: any, done: Function) {
crud.rowEdit(row);
done();
}
};
case "delete":
return {
label: "删除",
hidden: !crud.getPermission("delete"),
callback(_: any, done: Function) {
crud.rowDelete(row);
done();
}
};
case "check":
return {
label: crud.selection.find((e: any) => e.id == row.id)
? "取消选择"
: "选择",
hidden: !props.columns.find((e: any) => e.type === "selection"),
callback(_: any, done: Function) {
toggleRowSelection(row);
done();
}
};
case "order-desc":
return {
label: `${column.label} - 降序`,
hidden: !column.sortable,
callback(_: any, done: Function) {
changeSort(column.property, "desc");
done();
}
};
case "order-asc":
return {
label: `${column.label} - 升序`,
hidden: !column.sortable,
callback(_: any, done: Function) {
changeSort(column.property, "asc");
done();
}
};
default:
if (isFunction(e)) {
return e(row, column, event);
} else {
return e;
}
}
})
.filter((e) => Boolean(e) && !e.hidden);
// 打开菜单
if (list.length > 0) {
ContextMenu.open(event, {
list
});
}
}
// 回调
if (props.props.onRowContextmenu) {
props.props.onRowContextmenu(row, column, event);
}
}
// 计算表格最大高度
function calcMaxHeight() {
if (!props.autoHeight) {
return false;
}
nextTick(() => {
let vm: any = refs.value.table;
let h = 15;
// 获取表格元素
while (!vm.$parent?.$el.className.includes("cl-crud")) {
vm = vm.$parent;
}
// 获取表格上的高度
h += vm.$el.offsetTop;
// 获取表格下的高度
let n = vm.$el.nextSibling;
while (n && (n.className || "").includes("el-row")) {
h += n.clientHeight + 5;
n = n.nextSibling;
}
// 设置表格最大高度
maxHeight.value = crud.crudRef.clientHeight - h;
});
}
// 显示列
function showColumn(prop: string | string[], status?: boolean) {
const keys = isArray(prop) ? prop : [prop];
visible.value = false;
props.columns
.filter((e) => (e.prop ? keys.includes(e.prop) : false))
.forEach((e) => {
e.hidden = isBoolean(status) ? status : false;
});
nextTick(() => {
visible.value = true;
});
}
// 隐藏列
function hiddenColumn(prop: string | string[]) {
showColumn(prop, true);
}
// 监听事件
(function () {
// 刷新事件
mitt.on("crud.refresh", ({ list }: any) => {
data.value = list;
});
// 窗口大小改变事件
mitt.on("crud.resize", () => {
calcMaxHeight();
});
})();
// 设置请求参数
(function () {
const { order, prop } = props.props["default-sort"] || {};
if (order && prop) {
crud.params.order = order === "descending" ? "desc" : "asc";
crud.params.prop = prop;
}
})();
onMounted(function () {
calcMaxHeight();
});
return {
refs,
visible,
data,
maxHeight,
setRefs,
showColumn,
hiddenColumn,
onSelectionChange,
onSortChange,
onRowContextMenu,
clearSelection,
toggleRowSelection,
toggleAllSelection,
toggleRowExpansion,
setCurrentRow,
clearSort,
clearFilter,
doLayout,
sort
};
},
render(ctx: any) {
const crud = inject("crud") as Crud;
const browser = inject("browser") as Browser;
// 渲染列
const renderColumn = () => {
return ctx.columns
.filter((e: any) => !e.hidden)
.map((item: any, index: number) => {
const ElTableColumn = (
<el-table-column
key={`crud-table-column-${index}`}
align="center"></el-table-column>
);
// 操作按钮
if (item.type === "op") {
return h(
ElTableColumn,
{
label: "操作",
width: "160px",
fixed: browser.isMini ? null : "right",
...item
},
{
default: (scope: any) => {
return (
<div class="cl-table__op">
{(item.buttons || ["edit", "delete"]).map(
(vnode: any) => {
if (vnode === "edit") {
return (
<el-button
size="mini"
type="text"
v-show={crud.getPermission(
"update"
)}
onClick={() => {
crud.rowEdit(scope.row);
}}>
{crud.dict.label.update}
</el-button>
);
} else if (vnode === "delete") {
return (
<el-button
size="mini"
type="text"
v-show={crud.getPermission(
"delete"
)}
onClick={() => {
crud.rowDelete(scope.row);
}}>
{crud.dict.label.delete}
</el-button>
);
} else {
return renderNode(vnode, {
scope,
slots: ctx.$slots
});
}
}
)}
</div>
);
}
}
);
}
// 多选,序号
else if (["selection", "index"].includes(item.type)) {
return h(ElTableColumn, item);
}
// 默认
else {
const deep = (item: any) => {
const props = cloneDeep(item);
// Cannot set property children of #<Element>
delete props.children;
return h(ElTableColumn, props, {
header: (scope: any) => {
const slot = ctx.$slots[`header-${item.prop}`];
if (slot) {
return slot({
scope
});
} else {
return scope.column.label;
}
},
default: (scope: any) => {
if (item.children) {
return <div>{item.children.map(deep)}</div>;
}
// Scope data
const newScope = {
...scope,
...item
};
// 绑定值
const value = scope.row[item.prop];
// 使用插槽
const slot = ctx.$slots[`column-${item.prop}`];
if (slot) {
return slot({
scope: newScope
});
} else {
// 判断是否自定义渲染
if (item.component) {
return renderNode(item.component, {
prop: item.prop,
scope: newScope.row
});
}
// Formatter
else if (item.formatter) {
return item.formatter(
newScope.row,
newScope.column,
newScope.row[item.prop],
newScope.$index
);
}
// 字典状态
else if (item.dict) {
const data = item.dict.find(
(d: any) => d.value == value
);
if (data) {
const ElTag = (
<el-tag
disable-transitions
size="small"
effect="dark"></el-tag>
);
// Use el-tag
return h(ElTag, data, {
default() {
return data.label;
}
});
} else {
return value;
}
}
// Empty text
else if (isNull(value)) {
return scope.emptyText;
}
// Value
else {
return value;
}
}
}
});
};
return deep(item);
}
});
};
const ElTable = (
<el-table
class="cl-table"
ref={ctx.setRefs("table")}
border
size="mini"
v-loading={crud.loading}
data={ctx.data}></el-table>
);
return ctx.visible
? h(
ElTable,
{
onSortChange: ctx.onSortChange,
maxHeight: ctx.autoHeight ? ctx.maxHeight : null,
...ctx.props,
onSelectionChange: ctx.onSelectionChange,
onRowContextmenu: ctx.onRowContextMenu
},
{
default() {
return renderColumn();
},
empty() {
return (
<div class="cl-table__empty">
{ctx.$slots.empty && ctx.$slots.empty()}
</div>
);
},
append() {
return (
<div class="cl-table__append">
{ctx.$slots.append && ctx.$slots.append()}
</div>
);
}
}
)
: null;
}
});

View File

@ -1,26 +0,0 @@
export function useFormApi({ refs }: any) {
const apis: any = {};
[
"showLoading",
"hiddenLoading",
"collapseItem",
"getForm",
"setForm",
"setData",
"setOptions",
"toggleItem",
"hiddenItem",
"showItem",
"resetFields",
"clearValidate",
"validateField",
"validate"
].forEach((e) => {
apis[e] = (...args: any[]) => {
return refs.value.form[e](...args);
};
});
return apis;
}

View File

@ -1,293 +0,0 @@
import { ElMessage } from "element-plus";
import { defineComponent, h, inject, ref } from "vue";
import type { PropType } from "vue";
import { useFormApi } from "./helper";
import { useForm, useRefs } from "../../hooks/core";
import { Crud, UpsertItem } from "../../types";
export default defineComponent({
name: "cl-upsert",
props: {
// 绑定值
modelValue: {
type: Object,
default: () => {
return {};
}
},
// 表单项
items: {
type: Array as PropType<UpsertItem[]>,
default: () => []
},
// el-form 参数
props: {
type: Object,
default: () => {
return {};
}
},
// 编辑时是否同步打开
sync: Boolean,
// 操作按钮参数 { hidden, saveButtonText, closeButtonText, buttons }
op: Object,
// cl-dialog 参数 { props, hiddenControls, controls }
dialog: Object,
// 打开表单钩子 { isEdit, data, { submit, done, close } }
onOpen: Function,
// 关闭表单钩子 { done }
onClose: Function,
// 获取表单数据钩子 { data, { next, done, close } }
onInfo: Function,
// 表单提交钩子 { isEdit, data, { next, done, close } }
onSubmit: Function
},
emits: ["open", "close"],
setup(props, { emit }) {
const { refs, setRefs }: any = useRefs();
const { setFormData } = useForm(props);
// 参数注入
const mitt = inject<any>("mitt");
const crud = inject("crud") as Crud;
// 表单数据
const form: any = setFormData();
// 是否编辑
const isEdit = ref<boolean>(false);
// 关闭表单
function close() {
refs.value.form.close();
emit("close");
}
// 表单关闭前
function beforeClose() {
if (props.onClose) {
props.onClose(close);
} else {
close();
}
}
// 提交
function submit(data: any, event?: any) {
function done() {
return event ? event.done() : refs.value.form.done();
}
async function next(data: any) {
// 获取请求方法
const reqName: string = crud.dict.api[isEdit.value ? "update" : "add"];
// 验证请求
if (!crud.service[reqName]) {
done();
return Promise.reject(`Request function '${reqName}' is not fount!`);
}
// 发送请求
await crud.service[reqName](data)
.then((res: any) => {
ElMessage.success("保存成功");
close();
crud.refresh();
return res;
})
.catch((err: string) => {
ElMessage.error(err);
Promise.reject(err);
});
// 请求完成
done();
}
// 提交钩子
if (props.onSubmit) {
props.onSubmit(isEdit.value, data, {
done,
next,
close
});
} else {
next(data);
}
}
// 打开表单
function open() {
return new Promise((resolve) => {
if (!refs.value.form) {
return false;
}
refs.value.form.open({
title: isEdit.value ? "编辑" : "新增",
props: props.props,
items: props.items,
op: props.op,
dialog: props.dialog,
on: {
open: (_: any, { done, close }: any) => {
if (props.onOpen) {
props.onOpen(isEdit, form, {
submit: () => {
submit(form);
},
done,
close
});
}
resolve(true);
},
submit,
close: beforeClose
},
_data: {
isEdit: isEdit
}
});
});
}
// 新增
async function add() {
isEdit.value = false;
await open();
emit("open", false, {});
}
// 追加
async function append(data: any) {
isEdit.value = false;
await open();
if (data) {
Object.assign(form, data);
}
emit("open", false, form);
}
// 编辑
function edit(data?: any) {
if (!refs.value.form) {
return false;
}
const { showLoading, hiddenLoading } = refs.value.form;
// 设置为编辑
isEdit.value = true;
// 显示加载中
showLoading();
// 是否同步打开
if (!props.sync) {
open();
}
const done = (data?: any) => {
// 加载完成
hiddenLoading();
// 合并数据
if (data) {
refs.value.form.reBindForm(data);
}
};
// 关闭表单
const close = () => {
hiddenLoading();
close();
};
// 获取详情
const next = async (data: any) => {
// 获取请求名称
const reqName: any = crud.dict.api.info;
// 验证请求
if (!crud.service[reqName]) {
done();
return Promise.reject(`Request function '${reqName}' is not fount!`);
}
// 发送请求
await crud.service[reqName]({
id: data.id
})
.then((res: any) => {
done(res);
// 同步打开表单
if (props.sync) {
open();
}
emit("open", isEdit.value, form);
return res;
})
.catch((err: string) => {
ElMessage.error(err);
return Promise.reject(err);
});
// 隐藏加载框
hiddenLoading();
};
// 获取详情钩子
if (props.onInfo) {
props.onInfo(data, {
next,
done: (data: any) => {
done(data);
emit("open", true, form);
},
close
});
} else {
next(data);
}
}
// 消息事件
(function () {
mitt.on("crud.add", add);
mitt.on("crud.append", append);
mitt.on("crud.edit", edit);
mitt.on("crud.close", close);
})();
return {
refs,
setRefs,
form,
isEdit,
add,
append,
edit,
close,
...useFormApi({ refs })
};
},
render(ctx: any) {
return (
<div class="cl-upsert">
{h(
<cl-form ref={ctx.setRefs("form")} v-model={ctx.form}></cl-form>,
{},
ctx.$slots
)}
</div>
);
}
});

View File

@ -1,41 +0,0 @@
import { ref, onBeforeUpdate, watch, reactive } from "vue";
import { isEmpty } from "../utils";
export function useForm(props: any) {
const setFormData = () => {
const data = reactive<any>(props.modelValue || {});
watch(
() => props.modelValue,
(val: any) => {
for (const i in data) {
if (isEmpty(val[i])) {
delete data[i];
} else {
data[i] = val[i];
}
}
}
);
return data;
};
return {
setFormData
};
}
export function useRefs() {
const refs = ref<HTMLElement[]>([]);
onBeforeUpdate(() => {
refs.value = [];
});
const setRefs = (index: number) => (el: HTMLElement) => {
refs.value[index] = el;
};
return { refs, setRefs };
}

View File

@ -1,74 +0,0 @@
import { isArray, isFunction, isObject, isString } from "../utils";
export const format: any = {
number(value: any) {
return isArray(value) ? value.map(Number) : Number(value);
},
string(value: any) {
return isArray(value) ? value.map(String) : String(value);
},
split(value: any, separator = ",") {
return value.split(separator);
},
join(value: any, separator = ",") {
return value.join(separator);
},
boolean(value: any) {
return Boolean(value);
},
booleanNumber(value: any) {
return Boolean(value) ? 1 : 0;
}
};
function parse(method: string, { value, pipe, form }: any) {
if (value === undefined) {
return value;
}
if (!pipe) {
return value;
}
let pipes = [];
if (isString(pipe)) {
if (format[pipe]) {
pipes = [pipe];
} else {
console.error(`${pipe} is not found.`);
return value;
}
} else if (isArray(pipe)) {
pipes = pipe;
} else if (isObject(pipe)) {
pipes = isArray(pipe[method]) ? pipe[method] : [pipe[method]];
} else if (isFunction(pipe)) {
pipes = [pipe];
} else {
console.error(`Hook data error!`);
return value;
}
let d = value;
pipes.forEach((e: any) => {
if (isString(e)) {
d = format[e](d);
} else if (isFunction(e)) {
d = e(d, form);
}
});
return d;
}
export default {
bind(value: any, pipe: any, form: any) {
return parse("bind", { value, pipe, form });
},
submit(value: any, pipe: any, form: any) {
return parse("submit", { value, pipe, form });
}
};

View File

@ -1,58 +0,0 @@
import { reactive } from "vue";
import * as components from "./components";
import ContextMenu from "./components/context-menu/index";
import "./static/index.scss";
const CRUD = {
install(app: any, options: any = {}) {
app.provide("__crud", options.crud);
// 获取浏览器信息
(function () {
const browser = reactive<any>({
isMini: false,
screen: "full"
});
function resize() {
const w = document.body.clientWidth;
if (w < 768) {
browser.screen = "xs";
} else if (w < 992) {
browser.screen = "sm";
} else if (w < 1200) {
browser.screen = "md";
} else if (w < 1920) {
browser.screen = "xl";
} else {
browser.screen = "full";
}
browser.isMini = browser.screen === "xs";
}
window.addEventListener("resize", resize);
resize();
app.provide("browser", browser);
})();
// 设置组件
for (const i in components) {
// @ts-ignore
app.component(components[i].name, components[i]);
}
app.config.globalProperties.$crud = {
openContextMenu: ContextMenu.open
};
return {};
}
};
export default CRUD;
export { ContextMenu, CRUD };

View File

@ -1,596 +0,0 @@
.cl-crud {
display: flex;
flex-direction: column;
height: 100%;
position: relative;
padding: 10px;
box-sizing: border-box;
background-color: #fff;
overflow: hidden;
&.is-border {
border: 1px solid #eee;
}
.el-input-number {
&__decrease,
&__increase {
border: 0;
background-color: transparent;
}
}
& > .el-row {
overflow-x: auto;
overflow-y: hidden;
flex-wrap: nowrap;
padding-bottom: 5px;
margin-bottom: 0;
min-height: 33px;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(144, 147, 153, 0.3);
border-radius: 5px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
& + .el-row {
margin-top: 5px;
}
}
}
.cl-flex1 {
flex: 1;
font-size: 12px;
}
.cl-search-key {
display: flex;
margin-left: 10px;
&__select {
margin-right: 10px;
.el-input__inner {
width: 120px;
}
}
&__input {
.el-input__inner {
width: 250px;
}
}
&__button {
&.el-button {
margin-left: 10px;
}
}
}
.cl-adv-btn {
& > .el-button {
margin-left: 10px;
i {
margin-right: 5px;
}
}
}
.cl-table {
width: 100%;
.el-table {
.el-loading-mask {
.el-loading-spinner {
.el-icon-loading {
font-size: 25px;
color: #000;
}
.el-loading-text {
color: #666;
margin-top: 5px;
}
}
}
&.el-loading-parent--relative {
box-sizing: border-box;
}
}
}
.cl-crud__op-dropdown-menu {
.el-dropdown-menu__item {
height: 30px;
line-height: 30px;
padding: 0;
}
.el-button {
height: 30px;
width: 100%;
padding: 0 20px;
text-align: left;
box-sizing: border-box;
}
}
.cl-query {
display: inline-flex;
margin: 0 10px;
border-radius: 3px;
button {
border: 0;
background-color: #fff;
font-size: 12px;
outline: none;
cursor: pointer;
color: #666;
white-space: nowrap;
&:hover {
color: #6fa8ff;
}
&.is-active {
color: #409eff;
}
span {
display: inline-block;
padding: 0 15px;
border-right: 1px solid #ddd;
}
&:last-child {
span {
border: 0;
}
}
}
}
.cl-filter {
display: flex;
align-items: center;
margin: 0 10px;
&__label {
font-size: 12px;
margin-right: 10px;
white-space: nowrap;
}
.el-select {
min-width: 120px;
}
}
.cl-filter-group {
display: inline-flex;
white-space: nowrap;
margin: 0 10px;
&__items {
display: inline-flex;
}
}
.cl-adv-search {
.el-drawer {
&__header {
margin-bottom: 20px;
}
&__body {
height: calc(100% - 63px);
padding: 0 5px;
}
}
&__container {
height: calc(100% - 55px);
overflow-y: auto;
padding: 10px 20px;
margin-bottom: 5px;
box-sizing: border-box;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
border-radius: 6px;
background-color: rgba(144, 147, 153, 0.3);
}
.el-form-item__content {
& > div {
width: 100%;
}
}
}
&__footer {
display: flex;
align-items: center;
justify-content: flex-end;
height: 50px;
border-top: 1px solid #ebeef5;
}
.el-drawer {
outline: none;
&__header {
span {
outline: none;
font-size: 15px;
}
}
&__close-btn {
outline: none;
}
}
}
.cl-form {
.el-form-item {
.el-input-number {
&__decrease,
&__increase {
border: 0;
background-color: transparent;
}
}
&__label {
.el-tooltip {
i {
margin-left: 5px;
}
}
}
}
&-item {
display: flex;
&__prepend {
margin-right: 10px;
}
&__component {
&.is-flex {
flex: 1;
width: 100%;
& > div {
width: 100%;
}
}
}
&__append {
margin-left: 10px;
}
&__collapse {
width: 100%;
font-size: 12px;
cursor: pointer;
.el-divider {
margin: 16px 0;
&__text {
font-size: 12px;
}
}
i {
margin-left: 6px;
}
}
}
&__footer {
display: flex;
justify-content: flex-end;
}
}
.cl-form-tabs {
margin-bottom: 20px;
border-bottom: 1px solid #ccc;
overflow: hidden;
width: 100%;
ul {
height: 35px;
width: 100%;
overflow-x: auto;
white-space: nowrap;
position: relative;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
li {
display: inline-block;
list-style: none;
padding: 0 20px;
height: 35px;
line-height: 35px;
cursor: pointer;
}
}
&__line {
height: 2px;
width: 100%;
position: absolute;
bottom: -1px;
left: 0;
transition: transform 0.3s ease-in-out, width 0.2s 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
}
}
.cl-dialog {
margin-top: 0px !important;
.el-dialog {
&__header {
padding: 10px !important;
text-align: center;
border-bottom: 1px solid #f7f7f7;
.el-dialog__title {
font-size: 15px;
letter-spacing: 0.5px;
}
.el-dialog__headerbtn {
display: none;
top: 13px;
.el-dialog__close {
font-size: 18px;
}
}
&-slot {
&.is-drag {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
user-select: none;
cursor: move;
}
}
}
&__body {
padding: 20px;
}
&__footer {
padding: 0;
}
}
&__header {
height: 25px;
line-height: 25px;
text-align: center;
position: relative;
}
&__title {
display: block;
font-size: 15px;
letter-spacing: 0.5px;
}
&__controls {
display: flex;
justify-content: flex-end;
position: absolute;
right: 0;
top: 0;
z-index: 9;
width: 100%;
button,
.minimize,
.maximize,
.close {
display: flex;
align-items: center;
justify-content: center;
height: 25px;
width: 40px;
border: 0;
background-color: #fff;
cursor: pointer;
outline: none;
i {
font-size: 16px;
&:hover {
opacity: 0.7;
}
}
}
}
&.hidden-header {
.el-dialog__header {
display: none;
}
}
}
.cl-context-menu {
position: absolute;
z-index: 9999;
&__box {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
width: 160px;
padding: 5px 0;
background-color: #fff;
border-radius: 3px;
position: absolute;
top: 0;
&.is-append {
right: calc(-100% - 5px);
top: -5px;
}
& > div {
display: flex;
align-items: center;
height: 35px;
font-size: 13px;
cursor: pointer;
padding: 0 15px;
color: #666;
position: relative;
span {
height: 35px;
line-height: 35px;
flex: 1;
}
&:hover {
background-color: #f7f7f7;
color: #000;
}
i {
&:first-child {
margin-right: 5px;
}
&:last-child {
margin-left: 5px;
}
}
&.is-active {
background-color: #f7f7f7;
color: #000;
}
&.is-ellipsis {
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&.is-disabled {
span {
color: #ccc;
&:hover {
color: #ccc;
}
}
}
}
}
}
// Element-ui Theme
.el-message {
&.el-message--success,
&.el-message--error,
&.el-message--info,
&.el-message--warning {
min-width: auto;
background-color: #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border: 0;
padding: 12px 20px 12px 15px;
.el-message__content {
color: #999;
}
}
}
.el-table {
table {
th {
background-color: #ebeef5;
padding: 3px 0;
font-size: 14px;
}
.cell {
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
}
}
&__column {
&-filter-trigger {
margin-left: 5px;
}
}
&-column--selection {
.cell {
padding: 0 14px;
}
}
}
.el-table-filter {
margin-top: 5px !important;
.el-checkbox__label {
font-size: 12px;
}
}
@media only screen and (max-width: 768px) {
.el-message-box {
width: 90% !important;
}
.el-table {
&__body {
&-wrapper {
&::-webkit-scrollbar {
height: 6px;
width: 6px;
}
}
}
}
}

View File

@ -1,3 +0,0 @@
import { FormItem } from "./form";
export declare type AdvSearchItem = FormItem;

View File

@ -1,4 +0,0 @@
export declare type Browser = {
screen: string;
isMini: boolean;
};

View File

@ -1,15 +0,0 @@
export declare interface ContextMenuItem {
label: string;
"prefix-icon"?: string;
"suffix-icon"?: string;
ellipsis?: boolean;
disabled?: boolean;
hidden?: boolean;
children?: Array<ContextMenuItem>;
showChildren?: boolean;
callback?(item: ContextMenuItem, done: Function): void;
}
export declare interface ContextMenuOptions {
list?: ContextMenuItem[];
}

View File

@ -1,108 +0,0 @@
import { TableOptions } from "./table";
export declare type ServiceName = "page" | "list" | "add" | "delete" | "update" | "info" | string;
export declare interface Service {
page?(
params?: any
): Promise<{
list: any[];
pagination?: {
total?: number;
page?: number;
size?: number;
};
}>;
list?(params?: any): Promise<any[]>;
add?(params: any): Promise<any>;
delete?(params: any): Promise<any>;
update?(params: any): Promise<any>;
info?(params: any): Promise<any>;
}
export declare interface Dict {
api: {
list: string;
add: string;
update: string;
delete: string;
info: string;
page: string;
};
pagination: {
page: string;
size: string;
};
search: {
keyWord: string;
query: string;
};
sort: {
order: string;
prop: string;
};
label: {
add: string;
delete: string;
multiDelete: string;
update: string;
refresh: string;
advSearch: string;
saveButtonText: string;
closeButtonText: string;
};
}
export declare interface Permission {
page?: boolean;
list?: boolean;
add?: boolean;
delete?: boolean;
update?: boolean;
info?: boolean;
}
declare interface LoadCtx {
service(s: Service): LoadCtx;
permission(p: Function | any): LoadCtx;
set(key: "dict" | "style", value: any): LoadCtx;
done(): void;
}
declare interface LoadApp {
refresh(params?: any): Promise<any>;
}
export declare interface CrudLoad {
app: LoadApp;
ctx: LoadCtx;
}
export declare interface CrudRef {
getPermission(key?: string): boolean;
rowAdd(): any;
rowEdit(data: any): any;
rowAppend(data?: any): any;
rowClose(): any;
openAdvSearch(): any;
closeAdvSearch(): any;
rowDelete(...selection: any[]): void;
refresh(params?: any): void;
}
export declare interface Crud extends CrudRef {
crudRef: any;
permission: Permission;
service: any;
dict: Dict;
params: any;
table?: TableOptions;
selection: any[];
loading: boolean;
}
export declare interface Mitt {
on(name: string, ...args: any[]): void;
emit(name: string, ...args: any[]): void;
off(name: string, ...args: any[]): void;
}

View File

@ -1,84 +0,0 @@
import { FormHook } from "./hook";
import { RenderOptions } from "./render";
export declare interface FormItem {
type?: "tabs" | string;
prop?: string;
props?: {
labels?: Array<{ label: string; value: string }>;
labelWidth?: string;
error?: string;
showMessage?: boolean;
inlineMessage?: boolean;
size?: "medium" | "small" | "mini";
};
hook?: FormHook;
group?: string;
collapse?: boolean;
value?: any;
label?: string | { text?: string; icon?: string; tip?: string };
span?: number;
flex?: boolean;
hidden?: Function | boolean | string;
prepend?: RenderOptions;
component?: RenderOptions;
append?: RenderOptions;
rules?: any;
}
declare interface FormOpenEvent {
close(): void;
submit(): void;
done(): void;
}
declare interface FormSubmitEvent {
done(): void;
close(): void;
}
export declare interface Form {
title?: string;
width?: string;
props?: any;
items: Array<FormItem>;
form?: any;
on?: {
open?(form: any, event: FormOpenEvent): void;
close?(done: Function): void;
submit?(data: any, event: FormSubmitEvent): void;
};
op?: {
hidden?: boolean;
saveButtonText?: string;
closeButtonText?: string;
buttons?: Array<"close" | "save">;
};
dialog?: {
props?: any;
hiddenControls?: boolean;
controls?: Array<"fullscreen" | "close">;
};
_data?: any;
}
export declare interface FormRef {
create(options: Form): FormRef;
open(options: Form): FormRef;
close(): void;
done(): void;
clear(): void;
showLoading(): void;
hiddenLoading(): void;
setData(): void;
setOptions(prop: string, list: Array<{ label: string; value?: any }>): void;
getForm(prop?: string): any;
setForm(prop: string, value: any): void;
toggleItem(prop: string, flag?: boolean): void;
hiddenItem(props: string[]): void;
showItem(props: string[]): void;
resetFields(): void;
clearValidate(props: string[] | string): void;
validateField(props: string[] | string, callback: Function): void;
validate(callback: Function): void;
}

View File

@ -1,14 +0,0 @@
export declare type Pipe =
| "number"
| "string"
| "split"
| "join"
| "boolean"
| "booleanNumber"
| Function
| Array<Pipe>;
export declare interface FormHook {
bind?: Pipe;
submit?: Pipe;
}

View File

@ -1,10 +0,0 @@
export * from "./crud";
export * from "./table";
export * from "./context-menu";
export * from "./form";
export * from "./upsert";
export * from "./adv-search";
export * from "./query";
export * from "./op";
export * from "./browser";
export * from "./hook";

View File

@ -1,111 +0,0 @@
export declare interface RefreshOp {
/**
*
* @param list
* @param pagination
*/
render(list: any[], pagination?: { size?: number; page?: number; total?: number }): void;
/**
*
* @param params
*/
next(params?: any): Promise<any>;
/**
*
*/
done(): void;
}
export declare interface DeleteOp {
next(params?: any): Promise<any>;
}
export declare interface UpsertOpenOp {
/**
*
* @param form
*/
submit(form: any): void;
/**
*
*/
done(): void;
/**
*
*/
close(): void;
}
export declare interface UpsertCloseOp {
/**
*
*/
done(): void;
}
export declare interface UpsertInfoOp {
/**
*
* @param params
*/
next(params: any): Promise<any>;
/**
*
*/
done(data: any): void;
/**
*
*/
close(): void;
}
export declare interface UpsertSubmitOp {
/**
*
* @param params
*/
next(params: any): Promise<any>;
/**
*
*/
done(): void;
/**
*
*/
close(): void;
}
export declare interface AdvOpenOp {
/**
*
* @param data
*/
next(data: any): Promise<any>;
}
export declare interface AdvCloseOp {
/**
*
*/
done(): void;
}
export declare interface AdvSearchOp {
/**
*
* @param params
*/
next(params: any): Promise<any>;
/**
*
*/
done(): void;
}

View File

@ -1,4 +0,0 @@
export declare interface QueryList {
label: string;
value: any;
}

View File

@ -1,12 +0,0 @@
import { ComponentOptions } from "vue";
export declare interface Options extends ComponentOptions {
name: string;
options?: Array<{
label: string;
value?: any;
}>;
}
export declare type RenderOptions = Options | Function;

View File

@ -1,66 +0,0 @@
import { ContextMenuItem } from "./context-menu";
import { RenderOptions } from "./render";
export declare interface TableOptions {
"context-menu"?:
| boolean
| Array<
| ContextMenuItem
| Function
| "refresh"
| "check"
| "update"
| "delete"
| "order-desc"
| "order-asc"
>;
}
export declare interface TableColumn {
value?: any;
type?: "index" | "selection" | "expand" | "op";
hidden?: boolean | (({ scope }: any) => boolean);
component?: RenderOptions;
dict?: Array<{
label: string;
value?: any;
type?: "primary" | "success" | "warning" | "info" | "danger";
}>;
buttons?: Array<"edit" | "delete" | string | RenderOptions>;
align?: "left" | "center" | "right";
label?: string;
className?: string;
prop?: string;
width?: number;
minWidth?: number | string;
renderHeader?: Function;
sortable?: boolean | string;
sortMethod?: Function;
sortBy?: string | Function | unknown[];
resizable?: {
type: boolean;
default: true;
};
columnKey?: string;
headerAlign?: string;
showOverflowTooltip?: boolean;
fixed?: boolean | string;
formatter?: Function;
selectable?: Function;
reserveSelection?: boolean;
filterMethod?: Function;
filteredValue?: unknown[];
filters?: unknown[];
filterPlacement?: string;
filterMultiple?: {
type: boolean;
default: true;
};
index?: number | Function;
sortOrders?: unknown[];
}
export declare interface Table extends TableOptions {
props?: {};
columns: TableColumn[];
}

View File

@ -1,7 +0,0 @@
import { Form, FormItem, FormRef } from "./form";
export declare type UpsertItem = FormItem;
export declare type UpsertRef = FormRef;
export declare type Upsert = Form;

View File

@ -1,115 +0,0 @@
import cloneDeep from "clone-deep";
import flat from "array.prototype.flat";
import merge from "merge";
export function isArray(value: any) {
if (typeof Array.isArray === "function") {
return Array.isArray(value);
} else {
return Object.prototype.toString.call(value) === "[object Array]";
}
}
export function isObject(value: any) {
return Object.prototype.toString.call(value) === "[object Object]";
}
export function isNumber(value: any) {
return !isNaN(Number(value));
}
export function isFunction(value: any) {
return typeof value === "function";
}
export function isString(value: any) {
return typeof value === "string";
}
export function isNull(value: any) {
return !value && value !== 0;
}
export function isBoolean(value: any) {
return typeof value === "boolean";
}
export function isEmpty(value: any) {
if (isArray(value)) {
return value.length === 0;
}
if (isObject(value)) {
return Object.keys(value).length === 0;
}
return value === "" || value === undefined || value === null;
}
export function clone(obj: any) {
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
}
export function dataset(obj: any, key: string, value: any) {
const isGet = value === undefined;
let d = obj;
const arr = flat(
key.split(".").map((e) => {
if (e.includes("[")) {
return e.split("[").map((e) => e.replace(/"/g, ""));
} else {
return e;
}
})
);
try {
for (let i = 0; i < arr.length; i++) {
const e: any = arr[i];
let n: any = null;
if (e.includes("]")) {
const [k, v] = e.replace("]", "").split(":");
if (v) {
n = d.findIndex((x: any) => x[k] == v);
} else {
n = Number(n);
}
} else {
n = e;
}
if (i != arr.length - 1) {
d = d[n];
} else {
if (isGet) {
return d[n];
} else {
d[n] = value;
}
}
}
return obj;
} catch (e) {
console.error("格式错误", `${key}`);
return {};
}
}
export function contains(parent: any, node: any) {
return parent !== node && parent && parent.contains(node);
}
export function deepMerge(a: any, b: any) {
let k;
for (k in b) {
a[k] =
a[k] && a[k].toString() === "[object Object]" ? deepMerge(a[k], b[k]) : (a[k] = b[k]);
}
return a;
}
export { cloneDeep, flat, merge };

View File

@ -1,29 +0,0 @@
import mitt from "mitt";
const emitter: any = mitt();
class Emitter {
id: number;
constructor(id?: number) {
this.id = id || 0;
}
send(type: string, name: string, ...args: any[]) {
emitter[type](`${this.id}__${name}`, ...args);
}
on(name: string, ...args: any[]) {
this.send("on", name, ...args);
}
emit(name: string, ...args: any[]) {
this.send("emit", name, ...args);
}
off(name: string, ...args: any[]) {
this.send("off", name, ...args);
}
}
export default Emitter;

View File

@ -1,33 +0,0 @@
import { isString, isBoolean, isFunction } from "./index";
/**
* parse hidden
* 1 Boolean
* 2 Function({ scope })
* 3 :[prop] is bind form[prop] value
* @param {*} value
*/
export default function (method: string, { value, scope, data = {} }: any) {
if (data) {
data.isAdd = !data.isEdit;
}
if (method === "hidden") {
if (isBoolean(value)) {
return value;
} else if (isString(value)) {
const prop = value.substring(1, value.length);
switch (value[0]) {
case "@":
return !scope[prop];
case ":":
return data[prop];
}
} else if (isFunction(value)) {
return value({ scope, ...data });
}
return false;
}
}

View File

@ -1,153 +0,0 @@
// @ts-nocheck
import { h, resolveComponent, toRaw } from "vue";
import { isFunction, isString, isObject } from "./index";
const Regs: string[] = [];
/**
*
* @param {*} vnode
* @param {{scope,prop,children}} options
*/
function parseNode(vnode: any, options: any) {
const { scope, prop, slots } = options || [];
// 插槽模式渲染
if (vnode.name.indexOf("slot-") == 0) {
const rn = slots[vnode.name];
if (rn) {
return rn({ scope });
} else {
return <cl-error-message title={`组件渲染失败,未找到插槽:${vnode.name}`} />;
}
}
// 是否全局注册
let isReg: boolean = Regs.includes(vnode.name);
// 实例模式下,先注册到全局,再分解组件渲染
if (vnode.__file && !isReg) {
window.__app__.component(vnode.name, vnode);
isReg = true;
Regs.push(vnode.name);
}
// 组件参数
const props = {
...vnode.props,
...vnode,
...vnode.attrs,
scope
};
// 删除多余数据
delete props._children;
// 添加双向绑定
if (props && scope) {
props.modelValue = scope[prop];
props["onUpdate:modelValue"] = function (val: any) {
scope[prop] = val;
};
}
// 组件实例渲染
if (props.render && !isReg) {
return h(props, props);
}
return h(toRaw(resolveComponent(vnode.name)), props, {
default: () => {
return vnode._children;
}
});
}
/**
*
* @param {*} vnode
* @param {*} options
*/
export function renderNode(vnode: any, { prop, scope, slots }: any) {
if (!vnode) {
return null;
}
if (vnode.__v_isVNode) {
return vnode;
}
// 组件名渲染
if (isString(vnode)) {
return parseNode({ name: vnode }, { scope, slots });
}
// 方法回调
if (isFunction(vnode)) {
return vnode({ scope, h });
}
// jsx 模式
if (isObject(vnode)) {
if (vnode.name) {
// 扩展组件
const keys = ["el-select", "el-radio-group", "el-checkbox-group"];
if (keys.includes(vnode.name)) {
// 设置内容
vnode._children = (
<div>
{(vnode.options || []).map((e: any, i: number) => {
// 下拉框
if (vnode.name == "el-select") {
let label: any, value: any;
if (isString(e)) {
label = value = e;
} else if (isObject(e)) {
label = e.label;
value = e.value;
} else {
return (
<cl-error-message
title={`组件渲染失败options 参数错误`}
/>
);
}
return (
<el-option key={i} label={label} value={value} {...e.props} />
);
}
// 单选框组
else if (vnode.name == "el-radio-group") {
return h(<el-radio key={i} label={e.value}></el-radio>, e.props, {
default() {
return <span>{e.label}</span>;
}
});
}
// 多选框组
else if (vnode.name == "el-checkbox-group") {
return (
<el-checkbox key={i} label={e.value} {...e.props}>
{e.label}
</el-checkbox>
);
} else {
return null;
}
})}
</div>
);
return parseNode(vnode, { prop, scope });
} else {
return parseNode(vnode, { prop, scope, slots });
}
} else {
return <cl-error-message title={`组件渲染失败,组件 name 不能为空`} />;
}
}
}

View File

@ -6,7 +6,7 @@
</template>
<script lang="ts">
import { AdvSearchItem } from "/$/crud/types";
import { AdvSearchItem } from "cl-admin-crud-vue3/types";
import { defineComponent, ref } from "vue";
export default defineComponent({

View File

@ -5,7 +5,7 @@
</template>
<script lang="ts">
import { ContextMenu } from "/$/crud";
import { ContextMenu } from "cl-admin-crud-vue3";
import { ElMessage } from "element-plus";
import { defineComponent } from "vue";

View File

@ -50,7 +50,7 @@
<script lang="ts">
import { defineComponent, ref, resolveComponent, h } from "vue";
import { CrudLoad, FormItem, FormRef } from "/$/crud/types";
import { CrudLoad, FormItem, FormRef } from "cl-admin-crud-vue3/types";
import { TestService } from "../../utils/service";
import Test from "./render/test.vue";
import Test2 from "./render/test2";

View File

@ -3,7 +3,7 @@
</template>
<script lang="ts">
import { QueryList } from "/$/crud/types";
import { QueryList } from "cl-admin-crud-vue3/types";
import { defineComponent, ref } from "vue";
export default defineComponent({

View File

@ -6,7 +6,7 @@
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
import { TableColumn } from "/$/crud/types";
import { TableColumn } from "cl-admin-crud-vue3/types";
import { useRefs } from "/@/core";
import Test2 from "./render/test2";

View File

@ -5,7 +5,7 @@
</template>
<script lang="ts">
import { UpsertItem, UpsertRef } from "/$/crud/types";
import { UpsertItem, UpsertRef } from "cl-admin-crud-vue3/types";
import { defineComponent, ref } from "vue";
export default defineComponent({

View File

@ -36,7 +36,7 @@
<script lang="ts">
import { defineComponent } from "vue";
import { CrudLoad } from "/$/crud/types";
import { CrudLoad } from "cl-admin-crud-vue3/types";
import { TestService } from "../utils/service";
import Dialog from "../components/crud/dialog.vue";
import ContextMenu from "../components/crud/context-menu.vue";

View File

@ -218,7 +218,7 @@ import { computed, defineComponent, inject, onMounted, reactive } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import Draggable from "vuedraggable";
import { checkPerm } from "/$/base";
import { ContextMenu } from "/$/crud";
import { ContextMenu } from "cl-admin-crud-vue3";
import Cron from "../components/cron";
import { useRefs } from "/@/core";

View File

@ -37,8 +37,8 @@ import { ElMessage, ElMessageBox } from "element-plus";
import { computed, defineComponent, inject, ref, watch } from "vue";
import { useStore } from "vuex";
import { isEmpty } from "/@/core/utils";
import { ContextMenu } from "/$/crud";
import { useRefs } from "/$/crud/hooks/core";
import { ContextMenu } from "cl-admin-crud-vue3";
import { useRefs } from "/@/core";
export default defineComponent({
name: "cl-upload-space-category",

View File

@ -47,7 +47,7 @@
<script lang="ts">
import { computed, defineComponent, inject } from "vue";
import { ContextMenu } from "/$/crud";
import { ContextMenu } from "cl-admin-crud-vue3";
export default defineComponent({
name: "cl-upload-space-item",

11
src/shims-vue.d.ts vendored
View File

@ -5,6 +5,17 @@ declare module "*.vue" {
export default component;
}
declare module "cl-admin-crud-vue3" {
import type { ClContextMenu } from "cl-admin-crud-vue3/types";
import type { Plugin } from "vue";
const ContextMenu: ClContextMenu;
const Crud: Plugin;
export { ContextMenu, Crud };
export * from "cl-admin-crud-vue3";
}
declare module "array.prototype.flat" {
function Flat(list: any[]): any[];
export default Flat;

View File

@ -307,7 +307,7 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
"@types/lodash@^4.14.168":
"@types/lodash@^4.14.161", "@types/lodash@^4.14.168":
version "4.14.168"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008"
integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==
@ -930,6 +930,18 @@ change-case@^4.1.2:
optionalDependencies:
fsevents "~2.3.1"
cl-admin-crud-vue3@^0.1.3:
version "0.1.3"
resolved "https://registry.nlark.com/cl-admin-crud-vue3/download/cl-admin-crud-vue3-0.1.3.tgz?cache=0&sync_timestamp=1618901222085&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcl-admin-crud-vue3%2Fdownload%2Fcl-admin-crud-vue3-0.1.3.tgz#fc53ce0b42cecd243db6707f862d26a78406dcdb"
integrity sha1-/FPOC0LOzSQ9tnB/hi0mp4QG3Ns=
dependencies:
array.prototype.flat "^1.2.4"
core-js "^3.6.5"
element-plus "^1.0.2-beta.40"
merge "^2.1.1"
mitt "^2.1.0"
vue "^3.0.0"
class-utils@^0.3.5:
version "0.3.6"
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@ -1306,6 +1318,20 @@ element-plus@1.0.2-beta.35:
normalize-wheel "^1.0.1"
resize-observer-polyfill "^1.5.1"
element-plus@^1.0.2-beta.40:
version "1.0.2-beta.40"
resolved "https://registry.npm.taobao.org/element-plus/download/element-plus-1.0.2-beta.40.tgz#30fc9b161496ae587fab86235c80b728ea43d909"
integrity sha1-MPybFhSWrlh/q4YjXIC3KOpD2Qk=
dependencies:
"@popperjs/core" "^2.4.4"
"@types/lodash" "^4.14.161"
async-validator "^3.4.0"
dayjs "1.x"
lodash "^4.17.20"
mitt "^2.1.0"
normalize-wheel "^1.0.1"
resize-observer-polyfill "^1.5.1"
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@ -3760,7 +3786,7 @@ vue-tsc@^0.0.8:
dependencies:
unzipper latest
vue@^3.0.11:
vue@^3.0.0, vue@^3.0.11:
version "3.0.11"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.0.11.tgz#c82f9594cbf4dcc869241d4c8dd3e08d9a8f4b5f"
integrity sha512-3/eUi4InQz8MPzruHYSTQPxtM3LdZ1/S/BvaU021zBnZi0laRUyH6pfuE4wtUeLvI8wmUNwj5wrZFvbHUXL9dw==