init
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.idea
|
||||
assets
|
8
cool-admin-java/cool-admin.iml
Normal file
8
cool-admin-java/cool-admin.iml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="AdditionalModuleElements">
|
||||
<content url="file://$MODULE_DIR$" dumb="true">
|
||||
<sourceFolder url="file://$MODULE_DIR$/target/generated-sources/annotations" isTestSource="false" />
|
||||
</content>
|
||||
</component>
|
||||
</module>
|
@@ -5,8 +5,6 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.cool.core.util.ConvertUtil;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheType;
|
||||
@@ -17,6 +15,9 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 缓存工具类
|
||||
*/
|
||||
@@ -174,7 +175,7 @@ public class CoolCache {
|
||||
cache.put(key, value);
|
||||
} else if (type.equalsIgnoreCase(CacheType.REDIS.name())) {
|
||||
redisCache.put(cacheName, key.getBytes(), ObjectUtil.serialize(value),
|
||||
java.time.Duration.ofSeconds(ttl));
|
||||
Duration.ofSeconds(ttl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,7 @@
|
||||
package com.cool.core.plugin.event;
|
||||
|
||||
public enum PluginActionEnum {
|
||||
INSTALL,
|
||||
UNINSTALL,
|
||||
UPDATE,
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
✨🌈✨[cool-admin-java-plus](https://gitee.com/hlc4417/cool-admin-java-plus)✨🌈✨
|
||||
✨🌈✨===================================================================================✨🌈✨
|
||||
______ ___ ___ _____ _ ______ ____ ____ _____ ____ _____
|
||||
.' ___ | .' `. .' `.|_ _| V8.x / \ |_ _ `.|_ \ / _||_ _||_ \|_ _|
|
||||
/ .' \_|/ .-. \/ .-. \ | | ______ / _ \ | | `. \ | \/ | | | | \ | |
|
||||
| | | | | || | | | | | _|______|/ ___ \ | | | | | |\ /| | | | | |\ \| |
|
||||
\ `.___.'\\ `-' /\ `-' /_| |__/ | _/ / \ \_ _| |_.' /_| |_\/_| |_ _| |_ _| |_\ |_
|
||||
`.____ .' `.___.' `.___.'|________| |____| |____||______.'|_____||_____||_____||_____|\____|
|
||||
:: https://java.cool-admin.com ::
|
||||
✨🌈✨===================================================================================✨🌈✨
|
23
cool-admin-vue/packages/crud/.gitignore
vendored
Normal file
23
cool-admin-vue/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
cool-admin-vue/packages/crud/.prettierrc
Normal file
9
cool-admin-vue/packages/crud/.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"jsxBracketSameLine": true,
|
||||
"singleQuote": false,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "none"
|
||||
}
|
33
cool-admin-vue/packages/crud/README.md
Normal file
33
cool-admin-vue/packages/crud/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 介绍
|
||||
|
||||
**cool-admin for vue**是基于[Vue.js](https://v3.cn.vuejs.org)开发。
|
||||
|
||||
[cool-admin 官方文档](https://cool-js.com/)
|
||||
|
||||
尝试 `cool-admin` 最简单的方法就是查看文档及运行示例。
|
||||
|
||||
<img src='https://vue.cool-admin.com/show/admin.png' />
|
||||
|
||||
[Ai极速编码 🔥 在线体验](https://show.cool-admin.com/helper/ai-code)
|
||||
|
||||
<img src='https://vue.cool-admin.com/show/code.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),构建工具;
|
811
cool-admin-vue/packages/crud/index.d.ts
vendored
Normal file
811
cool-admin-vue/packages/crud/index.d.ts
vendored
Normal file
@@ -0,0 +1,811 @@
|
||||
// vue
|
||||
declare namespace Vue {
|
||||
interface Ref<T = any> {
|
||||
value: T;
|
||||
}
|
||||
|
||||
type Emit = (name: any, ...args: any[]) => void;
|
||||
}
|
||||
|
||||
// element-plus
|
||||
declare namespace ElementPlus {
|
||||
type Size = "large" | "default" | "small";
|
||||
type Align = "left" | "center" | "right";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// mitt
|
||||
declare interface Mitt {
|
||||
on(name: string, callback: (data: any) => void): void;
|
||||
off(name: string, callback: (data: any) => void): void;
|
||||
emit(name: string, data?: any): void;
|
||||
}
|
||||
|
||||
// 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;
|
||||
on(name: string, callback: (data: any) => void): void;
|
||||
emit(name: string, data?: any): void;
|
||||
}
|
||||
|
||||
// 方法
|
||||
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 type Merge<A, B> = Omit<A, keyof B> & B;
|
||||
|
||||
// 移除 [key]
|
||||
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>)>;
|
||||
|
||||
// 获取keys
|
||||
declare type PropKey<T> = keyof RemoveIndex<T> | (string & {});
|
||||
|
||||
// 任意字符串
|
||||
declare type AnyString = string & {};
|
||||
|
||||
// 类型或者 Ref 泛型
|
||||
declare type RefData<T = any> = T | Vue.Ref<T>;
|
||||
|
||||
// browser
|
||||
declare type Browser = {
|
||||
screen: string;
|
||||
isMini: boolean;
|
||||
};
|
||||
|
||||
// 字典选项
|
||||
declare type DictOptions = {
|
||||
label?: string;
|
||||
value?: any;
|
||||
color?: string;
|
||||
type?: string;
|
||||
[key: string]: any;
|
||||
}[];
|
||||
|
||||
// render
|
||||
declare namespace Render {
|
||||
type OpButton =
|
||||
| `slot-${string}`
|
||||
| {
|
||||
label: string;
|
||||
type?: string;
|
||||
hidden?: boolean;
|
||||
onClick(options: { scope: obj }): void;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
onChange?(value: any): void;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Component {
|
||||
name?: string;
|
||||
options?: RefData<DictOptions>;
|
||||
props?: RefData<Props>;
|
||||
style?: obj;
|
||||
slots?: {
|
||||
[key: string]: (data?: any) => any;
|
||||
};
|
||||
vm?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
}
|
||||
|
||||
// crud
|
||||
declare namespace ClCrud {
|
||||
declare interface Field {
|
||||
comment: string;
|
||||
source: string;
|
||||
propertyName: string;
|
||||
type: string;
|
||||
dict: string | string[];
|
||||
nullable: boolean;
|
||||
}
|
||||
|
||||
interface 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;
|
||||
placeholderSelect: string;
|
||||
tips: string;
|
||||
saveSuccess: string;
|
||||
deleteSuccess: string;
|
||||
deleteConfirm: string;
|
||||
empty: string;
|
||||
desc: string;
|
||||
asc: string;
|
||||
select: string;
|
||||
deselect: string;
|
||||
seeMore: string;
|
||||
hideContent: string;
|
||||
nonEmpty: string;
|
||||
collapse: string;
|
||||
expand: string;
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
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: Label;
|
||||
}
|
||||
|
||||
interface Permission {
|
||||
page?: boolean;
|
||||
list?: boolean;
|
||||
add?: boolean;
|
||||
delete?: boolean;
|
||||
update?: boolean;
|
||||
info?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Params {
|
||||
page: {
|
||||
page?: number;
|
||||
size?: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
list: obj;
|
||||
add: obj;
|
||||
delete: {
|
||||
ids?: any[];
|
||||
[key: string]: any;
|
||||
};
|
||||
update: {
|
||||
id?: any;
|
||||
[key: string]: any;
|
||||
};
|
||||
info: {
|
||||
id?: any;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
interface Response {
|
||||
page: {
|
||||
list: any[];
|
||||
pagination: {
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
[key: string]: any;
|
||||
};
|
||||
list: any[];
|
||||
add: any;
|
||||
update: any;
|
||||
info: any;
|
||||
delete: any;
|
||||
}
|
||||
|
||||
interface Service {
|
||||
api: {
|
||||
page(params?: Params["page"]): Promise<Response["page"]>;
|
||||
list(params?: Params["list"]): Promise<Response["list"]>;
|
||||
add(params?: Params["add"]): Promise<Response["add"]>;
|
||||
update(params?: Params["update"]): Promise<Response["update"]>;
|
||||
info(params?: Params["info"]): Promise<Response["info"]>;
|
||||
delete(params?: Params["delete"]): Promise<Response["delete"]>;
|
||||
permission: Permission;
|
||||
search: {
|
||||
fieldEq: Field[];
|
||||
fieldLike: Field[];
|
||||
keyWordLikeFields: Field[];
|
||||
};
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
interface Config {
|
||||
name: string;
|
||||
service: Service["api"];
|
||||
permission: Permission;
|
||||
dict: Dict;
|
||||
onRefresh(
|
||||
params: obj,
|
||||
event: {
|
||||
done: fn;
|
||||
next: Service["api"]["page"];
|
||||
render: (data: any | any[], pagination?: Response["page"]["pagination"]) => void;
|
||||
}
|
||||
): void;
|
||||
onDelete(
|
||||
selection: obj[],
|
||||
event: {
|
||||
next: Service["api"]["delete"];
|
||||
}
|
||||
): void;
|
||||
}
|
||||
|
||||
interface Ref {
|
||||
"cl-table": ClTable.Ref;
|
||||
"cl-upsert": ClUpsert.Ref;
|
||||
id: number;
|
||||
mitt: Mitt;
|
||||
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;
|
||||
setParams(data: obj): void;
|
||||
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 Options extends DeepPartial<Config> {
|
||||
service?: any;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace ClTable {
|
||||
type OpButton = Array<"info" | "edit" | "delete" | AnyString | Render.OpButton>;
|
||||
|
||||
type ColumnType = "index" | "selection" | "expand" | "op" | AnyString;
|
||||
|
||||
interface Column<T = any> {
|
||||
type: ColumnType;
|
||||
hidden: RefData<boolean>;
|
||||
component: Render.Component;
|
||||
search: {
|
||||
isInput: boolean;
|
||||
value: any;
|
||||
icon: () => any;
|
||||
refreshOnChange: boolean;
|
||||
component: Render.Component;
|
||||
};
|
||||
dict: RefData<DictOptions>;
|
||||
dictFormatter: (values: DictOptions) => string;
|
||||
dictColor: boolean;
|
||||
dictSeparator: string;
|
||||
dictAllLevels: boolean;
|
||||
buttons: OpButton | ((options: { scope: T }) => OpButton);
|
||||
align: ElementPlus.Align;
|
||||
label: any;
|
||||
renderLabel: (options: { column: any; $index: number }) => any;
|
||||
className: string;
|
||||
prop: PropKey<T>;
|
||||
orderNum: number;
|
||||
width: RefData<number | string>;
|
||||
minWidth: RefData<number | string>;
|
||||
renderHeader: (options: { column: any; $index: number }) => any;
|
||||
sortable: boolean | "desc" | "descending" | "ascending" | "asc" | "custom";
|
||||
sortMethod: fn;
|
||||
sortBy: string | ((row: T, index: number) => any) | any[];
|
||||
resizable: boolean;
|
||||
columnKey: string;
|
||||
headerAlign: ElementPlus.Align;
|
||||
showOverflowTooltip: boolean;
|
||||
fixed: boolean | string;
|
||||
render: (row: T, column: any, value: any, index: number) => any;
|
||||
formatter: (row: T, column: any, value: any, index: number) => any;
|
||||
selectable: (row: T, index: number) => boolean;
|
||||
reserveSelection: boolean;
|
||||
filterMethod: fn;
|
||||
filteredValue: unknown[];
|
||||
filters: unknown[];
|
||||
filterPlacement: string;
|
||||
filterMultiple: boolean;
|
||||
index: ((index: number) => number) | number;
|
||||
sortOrders: unknown[];
|
||||
children: Column<T>[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type ContextMenu = Array<
|
||||
| ClContextMenu.Item
|
||||
| ((row: obj, column: obj, event: PointerEvent) => ClContextMenu.Item)
|
||||
| "refresh"
|
||||
| "check"
|
||||
| "update"
|
||||
| "edit"
|
||||
| "delete"
|
||||
| "info"
|
||||
| "order-desc"
|
||||
| "order-asc"
|
||||
>;
|
||||
|
||||
type Plugin = (options: { exposed: Ref }) => void;
|
||||
|
||||
interface Config<T = any> {
|
||||
columns: Column<T>[];
|
||||
autoHeight: boolean;
|
||||
height: any;
|
||||
contextMenu: ContextMenu;
|
||||
defaultSort: {
|
||||
prop: string;
|
||||
order: "descending" | "ascending";
|
||||
};
|
||||
sortRefresh: boolean;
|
||||
emptyText: string;
|
||||
rowKey: string;
|
||||
on?: {
|
||||
[key: string]: (...args: any[]) => void;
|
||||
};
|
||||
props?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
plugins?: Plugin[];
|
||||
onRowContextmenu?(row: T, column: any, event: any): void;
|
||||
}
|
||||
|
||||
interface Ref<T = any> {
|
||||
Table: any;
|
||||
config: obj;
|
||||
selection: T[];
|
||||
data: T[];
|
||||
columns: Column<T>[];
|
||||
reBuild(cb?: fn): void;
|
||||
calcMaxHeight(): void;
|
||||
setData(data: T[]): void;
|
||||
setColumns(columns: Column[]): void;
|
||||
showColumn(props: PropKey<T> | PropKey<T>[], status?: boolean): void;
|
||||
hideColumn(props: PropKey<T> | PropKey<T>[]): void;
|
||||
changeSort(prop: PropKey<T>, order: string): void;
|
||||
clearSelection(): void;
|
||||
getSelectionRows(): any[];
|
||||
toggleRowSelection(row: T, selected?: boolean): void;
|
||||
toggleAllSelection(): void;
|
||||
toggleRowExpansion(row: T, expanded?: boolean): void;
|
||||
setCurrentRow(row: T): void;
|
||||
clearSort(): void;
|
||||
clearFilter(columnKeys: PropKey<T>[]): void;
|
||||
doLayout(): void;
|
||||
sort(prop: PropKey<T>, order: string): void;
|
||||
scrollTo(position: { top?: number; left?: number }): void;
|
||||
setScrollTop(top: number): void;
|
||||
setScrollLeft(left: number): void;
|
||||
updateKeyChildren(key: string, children: any[]): void;
|
||||
}
|
||||
|
||||
interface Options<T = any> extends DeepPartial<Config<T>> {
|
||||
columns?: List<ClTable.Column<T>>;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace ClFormTabs {
|
||||
type labels = {
|
||||
label: string;
|
||||
value: string;
|
||||
name?: string;
|
||||
icon?: any;
|
||||
lazy?: boolean;
|
||||
[key: string]: any;
|
||||
}[];
|
||||
}
|
||||
|
||||
declare namespace ClForm {
|
||||
type CloseAction = "close" | "save" | AnyString;
|
||||
|
||||
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 Hook {
|
||||
Fn: (value: any, options: { form: obj; prop: string; method: "submit" | "bind" }) => any;
|
||||
Key:
|
||||
| "number"
|
||||
| "string"
|
||||
| "split"
|
||||
| "join"
|
||||
| "boolean"
|
||||
| "booleanNumber"
|
||||
| "datetimeRange"
|
||||
| "splitJoin"
|
||||
| "json"
|
||||
| "empty"
|
||||
| AnyString;
|
||||
Pipe: Hook["Key"] | Hook["Fn"];
|
||||
Event: {
|
||||
bind?: Hook["Pipe"] | Hook["Pipe"][];
|
||||
submit?: Hook["Pipe"] | Hook["Pipe"][];
|
||||
reset?: (prop: string) => string[];
|
||||
};
|
||||
}
|
||||
|
||||
interface Item<T = any> {
|
||||
type?: "tabs";
|
||||
prop?: PropKey<T>;
|
||||
props?: {
|
||||
labels?: ClFormTabs.labels;
|
||||
justify?: "left" | "center" | "right";
|
||||
color?: string;
|
||||
mergeProp?: boolean;
|
||||
labelWidth?: string;
|
||||
error?: string;
|
||||
showMessage?: boolean;
|
||||
inlineMessage?: boolean;
|
||||
size?: "medium" | "default" | "small";
|
||||
[key: string]: any;
|
||||
};
|
||||
span?: number;
|
||||
col?: {
|
||||
span: number;
|
||||
offset: number;
|
||||
push: number;
|
||||
pull: number;
|
||||
xs: any;
|
||||
sm: any;
|
||||
md: any;
|
||||
lg: any;
|
||||
xl: any;
|
||||
tag: string;
|
||||
};
|
||||
group?: string;
|
||||
collapse?: boolean;
|
||||
value?: any;
|
||||
label?: string;
|
||||
renderLabel?: any;
|
||||
flex?: boolean;
|
||||
hook?: Hook["Event"] | Hook["Key"];
|
||||
hidden?: boolean | ((options: { scope: obj }) => boolean);
|
||||
prepend?: Render.Component;
|
||||
component?: Render.Component;
|
||||
append?: Render.Component;
|
||||
rules?: Rule | Rule[];
|
||||
required?: boolean;
|
||||
children?: Item[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Config<T = any> {
|
||||
title?: any;
|
||||
height?: any;
|
||||
width?: any;
|
||||
props: ElementPlus.FormProps;
|
||||
items: Item[];
|
||||
form: obj;
|
||||
isReset?: boolean;
|
||||
on?: {
|
||||
open?(data: T): void;
|
||||
close?(action: CloseAction, done: fn): void;
|
||||
submit?(data: T, event: { close: fn; done: fn }): void;
|
||||
change?(data: T, prop: string): void;
|
||||
};
|
||||
op: {
|
||||
hidden?: boolean;
|
||||
saveButtonText?: string;
|
||||
closeButtonText?: string;
|
||||
justify?: "flex-start" | "center" | "flex-end";
|
||||
buttons?: Array<CloseAction | Render.OpButton>;
|
||||
};
|
||||
dialog: {
|
||||
title?: any;
|
||||
height?: string;
|
||||
width?: string;
|
||||
hideHeader?: boolean;
|
||||
controls?: Array<"fullscreen" | "close" | AnyString>;
|
||||
[key: string]: any;
|
||||
};
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type Plugin = (options: {
|
||||
exposed: Ref;
|
||||
onOpen(cb: () => void): void;
|
||||
onClose(cb: () => void): void;
|
||||
onSubmit(cb: (data: obj) => obj): void;
|
||||
}) => void;
|
||||
|
||||
type Items<T = any> = List<Item<T>>;
|
||||
|
||||
interface Ref<T = any> {
|
||||
Form: any;
|
||||
form: T;
|
||||
config: {
|
||||
items: Item[];
|
||||
[key: string]: any;
|
||||
};
|
||||
open(options: Options<T>, plugins?: Plugin[]): void;
|
||||
close(action?: CloseAction): void;
|
||||
done(): void;
|
||||
clear(): void;
|
||||
reset(): void;
|
||||
showLoading(): void;
|
||||
hideLoading(): void;
|
||||
setDisabled(flag?: boolean): void;
|
||||
invokeData(data: any): void;
|
||||
setData(prop: string, value: any): void;
|
||||
bindForm(data: obj): void;
|
||||
getForm(prop?: string): any;
|
||||
setForm(prop: string, value: any): void;
|
||||
setOptions(prop: string, list: DictOptions): void;
|
||||
setProps(prop: string, value: any): void;
|
||||
setConfig(path: 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;
|
||||
Tabs: {
|
||||
active: RefData<string>;
|
||||
list: ClFormTabs.labels;
|
||||
change(value: any, valid?: boolean): Promise<any>;
|
||||
[key: string]: any;
|
||||
};
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Options<T = any> extends DeepPartial<Config> {
|
||||
items?: Items<T>;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace ClUpsert {
|
||||
interface Config<T = any> {
|
||||
sync: boolean;
|
||||
items: ClForm.Item[];
|
||||
props: ClForm.Config["props"];
|
||||
op: ClForm.Config["op"];
|
||||
dialog: ClForm.Config["dialog"];
|
||||
onOpen?(): void;
|
||||
onOpened?(data: T): void;
|
||||
onClose?(action: ClForm.CloseAction, done: fn): void;
|
||||
onClosed?(): void;
|
||||
onInfo?(
|
||||
data: T,
|
||||
event: { close: fn; done(data: T): void; next: ClCrud.Service["api"]["info"] }
|
||||
): void;
|
||||
onSubmit?(
|
||||
data: T,
|
||||
event: { close: fn; done: fn; next: ClCrud.Service["api"]["update"] }
|
||||
): void;
|
||||
plugins?: ClForm.Plugin[];
|
||||
}
|
||||
|
||||
interface Ref<T = any> extends ClForm.Ref<T> {
|
||||
mode: "add" | "update" | "info" | AnyString;
|
||||
}
|
||||
|
||||
interface Options<T = any> extends DeepPartial<Config<T>> {
|
||||
items?: ClForm.Items<T>;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace ClAdvSearch {
|
||||
interface Config<T = any> {
|
||||
items?: ClForm.Item[];
|
||||
title?: string;
|
||||
size?: string | number;
|
||||
op?: ("clear" | "reset" | "close" | "search" | `slot-${string}`)[];
|
||||
onSearch?(data: T, options: { next: ClCrud.Service["api"]["page"]; close(): void }): void;
|
||||
}
|
||||
|
||||
interface Ref<T = any> extends ClForm.Ref<T> {}
|
||||
|
||||
interface Options<T = any> extends DeepPartial<Config<T>> {
|
||||
items?: ClForm.Items<T>;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace ClSearch {
|
||||
type Plugin = (options: { exposed: Ref }) => void;
|
||||
|
||||
interface Config<T = any> {
|
||||
inline?: boolean;
|
||||
items?: ClForm.Item[];
|
||||
data?: T;
|
||||
props?: ElementPlus.FormProps;
|
||||
resetBtn?: boolean;
|
||||
collapse?: boolean;
|
||||
Form?: ClForm.Ref;
|
||||
onChange?(data: T, prop: string): void;
|
||||
onLoad?(data: T): void;
|
||||
onSearch?(data: T, options: { next: ClCrud.Service["api"]["page"] }): void;
|
||||
plugins?: Plugin[];
|
||||
}
|
||||
|
||||
interface Ref<T = any> extends ClForm.Ref<T> {
|
||||
search(params?: obj): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
interface Options<T = any> extends DeepPartial<Config<T>> {
|
||||
items?: ClForm.Items<T>;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace ClContextMenu {
|
||||
interface Item {
|
||||
label: string;
|
||||
prefixIcon?: any;
|
||||
suffixIcon?: any;
|
||||
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 {
|
||||
class?: string;
|
||||
hover?:
|
||||
| boolean
|
||||
| {
|
||||
target?: string;
|
||||
className?: string;
|
||||
};
|
||||
list?: Item[];
|
||||
}
|
||||
|
||||
interface Ref {
|
||||
open(event: Event, options: Options): Exposed;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
interface Exposed {
|
||||
close(): void;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace ClDialog {
|
||||
interface Provide {
|
||||
visible: Vue.Ref<boolean>;
|
||||
fullscreen: Vue.Ref<boolean>;
|
||||
}
|
||||
}
|
||||
|
||||
declare interface Config {
|
||||
dict: ClCrud.Dict;
|
||||
permission: ClCrud.Permission;
|
||||
events: {
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
style: {
|
||||
size: ElementPlus.Size;
|
||||
colors: string[];
|
||||
form: {
|
||||
labelPosition: ElementPlus.FormProps["labelPosition"];
|
||||
labelWidth: ElementPlus.FormProps["labelWidth"];
|
||||
span: number;
|
||||
plugins: ClForm.Plugin[];
|
||||
};
|
||||
table: {
|
||||
stripe: boolean;
|
||||
border: boolean;
|
||||
highlightCurrentRow: boolean;
|
||||
resizable: boolean;
|
||||
autoHeight: boolean;
|
||||
contextMenu: ClTable.ContextMenu;
|
||||
column: {
|
||||
minWidth: number | string;
|
||||
align: ElementPlus.Align;
|
||||
headerAlign: ElementPlus.Align;
|
||||
opWidth: number | string;
|
||||
};
|
||||
plugins: ClTable.Plugin[];
|
||||
};
|
||||
search: {
|
||||
plugins: ClSearch.Plugin[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
declare type Options = DeepPartial<Config>;
|
||||
|
||||
declare interface CrudOptions {
|
||||
options: Options;
|
||||
}
|
29
cool-admin-vue/packages/crud/index.html
Normal file
29
cool-admin-vue/packages/crud/index.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="referer" content="never" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
<title></title>
|
||||
<link rel="icon" href="./favicon.ico" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="preload__wrap" id="Loading">
|
||||
<div class="preload__container">
|
||||
<p class="preload__name"></p>
|
||||
<div class="preload__loading"></div>
|
||||
<p class="preload__title"></p>
|
||||
<p class="preload__sub-title"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
38
cool-admin-vue/packages/crud/package.json
Normal file
38
cool-admin-vue/packages/crud/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@cool-vue/crud",
|
||||
"version": "8.0.6",
|
||||
"private": false,
|
||||
"main": "./dist/index.umd.js",
|
||||
"module": "./dist/index.es.js",
|
||||
"types": "types/entry.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/runtime-core": "^3.5.13",
|
||||
"element-plus": "^2.10.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.16",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"prettier": "^3.5.1",
|
||||
"sass": "^1.85.0",
|
||||
"sass-loader": "^16.0.5",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^6.1.0",
|
||||
"vite-plugin-dts": "^4.5.0",
|
||||
"vue-tsc": "^2.2.2"
|
||||
},
|
||||
"files": [
|
||||
"types",
|
||||
"dist",
|
||||
"index.d.ts",
|
||||
"index.ts"
|
||||
]
|
||||
}
|
2350
cool-admin-vue/packages/crud/pnpm-lock.yaml
generated
Normal file
2350
cool-admin-vue/packages/crud/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
208
cool-admin-vue/packages/crud/src/App.vue
Normal file
208
cool-admin-vue/packages/crud/src/App.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="title">CRUD DEMO v8</div>
|
||||
|
||||
<cl-crud ref="Crud">
|
||||
<div class="search">
|
||||
<cl-search ref="Search" />
|
||||
</div>
|
||||
|
||||
<cl-row>
|
||||
<cl-add-btn />
|
||||
|
||||
<cl-flex1 />
|
||||
|
||||
<cl-search-key
|
||||
field="name"
|
||||
:field-list="[
|
||||
{
|
||||
label: '昵称',
|
||||
value: 'name'
|
||||
},
|
||||
{
|
||||
label: '手机号',
|
||||
value: 'phone'
|
||||
}
|
||||
]"
|
||||
refreshOnInput
|
||||
/>
|
||||
</cl-row>
|
||||
|
||||
<cl-row>
|
||||
<cl-table ref="Table" :auto-height="false"></cl-table>
|
||||
</cl-row>
|
||||
|
||||
<cl-row>
|
||||
<cl-flex1 />
|
||||
<cl-pagination />
|
||||
</cl-row>
|
||||
|
||||
<cl-upsert ref="Upsert"></cl-upsert>
|
||||
<cl-form ref="Form"></cl-form>
|
||||
</cl-crud>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { useCrud, useForm, useSearch, useTable, useUpsert } from "./hooks";
|
||||
import { EditPen } from "@element-plus/icons-vue";
|
||||
|
||||
interface Data {
|
||||
name?: string;
|
||||
age?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const Upsert = useUpsert<Data>({
|
||||
items: [
|
||||
{
|
||||
type: "tabs",
|
||||
props: {
|
||||
labels: [
|
||||
{
|
||||
label: "基础",
|
||||
value: "A",
|
||||
icon: EditPen
|
||||
},
|
||||
{
|
||||
label: "高级",
|
||||
value: "B"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "A",
|
||||
prop: "age",
|
||||
label: "年龄",
|
||||
component: {
|
||||
name: "el-input"
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "A",
|
||||
prop: "name",
|
||||
label: "昵称",
|
||||
component: {
|
||||
name: "el-input"
|
||||
},
|
||||
hidden({ scope }) {
|
||||
return scope.age < 18;
|
||||
}
|
||||
},
|
||||
{
|
||||
group: "B",
|
||||
prop: "phone",
|
||||
label: "手机",
|
||||
component: {
|
||||
name: "el-input"
|
||||
},
|
||||
hidden({ scope }) {
|
||||
return scope.age < 18;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
return {
|
||||
group: "A",
|
||||
hidden: Upsert.value?.mode == "add"
|
||||
};
|
||||
}
|
||||
],
|
||||
onOpened(data) {
|
||||
console.log(data);
|
||||
Upsert.value?.setForm("age", "18");
|
||||
}
|
||||
});
|
||||
|
||||
const Table = useTable<Data>(
|
||||
{
|
||||
contextMenu: [
|
||||
{
|
||||
label: "带图标",
|
||||
prefixIcon: EditPen
|
||||
},
|
||||
{
|
||||
label: "多层级",
|
||||
children: [
|
||||
{
|
||||
label: "A",
|
||||
children: [
|
||||
{
|
||||
label: "A-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "B"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
columns: [
|
||||
{
|
||||
label: "姓名",
|
||||
prop: "name",
|
||||
search: {
|
||||
component: {
|
||||
name: "el-date-picker"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "手机号",
|
||||
prop: "phone",
|
||||
search: {
|
||||
component: {
|
||||
name: "el-date-picker"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "op"
|
||||
}
|
||||
]
|
||||
},
|
||||
(table) => {
|
||||
console.log(table);
|
||||
}
|
||||
);
|
||||
|
||||
const Crud = useCrud(
|
||||
{
|
||||
service: "test"
|
||||
},
|
||||
(app) => {
|
||||
app.refresh();
|
||||
}
|
||||
);
|
||||
|
||||
const Form = useForm<Data>();
|
||||
|
||||
const Search = useSearch({
|
||||
collapse: true,
|
||||
resetBtn: true,
|
||||
items: [
|
||||
{
|
||||
label: "姓名",
|
||||
prop: "name",
|
||||
component: {
|
||||
name: "el-input"
|
||||
},
|
||||
hook: {
|
||||
reset() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,21 @@
|
||||
import { defineComponent } from "vue";
|
||||
import { useConfig, useCore } from "../../hooks";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-add-btn",
|
||||
|
||||
setup(_, { slots }) {
|
||||
const { crud } = useCore();
|
||||
const { style } = useConfig();
|
||||
|
||||
return () => {
|
||||
return (
|
||||
crud.getPermission("add") && (
|
||||
<el-button type="primary" size={style.size} onClick={crud.rowAdd}>
|
||||
{slots.default?.() || crud.dict.label.add}
|
||||
</el-button>
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
31
cool-admin-vue/packages/crud/src/components/adv/btn.tsx
Normal file
31
cool-admin-vue/packages/crud/src/components/adv/btn.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useConfig, useCore } 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 } = useConfig();
|
||||
|
||||
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?.() || crud.dict.label.advSearch}
|
||||
</el-button>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
212
cool-admin-vue/packages/crud/src/components/adv/search.tsx
Normal file
212
cool-admin-vue/packages/crud/src/components/adv/search.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
inject,
|
||||
mergeProps,
|
||||
nextTick,
|
||||
type PropType,
|
||||
reactive,
|
||||
ref
|
||||
} from "vue";
|
||||
import { Close } from "@element-plus/icons-vue";
|
||||
import { useBrowser, useConfig, useCore } from "../../hooks";
|
||||
import { renderNode } from "../../utils/vnode";
|
||||
import { useApi } from "../form/helper";
|
||||
import { isArray } from "lodash-es";
|
||||
|
||||
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 } = useConfig();
|
||||
const browser = useBrowser();
|
||||
|
||||
// 配置
|
||||
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(() => {
|
||||
Form.value?.open({
|
||||
items: config.items || [],
|
||||
op: {
|
||||
hidden: true
|
||||
},
|
||||
isReset: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭
|
||||
function close() {
|
||||
Drawer.value.handleClose();
|
||||
}
|
||||
|
||||
// 重置数据
|
||||
function reset() {
|
||||
const d: any = {};
|
||||
|
||||
config.items?.map((e) => {
|
||||
if (typeof e.hook != "string" && e.hook?.reset) {
|
||||
const props = e.hook.reset(e.prop!);
|
||||
|
||||
if (isArray(props)) {
|
||||
props.forEach((prop) => {
|
||||
d[prop] = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
d[e.prop!] = undefined;
|
||||
});
|
||||
|
||||
// 重置表单
|
||||
Form.value?.reset();
|
||||
|
||||
// 列表刷新
|
||||
search();
|
||||
|
||||
// 重置事件
|
||||
emit("reset", d);
|
||||
}
|
||||
|
||||
// 清空数据
|
||||
function clear() {
|
||||
Form.value?.clear();
|
||||
emit("clear");
|
||||
}
|
||||
|
||||
// 搜素请求
|
||||
function search(params?: any) {
|
||||
const form = Form.value?.getForm();
|
||||
|
||||
function next(data: any) {
|
||||
Form.value?.done();
|
||||
close();
|
||||
|
||||
return crud.refresh({
|
||||
...data,
|
||||
...params,
|
||||
page: 1
|
||||
});
|
||||
}
|
||||
|
||||
if (config.onSearch) {
|
||||
config.onSearch(form, { next, close });
|
||||
} else {
|
||||
next(form);
|
||||
}
|
||||
}
|
||||
|
||||
// 消息事件
|
||||
mitt.on("crud.openAdvSearch", open);
|
||||
|
||||
// 渲染表单
|
||||
function renderForm() {
|
||||
return h(<cl-form ref={Form} inner enable-plugin={false} />, {}, 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,
|
||||
...useApi({ Form }),
|
||||
reset,
|
||||
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%" : config.size}>
|
||||
<div class="cl-adv-search__header">
|
||||
<span class="text">{config.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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
@@ -0,0 +1,279 @@
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
nextTick,
|
||||
onMounted,
|
||||
type PropType,
|
||||
reactive,
|
||||
ref,
|
||||
render,
|
||||
toRaw
|
||||
} from "vue";
|
||||
import { isString } from "lodash-es";
|
||||
import { addClass, contains, removeClass } from "../../utils";
|
||||
import { useRefs } from "../../hooks";
|
||||
import { ElIcon } from "element-plus";
|
||||
import { ArrowRight } from "@element-plus/icons-vue";
|
||||
|
||||
const ClContextMenu = defineComponent({
|
||||
name: "cl-context-menu",
|
||||
|
||||
props: {
|
||||
show: Boolean,
|
||||
options: {
|
||||
type: Object as PropType<ClContextMenu.Options>,
|
||||
default: () => ({})
|
||||
},
|
||||
event: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
|
||||
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: any) {
|
||||
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;
|
||||
|
||||
// 关闭
|
||||
function close() {
|
||||
visible.value = false;
|
||||
ids.value = "";
|
||||
|
||||
if (targetEl) {
|
||||
removeClass(targetEl, "cl-context-menu__target");
|
||||
}
|
||||
}
|
||||
|
||||
// 打开
|
||||
function open(event: any, options: ClContextMenu.Options = {}) {
|
||||
// 阻止默认事件
|
||||
stopDefault(event);
|
||||
|
||||
// 显示
|
||||
visible.value = true;
|
||||
|
||||
// 元素
|
||||
const el = refs["context-menu"].querySelector(".cl-context-menu__box") as HTMLElement;
|
||||
|
||||
// 点击样式
|
||||
if (options?.hover) {
|
||||
const 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?.class) {
|
||||
addClass(el, options.class);
|
||||
}
|
||||
|
||||
// 菜单列表
|
||||
if (options?.list) {
|
||||
list.value = parseList(options.list);
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
// 计算位置
|
||||
let left = event.pageX;
|
||||
let top = event.pageY;
|
||||
|
||||
// 组件方式用 offset 计算
|
||||
if (!props.show) {
|
||||
left = event.offsetX;
|
||||
top = event.offsetY;
|
||||
}
|
||||
|
||||
const { clientHeight: h1, clientWidth: w1 } = event.target?.ownerDocument.body;
|
||||
const { clientHeight: h2, clientWidth: w2 } = el;
|
||||
|
||||
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}`;
|
||||
|
||||
if (!e.suffixIcon) {
|
||||
// 默认图标
|
||||
if (e.children) {
|
||||
e.suffixIcon = ArrowRight;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={{
|
||||
"is-active": ids.value.includes(id),
|
||||
"is-ellipsis": e.ellipsis ?? true,
|
||||
"is-disabled": e.disabled
|
||||
}}
|
||||
onClick={(ev: MouseEvent) => {
|
||||
rowClick(e, id);
|
||||
ev.stopPropagation();
|
||||
}}>
|
||||
{/* 前缀图标 */}
|
||||
{e.prefixIcon && <ElIcon>{h(toRaw(e.prefixIcon))}</ElIcon>}
|
||||
|
||||
{/* 标题 */}
|
||||
<span>{e.label}</span>
|
||||
|
||||
{/* 后缀图标 */}
|
||||
{e.suffixIcon && <ElIcon>{h(toRaw(e.suffixIcon))}</ElIcon>}
|
||||
|
||||
{/* 子集 */}
|
||||
{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 = h(ClContextMenu, {
|
||||
show: true,
|
||||
event,
|
||||
options
|
||||
});
|
||||
|
||||
render(vm, event.target.ownerDocument.createElement("div"));
|
||||
|
||||
return vm.component?.exposed as ClContextMenu.Exposed;
|
||||
}
|
||||
};
|
||||
|
||||
export default ClContextMenu;
|
287
cool-admin-vue/packages/crud/src/components/crud/helper.ts
Normal file
287
cool-admin-vue/packages/crud/src/components/crud/helper.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Mitt } from "../../utils/mitt";
|
||||
import { ref } from "vue";
|
||||
import { assign, isArray, isFunction } from "lodash-es";
|
||||
import { merge } from "../../utils";
|
||||
|
||||
interface Options {
|
||||
mitt: Mitt;
|
||||
config: ClCrud.Config;
|
||||
crud: ClCrud.Ref;
|
||||
}
|
||||
|
||||
export function useHelper({ config, crud, mitt }: 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((success, error) => {
|
||||
// 合并请求参数
|
||||
const reqParams = paramsReplace(assign(crud.params, params));
|
||||
|
||||
// Loading
|
||||
crud.loading = true;
|
||||
|
||||
// 预防脏数据
|
||||
const rd = (refreshRd.value = Math.random());
|
||||
|
||||
// 完成事件
|
||||
function done() {
|
||||
crud.loading = false;
|
||||
}
|
||||
|
||||
// 渲染
|
||||
function render(data: any | any[], pagination?: any) {
|
||||
const res = isArray(data) ? { list: data, pagination } : data;
|
||||
done();
|
||||
success(res);
|
||||
mitt.emit("crud.refresh", res);
|
||||
}
|
||||
|
||||
// 下一步
|
||||
function next(params: obj): Promise<any> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
await service[dict.api.page](params)
|
||||
.then((res) => {
|
||||
if (rd != refreshRd.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isArray(res)) {
|
||||
res = {
|
||||
list: res,
|
||||
pagination: {
|
||||
total: res.length
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render(res);
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
ElMessage.error(err.message);
|
||||
error(err);
|
||||
reject(err);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
// 刷新钩子
|
||||
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 setParams(data: obj) {
|
||||
merge(crud.params, data);
|
||||
}
|
||||
|
||||
// 设置
|
||||
function set(key: string, value: any) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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:
|
||||
merge(crud[key], value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听事件
|
||||
function on(name: string, callback: fn) {
|
||||
mitt.on(`${name}-${crud.id}`, callback);
|
||||
}
|
||||
|
||||
// 默认值
|
||||
set("dict", config.dict);
|
||||
set("service", config.service);
|
||||
set("permission", config.permission);
|
||||
|
||||
return {
|
||||
proxy,
|
||||
set,
|
||||
on,
|
||||
rowInfo,
|
||||
rowAdd,
|
||||
rowEdit,
|
||||
rowAppend,
|
||||
rowDelete,
|
||||
rowClose,
|
||||
refresh,
|
||||
getPermission,
|
||||
paramsReplace,
|
||||
getParams,
|
||||
setParams
|
||||
};
|
||||
}
|
91
cool-admin-vue/packages/crud/src/components/crud/index.tsx
Normal file
91
cool-admin-vue/packages/crud/src/components/crud/index.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { defineComponent, getCurrentInstance, inject, provide, reactive } from "vue";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { useHelper } from "./helper";
|
||||
import { Mitt } from "../../utils/mitt";
|
||||
import { merge, mergeConfig } from "../../utils";
|
||||
import { crudList } from "../../emitter";
|
||||
import { useConfig } from "../../hooks";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-crud",
|
||||
|
||||
props: {
|
||||
// 组件名
|
||||
name: String,
|
||||
// 是否有边框
|
||||
border: Boolean,
|
||||
// 内间距
|
||||
padding: {
|
||||
type: String,
|
||||
default: "10px"
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { slots, expose }) {
|
||||
// 当前实例
|
||||
const inst = getCurrentInstance();
|
||||
|
||||
// 配置
|
||||
const config = reactive<ClCrud.Config>(mergeConfig(inject("useCrud__options") || {}));
|
||||
|
||||
// 事件
|
||||
const mitt = new Mitt(inst?.uid);
|
||||
|
||||
// 全局配置
|
||||
const { dict, permission } = useConfig();
|
||||
|
||||
// 参数
|
||||
const crud = reactive(
|
||||
merge(
|
||||
{
|
||||
id: props.name || inst?.uid,
|
||||
// 绑定的路由地址
|
||||
routePath: location.pathname || "/",
|
||||
// 表格加载状态
|
||||
loading: false,
|
||||
// 表格已选列
|
||||
selection: [],
|
||||
// 请求参数
|
||||
params: {
|
||||
page: 1,
|
||||
size: 20
|
||||
},
|
||||
// 请求服务
|
||||
service: {},
|
||||
// 字典
|
||||
dict: {},
|
||||
// 权限
|
||||
permission: {},
|
||||
// 事件
|
||||
mitt,
|
||||
// 配置
|
||||
config
|
||||
},
|
||||
cloneDeep({ dict, permission })
|
||||
)
|
||||
);
|
||||
|
||||
// 追加参数
|
||||
merge(crud, useHelper({ config, crud, mitt }));
|
||||
|
||||
// 集合
|
||||
crudList.push(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?.()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
288
cool-admin-vue/packages/crud/src/components/dialog/index.tsx
Normal file
288
cool-admin-vue/packages/crud/src/components/dialog/index.tsx
Normal file
@@ -0,0 +1,288 @@
|
||||
import { computed, defineComponent, h, provide, ref, watch } from "vue";
|
||||
import { Close, FullScreen, Minus } from "@element-plus/icons-vue";
|
||||
import { renderNode } from "../../utils/vnode";
|
||||
import { isArray, isBoolean } from "lodash-es";
|
||||
import { useBrowser } from "../../hooks";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-dialog",
|
||||
|
||||
components: {
|
||||
Close,
|
||||
FullScreen,
|
||||
Minus
|
||||
},
|
||||
|
||||
props: {
|
||||
// 是否可见
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// Extraneous non-props attributes
|
||||
props: Object,
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: "-"
|
||||
},
|
||||
// 高度
|
||||
height: String,
|
||||
// 宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: "50%"
|
||||
},
|
||||
// 內间距
|
||||
padding: {
|
||||
type: String,
|
||||
default: "20px"
|
||||
},
|
||||
// 是否缓存
|
||||
keepAlive: Boolean,
|
||||
// 是否全屏
|
||||
fullscreen: Boolean,
|
||||
// 控制按钮
|
||||
controls: {
|
||||
type: Array,
|
||||
default: () => ["fullscreen", "close"]
|
||||
},
|
||||
// 隐藏头部元素
|
||||
hideHeader: Boolean,
|
||||
// 关闭前
|
||||
beforeClose: Function,
|
||||
// 是否需要滚动条
|
||||
scrollbar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 背景透明
|
||||
transparent: Boolean
|
||||
},
|
||||
|
||||
emits: ["update:modelValue", "fullscreen-change"],
|
||||
|
||||
setup(props, { emit, expose, slots }) {
|
||||
const browser = useBrowser();
|
||||
|
||||
// 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) => {
|
||||
visible.value = val;
|
||||
if (val && !props.keepAlive) {
|
||||
cacheKey.value += 1;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
// 监听 fullscreen 变化
|
||||
watch(
|
||||
() => props.fullscreen,
|
||||
(val) => {
|
||||
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", { "is-transparent": props.transparent }]}
|
||||
width={props.width}
|
||||
beforeClose={props.beforeClose}
|
||||
show-close={false}
|
||||
append-to-body
|
||||
fullscreen={isFullscreen.value}
|
||||
v-model={visible.value}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
{},
|
||||
{
|
||||
header() {
|
||||
return renderHeader();
|
||||
},
|
||||
default() {
|
||||
const height = isFullscreen.value ? "100%" : props.height;
|
||||
|
||||
const style = {
|
||||
padding: props.padding,
|
||||
height
|
||||
};
|
||||
|
||||
function content() {
|
||||
return (
|
||||
<div class="cl-dialog__default" style={style} key={cacheKey.value}>
|
||||
{slots.default?.()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.scrollbar) {
|
||||
style.height = "auto";
|
||||
|
||||
return <el-scrollbar height={height}>{content()}</el-scrollbar>;
|
||||
} else {
|
||||
return content();
|
||||
}
|
||||
},
|
||||
footer() {
|
||||
const d = slots.footer?.();
|
||||
|
||||
if (d && d[0]?.shapeFlag) {
|
||||
return <div class="cl-dialog__footer">{d}</div>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
@@ -0,0 +1,15 @@
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-error-message",
|
||||
|
||||
props: {
|
||||
title: String
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
return () => {
|
||||
return <div class="cl-error-message">{props.title}</div>;
|
||||
};
|
||||
}
|
||||
});
|
23
cool-admin-vue/packages/crud/src/components/filter/index.tsx
Normal file
23
cool-admin-vue/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?.()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
11
cool-admin-vue/packages/crud/src/components/flex1/index.tsx
Normal file
11
cool-admin-vue/packages/crud/src/components/flex1/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-flex1",
|
||||
|
||||
setup() {
|
||||
return () => {
|
||||
return <div class="cl-flex1" />;
|
||||
};
|
||||
}
|
||||
});
|
@@ -0,0 +1,51 @@
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { ArrowDown, ArrowUp } from "@element-plus/icons-vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-form-card",
|
||||
|
||||
components: {
|
||||
ArrowDown,
|
||||
ArrowUp
|
||||
},
|
||||
|
||||
props: {
|
||||
label: String,
|
||||
// 展开状态
|
||||
expand: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否能展开、收起
|
||||
isExpand: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { slots }) {
|
||||
const visible = ref(props.expand);
|
||||
|
||||
function toExpand() {
|
||||
if (props.isExpand) {
|
||||
visible.value = !visible.value;
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<div class={["cl-form-card", { "is-expand": visible.value }]}>
|
||||
<div class="cl-form-card__header" v-show={props.label} onClick={toExpand}>
|
||||
<span>{props.label}</span>
|
||||
|
||||
<el-icon v-show={props.isExpand}>
|
||||
<arrow-down v-show={!visible.value} />
|
||||
<arrow-up v-show={visible.value} />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="cl-form-card__container">{slots.default?.()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
145
cool-admin-vue/packages/crud/src/components/form-tabs/index.tsx
Normal file
145
cool-admin-vue/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 { isEmpty } from "lodash-es";
|
||||
import { useDialog, useRefs } from "../../hooks";
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
@@ -0,0 +1,146 @@
|
||||
import { assign } from "lodash-es";
|
||||
import { dataset } from "../../../utils";
|
||||
|
||||
export function useAction({
|
||||
config,
|
||||
form,
|
||||
Form
|
||||
}: {
|
||||
config: ClForm.Config;
|
||||
form: obj;
|
||||
Form: Vue.Ref<any>;
|
||||
}) {
|
||||
// 设置数据
|
||||
function set(
|
||||
{
|
||||
prop,
|
||||
key,
|
||||
path
|
||||
}: { prop?: string; key?: "options" | "props" | "hidden" | "hidden-toggle"; path?: string },
|
||||
data?: any
|
||||
) {
|
||||
const p: string = path || "";
|
||||
|
||||
if (path) {
|
||||
dataset(config, p, data);
|
||||
} else {
|
||||
let d: any;
|
||||
|
||||
if (prop) {
|
||||
function deep(arr: ClForm.Item[]) {
|
||||
arr.forEach((e) => {
|
||||
if (e.prop == prop) {
|
||||
d = e;
|
||||
} else {
|
||||
if (e.children) {
|
||||
deep(e.children);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deep(config.items);
|
||||
}
|
||||
|
||||
if (d) {
|
||||
switch (key) {
|
||||
case "options":
|
||||
d.component.options = data;
|
||||
break;
|
||||
|
||||
case "props":
|
||||
assign(d.component.props, data);
|
||||
break;
|
||||
|
||||
case "hidden":
|
||||
d.hidden = data;
|
||||
break;
|
||||
|
||||
case "hidden-toggle":
|
||||
d.hidden = data === undefined ? !d.hidden : !data;
|
||||
break;
|
||||
|
||||
default:
|
||||
assign(d, data);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
console.error(`[set] ${prop} is not found`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表单值
|
||||
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({ prop, key: "options" }, value);
|
||||
}
|
||||
|
||||
// 设置表单项的组件参数
|
||||
function setProps(prop: string, value: any) {
|
||||
set({ prop, key: "props" }, value);
|
||||
}
|
||||
|
||||
// 切换表单项的显示、隐藏
|
||||
function toggleItem(prop: string, value?: boolean) {
|
||||
set({ prop, key: "hidden-toggle" }, value);
|
||||
}
|
||||
|
||||
// 对部分表单项隐藏
|
||||
function hideItem(...props: string[]) {
|
||||
props.forEach((prop) => {
|
||||
set({ prop, key: "hidden" }, true);
|
||||
});
|
||||
}
|
||||
|
||||
// 对部分表单项显示
|
||||
function showItem(...props: string[]) {
|
||||
props.forEach((prop) => {
|
||||
set({ prop, key: "hidden" }, 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
|
||||
};
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
import { useElApi } from "../../../hooks";
|
||||
|
||||
export function useApi({ Form }: { Form: Vue.Ref<any> }) {
|
||||
return useElApi(
|
||||
[
|
||||
"open",
|
||||
"close",
|
||||
"clear",
|
||||
"reset",
|
||||
"submit",
|
||||
"bindForm",
|
||||
"changeTab",
|
||||
"setTitle",
|
||||
"showLoading",
|
||||
"hideLoading",
|
||||
"collapseItem",
|
||||
"getForm",
|
||||
"setForm",
|
||||
"invokeData",
|
||||
"setData",
|
||||
"setConfig",
|
||||
"setOptions",
|
||||
"setProps",
|
||||
"toggleItem",
|
||||
"hideItem",
|
||||
"showItem",
|
||||
"validate",
|
||||
"validateField",
|
||||
"resetFields",
|
||||
"scrollToField",
|
||||
"clearValidate",
|
||||
"fields"
|
||||
],
|
||||
Form
|
||||
);
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { useConfig } from "../../../hooks";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
|
||||
export function useForm() {
|
||||
const { dict } = useConfig();
|
||||
|
||||
// 表单配置
|
||||
const config = reactive<ClForm.Config>({
|
||||
title: "-",
|
||||
height: undefined,
|
||||
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<obj>({});
|
||||
|
||||
// 表单数据备份
|
||||
const oldForm = ref<obj>({});
|
||||
|
||||
// 表单是否可见
|
||||
const visible = ref(false);
|
||||
|
||||
// 表单提交保存状态
|
||||
const saving = ref(false);
|
||||
|
||||
// 表单加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单禁用状态
|
||||
const disabled = ref(false);
|
||||
|
||||
// 监听表单变化
|
||||
watch(
|
||||
() => form,
|
||||
(val) => {
|
||||
if (config.on?.change) {
|
||||
for (const i in val) {
|
||||
if (form[i] !== oldForm.value[i]) {
|
||||
config.on?.change(val, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldForm.value = cloneDeep(val);
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
Form,
|
||||
config,
|
||||
form,
|
||||
visible,
|
||||
saving,
|
||||
loading,
|
||||
disabled
|
||||
};
|
||||
}
|
||||
|
||||
export * from "./action";
|
||||
export * from "./api";
|
||||
export * from "./plugins";
|
||||
export * from "./tabs";
|
@@ -0,0 +1,92 @@
|
||||
import { getCurrentInstance, type Ref, watch, type WatchStopHandle } from "vue";
|
||||
import { useConfig } from "../../../hooks";
|
||||
import { uniqueFns } from "../../../utils";
|
||||
|
||||
export function usePlugins(enable: boolean, { visible }: { visible: Ref<boolean> }) {
|
||||
const that: any = getCurrentInstance();
|
||||
const { style } = useConfig();
|
||||
|
||||
interface Event {
|
||||
onOpen: (() => void)[];
|
||||
onClose: (() => void)[];
|
||||
onSubmit: ((data: obj) => Promise<obj> | obj)[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 事件
|
||||
const ev: Event = {
|
||||
onOpen: [],
|
||||
onClose: [],
|
||||
onSubmit: []
|
||||
};
|
||||
|
||||
// 监听器
|
||||
let timer: WatchStopHandle | null = null;
|
||||
|
||||
// 插件创建
|
||||
function create(plugins: ClForm.Plugin[] = []) {
|
||||
if (!enable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const i in ev) {
|
||||
ev[i] = [];
|
||||
}
|
||||
|
||||
// 停止监听
|
||||
if (timer) {
|
||||
timer();
|
||||
}
|
||||
|
||||
// 执行
|
||||
uniqueFns([...(style.form.plugins || []), ...plugins]).forEach((p) => {
|
||||
const d: any = {
|
||||
exposed: that.exposed
|
||||
};
|
||||
|
||||
for (const i in ev) {
|
||||
d[i] = (cb: any) => {
|
||||
ev[i].push(cb);
|
||||
};
|
||||
}
|
||||
|
||||
p(d);
|
||||
});
|
||||
|
||||
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
|
||||
};
|
||||
}
|
151
cool-admin-vue/packages/crud/src/components/form/helper/tabs.ts
Normal file
151
cool-admin-vue/packages/crud/src/components/form/helper/tabs.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
export function useTabs({ config, Form }: { config: ClForm.Config; Form: Vue.Ref<any> }) {
|
||||
// 选中
|
||||
const active = ref<string | undefined>();
|
||||
|
||||
// 列表
|
||||
const list = computed(() => {
|
||||
return get()?.props?.labels || [];
|
||||
});
|
||||
|
||||
// 获取选项
|
||||
function getItem(value: any) {
|
||||
return list.value.find((e) => e.value == value);
|
||||
}
|
||||
|
||||
// 是否已加载
|
||||
function isLoaded(value: any) {
|
||||
const d = getItem(value);
|
||||
return d?.lazy ? d.loaded : true;
|
||||
}
|
||||
|
||||
// 加载后
|
||||
function onLoad(value: any) {
|
||||
const d = getItem(value);
|
||||
d!.loaded = true;
|
||||
}
|
||||
|
||||
// 查找分组
|
||||
function toGroup(opts: { config: ClForm.Config; prop: string; refs: any }) {
|
||||
if (active.value) {
|
||||
let name;
|
||||
|
||||
// 查找标签上绑定的数据
|
||||
const el = opts.refs.form.querySelector(`[data-prop="${opts.prop}"]`);
|
||||
|
||||
// 各自判断
|
||||
if (el) {
|
||||
name = el?.getAttribute("data-group");
|
||||
} else {
|
||||
function deep(d: ClForm.Item) {
|
||||
if (d.prop == opts.prop) {
|
||||
name = d.group;
|
||||
} else {
|
||||
if (d.children) {
|
||||
d.children.forEach(deep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.items.forEach(deep);
|
||||
}
|
||||
|
||||
if (name) {
|
||||
set(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
function get() {
|
||||
return config.items.find((e) => e.type === "tabs");
|
||||
}
|
||||
|
||||
// 设置参数
|
||||
function set(data: any) {
|
||||
active.value = data;
|
||||
}
|
||||
|
||||
// 清空
|
||||
function clear() {
|
||||
// 清空选中
|
||||
active.value = undefined;
|
||||
|
||||
// 清空加载状态
|
||||
list.value.forEach((e) => {
|
||||
if (e.lazy && e.loaded) {
|
||||
e.loaded = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 切换
|
||||
function change(value: any, isValid = true) {
|
||||
return new Promise((resolve: Function, reject: Function) => {
|
||||
function next() {
|
||||
active.value = value;
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
let isError = false;
|
||||
|
||||
const arr = config.items
|
||||
.filter((e) => e.group == active.value && !e._hidden && e.prop)
|
||||
.map((e) => {
|
||||
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,
|
||||
list,
|
||||
isLoaded,
|
||||
onLoad,
|
||||
get,
|
||||
set,
|
||||
change,
|
||||
clear,
|
||||
mergeProp,
|
||||
toGroup
|
||||
};
|
||||
}
|
683
cool-admin-vue/packages/crud/src/components/form/index.tsx
Normal file
683
cool-admin-vue/packages/crud/src/components/form/index.tsx
Normal file
@@ -0,0 +1,683 @@
|
||||
import { defineComponent, h, nextTick } from "vue";
|
||||
import { assign, cloneDeep, isBoolean, isFunction, keys } from "lodash-es";
|
||||
import { useAction, useForm, usePlugins, useTabs } from "./helper";
|
||||
import { useBrowser, useConfig, useElApi, useRefs } from "../../hooks";
|
||||
import { getValue, merge } from "../../utils";
|
||||
import formHook from "../../utils/form-hook";
|
||||
import { renderNode } from "../../utils/vnode";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-form",
|
||||
|
||||
props: {
|
||||
name: String,
|
||||
inner: Boolean,
|
||||
inline: Boolean,
|
||||
enablePlugin: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { expose, slots }) {
|
||||
const { refs, setRefs } = useRefs();
|
||||
const { style, dict } = useConfig();
|
||||
const browser = useBrowser();
|
||||
const { Form, config, form, visible, saving, loading, disabled } = useForm();
|
||||
|
||||
// 关闭的操作类型
|
||||
let closeAction: ClForm.CloseAction = "close";
|
||||
|
||||
// 旧表单数据
|
||||
let defForm: obj | undefined;
|
||||
|
||||
// 选项卡
|
||||
const Tabs = useTabs({ config, Form });
|
||||
|
||||
// 操作
|
||||
const Action = useAction({ config, form, Form });
|
||||
|
||||
// 方法
|
||||
const ElFormApi = useElApi(
|
||||
[
|
||||
"validate",
|
||||
"validateField",
|
||||
"resetFields",
|
||||
"scrollToField",
|
||||
"clearValidate",
|
||||
"fields"
|
||||
],
|
||||
Form
|
||||
);
|
||||
|
||||
// 插件
|
||||
const plugin = usePlugins(props.enablePlugin, { visible });
|
||||
|
||||
// 显示加载中
|
||||
function showLoading() {
|
||||
loading.value = true;
|
||||
}
|
||||
|
||||
// 隐藏加载
|
||||
function hideLoading() {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 设置是否禁用
|
||||
function setDisabled(val: boolean = true) {
|
||||
disabled.value = val;
|
||||
}
|
||||
|
||||
// 请求表单保存状态
|
||||
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 invokeData(d: any) {
|
||||
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) => {
|
||||
if (!f[e]) {
|
||||
f[e] = {};
|
||||
}
|
||||
|
||||
f = f[e];
|
||||
});
|
||||
|
||||
// 设置关键值
|
||||
f[k] = d[i];
|
||||
|
||||
delete d[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) => {
|
||||
function deep(e: ClForm.Item) {
|
||||
if (e.prop) {
|
||||
// 过滤隐藏的表单项
|
||||
if (e._hidden) {
|
||||
if (e.prop) {
|
||||
delete d[e.prop];
|
||||
}
|
||||
}
|
||||
|
||||
// hook 提交处理
|
||||
if (e.hook) {
|
||||
formHook.submit({
|
||||
...e,
|
||||
value: e.prop ? d[e.prop] : undefined,
|
||||
form: d
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (e.children) {
|
||||
e.children.forEach(deep);
|
||||
}
|
||||
}
|
||||
|
||||
deep(e);
|
||||
});
|
||||
|
||||
// 处理数据
|
||||
invokeData(d);
|
||||
|
||||
const submit = callback || config.on?.submit;
|
||||
|
||||
// 提交事件
|
||||
if (submit) {
|
||||
submit(await plugin.submit(d), {
|
||||
close() {
|
||||
close("save");
|
||||
},
|
||||
done
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
} else {
|
||||
// 切换到对应的选项卡
|
||||
Tabs.toGroup({
|
||||
refs,
|
||||
config,
|
||||
prop: keys(error)[0]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 打开表单
|
||||
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":
|
||||
function deep(arr: any[]): any[] {
|
||||
return arr.map((e) => {
|
||||
const d = getValue(e);
|
||||
|
||||
return {
|
||||
...d,
|
||||
children: d?.children ? deep(d.children) : undefined
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
config.items = deep(options.items || []);
|
||||
break;
|
||||
// 事件、参数、操作
|
||||
case "on":
|
||||
case "op":
|
||||
case "props":
|
||||
case "dialog":
|
||||
case "_data":
|
||||
merge(config[i], options[i] || {});
|
||||
break;
|
||||
// 其他
|
||||
default:
|
||||
config[i] = options[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 预设表单值
|
||||
if (options?.form) {
|
||||
for (const i in options.form) {
|
||||
form[i] = options.form[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 设置表单数据
|
||||
config.items.forEach((e) => {
|
||||
function deep(e: ClForm.Item) {
|
||||
if (e.prop) {
|
||||
// 解析 prop
|
||||
if (e.prop.includes(".")) {
|
||||
e.prop = e.prop.replace(/\./g, "-");
|
||||
}
|
||||
|
||||
// prop 合并
|
||||
Tabs.mergeProp(e);
|
||||
|
||||
// hook 绑定值
|
||||
formHook.bind({
|
||||
...e,
|
||||
value: form[e.prop] !== undefined ? form[e.prop] : cloneDeep(e.value),
|
||||
form
|
||||
});
|
||||
|
||||
// 表单验证
|
||||
if (e.required) {
|
||||
e.rules = {
|
||||
required: true,
|
||||
message: dict.label.nonEmpty.replace("{label}", e.label || "")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 设置 tabs 默认值
|
||||
if (e.type == "tabs") {
|
||||
Tabs.set(e.value);
|
||||
}
|
||||
|
||||
// 子集
|
||||
if (e.children) {
|
||||
e.children.forEach(deep);
|
||||
}
|
||||
}
|
||||
|
||||
deep(e);
|
||||
});
|
||||
|
||||
// 设置默认值
|
||||
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) => {
|
||||
function deep(e: ClForm.Item) {
|
||||
formHook.bind({
|
||||
...e,
|
||||
value: e.prop ? data[e.prop] : undefined,
|
||||
form: data
|
||||
});
|
||||
|
||||
if (e.children) {
|
||||
e.children.forEach(deep);
|
||||
}
|
||||
}
|
||||
|
||||
deep(e);
|
||||
});
|
||||
|
||||
assign(form, data);
|
||||
}
|
||||
|
||||
// 渲染表单项
|
||||
function renderFormItem(e: ClForm.Item) {
|
||||
const { isDisabled } = config._data;
|
||||
|
||||
if (e.type == "tabs") {
|
||||
return (
|
||||
<cl-form-tabs v-model={Tabs.active.value} {...e.props} onChange={Tabs.onLoad} />
|
||||
);
|
||||
}
|
||||
|
||||
// 是否隐藏
|
||||
e._hidden = parseHidden(e.hidden, {
|
||||
scope: form
|
||||
});
|
||||
|
||||
// 分组显示
|
||||
const inGroup = e.group ? e.group === Tabs.active.value : true;
|
||||
|
||||
// 是否已加载完成
|
||||
const isLoaded = e.component && Tabs.isLoaded(e.group);
|
||||
|
||||
// 表单项
|
||||
const FormItem = h(
|
||||
<el-form-item
|
||||
class={{
|
||||
"no-label": !(e.renderLabel || e.label),
|
||||
"has-children": !!e.children
|
||||
}}
|
||||
key={e.prop}
|
||||
data-group={e.group || "-"}
|
||||
data-prop={e.prop || "-"}
|
||||
label-width={props.inline ? "auto" : ""}
|
||||
label={e.label}
|
||||
prop={e.prop}
|
||||
rules={isDisabled ? null : e.rules}
|
||||
required={e._hidden ? false : e.required}
|
||||
v-show={inGroup && !e._hidden}
|
||||
/>,
|
||||
e.props,
|
||||
{
|
||||
label() {
|
||||
if (e.renderLabel) {
|
||||
return renderNode(e.renderLabel, {
|
||||
scope: form,
|
||||
render: "slot",
|
||||
slots
|
||||
});
|
||||
} else {
|
||||
return e.label;
|
||||
}
|
||||
},
|
||||
default() {
|
||||
return (
|
||||
<div>
|
||||
<div class="cl-form-item">
|
||||
{["prepend", "component", "append"]
|
||||
.filter((k) => e[k])
|
||||
.map((name) => {
|
||||
const children = e.children && (
|
||||
<div class="cl-form-item__children">
|
||||
<el-row gutter={10}>
|
||||
{e.children.map(renderFormItem)}
|
||||
</el-row>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Item = renderNode(e[name], {
|
||||
item: e,
|
||||
prop: e.prop,
|
||||
scope: form,
|
||||
slots,
|
||||
children,
|
||||
_data: {
|
||||
isDisabled
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
v-show={!e.collapse}
|
||||
class={[
|
||||
`cl-form-item__${name}`,
|
||||
{
|
||||
flex1: e.flex !== false
|
||||
}
|
||||
]}
|
||||
style={e[name].style}>
|
||||
{Item}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{isBoolean(e.collapse) && (
|
||||
<div
|
||||
class="cl-form-item__collapse"
|
||||
onClick={() => {
|
||||
Action.collapseItem(e);
|
||||
}}>
|
||||
<el-divider content-position="center">
|
||||
{e.collapse
|
||||
? dict.label.seeMore
|
||||
: dict.label.hideContent}
|
||||
</el-divider>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let span = e.span || style.form.span;
|
||||
|
||||
if (browser.isMini) {
|
||||
span = 24;
|
||||
}
|
||||
|
||||
// 是否行内
|
||||
const Item = props.inline ? (
|
||||
FormItem
|
||||
) : (
|
||||
<el-col span={span} {...e.col} v-show={inGroup && !e._hidden}>
|
||||
{FormItem}
|
||||
</el-col>
|
||||
);
|
||||
|
||||
return isLoaded ? Item : null;
|
||||
}
|
||||
|
||||
// 渲染表单
|
||||
function renderContainer() {
|
||||
// 表单项列表
|
||||
const children = config.items.map(renderFormItem);
|
||||
|
||||
// 表单标签位置
|
||||
const labelPosition =
|
||||
browser.isMini && !props.inline
|
||||
? "top"
|
||||
: config.props.labelPosition || style.form.labelPosition;
|
||||
|
||||
return (
|
||||
<div class="cl-form__container" ref={setRefs("form")}>
|
||||
{h(
|
||||
<el-form
|
||||
ref={Form}
|
||||
size={style.size}
|
||||
label-width={style.form.labelWidth}
|
||||
inline={props.inline}
|
||||
require-asterisk-position="right"
|
||||
disabled={saving.value}
|
||||
scroll-to-error
|
||||
model={form}
|
||||
onSubmit={(e: Event) => {
|
||||
submit();
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
...config.props,
|
||||
labelPosition
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
const items = [
|
||||
slots.prepend && slots.prepend({ scope: form }),
|
||||
children,
|
||||
slots.append && slots.append({ scope: form })
|
||||
];
|
||||
|
||||
return (
|
||||
<div class="cl-form__items" v-loading={loading.value}>
|
||||
{props.inline ? (
|
||||
items
|
||||
) : (
|
||||
<el-row gutter={10}>{items}</el-row>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 渲染表单底部按钮
|
||||
function renderFooter() {
|
||||
const { hidden, buttons, saveButtonText, closeButtonText, justify } = config.op;
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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() {
|
||||
return (
|
||||
<el-button
|
||||
type={e.type}
|
||||
{...e.props}
|
||||
onClick={() => {
|
||||
e.onClick({ scope: form });
|
||||
}}>
|
||||
{e.label}
|
||||
</el-button>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
class="cl-form__footer"
|
||||
style={{
|
||||
justifyContent: justify || "flex-end"
|
||||
}}>
|
||||
{Btns}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Tools
|
||||
function parseHidden(value: any, { scope }: any) {
|
||||
if (isBoolean(value)) {
|
||||
return value;
|
||||
} else if (isFunction(value)) {
|
||||
return value({ scope });
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
name: props.name,
|
||||
refs,
|
||||
Form,
|
||||
visible,
|
||||
saving,
|
||||
form,
|
||||
config,
|
||||
loading,
|
||||
disabled,
|
||||
open,
|
||||
close,
|
||||
done,
|
||||
clear,
|
||||
reset,
|
||||
submit,
|
||||
invokeData,
|
||||
bindForm,
|
||||
showLoading,
|
||||
hideLoading,
|
||||
setDisabled,
|
||||
Tabs,
|
||||
...Action,
|
||||
...ElFormApi
|
||||
};
|
||||
|
||||
expose(ctx);
|
||||
|
||||
return () => {
|
||||
if (props.inner) {
|
||||
return (
|
||||
visible.value && (
|
||||
<div class="cl-form">
|
||||
{renderContainer()}
|
||||
{renderFooter()}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return h(
|
||||
<cl-dialog v-model={visible.value} class="cl-form" />,
|
||||
{
|
||||
title: config.title,
|
||||
height: config.height,
|
||||
width: config.width,
|
||||
...config.dialog,
|
||||
beforeClose,
|
||||
onClosed,
|
||||
keepAlive: false
|
||||
},
|
||||
{
|
||||
default() {
|
||||
return renderContainer();
|
||||
},
|
||||
footer() {
|
||||
return renderFooter();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
50
cool-admin-vue/packages/crud/src/components/index.tsx
Normal file
50
cool-admin-vue/packages/crud/src/components/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
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 FormCard from "./form-card";
|
||||
import MultiDeleteBtn from "./multi-delete-btn";
|
||||
import Pagination from "./pagination";
|
||||
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 Search from "./search";
|
||||
import ErrorMessage from "./error-message";
|
||||
import Row from "./row";
|
||||
import ContextMenu from "./context-menu";
|
||||
|
||||
export const components: { [key: string]: any } = {
|
||||
Crud,
|
||||
AddBtn,
|
||||
AdvBtn,
|
||||
AdvSearch,
|
||||
Flex,
|
||||
Form,
|
||||
FormTabs,
|
||||
FormCard,
|
||||
MultiDeleteBtn,
|
||||
Pagination,
|
||||
RefreshBtn,
|
||||
SearchKey,
|
||||
Table,
|
||||
Upsert,
|
||||
Dialog,
|
||||
Filter,
|
||||
Search,
|
||||
ErrorMessage,
|
||||
Row,
|
||||
ContextMenu
|
||||
};
|
||||
|
||||
export function useComponent(app: App) {
|
||||
for (const i in components) {
|
||||
app.component(components[i].name, components[i]);
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
import { defineComponent } from "vue";
|
||||
import { useConfig, useCore } from "../../hooks";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-multi-delete-btn",
|
||||
|
||||
setup(_, { slots }) {
|
||||
const { crud } = useCore();
|
||||
const { style } = useConfig();
|
||||
|
||||
return () => {
|
||||
return (
|
||||
crud.getPermission("delete") && (
|
||||
<el-button
|
||||
type="danger"
|
||||
size={style.size}
|
||||
disabled={crud.selection.length === 0}
|
||||
onClick={() => {
|
||||
crud.rowDelete(...crud.selection);
|
||||
}}>
|
||||
{slots.default?.() || crud.dict.label.multiDelete}
|
||||
</el-button>
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
@@ -0,0 +1,90 @@
|
||||
import { defineComponent, h, onMounted, onUnmounted, ref } from "vue";
|
||||
import { useBrowser, useConfig, useCore } from "../../hooks";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-pagination",
|
||||
|
||||
setup(_, { expose }) {
|
||||
const { crud, mitt } = useCore();
|
||||
const { style } = useConfig();
|
||||
const browser = useBrowser();
|
||||
|
||||
// 总数
|
||||
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: obj) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 数据刷新
|
||||
function onRefresh(res: ClCrud.Response["page"]) {
|
||||
setPagination(res.pagination);
|
||||
}
|
||||
|
||||
// 监听刷新事件
|
||||
onMounted(() => {
|
||||
mitt.on("crud.refresh", onRefresh);
|
||||
});
|
||||
|
||||
// 移除监听事件
|
||||
onUnmounted(() => {
|
||||
mitt.off("crud.refresh", onRefresh);
|
||||
});
|
||||
|
||||
expose({
|
||||
total,
|
||||
currentPage,
|
||||
pageSize,
|
||||
setPagination
|
||||
});
|
||||
|
||||
return () => {
|
||||
return h(
|
||||
<el-pagination
|
||||
class="cl-pagination"
|
||||
size={browser.isMini ? "small" : style.size}
|
||||
background
|
||||
page-sizes={[10, 20, 30, 40, 50, 100]}
|
||||
pager-count={browser.isMini ? 5 : 7}
|
||||
layout={
|
||||
browser.isMini ? "total, pager" : "total, sizes, prev, pager, next, jumper"
|
||||
}
|
||||
/>,
|
||||
{
|
||||
onSizeChange,
|
||||
onCurrentChange,
|
||||
total: total.value,
|
||||
currentPage: currentPage.value,
|
||||
pageSize: pageSize.value
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
@@ -0,0 +1,23 @@
|
||||
import { defineComponent } from "vue";
|
||||
import { useConfig, useCore } from "../../hooks";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-refresh-btn",
|
||||
|
||||
setup(_, { slots }) {
|
||||
const { crud } = useCore();
|
||||
const { style } = useConfig();
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<el-button
|
||||
size={style.size}
|
||||
onClick={() => {
|
||||
crud.refresh();
|
||||
}}>
|
||||
{slots.default?.() || crud.dict.label.refresh}
|
||||
</el-button>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
11
cool-admin-vue/packages/crud/src/components/row/index.tsx
Normal file
11
cool-admin-vue/packages/crud/src/components/row/index.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>;
|
||||
};
|
||||
}
|
||||
});
|
179
cool-admin-vue/packages/crud/src/components/search-key/index.tsx
Normal file
179
cool-admin-vue/packages/crud/src/components/search-key/index.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import { computed, defineComponent, type PropType, ref, useModel } from "vue";
|
||||
import { useConfig, useCore } from "../../hooks";
|
||||
import { parsePx } from "../../utils";
|
||||
import { debounce } from "lodash-es";
|
||||
|
||||
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, Number],
|
||||
default: 280
|
||||
},
|
||||
// 是否实时刷新
|
||||
refreshOnInput: Boolean
|
||||
},
|
||||
|
||||
emits: ["update:modelValue", "change", "field-change"],
|
||||
|
||||
setup(props, { emit, expose }) {
|
||||
const { crud } = useCore();
|
||||
const { style } = useConfig();
|
||||
|
||||
// 选中字段
|
||||
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 = useModel(props, "modelValue");
|
||||
|
||||
// 锁
|
||||
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
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
if (props.onSearch) {
|
||||
props.onSearch(params, { next });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 回车搜索
|
||||
function onKeydown({ key }: KeyboardEvent) {
|
||||
if (key === "Enter") {
|
||||
search();
|
||||
}
|
||||
}
|
||||
|
||||
// 监听变化
|
||||
function onChange(val: string) {
|
||||
if (!props.refreshOnInput) {
|
||||
search();
|
||||
lock = true;
|
||||
|
||||
setTimeout(() => {
|
||||
lock = false;
|
||||
}, 300);
|
||||
|
||||
emit("change", val);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听输入
|
||||
const onInput = debounce((val: string) => {
|
||||
emit("change", val);
|
||||
|
||||
if (props.refreshOnInput) {
|
||||
search();
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// 监听字段选择
|
||||
function onFieldChange() {
|
||||
emit("field-change", selectField.value);
|
||||
value.value = undefined;
|
||||
}
|
||||
|
||||
expose({
|
||||
search
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<div class="cl-search-key">
|
||||
<el-select
|
||||
class="cl-search-key__select"
|
||||
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: parsePx(props.width) }}>
|
||||
<el-input
|
||||
v-model={value.value}
|
||||
size={style.size}
|
||||
placeholder={placeholder.value}
|
||||
onKeydown={onKeydown}
|
||||
onChange={onChange}
|
||||
onInput={onInput}
|
||||
clearable
|
||||
/>
|
||||
|
||||
<el-button
|
||||
size={style.size}
|
||||
type="primary"
|
||||
loading={loading.value}
|
||||
onClick={search}>
|
||||
{crud.dict.label.search}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
@@ -0,0 +1,21 @@
|
||||
import { getCurrentInstance } from "vue";
|
||||
import { useConfig } from "../../../hooks";
|
||||
import { uniqueFns } from "../../../utils";
|
||||
|
||||
export function usePlugins() {
|
||||
const that: any = getCurrentInstance();
|
||||
const { style } = useConfig();
|
||||
|
||||
// 插件创建
|
||||
function create(plugins: ClSearch.Plugin[] = []) {
|
||||
uniqueFns([...(style.search?.plugins || []), ...plugins]).forEach((p) => {
|
||||
p({
|
||||
exposed: that.exposed
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
create
|
||||
};
|
||||
}
|
307
cool-admin-vue/packages/crud/src/components/search/index.tsx
Normal file
307
cool-admin-vue/packages/crud/src/components/search/index.tsx
Normal file
@@ -0,0 +1,307 @@
|
||||
import { useConfig, useCore, useForm, useProxy, useRefs } from "../../hooks";
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
inject,
|
||||
mergeProps,
|
||||
nextTick,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
PropType,
|
||||
reactive,
|
||||
ref
|
||||
} from "vue";
|
||||
import { useApi } from "../form/helper";
|
||||
import { Bottom, Refresh, Search, Top } from "@element-plus/icons-vue";
|
||||
import { mitt } from "../../utils/mitt";
|
||||
import { isArray, isEmpty } from "lodash-es";
|
||||
import { usePlugins } from "./helper/plugins";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-search",
|
||||
|
||||
props: {
|
||||
// 是否行内
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
// cl-form 表单配置
|
||||
props: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
|
||||
// 表单值
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
|
||||
// 列
|
||||
items: {
|
||||
type: Array as PropType<ClForm.Item[]>,
|
||||
default: () => []
|
||||
},
|
||||
|
||||
// 是否需要重置按钮
|
||||
resetBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// 是否需要折叠
|
||||
collapse: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// 初始化
|
||||
onLoad: Function,
|
||||
|
||||
// 搜索时钩子
|
||||
onSearch: Function
|
||||
},
|
||||
|
||||
emits: ["reset"],
|
||||
|
||||
setup(props, { slots, expose, emit }) {
|
||||
const { crud } = useCore();
|
||||
const { refs, setRefs } = useRefs();
|
||||
const { style } = useConfig();
|
||||
const plugin = usePlugins();
|
||||
|
||||
// 配置
|
||||
const config = reactive<ClSearch.Config>(
|
||||
mergeProps(props, inject("useSearch__options") || {})
|
||||
);
|
||||
|
||||
// cl-form
|
||||
const Form = useForm();
|
||||
|
||||
// 加载中
|
||||
const loading = ref(false);
|
||||
|
||||
// 展开
|
||||
const isExpand = ref(!config.collapse);
|
||||
|
||||
// 显示展开、收起按钮
|
||||
const showExpandBtn = ref(false);
|
||||
|
||||
// 搜索
|
||||
function search(params?: any) {
|
||||
const form = Form.value?.getForm();
|
||||
|
||||
async function next(data?: any) {
|
||||
loading.value = true;
|
||||
|
||||
const d = {
|
||||
page: 1,
|
||||
...form,
|
||||
...data,
|
||||
...params
|
||||
};
|
||||
|
||||
for (const i in d) {
|
||||
if (d[i] === "") {
|
||||
d[i] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await crud.refresh(d);
|
||||
|
||||
loading.value = false;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
if (config.onSearch) {
|
||||
config.onSearch(form, { next });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
function reset() {
|
||||
const d: any = {};
|
||||
|
||||
config.items?.map((e) => {
|
||||
if (typeof e.hook != "string" && e.hook?.reset) {
|
||||
const props = e.hook.reset(e.prop!);
|
||||
|
||||
if (isArray(props)) {
|
||||
props.forEach((prop) => {
|
||||
d[prop] = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
d[e.prop!] = undefined;
|
||||
});
|
||||
|
||||
// 重置表单
|
||||
Form.value?.reset();
|
||||
|
||||
// 列表刷新
|
||||
search(d);
|
||||
|
||||
// 重置事件
|
||||
emit("reset", d);
|
||||
}
|
||||
|
||||
// 收起、展开
|
||||
function expand() {
|
||||
isExpand.value = !isExpand.value;
|
||||
|
||||
nextTick(() => {
|
||||
crud?.["cl-table"].calcMaxHeight();
|
||||
});
|
||||
}
|
||||
|
||||
// 判断展开状态
|
||||
function onExpand() {
|
||||
if (config.collapse) {
|
||||
const el = refs.form?.querySelector(".cl-form__items");
|
||||
|
||||
if (el) {
|
||||
showExpandBtn.value = el.clientHeight > 84;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
onExpand();
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
search,
|
||||
reset,
|
||||
Form,
|
||||
config,
|
||||
...useApi({ Form })
|
||||
};
|
||||
|
||||
useProxy(ctx);
|
||||
expose(ctx);
|
||||
plugin.create(config.plugins);
|
||||
|
||||
onMounted(() => {
|
||||
Form.value?.open({
|
||||
op: {
|
||||
hidden: true
|
||||
},
|
||||
props: {
|
||||
labelPosition: "right",
|
||||
...config.props
|
||||
},
|
||||
items: config.items?.map((e) => {
|
||||
return {
|
||||
col: {
|
||||
sm: 12,
|
||||
md: 8,
|
||||
xs: 24,
|
||||
lg: 6
|
||||
},
|
||||
...e
|
||||
};
|
||||
}),
|
||||
form: config.data,
|
||||
on: {
|
||||
open(data) {
|
||||
config.onLoad?.(data);
|
||||
onExpand();
|
||||
},
|
||||
change(data, prop) {
|
||||
config.onChange?.(data, prop);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mitt.on("resize", onResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
mitt.off("resize", onResize);
|
||||
});
|
||||
|
||||
return () => {
|
||||
const btnEl = (
|
||||
<el-form-item label=" " class="cl-search__btns">
|
||||
{/* 重置按钮 */}
|
||||
{config.resetBtn && (
|
||||
<el-button size={style.size} icon={Refresh} onClick={reset}>
|
||||
{crud.dict.label.reset}
|
||||
</el-button>
|
||||
)}
|
||||
|
||||
{/* 搜索按钮 */}
|
||||
<el-button
|
||||
type="primary"
|
||||
loading={loading.value}
|
||||
size={style.size}
|
||||
icon={Search}
|
||||
onClick={() => {
|
||||
search();
|
||||
}}>
|
||||
{crud.dict.label.search}
|
||||
</el-button>
|
||||
|
||||
{/* 自定义按钮 */}
|
||||
{slots?.buttons?.(Form.value?.form)}
|
||||
</el-form-item>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
"cl-search",
|
||||
isExpand.value ? "is-expand" : "is-fold",
|
||||
{
|
||||
"is-inline": config.inline,
|
||||
"is-collapse": config.collapse
|
||||
}
|
||||
]}>
|
||||
<div class="cl-search__form" ref={setRefs("form")}>
|
||||
{h(
|
||||
<cl-form
|
||||
ref={Form}
|
||||
inner
|
||||
inline={config.inline}
|
||||
enable-plugin={false}
|
||||
name="search"
|
||||
/>,
|
||||
{},
|
||||
{
|
||||
append() {
|
||||
return config.collapse ? null : isEmpty(config.items) || btnEl;
|
||||
},
|
||||
...slots
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{config.collapse && (
|
||||
<div class="cl-search__more">
|
||||
{showExpandBtn.value && (
|
||||
<el-button onClick={expand}>
|
||||
<span>
|
||||
{isExpand.value
|
||||
? crud.dict.label.collapse
|
||||
: crud.dict.label.expand}
|
||||
</span>
|
||||
<el-icon>{isExpand.value ? <Top /> : <Bottom />}</el-icon>
|
||||
</el-button>
|
||||
)}
|
||||
|
||||
<cl-flex1 />
|
||||
|
||||
{btnEl}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
@@ -0,0 +1,35 @@
|
||||
import { nextTick, ref } from "vue";
|
||||
import { useCore } from "../../../hooks";
|
||||
|
||||
export function useData({ config, Table }: { config: ClTable.Config; Table: Vue.Ref<any> }) {
|
||||
const { mitt, crud } = useCore();
|
||||
|
||||
// 列表数据
|
||||
const data = ref<obj[]>([]);
|
||||
|
||||
// 设置数据
|
||||
function setData(list: obj[]) {
|
||||
data.value = list;
|
||||
}
|
||||
|
||||
// 监听刷新
|
||||
mitt.on("crud.refresh", ({ list }: ClCrud.Response["page"]) => {
|
||||
data.value = list;
|
||||
|
||||
// 显示选中行
|
||||
nextTick(() => {
|
||||
crud.selection.forEach((e) => {
|
||||
const d = list.find((a) => a[config.rowKey] == e[config.rowKey]);
|
||||
|
||||
if (d) {
|
||||
Table.value.toggleRowSelection(d, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
setData
|
||||
};
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
import { CloseBold, Search } from "@element-plus/icons-vue";
|
||||
import { h } from "vue";
|
||||
import { useCrud } from "../../../hooks";
|
||||
import { renderNode } from "../../../utils/vnode";
|
||||
|
||||
export function renderHeader(item: ClTable.Column, { scope, slots }: any) {
|
||||
const crud = useCrud();
|
||||
|
||||
const slot = slots[`header-${item.prop}`];
|
||||
|
||||
if (slot) {
|
||||
return slot({
|
||||
scope
|
||||
});
|
||||
}
|
||||
|
||||
if (!item.search || !item.search.component) {
|
||||
return item.label;
|
||||
}
|
||||
|
||||
// 显示输入框
|
||||
function show(e: MouseEvent) {
|
||||
item.search.isInput = true;
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// 隐藏输入框
|
||||
function hide() {
|
||||
if (item.search.value !== undefined) {
|
||||
item.search.value = undefined;
|
||||
refresh();
|
||||
}
|
||||
|
||||
item.search.isInput = false;
|
||||
}
|
||||
|
||||
// 刷新
|
||||
function refresh(params?: any) {
|
||||
const { value } = item.search;
|
||||
|
||||
crud.value?.refresh({
|
||||
page: 1,
|
||||
[item.prop]: value === "" ? undefined : value,
|
||||
...params
|
||||
});
|
||||
}
|
||||
|
||||
// 文字
|
||||
const text = (
|
||||
<div class="cl-table-header__search-label" onClick={show}>
|
||||
<el-icon size={14}>{item.search.icon?.() ?? <Search />}</el-icon>
|
||||
|
||||
{item.renderLabel ? item.renderLabel(scope) : item.label}
|
||||
</div>
|
||||
);
|
||||
|
||||
// 输入框
|
||||
const input = h(renderNode(item.search.component, { prop: item.prop }), {
|
||||
clearable: true,
|
||||
modelValue: item.search.value,
|
||||
onVnodeMounted(vn) {
|
||||
// 默认聚焦
|
||||
vn.component?.exposed?.focus?.();
|
||||
},
|
||||
onInput(val: any) {
|
||||
item.search.value = val;
|
||||
},
|
||||
onChange(val: any) {
|
||||
item.search.value = val;
|
||||
|
||||
// 更改时刷新列表
|
||||
if (item.search.refreshOnChange) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div class={["cl-table-header__search", { "is-input": item.search.isInput }]}>
|
||||
<div class="cl-table-header__search-inner">{item.search.isInput ? input : text}</div>
|
||||
|
||||
{item.search.isInput && (
|
||||
<div class="cl-table-header__search-close" onClick={hide}>
|
||||
<el-icon>
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
import { debounce, last } from "lodash-es";
|
||||
import { nextTick, onActivated, onMounted, ref } from "vue";
|
||||
import { addClass, removeClass } from "../../../utils";
|
||||
import { mitt } from "../../../utils/mitt";
|
||||
|
||||
// 表格高度
|
||||
export function useHeight({ config, Table }: { Table: Vue.Ref<any>; config: ClTable.Config }) {
|
||||
// 最大高度
|
||||
const maxHeight = ref(0);
|
||||
|
||||
// 计算表格最大高度
|
||||
const update = debounce(async () => {
|
||||
await nextTick();
|
||||
|
||||
let vm = Table.value;
|
||||
|
||||
if (vm) {
|
||||
while (!vm.$parent?.$el.className?.includes("cl-crud")) {
|
||||
vm = vm.$parent;
|
||||
}
|
||||
|
||||
if (vm) {
|
||||
const p = vm.$parent.$el;
|
||||
|
||||
await nextTick();
|
||||
|
||||
// 高度
|
||||
let h = 0;
|
||||
|
||||
// 表格下间距
|
||||
if (vm.$el.className.includes("cl-row")) {
|
||||
h += 10;
|
||||
}
|
||||
|
||||
// 上高度
|
||||
h += vm.$el.offsetTop;
|
||||
|
||||
// 获取下高度
|
||||
let n = vm.$el.nextSibling;
|
||||
|
||||
// 集合
|
||||
const arr = [vm.$el];
|
||||
|
||||
while (n) {
|
||||
if (n.offsetHeight > 0) {
|
||||
h += n.offsetHeight || 0;
|
||||
arr.push(n);
|
||||
|
||||
if (n.className.includes("cl-row--last")) {
|
||||
h += 10;
|
||||
}
|
||||
}
|
||||
|
||||
n = n.nextSibling;
|
||||
}
|
||||
|
||||
// 移除 cl-row--last
|
||||
arr.forEach((e) => {
|
||||
removeClass(e, "cl-row--last");
|
||||
});
|
||||
|
||||
// 最后一个可视元素
|
||||
const z = last(arr);
|
||||
|
||||
// 去掉 cl-row 下间距高度
|
||||
if (z?.className.includes("cl-row")) {
|
||||
addClass(z, "cl-row--last");
|
||||
h -= 10;
|
||||
}
|
||||
|
||||
// 上间距
|
||||
h += parseInt(window.getComputedStyle(p).paddingTop, 10);
|
||||
|
||||
// 设置最大高度
|
||||
if (config.autoHeight) {
|
||||
maxHeight.value = p.clientHeight - h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 窗口大小改变事件
|
||||
mitt.on("resize", () => {
|
||||
update();
|
||||
});
|
||||
|
||||
onMounted(function () {
|
||||
update();
|
||||
});
|
||||
|
||||
onActivated(function () {
|
||||
update();
|
||||
});
|
||||
|
||||
return {
|
||||
maxHeight,
|
||||
calcMaxHeight: update
|
||||
};
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
import { inject, reactive, ref } from "vue";
|
||||
import { useConfig } from "../../../hooks";
|
||||
import { getValue, mergeConfig } from "../../../utils";
|
||||
import type { TableInstance } from "element-plus";
|
||||
|
||||
export function useTable(props: any) {
|
||||
const { style } = useConfig();
|
||||
|
||||
const Table = ref<TableInstance>();
|
||||
|
||||
// 配置
|
||||
const config = reactive<ClTable.Config>(mergeConfig(props, inject("useTable__options") || {}));
|
||||
|
||||
// 列表项动态处理
|
||||
config.columns = (config.columns || []).map((e) => getValue(e));
|
||||
|
||||
// 自动高度
|
||||
config.autoHeight = config.autoHeight ?? style.table.autoHeight;
|
||||
|
||||
// 右键菜单
|
||||
config.contextMenu = config.contextMenu ?? style.table.contextMenu;
|
||||
|
||||
// 事件
|
||||
if (!config.on) {
|
||||
config.on = {};
|
||||
}
|
||||
|
||||
// 参数
|
||||
if (!config.props) {
|
||||
config.props = {};
|
||||
}
|
||||
|
||||
return { Table, config };
|
||||
}
|
||||
|
||||
export * from "./data";
|
||||
export * from "./height";
|
||||
export * from "./op";
|
||||
export * from "./render";
|
||||
export * from "./row";
|
||||
export * from "./selection";
|
||||
export * from "./sort";
|
||||
export * from "./header";
|
@@ -0,0 +1,69 @@
|
||||
import { nextTick, ref } from "vue";
|
||||
import { useCore } from "../../../hooks";
|
||||
import { isArray, isBoolean } from "lodash-es";
|
||||
|
||||
export function useOp({ config }: { config: ClTable.Config }) {
|
||||
const { mitt } = useCore();
|
||||
|
||||
// 是否可见,用于解决一些显示隐藏的副作用
|
||||
const visible = ref(true);
|
||||
|
||||
// 重新构建
|
||||
async function reBuild(cb?: fn) {
|
||||
visible.value = false;
|
||||
|
||||
await nextTick();
|
||||
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
|
||||
visible.value = true;
|
||||
|
||||
await nextTick();
|
||||
|
||||
mitt.emit("resize");
|
||||
}
|
||||
|
||||
// 显示列
|
||||
function showColumn(prop: string | string[], status?: boolean) {
|
||||
const keys = isArray(prop) ? prop : [prop];
|
||||
|
||||
// 多级表头
|
||||
function deep(list: ClTable.Column[]) {
|
||||
list.forEach((e) => {
|
||||
if (e.prop && keys.includes(e.prop)) {
|
||||
e.hidden = isBoolean(status) ? !status : false;
|
||||
}
|
||||
|
||||
if (e.children) {
|
||||
deep(e.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deep(config.columns);
|
||||
}
|
||||
|
||||
// 隐藏列
|
||||
function hideColumn(prop: string | string[]) {
|
||||
showColumn(prop, false);
|
||||
}
|
||||
|
||||
// 设置列
|
||||
function setColumns(list: ClTable.Column[]) {
|
||||
if (list) {
|
||||
reBuild(() => {
|
||||
config.columns.splice(0, config.columns.length, ...list);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
visible,
|
||||
reBuild,
|
||||
showColumn,
|
||||
hideColumn,
|
||||
setColumns
|
||||
};
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
import { getCurrentInstance } from "vue";
|
||||
import { useConfig } from "../../../hooks";
|
||||
import { uniqueFns } from "../../../utils";
|
||||
|
||||
export function usePlugins() {
|
||||
const that: any = getCurrentInstance();
|
||||
const { style } = useConfig();
|
||||
|
||||
// 插件创建
|
||||
function create(plugins: ClTable.Plugin[] = []) {
|
||||
// 执行
|
||||
uniqueFns([...(style.table.plugins || []), ...plugins]).forEach((p) => {
|
||||
p({
|
||||
exposed: that.exposed
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
create
|
||||
};
|
||||
}
|
@@ -0,0 +1,327 @@
|
||||
import { h, useSlots } from "vue";
|
||||
import { useBrowser, useConfig, useCore } from "../../../hooks";
|
||||
import { assign, cloneDeep, isArray, isEmpty, isObject, isString, orderBy } from "lodash-es";
|
||||
import { deepFind, getValue } from "../../../utils";
|
||||
import { renderNode } from "../../../utils/vnode";
|
||||
import { renderHeader } from "./header";
|
||||
|
||||
// 渲染
|
||||
export function useRender() {
|
||||
const browser = useBrowser();
|
||||
const slots = useSlots();
|
||||
const { crud } = useCore();
|
||||
const { style } = useConfig();
|
||||
|
||||
// 渲染列
|
||||
function renderColumn(columns: ClTable.Column[]) {
|
||||
const arr = columns.map((e) => {
|
||||
const d = getValue(e);
|
||||
|
||||
if (!d.orderNum) {
|
||||
d.orderNum = 0;
|
||||
}
|
||||
|
||||
return d;
|
||||
});
|
||||
|
||||
return orderBy(arr, "orderNum", "asc")
|
||||
.map((item, index) => {
|
||||
if (item.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ElTableColumn = (
|
||||
<el-table-column
|
||||
key={`cl-table-column__${index}`}
|
||||
align={style.table.column.align}
|
||||
header-align={style.table.column.headerAlign}
|
||||
minWidth={style.table.column.minWidth}
|
||||
/>
|
||||
);
|
||||
|
||||
// 操作按钮
|
||||
if (item.type === "op") {
|
||||
const props = assign(
|
||||
{
|
||||
label: crud.dict.label.op,
|
||||
width: style.table.column.opWidth,
|
||||
fixed: browser.isMini ? null : "right"
|
||||
},
|
||||
item
|
||||
);
|
||||
|
||||
return h(ElTableColumn, props, {
|
||||
default: (scope: any) => {
|
||||
return (
|
||||
<div class="cl-table__op">
|
||||
{renderOpButtons(item.buttons, { scope })}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 多选,序号
|
||||
else if (["selection", "index"].includes(item.type)) {
|
||||
return h(ElTableColumn, item);
|
||||
}
|
||||
// 默认
|
||||
else {
|
||||
function deep(item: ClTable.Column) {
|
||||
if (item.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const props: obj = cloneDeep(item);
|
||||
|
||||
// Cannot set property children of #<Element>
|
||||
delete props.children;
|
||||
|
||||
return h(ElTableColumn, props, {
|
||||
header(scope: any) {
|
||||
return renderHeader(item, { scope, slots });
|
||||
},
|
||||
default(scope: any) {
|
||||
if (item.children) {
|
||||
return item.children.map(deep);
|
||||
}
|
||||
|
||||
// 使用插槽
|
||||
const slot = slots[`column-${item.prop}`];
|
||||
|
||||
if (slot) {
|
||||
return slot({
|
||||
scope,
|
||||
item
|
||||
});
|
||||
} else {
|
||||
// 绑定值
|
||||
let value = scope.row[item.prop];
|
||||
|
||||
// 格式化
|
||||
if (item.formatter) {
|
||||
value = item.formatter(
|
||||
scope.row,
|
||||
scope.column,
|
||||
value,
|
||||
scope.$index
|
||||
);
|
||||
|
||||
if (isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义渲染
|
||||
if (item.render) {
|
||||
return item.render(
|
||||
scope.row,
|
||||
scope.column,
|
||||
value,
|
||||
scope.$index
|
||||
);
|
||||
}
|
||||
// 自定义渲染2
|
||||
else if (item.component) {
|
||||
return renderNode(item.component, {
|
||||
prop: item.prop,
|
||||
scope: scope.row,
|
||||
_data: {
|
||||
column: scope.column,
|
||||
index: scope.$index,
|
||||
row: scope.row
|
||||
}
|
||||
});
|
||||
}
|
||||
// 字典状态
|
||||
else if (item.dict) {
|
||||
return renderDict(value, item);
|
||||
}
|
||||
// 空数据
|
||||
else if (isEmpty(value)) {
|
||||
return scope.emptyText;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return deep(item);
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
// 渲染操作按钮
|
||||
function renderOpButtons(buttons: any, { scope }: any) {
|
||||
const list = getValue(buttons || ["edit", "delete"], { scope }) as ClTable.OpButton;
|
||||
|
||||
return list.map((vnode) => {
|
||||
if (vnode === "info") {
|
||||
return (
|
||||
<el-button
|
||||
plain
|
||||
size={style.size}
|
||||
v-show={crud.getPermission("info")}
|
||||
onClick={(e: MouseEvent) => {
|
||||
crud.rowInfo(scope.row);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{crud.dict.label?.info}
|
||||
</el-button>
|
||||
);
|
||||
} else if (vnode === "edit") {
|
||||
return (
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
size={style.size}
|
||||
v-show={crud.getPermission("update")}
|
||||
onClick={(e: MouseEvent) => {
|
||||
crud.rowEdit(scope.row);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{crud.dict.label?.update}
|
||||
</el-button>
|
||||
);
|
||||
} else if (vnode === "delete") {
|
||||
return (
|
||||
<el-button
|
||||
text
|
||||
type="danger"
|
||||
size={style.size}
|
||||
v-show={crud.getPermission("delete")}
|
||||
onClick={(e: MouseEvent) => {
|
||||
crud.rowDelete(scope.row);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{crud.dict.label?.delete}
|
||||
</el-button>
|
||||
);
|
||||
} else {
|
||||
if (typeof vnode === "object") {
|
||||
if (vnode.hidden) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return renderNode(vnode, {
|
||||
scope,
|
||||
slots,
|
||||
custom(vnode) {
|
||||
return (
|
||||
<el-button
|
||||
text
|
||||
type={vnode.type}
|
||||
{...vnode?.props}
|
||||
onClick={(e: MouseEvent) => {
|
||||
vnode.onClick({ scope });
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{vnode.label}
|
||||
</el-button>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染字典
|
||||
function renderDict(value: any, item: ClTable.Column) {
|
||||
// 选项列表
|
||||
const list = cloneDeep(item.dict || []) as DictOptions;
|
||||
|
||||
// 字符串分隔符
|
||||
const separator = item.dictSeparator === undefined ? "," : item.dictSeparator;
|
||||
|
||||
// 设置颜色
|
||||
if (item.dictColor) {
|
||||
list.forEach((e, i) => {
|
||||
if (!e.color) {
|
||||
e.color = style.colors[i];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定值
|
||||
let values: any[] = [];
|
||||
|
||||
// 格式化值
|
||||
if (isArray(value)) {
|
||||
values = value;
|
||||
} else if (isString(value)) {
|
||||
if (separator) {
|
||||
values = value.split(separator);
|
||||
} else {
|
||||
values = [value];
|
||||
}
|
||||
} else {
|
||||
values = [value];
|
||||
}
|
||||
|
||||
// 返回值
|
||||
const result = values
|
||||
.filter((e) => e !== undefined && e !== null && e !== "")
|
||||
.map((v) => {
|
||||
const d = deepFind(v, list, { allLevels: item.dictAllLevels }) || {
|
||||
label: v,
|
||||
value: v
|
||||
};
|
||||
|
||||
return {
|
||||
...d,
|
||||
children: []
|
||||
};
|
||||
});
|
||||
|
||||
// 格式化返回
|
||||
if (item.dictFormatter) {
|
||||
return item.dictFormatter(result);
|
||||
} else {
|
||||
// tag 返回
|
||||
return result.map((e) => {
|
||||
return h(
|
||||
<el-tag disable-transitions style="margin: 2px; border: 0" />,
|
||||
{
|
||||
type: e.type,
|
||||
closable: e.closable,
|
||||
hit: e.hit,
|
||||
color: e.color,
|
||||
size: e.size,
|
||||
effect: e.effect || "dark",
|
||||
round: e.round
|
||||
},
|
||||
{
|
||||
default: () => <span>{e.label}</span>
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 插槽 empty
|
||||
function renderEmpty(emptyText: string) {
|
||||
return (
|
||||
<div class="cl-table__empty">
|
||||
{slots.empty ? (
|
||||
slots.empty()
|
||||
) : (
|
||||
<el-empty image-size={100} description={emptyText}></el-empty>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 插槽 append
|
||||
function renderAppend() {
|
||||
return <div class="cl-table__append">{slots.append && slots.append()}</div>;
|
||||
}
|
||||
|
||||
return {
|
||||
renderColumn,
|
||||
renderEmpty,
|
||||
renderAppend
|
||||
};
|
||||
}
|
130
cool-admin-vue/packages/crud/src/components/table/helper/row.ts
Normal file
130
cool-admin-vue/packages/crud/src/components/table/helper/row.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { isEmpty, isFunction } from "lodash-es";
|
||||
import { useCore } from "../../../hooks";
|
||||
import { ContextMenu } from "../../context-menu";
|
||||
|
||||
// 单元行事件
|
||||
export function useRow({
|
||||
Table,
|
||||
config,
|
||||
Sort
|
||||
}: {
|
||||
Table: Vue.Ref<any>;
|
||||
config: ClTable.Config;
|
||||
Sort: {
|
||||
defaultSort: {
|
||||
prop?: string;
|
||||
order?: string;
|
||||
};
|
||||
changeSort(prop: string, order: string): void;
|
||||
};
|
||||
}) {
|
||||
const { crud } = useCore();
|
||||
|
||||
// 右键菜单
|
||||
function onRowContextMenu(row: obj, column: obj, event: PointerEvent) {
|
||||
// 菜单按钮
|
||||
const buttons = config.contextMenu;
|
||||
// 是否开启
|
||||
const enable = !isEmpty(buttons);
|
||||
|
||||
if (enable) {
|
||||
// 高亮
|
||||
Table.value.setCurrentRow(row);
|
||||
|
||||
// 解析按钮
|
||||
const list = buttons
|
||||
.map((e) => {
|
||||
switch (e) {
|
||||
case "refresh":
|
||||
return {
|
||||
label: crud.dict.label.refresh,
|
||||
callback(done: fn) {
|
||||
crud.refresh();
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "edit":
|
||||
case "update":
|
||||
return {
|
||||
label: crud.dict.label.update,
|
||||
hidden: !crud.getPermission("update"),
|
||||
callback(done: fn) {
|
||||
crud.rowEdit(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "delete":
|
||||
return {
|
||||
label: crud.dict.label.delete,
|
||||
hidden: !crud.getPermission("delete"),
|
||||
callback(done: fn) {
|
||||
crud.rowDelete(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "info":
|
||||
return {
|
||||
label: crud.dict.label.info,
|
||||
hidden: !crud.getPermission("info"),
|
||||
callback(done: fn) {
|
||||
crud.rowInfo(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "check":
|
||||
return {
|
||||
label: crud.selection.find((e) => e.id == row.id)
|
||||
? crud.dict.label.deselect
|
||||
: crud.dict.label.select,
|
||||
hidden: !config.columns.find((e) => e.type === "selection"),
|
||||
callback(done: fn) {
|
||||
Table.value.toggleRowSelection(row);
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "order-desc":
|
||||
return {
|
||||
label: `${column.label} - ${crud.dict.label.desc}`,
|
||||
hidden: !column.sortable,
|
||||
callback(done: fn) {
|
||||
Sort.changeSort(column.property, "desc");
|
||||
done();
|
||||
}
|
||||
};
|
||||
case "order-asc":
|
||||
return {
|
||||
label: `${column.label} - ${crud.dict.label.asc}`,
|
||||
hidden: !column.sortable,
|
||||
callback(done: fn) {
|
||||
Sort.changeSort(column.property, "asc");
|
||||
done();
|
||||
}
|
||||
};
|
||||
default:
|
||||
if (isFunction(e)) {
|
||||
return e(row, column, event);
|
||||
} else {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter((e) => Boolean(e) && !e.hidden);
|
||||
|
||||
// 打开菜单
|
||||
if (!isEmpty(list)) {
|
||||
ContextMenu.open(event, {
|
||||
list
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 回调
|
||||
if (config.onRowContextmenu) {
|
||||
config.onRowContextmenu(row, column, event);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
onRowContextMenu
|
||||
};
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { useCore } from "../../../hooks";
|
||||
|
||||
export function useSelection({ emit }: { emit: Vue.Emit }) {
|
||||
const { crud } = useCore();
|
||||
|
||||
// 选择项发生变化
|
||||
function onSelectionChange(selection: any[]) {
|
||||
crud.selection.splice(0, crud.selection.length, ...selection);
|
||||
emit("selection-change", crud.selection);
|
||||
}
|
||||
|
||||
return {
|
||||
selection: crud.selection,
|
||||
onSelectionChange
|
||||
};
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
import { useCore } from "../../../hooks";
|
||||
|
||||
// 排序
|
||||
export function useSort({
|
||||
config,
|
||||
Table,
|
||||
emit
|
||||
}: {
|
||||
config: ClTable.Config;
|
||||
Table: Vue.Ref<any>;
|
||||
emit: Vue.Emit;
|
||||
}) {
|
||||
const { crud } = useCore();
|
||||
|
||||
// 设置默认排序Ï
|
||||
const defaultSort = (function () {
|
||||
let { prop, order } = config.defaultSort || {};
|
||||
|
||||
const item = config.columns.find((e) =>
|
||||
["desc", "asc", "descending", "ascending"].find((a) => a == e.sortable)
|
||||
);
|
||||
|
||||
if (item) {
|
||||
prop = item.prop;
|
||||
order = ["descending", "desc"].find((a) => a == item.sortable)
|
||||
? "descending"
|
||||
: "ascending";
|
||||
}
|
||||
|
||||
if (order && prop) {
|
||||
crud.params.order = ["descending", "desc"].includes(order) ? "desc" : "asc";
|
||||
crud.params.prop = prop;
|
||||
|
||||
return {
|
||||
prop,
|
||||
order
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
})();
|
||||
|
||||
// 排序监听
|
||||
function onSortChange({ prop, order }: { prop: string | undefined; order: string }) {
|
||||
if (config.sortRefresh) {
|
||||
if (order === "descending") {
|
||||
order = "desc";
|
||||
}
|
||||
|
||||
if (order === "ascending") {
|
||||
order = "asc";
|
||||
}
|
||||
|
||||
if (!order) {
|
||||
prop = undefined;
|
||||
}
|
||||
|
||||
crud.refresh({
|
||||
prop,
|
||||
order,
|
||||
page: 1
|
||||
});
|
||||
}
|
||||
|
||||
emit("sort-change", { prop, order });
|
||||
}
|
||||
|
||||
// 改变排序
|
||||
function changeSort(prop: string, order: string) {
|
||||
if (order === "desc") {
|
||||
order = "descending";
|
||||
}
|
||||
|
||||
if (order === "asc") {
|
||||
order = "ascending";
|
||||
}
|
||||
|
||||
Table.value?.sort(prop, order);
|
||||
}
|
||||
|
||||
return {
|
||||
defaultSort,
|
||||
onSortChange,
|
||||
changeSort
|
||||
};
|
||||
}
|
165
cool-admin-vue/packages/crud/src/components/table/index.tsx
Normal file
165
cool-admin-vue/packages/crud/src/components/table/index.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import { defineComponent, h } from "vue";
|
||||
import {
|
||||
useData,
|
||||
useHeight,
|
||||
useOp,
|
||||
useRender,
|
||||
useRow,
|
||||
useSelection,
|
||||
useSort,
|
||||
useTable
|
||||
} from "./helper";
|
||||
import { useConfig, useCore, useElApi, useProxy } from "../../hooks";
|
||||
import { usePlugins } from "./helper/plugins";
|
||||
|
||||
export default defineComponent({
|
||||
name: "cl-table",
|
||||
|
||||
props: {
|
||||
// 列配置
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 是否自动计算高度
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: null
|
||||
},
|
||||
// 固定高度
|
||||
height: null,
|
||||
// 右键菜单
|
||||
contextMenu: {
|
||||
type: [Array, Boolean],
|
||||
default: null
|
||||
},
|
||||
// 默认排序
|
||||
defaultSort: Object,
|
||||
// 排序后是否刷新
|
||||
sortRefresh: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 空数据显示文案
|
||||
emptyText: String,
|
||||
// 当前行的 key
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: "id"
|
||||
}
|
||||
},
|
||||
|
||||
emits: ["selection-change", "sort-change"],
|
||||
|
||||
setup(props, { emit, expose }) {
|
||||
const { crud } = useCore();
|
||||
const { style } = useConfig();
|
||||
const { Table, config } = useTable(props);
|
||||
const plugin = usePlugins();
|
||||
|
||||
// 排序
|
||||
const Sort = useSort({ config, emit, Table });
|
||||
|
||||
// 行
|
||||
const Row = useRow({
|
||||
config,
|
||||
Table,
|
||||
Sort
|
||||
});
|
||||
|
||||
// 高度
|
||||
const Height = useHeight({ config, Table });
|
||||
|
||||
// 数据
|
||||
const Data = useData({ config, Table });
|
||||
|
||||
// 多选
|
||||
const Selection = useSelection({ emit });
|
||||
|
||||
// 操作
|
||||
const Op = useOp({ config });
|
||||
|
||||
// 方法
|
||||
const ElTableApi = useElApi(
|
||||
[
|
||||
"clearSelection",
|
||||
"getSelectionRows",
|
||||
"toggleRowSelection",
|
||||
"toggleAllSelection",
|
||||
"toggleRowExpansion",
|
||||
"setCurrentRow",
|
||||
"clearSort",
|
||||
"clearFilter",
|
||||
"doLayout",
|
||||
"sort",
|
||||
"scrollTo",
|
||||
"setScrollTop",
|
||||
"setScrollLeft",
|
||||
"updateKeyChildren"
|
||||
],
|
||||
Table
|
||||
);
|
||||
|
||||
const ctx = {
|
||||
Table,
|
||||
config,
|
||||
columns: config.columns,
|
||||
...Selection,
|
||||
...Data,
|
||||
...Sort,
|
||||
...Row,
|
||||
...Height,
|
||||
...Op,
|
||||
...ElTableApi
|
||||
};
|
||||
|
||||
useProxy(ctx);
|
||||
expose(ctx);
|
||||
plugin.create(config.plugins);
|
||||
|
||||
return () => {
|
||||
const { renderColumn, renderAppend, renderEmpty } = useRender();
|
||||
|
||||
return (
|
||||
ctx.visible.value &&
|
||||
h(
|
||||
<el-table class="cl-table" ref={Table} v-loading={crud.loading} />,
|
||||
{
|
||||
...config.on,
|
||||
...config.props,
|
||||
|
||||
// config
|
||||
maxHeight: config.autoHeight ? ctx.maxHeight.value : null,
|
||||
height: config.autoHeight ? config.height : null,
|
||||
rowKey: config.rowKey,
|
||||
|
||||
// ctx
|
||||
defaultSort: ctx.defaultSort,
|
||||
data: ctx.data.value,
|
||||
onRowContextmenu: ctx.onRowContextMenu,
|
||||
onSelectionChange: ctx.onSelectionChange,
|
||||
onSortChange: ctx.onSortChange,
|
||||
|
||||
// style
|
||||
size: style.size,
|
||||
border: style.table.border,
|
||||
highlightCurrentRow: style.table.highlightCurrentRow,
|
||||
resizable: style.table.resizable,
|
||||
stripe: style.table.stripe
|
||||
},
|
||||
{
|
||||
default() {
|
||||
return renderColumn(ctx.columns);
|
||||
},
|
||||
empty() {
|
||||
return renderEmpty(config.emptyText || crud.dict.label.empty);
|
||||
},
|
||||
append() {
|
||||
return renderAppend();
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
306
cool-admin-vue/packages/crud/src/components/upsert/index.tsx
Normal file
306
cool-admin-vue/packages/crud/src/components/upsert/index.tsx
Normal file
@@ -0,0 +1,306 @@
|
||||
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",
|
||||
|
||||
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
|
||||
},
|
||||
|
||||
emits: ["opened", "closed"],
|
||||
|
||||
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() {
|
||||
if (config.onOpen) {
|
||||
config.onOpen();
|
||||
}
|
||||
|
||||
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} />, {}, slots)}</div>;
|
||||
};
|
||||
}
|
||||
});
|
27
cool-admin-vue/packages/crud/src/emitter.ts
Normal file
27
cool-admin-vue/packages/crud/src/emitter.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export const crudList: ClCrud.Ref[] = [];
|
||||
|
||||
export const emitter: Emitter = {
|
||||
list: [],
|
||||
init(events) {
|
||||
for (const i in events) {
|
||||
this.on(i, events[i]);
|
||||
}
|
||||
},
|
||||
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 });
|
||||
}
|
||||
};
|
30
cool-admin-vue/packages/crud/src/entry.ts
Normal file
30
cool-admin-vue/packages/crud/src/entry.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { type App } from "vue";
|
||||
import { useComponent } from "./components";
|
||||
import { useProvide } from "./provide";
|
||||
import global from "./utils/global";
|
||||
import "./static/index.scss";
|
||||
|
||||
const Crud = {
|
||||
install(app: App, options?: any) {
|
||||
global.set("__CrudApp__", app);
|
||||
|
||||
// 穿透值
|
||||
useProvide(app, options);
|
||||
|
||||
// 设置组件
|
||||
useComponent(app);
|
||||
|
||||
return {
|
||||
name: "cl-crud"
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export { Crud };
|
||||
|
||||
export * from "./emitter";
|
||||
export * from "./hooks";
|
||||
export * from "./locale";
|
||||
export { registerFormHook } from "./utils/form-hook";
|
||||
export { renderNode } from "./utils/vnode";
|
||||
export { ContextMenu } from "./components/context-menu";
|
191
cool-admin-vue/packages/crud/src/hooks/crud.ts
Normal file
191
cool-admin-vue/packages/crud/src/hooks/crud.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { assign } from "lodash-es";
|
||||
import { TestService } from "../test/service";
|
||||
import { getCurrentInstance, inject, nextTick, provide, ref, type Ref, watch } from "vue";
|
||||
|
||||
// 获取上级
|
||||
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, isChild }: { r: any; options: any; clear?: string; isChild?: boolean }
|
||||
) {
|
||||
if (!r.__ev) r.__ev = {};
|
||||
|
||||
const d: { [key: string]: (args: any[]) => void } = {};
|
||||
const ev = r.__ev as { [key: string]: { fn: any; isChild?: boolean }[] };
|
||||
|
||||
names.forEach((k) => {
|
||||
if (!ev[k]) ev[k] = [];
|
||||
|
||||
if (options[k]) {
|
||||
ev[k].push({
|
||||
fn: options[k],
|
||||
isChild
|
||||
});
|
||||
}
|
||||
|
||||
d[k] = (...args: any[]) => {
|
||||
ev[k].forEach((e) => {
|
||||
if (e.fn) {
|
||||
e.fn(...args);
|
||||
}
|
||||
});
|
||||
|
||||
if (clear == k) {
|
||||
for (const i in ev) {
|
||||
ev[i] = ev[i].filter((e) => !e.isChild);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
// crud
|
||||
export function useCrud(options?: ClCrud.Options, cb?: (app: ClCrud.Ref) => void) {
|
||||
const Crud = ref<ClCrud.Ref>();
|
||||
useParent("cl-crud", Crud);
|
||||
|
||||
if (options) {
|
||||
// 测试模式
|
||||
if (options.service == "test") {
|
||||
options.service = new TestService();
|
||||
}
|
||||
|
||||
provide("useCrud__options", options);
|
||||
}
|
||||
|
||||
watch(Crud, (val) => {
|
||||
if (val) {
|
||||
if (cb) {
|
||||
cb(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Crud;
|
||||
}
|
||||
|
||||
// 新增、编辑
|
||||
export function useUpsert<T = any>(options?: ClUpsert.Options<T>) {
|
||||
const Upsert = ref<ClUpsert.Ref>();
|
||||
useParent("cl-upsert", Upsert);
|
||||
const isChild = !!Upsert.value;
|
||||
|
||||
if (options) {
|
||||
provide("useUpsert__options", options);
|
||||
}
|
||||
|
||||
watch(
|
||||
Upsert,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (options) {
|
||||
const event = useEvent(["onOpen", "onOpened", "onClosed"], {
|
||||
r: val,
|
||||
options,
|
||||
clear: "onClosed",
|
||||
isChild
|
||||
});
|
||||
|
||||
assign(val.config, event);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
return Upsert;
|
||||
}
|
||||
|
||||
// 表格
|
||||
export function useTable<T = any>(options?: ClTable.Options<T>, cb?: (table: ClTable.Ref) => void) {
|
||||
const Table = ref<ClTable.Ref<T>>();
|
||||
useParent("cl-table", Table);
|
||||
|
||||
if (options) {
|
||||
provide("useTable__options", options);
|
||||
}
|
||||
|
||||
watch(Table, (val) => {
|
||||
if (val) {
|
||||
if (cb) {
|
||||
cb(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Table;
|
||||
}
|
||||
|
||||
// 表单
|
||||
export function useForm<T = any>(cb?: (app: ClForm.Ref<T>) => void) {
|
||||
const Form = ref<ClForm.Ref<T>>();
|
||||
useParent("cl-form", Form);
|
||||
|
||||
nextTick(() => {
|
||||
if (cb && Form.value) {
|
||||
cb(Form.value);
|
||||
}
|
||||
});
|
||||
|
||||
return Form;
|
||||
}
|
||||
|
||||
// 高级搜索
|
||||
export function useAdvSearch<T = any>(options?: ClAdvSearch.Options<T>) {
|
||||
const AdvSearch = ref<ClAdvSearch.Ref<T>>();
|
||||
useParent("cl-adv-search", AdvSearch);
|
||||
|
||||
if (options) {
|
||||
provide("useAdvSearch__options", options);
|
||||
}
|
||||
|
||||
return AdvSearch;
|
||||
}
|
||||
|
||||
// 搜索
|
||||
export function useSearch<T = any>(options?: ClSearch.Options<T>) {
|
||||
const Search = ref<ClSearch.Ref<T>>();
|
||||
useParent("cl-search", Search);
|
||||
|
||||
provide("useSearch__options", options);
|
||||
|
||||
return Search;
|
||||
}
|
||||
|
||||
// 对话框
|
||||
export function useDialog(options?: { onFullscreen(visible: boolean): void }) {
|
||||
const Dialog = inject("dialog") as ClDialog.Provide;
|
||||
|
||||
watch(
|
||||
() => Dialog?.fullscreen.value,
|
||||
(val) => {
|
||||
options?.onFullscreen(val);
|
||||
}
|
||||
);
|
||||
|
||||
return Dialog;
|
||||
}
|
74
cool-admin-vue/packages/crud/src/hooks/index.ts
Normal file
74
cool-admin-vue/packages/crud/src/hooks/index.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Mitt } from "../utils/mitt";
|
||||
import { isFunction } from "lodash-es";
|
||||
import { getCurrentInstance, inject, reactive } from "vue";
|
||||
|
||||
export function useCore() {
|
||||
const crud = inject("crud") as ClCrud.Ref;
|
||||
const mitt = inject("mitt") as Mitt;
|
||||
|
||||
return {
|
||||
crud,
|
||||
mitt
|
||||
};
|
||||
}
|
||||
|
||||
export function useConfig() {
|
||||
return inject("__config__") as Config;
|
||||
}
|
||||
|
||||
export function useBrowser() {
|
||||
return inject("__browser__") as Browser;
|
||||
}
|
||||
|
||||
export function useRefs() {
|
||||
const refs = reactive<{ [key: string]: obj }>({});
|
||||
|
||||
function setRefs(name: string) {
|
||||
return (el: any) => {
|
||||
refs[name] = el;
|
||||
};
|
||||
}
|
||||
|
||||
return { refs, setRefs };
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function useElApi(keys: string[], el: any) {
|
||||
const apis: obj = {};
|
||||
|
||||
keys.forEach((e) => {
|
||||
apis[e] = (...args: any[]) => {
|
||||
return el.value[e](...args);
|
||||
};
|
||||
});
|
||||
|
||||
return apis;
|
||||
}
|
||||
|
||||
export * from "./crud";
|
1
cool-admin-vue/packages/crud/src/index.ts
Normal file
1
cool-admin-vue/packages/crud/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./entry";
|
33
cool-admin-vue/packages/crud/src/locale/en.ts
Normal file
33
cool-admin-vue/packages/crud/src/locale/en.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export default {
|
||||
op: "Operation",
|
||||
add: "Add",
|
||||
delete: "Delete",
|
||||
multiDelete: "Delete",
|
||||
update: "Edit",
|
||||
refresh: "Refresh",
|
||||
info: "Details",
|
||||
search: "Search",
|
||||
reset: "Reset",
|
||||
clear: "Clear",
|
||||
save: "Save",
|
||||
close: "Cancel",
|
||||
confirm: "Confirm",
|
||||
advSearch: "Advanced Search",
|
||||
searchKey: "Search Keyword",
|
||||
placeholder: "Please enter",
|
||||
tips: "Tips",
|
||||
saveSuccess: "Save successful",
|
||||
deleteSuccess: "Delete successful",
|
||||
deleteConfirm:
|
||||
"This operation will permanently delete the selected data. Do you want to continue?",
|
||||
empty: "No data available",
|
||||
desc: "Descending",
|
||||
asc: "Ascending",
|
||||
select: "Select",
|
||||
deselect: "Deselect",
|
||||
seeMore: "See more",
|
||||
hideContent: "Hide content",
|
||||
nonEmpty: "{label} cannot be empty",
|
||||
collapse: "Collapse",
|
||||
expand: "Expand"
|
||||
};
|
11
cool-admin-vue/packages/crud/src/locale/index.ts
Normal file
11
cool-admin-vue/packages/crud/src/locale/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import en from "./en";
|
||||
import ja from "./ja";
|
||||
import zhCn from "./zh-cn";
|
||||
import zhTw from "./zh-tw";
|
||||
|
||||
export const locale = {
|
||||
en,
|
||||
ja,
|
||||
"zh-cn": zhCn,
|
||||
"zh-tw": zhTw
|
||||
};
|
32
cool-admin-vue/packages/crud/src/locale/ja.ts
Normal file
32
cool-admin-vue/packages/crud/src/locale/ja.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
op: "操作",
|
||||
add: "追加",
|
||||
delete: "削除",
|
||||
multiDelete: "削除",
|
||||
update: "編集",
|
||||
refresh: "リフレッシュ",
|
||||
info: "詳細",
|
||||
search: "検索",
|
||||
reset: "リセット",
|
||||
clear: "クリア",
|
||||
save: "保存",
|
||||
close: "キャンセル",
|
||||
confirm: "確認",
|
||||
advSearch: "高度な検索",
|
||||
searchKey: "検索キーワード",
|
||||
placeholder: "入力してください",
|
||||
tips: "ヒント",
|
||||
saveSuccess: "保存が成功しました",
|
||||
deleteSuccess: "削除が成功しました",
|
||||
deleteConfirm: "この操作は選択したデータを永久に削除します。続行しますか?",
|
||||
empty: "データがありません",
|
||||
desc: "降順",
|
||||
asc: "昇順",
|
||||
select: "選択",
|
||||
deselect: "選択解除",
|
||||
seeMore: "詳細を表示",
|
||||
hideContent: "コンテンツを非表示",
|
||||
nonEmpty: "{label}は空にできません",
|
||||
collapse: "折り畳む",
|
||||
expand: "展開"
|
||||
};
|
32
cool-admin-vue/packages/crud/src/locale/zh-cn.ts
Normal file
32
cool-admin-vue/packages/crud/src/locale/zh-cn.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
op: "操作",
|
||||
add: "新增",
|
||||
delete: "删除",
|
||||
multiDelete: "删除",
|
||||
update: "编辑",
|
||||
refresh: "刷新",
|
||||
info: "详情",
|
||||
search: "搜索",
|
||||
reset: "重置",
|
||||
clear: "清空",
|
||||
save: "保存",
|
||||
close: "取消",
|
||||
confirm: "确定",
|
||||
advSearch: "高级搜索",
|
||||
searchKey: "搜索关键字",
|
||||
placeholder: "请输入",
|
||||
tips: "提示",
|
||||
saveSuccess: "保存成功",
|
||||
deleteSuccess: "删除成功",
|
||||
deleteConfirm: "此操作将永久删除选中数据,是否继续?",
|
||||
empty: "暂无数据",
|
||||
desc: "降序",
|
||||
asc: "升序",
|
||||
select: "选择",
|
||||
deselect: "取消选择",
|
||||
seeMore: "查看更多",
|
||||
hideContent: "隐藏内容",
|
||||
nonEmpty: "{label}不能为空",
|
||||
collapse: "收起",
|
||||
expand: "展开更多"
|
||||
};
|
32
cool-admin-vue/packages/crud/src/locale/zh-tw.ts
Normal file
32
cool-admin-vue/packages/crud/src/locale/zh-tw.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
op: "操作",
|
||||
add: "新增",
|
||||
delete: "刪除",
|
||||
multiDelete: "刪除",
|
||||
update: "編輯",
|
||||
refresh: "刷新",
|
||||
info: "詳情",
|
||||
search: "搜尋",
|
||||
reset: "重置",
|
||||
clear: "清空",
|
||||
save: "保存",
|
||||
close: "取消",
|
||||
confirm: "確定",
|
||||
advSearch: "高級搜索",
|
||||
searchKey: "搜索關鍵字",
|
||||
placeholder: "請輸入",
|
||||
tips: "提示",
|
||||
saveSuccess: "保存成功",
|
||||
deleteSuccess: "刪除成功",
|
||||
deleteConfirm: "此操作將永久刪除選中數據,是否繼續?",
|
||||
empty: "暫無數據",
|
||||
desc: "降序",
|
||||
asc: "升序",
|
||||
select: "選擇",
|
||||
deselect: "取消選擇",
|
||||
seeMore: "查看更多",
|
||||
hideContent: "隱藏內容",
|
||||
nonEmpty: "{label}不能為空",
|
||||
collapse: "收起",
|
||||
expand: "展開"
|
||||
};
|
27
cool-admin-vue/packages/crud/src/main.ts
Normal file
27
cool-admin-vue/packages/crud/src/main.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import { Crud, locale } from "./entry";
|
||||
// import Crud, { locale } from "../dist/index.umd";
|
||||
// import "../dist/index.css";
|
||||
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["zh-cn"]
|
||||
},
|
||||
style: {
|
||||
// size: "default"
|
||||
},
|
||||
render: {
|
||||
autoHeight: true
|
||||
}
|
||||
})
|
||||
.mount("#app");
|
129
cool-admin-vue/packages/crud/src/provide.ts
Normal file
129
cool-admin-vue/packages/crud/src/provide.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { type App, reactive } from "vue";
|
||||
import { mitt } from "./utils/mitt";
|
||||
import { emitter } from "./emitter";
|
||||
import { locale } from "./locale";
|
||||
import { merge } from "./utils";
|
||||
|
||||
// 设置配置
|
||||
function setConfig(app: App, options: Options = {}) {
|
||||
const config = merge(
|
||||
{
|
||||
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["zh-cn"]
|
||||
},
|
||||
style: {
|
||||
colors: [
|
||||
"#d42ca8",
|
||||
"#1c109d",
|
||||
"#6d17c3",
|
||||
"#6dc9f1",
|
||||
"#04c273",
|
||||
"#06b31c",
|
||||
"#f9f494",
|
||||
"#aa7a24",
|
||||
"#d57121",
|
||||
"#e93f4d"
|
||||
],
|
||||
form: {
|
||||
labelPostion: "right",
|
||||
labelWidth: "100px",
|
||||
span: 24
|
||||
},
|
||||
table: {
|
||||
border: true,
|
||||
highlightCurrentRow: true,
|
||||
autoHeight: true,
|
||||
contextMenu: ["refresh", "check", "edit", "delete", "order-asc", "order-desc"],
|
||||
column: {
|
||||
align: "center",
|
||||
opWidth: 180
|
||||
}
|
||||
}
|
||||
},
|
||||
events: {}
|
||||
} as Options,
|
||||
options
|
||||
);
|
||||
|
||||
// 初始化事件
|
||||
if (config.events) {
|
||||
emitter.init(config.events);
|
||||
}
|
||||
|
||||
app.provide("__config__", config);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// 设置浏览器
|
||||
function setBrowser(app: App) {
|
||||
// 浏览器信息
|
||||
const browser = reactive({
|
||||
isMini: false,
|
||||
screen: "full"
|
||||
});
|
||||
|
||||
// 更新信息
|
||||
function update() {
|
||||
const w = document.body.clientWidth;
|
||||
|
||||
if (w < 768) {
|
||||
browser.screen = "xs";
|
||||
} else if (w < 992) {
|
||||
browser.screen = "sm";
|
||||
} else if (w < 1200) {
|
||||
browser.screen = "md";
|
||||
} else if (w < 1920) {
|
||||
browser.screen = "xl";
|
||||
} else {
|
||||
browser.screen = "full";
|
||||
}
|
||||
|
||||
browser.isMini = browser.screen === "xs";
|
||||
}
|
||||
|
||||
// 监听浏览器窗口变化
|
||||
window.addEventListener("resize", () => {
|
||||
update();
|
||||
|
||||
// 事件
|
||||
mitt.emit("resize");
|
||||
});
|
||||
|
||||
update();
|
||||
app.provide("__browser__", browser);
|
||||
}
|
||||
|
||||
export function useProvide(app: App, options: Options = {}) {
|
||||
setBrowser(app);
|
||||
setConfig(app, options);
|
||||
}
|
862
cool-admin-vue/packages/crud/src/static/index.scss
Normal file
862
cool-admin-vue/packages/crud/src/static/index.scss
Normal file
@@ -0,0 +1,862 @@
|
||||
.cl-crud {
|
||||
height: 100%;
|
||||
overflow: hidden auto;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
|
||||
&.is-border {
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: var(--el-border-radius-base);
|
||||
}
|
||||
|
||||
& > .cl-row {
|
||||
width: 100%;
|
||||
|
||||
&:not(.cl-row--last) > * {
|
||||
margin: 0 10px 10px 0;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cl-flex1 {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-flex1 {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cl-search-key {
|
||||
display: inline-flex;
|
||||
|
||||
&__select {
|
||||
margin-right: 10px;
|
||||
|
||||
&.el-select {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
&__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: var(--el-fill-color-lighter) !important;
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
.cell {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
&.is-sortable {
|
||||
.cl-table-header__search {
|
||||
width: auto;
|
||||
|
||||
&.is-input {
|
||||
width: calc(100% - 24px) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__empty-block {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-loading-mask {
|
||||
.el-loading-spinner {
|
||||
.el-icon-loading {
|
||||
font-size: 25px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.el-loading-text {
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__op {
|
||||
margin-bottom: -5px;
|
||||
|
||||
.el-button {
|
||||
margin-bottom: 5px;
|
||||
outline-offset: -2px !important;
|
||||
|
||||
&.is-text {
|
||||
border: 1px solid var(--el-button-border-color) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-button-bg-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-table-header__search {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
&-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&-inner {
|
||||
flex: 1;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
|
||||
.el-date-editor {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-close {
|
||||
font-size: 14px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 6px;
|
||||
background-color: var(--el-fill-color-blank);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-border-color-hover);
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-left {
|
||||
.cl-table-header__search-label {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.is-right {
|
||||
.cl-table-header__search-label {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-pagination {
|
||||
&.el-pagination {
|
||||
--el-pagination-border-radius: var(--el-border-radius-base);
|
||||
}
|
||||
|
||||
.btn-prev,
|
||||
.btn-next,
|
||||
.el-pager li {
|
||||
border-radius: var(--el-border-radius-base);
|
||||
}
|
||||
}
|
||||
|
||||
.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-search {
|
||||
&__btns {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&__more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-form:not(.el-form--label-top) {
|
||||
.el-form-item__label {
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cl-search__btns {
|
||||
.el-form-item__label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-search__btns {
|
||||
.el-button + .el-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-inline {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
&.is-collapse {
|
||||
background-color: var(--el-fill-color-lighter);
|
||||
padding: 10px;
|
||||
margin-bottom: 10px !important;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--el-border-color-extra-light);
|
||||
|
||||
&.is-fold {
|
||||
.cl-search__form {
|
||||
max-height: 42px;
|
||||
overflow: hidden;
|
||||
|
||||
&:has(.el-form--label-top) {
|
||||
max-height: 68px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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 var(--el-border-color-extra-light);
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.cl-form {
|
||||
[class*="el-col-"].is-guttered {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
&-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;
|
||||
}
|
||||
|
||||
.el-form {
|
||||
.el-form-item {
|
||||
.el-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.el-input-number {
|
||||
&__decrease,
|
||||
&__increase {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
.el-tooltip {
|
||||
i {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
min-width: 0px;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.el-form--label-top) {
|
||||
.el-form-item {
|
||||
&.no-label {
|
||||
& > .el-form-item__label {
|
||||
padding: 0;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.el-form--label-top {
|
||||
.el-form-item {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
margin: 0 0 4px 0;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
.el-form-item__error {
|
||||
padding-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&.el-form--inline {
|
||||
.cl-form__items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin: 0 10px 10px 0;
|
||||
|
||||
.el-date-editor {
|
||||
box-sizing: border-box;
|
||||
|
||||
.el-range-input {
|
||||
&:nth-child(2) {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-select {
|
||||
width: 173px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-form-tabs {
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
overflow: hidden;
|
||||
width: calc(100% - 10px);
|
||||
margin: 0 5px 20px 5px;
|
||||
|
||||
&__wrap {
|
||||
height: 35px;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
position: relative;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
display: inline-flex;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
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 var(--el-border-color);
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
|
||||
li {
|
||||
border-left: 1px solid var(--el-border-color);
|
||||
|
||||
&:first-child {
|
||||
border-left-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-form-card {
|
||||
margin-top: 0;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
height: 32px;
|
||||
line-height: normal;
|
||||
font-weight: bold;
|
||||
padding: 0 10px;
|
||||
background-color: var(--el-fill-color-lighter);
|
||||
border-radius: var(--el-border-radius-base);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
}
|
||||
|
||||
&__container {
|
||||
transition: all 0.3s;
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
|
||||
> .cl-form-item__children {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.el-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-expand {
|
||||
> .cl-form-card__container {
|
||||
grid-template-rows: 1fr;
|
||||
margin-bottom: -18px;
|
||||
}
|
||||
}
|
||||
|
||||
.cl-form-card {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.cl-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.el-dialog {
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
&__header {
|
||||
padding: 0;
|
||||
margin-right: 0;
|
||||
|
||||
&-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: 0;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid var(--el-border-color-extra-light);
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&__default {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
border-top: 1px solid var(--el-border-color-extra-light);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
&__controls {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
z-index: 9;
|
||||
width: 100%;
|
||||
|
||||
&-icon,
|
||||
.minimize,
|
||||
.maximize,
|
||||
.close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border-radius: 6px;
|
||||
margin-left: 5px;
|
||||
|
||||
i {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-fill-color-darker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden-header {
|
||||
.el-dialog__header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-fullscreen {
|
||||
height: 100vh !important;
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.cl-dialog__container {
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-transparent {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cl-context-menu {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
|
||||
&__box {
|
||||
width: 160px;
|
||||
background-color: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-extra-light);
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||
0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
padding: 5px;
|
||||
|
||||
&.is-append {
|
||||
right: calc(-100% - 5px);
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
padding: 5px 15px;
|
||||
color: var(--el-text-color-primary);
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-fill-color-lighter);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.el-table {
|
||||
&__body {
|
||||
&-wrapper {
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-search-key {
|
||||
width: 100%;
|
||||
margin-right: 0 !important;
|
||||
|
||||
&__wrap {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cl-error-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
color: var(--el-color-danger);
|
||||
border: 1px solid var(--el-color-danger);
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
}
|
231
cool-admin-vue/packages/crud/src/test/service.ts
Normal file
231
cool-admin-vue/packages/crud/src/test/service.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { assign, orderBy } from "lodash-es";
|
||||
import { uuid } from "../utils";
|
||||
|
||||
const userList = [
|
||||
{
|
||||
id: 1,
|
||||
name: "楚行云",
|
||||
createTime: "1996-09-14",
|
||||
wages: 73026,
|
||||
status: 1,
|
||||
account: "chuxingyun",
|
||||
occupation: 4,
|
||||
phone: 13797353874
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "秦尘",
|
||||
createTime: "1977-11-09",
|
||||
wages: 74520,
|
||||
status: 0,
|
||||
account: "qincheng",
|
||||
occupation: 3,
|
||||
phone: 18593911044
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "叶凡",
|
||||
createTime: "1982-11-28",
|
||||
wages: 81420,
|
||||
status: 0,
|
||||
account: "yefan",
|
||||
occupation: 1,
|
||||
phone: 16234136338
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "白小纯",
|
||||
createTime: "2012-12-17",
|
||||
wages: 65197,
|
||||
status: 1,
|
||||
account: "baixiaochun",
|
||||
occupation: 2,
|
||||
phone: 16325661110
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "韩立",
|
||||
createTime: "1982-07-10",
|
||||
wages: 99107,
|
||||
status: 1,
|
||||
account: "hanli",
|
||||
occupation: 2,
|
||||
phone: 18486594866
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "唐三",
|
||||
createTime: "2019-07-31",
|
||||
wages: 80658,
|
||||
status: 1,
|
||||
account: "tangsan",
|
||||
occupation: 5,
|
||||
phone: 15565014642
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "王林",
|
||||
createTime: "2009-07-26",
|
||||
wages: 57408,
|
||||
status: 1,
|
||||
account: "wanglin",
|
||||
occupation: 1,
|
||||
phone: 13852767084
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "李强",
|
||||
createTime: "2016-04-26",
|
||||
wages: 71782,
|
||||
status: 1,
|
||||
account: "liqiang",
|
||||
occupation: 3,
|
||||
phone: 18365332834
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "秦羽",
|
||||
createTime: "1984-01-18",
|
||||
wages: 87860,
|
||||
status: 1,
|
||||
account: "qinyu",
|
||||
occupation: 0,
|
||||
phone: 18149247129
|
||||
}
|
||||
];
|
||||
|
||||
class TestService {
|
||||
// 分页列表
|
||||
async page(params: any) {
|
||||
const { keyWord, page, size, sort, order } = params || {};
|
||||
|
||||
// 关键字查询
|
||||
const keyWordLikeFields = ["phone", "name"];
|
||||
|
||||
// 等值查询
|
||||
const fieldEq = ["createTime", "occupation", "status"];
|
||||
|
||||
// 模糊查询
|
||||
const likeFields = ["phone", "name"];
|
||||
|
||||
// 过滤后的列表
|
||||
const list = orderBy(userList, order, sort).filter((e: any) => {
|
||||
let f = true;
|
||||
|
||||
if (keyWord !== undefined) {
|
||||
f = !!keyWordLikeFields.find((k) => String(e[k]).includes(String(params.keyWord)));
|
||||
}
|
||||
|
||||
fieldEq.forEach((k) => {
|
||||
if (f) {
|
||||
if (params[k] !== undefined) {
|
||||
f = e[k] == params[k];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
likeFields.forEach((k) => {
|
||||
if (f) {
|
||||
if (params[k] !== undefined) {
|
||||
f = String(e[k]).includes(String(params[k]));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return f;
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// 模拟延迟
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
list: list.slice((page - 1) * size, page * size),
|
||||
pagination: {
|
||||
total: list.length,
|
||||
page,
|
||||
size
|
||||
},
|
||||
subData: {
|
||||
wages: list.reduce((a, b) => {
|
||||
return a + b.wages;
|
||||
}, 0)
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
// 更新
|
||||
async update(params: { id: any; [key: string]: any }) {
|
||||
const item = userList.find((e) => e.id == params.id);
|
||||
|
||||
if (item) {
|
||||
assign(item, params);
|
||||
}
|
||||
}
|
||||
|
||||
// 新增
|
||||
async add(params: any) {
|
||||
const id = uuid();
|
||||
|
||||
userList.push({
|
||||
id,
|
||||
...params
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
// 详情
|
||||
async info(params: { id: any }) {
|
||||
const { id } = params || {};
|
||||
return userList.find((e) => e.id == id);
|
||||
}
|
||||
|
||||
// 删除
|
||||
async delete(params: { ids: any[] }) {
|
||||
const { ids = [] } = params || {};
|
||||
|
||||
ids.forEach((id) => {
|
||||
const index = userList.findIndex((e) => e.id == id);
|
||||
userList.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
// 全部列表
|
||||
async list() {
|
||||
return userList;
|
||||
}
|
||||
|
||||
search = {
|
||||
fieldEq: [
|
||||
{
|
||||
propertyName: "occupation",
|
||||
comment: "工作",
|
||||
source: "a.occupation"
|
||||
}
|
||||
],
|
||||
fieldLike: [
|
||||
{
|
||||
propertyName: "status",
|
||||
comment: "状态",
|
||||
dict: ["关闭", "开启"],
|
||||
source: "a.status"
|
||||
}
|
||||
],
|
||||
keyWordLikeFields: [
|
||||
{
|
||||
propertyName: "name",
|
||||
comment: "姓名",
|
||||
source: "a.name"
|
||||
},
|
||||
{
|
||||
propertyName: "phone",
|
||||
comment: "手机号",
|
||||
source: "a.phone"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
export { TestService };
|
149
cool-admin-vue/packages/crud/src/utils/form-hook.ts
Normal file
149
cool-admin-vue/packages/crud/src/utils/form-hook.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { isArray, isEmpty, isFunction, isObject, isString } from "lodash-es";
|
||||
|
||||
export const format: { [key: string]: ClForm.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;
|
||||
}
|
||||
|
||||
if (isArray(value)) {
|
||||
return isEmpty(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: any[] = [];
|
||||
|
||||
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] ${pipe} format error`);
|
||||
}
|
||||
|
||||
let v = value;
|
||||
|
||||
pipes.forEach((e) => {
|
||||
let f: any = 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: ClForm.Hook["Fn"]) {
|
||||
format[name] = fn;
|
||||
}
|
||||
|
||||
export default formHook;
|
16
cool-admin-vue/packages/crud/src/utils/global.ts
Normal file
16
cool-admin-vue/packages/crud/src/utils/global.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// @ts-nocheck
|
||||
import { type 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;
|
||||
}
|
||||
};
|
164
cool-admin-vue/packages/crud/src/utils/index.ts
Normal file
164
cool-admin-vue/packages/crud/src/utils/index.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { isRef, mergeProps, toValue } from "vue";
|
||||
import { assign, flatMap, isArray, isFunction, isNumber, mergeWith } from "lodash-es";
|
||||
|
||||
// 是否对象
|
||||
export function isObject(val: any) {
|
||||
return val !== null && typeof val === "object";
|
||||
}
|
||||
|
||||
// 解析px
|
||||
export function parsePx(val: string | number) {
|
||||
return isNumber(val) ? `${val}px` : val;
|
||||
}
|
||||
|
||||
// 数据设置
|
||||
export function dataset(obj: any, key: string, value: any): any {
|
||||
const isGet = value === undefined;
|
||||
let d = obj;
|
||||
|
||||
const arr = flatMap(
|
||||
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)) {
|
||||
assign(d[n], value);
|
||||
} else {
|
||||
d[n] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
} catch (e) {
|
||||
console.error("[dataset] format error", `${key}`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// 元素是否包含
|
||||
export function contains(parent: any, node: any) {
|
||||
return parent !== node && parent && parent.contains(node);
|
||||
}
|
||||
|
||||
// 合并配置
|
||||
export function mergeConfig(a: any, b?: any): any {
|
||||
return b ? mergeProps(a, b) : a;
|
||||
}
|
||||
|
||||
// 合并数据
|
||||
export function merge(d1: any, d2: any) {
|
||||
return mergeWith(d1, d2, (_, b) => {
|
||||
if (isArray(b)) {
|
||||
return b;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加元素
|
||||
export function addClass(el: Element, name: string) {
|
||||
if (el?.classList) {
|
||||
el.classList.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除元素
|
||||
export function removeClass(el: Element, name: string) {
|
||||
if (el?.classList) {
|
||||
el.classList.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取值
|
||||
export function getValue<T = any>(value: T | Vue.Ref<T> | ((d: any) => T), data?: any): T {
|
||||
if (isRef(value)) {
|
||||
return toValue(value) as T;
|
||||
} else {
|
||||
if (isFunction(value)) {
|
||||
return value(data);
|
||||
} else {
|
||||
return value as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深度查找
|
||||
export function deepFind(value: any, list: any[], options?: { allLevels: boolean }) {
|
||||
const { allLevels = true } = options || {};
|
||||
|
||||
function deep(arr: DictOptions, name: string[]): any | undefined {
|
||||
for (const e of arr) {
|
||||
if (e.value === value) {
|
||||
if (allLevels) {
|
||||
return {
|
||||
...e,
|
||||
label: [...name, e.label].join(" / ")
|
||||
};
|
||||
} else {
|
||||
return e;
|
||||
}
|
||||
} else if (e.children) {
|
||||
const d = deep(e.children, [...name!, e.label!]);
|
||||
|
||||
if (d !== undefined) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return deep(list, []);
|
||||
}
|
||||
|
||||
// uuid
|
||||
export function uuid(separator = "-"): string {
|
||||
const s: any[] = [];
|
||||
const hexDigits = "0123456789abcdef";
|
||||
for (let i = 0; i < 36; i++) {
|
||||
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
|
||||
}
|
||||
s[14] = "4";
|
||||
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
|
||||
s[8] = s[13] = s[18] = s[23] = separator;
|
||||
|
||||
return s.join("");
|
||||
}
|
||||
|
||||
// 移除相同的方法
|
||||
export function uniqueFns(fns: any[]) {
|
||||
const arr = new Map();
|
||||
fns.forEach((fn) => {
|
||||
arr.set(fn.name, fn);
|
||||
});
|
||||
return Array.from(arr.values());
|
||||
}
|
30
cool-admin-vue/packages/crud/src/utils/mitt.ts
Normal file
30
cool-admin-vue/packages/crud/src/utils/mitt.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import _mitt from "mitt";
|
||||
|
||||
const mitt = _mitt();
|
||||
|
||||
class Mitt {
|
||||
id: number;
|
||||
|
||||
constructor(id?: number) {
|
||||
this.id = id || 0;
|
||||
}
|
||||
|
||||
send(type: "emit" | "off" | "on", name: string, ...args: any[]) {
|
||||
// @ts-expect-error
|
||||
mitt[type](`${this.id}__${name}`, ...args);
|
||||
}
|
||||
|
||||
emit(name: string, ...args: any[]) {
|
||||
this.send("emit", name, ...args);
|
||||
}
|
||||
|
||||
off(name: string, handler: (...args: any[]) => void) {
|
||||
this.send("off", name, handler);
|
||||
}
|
||||
|
||||
on(name: string, handler: (...args: any[]) => void) {
|
||||
this.send("on", name, handler);
|
||||
}
|
||||
}
|
||||
|
||||
export { Mitt, mitt };
|
52
cool-admin-vue/packages/crud/src/utils/parse.tsx
Normal file
52
cool-admin-vue/packages/crud/src/utils/parse.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { isString } from "lodash-es";
|
||||
import { getValue, isObject } from ".";
|
||||
|
||||
// 解析扩展组件
|
||||
export function parseExtensionComponent(vnode: Render.Component) {
|
||||
if (["el-select", "el-radio-group", "el-checkbox-group"].includes(vnode.name!)) {
|
||||
const list = getValue(vnode.options || []);
|
||||
|
||||
const children = (
|
||||
<div>
|
||||
{list.map((e, i) => {
|
||||
let label: any;
|
||||
let value: any;
|
||||
|
||||
if (isString(e)) {
|
||||
label = value = e;
|
||||
} else if (isObject(e)) {
|
||||
label = e.label;
|
||||
value = e.value;
|
||||
} else {
|
||||
return <cl-error-message title={`Component options error`} />;
|
||||
}
|
||||
|
||||
switch (vnode.name) {
|
||||
case "el-select":
|
||||
return <el-option key={i} label={label} value={value} {...e.props} />;
|
||||
case "el-radio-group":
|
||||
return (
|
||||
<el-radio key={i} value={value} {...e.props}>
|
||||
{label}
|
||||
</el-radio>
|
||||
);
|
||||
case "el-checkbox-group":
|
||||
return (
|
||||
<el-checkbox key={i} value={value} {...e.props}>
|
||||
{label}
|
||||
</el-checkbox>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
return {
|
||||
children
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
182
cool-admin-vue/packages/crud/src/utils/vnode.tsx
Normal file
182
cool-admin-vue/packages/crud/src/utils/vnode.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import { h, resolveComponent, toRaw, type VNode } from "vue";
|
||||
import { isObject } from "./index";
|
||||
import { parseExtensionComponent } from "./parse";
|
||||
import global from "./global";
|
||||
import { useConfig } from "../hooks";
|
||||
import { isFunction, isString } from "lodash-es";
|
||||
|
||||
// 配置
|
||||
interface Options {
|
||||
// 标识
|
||||
prop?: string;
|
||||
// 数据值
|
||||
scope?: any;
|
||||
// 当前行
|
||||
item?: any;
|
||||
// 插槽
|
||||
slots?: any;
|
||||
// 子集
|
||||
children?: any[] & 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 || {};
|
||||
|
||||
// 渲染后组件
|
||||
let comp: VNode | null = null;
|
||||
|
||||
// 插槽模式渲染
|
||||
if (vnode.name?.includes("slot-")) {
|
||||
const rn = slots[vnode.name];
|
||||
|
||||
if (rn) {
|
||||
return rn({ scope, prop, ..._data });
|
||||
} else {
|
||||
return <cl-error-message title={`${vnode.name} is not found`} />;
|
||||
}
|
||||
}
|
||||
|
||||
// 实例模式下,先注册到全局,再分解组件渲染
|
||||
if (vnode.vm && !regs.get(vnode.name)) {
|
||||
global.vue.component(vnode.name, { ...vnode.vm });
|
||||
regs.set(vnode.name, { ...vnode.vm });
|
||||
}
|
||||
|
||||
// 处理 props
|
||||
if (isFunction(vnode.props)) {
|
||||
vnode.props = vnode.props({ scope, prop, ..._data });
|
||||
}
|
||||
|
||||
// 组件参数
|
||||
const props = {
|
||||
...vnode.props,
|
||||
..._data,
|
||||
prop,
|
||||
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 slots = {
|
||||
...vnode.slots
|
||||
};
|
||||
|
||||
if (children) {
|
||||
slots.default = () => children;
|
||||
}
|
||||
|
||||
// 渲染组件
|
||||
comp = h(toRaw(resolveComponent(vnode.name)), props, slots);
|
||||
}
|
||||
|
||||
// 挂载到 refs 中
|
||||
const refBind = vnode.ref || options.ref;
|
||||
if (isFunction(refBind)) {
|
||||
setTimeout(() => {
|
||||
refBind(comp?.component?.exposed);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
// 渲染节点
|
||||
export function renderNode(vnode: any, options: Options) {
|
||||
const config = useConfig();
|
||||
const { item, scope, children, _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 = config.dict.label.placeholder;
|
||||
break;
|
||||
}
|
||||
|
||||
if (placeholder) {
|
||||
if (!item.component.props.placeholder) {
|
||||
item.component.props.placeholder = placeholder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 组件实例
|
||||
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) {
|
||||
return parseNode(vnode, { ...options, children, ...parseExtensionComponent(vnode) });
|
||||
} else {
|
||||
if (options.custom) {
|
||||
return options.custom(vnode);
|
||||
}
|
||||
|
||||
return <cl-error-message title="Error,component name is required" />;
|
||||
}
|
||||
}
|
||||
}
|
34
cool-admin-vue/packages/crud/tsconfig.json
Normal file
34
cool-admin-vue/packages/crud/tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"noImplicitAny": false,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
"types": ["./index.d.ts"],
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"declaration": true,
|
||||
"declarationDir": "types",
|
||||
"emitDeclarationOnly": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"exclude": ["src/App.vue", "node_modules", "dist"]
|
||||
}
|
2
cool-admin-vue/packages/crud/types/components/add-btn/index.d.ts
vendored
Normal file
2
cool-admin-vue/packages/crud/types/components/add-btn/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const _default: import('vue').DefineComponent<{}, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
4
cool-admin-vue/packages/crud/types/components/adv/btn.d.ts
vendored
Normal file
4
cool-admin-vue/packages/crud/types/components/adv/btn.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare const _default: import('vue').DefineComponent<{}, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {
|
||||
Search: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<import('vue').ExtractPropTypes<{}>>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
90
cool-admin-vue/packages/crud/types/components/adv/search.d.ts
vendored
Normal file
90
cool-admin-vue/packages/crud/types/components/adv/search.d.ts
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
import { PropType } from "vue";
|
||||
|
||||
declare const _default: import("vue").DefineComponent<
|
||||
import("vue").ExtractPropTypes<{
|
||||
items: {
|
||||
type: PropType<ClForm.Item[]>;
|
||||
default: () => any[];
|
||||
};
|
||||
title: StringConstructor;
|
||||
size: {
|
||||
type: (NumberConstructor | StringConstructor)[];
|
||||
default: string;
|
||||
};
|
||||
op: {
|
||||
type: ArrayConstructor;
|
||||
default: () => string[];
|
||||
};
|
||||
onSearch: FunctionConstructor;
|
||||
}>,
|
||||
() => any,
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
import("vue").ComponentOptionsMixin,
|
||||
import("vue").ComponentOptionsMixin,
|
||||
("clear" | "reset")[],
|
||||
"clear" | "reset",
|
||||
import("vue").PublicProps,
|
||||
Readonly<
|
||||
import("vue").ExtractPropTypes<{
|
||||
items: {
|
||||
type: PropType<ClForm.Item[]>;
|
||||
default: () => any[];
|
||||
};
|
||||
title: StringConstructor;
|
||||
size: {
|
||||
type: (NumberConstructor | StringConstructor)[];
|
||||
default: string;
|
||||
};
|
||||
op: {
|
||||
type: ArrayConstructor;
|
||||
default: () => string[];
|
||||
};
|
||||
onSearch: FunctionConstructor;
|
||||
}>
|
||||
> &
|
||||
Readonly<{
|
||||
onReset?: (...args: any[]) => any;
|
||||
onClear?: (...args: any[]) => any;
|
||||
}>,
|
||||
{
|
||||
size: string | number;
|
||||
items: ClForm.Item<any>[];
|
||||
op: unknown[];
|
||||
},
|
||||
{},
|
||||
{
|
||||
Close: import("vue").DefineComponent<
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
import("vue").ComponentOptionsMixin,
|
||||
import("vue").ComponentOptionsMixin,
|
||||
{},
|
||||
string,
|
||||
import("vue").VNodeProps &
|
||||
import("vue").AllowedComponentProps &
|
||||
import("vue").ComponentCustomProps,
|
||||
Readonly<import("vue").ExtractPropTypes<{}>>,
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
string,
|
||||
import("vue").ComponentProvideOptions,
|
||||
true,
|
||||
{},
|
||||
any
|
||||
>;
|
||||
},
|
||||
{},
|
||||
string,
|
||||
import("vue").ComponentProvideOptions,
|
||||
true,
|
||||
{},
|
||||
any
|
||||
>;
|
||||
export default _default;
|
55
cool-admin-vue/packages/crud/types/components/context-menu/index.d.ts
vendored
Normal file
55
cool-admin-vue/packages/crud/types/components/context-menu/index.d.ts
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import { PropType } from "vue";
|
||||
|
||||
declare const ClContextMenu: import("vue").DefineComponent<
|
||||
import("vue").ExtractPropTypes<{
|
||||
show: BooleanConstructor;
|
||||
options: {
|
||||
type: PropType<ClContextMenu.Options>;
|
||||
default: () => {};
|
||||
};
|
||||
event: {
|
||||
type: ObjectConstructor;
|
||||
default: () => {};
|
||||
};
|
||||
}>,
|
||||
() => any,
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
import("vue").ComponentOptionsMixin,
|
||||
import("vue").ComponentOptionsMixin,
|
||||
{},
|
||||
string,
|
||||
import("vue").PublicProps,
|
||||
Readonly<
|
||||
import("vue").ExtractPropTypes<{
|
||||
show: BooleanConstructor;
|
||||
options: {
|
||||
type: PropType<ClContextMenu.Options>;
|
||||
default: () => {};
|
||||
};
|
||||
event: {
|
||||
type: ObjectConstructor;
|
||||
default: () => {};
|
||||
};
|
||||
}>
|
||||
> &
|
||||
Readonly<{}>,
|
||||
{
|
||||
options: ClContextMenu.Options;
|
||||
show: boolean;
|
||||
event: Record<string, any>;
|
||||
},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
string,
|
||||
import("vue").ComponentProvideOptions,
|
||||
true,
|
||||
{},
|
||||
any
|
||||
>;
|
||||
export declare const ContextMenu: {
|
||||
open(event: any, options: ClContextMenu.Options): ClContextMenu.Exposed;
|
||||
};
|
||||
export default ClContextMenu;
|
24
cool-admin-vue/packages/crud/types/components/crud/helper.d.ts
vendored
Normal file
24
cool-admin-vue/packages/crud/types/components/crud/helper.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Mitt } from "../../utils/mitt";
|
||||
|
||||
interface Options {
|
||||
mitt: Mitt;
|
||||
config: ClCrud.Config;
|
||||
crud: ClCrud.Ref;
|
||||
}
|
||||
export declare function useHelper({ config, crud, mitt }: Options): {
|
||||
proxy: (name: string, data?: any[]) => void;
|
||||
set: (key: string, value: any) => boolean;
|
||||
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;
|
||||
setParams: (data: obj) => void;
|
||||
};
|
||||
export {};
|
19
cool-admin-vue/packages/crud/types/components/crud/index.d.ts
vendored
Normal file
19
cool-admin-vue/packages/crud/types/components/crud/index.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
||||
name: StringConstructor;
|
||||
border: BooleanConstructor;
|
||||
padding: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
||||
name: StringConstructor;
|
||||
border: BooleanConstructor;
|
||||
padding: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
border: boolean;
|
||||
padding: string;
|
||||
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
86
cool-admin-vue/packages/crud/types/components/dialog/index.d.ts
vendored
Normal file
86
cool-admin-vue/packages/crud/types/components/dialog/index.d.ts
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
||||
modelValue: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
props: ObjectConstructor;
|
||||
title: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
height: StringConstructor;
|
||||
width: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
padding: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
keepAlive: BooleanConstructor;
|
||||
fullscreen: BooleanConstructor;
|
||||
controls: {
|
||||
type: ArrayConstructor;
|
||||
default: () => string[];
|
||||
};
|
||||
hideHeader: BooleanConstructor;
|
||||
beforeClose: FunctionConstructor;
|
||||
scrollbar: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
transparent: BooleanConstructor;
|
||||
}>, () => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
|
||||
[key: string]: any;
|
||||
}>, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, ("update:modelValue" | "fullscreen-change")[], "update:modelValue" | "fullscreen-change", import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
||||
modelValue: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
props: ObjectConstructor;
|
||||
title: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
height: StringConstructor;
|
||||
width: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
padding: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
keepAlive: BooleanConstructor;
|
||||
fullscreen: BooleanConstructor;
|
||||
controls: {
|
||||
type: ArrayConstructor;
|
||||
default: () => string[];
|
||||
};
|
||||
hideHeader: BooleanConstructor;
|
||||
beforeClose: FunctionConstructor;
|
||||
scrollbar: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
transparent: BooleanConstructor;
|
||||
}>> & Readonly<{
|
||||
"onUpdate:modelValue"?: (...args: any[]) => any;
|
||||
"onFullscreen-change"?: (...args: any[]) => any;
|
||||
}>, {
|
||||
title: string;
|
||||
padding: string;
|
||||
width: string;
|
||||
hideHeader: boolean;
|
||||
controls: unknown[];
|
||||
fullscreen: boolean;
|
||||
keepAlive: boolean;
|
||||
modelValue: boolean;
|
||||
transparent: boolean;
|
||||
scrollbar: boolean;
|
||||
}, {}, {
|
||||
Close: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<import('vue').ExtractPropTypes<{}>>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
FullScreen: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<import('vue').ExtractPropTypes<{}>>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
Minus: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<import('vue').ExtractPropTypes<{}>>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
6
cool-admin-vue/packages/crud/types/components/error-message/index.d.ts
vendored
Normal file
6
cool-admin-vue/packages/crud/types/components/error-message/index.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
||||
title: StringConstructor;
|
||||
}>, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
||||
title: StringConstructor;
|
||||
}>> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
6
cool-admin-vue/packages/crud/types/components/filter/index.d.ts
vendored
Normal file
6
cool-admin-vue/packages/crud/types/components/filter/index.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
||||
label: StringConstructor;
|
||||
}>, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
||||
label: StringConstructor;
|
||||
}>> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
2
cool-admin-vue/packages/crud/types/components/flex1/index.d.ts
vendored
Normal file
2
cool-admin-vue/packages/crud/types/components/flex1/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const _default: import('vue').DefineComponent<{}, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
28
cool-admin-vue/packages/crud/types/components/form-card/index.d.ts
vendored
Normal file
28
cool-admin-vue/packages/crud/types/components/form-card/index.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
||||
label: StringConstructor;
|
||||
expand: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
isExpand: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
}>, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
||||
label: StringConstructor;
|
||||
expand: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
isExpand: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
expand: boolean;
|
||||
isExpand: boolean;
|
||||
}, {}, {
|
||||
ArrowDown: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<import('vue').ExtractPropTypes<{}>>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
ArrowUp: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<import('vue').ExtractPropTypes<{}>>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
67
cool-admin-vue/packages/crud/types/components/form-tabs/index.d.ts
vendored
Normal file
67
cool-admin-vue/packages/crud/types/components/form-tabs/index.d.ts
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import { PropType } from "vue";
|
||||
|
||||
declare const _default: import("vue").DefineComponent<
|
||||
import("vue").ExtractPropTypes<{
|
||||
modelValue: (NumberConstructor | StringConstructor)[];
|
||||
labels: {
|
||||
type: ArrayConstructor;
|
||||
default: () => any[];
|
||||
};
|
||||
justify: {
|
||||
type: PropType<
|
||||
"start" | "end" | "left" | "right" | "center" | "justify" | "match-parent"
|
||||
>;
|
||||
default: string;
|
||||
};
|
||||
type: {
|
||||
type: PropType<"card" | "default">;
|
||||
default: string;
|
||||
};
|
||||
}>,
|
||||
() => any,
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
import("vue").ComponentOptionsMixin,
|
||||
import("vue").ComponentOptionsMixin,
|
||||
("change" | "update:modelValue")[],
|
||||
"change" | "update:modelValue",
|
||||
import("vue").PublicProps,
|
||||
Readonly<
|
||||
import("vue").ExtractPropTypes<{
|
||||
modelValue: (NumberConstructor | StringConstructor)[];
|
||||
labels: {
|
||||
type: ArrayConstructor;
|
||||
default: () => any[];
|
||||
};
|
||||
justify: {
|
||||
type: PropType<
|
||||
"start" | "end" | "left" | "right" | "center" | "justify" | "match-parent"
|
||||
>;
|
||||
default: string;
|
||||
};
|
||||
type: {
|
||||
type: PropType<"card" | "default">;
|
||||
default: string;
|
||||
};
|
||||
}>
|
||||
> &
|
||||
Readonly<{
|
||||
onChange?: (...args: any[]) => any;
|
||||
"onUpdate:modelValue"?: (...args: any[]) => any;
|
||||
}>,
|
||||
{
|
||||
type: "default" | "card";
|
||||
labels: unknown[];
|
||||
justify: "left" | "center" | "right" | "justify" | "start" | "end" | "match-parent";
|
||||
},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
string,
|
||||
import("vue").ComponentProvideOptions,
|
||||
true,
|
||||
{},
|
||||
any
|
||||
>;
|
||||
export default _default;
|
17
cool-admin-vue/packages/crud/types/components/form/helper/action.d.ts
vendored
Normal file
17
cool-admin-vue/packages/crud/types/components/form/helper/action.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
export declare function useAction({ config, form, Form }: {
|
||||
config: ClForm.Config;
|
||||
form: obj;
|
||||
Form: Vue.Ref<any>;
|
||||
}): {
|
||||
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;
|
||||
};
|
3
cool-admin-vue/packages/crud/types/components/form/helper/api.d.ts
vendored
Normal file
3
cool-admin-vue/packages/crud/types/components/form/helper/api.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export declare function useApi({ Form }: {
|
||||
Form: Vue.Ref<any>;
|
||||
}): obj;
|
237
cool-admin-vue/packages/crud/types/components/form/helper/index.d.ts
vendored
Normal file
237
cool-admin-vue/packages/crud/types/components/form/helper/index.d.ts
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
export declare function useForm(): {
|
||||
Form: import('vue').Ref<any, any>;
|
||||
config: {
|
||||
[x: string]: any;
|
||||
title?: any;
|
||||
height?: any;
|
||||
width?: any;
|
||||
props: {
|
||||
[x: string]: any;
|
||||
inline?: boolean;
|
||||
labelPosition?: "left" | "right" | "top";
|
||||
labelWidth?: string | number;
|
||||
labelSuffix?: string;
|
||||
hideRequiredAsterisk?: boolean;
|
||||
showMessage?: boolean;
|
||||
inlineMessage?: boolean;
|
||||
statusIcon?: boolean;
|
||||
validateOnRuleChange?: boolean;
|
||||
size?: ElementPlus.Size;
|
||||
disabled?: boolean;
|
||||
};
|
||||
items: {
|
||||
[x: string]: any;
|
||||
type?: "tabs";
|
||||
prop?: string & {};
|
||||
props?: {
|
||||
[x: string]: any;
|
||||
labels?: {
|
||||
[x: string]: any;
|
||||
label: string;
|
||||
value: string;
|
||||
name?: string;
|
||||
icon?: any;
|
||||
lazy?: boolean;
|
||||
}[];
|
||||
justify?: "left" | "center" | "right";
|
||||
color?: string;
|
||||
mergeProp?: boolean;
|
||||
labelWidth?: string;
|
||||
error?: string;
|
||||
showMessage?: boolean;
|
||||
inlineMessage?: boolean;
|
||||
size?: "medium" | "default" | "small";
|
||||
};
|
||||
span?: number;
|
||||
col?: {
|
||||
span: number;
|
||||
offset: number;
|
||||
push: number;
|
||||
pull: number;
|
||||
xs: any;
|
||||
sm: any;
|
||||
md: any;
|
||||
lg: any;
|
||||
xl: any;
|
||||
tag: string;
|
||||
};
|
||||
group?: string;
|
||||
collapse?: boolean;
|
||||
value?: any;
|
||||
label?: string;
|
||||
renderLabel?: any;
|
||||
flex?: boolean;
|
||||
hook?: "string" | "number" | "boolean" | "join" | "split" | AnyString | "empty" | "booleanNumber" | "datetimeRange" | "splitJoin" | "json" | {
|
||||
bind?: ClForm.Hook["Pipe"] | ClForm.Hook["Pipe"][];
|
||||
submit?: ClForm.Hook["Pipe"] | ClForm.Hook["Pipe"][];
|
||||
reset?: (prop: string) => string[];
|
||||
};
|
||||
hidden?: boolean | ((options: {
|
||||
scope: obj;
|
||||
}) => boolean);
|
||||
prepend?: {
|
||||
[x: string]: any;
|
||||
name?: string;
|
||||
options?: {
|
||||
[x: string]: any;
|
||||
label?: string;
|
||||
value?: any;
|
||||
color?: string;
|
||||
type?: string;
|
||||
}[] | {
|
||||
value: {
|
||||
[x: string]: any;
|
||||
label?: string;
|
||||
value?: any;
|
||||
color?: string;
|
||||
type?: string;
|
||||
}[];
|
||||
};
|
||||
props?: {
|
||||
[x: string]: any;
|
||||
onChange?: (value: any) => void;
|
||||
} | {
|
||||
value: {
|
||||
[x: string]: any;
|
||||
onChange?: (value: any) => void;
|
||||
};
|
||||
};
|
||||
style?: obj;
|
||||
slots?: {
|
||||
[key: string]: (data?: any) => any;
|
||||
};
|
||||
vm?: any;
|
||||
};
|
||||
component?: {
|
||||
[x: string]: any;
|
||||
name?: string;
|
||||
options?: {
|
||||
[x: string]: any;
|
||||
label?: string;
|
||||
value?: any;
|
||||
color?: string;
|
||||
type?: string;
|
||||
}[] | {
|
||||
value: {
|
||||
[x: string]: any;
|
||||
label?: string;
|
||||
value?: any;
|
||||
color?: string;
|
||||
type?: string;
|
||||
}[];
|
||||
};
|
||||
props?: {
|
||||
[x: string]: any;
|
||||
onChange?: (value: any) => void;
|
||||
} | {
|
||||
value: {
|
||||
[x: string]: any;
|
||||
onChange?: (value: any) => void;
|
||||
};
|
||||
};
|
||||
style?: obj;
|
||||
slots?: {
|
||||
[key: string]: (data?: any) => any;
|
||||
};
|
||||
vm?: any;
|
||||
};
|
||||
append?: {
|
||||
[x: string]: any;
|
||||
name?: string;
|
||||
options?: {
|
||||
[x: string]: any;
|
||||
label?: string;
|
||||
value?: any;
|
||||
color?: string;
|
||||
type?: string;
|
||||
}[] | {
|
||||
value: {
|
||||
[x: string]: any;
|
||||
label?: string;
|
||||
value?: any;
|
||||
color?: string;
|
||||
type?: string;
|
||||
}[];
|
||||
};
|
||||
props?: {
|
||||
[x: string]: any;
|
||||
onChange?: (value: any) => void;
|
||||
} | {
|
||||
value: {
|
||||
[x: string]: any;
|
||||
onChange?: (value: any) => void;
|
||||
};
|
||||
};
|
||||
style?: obj;
|
||||
slots?: {
|
||||
[key: string]: (data?: any) => any;
|
||||
};
|
||||
vm?: any;
|
||||
};
|
||||
rules?: {
|
||||
[x: string]: any;
|
||||
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;
|
||||
} | {
|
||||
[x: string]: any;
|
||||
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;
|
||||
}[];
|
||||
required?: boolean;
|
||||
children?: /*elided*/ any[];
|
||||
}[];
|
||||
form: obj;
|
||||
isReset?: boolean;
|
||||
on?: {
|
||||
open?: (data: any) => void;
|
||||
close?: (action: ClForm.CloseAction, done: fn) => void;
|
||||
submit?: (data: any, event: {
|
||||
close: fn;
|
||||
done: fn;
|
||||
}) => void;
|
||||
change?: (data: any, prop: string) => void;
|
||||
};
|
||||
op: {
|
||||
hidden?: boolean;
|
||||
saveButtonText?: string;
|
||||
closeButtonText?: string;
|
||||
justify?: "flex-start" | "center" | "flex-end";
|
||||
buttons?: (`slot-${string}` | ClForm.CloseAction | {
|
||||
[x: string]: any;
|
||||
label: string;
|
||||
type?: string;
|
||||
hidden?: boolean;
|
||||
onClick: (options: {
|
||||
scope: obj;
|
||||
}) => void;
|
||||
})[];
|
||||
};
|
||||
dialog: {
|
||||
[x: string]: any;
|
||||
title?: any;
|
||||
height?: string;
|
||||
width?: string;
|
||||
hideHeader?: boolean;
|
||||
controls?: Array<"fullscreen" | "close" | AnyString>;
|
||||
};
|
||||
};
|
||||
form: obj;
|
||||
visible: import('vue').Ref<boolean, boolean>;
|
||||
saving: import('vue').Ref<boolean, boolean>;
|
||||
loading: import('vue').Ref<boolean, boolean>;
|
||||
disabled: import('vue').Ref<boolean, boolean>;
|
||||
};
|
||||
export * from './action';
|
||||
export * from './api';
|
||||
export * from './plugins';
|
||||
export * from './tabs';
|
13
cool-admin-vue/packages/crud/types/components/form/helper/plugins.d.ts
vendored
Normal file
13
cool-admin-vue/packages/crud/types/components/form/helper/plugins.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Ref } from "vue";
|
||||
|
||||
export declare function usePlugins(
|
||||
enable: boolean,
|
||||
{
|
||||
visible
|
||||
}: {
|
||||
visible: Ref<boolean>;
|
||||
}
|
||||
): {
|
||||
create: (plugins?: ClForm.Plugin[]) => boolean;
|
||||
submit: (data: any) => Promise<any>;
|
||||
};
|
19
cool-admin-vue/packages/crud/types/components/form/helper/tabs.d.ts
vendored
Normal file
19
cool-admin-vue/packages/crud/types/components/form/helper/tabs.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
export declare function useTabs({ config, Form }: {
|
||||
config: ClForm.Config;
|
||||
Form: Vue.Ref<any>;
|
||||
}): {
|
||||
active: import('vue').Ref<string, string>;
|
||||
list: import('vue').ComputedRef<ClFormTabs.labels>;
|
||||
isLoaded: (value: any) => any;
|
||||
onLoad: (value: any) => void;
|
||||
get: () => ClForm.Item<any>;
|
||||
set: (data: any) => void;
|
||||
change: (value: any, isValid?: boolean) => Promise<unknown>;
|
||||
clear: () => void;
|
||||
mergeProp: (item: ClForm.Item) => void;
|
||||
toGroup: (opts: {
|
||||
config: ClForm.Config;
|
||||
prop: string;
|
||||
refs: any;
|
||||
}) => void;
|
||||
};
|
22
cool-admin-vue/packages/crud/types/components/form/index.d.ts
vendored
Normal file
22
cool-admin-vue/packages/crud/types/components/form/index.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
||||
name: StringConstructor;
|
||||
inner: BooleanConstructor;
|
||||
inline: BooleanConstructor;
|
||||
enablePlugin: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
}>, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
||||
name: StringConstructor;
|
||||
inner: BooleanConstructor;
|
||||
inline: BooleanConstructor;
|
||||
enablePlugin: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
}>> & Readonly<{}>, {
|
||||
inline: boolean;
|
||||
inner: boolean;
|
||||
enablePlugin: boolean;
|
||||
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
6
cool-admin-vue/packages/crud/types/components/index.d.ts
vendored
Normal file
6
cool-admin-vue/packages/crud/types/components/index.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { App } from "vue";
|
||||
|
||||
export declare const components: {
|
||||
[key: string]: any;
|
||||
};
|
||||
export declare function useComponent(app: App): void;
|
2
cool-admin-vue/packages/crud/types/components/multi-delete-btn/index.d.ts
vendored
Normal file
2
cool-admin-vue/packages/crud/types/components/multi-delete-btn/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const _default: import('vue').DefineComponent<{}, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
4
cool-admin-vue/packages/crud/types/components/pagination/index.d.ts
vendored
Normal file
4
cool-admin-vue/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').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
2
cool-admin-vue/packages/crud/types/components/refresh-btn/index.d.ts
vendored
Normal file
2
cool-admin-vue/packages/crud/types/components/refresh-btn/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const _default: import('vue').DefineComponent<{}, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
2
cool-admin-vue/packages/crud/types/components/row/index.d.ts
vendored
Normal file
2
cool-admin-vue/packages/crud/types/components/row/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const _default: import('vue').DefineComponent<{}, () => any, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
||||
export default _default;
|
84
cool-admin-vue/packages/crud/types/components/search-key/index.d.ts
vendored
Normal file
84
cool-admin-vue/packages/crud/types/components/search-key/index.d.ts
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
import { PropType } from "vue";
|
||||
|
||||
declare const _default: import("vue").DefineComponent<
|
||||
import("vue").ExtractPropTypes<{
|
||||
modelValue: StringConstructor;
|
||||
field: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
fieldList: {
|
||||
type: PropType<
|
||||
Array<{
|
||||
label: string;
|
||||
value: string;
|
||||
}>
|
||||
>;
|
||||
default: () => any[];
|
||||
};
|
||||
onSearch: FunctionConstructor;
|
||||
placeholder: StringConstructor;
|
||||
width: {
|
||||
type: (NumberConstructor | StringConstructor)[];
|
||||
default: number;
|
||||
};
|
||||
refreshOnInput: BooleanConstructor;
|
||||
}>,
|
||||
() => any,
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
import("vue").ComponentOptionsMixin,
|
||||
import("vue").ComponentOptionsMixin,
|
||||
("change" | "update:modelValue" | "field-change")[],
|
||||
"change" | "update:modelValue" | "field-change",
|
||||
import("vue").PublicProps,
|
||||
Readonly<
|
||||
import("vue").ExtractPropTypes<{
|
||||
modelValue: StringConstructor;
|
||||
field: {
|
||||
type: StringConstructor;
|
||||
default: string;
|
||||
};
|
||||
fieldList: {
|
||||
type: PropType<
|
||||
Array<{
|
||||
label: string;
|
||||
value: string;
|
||||
}>
|
||||
>;
|
||||
default: () => any[];
|
||||
};
|
||||
onSearch: FunctionConstructor;
|
||||
placeholder: StringConstructor;
|
||||
width: {
|
||||
type: (NumberConstructor | StringConstructor)[];
|
||||
default: number;
|
||||
};
|
||||
refreshOnInput: BooleanConstructor;
|
||||
}>
|
||||
> &
|
||||
Readonly<{
|
||||
onChange?: (...args: any[]) => any;
|
||||
"onUpdate:modelValue"?: (...args: any[]) => any;
|
||||
"onField-change"?: (...args: any[]) => any;
|
||||
}>,
|
||||
{
|
||||
width: string | number;
|
||||
refreshOnInput: boolean;
|
||||
field: string;
|
||||
fieldList: {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
string,
|
||||
import("vue").ComponentProvideOptions,
|
||||
true,
|
||||
{},
|
||||
any
|
||||
>;
|
||||
export default _default;
|
3
cool-admin-vue/packages/crud/types/components/search/helper/plugins.d.ts
vendored
Normal file
3
cool-admin-vue/packages/crud/types/components/search/helper/plugins.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export declare function usePlugins(): {
|
||||
create: (plugins?: ClSearch.Plugin[]) => void;
|
||||
};
|
91
cool-admin-vue/packages/crud/types/components/search/index.d.ts
vendored
Normal file
91
cool-admin-vue/packages/crud/types/components/search/index.d.ts
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
import { PropType } from "vue";
|
||||
|
||||
declare const _default: import("vue").DefineComponent<
|
||||
import("vue").ExtractPropTypes<{
|
||||
inline: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
props: {
|
||||
type: ObjectConstructor;
|
||||
default: () => {};
|
||||
};
|
||||
data: {
|
||||
type: ObjectConstructor;
|
||||
default: () => {};
|
||||
};
|
||||
items: {
|
||||
type: PropType<ClForm.Item[]>;
|
||||
default: () => any[];
|
||||
};
|
||||
resetBtn: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
collapse: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
onLoad: FunctionConstructor;
|
||||
onSearch: FunctionConstructor;
|
||||
}>,
|
||||
() => any,
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
import("vue").ComponentOptionsMixin,
|
||||
import("vue").ComponentOptionsMixin,
|
||||
"reset"[],
|
||||
"reset",
|
||||
import("vue").PublicProps,
|
||||
Readonly<
|
||||
import("vue").ExtractPropTypes<{
|
||||
inline: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
props: {
|
||||
type: ObjectConstructor;
|
||||
default: () => {};
|
||||
};
|
||||
data: {
|
||||
type: ObjectConstructor;
|
||||
default: () => {};
|
||||
};
|
||||
items: {
|
||||
type: PropType<ClForm.Item[]>;
|
||||
default: () => any[];
|
||||
};
|
||||
resetBtn: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
collapse: {
|
||||
type: BooleanConstructor;
|
||||
default: boolean;
|
||||
};
|
||||
onLoad: FunctionConstructor;
|
||||
onSearch: FunctionConstructor;
|
||||
}>
|
||||
> &
|
||||
Readonly<{
|
||||
onReset?: (...args: any[]) => any;
|
||||
}>,
|
||||
{
|
||||
data: Record<string, any>;
|
||||
props: Record<string, any>;
|
||||
items: ClForm.Item<any>[];
|
||||
inline: boolean;
|
||||
resetBtn: boolean;
|
||||
collapse: boolean;
|
||||
},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
string,
|
||||
import("vue").ComponentProvideOptions,
|
||||
true,
|
||||
{},
|
||||
any
|
||||
>;
|
||||
export default _default;
|
7
cool-admin-vue/packages/crud/types/components/table/helper/data.d.ts
vendored
Normal file
7
cool-admin-vue/packages/crud/types/components/table/helper/data.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export declare function useData({ config, Table }: {
|
||||
config: ClTable.Config;
|
||||
Table: Vue.Ref<any>;
|
||||
}): {
|
||||
data: import('vue').Ref<obj[], obj[]>;
|
||||
setData: (list: obj[]) => void;
|
||||
};
|
1
cool-admin-vue/packages/crud/types/components/table/helper/header.d.ts
vendored
Normal file
1
cool-admin-vue/packages/crud/types/components/table/helper/header.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function renderHeader(item: ClTable.Column, { scope, slots }: any): any;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user