mirror of
https://github.com/cool-team-official/cool-admin-vue.git
synced 2026-03-25 23:03:04 +00:00
添加 packages
This commit is contained in:
parent
2bea8cd73d
commit
95a638f40c
109
build/cool/temp/eps.d.ts
vendored
109
build/cool/temp/eps.d.ts
vendored
@ -780,6 +780,57 @@ declare namespace Eps {
|
||||
*/
|
||||
[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 {
|
||||
/**
|
||||
* list
|
||||
@ -2334,6 +2385,63 @@ declare namespace Eps {
|
||||
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 = {
|
||||
request(options?: {
|
||||
url: string;
|
||||
@ -2364,5 +2472,6 @@ declare namespace Eps {
|
||||
recycle: { data: RecycleData };
|
||||
space: { info: SpaceInfo; type: SpaceType };
|
||||
task: { info: TaskInfo };
|
||||
user: { address: UserAddress };
|
||||
};
|
||||
}
|
||||
|
||||
@ -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"]]]]
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "front-next",
|
||||
"version": "6.0.0",
|
||||
"version": "6.1.0",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"build": "vite build",
|
||||
@ -9,7 +9,7 @@
|
||||
"lint:eslint": "eslint \"{src}/**/*.{vue,ts,tsx}\" --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cool-vue/crud": "^6.1.11",
|
||||
"@cool-vue/crud": "^6.2.0",
|
||||
"@element-plus/icons-vue": "^2.0.10",
|
||||
"@vueuse/core": "^9.1.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
|
||||
3
packages/crud/.browserslistrc
Normal file
3
packages/crud/.browserslistrc
Normal file
@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
23
packages/crud/.gitignore
vendored
Normal file
23
packages/crud/.gitignore
vendored
Normal 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?
|
||||
9
packages/crud/.prettierrc
Normal file
9
packages/crud/.prettierrc
Normal 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
35
packages/crud/README.md
Normal 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),构建工具;
|
||||
3
packages/crud/babel.config.js
Normal file
3
packages/crud/babel.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"]
|
||||
};
|
||||
1
packages/crud/env.d.ts
vendored
Normal file
1
packages/crud/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="./index" />
|
||||
641
packages/crud/index.d.ts
vendored
Normal file
641
packages/crud/index.d.ts
vendored
Normal 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;
|
||||
}
|
||||
38
packages/crud/package.json
Normal file
38
packages/crud/package.json
Normal 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"
|
||||
]
|
||||
}
|
||||
21
packages/crud/src/components/add-btn.tsx
Normal file
21
packages/crud/src/components/add-btn.tsx
Normal 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>
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
31
packages/crud/src/components/adv/btn.tsx
Normal file
31
packages/crud/src/components/adv/btn.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
174
packages/crud/src/components/adv/search.tsx
Normal file
174
packages/crud/src/components/adv/search.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
248
packages/crud/src/components/context-menu/index.tsx
Normal file
248
packages/crud/src/components/context-menu/index.tsx
Normal 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;
|
||||
283
packages/crud/src/components/crud/helper.ts
Normal file
283
packages/crud/src/components/crud/helper.ts
Normal 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
|
||||
};
|
||||
}
|
||||
88
packages/crud/src/components/crud/index.tsx
Normal file
88
packages/crud/src/components/crud/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
269
packages/crud/src/components/dialog/index.tsx
Normal file
269
packages/crud/src/components/dialog/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
15
packages/crud/src/components/error-message.tsx
Normal file
15
packages/crud/src/components/error-message.tsx
Normal 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" />;
|
||||
};
|
||||
}
|
||||
});
|
||||
127
packages/crud/src/components/filter/group.tsx
Normal file
127
packages/crud/src/components/filter/group.tsx
Normal 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>
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
23
packages/crud/src/components/filter/index.tsx
Normal file
23
packages/crud/src/components/filter/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
11
packages/crud/src/components/flex1.tsx
Normal file
11
packages/crud/src/components/flex1.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-flex1",
|
||||
|
||||
setup() {
|
||||
return () => {
|
||||
return <div class="cl-flex1" />;
|
||||
};
|
||||
}
|
||||
});
|
||||
145
packages/crud/src/components/form-tabs/index.tsx
Normal file
145
packages/crud/src/components/form-tabs/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
323
packages/crud/src/components/form/helper.tsx
Normal file
323
packages/crud/src/components/form/helper.tsx
Normal 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
|
||||
};
|
||||
}
|
||||
608
packages/crud/src/components/form/index.tsx
Normal file
608
packages/crud/src/components/form/index.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
48
packages/crud/src/components/index.tsx
Normal file
48
packages/crud/src/components/index.tsx
Normal 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]);
|
||||
}
|
||||
}
|
||||
27
packages/crud/src/components/multi-delete-btn.tsx
Normal file
27
packages/crud/src/components/multi-delete-btn.tsx
Normal 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>
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
77
packages/crud/src/components/pagination/index.tsx
Normal file
77
packages/crud/src/components/pagination/index.tsx
Normal 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
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
118
packages/crud/src/components/query/index.tsx
Normal file
118
packages/crud/src/components/query/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
23
packages/crud/src/components/refresh-btn.tsx
Normal file
23
packages/crud/src/components/refresh-btn.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
11
packages/crud/src/components/row.tsx
Normal file
11
packages/crud/src/components/row.tsx
Normal 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>;
|
||||
};
|
||||
}
|
||||
});
|
||||
176
packages/crud/src/components/search-key/index.tsx
Normal file
176
packages/crud/src/components/search-key/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
582
packages/crud/src/components/table/helper.tsx
Normal file
582
packages/crud/src/components/table/helper.tsx
Normal 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
|
||||
};
|
||||
}
|
||||
149
packages/crud/src/components/table/index.tsx
Normal file
149
packages/crud/src/components/table/index.tsx
Normal 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();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
310
packages/crud/src/components/upsert/index.tsx
Normal file
310
packages/crud/src/components/upsert/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
362
packages/crud/src/demo/app.vue
Normal file
362
packages/crud/src/demo/app.vue
Normal 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>
|
||||
24
packages/crud/src/demo/btn.vue
Normal file
24
packages/crud/src/demo/btn.vue
Normal 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>
|
||||
25
packages/crud/src/demo/column-t1.vue
Normal file
25
packages/crud/src/demo/column-t1.vue
Normal 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>
|
||||
52
packages/crud/src/demo/form-crud.vue
Normal file
52
packages/crud/src/demo/form-crud.vue
Normal 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>
|
||||
46
packages/crud/src/demo/t2.vue
Normal file
46
packages/crud/src/demo/t2.vue
Normal 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>
|
||||
58
packages/crud/src/demo/test.vue
Normal file
58
packages/crud/src/demo/test.vue
Normal 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>
|
||||
95
packages/crud/src/demo/upsert.vue
Normal file
95
packages/crud/src/demo/upsert.vue
Normal 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>
|
||||
33
packages/crud/src/emitter.ts
Normal file
33
packages/crud/src/emitter.ts
Normal 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
1
packages/crud/src/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="../index" />
|
||||
38
packages/crud/src/hooks/browser.ts
Normal file
38
packages/crud/src/hooks/browser.ts
Normal 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;
|
||||
}
|
||||
78
packages/crud/src/hooks/core.ts
Normal file
78
packages/crud/src/hooks/core.ts
Normal 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();
|
||||
}
|
||||
159
packages/crud/src/hooks/crud.ts
Normal file
159
packages/crud/src/hooks/crud.ts
Normal 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;
|
||||
}
|
||||
5
packages/crud/src/hooks/index.ts
Normal file
5
packages/crud/src/hooks/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./crud";
|
||||
export * from "./core";
|
||||
export * from "./browser";
|
||||
export * from "./proxy";
|
||||
export * from "./plugins";
|
||||
28
packages/crud/src/hooks/plugins.ts
Normal file
28
packages/crud/src/hooks/plugins.ts
Normal 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();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
30
packages/crud/src/hooks/proxy.ts
Normal file
30
packages/crud/src/hooks/proxy.ts
Normal 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;
|
||||
}
|
||||
97
packages/crud/src/index.ts
Normal file
97
packages/crud/src/index.ts
Normal 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";
|
||||
23
packages/crud/src/locale/en.ts
Normal file
23
packages/crud/src/locale/en.ts
Normal 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"
|
||||
};
|
||||
7
packages/crud/src/locale/index.ts
Normal file
7
packages/crud/src/locale/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import en from "./en";
|
||||
import zhCn from "./zh-cn";
|
||||
|
||||
export const locale = {
|
||||
en,
|
||||
zhCn
|
||||
};
|
||||
23
packages/crud/src/locale/zh-cn.ts
Normal file
23
packages/crud/src/locale/zh-cn.ts
Normal 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
26
packages/crud/src/main.ts
Normal 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");
|
||||
689
packages/crud/src/static/index.scss
Normal file
689
packages/crud/src/static/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
145
packages/crud/src/utils/form-hook.ts
Normal file
145
packages/crud/src/utils/form-hook.ts
Normal 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;
|
||||
159
packages/crud/src/utils/index.ts
Normal file
159
packages/crud/src/utils/index.ts
Normal 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 };
|
||||
30
packages/crud/src/utils/mitt.ts
Normal file
30
packages/crud/src/utils/mitt.ts
Normal 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;
|
||||
192
packages/crud/src/utils/parse.tsx
Normal file
192
packages/crud/src/utils/parse.tsx
Normal 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 {};
|
||||
}
|
||||
}
|
||||
17
packages/crud/src/utils/temp.ts
Normal file
17
packages/crud/src/utils/temp.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
143
packages/crud/src/utils/test.ts
Normal file
143
packages/crud/src/utils/test.ts
Normal 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();
|
||||
}
|
||||
};
|
||||
186
packages/crud/src/utils/vnode.tsx
Normal file
186
packages/crud/src/utils/vnode.tsx
Normal 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 不能为空`} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
packages/crud/tsconfig.json
Normal file
27
packages/crud/tsconfig.json
Normal 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/*"]
|
||||
}
|
||||
2
packages/crud/types/components/add-btn.d.ts
vendored
Normal file
2
packages/crud/types/components/add-btn.d.ts
vendored
Normal 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;
|
||||
2
packages/crud/types/components/adv-btn.d.ts
vendored
Normal file
2
packages/crud/types/components/adv-btn.d.ts
vendored
Normal 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;
|
||||
50
packages/crud/types/components/adv-search.d.ts
vendored
Normal file
50
packages/crud/types/components/adv-search.d.ts
vendored
Normal 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;
|
||||
2
packages/crud/types/components/adv/btn.d.ts
vendored
Normal file
2
packages/crud/types/components/adv/btn.d.ts
vendored
Normal 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;
|
||||
41
packages/crud/types/components/adv/search.d.ts
vendored
Normal file
41
packages/crud/types/components/adv/search.d.ts
vendored
Normal 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;
|
||||
30
packages/crud/types/components/context-menu/index.d.ts
vendored
Normal file
30
packages/crud/types/components/context-menu/index.d.ts
vendored
Normal 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;
|
||||
23
packages/crud/types/components/crud/helper.d.ts
vendored
Normal file
23
packages/crud/types/components/crud/helper.d.ts
vendored
Normal 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 {};
|
||||
19
packages/crud/types/components/crud/index.d.ts
vendored
Normal file
19
packages/crud/types/components/crud/index.d.ts
vendored
Normal 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;
|
||||
70
packages/crud/types/components/dialog/index.d.ts
vendored
Normal file
70
packages/crud/types/components/dialog/index.d.ts
vendored
Normal 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;
|
||||
6
packages/crud/types/components/error-message.d.ts
vendored
Normal file
6
packages/crud/types/components/error-message.d.ts
vendored
Normal 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;
|
||||
41
packages/crud/types/components/filter-group.d.ts
vendored
Normal file
41
packages/crud/types/components/filter-group.d.ts
vendored
Normal 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;
|
||||
6
packages/crud/types/components/filter.d.ts
vendored
Normal file
6
packages/crud/types/components/filter.d.ts
vendored
Normal 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;
|
||||
36
packages/crud/types/components/filter/group.d.ts
vendored
Normal file
36
packages/crud/types/components/filter/group.d.ts
vendored
Normal 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;
|
||||
6
packages/crud/types/components/filter/index.d.ts
vendored
Normal file
6
packages/crud/types/components/filter/index.d.ts
vendored
Normal 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;
|
||||
2
packages/crud/types/components/flex1.d.ts
vendored
Normal file
2
packages/crud/types/components/flex1.d.ts
vendored
Normal 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;
|
||||
59
packages/crud/types/components/form-tabs.d.ts
vendored
Normal file
59
packages/crud/types/components/form-tabs.d.ts
vendored
Normal 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;
|
||||
38
packages/crud/types/components/form-tabs/index.d.ts
vendored
Normal file
38
packages/crud/types/components/form-tabs/index.d.ts
vendored
Normal 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;
|
||||
44
packages/crud/types/components/form/helper.d.ts
vendored
Normal file
44
packages/crud/types/components/form/helper.d.ts
vendored
Normal 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 {};
|
||||
15
packages/crud/types/components/form/index.d.ts
vendored
Normal file
15
packages/crud/types/components/form/index.d.ts
vendored
Normal 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;
|
||||
5
packages/crud/types/components/index.d.ts
vendored
Normal file
5
packages/crud/types/components/index.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import { App } from "vue";
|
||||
export declare const components: {
|
||||
[key: string]: any;
|
||||
};
|
||||
export declare function useComponent(app: App): void;
|
||||
2
packages/crud/types/components/multi-delete-btn.d.ts
vendored
Normal file
2
packages/crud/types/components/multi-delete-btn.d.ts
vendored
Normal 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;
|
||||
9
packages/crud/types/components/pagination.d.ts
vendored
Normal file
9
packages/crud/types/components/pagination.d.ts
vendored
Normal 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;
|
||||
4
packages/crud/types/components/pagination/index.d.ts
vendored
Normal file
4
packages/crud/types/components/pagination/index.d.ts
vendored
Normal 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;
|
||||
39
packages/crud/types/components/query.d.ts
vendored
Normal file
39
packages/crud/types/components/query.d.ts
vendored
Normal 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;
|
||||
32
packages/crud/types/components/query/index.d.ts
vendored
Normal file
32
packages/crud/types/components/query/index.d.ts
vendored
Normal 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;
|
||||
2
packages/crud/types/components/refresh-btn.d.ts
vendored
Normal file
2
packages/crud/types/components/refresh-btn.d.ts
vendored
Normal 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;
|
||||
2
packages/crud/types/components/row.d.ts
vendored
Normal file
2
packages/crud/types/components/row.d.ts
vendored
Normal 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;
|
||||
61
packages/crud/types/components/search-key.d.ts
vendored
Normal file
61
packages/crud/types/components/search-key.d.ts
vendored
Normal 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;
|
||||
52
packages/crud/types/components/search-key/index.d.ts
vendored
Normal file
52
packages/crud/types/components/search-key/index.d.ts
vendored
Normal 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;
|
||||
74
packages/crud/types/components/table/helper.d.ts
vendored
Normal file
74
packages/crud/types/components/table/helper.d.ts
vendored
Normal 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 {};
|
||||
54
packages/crud/types/components/table/index.d.ts
vendored
Normal file
54
packages/crud/types/components/table/index.d.ts
vendored
Normal 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;
|
||||
38
packages/crud/types/components/upsert/index.d.ts
vendored
Normal file
38
packages/crud/types/components/upsert/index.d.ts
vendored
Normal 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
3
packages/crud/types/emitter.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/// <reference types="../index" />
|
||||
export declare const crudList: ClCrud.Ref[];
|
||||
export declare const emitter: Emitter;
|
||||
9
packages/crud/types/hooks/browser.d.ts
vendored
Normal file
9
packages/crud/types/hooks/browser.d.ts
vendored
Normal 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
40
packages/crud/types/hooks/core.d.ts
vendored
Normal 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
10
packages/crud/types/hooks/crud.d.ts
vendored
Normal 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
Loading…
x
Reference in New Issue
Block a user