添加 packages

This commit is contained in:
icssoa 2023-04-03 19:02:44 +08:00
parent 2bea8cd73d
commit 95a638f40c
122 changed files with 15065 additions and 65 deletions

View File

@ -780,6 +780,57 @@ declare namespace Eps {
*/ */
[key: string]: any; [key: string]: any;
} }
interface UserAddressEntity {
/**
* ID
*/
id?: number;
/**
* ID
*/
userId?: string;
/**
*
*/
contact?: string;
/**
*
*/
phone?: string;
/**
*
*/
province?: string;
/**
*
*/
city?: string;
/**
*
*/
district?: string;
/**
*
*/
address?: string;
/**
* 0- 1-
*/
isDefault?: number;
/**
*
*/
createTime?: Date;
/**
*
*/
updateTime?: Date;
/**
*
*/
[key: string]: any;
}
interface ChatMessage { interface ChatMessage {
/** /**
* list * list
@ -2334,6 +2385,63 @@ declare namespace Eps {
request: Service["request"]; request: Service["request"];
} }
interface UserAddress {
/**
*
*/
delete(data?: any): Promise<any>;
/**
*
*/
update(data?: any): Promise<any>;
/**
*
*/
info(data?: any): Promise<UserAddressEntity>;
/**
*
*/
list(data?: any): Promise<UserAddressEntity[]>;
/**
*
*/
page(data?: any): Promise<{
pagination: { size: number; page: number; total: number };
list: UserAddressEntity[];
[key: string]: any;
}>;
/**
*
*/
add(data?: any): Promise<any>;
/**
*
*/
permission: {
delete: string;
update: string;
info: string;
list: string;
page: string;
add: string;
};
/**
*
*/
_permission: {
delete: boolean;
update: boolean;
info: boolean;
list: boolean;
page: boolean;
add: boolean;
};
/**
*
*/
request: Service["request"];
}
type Service = { type Service = {
request(options?: { request(options?: {
url: string; url: string;
@ -2364,5 +2472,6 @@ declare namespace Eps {
recycle: { data: RecycleData }; recycle: { data: RecycleData };
space: { info: SpaceInfo; type: SpaceType }; space: { info: SpaceInfo; type: SpaceType };
task: { info: TaskInfo }; task: { info: TaskInfo };
user: { address: UserAddress };
}; };
} }

View File

@ -1 +1 @@
[["/admin/base/comm","",[["/personUpdate","post"],["/uploadMode","get"],["/permmenu","get"],["/person","get"],["/upload","post"],["/logout","post"],["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/base/open","",[["/refreshToken","get"],["/captcha","get"],["/login","post"],["/html","get"],["/eps","get"],["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/base/sys/department","BaseSysDepartmentEntity",[["/delete","post"],["/update","post"],["/order","post"],["/list","post"],["/add","post"],["/page"],["/info"]]],["/admin/base/sys/log","BaseSysLogEntity",[["/setKeep","post"],["/getKeep","get"],["/clear","post"],["/page","post"],["/list"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/base/sys/menu","BaseSysMenuEntity",[["/create","post"],["/delete","post"],["/update","post"],["/parse","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/base/sys/param","BaseSysParamEntity",[["/delete","post"],["/update","post"],["/html","get"],["/info","get"],["/page","post"],["/add","post"],["/list"]]],["/admin/base/sys/role","BaseSysRoleEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/base/sys/user","BaseSysUserEntity",[["/delete","post"],["/update","post"],["/move","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/cloud/db","CloudDBEntity",[["/initEntity","post"],["/delete","post"],["/update","post"],["/data","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/cloud/func/info","CloudFuncInfoEntity",[["/invoke","post"],["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/cloud/func/log","CloudFuncLogEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/demo/goods","DemoGoodsEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/dict/info","DictInfoEntity",[["/delete","post"],["/update","post"],["/data","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/dict/type","DictTypeEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/iot/device","IotDeviceEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/iot/message","IotMessageEntity",[["/page","post"],["/list"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/iot/mqtt","",[["/publish","post"],["/config","get"],["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/recycle/data","RecycleDataEntity",[["/restore","post"],["/info","get"],["/page","post"],["/list"],["/update"],["/delete"],["/add"]]],["/admin/space/info","SpaceInfoEntity",[["/getConfig","get"],["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/space/type","SpaceTypeEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/task/info","TaskInfoEntity",[["/delete","post"],["/update","post"],["/start","post"],["/once","post"],["/stop","post"],["/info","get"],["/page","post"],["/log","get"],["/add","post"],["/list"]]],["/chat/message","",[["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/chat/session","",[["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/test","",[["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]]] [["/admin/base/comm","",[["/personUpdate","post"],["/uploadMode","get"],["/permmenu","get"],["/person","get"],["/upload","post"],["/logout","post"],["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/base/open","",[["/refreshToken","get"],["/captcha","get"],["/login","post"],["/html","get"],["/eps","get"],["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/base/sys/department","BaseSysDepartmentEntity",[["/delete","post"],["/update","post"],["/order","post"],["/list","post"],["/add","post"],["/page"],["/info"]]],["/admin/base/sys/log","BaseSysLogEntity",[["/setKeep","post"],["/getKeep","get"],["/clear","post"],["/page","post"],["/list"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/base/sys/menu","BaseSysMenuEntity",[["/create","post"],["/delete","post"],["/update","post"],["/parse","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/base/sys/param","BaseSysParamEntity",[["/delete","post"],["/update","post"],["/html","get"],["/info","get"],["/page","post"],["/add","post"],["/list"]]],["/admin/base/sys/role","BaseSysRoleEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/base/sys/user","BaseSysUserEntity",[["/delete","post"],["/update","post"],["/move","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/cloud/db","CloudDBEntity",[["/initEntity","post"],["/delete","post"],["/update","post"],["/data","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/cloud/func/info","CloudFuncInfoEntity",[["/invoke","post"],["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/cloud/func/log","CloudFuncLogEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/demo/goods","DemoGoodsEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/dict/info","DictInfoEntity",[["/delete","post"],["/update","post"],["/data","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/dict/type","DictTypeEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/iot/device","IotDeviceEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/iot/message","IotMessageEntity",[["/page","post"],["/list"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/iot/mqtt","",[["/publish","post"],["/config","get"],["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/admin/recycle/data","RecycleDataEntity",[["/restore","post"],["/info","get"],["/page","post"],["/list"],["/update"],["/delete"],["/add"]]],["/admin/space/info","SpaceInfoEntity",[["/getConfig","get"],["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/space/type","SpaceTypeEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/admin/task/info","TaskInfoEntity",[["/delete","post"],["/update","post"],["/start","post"],["/once","post"],["/stop","post"],["/info","get"],["/page","post"],["/log","get"],["/add","post"],["/list"]]],["/admin/user/address","UserAddressEntity",[["/delete","post"],["/update","post"],["/info","get"],["/list","post"],["/page","post"],["/add","post"]]],["/chat/message","",[["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/chat/session","",[["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]],["/test","",[["/list"],["/page"],["/info"],["/update"],["/delete"],["/add"]]]]

View File

@ -1,6 +1,6 @@
{ {
"name": "front-next", "name": "front-next",
"version": "6.0.0", "version": "6.1.0",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"build": "vite build", "build": "vite build",
@ -9,7 +9,7 @@
"lint:eslint": "eslint \"{src}/**/*.{vue,ts,tsx}\" --fix" "lint:eslint": "eslint \"{src}/**/*.{vue,ts,tsx}\" --fix"
}, },
"dependencies": { "dependencies": {
"@cool-vue/crud": "^6.1.11", "@cool-vue/crud": "^6.2.0",
"@element-plus/icons-vue": "^2.0.10", "@element-plus/icons-vue": "^2.0.10",
"@vueuse/core": "^9.1.0", "@vueuse/core": "^9.1.0",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",

View File

@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

23
packages/crud/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,9 @@
{
"tabWidth": 4,
"useTabs": true,
"semi": true,
"jsxBracketSameLine": true,
"singleQuote": false,
"printWidth": 100,
"trailingComma": "none"
}

35
packages/crud/README.md Normal file
View File

@ -0,0 +1,35 @@
# 介绍
**cool-admin for vue**是基于[Vue.js](https://v3.cn.vuejs.org)开发的,[官方文档](https://v3.cn.vuejs.org)。
Vue.js 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是Vue 被设计为可以自底向上逐层应用。
尝试 `cool-admin` 最简单的方法就是查看文档及运行示例。
<img src='https://cool-js.com/assets/login.350e25ec.png' />
<img src='https://cool-js.com/assets/home.1706ac70.png' />
<span style="font-size: 18px; color: #F56C6C">v6.0.0 新增 Ai 极速编码 ~~~~</span>
<img src='https://cool-js.com/assets/ai-code2.9a122008.png' />
## 代码仓库
**cool-admin for vue** 是开源免费的,遵循[MIT](https://baike.baidu.com/item/MIT/10772952)开源协议,意味着您无需支付任何费用,也无需授权,即可将它应用到您的产品中。
开源免费,并不意味着您可以将 cool-admin 应用到非法的领域,比如涉及赌博,暴力等方面。如因此产生纠纷等法律问题,`cool-admin`不承担任何责任。
[https://github.com/cool-team-official/cool-admin-vue](https://github.com/cool-team-official/cool-admin-vue)
```shell
git clone https://github.com/cool-team-official/cool-admin-vue.git
```
## 技术选型
- [Vue.js](https://v3.cn.vuejs.org),基础框架;
- [VueRouter](https://router.vuejs.org)Vue.js 官方路由;
- [Pinia](https://pinia.vuejs.org),轻量级状态管理库;
- [ElementPlus](https://element-plus.gitee.io/zh-CN),桌面端组件库;
- [Vite](https://vitejs.cn),构建工具;

View File

@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};

1
packages/crud/env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="./index" />

641
packages/crud/index.d.ts vendored Normal file
View File

@ -0,0 +1,641 @@
declare type fn = () => void;
declare type obj = {
[key: string]: any;
};
declare type DeepPartial<T> = T extends Function
? T
: T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T;
declare namespace Vue {
interface Ref<T = any> {
value: T;
}
type Emit = (name: string, data: any) => void;
}
declare type Merge<A, B> = Omit<A, keyof B> & B;
declare type RemoveIndex<T> = {
[P in keyof T as string extends P ? never : number extends P ? never : P]: T[P];
};
declare type List<T> = Array<DeepPartial<T> | (() => DeepPartial<T>)>;
// element-plus
declare namespace ElementPlus {
type Size = "large" | "default" | "small";
interface FormProps {
inline?: boolean;
labelPosition?: "left" | "right" | "top";
labelWidth?: string | number;
labelSuffix?: string;
hideRequiredAsterisk?: boolean;
showMessage?: boolean;
inlineMessage?: boolean;
statusIcon?: boolean;
validateOnRuleChange?: boolean;
size?: Size;
disabled?: boolean;
[key: string]: any;
}
}
// emitter
declare interface EmitterItem {
name: string;
callback(data: any, events: { refresh(params: any): void; crudList: ClCrud.Ref[] }): void;
}
declare interface Emitter {
list: EmitterItem[];
init(events: any): void;
emit(name: string, data?: any): void;
on(name: string, callback: (data: any) => void): void;
}
// browser
declare type Browser = {
screen: string;
isMini: boolean;
};
// hook
declare namespace Hook {
interface Options {
form: obj;
prop: string;
method: "submit" | "bind";
}
type fn = (value: any, options: Options) => any;
type FormPipe =
| "number"
| "string"
| "split"
| "join"
| "boolean"
| "booleanNumber"
| "datetimeRange"
| "splitJoin"
| "json"
| "empty"
| fn;
type FormPipes = FormPipe | FormPipe[];
type Form =
| string
| {
bind?: FormPipes;
submit?: FormPipes;
};
}
// render
declare namespace Render {
type OpButton =
| `slot-${string}`
| {
label: string;
type?: string;
hidden?: boolean;
onClick(options: { scope: obj }): void;
[key: string]: any;
};
type Options = Array<{ label: string; value?: any; [key: string]: any }>;
interface Props {
onChange?(value: any): void;
[key: string]: any;
}
interface Component {
name?: string;
options?: Options | Vue.Ref<Options>;
props?: Props | Vue.Ref<Props>;
style?: obj;
functionSlot?: boolean;
vm?: any;
[key: string]: any;
}
}
declare namespace ClCrud {
type Pagination = {
total: number;
page: number;
size: number;
[key: string]: any;
};
interface Dict {
primaryId: string;
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: {
op: string;
add: string;
delete: string;
multiDelete: string;
update: string;
refresh: string;
info: string;
search: string;
reset: string;
clear: string;
save: string;
close: string;
confirm: string;
advSearch: string;
searchKey: string;
placeholder: string;
tips: string;
saveSuccess: string;
deleteSuccess: string;
deleteConfirm: string;
empty: String;
};
}
interface Permission {
page?: boolean;
list?: boolean;
add?: boolean;
delete?: boolean;
update?: boolean;
info?: boolean;
[key: string]: any;
}
interface Service {
api: {
page(
params?: obj
): Promise<{ list: any[]; pagination: Pagination; [key: string]: any }>;
list(params?: obj): Promise<any[]>;
add(params?: obj): Promise<any>;
update(params?: obj): Promise<any>;
info(params?: obj): Promise<obj>;
delete(params?: obj): Promise<any>;
[key: string]: (params?: any) => Promise<any>;
};
}
interface Config {
name: string;
service: Service["api"];
permission: Permission;
dict: Dict;
onRefresh(
params: obj,
event: {
done: fn;
next: Service["api"]["page"];
render: (list: any[], pagination?: Pagination) => void;
}
): void;
onDelete(
selection: obj[],
event: {
next: Service["api"]["delete"];
}
): void;
}
interface Options extends Config {
service: "test" | any;
}
interface Ref {
"cl-table": ClTable.Ref;
"cl-upsert": ClUpsert.Ref;
name: string;
routePath: string;
permission: Permission;
dict: Dict;
service: Service["api"];
loading: boolean;
params: obj;
selection: obj[];
set(key: "dict" | "style" | "service" | "permission", value: any): void;
done(): void;
getParams(): obj;
getPermission(key?: string): boolean;
rowInfo(data: obj): void;
rowAdd(): void;
rowEdit(data: obj): void;
rowAppend(data?: obj): void;
rowClose(): void;
rowDelete(...selection: obj[]): void;
proxy(name: string, data?: any[]): any;
paramsReplace(params: obj): obj;
refresh: Service["api"]["page"];
[key: string]: any;
}
interface Provide extends Ref {}
}
declare namespace ClTable {
type DictOptions = {
label: string;
value: any;
color?: string;
[key: string]: any;
}[];
type Dict =
| {
text?: boolean;
separator?: string;
options: DictOptions;
}
| DictOptions;
type OpButton = Array<"info" | "edit" | "delete" | Render.OpButton>;
type OpButtonHandler = (options: { scope: obj }) => OpButton;
interface Column {
type: "index" | "selection" | "expand" | "op";
hidden: boolean | Vue.Ref<boolean>;
component: Render.Component;
dict: Dict | Vue.Ref<Dict>;
buttons: OpButton | OpButtonHandler;
align: "left" | "center" | "right";
label: string | Vue.Ref<string>;
className: string;
prop: string;
orderNum: number;
width: number;
minWidth: number | string;
renderHeader: (options: { column: any; $index: number }) => any;
sortable: boolean | "desc" | "descending" | "ascending" | "asc" | "custom";
sortMethod: fn;
sortBy: string | ((row: any, index: number) => any) | any[];
resizable: {
type: boolean;
default: true;
};
columnKey: string;
headerAlign: string;
showOverflowTooltip: boolean;
fixed: boolean | string;
formatter: (row: any, column: any, value: any, index: number) => any;
selectable: (row: any, index: number) => boolean;
reserveSelection: boolean;
filterMethod: fn;
filteredValue: unknown[];
filters: unknown[];
filterPlacement: string;
filterMultiple: {
type: boolean;
default: true;
};
index: ((index: number) => number) | number;
sortOrders: unknown[];
children: Column[];
[key: string]: any;
}
interface Config {
columns: Column[];
autoHeight: boolean;
height: string | number;
contextMenu:
| boolean
| Array<
| ClContextMenu.Item
| ((row: obj) => ClContextMenu.Item)
| "refresh"
| "check"
| "update"
| "edit"
| "delete"
| "info"
| "order-desc"
| "order-asc"
>;
defaultSort: {
prop: string;
order: "descending" | "ascending";
};
sortRefresh: boolean;
emptyText: string;
rowKey: string;
onRowContextmenu?(row: any, column: any, event: any): void;
}
interface Ref {
Table: any;
config: obj;
selection: obj[];
data: obj[];
columns: Column[];
reBuild(cb?: fn): void;
calcMaxHeight(): void;
setData(data: any[]): void;
setColumns(columns: Column[]): void;
showColumn(props: string | string[], status?: boolean): void;
hideColumn(props: string | string[]): void;
changeSort(prop: string, order: string): void;
clearSelection(): void;
getSelectionRows(): any[];
toggleRowSelection(row: any, selected?: boolean): void;
toggleAllSelection(): void;
toggleRowExpansion(row: any, expanded?: boolean): void;
setCurrentRow(row: any): void;
clearSort(): void;
clearFilter(columnKeys: string[]): void;
doLayout(): void;
sort(prop: string, order: string): void;
scrollTo(position: { top?: number; left?: number }): void;
setScrollTop(top: number): void;
setScrollLeft(left: number): void;
}
interface Options extends Config {
columns: List<ClTable.Column>;
}
}
declare namespace ClForm {
type CloseAction = "close" | "save";
interface Rule {
type?:
| "string"
| "number"
| "boolean"
| "method"
| "regexp"
| "integer"
| "float"
| "array"
| "object"
| "enum"
| "date"
| "url"
| "hex"
| "email"
| "any";
required?: boolean;
message?: string;
min?: number;
max?: number;
trigger?: any;
validator?(rule: any, value: any, callback: (error?: Error) => void): void;
[key: string]: any;
}
interface Item {
type?: "tabs";
prop?: string;
props?: {
labels?: Array<{ label: string; value: string; name?: string; icon?: any }>;
justify?: "left" | "center" | "right";
color?: string;
mergeProp?: boolean;
labelWidth?: string;
error?: string;
showMessage?: boolean;
inlineMessage?: boolean;
size?: "medium" | "default" | "small";
[key: string]: any;
};
hook?: Hook.Form;
group?: string;
collapse?: boolean;
value?: any;
label?: string;
renderLabel?: any;
span?: number;
flex?: boolean;
hidden?: boolean | Vue.Ref<boolean> | ((options: { scope: obj }) => boolean);
prepend?: Render.Component;
component?: Render.Component;
append?: Render.Component;
rules?: Rule | Rule[];
required?: boolean;
[key: string]: any;
}
type Plugin = (options: {
exposed: Ref;
onOpen(cb: () => void): void;
onClose(cb: () => void): void;
onSubmit(cb: (data: obj) => obj): void;
}) => void;
interface Config {
title?: string;
width?: string;
props: ElementPlus.FormProps;
items: Item[];
form: obj;
isReset?: boolean;
on?: {
open?(data: obj): void;
close?(action: CloseAction, done: fn): void;
submit?(data: obj, event: { close: fn; done: fn }): void;
};
op: {
hidden?: boolean;
saveButtonText?: string;
closeButtonText?: string;
justify?: "flex-start" | "center" | "flex-end";
buttons?: Array<CloseAction | Render.OpButton>;
};
dialog: {
title?: string;
width?: string;
hideHeader?: boolean;
controls?: Array<"fullscreen" | "close">;
[key: string]: any;
};
[key: string]: any;
}
type Items = List<Item>;
interface Options extends Config {
items: Items;
}
interface Ref {
Form: any;
form: obj;
config: {
items: Item[];
[key: string]: any;
};
open(options: DeepPartial<Options>, plugins?: Plugin[]): void;
close(action?: CloseAction): void;
done(): void;
clear(): void;
reset(): void;
showLoading(): void;
hideLoading(): void;
setData(prop: string, value: any): void;
bindForm(data: obj): void;
setOptions(prop: string, list: Array<{ label: string; value?: any }>): void;
setProps(prop: string, value: any): void;
getForm(prop?: string): any;
setForm(prop: string, value: any): void;
showItem(props: string[] | string): void;
hideItem(props: string[] | string): void;
toggleItem(prop: string, flag?: boolean): void;
resetFields(): void;
clearValidate(props?: string[] | string): void;
validateField(
props?: string[] | string,
callback?: (isValid: boolean, invalidFields: any[]) => void
): Promise<void>;
validate(callback: (isValid: boolean, invalidFields: any[]) => void): Promise<void>;
changeTab(value: any, valid?: boolean): Promise<any>;
setTitle(value: string): void;
submit(cb?: (data: obj) => void): void;
[key: string]: any;
}
}
declare namespace ClUpsert {
interface Config {
items: ClForm.Item[];
props: ClForm.Config["props"];
sync: boolean;
op: ClForm.Config["op"];
dialog: ClForm.Config["dialog"];
onOpen?(data: obj): void;
onOpened?(data: obj): void;
onClose?(action: ClForm.CloseAction, done: fn): void;
onClosed?(): void;
onInfo?(
data: obj,
event: { close: fn; done(data: obj): void; next: ClCrud.Service["api"]["info"] }
): void;
onSubmit?(
data: obj,
event: { close: fn; done: fn; next: ClCrud.Service["api"]["update"] }
): void;
plugins?: ClForm.Plugin[];
}
interface Ref extends ClForm.Ref {
mode: "add" | "update" | "info";
}
interface Options extends Config {
items: List<ClForm.Item>;
}
}
declare namespace ClAdvSearch {
interface Config {
items?: ClForm.Item[];
title?: string;
size?: string | number;
op?: Array<"clear" | "reset" | "close" | "search">;
onSearch?(data: obj, options: { next: ClCrud.Service["api"]["page"]; close(): void }): void;
}
interface Options extends Config {
items: ClForm.Items;
}
interface Ref extends ClForm.Ref {}
}
declare namespace ClContextMenu {
interface Item {
label: string;
icon?: string;
prefixIcon?: string;
suffixIcon?: string;
ellipsis?: boolean;
disabled?: boolean;
hidden?: boolean;
children?: Item[];
showChildren?: boolean;
callback?(done: fn): void;
[key: string]: any;
}
interface Event {
pageX: number;
pageY: number;
[key: string]: any;
}
interface Options {
hover?:
| boolean
| {
target?: string;
className?: string;
};
list: Item[];
}
interface Ref {
open(event: Event, options: Options): Ref;
close(): void;
}
}
declare namespace ClDialog {
interface Provide {
visible: Vue.Ref<boolean>;
fullscreen: Vue.Ref<boolean>;
}
}
declare interface GlobalOptions {
dict: ClCrud.Dict;
permission: ClCrud.Permission;
style: {
size: ElementPlus.Size;
};
events: {
[key: string]: (...args: any[]) => any;
};
render: {
autoHeight: boolean;
functionSlots: {
exclude: string[];
};
};
crud: any;
}
declare type Options = DeepPartial<GlobalOptions>;
declare interface CrudOptions {
options: Options;
}

View File

@ -0,0 +1,38 @@
{
"name": "@cool-vue/crud",
"version": "6.2.0",
"private": false,
"main": "./dist/index.umd.min.js",
"typings": "types/index.d.ts",
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"dist": "tsc && yarn build --target lib --name index ./src/index.ts"
},
"dependencies": {
"array.prototype.flat": "^1.2.4",
"core-js": "^3.21.1",
"element-plus": "2.2.28",
"lodash": "^4.17.21",
"merge": "^2.1.1",
"mitt": "^3.0.0",
"vue": "^3.2.47"
},
"devDependencies": {
"@types/array.prototype.flat": "^1.2.1",
"@types/clone-deep": "^4.0.1",
"@vue/cli-plugin-babel": "^5.0.1",
"@vue/cli-plugin-typescript": "^5.0.3",
"@vue/cli-service": "^5.0.3",
"@vue/compiler-sfc": "^3.2.39",
"prettier": "^2.4.1",
"sass": "^1.55.0",
"sass-loader": "^12.6.0",
"typescript": "^4.6.2"
},
"files": [
"dist",
"types",
"index.d.ts"
]
}

View File

@ -0,0 +1,21 @@
import { defineComponent } from "vue";
import { useCore, useTools } from "../hooks";
export default defineComponent({
name: "cl-add-btn",
setup(_, { slots }) {
const { crud } = useCore();
const { style } = useTools();
return () => {
return (
crud.getPermission("add") && (
<el-button type="primary" size={style.size} onClick={crud.rowAdd}>
{slots.default ? slots.default() : crud.dict.label.add}
</el-button>
)
);
};
}
});

View File

@ -0,0 +1,31 @@
import { useCore, useTools } from "../../hooks";
import { defineComponent } from "vue";
import { Search } from "@element-plus/icons-vue";
export default defineComponent({
name: "cl-adv-btn",
components: {
Search
},
setup(_, { slots }) {
const { crud, mitt } = useCore();
const { style } = useTools();
function open() {
mitt.emit("crud.openAdvSearch");
}
return () => {
return (
<el-button size={style.size} onClick={open} class="cl-adv-btn">
<el-icon>
<Search />
</el-icon>
{slots.default ? slots.default() : crud.dict.label.advSearch}
</el-button>
);
};
}
});

View File

@ -0,0 +1,174 @@
import { defineComponent, h, inject, mergeProps, nextTick, PropType, reactive, ref } from "vue";
import { Close } from "@element-plus/icons-vue";
import { useCore, useTools } from "../../hooks";
import { renderNode } from "../../utils/vnode";
import { useApi } from "../form/helper";
export default defineComponent({
name: "cl-adv-search",
components: {
Close
},
props: {
// 表单项
items: {
type: Array as PropType<ClForm.Item[]>,
default: () => []
},
// 标题
title: String,
// 窗体大小
size: {
type: [Number, String],
default: "30%"
},
// 操作按钮
op: {
type: Array,
default: () => ["clear", "reset", "close", "search"]
},
// 搜索钩子
onSearch: Function
},
emits: ["reset", "clear"],
setup(props, { emit, slots, expose }) {
const { crud, mitt } = useCore();
const { style, browser } = useTools();
const config = reactive<ClAdvSearch.Config>(
mergeProps(props, inject("useAdvSearch__options") || {})
);
// cl-form
const Form = ref<ClForm.Ref>();
// el-drawer
const Drawer = ref();
// 是否可见
const visible = ref(false);
// 打开
function open() {
visible.value = true;
nextTick(function () {
Form.value?.open({
items: config.items || [],
op: {
hidden: true
},
isReset: false
});
});
}
// 关闭
function close() {
Drawer.value.handleClose();
}
// 重置数据
function reset() {
Form.value?.reset();
emit("reset");
}
// 清空数据
function clear() {
Form.value?.clear();
emit("clear");
}
// 搜素请求
function search() {
Form.value?.submit((data) => {
function next(params: any) {
Form.value?.done();
close();
return crud.refresh({
...params,
page: 1
});
}
if (config.onSearch) {
config.onSearch(data, { next, close });
} else {
next(data);
}
});
}
// 消息事件
mitt.on("crud.openAdvSearch", open);
// 渲染表单
function renderForm() {
return h(<cl-form ref={Form} inner />, {}, slots);
}
// 渲染底部
function renderFooter() {
const fns = { search, reset, clear, close };
return config.op?.map((e: string) => {
switch (e) {
case "search":
case "reset":
case "clear":
case "close":
return h(
<el-button />,
{
type: e == "search" ? "primary" : null,
size: style.size,
onClick: fns[e]
},
{ default: () => crud.dict.label[e] }
);
default:
return renderNode(e, {
scope: Form.value?.getForm(),
slots
});
}
});
}
expose({
open,
close,
clear,
reset,
...useApi({ Form })
});
return () => {
return (
<el-drawer
ref={Drawer}
modal-class="cl-adv-search"
v-model={visible.value}
direction="rtl"
with-header={false}
size={browser.isMini ? "100%" : props.size}>
<div class="cl-adv-search__header">
<span class="text">{props.title || crud.dict.label.advSearch}</span>
<el-icon size={20} onClick={close}>
<Close />
</el-icon>
</div>
<div class="cl-adv-search__container">{renderForm()}</div>
<div class="cl-adv-search__footer">{renderFooter()}</div>
</el-drawer>
);
};
}
});

View File

@ -0,0 +1,248 @@
import { defineComponent, nextTick, onMounted, reactive, ref, h, render } from "vue";
import { addClass, contains, isString, removeClass } from "../../utils";
import { useRefs } from "../../hooks";
const ClContextMenu = defineComponent({
name: "cl-context-menu",
props: {
show: Boolean,
options: {
type: Object,
default: () => {
return {};
}
},
event: {
type: Object,
default: () => {
return {};
}
}
},
setup(props, { expose, slots }) {
const { refs, setRefs } = useRefs();
// 是否可见
const visible = ref(props.show || false);
// 按钮列表
const list = ref<ClContextMenu.Item[]>([]);
// 样式
const style = reactive({
left: "0px",
top: "0px"
});
// 选中值
const ids = ref("");
// 阻止默认事件
function stopDefault(e: MouseEvent) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
}
// 解析列表
function parseList(list: ClContextMenu.Item[]) {
function deep(list: ClContextMenu.Item[]) {
list.forEach((e) => {
e.showChildren = false;
if (e.children) {
deep(e.children);
}
});
}
deep(list);
return list;
}
// 目标元素
let targetEl: any = null;
// 关闭
function close() {
visible.value = false;
ids.value = "";
removeClass(targetEl, "cl-context-menu__target");
}
// 打开
function open(event: any, options?: any) {
let left = event.pageX;
let top = event.pageY;
if (!options) {
options = {};
}
// 点击样式
if (options.hover) {
let d = options.hover === true ? {} : options.hover;
targetEl = event.target;
if (targetEl && isString(targetEl.className)) {
if (d.target) {
while (!targetEl.className.includes(d.target)) {
targetEl = targetEl.parentNode;
}
}
addClass(targetEl, d.className || "cl-context-menu__target");
}
}
if (options.list) {
list.value = parseList(options.list);
}
// 阻止默认事件
stopDefault(event);
// 显示
visible.value = true;
nextTick(() => {
const { clientHeight: h1, clientWidth: w1 } = event.target.ownerDocument.body;
const { clientHeight: h2, clientWidth: w2 } =
refs["context-menu"].querySelector(".cl-context-menu__box");
if (top + h2 > h1) {
top = h1 - h2 - 5;
}
if (left + w2 > w1) {
left = w1 - w2 - 5;
}
style.left = left + "px";
style.top = top + "px";
});
return {
close
};
}
// 行点击
function rowClick(item: ClContextMenu.Item, id: string) {
ids.value = id;
if (item.disabled) {
return false;
}
if (item.callback) {
return item.callback(close);
}
if (item.children) {
item.showChildren = !item.showChildren;
} else {
close();
}
}
expose({
open,
close
});
onMounted(function () {
if (visible.value) {
const { body, documentElement } = props.event.target.ownerDocument;
// 添加到 body 下
body.appendChild(refs["context-menu"]);
// 关闭事件
(documentElement || body).addEventListener("mousedown", (e: any) => {
const el = refs["context-menu"];
if (!contains(el, e.target) && el != e.target) {
close();
}
});
// 默认打开
open(props.event, props.options);
}
});
return () => {
function deep(list: ClContextMenu.Item[], 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": ids.value.includes(id),
"is-ellipsis": e.ellipsis,
"is-disabled": e.disabled
}}>
{/* 前缀图标 */}
{e.prefixIcon && <i class={e.prefixIcon}></i>}
{/* 标题 */}
<span
onClick={() => {
rowClick(e, id);
}}>
{e.label}
</span>
{/* 后缀图标 */}
{e.suffixIcon && <i class={e.suffixIcon}></i>}
{/* 子集*/}
{e.children &&
e.showChildren &&
deep(e.children, id, level + 1)}
</div>
);
})}
</div>
);
}
return (
visible.value && (
<div
class="cl-context-menu"
ref={setRefs("context-menu")}
style={style}
onContextmenu={stopDefault}>
{slots.default ? slots.default() : deep(list.value, "0", 1)}
</div>
)
);
};
}
});
export const ContextMenu = {
open(event: any, options: ClContextMenu.Options) {
const vm: any = h(ClContextMenu, {
show: true,
event,
options
});
render(vm, event.target.ownerDocument.createElement("div"));
}
};
export default ClContextMenu;

View File

@ -0,0 +1,283 @@
import { deepMerge, isArray, isFunction, isObject, isString, merge } from "../../utils";
import { emitter } from "../../emitter";
import { ElMessageBox, ElMessage } from "element-plus";
import Mitt from "../../utils/mitt";
import { useEventListener } from "../../hooks";
import { ref } from "vue";
interface Options {
mitt: Mitt;
config: ClCrud.Config;
crud: ClCrud.Ref;
}
export function useHelper({ mitt, config, crud }: Options) {
// 刷新随机值,避免脏数据
const refreshRd = ref(0);
// 获取权限
function getPermission(key: "page" | "list" | "info" | "update" | "add" | "delete"): boolean {
return Boolean(crud.permission[key]);
}
// 根据字典替换请求参数
function paramsReplace(params: obj) {
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(params?: obj) {
const { service, dict } = crud;
return new Promise((r1) => {
// 合并请求参数
const reqParams = paramsReplace(Object.assign(crud.params, params));
// Loading
crud.loading = true;
// 预防脏数据
const rd = (refreshRd.value = Math.random());
// 完成事件
function done() {
crud.loading = false;
r1(true);
}
// 渲染
function render(list: any[], pagination?: ClCrud.Pagination) {
mitt.emit("crud.refresh", { list, pagination });
done();
}
// 下一步
const next: ClCrud.Service["api"]["page"] = (params) => {
return new Promise(async (resolve, reject) => {
await service[dict.api.page](params)
.then((res) => {
if (rd != refreshRd.value) {
return false;
}
if (isString(res)) {
return reject(`service[page] response error`);
}
if (isArray(res)) {
render(res);
} else if (isObject(res)) {
render(res.list, res.pagination);
}
resolve(res);
done();
})
.catch((err) => {
ElMessage.error(err.message);
reject(err);
done();
});
r1(true);
});
};
// 刷新钩子
if (config.onRefresh) {
config.onRefresh(reqParams, { next, done, render });
} else {
next(reqParams);
}
});
}
// 打开详情
function rowInfo(data: any) {
mitt.emit("crud.proxy", {
name: "info",
data: [data]
});
}
// 打开新增
function rowAdd() {
mitt.emit("crud.proxy", {
name: "add"
});
}
// 打开编辑
function rowEdit(data: any) {
mitt.emit("crud.proxy", {
name: "edit",
data: [data]
});
}
// 打开追加
function rowAppend(data: any) {
mitt.emit("crud.proxy", {
name: "append",
data: [data]
});
}
// 关闭新增、编辑弹窗
function rowClose() {
mitt.emit("crud.proxy", {
name: "close"
});
}
// 删除请求
function rowDelete(...selection: any[]) {
const { service, dict } = crud;
// 参数
const params = {
ids: selection.map((e) => e[dict.primaryId])
};
// 下一步
async function next(data: obj) {
return new Promise((resolve, reject) => {
ElMessageBox({
type: "warning",
title: dict.label.tips,
message: dict.label.deleteConfirm,
confirmButtonText: dict.label.confirm,
cancelButtonText: dict.label.close,
showCancelButton: true,
async beforeClose(action, instance, done) {
if (action === "confirm") {
instance.confirmButtonLoading = true;
await service[dict.api.delete]({ ...params, ...data })
.then((res) => {
ElMessage.success(dict.label.deleteSuccess);
refresh();
resolve(res);
})
.catch((err) => {
ElMessage.error(err.message);
reject(err);
});
instance.confirmButtonLoading = false;
}
done();
}
}).catch(() => null);
});
}
// 删除钩子
if (config.onDelete) {
config.onDelete(selection, { next });
} else {
next(params);
}
}
// 代理
function proxy(name: string, data?: any[]) {
mitt.emit("crud.proxy", {
name,
data
});
}
// 获取请求参数
function getParams() {
return crud.params;
}
// 设置
function set(key: string, value: any) {
switch (key) {
// 服务
case "service":
Object.assign(crud.service, value);
crud.service.__proto__ = value.__proto__;
if (value._permission) {
for (const i in value._permission) {
crud.permission[i] = value._permission[i];
}
}
break;
// 权限
case "permission":
if (isFunction(value)) {
merge(crud.permission, value(crud));
} else {
merge(crud.permission, value);
}
break;
default:
deepMerge(crud[key], value);
break;
}
}
// 监听事件
function on(name: string, callback: fn) {
emitter.on(`${name}-${crud.id}`, callback);
}
// 默认值
set("dict", config.dict);
set("service", config.service);
set("permission", config.permission);
// 监听窗口
useEventListener("resize", () => {
mitt.emit("crud.resize");
});
return {
proxy,
set,
on,
rowInfo,
rowAdd,
rowEdit,
rowAppend,
rowDelete,
rowClose,
refresh,
getPermission,
paramsReplace,
getParams
};
}

View File

@ -0,0 +1,88 @@
import { defineComponent, getCurrentInstance, inject, provide, reactive } from "vue";
import { merge } from "merge";
import { useHelper } from "./helper";
import Mitt from "../../utils/mitt";
import { cloneDeep, deepMerge, mergeConfig } from "../../utils";
import { crudList } from "../../emitter";
import { useGlobal } from "../../hooks";
export default defineComponent({
name: "cl-crud",
props: {
// 组件名
name: String,
// 是否有边框
border: Boolean,
// 内间距
padding: {
type: String,
default: "10px"
}
},
setup(props, { slots, expose }) {
const { uid }: any = getCurrentInstance();
// 配置
const config = reactive<ClCrud.Config>(mergeConfig(inject("useCrud__options") || {}));
// 组件间通讯
const mitt = new Mitt(uid);
// 全局配置
const { dict, permission } = useGlobal();
// 参数
const crud = reactive(
deepMerge(
{
id: props.name || uid,
// 绑定的路由地址
routePath: location.pathname || "/",
// 表格配置
table: {
contextMenu: true,
border: true
},
// 表格加载状态
loading: false,
// 表格已选列
selection: [],
// 请求参数
params: {
page: 1,
size: 20
},
// 请求服务
service: {},
// 字典
dict: {},
// 权限
permission: {}
},
cloneDeep({ dict, permission })
)
);
// 集合
crudList.push(merge(crud, useHelper({ mitt, config, crud })));
// 提供
provide("crud", crud);
provide("mitt", mitt);
// 导出
expose(crud);
return () => {
return (
<div
class={["cl-crud", { "is-border": props.border }]}
style={{ padding: props.padding }}>
{slots.default && slots.default()}
</div>
);
};
}
});

View File

@ -0,0 +1,269 @@
import { defineComponent, h, ref, watch, computed, provide } from "vue";
import { Close, FullScreen, Minus } from "@element-plus/icons-vue";
import { isArray, isBoolean } from "../../utils";
import { renderNode } from "../../utils/vnode";
import { useTools } from "../../hooks";
export default defineComponent({
name: "cl-dialog",
components: {
Close,
FullScreen,
Minus
},
props: {
// 是否可见
modelValue: {
type: Boolean,
default: false
},
// Extraneous non-props attributes
props: Object,
// 自定义样式名
customClass: String,
// 标题
title: {
type: String,
default: "-"
},
// 高度
height: {
type: String,
default: null
},
// 宽度
width: {
type: String,
default: "50%"
},
// 是否缓存
keepAlive: Boolean,
// 是否全屏
fullscreen: Boolean,
// 控制按钮
controls: {
type: Array,
default: () => ["fullscreen", "close"]
},
// 隐藏头部元素
hideHeader: Boolean,
// 关闭前
beforeClose: Function
},
emits: ["update:modelValue", "fullscreen-change"],
setup(props, { emit, expose, slots }) {
const { browser } = useTools();
// el-dialog
const Dialog = ref();
// 是否全屏
const fullscreen = ref(false);
// 是否可见
const visible = ref(false);
// 缓存数
const cacheKey = ref(0);
// 是否全屏
const isFullscreen = computed(() => {
return browser && browser.isMini ? true : fullscreen.value;
});
// 监听绑定值
watch(
() => props.modelValue,
(val: boolean) => {
visible.value = val;
if (val && !props.keepAlive) {
cacheKey.value += 1;
}
},
{
immediate: true
}
);
// 监听 fullscreen 变化
watch(
() => props.fullscreen,
(val: boolean) => {
fullscreen.value = val;
},
{
immediate: true
}
);
// fullscreen-change 回调
watch(fullscreen, (val: boolean) => {
emit("fullscreen-change", val);
});
// 提供
provide("dialog", {
visible,
fullscreen: isFullscreen
});
// 打开
function open() {
fullscreen.value = true;
}
// 关闭
function close() {
function done() {
onClose();
}
if (props.beforeClose) {
props.beforeClose(done);
} else {
done();
}
}
// 关闭后
function onClose() {
emit("update:modelValue", false);
}
// 切换全屏
function changeFullscreen(val?: boolean) {
fullscreen.value = isBoolean(val) ? Boolean(val) : !fullscreen.value;
}
// 双击全屏
function dblClickFullscreen() {
if (isArray(props.controls) && props.controls.includes("fullscreen")) {
changeFullscreen();
}
}
// 渲染头部
function renderHeader() {
return (
props.hideHeader || (
<div class="cl-dialog__header" onDblclick={dblClickFullscreen}>
<span class="cl-dialog__title">{props.title}</span>
<div class="cl-dialog__controls">
{props.controls.map((e: any) => {
switch (e) {
//全屏按钮
case "fullscreen":
if (browser.screen === "xs") {
return null;
}
// 是否显示全屏按钮
if (isFullscreen.value) {
return (
<button
type="button"
class="minimize"
onClick={() => {
changeFullscreen(false);
}}>
<el-icon>
<Minus />
</el-icon>
</button>
);
} else {
return (
<button
type="button"
class="maximize"
onClick={() => {
changeFullscreen(true);
}}>
<el-icon>
<FullScreen />
</el-icon>
</button>
);
}
// 关闭按钮
case "close":
return (
<button type="button" class="close" onClick={close}>
<el-icon>
<Close />
</el-icon>
</button>
);
// 自定义按钮
default:
return renderNode(e, {
slots
});
}
})}
</div>
</div>
)
);
}
expose({
Dialog,
visible,
isFullscreen,
open,
close,
changeFullscreen
});
return () => {
return h(
<el-dialog
ref={Dialog}
class={[
"cl-dialog",
props.customClass,
{
"is-fixed": !!props.height
}
]}
width={props.width}
beforeClose={props.beforeClose}
show-close={false}
append-to-body
onClose={onClose}
fullscreen={isFullscreen.value}
v-model={visible.value}
/>,
{},
{
header() {
return renderHeader();
},
default() {
return (
<div
class="cl-dialog__container"
style={{ height: props.height }}
key={cacheKey.value}>
{slots.default && slots.default()}
</div>
);
},
footer() {
return (
slots.footer && <div class="cl-dialog__footer">{slots.footer()}</div>
);
}
}
);
};
}
});

View File

@ -0,0 +1,15 @@
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" />;
};
}
});

View File

@ -0,0 +1,127 @@
import { useCore, useTools, useForm } from "../../hooks";
import { isEmpty } from "../../utils";
import { onMounted, PropType, defineComponent, ref, h } from "vue";
export default defineComponent({
name: "cl-filter-group",
props: {
// 表单值
data: {
type: Object,
default: () => {
return {};
}
},
// 列
items: {
type: Array as PropType<ClForm.Item[]>,
default: () => []
},
// 是否需要重置按钮
resetBtn: {
type: Boolean,
default: false
},
// 搜索时钩子
onSearch: Function
},
setup(props, { slots, expose }) {
const { crud } = useCore();
const { style } = useTools();
// cl-form
const Form = useForm();
// 加载中
const loading = ref(false);
// 搜索
function search() {
const form = Form.value?.getForm();
async function next(params?: any) {
loading.value = true;
await crud.refresh({
...form,
page: 1,
...params
});
loading.value = false;
}
if (props.onSearch) {
props.onSearch(form, { next });
} else {
next();
}
}
// 重置
function reset() {
Form.value?.reset();
}
expose({
search,
reset
});
onMounted(() => {
Form.value?.open({
op: {
hidden: true
},
items: props.items,
form: props.data
});
});
return () => {
return (
isEmpty(props.items) || (
<div class="cl-filter-group">
{h(
<cl-form ref={Form} />,
{
inner: true,
inline: true
},
{
append() {
return (
<el-form-item>
<el-button
type="primary"
loading={loading.value}
size={style.size}
onClick={() => {
search();
}}>
{crud.dict.label.search}
</el-button>
{props.resetBtn && (
<el-button size={style.size} onClick={reset}>
{crud.dict.label.reset}
</el-button>
)}
</el-form-item>
);
},
default: () => {
slots.default && slots.default();
}
}
)}
</div>
)
);
};
}
});

View File

@ -0,0 +1,23 @@
import { defineComponent } from "vue";
export default defineComponent({
name: "cl-filter",
props: {
label: String
},
setup(props, { slots }) {
return () => {
return (
<div class="cl-filter">
<span class="cl-filter__label" v-show={props.label}>
{props.label}
</span>
{slots.default && slots.default()}
</div>
);
};
}
});

View File

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

View File

@ -0,0 +1,145 @@
import {
defineComponent,
h,
nextTick,
onMounted,
PropType,
reactive,
ref,
toRaw,
watch
} from "vue";
import { useRefs, useDialog } from "../../hooks";
import { isEmpty } from "../../utils";
export default defineComponent({
name: "cl-form-tabs",
props: {
modelValue: [String, Number],
labels: {
type: Array,
default: () => []
},
justify: {
type: String as PropType<
"start" | "end" | "left" | "right" | "center" | "justify" | "match-parent"
>,
default: "center"
},
type: {
type: String as PropType<"card" | "default">,
default: "default"
}
},
emits: ["update:modelValue", "change"],
setup(props, { emit, expose }) {
const { refs, setRefs } = useRefs();
// 标识
const active = ref("");
// 切换列表
const list = ref<any[]>([]);
// 下划线
const line = reactive({
width: "",
offsetLeft: "",
transform: "",
backgroundColor: ""
});
function update(val: any) {
if (!val) {
return false;
}
nextTick(() => {
const index = list.value.findIndex((e) => e.value === val);
const item = refs[`tab-${index}`];
if (item) {
// 下划线位置
line.width = item.offsetWidth + "px";
line.transform = `translateX(${item.offsetLeft}px)`;
// 靠左位置
let left = item.offsetLeft + item.clientWidth / 2 - 414 / 2 + 15;
if (left < 0) {
left = 0;
}
// 设置滚动距离
refs.tabs.scrollLeft = left;
}
});
active.value = val;
emit("update:modelValue", val);
}
// 监听绑定值变化
watch(() => props.modelValue, update);
// 监听值修改
watch(
() => active.value,
(val) => {
emit("change", val);
}
);
useDialog({
onFullscreen() {
update(active.value);
}
});
onMounted(function () {
if (!isEmpty(props.labels)) {
list.value = props.labels;
update(isEmpty(props.modelValue) ? list.value[0].value : props.modelValue);
}
});
expose({
active,
list,
line,
update
});
return () => {
return (
<div class={["cl-form-tabs", `cl-form-tabs--${props.type}`]}>
<div
class="cl-form-tabs__wrap"
style={{ textAlign: props.justify }}
ref={setRefs("tabs")}>
<ul>
{list.value.map((e, i) => {
return (
<li
ref={setRefs(`tab-${i}`)}
class={{ "is-active": e.value === active.value }}
onClick={() => {
update(e.value);
}}>
{e.icon && <el-icon>{h(toRaw(e.icon))}</el-icon>}
<span>{e.label}</span>
</li>
);
})}
{line.width && <div class="cl-form-tabs__line" style={line}></div>}
</ul>
</div>
</div>
);
};
}
});

View File

@ -0,0 +1,323 @@
import { getCurrentInstance, Ref, ref, watch, WatchStopHandle } from "vue";
import { useElApi } from "../../hooks";
import { dataset } from "../../utils";
declare type Config = ClForm.Config;
declare type Form = Vue.Ref<any>;
// 操作
export function useAction({ config, form, Form }: { config: Config; form: obj; Form: Form }) {
// 设置数据
function set({ prop, options, hidden, path, props }: any, data?: any): any {
let p: string = path || "";
if (prop) {
p = `items[prop:${prop}]`;
}
if (options) {
p += `.component.options`;
}
if (props) {
p += `.component.props`;
}
if (hidden) {
p += ".hidden";
}
return dataset(config, p, data);
}
// 合并数据
function merge(options: any, value: any): any {
return set(
{
...options,
isMerge: true
},
value
);
}
// 获取表单值
function getForm(prop: string) {
return prop ? form[prop] : form;
}
// 设置表单值
function setForm(prop: string, value: any) {
form[prop] = value;
}
// 设置配置
function setConfig(path: string, value: any) {
set({ path }, value);
}
// 设置数据
function setData(prop: string, value: any) {
set({ prop }, value);
}
// 设置表单项的下拉数据列表
function setOptions(prop: string, value: any[]) {
set({ options: true, prop }, value);
}
// 设置表单项的组件参数
function setProps(prop: string, value: any) {
merge({ props: true, prop }, value);
}
// 切换表单项的显示、隐藏
function toggleItem(prop: string, value?: boolean) {
if (value === undefined) {
value = set({ prop, hidden: true });
}
set({ hidden: true, prop }, !value);
}
// 对部分表单项隐藏
function hideItem(...props: string[]) {
props.forEach((prop) => {
set({ hidden: true, prop }, true);
});
}
// 对部分表单项显示
function showItem(...props: string[]) {
props.forEach((prop) => {
set({ hidden: true, prop }, false);
});
}
// 设置标题
function setTitle(value: string) {
config.title = value;
}
// 是否展开表单项
function collapseItem(e: any) {
Form.value?.clearValidate(e.prop);
e.collapse = !e.collapse;
}
return {
getForm,
setForm,
setData,
setConfig,
setOptions,
setProps,
toggleItem,
hideItem,
showItem,
setTitle,
collapseItem
};
}
// 选项卡
export function useTabs({ config, Form }: { config: Config; Form: Form }) {
// 选中
const active = ref<any>();
// 获取参数
function get() {
return config.items.find((e) => e.type === "tabs");
}
function set(data: any) {
active.value = data;
}
function clear() {
active.value = null;
}
// 切换
function change(value: any, isValid: boolean = true) {
return new Promise((resolve: Function, reject: Function) => {
function next() {
active.value = value;
resolve();
}
if (isValid) {
let isError: boolean = false;
const arr = config.items
.filter((e: any) => e.group == active.value && !e._hidden && e.prop)
.map((e: any) => {
return new Promise((r: Function) => {
// 验证表单
Form.value.validateField(e.prop, (valid: string) => {
if (valid) {
isError = true;
}
r(valid);
});
});
});
Promise.all(arr).then((msg) => {
if (isError) {
reject(msg.filter(Boolean));
} else {
next();
}
});
} else {
next();
}
});
}
// 合并
function mergeProp(item: ClForm.Item) {
const d = get();
if (d && d.props) {
const { mergeProp, labels = [] } = d.props;
if (mergeProp) {
const t = labels.find((e) => e.value == item.group);
if (t && t.name) {
item.prop = `${t.name}-${item.prop}`;
}
}
}
}
return {
active,
get,
set,
change,
clear,
mergeProp
};
}
// 方法
export function useApi({ Form }: { Form: Form }) {
return useElApi(
[
"open",
"close",
"clear",
"reset",
"submit",
"bindForm",
"changeTab",
"setTitle",
"showLoading",
"hideLoading",
"collapseItem",
"getForm",
"setForm",
"setData",
"setOptions",
"setProps",
"toggleItem",
"hideItem",
"showItem",
"validate",
"validateField",
"resetFields",
"scrollToField",
"clearValidate"
],
Form
);
}
// 插件
export function usePlugins({ visible }: { visible: Ref<boolean> }) {
const that: any = getCurrentInstance();
interface Event {
onOpen: (() => void)[];
onClose: (() => void)[];
onSubmit: ((data: obj) => obj)[];
[key: string]: any;
}
// 事件
const ev: Event = {
onOpen: [],
onClose: [],
onSubmit: []
};
// 监听器
let timer: WatchStopHandle | null = null;
// 插件创建
function create(plugins?: ClForm.Plugin[]) {
for (const i in ev) {
ev[i] = [];
}
if (timer) {
timer();
}
if (plugins) {
plugins.forEach((p) => {
p({
exposed: that.exposed,
onOpen(cb: any) {
ev.onOpen.push(cb);
},
onClose(cb: any) {
ev.onClose.push(cb);
},
onSubmit(cb: any) {
ev.onSubmit.push(cb);
}
});
});
timer = watch(
visible,
(val) => {
if (val) {
setTimeout(() => {
ev.onOpen.forEach((e) => e());
}, 10);
} else {
ev.onClose.forEach((e) => e());
}
},
{
immediate: true
}
);
}
}
// 表单提交
async function submit(data: any) {
let d = data;
for (let i = 0; i < ev.onSubmit.length; i++) {
const d2 = await ev.onSubmit[i](d);
if (d2) {
d = d2;
}
}
return d;
}
return {
create,
submit
};
}

View File

@ -0,0 +1,608 @@
import { defineComponent, h, nextTick, reactive, ref } from "vue";
import { useAction, usePlugins, useTabs } from "./helper";
import { useElApi, useTools } from "../../hooks";
import { deepMerge, cloneDeep, isEmpty, isBoolean } from "../../utils";
import formHook from "../../utils/form-hook";
import { renderNode } from "../../utils/vnode";
import { parseFormHidden } from "../../utils/parse";
export default defineComponent({
name: "cl-form",
props: {
inner: Boolean,
inline: Boolean,
customClass: String
},
setup(props, { expose, slots }) {
const { getValue, dict, browser, style } = useTools();
// 表单配置
const config = reactive<ClForm.Config>({
title: "-",
width: "50%",
props: {
labelWidth: 100
},
on: {},
op: {
hidden: false,
saveButtonText: dict.label.save,
closeButtonText: dict.label.close,
buttons: ["close", "save"]
},
dialog: {
closeOnClickModal: false,
appendToBody: true
},
items: [],
form: {},
_data: {}
});
const Form = ref();
// 表单数据
const form = reactive<any>({});
// 关闭的操作类型
let closeAction: ClForm.CloseAction = "close";
// 旧表单数据
let defForm: any = null;
// 表单是否可见
const visible = ref(false);
// 表单提交保存状态
const saving = ref(false);
// 表单加载状态
const loading = ref(false);
// 选项卡
const Tabs = useTabs({ config, Form });
// 操作
const Action = useAction({ config, form, Form });
// 方法
const ElFormApi = useElApi(
["validate", "validateField", "resetFields", "scrollToField", "clearValidate"],
Form
);
// 插件
const plugin = usePlugins({ visible });
// 显示加载中
function showLoading() {
loading.value = true;
}
// 隐藏加载
function hideLoading() {
loading.value = false;
}
// 请求表单保存状态
function done() {
saving.value = false;
}
// 关闭表单
function close(action?: ClForm.CloseAction) {
if (action) {
closeAction = action;
}
beforeClose(() => {
visible.value = false;
done();
});
}
// 关闭前
function beforeClose(done: fn) {
if (config.on?.close) {
config.on.close(closeAction, done);
} else {
done();
}
}
// 关闭后
function onClosed() {
Tabs.clear();
Form.value?.clearValidate();
}
// 清空表单验证
function clear() {
for (const i in form) {
delete form[i];
}
setTimeout(() => {
Form.value?.clearValidate();
}, 0);
}
// 重置
function reset() {
if (defForm) {
for (const i in defForm) {
form[i] = cloneDeep(defForm[i]);
}
}
}
// 表单提交
function submit(callback?: fn) {
// 验证表单
Form.value.validate(async (valid: boolean, error: any) => {
if (valid) {
saving.value = true;
// 拷贝表单值
const d = cloneDeep(form);
// 过滤隐藏的表单项
config.items.forEach((e) => {
if (e._hidden) {
if (e.prop) {
delete d[e.prop];
}
}
if (e.hook) {
formHook.submit({
...e,
value: e.prop ? d[e.prop] : undefined,
form: d
});
}
});
// 处理 "-" 多层级
for (const i in d) {
if (i.includes("-")) {
// 结构参数
const [a, ...arr] = i.split("-");
// 关键值的key
const k: string = arr.pop() || "";
if (!d[a]) {
d[a] = {};
}
let f: any = d[a];
// 设置默认值
arr.forEach((e: any) => {
if (!f[e]) {
f[e] = {};
}
f = f[e];
});
// 设置关键值
f[k] = d[i];
delete d[i];
}
}
const submit = callback || config.on?.submit;
// 提交事件
if (submit) {
submit(await plugin.submit(d), {
close() {
close("save");
},
done
});
} else {
done();
}
} else {
// 判断是否使用form-tabs切换到对应的选项卡
const keys = Object.keys(error);
if (Tabs.active.value) {
const item = config.items.find((e) => e.prop === keys[0]);
if (item) {
Tabs.set(item.group);
}
}
}
});
}
// 打开表单
function open(options?: ClForm.Options, plugins?: ClForm.Plugin[]) {
if (!options) {
return console.error("Options is not null");
}
// 清空
if (options.isReset !== false) {
clear();
}
// 显示对话框
visible.value = true;
// 默认关闭方式
closeAction = "close";
// 合并配置
for (const i in config) {
switch (i) {
// 动态处理
case "items":
config.items = (options.items || []).map((e) => getValue(e));
break;
// 合并处理
case "on":
case "op":
case "props":
case "dialog":
case "_data":
deepMerge(config[i], options[i] || {});
break;
// 赋值
case "title":
case "width":
default:
config[i] = options[i];
break;
}
}
// 预设表单值
if (options?.form) {
for (const i in options.form) {
form[i] = options.form[i];
}
}
// 设置表单数据
config.items.map((e) => {
if (e.prop) {
// 解析 prop
if (e.prop.includes(".")) {
e.prop = e.prop.replace(/\./g, "-");
}
// prop 合并
Tabs.mergeProp(e);
// 绑定值
formHook.bind({
...e,
value: form[e.prop] !== undefined ? form[e.prop] : cloneDeep(e.value),
form
});
// 表单验证
if (e.required) {
e.rules = {
required: true,
message: `${e.label}不能为空`
};
}
}
// 设置 tabs 默认值
if (e.type == "tabs") {
Tabs.set(e.value);
}
});
// 设置默认值
if (!defForm) {
defForm = cloneDeep(form);
}
// 创建插件
plugin.create(plugins);
// 打开回调
nextTick(() => {
setTimeout(() => {
// 打开事件
if (config.on?.open) {
config.on.open(form);
}
}, 10);
});
}
// 绑定表单数据
function bindForm(data: any) {
config.items.forEach((e) => {
formHook.bind({
...e,
value: e.prop ? data[e.prop] : undefined,
form: data
});
});
Object.assign(form, data);
}
// 渲染表单及表单项
function renderForm() {
const { isDisabled } = config._data;
// 表单项列表
const children = config.items.map((e) => {
if (e.type == "tabs") {
return <cl-form-tabs v-model={Tabs.active.value} {...e.props} />;
}
// 是否隐藏
e._hidden = parseFormHidden(e.hidden, {
scope: form
});
// 分组显示
const inGroup =
isEmpty(Tabs.active.value) || isEmpty(e.group)
? true
: e.group === Tabs.active.value;
// 表单项
const FormItem = e.component
? h(
<el-form-item
label-width={props.inline ? "auto" : ""}
label={e.label}
prop={e.prop}
rules={isDisabled ? null : e.rules}
v-show={inGroup}
/>,
e.props,
{
label() {
return e.renderLabel
? renderNode(e.renderLabel, {
scope: form,
render: "slot",
slots
})
: e.label;
},
default() {
return (
<div>
<div class="cl-form-item">
{["prepend", "component", "append"].map((name) => {
return (
e[name] && (
<div
v-show={!e.collapse}
class={[
`cl-form-item__${name}`,
{
flex1: e.flex !== false
}
]}
style={e[name].style}>
{renderNode(e[name], {
item: e,
prop: e.prop,
scope: form,
slots,
_data: {
isDisabled
}
})}
</div>
)
);
})}
</div>
{isBoolean(e.collapse) && (
<div
class="cl-form-item__collapse"
onClick={() => {
Action.collapseItem(e);
}}>
<el-divider content-position="center">
{e.collapse ? "查看更多" : "隐藏内容"}
</el-divider>
</div>
)}
</div>
);
}
}
)
: null;
// 隐藏
if (e._hidden) {
return null;
}
// 行内
if (props.inline) {
return FormItem;
}
return (
<el-col span={24} key={e.prop} {...e}>
{FormItem}
</el-col>
);
});
return (
<div class="cl-form__container">
{h(
<el-form
ref={Form}
size={style.size}
label-position={browser.isMini && !props.inline ? "top" : "right"}
label-width="100px"
inline={props.inline}
disabled={saving.value}
scroll-to-error
model={form}
onSubmit={(e: Event) => {
submit();
e.preventDefault();
}}
/>,
config.props,
{
default: () => {
return (
<div class="cl-form__items">
{/* 前 */}
{slots.prepend && slots.prepend({ scope: form })}
{/* 项 */}
{props.inline ? (
children
) : (
<el-row gutter={10} v-loading={loading.value}>
{children}
</el-row>
)}
{/* 后 */}
{slots.append && slots.append({ scope: form })}
</div>
);
}
}
)}
</div>
);
}
// 渲染表单按钮
function renderFooter() {
const { hidden, buttons, saveButtonText, closeButtonText, justify } = config.op;
const { style } = useTools();
const Btns = buttons?.map((e: any) => {
switch (e) {
case "save":
return (
<el-button
type="success"
size={style.size}
disabled={loading.value}
loading={saving.value}
onClick={() => {
submit();
}}>
{saveButtonText}
</el-button>
);
case "close":
return (
<el-button
size={style.size}
onClick={() => {
close("close");
}}>
{closeButtonText}
</el-button>
);
default:
return renderNode(e, {
scope: form,
slots,
custom({ scope }) {
return (
<el-button
text
type={e.type}
bg
onClick={() => {
e.onClick({ scope });
}}>
{e.label}
</el-button>
);
}
});
}
});
return (
hidden || (
<div
class="cl-form__footer"
style={{
justifyContent: justify || "flex-end"
}}>
{Btns}
</div>
)
);
}
expose({
Form,
visible,
saving,
form,
config,
loading,
open,
close,
done,
clear,
reset,
submit,
bindForm,
showLoading,
hideLoading,
Tabs,
...Action,
...ElFormApi
});
return () => {
const Form = (
<div class={["cl-form", props.customClass]}>
{renderForm()}
{renderFooter()}
</div>
);
if (props.inner) {
return visible.value && Form;
} else {
return h(
<cl-dialog v-model={visible.value} />,
{
title: config.title,
width: config.width,
...config.dialog,
beforeClose,
onClosed,
keepAlive: false
},
{
default() {
return Form;
}
}
);
}
};
}
});

View File

@ -0,0 +1,48 @@
import { App } from "vue";
import Crud from "./crud";
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";
import Upsert from "./upsert";
import Dialog from "./dialog";
import Filter from "./filter";
import FilterGroup from "./filter/group";
import ErrorMessage from "./error-message";
import Row from "./row";
export const components: { [key: string]: any } = {
Crud,
AddBtn,
AdvBtn,
AdvSearch,
Flex,
Form,
FormTabs,
MultiDeleteBtn,
Pagination,
Query,
RefreshBtn,
SearchKey,
Table,
Upsert,
Dialog,
Filter,
FilterGroup,
ErrorMessage,
Row
};
export function useComponent(app: App) {
for (const i in components) {
app.component(components[i].name, components[i]);
}
}

View File

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

View File

@ -0,0 +1,77 @@
import { useCore, useTools } from "../../hooks";
import { defineComponent, h, ref } from "vue";
export default defineComponent({
name: "cl-pagination",
setup(_, { expose }) {
const { crud, mitt } = useCore();
const { style, browser } = useTools();
// 总数
const total = ref(0);
// 当前页数
const currentPage = ref(1);
// 每页大小
const pageSize = ref(20);
// 页数发生变化
function onCurrentChange(index: number) {
crud.refresh({
page: index
});
}
// 条目发生变化
function onSizeChange(size: number) {
crud.refresh({
page: 1,
size
});
}
function setPagination(res: DeepPartial<ClCrud.Pagination>) {
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 }: { pagination: ClCrud.Pagination }) => {
setPagination(pagination);
});
expose({
total,
currentPage,
pageSize,
setPagination
});
return () => {
return h(
<el-pagination
small={style.size == "small"}
background
page-sizes={[10, 20, 30, 40, 50, 100]}
layout={
browser.isMini
? "prev, pager, next"
: "total, sizes, prev, pager, next, jumper"
}
/>,
{
onSizeChange,
onCurrentChange,
total: total.value,
currentPage: currentPage.value,
pageSize: pageSize.value
}
);
};
}
});

View File

@ -0,0 +1,118 @@
import { defineComponent, ref, watch } from "vue";
import { useCore } from "../../hooks";
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, expose }) {
const { crud } = useCore();
// 列表
const list = ref<{ label: string; value: any; active: boolean }[]>([]);
// 更新数据列表
function update() {
let arr: any[] = [];
if (isArray(props.modelValue)) {
arr = props.modelValue;
} else {
arr = [props.modelValue];
}
if (!props.multiple) {
arr.splice(1);
}
// 默认选择
list.value = (props.list || []).map((e: any) => {
e.active = arr.some((v) => v === e.value);
return e;
});
}
update();
// 点击选择项
function selectItem(item: any) {
if (item.active) {
item.active = false;
} else {
if (props.multiple) {
item.active = true;
} else {
list.value.map((e) => {
e.active = e.value == item.value;
});
}
}
// 过滤未选中的
const selection = list.value.filter((e) => e.active).map((e) => e.value);
// 处理多选情况
const value = props.multiple ? selection : selection[0];
// 请求回调
if (props.callback) {
props.callback(value);
} else {
crud.refresh({
[props.field]: value
});
emit("change", value);
}
}
// 监听绑定值,更新数据列表
watch(
() => props.modelValue,
() => {
update();
}
);
expose({
list,
selectItem
});
return () => {
return (
<div class="cl-query">
<ul>
{list.value.map((item, index) => {
return (
<li
class={{ "is-active": item.active }}
key={index}
onClick={() => {
selectItem(item);
}}>
<span>{item.label}</span>
</li>
);
})}
</ul>
</div>
);
};
}
});

View File

@ -0,0 +1,23 @@
import { defineComponent } from "vue";
import { useCore, useTools } from "../hooks";
export default defineComponent({
name: "cl-refresh-btn",
setup(_, { slots }) {
const { crud } = useCore();
const { style } = useTools();
return () => {
return (
<el-button
size={style.size}
onClick={() => {
crud.refresh();
}}>
{slots.default ? slots.default() : crud.dict.label.refresh}
</el-button>
);
};
}
});

View File

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

View File

@ -0,0 +1,176 @@
import { defineComponent, ref, watch, computed, PropType } from "vue";
import { useCore, useTools } from "../../hooks";
export default defineComponent({
name: "cl-search-key",
props: {
// 绑定值
modelValue: String,
// 选中字段
field: {
type: String,
default: "keyWord"
},
// 字段列表
fieldList: {
type: Array as PropType<Array<{ label: string; value: string }>>,
default: () => []
},
// 搜索时的钩子
onSearch: Function,
// 输入框占位内容
placeholder: String,
// 宽度
width: {
type: String,
default: "300px"
}
},
emits: ["update:modelValue", "change", "field-change"],
setup(props, { emit, expose }) {
const { crud } = useCore();
const { style } = useTools();
// 选中字段
const selectField = ref(props.field);
// 加载状态
const loading = ref(false);
// 文字提示
const placeholder = computed(() => {
if (props.placeholder) {
return props.placeholder;
} else {
const item = props.fieldList.find((e) => e.value == selectField.value);
if (item) {
return crud.dict.label.placeholder + item.label;
} else {
return crud.dict.label.searchKey;
}
}
});
// 搜索内容
const value = ref("");
watch(
() => props.modelValue,
(val) => {
value.value = val || "";
},
{
immediate: true
}
);
// 锁
let lock = false;
// 搜索
function search() {
if (!lock) {
const params: obj = {};
props.fieldList.forEach((e) => {
params[e.value] = undefined;
});
async function next(newParams?: obj) {
loading.value = true;
await crud.refresh({
page: 1,
...params,
[selectField.value]: value.value || undefined,
...newParams
});
loading.value = false;
}
if (props.onSearch) {
props.onSearch(params, { next });
} else {
next();
}
}
}
// 回车搜索
function onKeydown({ key }: KeyboardEvent) {
if (key === "Enter") {
search();
}
}
// 监听输入
function onInput(val: string) {
emit("update:modelValue", val);
emit("change", val);
}
// 监听变化
function onChange() {
search();
lock = true;
setTimeout(() => {
lock = false;
}, 300);
}
// 监听字段选择
function onFieldChange() {
emit("field-change", selectField.value);
onInput("");
value.value = "";
}
expose({
search
});
return () => {
return (
<div class="cl-search-key">
<el-select
class="cl-search-key__select"
filterable
size={style.size}
v-model={selectField.value}
v-show={props.fieldList.length > 0}
onChange={onFieldChange}>
{props.fieldList.map((e, i) => (
<el-option key={i} label={e.label} value={e.value} />
))}
</el-select>
<div class="cl-search-key__wrap" style={{ width: props.width }}>
<el-input
v-model={value.value}
size={style.size}
placeholder={placeholder.value}
onKeydown={onKeydown}
onInput={onInput}
onChange={onChange}
clearable
/>
<el-button
size={style.size}
type="primary"
loading={loading.value}
onClick={search}>
{crud.dict.label.search}
</el-button>
</div>
</div>
);
};
}
});

View File

@ -0,0 +1,582 @@
import { computed, h, nextTick, onActivated, onMounted, ref } from "vue";
import { useCore, useTools } from "../../hooks";
import {
addClass,
cloneDeep,
debounce,
isArray,
isBoolean,
isEmpty,
isFunction
} from "../../utils";
import { renderNode } from "../../utils/vnode";
import { parseTableDict, parseTableOpButtons } from "../../utils/parse";
import { ContextMenu } from "../context-menu";
import { orderBy } from "lodash";
declare type Emit = (name: "selection-change" | "sort-change", ...args: any[]) => void;
declare type Table = Vue.Ref<any>;
declare type Config = ClTable.Config;
declare interface Sort {
defaultSort: {
prop?: string;
order?: string;
};
changeSort(prop: string, order: string): void;
}
// 排序
export function useSort({ config, emit, Table }: { config: Config; emit: Emit; Table: Table }) {
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
};
}
// 单元行事件
export function useRow({ Table, config, Sort }: { Table: Table; config: Config; Sort: Sort }) {
const { crud } = useCore();
// 右键菜单
function onRowContextMenu(row: any, column: any, event: any) {
// 菜单配置
const cm: any =
isEmpty(config.contextMenu) && !isArray(config.contextMenu)
? crud.table.contextMenu
: config.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) {
// 高亮
Table.value.setCurrentRow(row);
// 解析按钮
const list = buttons
.map((e: any) => {
switch (e) {
case "refresh":
return {
label: "刷新",
callback(done: fn) {
crud.refresh();
done();
}
};
case "edit":
case "update":
return {
label: "编辑",
hidden: !crud.getPermission("update"),
callback(done: fn) {
crud.rowEdit(row);
done();
}
};
case "delete":
return {
label: "删除",
hidden: !crud.getPermission("delete"),
callback(done: fn) {
crud.rowDelete(row);
done();
}
};
case "info":
return {
label: "详情",
hidden: !crud.getPermission("info"),
callback(done: fn) {
crud.rowInfo(row);
done();
}
};
case "check":
return {
label: crud.selection.find((e) => e.id == row.id)
? "取消选择"
: "选择",
hidden: !config.columns.find((e) => e.type === "selection"),
callback(done: fn) {
Table.value.toggleRowSelection(row);
done();
}
};
case "order-desc":
return {
label: `${column.label} - 降序`,
hidden: !column.sortable,
callback(done: fn) {
Sort.changeSort(column.property, "desc");
done();
}
};
case "order-asc":
return {
label: `${column.label} - 升序`,
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 (list.length > 0) {
ContextMenu.open(event, {
list
});
}
}
// 回调
if (config.onRowContextmenu) {
config.onRowContextmenu(row, column, event);
}
}
return {
onRowContextMenu
};
}
// 表格高度
export function useHeight({ config, Table }: { Table: Table; config: Config }) {
const { mitt } = useCore();
const { render } = useTools();
// 是否自动计算
const isAuto = computed(() =>
isBoolean(config.autoHeight) ? config.autoHeight : render.autoHeight
);
// 最大高度
const maxHeight = ref(0);
// 计算表格最大高度
const update = debounce(async () => {
if (!isAuto.value) {
return false;
}
await nextTick();
let vm: any = Table.value;
let r: any = null;
if (vm) {
while (!vm.$parent?.$el.className.includes("cl-crud")) {
vm = vm.$parent;
r = vm.$parent.$el;
}
// 设置序号
r.querySelectorAll(".el-row").forEach((e: any, i: number, arr: any[]) => {
if (i == arr.length - 1) {
addClass(e, "cl-row--last");
}
});
// 获取上高度
let h = vm.$el.offsetTop;
// 获取下高度
let n = vm.$el.nextSibling;
while (n && (n.className || "").includes("cl-row")) {
h += n.clientHeight;
n = n.nextSibling;
}
// 设置最大高度
maxHeight.value = r.clientHeight - h - 10;
}
}, 100);
// 窗口大小改变事件
mitt.on("crud.resize", () => {
update();
});
onMounted(function () {
update();
});
onActivated(function () {
update();
});
return {
isAuto,
maxHeight,
calcMaxHeight: update
};
}
// 多选框
export function useSelection({ emit }: { emit: 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
};
}
// 数据处理
export function useData({ config, Table }: { config: Config; Table: Table }) {
const { mitt, crud } = useCore();
// 列表数据
const data = ref<any[]>([]);
// 设置数据
function setData(list: any[]) {
data.value = list;
}
// 监听刷新
mitt.on("crud.refresh", ({ list }: { list: any[] }) => {
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
};
}
// 表格操作
export function useOp({ config }: { config: 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("crud.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
};
}
// 渲染
export function useRender() {
const { getValue, browser, slots } = useTools();
const { crud } = useCore();
// 渲染列
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="center" />
);
// 操作按钮
if (item.type === "op") {
return h(
ElTableColumn,
{
label: crud.dict.label.op,
width: "160px",
fixed: browser.isMini ? null : "right",
...item
},
{
default: (scope: any) => {
return (
<div class="cl-table__op">
{parseTableOpButtons(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) {
const slot = slots[`header-${item.prop}`];
if (slot) {
return slot({
scope
});
} else {
return scope.column.label;
}
},
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 (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 parseTableDict(value, item.dict);
}
// 空数据
else if (isEmpty(value)) {
return scope.emptyText;
} else {
return value;
}
}
}
});
}
return deep(item);
}
})
.filter(Boolean);
}
// 插槽 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
};
}

View File

@ -0,0 +1,149 @@
import { defineComponent, h, ref, inject, reactive } from "vue";
import { useRow, useHeight, useRender, useSort, useData, useSelection, useOp } from "./helper";
import { useCore, useTools, useProxy, useElApi } from "../../hooks";
import { mergeConfig } from "../../utils";
export default defineComponent({
name: "cl-table",
emits: ["selection-change", "sort-change"],
props: {
// 列配置
columns: {
type: Array,
default: () => []
},
// 是否自动计算高度
autoHeight: {
type: Boolean,
default: null
},
// 固定高度
height: null,
// 右键菜单
contextMenu: Array,
// 默认排序
defaultSort: Object,
// 排序后是否刷新
sortRefresh: {
type: Boolean,
default: true
},
// 空数据显示文案
emptyText: String,
// 当前行的 key
rowKey: {
type: String,
default: "id"
}
},
setup(props, { emit, expose }) {
const { crud } = useCore();
const { getValue, style } = useTools();
// 配置
const config = reactive<ClTable.Config>(
mergeConfig(props, inject("useTable__options") || {})
);
// 列表项动态处理
config.columns = (config.columns || []).map((e) => getValue(e));
// el-table
const Table = ref();
// 排序
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"
],
Table
);
const ctx = {
Table,
columns: config.columns,
...Selection,
...Data,
...Sort,
...Row,
...Height,
...Op,
...ElTableApi
};
useProxy(ctx);
expose(ctx);
return () => {
const { renderColumn, renderAppend, renderEmpty } = useRender();
return (
ctx.visible.value &&
h(
<el-table class="cl-table" ref={Table} v-loading={crud.loading} />,
{
maxHeight: ctx.isAuto.value ? ctx.maxHeight.value : null,
height: ctx.isAuto.value ? config.height : null,
defaultSort: ctx.defaultSort,
rowKey: config.rowKey,
size: style.size,
border: true,
highlightCurrentRow: true,
data: ctx.data.value,
onRowContextmenu: ctx.onRowContextMenu,
onSelectionChange: ctx.onSelectionChange,
onSortChange: ctx.onSortChange
},
{
default() {
return renderColumn(ctx.columns);
},
empty() {
return renderEmpty(config.emptyText || crud.dict.label.empty);
},
append() {
return renderAppend();
}
}
)
);
};
}
});

View File

@ -0,0 +1,310 @@
import { defineComponent, h, inject, reactive, ref, toRefs } from "vue";
import { ElMessage } from "element-plus";
import { useCore, useProxy } from "../../hooks";
import { useApi } from "../form/helper";
import { mergeConfig } from "../../utils";
export default defineComponent({
name: "cl-upsert",
emits: ["opened", "closed"],
props: {
// 表单项
items: {
type: Array,
default: () => []
},
// <el-form /> 参数
props: Object,
// 编辑时是否同步打开
sync: Boolean,
// 操作按钮参数
op: Object,
// <cl-dialog /> 参数
dialog: Object,
// 打开表单钩子
onOpen: Function,
// 打开表单后钩子
onOpened: Function,
// 关闭表单钩子
onClose: Function,
// 关闭表单后钩子
onClosed: Function,
// 获取表单数据钩子
onInfo: Function,
// 表单提交钩子
onSubmit: Function
},
setup(props, { slots, expose }) {
const { crud } = useCore();
const config = reactive<ClUpsert.Config>(
mergeConfig(props, inject("useUpsert__options") || {})
);
// el-form
const Form = ref<ClForm.Ref>();
// 模式
const mode = ref<ClUpsert.Ref["mode"]>("info");
// 关闭表单
function close(action?: ClForm.CloseAction) {
Form.value?.close(action);
}
// 关闭后
function onClosed() {
Form.value?.hideLoading();
if (config.onClosed) {
config.onClosed();
}
}
// 关闭前
function beforeClose(action: ClForm.CloseAction, done: fn) {
function next() {
done();
onClosed();
}
if (config.onClose) {
config.onClose(action, next);
} else {
next();
}
}
// 提交
function submit(data: obj) {
const { service, dict, refresh } = crud;
function done() {
Form.value?.done();
}
function next(data: obj) {
return new Promise((resolve, reject) => {
// 发送请求
service[dict.api[mode.value]](data)
.then((res) => {
ElMessage.success(dict.label.saveSuccess);
done();
close("save");
refresh();
resolve(res);
})
.catch((err) => {
ElMessage.error(err.message);
done();
reject(err);
});
});
}
// 提交钩子
if (config.onSubmit) {
config.onSubmit(data, {
done,
next,
close() {
close("save");
}
});
} else {
next(data);
}
}
// 打开表单
function open() {
// 是否禁用
const isDisabled = mode.value == "info";
return new Promise((resolve) => {
if (!Form.value) {
return console.error("<cl-upsert /> is not found");
}
Form.value?.open(
{
title: crud.dict.label[mode.value],
props: {
...config.props,
disabled: isDisabled
},
op: {
...config.op,
hidden: isDisabled
},
dialog: config.dialog,
items: config.items || [],
on: {
open(data) {
if (config.onOpen) {
config.onOpen(data);
}
resolve(true);
},
submit,
close: beforeClose
},
form: {},
_data: {
isDisabled
}
},
config.plugins
);
});
}
// 打开后事件
function onOpened() {
const data = Form.value?.getForm();
if (config.onOpened) {
config.onOpened(data);
}
}
// 新增
async function add() {
mode.value = "add";
// 打开中
await open();
// 打开后
onOpened();
}
// 追加
async function append(data: any) {
mode.value = "add";
// 打开中
await open();
// 绑定值
if (data) {
Form.value?.bindForm(data);
}
// 打开后
onOpened();
}
// 编辑
function edit(data?: any) {
mode.value = "update";
getInfo(data);
}
// 详情
function info(data?: any) {
mode.value = "info";
getInfo(data);
}
// 信息
function getInfo(data: any) {
// 显示加载中
Form.value?.showLoading();
// 是否同步打开
if (!config.sync) {
open();
}
// 完成
async function done(data?: any) {
// 加载完成
Form.value?.hideLoading();
// 合并数据
if (data) {
Form.value?.bindForm(data);
}
// 同步打开表单
if (config.sync) {
await open();
}
onOpened();
}
// 获取详情
function next(data: any): Promise<any> {
return new Promise(async (resolve, reject) => {
// 发送请求
await crud.service[crud.dict.api.info]({
[crud.dict.primaryId]: data[crud.dict.primaryId]
})
.then((res) => {
done(res);
resolve(res);
})
.catch((err) => {
ElMessage.error(err.message);
reject(err);
});
// 隐藏加载框
Form.value?.hideLoading();
});
}
// 详情钩子
if (config.onInfo) {
config.onInfo(data, {
close,
next,
done
});
} else {
next(data);
}
}
// 完成
function done() {
Form.value?.hideLoading();
}
const ctx = {
config,
...toRefs(config),
...useApi({ Form }),
Form,
get form() {
return Form.value?.form || {};
},
mode,
add,
append,
edit,
info,
open,
close,
done,
submit
};
useProxy(ctx);
expose(ctx);
return () => {
return (
<div class="cl-upsert">
{h(<cl-form ref={Form} custom-class={`is-${mode.value}`} />, {}, slots)}
</div>
);
};
}
});

View File

@ -0,0 +1,362 @@
<template>
<div class="demo">
<div class="wrap">
<cl-crud ref="Crud" padding="10px" border>
<cl-row>
<cl-filter-group
:items="[
{
label: '姓名',
prop: 'name',
component: { name: 'el-input' }
},
{ label: '姓名', prop: 'name', component: { name: 'el-input' } }
]"
></cl-filter-group>
</cl-row>
<cl-row>
<cl-refresh-btn />
<cl-add-btn />
<cl-multi-delete-btn />
<el-button @click="openForm">自定义表单</el-button>
<el-button :disabled="Table?.columns.length == 0" @click="openDialog"
>对话框</el-button
>
<form-crud />
<cl-query
field="status"
:list="[
{ label: '关闭', value: '0' },
{ label: '开启', value: '1' }
]"
/>
<cl-flex1>1</cl-flex1>
<cl-search-key
field="name"
:field-list="[
{ label: 'ID', value: 'id' },
{ label: '姓名', value: 'name' },
{ label: '店铺名称', value: 'shopName' }
]"
/>
<cl-adv-btn />
</cl-row>
<cl-row>
<cl-table :row-class-name="oRowClassName" show-summary ref="Table"> </cl-table>
</cl-row>
<cl-row>
<el-button>11</el-button>
<cl-flex1></cl-flex1>
<cl-pagination :page-size="2"></cl-pagination>
</cl-row>
<!-- <cl-row>
{{ Table?.selection }}
</cl-row> -->
<upsert />
<cl-adv-search ref="AdvSearch"></cl-adv-search>
<cl-form ref="Form"></cl-form>
</cl-crud>
</div>
<cl-dialog
height="400px"
v-model="dialog.visible"
custom-class="xxx"
:append-to-body="true"
>
<div class="dialog">
<btn />
<p v-for="item in 100" :key="item">{{ item }}</p>
</div>
<template #footer>
<el-button>保存</el-button>
</template>
</cl-dialog>
</div>
</template>
<script lang="tsx" setup>
import { useCrud, useTable, useForm, useAdvSearch, useRefs, setFocus } from "../hooks";
import { registerFormHook } from "../index";
import ColumnT1 from "./column-t1.vue";
import { ContextMenu } from "../components/context-menu";
import { computed, reactive, onMounted, ref, nextTick } from "vue";
import Upsert from "./upsert.vue";
import FormCrud from "./form-crud.vue";
import Btn from "./btn.vue";
const { refs, setRefs } = useRefs();
const dialog = reactive<any>({
visible: false
});
const Crud = useCrud(
{
service: "test",
dict: {
label: {
add: "添加"
}
},
onDelete(selection, { next }) {
next({
ids: selection.map((e) => e.id)
});
}
},
(app) => {
app.refresh();
}
);
const options = reactive({
status: [
{
label: "启用",
type: "success",
value: 1
},
{
label: "禁用",
type: "danger",
value: 0
}
],
tags: [
{
label: "A",
value: 1
},
{
label: "B",
value: 2
}
]
});
const Table = useTable({
autoHeight: true,
defaultSort: {
prop: "status",
order: "ascending"
},
columns: [
{
type: "selection",
width: 80,
reserveSelection: true
},
() => {
return {
type: "expand",
prop: "detail"
};
},
{
label: "用户信息",
children: [
{
label: "编号",
prop: "id"
},
{
label: "姓名",
prop: "name"
}
]
},
{
label: "测试",
width: 250,
component: {
vm: ColumnT1
}
},
{
label: "标签",
prop: "tags",
dict: computed(() => {
return {
text: false,
separator: "-",
options: options.tags
};
})
},
{
label: "状态",
prop: "status",
sortable: "custom",
dict: computed(() => options.status)
},
{
label: "创建时间",
prop: "createTime",
sortable: "desc"
},
{
type: "op",
width: 350,
buttons({ scope }) {
return [
"info",
"edit",
"delete",
{
label: "测试",
hidden: false,
onClick() {}
}
];
}
}
]
});
function openDialog() {
dialog.visible = true;
}
const Form = useForm();
function openForm() {
Form.value?.open(
{
props: {
disabled: false
},
form: {
user: {
name: "神仙"
}
},
op: {
hidden: true
},
items: [
() => {
return {
label: "方法",
prop: "fx",
hook: "test",
component: {
name: "el-input"
}
};
}
],
on: {
open(data) {
// refs.name.focus();
},
submit(data, { done, close }) {
console.log(data);
close();
},
close(action, done) {
console.log("close action", action);
done();
}
}
},
[
setFocus(),
({ exposed, onOpen }) => {
console.log(exposed);
}
]
);
}
const AdvSearch = useAdvSearch({
items: [
{
label: "昵称",
prop: "name",
component: {
name: "el-input"
}
},
{
label: "日期",
prop: "date",
component: {
name: "el-date-picker",
props: {
type: "datetime"
}
}
}
]
});
function oRowClassName({ row }) {
// console.log("row-class-name", row);
}
registerFormHook("test", (value, { form, method, prop }) => {
if (method == "bind") {
return 1;
}
if (method == "submit") {
return 2;
}
});
onMounted(() => {
// AdvSearch.value?.open()
// Table.value?.setColumns([{ label: "xx", prop: "name" }]);
});
</script>
<style lang="scss">
* {
padding: 0;
margin: 0;
}
#app {
height: 100vh;
.demo {
height: 100%;
.wrap {
height: 100%;
padding: 10px;
box-sizing: border-box;
overflow: hidden;
}
.cm {
li {
height: 30px;
width: 200px;
border-bottom: 1px solid #eee;
}
}
}
}
.dialog {
height: 100%;
overflow: auto;
}
</style>

View File

@ -0,0 +1,24 @@
<template>
<el-button @click="onTest">测试</el-button>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useCrud } from "../hooks";
export default defineComponent({
name: "test-btn",
setup() {
const Crud = useCrud();
function onTest() {
Crud.value?.refresh();
}
return {
onTest
};
}
});
</script>

View File

@ -0,0 +1,25 @@
<template>
<div class="c">
<el-button @click="show">显示名称</el-button>
<el-button @click="hide">隐藏名称</el-button>
</div>
</template>
<script lang="ts" setup>
import { useCrud, useTable } from "../hooks";
const Crud = useCrud();
const Table = useTable();
function refresh() {
Crud.value?.refresh();
}
function hide() {
Table.value?.hideColumn(["name"]);
}
function show() {
Table.value?.showColumn(["name"]);
}
</script>

View File

@ -0,0 +1,52 @@
<template>
<el-button @click="open">内嵌crud</el-button>
<cl-dialog v-model="visible" title="内嵌crud">
<cl-crud name="22" ref="Crud" padding="0">
<cl-row>
<cl-refresh-btn></cl-refresh-btn>
<btn />
</cl-row>
<cl-row>
<cl-table :auto-height="false" ref="Table"></cl-table>
</cl-row>
<cl-row>
<cl-pagination></cl-pagination>
</cl-row>
</cl-crud>
</cl-dialog>
</template>
<script lang="ts" setup>
import { nextTick, ref } from "vue";
import { useCrud, useRefs, useTable } from "../hooks";
import Btn from "./btn.vue";
const { refs, setRefs } = useRefs();
const Table = useTable({
columns: [
{
label: "昵称",
prop: "name"
}
]
});
const Crud = useCrud(
{
service: "test"
},
(app) => {
app.refresh();
}
);
const visible = ref(false);
function open() {
visible.value = true;
}
</script>

View File

@ -0,0 +1,46 @@
<template>
<div class="c">
<!-- <el-button @click="submit">submit</el-button> -->
{{ Form?.form.t2.name }} - {{ disabled }}
<el-input v-model="val" @input="onChange"></el-input>
<el-button @click="add">a</el-button>
<el-button @click="remove">b</el-button>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from "vue";
import { useForm, useUpsert } from "../hooks";
const props = defineProps({
modelValue: Object,
title: String,
scope: null,
column: null,
disabled: Boolean
});
const emit = defineEmits(["update:modelValue"]);
const Upsert = useUpsert();
const Form = useForm();
const form = computed(() => Upsert.value?.form);
const val = ref<string>("");
function onChange(val: string) {
emit("update:modelValue", val);
}
function add() {
emit("update:modelValue", "1");
Form.value?.validateField("test");
console.log(Form.value?.getForm().name);
}
function remove() {
emit("update:modelValue", "");
Form.value?.validateField("test");
}
</script>

View File

@ -0,0 +1,58 @@
<template>
<el-row type="flex">
<el-input v-model="form.test" @input="onChange"></el-input>
<el-button @click="change">change</el-button>
<el-button @click="refresh">refresh</el-button>
</el-row>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useCrud, useForm, useUpsert } from "../hooks";
export default defineComponent({
name: "test",
props: {
modelValue: String
},
setup(props, { emit }) {
const Form = useForm();
const Crud = useCrud();
const Upsert = useUpsert({
onOpened(data) {
console.log(data);
}
});
function change() {
Form.value?.setForm("name", "🐑");
}
function reset() {
Form.value?.clear();
}
function refresh() {
Form.value?.close();
Crud.value?.refresh();
}
function onChange(val: string) {
console.log(val);
emit("update:modelValue", val);
emit("change", val);
}
return {
form: Form.value?.form,
change,
reset,
refresh,
onChange
};
}
});
</script>

View File

@ -0,0 +1,95 @@
<template>
<cl-upsert ref="Upsert">
<template #prepend="{ scope }">
<el-divider></el-divider>
</template>
<template #append="{ scope }">
<el-divider></el-divider>
</template>
</cl-upsert>
</template>
<script lang="tsx" setup>
import { setFocus, useRefs, useUpsert } from "../hooks";
const { refs, setRefs } = useRefs();
const Upsert = useUpsert({
items: [
{
label: "姓名",
renderLabel: () => {
return <p>1</p>;
},
prop: "name",
required: true,
component: {
name: "el-input"
}
},
{
type: "tabs",
props: {
type: "card",
labels: [
{
label: "基础",
value: "base"
},
{
label: "其他",
value: "other"
}
]
}
},
{
label: "年龄",
group: "base",
prop: "age",
component: {
name: "el-input-number"
}
},
{
label: "工作",
group: "other",
prop: "work",
component: {
name: "el-select",
options: []
}
}
],
plugins: [setFocus()],
onOpened() {
Upsert.value?.setOptions("work", [
{
label: "法师",
value: 1
},
{
label: "战士",
value: 2
}
]);
},
onClose(action, done) {
console.log("action", action);
done();
},
onClosed() {
console.log("closed");
},
onSubmit(data, { close }) {
console.log("submit", data);
close();
}
});
</script>

View File

@ -0,0 +1,33 @@
import { isObject } from "./utils";
export const crudList: ClCrud.Ref[] = [];
export const emitter: Emitter = {
list: [],
init(events) {
if (events && isObject(events)) {
for (const i in events) {
this.on(i, events[i]);
}
} else {
console.error("Events error");
}
},
emit(name, data) {
this.list.forEach((e: EmitterItem) => {
const [_name] = e.name.split("-");
if (name == _name) {
e.callback(data, {
crudList,
refresh(params) {
crudList.forEach((c) => c.refresh(params));
}
});
}
});
},
on(name, callback) {
this.list.push({ name, callback });
}
};

1
packages/crud/src/env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="../index" />

View File

@ -0,0 +1,38 @@
import { App, reactive } from "vue";
import { useEventListener } from "./core";
export declare type Browser = {
screen: string;
isMini: boolean;
};
export function useBrowser(app?: App) {
const browser = reactive<Browser>({
isMini: false,
screen: "full"
});
if (app) {
useEventListener("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";
});
app.provide("browser", browser);
}
return browser;
}

View File

@ -0,0 +1,78 @@
import { inject, reactive, isRef, useSlots } from "vue";
import { Browser } from "./browser";
import { isFunction } from "../utils";
export function useRefs() {
const refs = reactive<{ [key: string]: obj }>({});
function setRefs(name: string) {
return (el: any) => {
refs[name] = el;
};
}
return { refs, setRefs };
}
export function useGlobal() {
return inject("globalOptions") as GlobalOptions;
}
export function useTools() {
const browser = inject("browser") as Browser;
const global = useGlobal();
const slots = useSlots();
function getValue(data: any, params?: any) {
if (isRef(data)) {
return data.value;
} else {
if (isFunction(data)) {
return data(params);
} else {
return data;
}
}
}
return { browser, ...global, slots, getValue };
}
export function useCore() {
const crud = inject("crud") as ClCrud.Provide;
const mitt = inject("mitt") as Emitter;
return {
crud,
mitt
};
}
export function useElApi(keys: string[], el: any) {
const apis: { [key: string]: any } = {};
keys.forEach((e) => {
apis[e] = (...args: any[]) => {
return el.value[e](...args);
};
});
return apis;
}
export function useConfig({ props }: any) {
const config = reactive(props);
function setConfig(data: any) {
Object.assign(config, data);
}
return { setConfig, ...config };
}
export function useEventListener(name: string, cb: () => any) {
window.removeEventListener(name, cb);
window.addEventListener(name, cb);
cb();
}

View File

@ -0,0 +1,159 @@
import { watch, ref, nextTick, getCurrentInstance, Ref, inject, provide } from "vue";
import { TestService } from "../utils/test";
// 获取上级
function useParent(name: string, r: Ref) {
const d = getCurrentInstance();
if (d) {
let parent = d.proxy?.$.parent;
if (parent) {
while (parent && parent.type?.name != name && parent.type?.name != "cl-crud") {
parent = parent?.parent;
}
if (parent) {
if (parent.type.name == name) {
r.value = parent.exposed;
}
}
}
}
}
// 多事件
function useEvent(names: string[], { r, options, clear }: any) {
const d: any = {};
if (!r.__ev) r.__ev = {};
names.forEach((k) => {
if (!r.__ev[k]) r.__ev[k] = [];
if (options[k]) {
r.__ev[k].push(options[k]);
}
d[k] = (...args: any[]) => {
r.__ev[k].filter(Boolean).forEach((e: any) => {
e(...args);
});
if (clear == k) {
for (const i in r.__ev) {
r.__ev[i].splice(1, 999);
}
}
};
});
return d;
}
// crud
export function useCrud(options?: DeepPartial<ClCrud.Options>, cb?: (app: ClCrud.Ref) => void) {
const Crud = ref<ClCrud.Ref>();
useParent("cl-crud", Crud);
if (options) {
provide("useCrud__options", {
...options,
service: options.service == "test" ? TestService : options.service
});
}
watch(Crud, (val: any) => {
if (val) {
if (cb) {
cb(val);
}
}
});
return Crud;
}
// 新增、编辑
export function useUpsert(options?: DeepPartial<ClUpsert.Options>) {
const Upsert = ref<ClUpsert.Ref>();
useParent("cl-upsert", Upsert);
if (options) {
provide("useUpsert__options", options);
}
watch(
Upsert,
(val: any) => {
if (val) {
if (options) {
const event = useEvent(["onOpen", "onOpened", "onClosed"], {
r: val,
options,
clear: "onClosed"
});
Object.assign(val.config, event);
}
}
},
{
immediate: true
}
);
return Upsert;
}
// 表格
export function useTable(options?: DeepPartial<ClTable.Options>) {
const Table = ref<ClTable.Ref>();
useParent("cl-table", Table);
if (options) {
provide("useTable__options", options);
}
return Table;
}
// 表单
export function useForm(cb?: (app: ClForm.Ref) => void) {
const Form = ref<ClForm.Ref>();
useParent("cl-form", Form);
nextTick(() => {
if (cb && Form.value) {
cb(Form.value);
}
});
return Form;
}
// 高级搜索
export function useAdvSearch(options?: DeepPartial<ClAdvSearch.Options>) {
const AdvSearch = ref<ClAdvSearch.Ref>();
useParent("cl-adv-search", AdvSearch);
if (options) {
provide("useAdvSearch__options", options);
}
return AdvSearch;
}
// 对话框
export function useDialog(options?: { onFullscreen(visible: boolean): void }) {
const Dialog = inject("dialog") as ClDialog.Provide;
watch(
() => Dialog?.fullscreen.value,
(val: any) => {
options?.onFullscreen(val);
}
);
return Dialog;
}

View File

@ -0,0 +1,5 @@
export * from "./crud";
export * from "./core";
export * from "./browser";
export * from "./proxy";
export * from "./plugins";

View File

@ -0,0 +1,28 @@
import { useRefs } from "./core";
/**
* prop为空则默认第一个选项
* @param prop
* @returns
*/
export function setFocus(prop?: string): ClForm.Plugin {
const { refs, setRefs } = useRefs();
return ({ exposed, onOpen }) => {
const name = prop || exposed.config.items[0].prop;
if (name) {
exposed.config.items.find((e) => {
if (e.prop == name) {
if (e.component) {
e.component.ref = setRefs(name);
}
}
});
onOpen(() => {
refs[name]?.focus();
});
}
};
}

View File

@ -0,0 +1,30 @@
import { getCurrentInstance } from "vue";
import { useCore } from "./core";
import { isFunction } from "../utils";
export function useProxy(ctx: any) {
const { type }: any = getCurrentInstance();
const { mitt, crud } = useCore();
// 挂载
crud[type.name] = ctx;
// 事件
mitt.on("crud.proxy", ({ name, data = [], callback }: any) => {
if (ctx[name]) {
let d = null;
if (isFunction(ctx[name])) {
d = ctx[name](...data);
} else {
d = ctx[name];
}
if (callback) {
callback(d);
}
}
});
return ctx;
}

View File

@ -0,0 +1,97 @@
import { App } from "vue";
import { useComponent } from "./components";
import { useBrowser } from "./hooks";
import { emitter } from "./emitter";
import { deepMerge } from "./utils";
import temp from "./utils/temp";
import { locale } from "./locale";
import "./static/index.scss";
const Crud = {
install(app: App, options?: Options) {
// 兼容
if (options?.crud) {
options = {
...options.crud,
...options
};
}
// 合并配置
const config = deepMerge(
{
permission: {
update: true,
page: true,
info: true,
list: true,
add: true,
delete: true
},
dict: {
primaryId: "id",
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: locale.zhCn
},
style: {},
events: {},
render: {
autoHeight: true,
functionSlots: {
exclude: ["el-date-picker", "el-cascader", "el-time-select"]
}
}
},
options || {}
);
// 初始化事件
if (config.events) {
emitter.init(config.events);
}
// 全局配置
app.provide("globalOptions", config);
// 临时
temp.set("__crudApp__", app);
// 浏览器信息
useBrowser(app);
// 设置组件
useComponent(app);
return {
name: "cl-crud"
};
}
};
export default Crud;
export * from "./emitter";
export * from "./hooks/crud";
export * from "./hooks/plugins";
export * from "./locale";
export { registerFormHook } from "./utils/form-hook";
export { ContextMenu } from "./components/context-menu";

View File

@ -0,0 +1,23 @@
export default {
op: "Operation",
add: "Add",
delete: "Delete",
multiDelete: "Delete",
update: "Edit",
refresh: "Refresh",
info: "Info",
search: "Search",
reset: "Reset",
clear: "Clear",
save: "Save",
close: "Close",
confirm: "Confirm",
advSearch: "Advanced Search",
searchKey: "Search keyword",
placeholder: "Please enter ",
tips: "Warning",
saveSuccess: "Save successful",
deleteSuccess: "Delete successful",
deleteConfirm: "Will permanently delete the data, continue?",
empty: "Empty Data"
};

View File

@ -0,0 +1,7 @@
import en from "./en";
import zhCn from "./zh-cn";
export const locale = {
en,
zhCn
};

View File

@ -0,0 +1,23 @@
export default {
op: "操作",
add: "新增",
delete: "删除",
multiDelete: "删除",
update: "编辑",
refresh: "刷新",
info: "详情",
search: "搜索",
reset: "重置",
clear: "清空",
save: "保存",
close: "取消",
confirm: "确定",
advSearch: "高级搜索",
searchKey: "搜索关键字",
placeholder: "请输入",
tips: "提示",
saveSuccess: "保存成功",
deleteSuccess: "删除成功",
deleteConfirm: "此操作将永久删除选中数据,是否继续?",
empty: "暂无数据"
};

26
packages/crud/src/main.ts Normal file
View File

@ -0,0 +1,26 @@
import { createApp } from "vue";
import App from "./demo/app.vue";
import Crud, { locale } from "./index";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
const app = createApp(App);
app.use(ElementPlus)
.use(Crud, {
dict: {
sort: {
prop: "order",
order: "sort"
},
label: locale.en
},
style: {
// size: "default"
},
render: {
autoHeight: true
}
})
.mount("#app");

View File

@ -0,0 +1,689 @@
.cl-crud {
height: 100%;
overflow: auto;
position: relative;
box-sizing: border-box;
background-color: #fff;
&.is-border {
border: 1px solid #eee;
}
.el-input-number {
&__decrease,
&__increase {
border: 0;
background-color: transparent;
}
}
.cl-row {
width: 100%;
}
.el-row {
& > *:not(.cl-flex1):not(.cl-flex1) {
margin: 0 10px 10px 0;
&:last-child {
margin-right: 0;
}
}
&.cl-row--last {
margin-bottom: -10px;
}
}
}
.cl-flex1 {
flex: 1;
font-size: 12px;
margin: 0 !important;
}
.cl-search-key {
display: inline-flex;
&__select {
margin-right: 10px;
.el-input__inner {
width: 60px;
}
}
&__wrap {
display: inline-flex;
.el-input {
flex: 1;
}
.el-button {
margin-left: 10px;
}
}
}
.cl-table {
width: 100%;
.el-table {
&.el-loading-parent--relative {
box-sizing: border-box;
}
&__header {
.el-table__cell {
background-color: #f5f7fa !important;
color: #333;
.cell {
line-height: normal;
}
}
}
&__empty-block {
height: auto !important;
}
}
.el-loading-mask {
.el-loading-spinner {
.el-icon-loading {
font-size: 25px;
color: #000;
}
.el-loading-text {
color: #666;
margin-top: 5px;
}
}
}
&__op {
margin-bottom: -5px;
.el-button {
margin-bottom: 5px;
}
}
}
.cl-query {
margin: 0 10px;
ul {
display: inline-flex;
align-items: center;
height: 100%;
li {
background-color: #fff;
list-style: none;
font-size: 14px;
cursor: pointer;
color: #666;
white-space: nowrap;
&:hover {
color: var(--el-color-primary);
}
&.is-active {
color: var(--el-color-primary);
font-weight: bold;
}
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 {
margin-bottom: 0px !important;
.el-form--inline {
.el-form-item {
margin: 0 10px 10px 0;
.el-date-editor {
box-sizing: border-box;
.el-range-input {
&:nth-child(2) {
margin-left: 5px;
}
}
}
&:last-child {
margin-right: 0;
}
}
}
}
.cl-adv-btn {
margin-left: 10px;
.el-icon {
margin-right: 5px;
}
}
.cl-adv-search {
&.el-drawer {
background-color: #fff;
}
.el-drawer__body {
padding: 0;
}
&__header {
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
padding: 0 15px 0 20px;
user-select: none;
.text {
font-size: 16px;
}
.el-icon {
cursor: pointer;
&:hover {
color: red;
}
}
}
&__container {
height: calc(100% - 110px);
overflow-y: auto;
padding: 10px 20px;
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: 60px;
border-top: 1px solid #ebeef5;
padding: 0 10px;
box-sizing: border-box;
}
}
.cl-form {
[class*="el-col-"].is-guttered {
min-height: 0;
}
.el-form-item {
.el-input-number {
&__decrease,
&__increase {
border: 0;
background-color: transparent;
}
}
&__label {
.el-tooltip {
i {
margin-left: 5px;
}
}
}
&__content {
min-width: 0px;
& > div {
width: 100%;
}
}
}
&-item {
display: flex;
&__component {
display: flex;
&.flex1 {
flex: 1;
width: 100%;
& > div {
width: 100%;
}
}
}
&__prepend {
margin-right: 10px;
}
&__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;
}
}
.el-table__header tr {
line-height: normal;
}
}
&__footer {
display: flex;
justify-content: flex-end;
}
.cl-crud {
line-height: normal;
}
}
.cl-form-tabs {
border-bottom: 1px solid #dcdfe6;
overflow: hidden;
width: calc(100% - 10px);
margin: 0 5px 20px 5px;
&__wrap {
height: 35px;
width: 100%;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
position: relative;
&::-webkit-scrollbar {
display: none;
}
}
ul {
display: inline-flex;
white-space: nowrap;
li {
display: inline-flex;
align-items: center;
list-style: none;
padding: 0 20px;
height: 35px;
cursor: pointer;
.el-icon {
margin-right: 5px;
font-size: 16px;
}
&.is-active {
color: var(--el-color-primary);
}
}
}
&__line {
height: 3px;
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);
background-color: var(--el-color-primary);
}
&--card {
.cl-form-tabs__line {
display: none;
}
ul {
border: 1px solid #dcdfe6;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
li {
border-left: 1px solid #dcdfe6;
&:first-child {
border-left-color: transparent;
}
}
}
}
}
.cl-dialog {
border-radius: 5px;
.el-dialog {
&__header {
padding: 0;
text-align: center;
border-bottom: 1px solid #f7f7f7;
margin-right: 0;
user-select: none;
&-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;
box-sizing: border-box;
}
&__footer {
padding: 0;
}
}
&__header {
position: relative;
padding: 10px;
}
&__footer {
padding: 20px;
}
&__title {
display: block;
font-size: 15px;
text-align: center;
letter-spacing: 0.5px;
}
&__controls {
display: flex;
justify-content: flex-end;
position: absolute;
right: 0;
top: 0;
z-index: 9;
width: 100%;
&-icon,
.minimize,
.maximize,
.close {
display: flex;
align-items: center;
justify-content: center;
height: 40px;
width: 40px;
border: 0;
background-color: transparent;
cursor: pointer;
outline: none;
i {
font-size: 18px;
&:hover {
opacity: 0.7;
}
}
}
}
&.hidden-header {
.el-dialog__header {
display: none;
}
}
&.is-fixed {
&.is-fullscreen {
display: flex;
flex-direction: column;
height: 100vh !important;
border-radius: 0;
.cl-dialog__container {
height: 100% !important;
}
.el-dialog__body {
flex: 1;
height: 0px;
}
}
.el-dialog__body {
padding: 0;
}
}
}
.cl-context-menu {
position: absolute;
z-index: 9999;
&__box {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
width: 160px;
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;
&:first-child {
margin-top: 5px;
}
&:last-child {
margin-bottom: 5px;
}
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;
}
}
}
}
}
&__target {
position: relative;
&::after {
content: "";
display: block;
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.05);
}
}
}
.el-message,
.el-overlay.is-message-box {
z-index: 10000 !important;
}
// Element-ui Theme
.el-message {
&.el-message--success,
&.el-message--error,
&.el-message--info,
&.el-message--warning {
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;
}
}
}
@media only screen and (max-width: 768px) {
.el-message-box {
width: 90% !important;
}
.el-table {
&__body {
&-wrapper {
&::-webkit-scrollbar {
height: 6px;
width: 6px;
}
}
}
}
.cl-search-key {
width: 100%;
margin-right: 0 !important;
&__wrap {
width: 100% !important;
}
}
}
.theme {
.cl-dialog {
border-radius: 10px !important;
}
}

View File

@ -0,0 +1,145 @@
import { isArray, isFunction, isObject, isString } from "../utils";
export const format: { [key: string]: Hook.fn } = {
number(value) {
return value ? (isArray(value) ? value.map(Number) : Number(value)) : value;
},
string(value) {
return value ? (isArray(value) ? value.map(String) : String(value)) : value;
},
split(value) {
if (isString(value)) {
return value.split(",").filter(Boolean);
} else if (isArray(value)) {
return value;
} else {
return [];
}
},
join(value) {
return isArray(value) ? value.join(",") : value;
},
boolean(value) {
return Boolean(value);
},
booleanNumber(value) {
return value ? 1 : 0;
},
datetimeRange(value, { form, method, prop }) {
const key = prop.charAt(0).toUpperCase() + prop.slice(1);
const start = `start${key}`;
const end = `end${key}`;
if (method == "bind") {
return [form[start], form[end]];
} else {
const [startTime, endTime] = value || [];
form[start] = startTime;
form[end] = endTime;
return undefined;
}
},
splitJoin(value, { method }) {
if (method == "bind") {
return isString(value) ? value.split(",").filter(Boolean) : value;
} else {
return isArray(value) ? value.join(",") : value;
}
},
json(value, { method }) {
if (method == "bind") {
try {
return JSON.parse(value);
} catch (e) {
return {};
}
} else {
return JSON.stringify(value);
}
},
empty(value) {
if (isString(value)) {
return value === "" ? undefined : value;
}
return value;
}
};
function init({ value, form, prop }: any) {
if (prop) {
const [a, b] = prop.split("-");
if (b) {
form[prop] = form[a] ? form[a][b] : form[a];
} else {
form[prop] = value;
}
}
}
function parse(method: "submit" | "bind", { value, hook: pipe, form, prop }: any) {
init({ value, method, form, prop });
if (!pipe) {
return false;
}
let pipes = [];
if (isString(pipe)) {
if (format[pipe]) {
pipes = [pipe];
} else {
console.error(`Hook[${pipe}] is not found`);
}
} 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 error`);
}
let v = value;
pipes.forEach((e: any) => {
let f = null;
if (isString(e)) {
f = format[e];
} else if (isFunction(e)) {
f = e;
}
if (f) {
v = f(v, {
method,
form,
prop
});
}
});
if (prop) {
form[prop] = v;
}
}
const formHook = {
bind(data: any) {
parse("bind", data);
},
submit(data: any) {
parse("submit", data);
}
};
export function registerFormHook(name: string, fn: Hook.fn) {
format[name] = fn;
}
export default formHook;

View File

@ -0,0 +1,159 @@
import cloneDeep from "clone-deep";
import flat from "array.prototype.flat";
import merge from "merge";
import { mergeProps } from "vue";
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): 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(k);
}
} else {
n = e;
}
if (i != arr.length - 1) {
d = d[n];
} else {
if (isGet) {
return d[n];
} else {
if (isObject(value)) {
Object.assign(d[n], value);
} else {
d[n] = value;
}
}
}
}
return obj;
} catch (e) {
console.error("Format 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 function mergeConfig(a: any, b?: any): any {
return b ? mergeProps(a, b) : a;
}
export function debounce(fn: (...args: any[]) => any, delay: number, immediate?: boolean) {
let timer: any;
let result;
return function (this: any, ...args: any[]) {
if (timer) clearTimeout(timer);
if (immediate) {
if (timer) {
timer = setTimeout(() => (timer = null), delay);
} else {
result = fn.apply(this, args);
return result;
}
} else {
timer = setTimeout(() => fn.apply(this, args), delay);
}
};
}
export function addClass(el: Element, name: string) {
if (isString(el?.className)) {
const f = el.className.includes(name);
if (!f) {
el.className = el.className + " " + name;
}
}
}
export function removeClass(el: Element, name: string) {
if (isString(el?.className)) {
el.className = el.className.replace(name, "");
}
}
export { cloneDeep, flat, merge };

View File

@ -0,0 +1,30 @@
import mitt from "mitt";
const ev = mitt();
class Mitt {
id: number;
constructor(id?: number) {
this.id = id || 0;
}
send(type: "emit" | "off" | "on", name: string, ...args: any[]) {
// @ts-ignore
ev[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 Mitt;

View File

@ -0,0 +1,192 @@
import { h, ref } from "vue";
import { useCore, useTools } from "../hooks";
import { isBoolean, isFunction, isArray, isString, isObject } from "./index";
import { renderNode } from "./vnode";
/**
* form.hidden
*/
export function parseFormHidden(value: any, { scope }: any) {
if (isBoolean(value)) {
return value;
} else if (isFunction(value)) {
return value({ scope });
}
return false;
}
/**
* table.dict
*/
export function parseTableDict(value: any, data: any) {
// 匹配列表
let list: any = [];
// 是否文本显示
let isText = false;
// 分割符
let separator = ",";
if (data?.options) {
list = data.options;
isText = data.text;
separator = data.separator;
} else {
list = data;
}
// 多个值
const values: any[] = isArray(value) ? value : [value];
// 返回值
const arr = values.map((v) => {
const d = list.find((d: any) => d.value == v);
if (d) {
return isText
? d.label
: h(<el-tag disable-transitions effect="dark" style="margin: 2px"></el-tag>, d, {
default: () => d.label
});
} else {
return v;
}
});
return isText ? arr.join(separator) : arr;
}
/**
* table.op.buttons
*/
export function parseTableOpButtons(buttons: any, { scope }: any) {
const { style, getValue, slots } = useTools();
const { crud } = useCore();
const list = getValue(buttons, { scope }) || ["edit", "delete"];
return list.map((vnode: any) => {
if (vnode === "info") {
return (
<el-button
text
bg
size={style.size}
v-show={crud.getPermission("info")}
onClick={() => {
crud.rowInfo(scope.row);
}}>
{crud.dict.label?.info}
</el-button>
);
} else if (vnode === "edit") {
return (
<el-button
text
bg
type="primary"
size={style.size}
v-show={crud.getPermission("update")}
onClick={() => {
crud.rowEdit(scope.row);
}}>
{crud.dict.label?.update}
</el-button>
);
} else if (vnode === "delete") {
return (
<el-button
text
bg
type="danger"
size={style.size}
v-show={crud.getPermission("delete")}
onClick={() => {
crud.rowDelete(scope.row);
}}>
{crud.dict.label?.delete}
</el-button>
);
} else {
return (
!vnode.hidden &&
renderNode(vnode, {
scope,
slots,
custom(vnode) {
return (
<el-button
text
type={vnode.type}
bg
onClick={() => {
vnode.onClick({ scope });
}}>
{vnode.label}
</el-button>
);
}
})
);
}
});
}
/**
*
*/
export function parseExtensionComponent(vnode: any) {
const { getValue } = useTools();
if (["el-select", "el-radio-group", "el-checkbox-group"].includes(vnode.name)) {
const list = getValue(vnode.options) || [];
const children = (
<div>
{list.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 (
<el-radio key={i} label={e.value} {...e.props}>
{e.label}
</el-radio>
);
}
// 多选框组
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 {
children
};
} else {
return {};
}
}

View File

@ -0,0 +1,17 @@
// @ts-nocheck
import { App } from "vue";
export default {
get vue(): App {
return window.__crudApp__;
},
get(key: string) {
return window[key];
},
set(key: string, value: any) {
window[key] = value;
}
};

View File

@ -0,0 +1,143 @@
let id = 10;
export const UserList = [
{
id: 1,
name: "刘一",
createTime: "2019年09月02日",
price: 75.99,
status: 1,
hook: "1,2",
user: {
name: "神仙"
},
t2: JSON.stringify({ name: "icssoa" }),
tags: [1, 3]
},
{
id: 2,
name: "陈二",
createTime: "2019年09月05日",
price: 242.1,
status: 1,
tags: [2, 3]
},
{
id: 3,
name: "张三",
createTime: "2019年09月12日",
price: 74.11,
status: 0,
tags: [1, 2]
},
{
id: 4,
name: "李四",
createTime: "2019年09月13日",
price: 276.64,
status: 0,
tags: [1, 2, 3]
},
{
id: 5,
name: "王五",
createTime: "2019年09月18日",
price: 160.23,
status: 1,
tags: [2]
}
];
export const TestService = {
page: (p: any) => {
console.log("POST[page]", p);
let total = 0;
const list = UserList.filter((e, i) => {
if (p.name) {
return e.name.includes(p.name);
}
if (![undefined, null, ""].includes(p.status)) {
return e.status === p.status;
}
total++;
if (i >= (p.page - 1) * p.size && i < p.page * p.size) {
return true;
} else {
return false;
}
});
return new Promise((resolve) => {
setTimeout(() => {
resolve({
list,
pagination: {
page: p.page,
size: p.size,
total
}
});
}, 50);
});
},
list: (p: any) => {
console.log("POST[list]", p);
return new Promise((resolve) => {
resolve(UserList);
});
},
info: (d: any) => {
console.log("GET[info]", d);
return new Promise((resolve) => {
const user = UserList.find((e) => e.id == d.id);
setTimeout(() => {
resolve({
...user,
startTime: "2021-12-02 00:00:00",
endTime: "2021-12-12 00:00:00",
hook: "1,2",
user: {
name: user?.name
}
});
}, 500);
});
},
add: (d: any) => {
console.log("POST[add]", d);
UserList.push({
...d,
id: id++,
createTime: new Date()
});
return Promise.resolve();
},
delete: (d: any) => {
console.log("POST[delete]", d);
return new Promise((resolve, reject) => {
d.ids.forEach((id: any) => {
const index = UserList.findIndex((e) => e.id == id);
UserList.splice(index, 1);
});
setTimeout(() => {
resolve(true);
}, 1000);
});
},
update: (d: any) => {
console.log("POST[update]", d);
const item = UserList.find((e) => e.id == d.id);
if (item) {
Object.assign(item, d);
}
return Promise.resolve();
}
};

View File

@ -0,0 +1,186 @@
import { h, resolveComponent, toRaw, VNode } from "vue";
import { useGlobal } from "../hooks";
import { isFunction, isString, isObject } from "./index";
import { parseExtensionComponent } from "./parse";
import temp from "./temp";
// 配置
interface Options {
// 标识
prop?: string;
// 数据值
scope?: any;
// 当前行
item?: any;
// 插槽
slots?: any;
// 自定义
custom?: (vnode: any) => any;
// 渲染方式
render?: "slot" | null;
// 其他
[key: string]: any;
}
// 临时注册组件列表
const regs: Map<string, any> = new Map();
// 解析节点
export function parseNode(vnode: any, options: Options): VNode {
const { scope, prop, slots, children, _data } = options || [];
const global = useGlobal();
// 渲染后组件
let comp: VNode | null = null;
// 插槽模式渲染
if (vnode.name.includes("slot-")) {
const rn = slots[vnode.name];
if (rn) {
return rn({ scope, ...options._data });
} else {
return <cl-error-message title={`${vnode.name} is not found`} />;
}
}
// 实例模式下,先注册到全局,再分解组件渲染
if (vnode.vm && !regs.get(vnode.name)) {
temp.vue.component(vnode.name, { ...vnode.vm });
regs.set(vnode.name, { ...vnode.vm });
}
// 处理 props
if (isFunction(vnode.props)) {
vnode.props = vnode.props({ scope, ...options._data });
}
// 组件参数
const props = {
...vnode.props,
..._data,
scope
};
// 是否禁用
props.disabled = _data?.isDisabled || props.disabled;
// 添加双向绑定
if (props && scope) {
if (prop) {
props.modelValue = scope[prop];
props["onUpdate:modelValue"] = function (val: any) {
scope[prop] = val;
};
}
}
// 组件实例渲染
if (vnode.vm) {
comp = h(regs.get(vnode.name), props);
} else {
// 是否函数式插槽
const isFunctionSlot =
!global.render.functionSlots.exclude?.includes(vnode.name) &&
(vnode.functionSlot === undefined ? true : vnode.functionSlot);
// 渲染组件
comp = h(
toRaw(resolveComponent(vnode.name)),
props,
isFunctionSlot ? () => children : children
);
}
// 挂载到 refs 中
if (isFunction(vnode.ref)) {
setTimeout(() => {
vnode.ref(comp?.component?.exposed);
}, 0);
}
return comp;
}
// 渲染节点
export function renderNode(vnode: any, options: Options) {
const { item, scope, _data, render } = options || {};
if (!vnode) {
return null;
}
if (vnode.__v_isVNode) {
return vnode;
}
// 默认参数配置
if (item) {
if (item.component) {
if (!item.component.props) {
item.component.props = {};
}
// 占位符
let placeholder = "";
switch (item.component?.name) {
case "el-input":
placeholder = "请填写";
break;
case "el-select":
placeholder = "请选择";
break;
default:
break;
}
if (placeholder) {
if (!item.component.props.placeholder) {
item.component.props.placeholder = placeholder + item.label;
}
}
}
}
// 组件实例
if (vnode.vm) {
if (!vnode.name) {
vnode.name = vnode.vm?.name || vnode.vm?.__hmrId;
}
return parseNode(vnode, options);
}
// 组件名渲染
if (isString(vnode)) {
if (render == "slot") {
if (!vnode.includes("slot-")) {
return vnode;
}
}
return parseNode({ name: vnode }, options);
}
// 方法回调
if (isFunction(vnode)) {
return vnode({ scope, h, ..._data });
}
// jsx 模式
if (isObject(vnode)) {
if (vnode.name) {
const { children } = parseExtensionComponent(vnode);
return parseNode(vnode, { ...options, children });
} else {
if (options.custom) {
return options.custom(vnode);
}
return <cl-error-message title={`组件渲染失败,组件 name 不能为空`} />;
}
}
}

View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": false,
"declaration": true,
"declarationDir": "types",
"inlineSourceMap": false,
"disableSizeLimit": true,
"baseUrl": ".",
"outDir": "dist",
"types": ["webpack-env"],
"paths": {
"@/*": ["src/*"]
},
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "src/demo/*", "src/main.ts", "src/components/*"]
}

View File

@ -0,0 +1,2 @@
declare const _default: import("vue").DefineComponent<{}, () => false | JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -0,0 +1,2 @@
declare const _default: import("vue").DefineComponent<{}, () => JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -0,0 +1,50 @@
/// <reference types="../index" />
import { PropType } from "vue";
declare const _default: import("vue").DefineComponent<{
items: {
type: PropType<ClForm.Item[]>;
default: () => never[];
};
title: StringConstructor;
size: {
type: (NumberConstructor | StringConstructor)[];
default: string;
};
op: {
type: ArrayConstructor;
default: () => string[];
};
onSearch: FunctionConstructor;
}, {
open: () => void;
close: () => void;
reset: () => void;
clear: () => void;
search: () => void;
Drawer: import("vue").Ref<any>;
Form: import("vue").Ref<ClForm.Ref | undefined>;
visible: import("vue").Ref<boolean>;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("clear" | "reset")[], "clear" | "reset", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
items: {
type: PropType<ClForm.Item[]>;
default: () => never[];
};
title: StringConstructor;
size: {
type: (NumberConstructor | StringConstructor)[];
default: string;
};
op: {
type: ArrayConstructor;
default: () => string[];
};
onSearch: FunctionConstructor;
}>> & {
onReset?: ((...args: any[]) => any) | undefined;
onClear?: ((...args: any[]) => any) | undefined;
}, {
items: ClForm.Item[];
op: unknown[];
size: string | number;
}>;
export default _default;

View File

@ -0,0 +1,2 @@
declare const _default: import("vue").DefineComponent<{}, () => JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -0,0 +1,41 @@
/// <reference types="../index" />
import { PropType } from "vue";
declare const _default: import("vue").DefineComponent<{
items: {
type: PropType<ClForm.Item[]>;
default: () => never[];
};
title: StringConstructor;
size: {
type: (NumberConstructor | StringConstructor)[];
default: string;
};
op: {
type: ArrayConstructor;
default: () => string[];
};
onSearch: FunctionConstructor;
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("clear" | "reset")[], "clear" | "reset", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
items: {
type: PropType<ClForm.Item[]>;
default: () => never[];
};
title: StringConstructor;
size: {
type: (NumberConstructor | StringConstructor)[];
default: string;
};
op: {
type: ArrayConstructor;
default: () => string[];
};
onSearch: FunctionConstructor;
}>> & {
onReset?: ((...args: any[]) => any) | undefined;
onClear?: ((...args: any[]) => any) | undefined;
}, {
items: ClForm.Item[];
op: unknown[];
size: string | number;
}>;
export default _default;

View File

@ -0,0 +1,30 @@
/// <reference types="../index" />
declare const ClContextMenu: import("vue").DefineComponent<{
show: BooleanConstructor;
options: {
type: ObjectConstructor;
default: () => {};
};
event: {
type: ObjectConstructor;
default: () => {};
};
}, () => false | JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
show: BooleanConstructor;
options: {
type: ObjectConstructor;
default: () => {};
};
event: {
type: ObjectConstructor;
default: () => {};
};
}>>, {
options: Record<string, any>;
show: boolean;
event: Record<string, any>;
}>;
export declare const ContextMenu: {
open(event: any, options: ClContextMenu.Options): void;
};
export default ClContextMenu;

View File

@ -0,0 +1,23 @@
/// <reference types="../index" />
import Mitt from "../../utils/mitt";
interface Options {
mitt: Mitt;
config: ClCrud.Config;
crud: ClCrud.Ref;
}
export declare function useHelper({ mitt, config, crud }: Options): {
proxy: (name: string, data?: any[]) => void;
set: (key: string, value: any) => void;
on: (name: string, callback: fn) => void;
rowInfo: (data: any) => void;
rowAdd: () => void;
rowEdit: (data: any) => void;
rowAppend: (data: any) => void;
rowDelete: (...selection: any[]) => void;
rowClose: () => void;
refresh: (params?: obj) => Promise<unknown>;
getPermission: (key: "page" | "list" | "info" | "update" | "add" | "delete") => boolean;
paramsReplace: (params: obj) => any;
getParams: () => obj;
};
export {};

View File

@ -0,0 +1,19 @@
declare const _default: import("vue").DefineComponent<{
name: StringConstructor;
border: BooleanConstructor;
padding: {
type: StringConstructor;
default: string;
};
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
name: StringConstructor;
border: BooleanConstructor;
padding: {
type: StringConstructor;
default: string;
};
}>>, {
border: boolean;
padding: string;
}>;
export default _default;

View File

@ -0,0 +1,70 @@
declare const _default: import("vue").DefineComponent<{
modelValue: {
type: BooleanConstructor;
default: boolean;
};
props: ObjectConstructor;
customClass: StringConstructor;
title: {
type: StringConstructor;
default: string;
};
height: {
type: StringConstructor;
default: null;
};
width: {
type: StringConstructor;
default: string;
};
keepAlive: BooleanConstructor;
fullscreen: BooleanConstructor;
controls: {
type: ArrayConstructor;
default: () => string[];
};
hideHeader: BooleanConstructor;
beforeClose: FunctionConstructor;
}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "fullscreen-change")[], "update:modelValue" | "fullscreen-change", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
modelValue: {
type: BooleanConstructor;
default: boolean;
};
props: ObjectConstructor;
customClass: StringConstructor;
title: {
type: StringConstructor;
default: string;
};
height: {
type: StringConstructor;
default: null;
};
width: {
type: StringConstructor;
default: string;
};
keepAlive: BooleanConstructor;
fullscreen: BooleanConstructor;
controls: {
type: ArrayConstructor;
default: () => string[];
};
hideHeader: BooleanConstructor;
beforeClose: FunctionConstructor;
}>> & {
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
"onFullscreen-change"?: ((...args: any[]) => any) | undefined;
}, {
height: string;
title: string;
width: string;
keepAlive: boolean;
hideHeader: boolean;
controls: unknown[];
fullscreen: boolean;
modelValue: boolean;
}>;
export default _default;

View File

@ -0,0 +1,6 @@
declare const _default: import("vue").DefineComponent<{
title: StringConstructor;
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
title: StringConstructor;
}>>, {}>;
export default _default;

View File

@ -0,0 +1,41 @@
/// <reference types="../index" />
import { PropType } from "vue";
declare const _default: import("vue").DefineComponent<{
data: {
type: ObjectConstructor;
default: () => {};
};
items: {
type: PropType<ClForm.Item[]>;
default: () => never[];
};
resetBtn: {
type: BooleanConstructor;
default: boolean;
};
onSearch: FunctionConstructor;
}, {
Form: import("vue").Ref<ClForm.Ref | undefined>;
loading: import("vue").Ref<boolean>;
search: () => void;
reset: () => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
data: {
type: ObjectConstructor;
default: () => {};
};
items: {
type: PropType<ClForm.Item[]>;
default: () => never[];
};
resetBtn: {
type: BooleanConstructor;
default: boolean;
};
onSearch: FunctionConstructor;
}>>, {
items: ClForm.Item[];
data: Record<string, any>;
resetBtn: boolean;
}>;
export default _default;

View File

@ -0,0 +1,6 @@
declare const _default: import("vue").DefineComponent<{
label: StringConstructor;
}, unknown, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
label: StringConstructor;
}>>, {}>;
export default _default;

View File

@ -0,0 +1,36 @@
/// <reference types="../index" />
import { PropType } from "vue";
declare const _default: import("vue").DefineComponent<{
data: {
type: ObjectConstructor;
default: () => {};
};
items: {
type: PropType<ClForm.Item[]>;
default: () => never[];
};
resetBtn: {
type: BooleanConstructor;
default: boolean;
};
onSearch: FunctionConstructor;
}, () => true | JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
data: {
type: ObjectConstructor;
default: () => {};
};
items: {
type: PropType<ClForm.Item[]>;
default: () => never[];
};
resetBtn: {
type: BooleanConstructor;
default: boolean;
};
onSearch: FunctionConstructor;
}>>, {
items: ClForm.Item[];
data: Record<string, any>;
resetBtn: boolean;
}>;
export default _default;

View File

@ -0,0 +1,6 @@
declare const _default: import("vue").DefineComponent<{
label: StringConstructor;
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
label: StringConstructor;
}>>, {}>;
export default _default;

View File

@ -0,0 +1,2 @@
declare const _default: import("vue").DefineComponent<{}, () => JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -0,0 +1,59 @@
import { PropType } from "vue";
declare const _default: import("vue").DefineComponent<{
modelValue: (NumberConstructor | StringConstructor)[];
labels: {
type: ArrayConstructor;
default: () => never[];
};
justify: {
type: StringConstructor;
default: string;
};
color: {
type: StringConstructor;
default: string;
};
type: {
type: PropType<"default" | "card">;
default: string;
};
}, {
active: import("vue").Ref<string>;
list: import("vue").Ref<any[]>;
line: {
width: string;
offsetLeft: string;
transform: string;
backgroundColor: string;
};
refs: any;
setRefs: (index: number) => (el: HTMLElement) => void;
update: (val: any) => false | undefined;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "change")[], "update:modelValue" | "change", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
modelValue: (NumberConstructor | StringConstructor)[];
labels: {
type: ArrayConstructor;
default: () => never[];
};
justify: {
type: StringConstructor;
default: string;
};
color: {
type: StringConstructor;
default: string;
};
type: {
type: PropType<"default" | "card">;
default: string;
};
}>> & {
onChange?: ((...args: any[]) => any) | undefined;
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
}, {
type: "default" | "card";
labels: unknown[];
justify: string;
color: string;
}>;
export default _default;

View File

@ -0,0 +1,38 @@
import { PropType } from "vue";
declare const _default: import("vue").DefineComponent<{
modelValue: (NumberConstructor | StringConstructor)[];
labels: {
type: ArrayConstructor;
default: () => never[];
};
justify: {
type: PropType<"center" | "justify" | "left" | "right" | "end" | "start" | "match-parent">;
default: string;
};
type: {
type: PropType<"default" | "card">;
default: string;
};
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("change" | "update:modelValue")[], "change" | "update:modelValue", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
modelValue: (NumberConstructor | StringConstructor)[];
labels: {
type: ArrayConstructor;
default: () => never[];
};
justify: {
type: PropType<"center" | "justify" | "left" | "right" | "end" | "start" | "match-parent">;
default: string;
};
type: {
type: PropType<"default" | "card">;
default: string;
};
}>> & {
onChange?: ((...args: any[]) => any) | undefined;
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
}, {
type: "default" | "card";
labels: unknown[];
justify: "center" | "justify" | "left" | "right" | "end" | "start" | "match-parent";
}>;
export default _default;

View File

@ -0,0 +1,44 @@
/// <reference types="../index" />
import { Ref } from "vue";
declare type Config = ClForm.Config;
declare type Form = Vue.Ref<any>;
export declare function useAction({ config, form, Form }: {
config: Config;
form: obj;
Form: Form;
}): {
getForm: (prop: string) => any;
setForm: (prop: string, value: any) => void;
setData: (prop: string, value: any) => void;
setConfig: (path: string, value: any) => void;
setOptions: (prop: string, value: any[]) => void;
setProps: (prop: string, value: any) => void;
toggleItem: (prop: string, value?: boolean) => void;
hideItem: (...props: string[]) => void;
showItem: (...props: string[]) => void;
setTitle: (value: string) => void;
collapseItem: (e: any) => void;
};
export declare function useTabs({ config, Form }: {
config: Config;
Form: Form;
}): {
active: Ref<any>;
get: () => ClForm.Item | undefined;
set: (data: any) => void;
change: (value: any, isValid?: boolean) => Promise<unknown>;
clear: () => void;
mergeProp: (item: ClForm.Item) => void;
};
export declare function useApi({ Form }: {
Form: Form;
}): {
[key: string]: any;
};
export declare function usePlugins({ visible }: {
visible: Ref<boolean>;
}): {
create: (plugins?: ClForm.Plugin[]) => void;
submit: (data: any) => Promise<any>;
};
export {};

View File

@ -0,0 +1,15 @@
declare const _default: import("vue").DefineComponent<{
inner: BooleanConstructor;
inline: BooleanConstructor;
customClass: StringConstructor;
}, () => false | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
inner: BooleanConstructor;
inline: BooleanConstructor;
customClass: StringConstructor;
}>>, {
inner: boolean;
inline: boolean;
}>;
export default _default;

View File

@ -0,0 +1,5 @@
import { App } from "vue";
export declare const components: {
[key: string]: any;
};
export declare function useComponent(app: App): void;

View File

@ -0,0 +1,2 @@
declare const _default: import("vue").DefineComponent<{}, () => false | JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -0,0 +1,9 @@
declare const _default: import("vue").DefineComponent<{}, {
total: import("vue").Ref<number>;
currentPage: import("vue").Ref<number>;
pageSize: import("vue").Ref<number>;
onCurrentChange: (index: number) => void;
onSizeChange: (size: number) => void;
setPagination: (res: any) => void;
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -0,0 +1,4 @@
declare const _default: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -0,0 +1,39 @@
declare const _default: import("vue").DefineComponent<{
modelValue: null;
list: {
type: ArrayConstructor;
required: true;
};
field: {
type: StringConstructor;
default: string;
};
multiple: BooleanConstructor;
callback: FunctionConstructor;
}, {
list: import("vue").Ref<{
label: string;
value: any;
active: boolean;
}[]>;
selectItem: (event: any, item: any) => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "change")[], "update:modelValue" | "change", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
modelValue: null;
list: {
type: ArrayConstructor;
required: true;
};
field: {
type: StringConstructor;
default: string;
};
multiple: BooleanConstructor;
callback: FunctionConstructor;
}>> & {
onChange?: ((...args: any[]) => any) | undefined;
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
}, {
field: string;
multiple: boolean;
}>;
export default _default;

View File

@ -0,0 +1,32 @@
declare const _default: import("vue").DefineComponent<{
modelValue: null;
list: {
type: ArrayConstructor;
required: true;
};
field: {
type: StringConstructor;
default: string;
};
multiple: BooleanConstructor;
callback: FunctionConstructor;
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("change" | "update:modelValue")[], "change" | "update:modelValue", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
modelValue: null;
list: {
type: ArrayConstructor;
required: true;
};
field: {
type: StringConstructor;
default: string;
};
multiple: BooleanConstructor;
callback: FunctionConstructor;
}>> & {
onChange?: ((...args: any[]) => any) | undefined;
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
}, {
field: string;
multiple: boolean;
}>;
export default _default;

View File

@ -0,0 +1,2 @@
declare const _default: import("vue").DefineComponent<{}, () => JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -0,0 +1,2 @@
declare const _default: import("vue").DefineComponent<{}, () => JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
export default _default;

View File

@ -0,0 +1,61 @@
import { PropType } from "vue";
declare const _default: import("vue").DefineComponent<{
modelValue: StringConstructor;
field: {
type: StringConstructor;
default: string;
};
fieldList: {
type: PropType<{
label: string;
value: string;
}[]>;
default: () => never[];
};
onSearch: FunctionConstructor;
placeholder: StringConstructor;
width: {
type: StringConstructor;
default: string;
};
}, {
value: import("vue").Ref<string>;
placeholder: import("vue").ComputedRef<string>;
selectField: import("vue").Ref<string>;
search: (this: any, ...args: any[]) => any;
onKeydown: ({ keyCode }: any) => void;
onInput: (val: string) => void;
onChange: () => void;
onFieldChange: () => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "change" | "field-change")[], "update:modelValue" | "change" | "field-change", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
modelValue: StringConstructor;
field: {
type: StringConstructor;
default: string;
};
fieldList: {
type: PropType<{
label: string;
value: string;
}[]>;
default: () => never[];
};
onSearch: FunctionConstructor;
placeholder: StringConstructor;
width: {
type: StringConstructor;
default: string;
};
}>> & {
onChange?: ((...args: any[]) => any) | undefined;
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
"onField-change"?: ((...args: any[]) => any) | undefined;
}, {
width: string;
field: string;
fieldList: {
label: string;
value: string;
}[];
}>;
export default _default;

View File

@ -0,0 +1,52 @@
import { PropType } from "vue";
declare const _default: import("vue").DefineComponent<{
modelValue: StringConstructor;
field: {
type: StringConstructor;
default: string;
};
fieldList: {
type: PropType<{
label: string;
value: string;
}[]>;
default: () => never[];
};
onSearch: FunctionConstructor;
placeholder: StringConstructor;
width: {
type: StringConstructor;
default: string;
};
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("change" | "update:modelValue" | "field-change")[], "change" | "update:modelValue" | "field-change", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
modelValue: StringConstructor;
field: {
type: StringConstructor;
default: string;
};
fieldList: {
type: PropType<{
label: string;
value: string;
}[]>;
default: () => never[];
};
onSearch: FunctionConstructor;
placeholder: StringConstructor;
width: {
type: StringConstructor;
default: string;
};
}>> & {
onChange?: ((...args: any[]) => any) | undefined;
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
"onField-change"?: ((...args: any[]) => any) | undefined;
}, {
width: string;
field: string;
fieldList: {
label: string;
value: string;
}[];
}>;
export default _default;

View File

@ -0,0 +1,74 @@
/// <reference types="../index" />
declare type Emit = (name: "selection-change" | "sort-change", ...args: any[]) => void;
declare type Table = Vue.Ref<any>;
declare type Config = ClTable.Config;
declare interface Sort {
defaultSort: {
prop?: string;
order?: string;
};
changeSort(prop: string, order: string): void;
}
export declare function useSort({ config, emit, Table }: {
config: Config;
emit: Emit;
Table: Table;
}): {
defaultSort: {
prop: string;
order: "descending" | "ascending";
} | {
prop?: undefined;
order?: undefined;
};
onSortChange: ({ prop, order }: {
prop: string | undefined;
order: string;
}) => void;
changeSort: (prop: string, order: string) => void;
};
export declare function useRow({ Table, config, Sort }: {
Table: Table;
config: Config;
Sort: Sort;
}): {
onRowContextMenu: (row: any, column: any, event: any) => void;
};
export declare function useHeight({ config, Table }: {
Table: Table;
config: Config;
}): {
isAuto: import("vue").ComputedRef<boolean>;
maxHeight: import("vue").Ref<number>;
calcMaxHeight: (this: any, ...args: any[]) => any;
};
export declare function useSelection({ emit }: {
emit: Emit;
}): {
selection: obj[];
onSelectionChange: (selection: any[]) => void;
};
export declare function useData({ config, Table }: {
config: Config;
Table: Table;
}): {
data: import("vue").Ref<any[]>;
setData: (list: any[]) => void;
};
export declare function useOp({ config }: {
config: Config;
}): {
visible: import("vue").Ref<boolean>;
reBuild: (cb?: fn) => Promise<void>;
showColumn: (prop: string | string[], status?: boolean) => void;
hideColumn: (prop: string | string[]) => void;
setColumns: (list: ClTable.Column[]) => void;
};
export declare function useRender(): {
renderColumn: (columns: ClTable.Column[]) => (import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}> | null)[];
renderEmpty: (emptyText: String) => JSX.Element;
renderAppend: () => JSX.Element;
};
export {};

View File

@ -0,0 +1,54 @@
declare const _default: import("vue").DefineComponent<{
columns: {
type: ArrayConstructor;
default: () => never[];
};
autoHeight: {
type: BooleanConstructor;
default: null;
};
height: null;
contextMenu: ArrayConstructor;
defaultSort: ObjectConstructor;
sortRefresh: {
type: BooleanConstructor;
default: boolean;
};
emptyText: StringConstructor;
rowKey: {
type: StringConstructor;
default: string;
};
}, () => false | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("selection-change" | "sort-change")[], "selection-change" | "sort-change", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
columns: {
type: ArrayConstructor;
default: () => never[];
};
autoHeight: {
type: BooleanConstructor;
default: null;
};
height: null;
contextMenu: ArrayConstructor;
defaultSort: ObjectConstructor;
sortRefresh: {
type: BooleanConstructor;
default: boolean;
};
emptyText: StringConstructor;
rowKey: {
type: StringConstructor;
default: string;
};
}>> & {
"onSelection-change"?: ((...args: any[]) => any) | undefined;
"onSort-change"?: ((...args: any[]) => any) | undefined;
}, {
columns: unknown[];
autoHeight: boolean;
sortRefresh: boolean;
rowKey: string;
}>;
export default _default;

View File

@ -0,0 +1,38 @@
declare const _default: import("vue").DefineComponent<{
items: {
type: ArrayConstructor;
default: () => never[];
};
props: ObjectConstructor;
sync: BooleanConstructor;
op: ObjectConstructor;
dialog: ObjectConstructor;
onOpen: FunctionConstructor;
onOpened: FunctionConstructor;
onClose: FunctionConstructor;
onClosed: FunctionConstructor;
onInfo: FunctionConstructor;
onSubmit: FunctionConstructor;
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("opened" | "closed")[], "opened" | "closed", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
items: {
type: ArrayConstructor;
default: () => never[];
};
props: ObjectConstructor;
sync: BooleanConstructor;
op: ObjectConstructor;
dialog: ObjectConstructor;
onOpen: FunctionConstructor;
onOpened: FunctionConstructor;
onClose: FunctionConstructor;
onClosed: FunctionConstructor;
onInfo: FunctionConstructor;
onSubmit: FunctionConstructor;
}>> & {
onOpened?: ((...args: any[]) => any) | undefined;
onClosed?: ((...args: any[]) => any) | undefined;
}, {
items: unknown[];
sync: boolean;
}>;
export default _default;

3
packages/crud/types/emitter.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
/// <reference types="../index" />
export declare const crudList: ClCrud.Ref[];
export declare const emitter: Emitter;

View File

@ -0,0 +1,9 @@
import { App } from "vue";
export declare type Browser = {
screen: string;
isMini: boolean;
};
export declare function useBrowser(app?: App): {
screen: string;
isMini: boolean;
};

40
packages/crud/types/hooks/core.d.ts vendored Normal file
View File

@ -0,0 +1,40 @@
/// <reference types="../index" />
import { Browser } from "./browser";
export declare function useRefs(): {
refs: {
[key: string]: obj;
};
setRefs: (name: string) => (el: any) => void;
};
export declare function useGlobal(): GlobalOptions;
export declare function useTools(): {
slots: Readonly<{
[name: string]: import("vue").Slot | undefined;
}>;
getValue: (data: any, params?: any) => any;
dict: ClCrud.Dict;
permission: ClCrud.Permission;
style: {
size: ElementPlus.Size;
};
events: {
[key: string]: (...args: any[]) => any;
};
render: {
autoHeight: boolean;
functionSlots: {
exclude: string[];
};
};
crud: any;
browser: Browser;
};
export declare function useCore(): {
crud: ClCrud.Provide;
mitt: Emitter;
};
export declare function useElApi(keys: string[], el: any): {
[key: string]: any;
};
export declare function useConfig({ props }: any): any;
export declare function useEventListener(name: string, cb: () => any): void;

10
packages/crud/types/hooks/crud.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
/// <reference types="../index" />
import { Ref } from "vue";
export declare function useCrud(options?: DeepPartial<ClCrud.Options>, cb?: (app: ClCrud.Ref) => void): Ref<ClCrud.Ref | undefined>;
export declare function useUpsert(options?: DeepPartial<ClUpsert.Options>): Ref<ClUpsert.Ref | undefined>;
export declare function useTable(options?: DeepPartial<ClTable.Options>): Ref<ClTable.Ref | undefined>;
export declare function useForm(cb?: (app: ClForm.Ref) => void): Ref<ClForm.Ref | undefined>;
export declare function useAdvSearch(options?: DeepPartial<ClAdvSearch.Options>): Ref<ClAdvSearch.Ref | undefined>;
export declare function useDialog(options?: {
onFullscreen(visible: boolean): void;
}): ClDialog.Provide;

Some files were not shown because too many files have changed in this diff Show More