发布 7.1.0

This commit is contained in:
神仙都没用 2024-02-02 20:49:42 +08:00
parent 70392f0599
commit 93afe3e00b
111 changed files with 3722 additions and 3062 deletions

View File

@ -2,7 +2,7 @@ import { createDir, error, firstUpperCase, readFile, toCamel } from "../utils";
import { join } from "path";
import { Entity, DistPath } from "./config";
import axios from "axios";
import { isArray, isEmpty, last, merge, unionBy } from "lodash";
import { isArray, isEmpty, last, merge, unionBy } from "lodash-es";
import { createWriteStream } from "fs";
import prettier from "prettier";
import { proxy } from "../../../src/config/proxy";

View File

@ -9,17 +9,17 @@
"lint:eslint": "eslint \"./src/**/*.{vue,ts,tsx}\" --fix"
},
"dependencies": {
"@cool-vue/crud": "^7.1.10",
"@cool-vue/crud": "^7.1.11",
"@element-plus/icons-vue": "^2.1.0",
"@vueuse/core": "^10.4.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.7",
"chardet": "^1.6.0",
"chardet": "^2.0.0",
"core-js": "^3.32.1",
"dayjs": "^1.11.10",
"echarts": "^5.4.3",
"element-plus": "^2.4.3",
"element-plus": "^2.5.4",
"file-saver": "^2.0.5",
"lodash-es": "^4.17.21",
"marked": "^11.1.1",
@ -31,8 +31,7 @@
"pinia": "^2.1.7",
"socket.io-client": "^4.7.2",
"store": "^2.0.12",
"ts-wps": "^1.0.5",
"vue": "^3.3.9",
"vue": "^3.4.15",
"vue-echarts": "^6.6.1",
"vue-router": "^4.2.5",
"vuedraggable": "^4.1.0",
@ -43,26 +42,24 @@
"@types/mockjs": "^1.0.7",
"@types/node": "^20.5.6",
"@types/nprogress": "^0.2.0",
"@types/prettier": "^3.0.0",
"@types/store": "^2.0.2",
"@typescript-eslint/eslint-plugin": "^6.4.1",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.4.1",
"@vitejs/plugin-vue": "^4.3.3",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/compiler-sfc": "^3.3.4",
"@vitejs/plugin-vue": "^5.0.3",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/compiler-sfc": "^3.4.15",
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.17.0",
"glob": "^10.3.10",
"lodash": "^4.17.21",
"magic-string": "^0.30.3",
"prettier": "^3.1.0",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.66.1",
"terser": "^5.19.2",
"terser": "^5.27.0",
"typescript": "^5.2.2",
"vite": "^4.5.0",
"vite": "^5.0.12",
"vite-plugin-compression": "^0.5.1"
}
}

View File

@ -10,7 +10,7 @@ Vue.js 是一套用于构建用户界面的渐进式框架。与其它大型框
<img src='https://cool-js.com/assets/home.1706ac70.png' />
<span style="font-size: 18px; color: #F56C6C">v6.0.0 新增 Ai 极速编码 ~~~~</span>
<span style="font-size: 18px; color: #F56C6C">v7.0.0 新增 Ai 极速编码 ~~~~</span>
<img src='https://cool-js.com/assets/ai-code2.9a122008.png' />

View File

@ -113,6 +113,9 @@ declare namespace Render {
// 获取keys
type PropKey<T> = keyof RemoveIndex<T> | (string & {});
// 任意字符串
type AnyString = string & {};
declare namespace ClCrud {
interface Label {
op: string;
@ -291,10 +294,12 @@ declare namespace ClCrud {
}
declare namespace ClTable {
type OpButton = Array<"info" | "edit" | "delete" | Render.OpButton>;
type OpButton = Array<"info" | "edit" | "delete" | AnyString | Render.OpButton>;
type ColumnType = "index" | "selection" | "expand" | "op" | AnyString;
interface Column<T = any> {
type: "index" | "selection" | "expand" | "op";
type: ColumnType;
hidden: boolean | Vue.Ref<boolean>;
component: Render.Component;
search: {
@ -352,10 +357,12 @@ declare namespace ClTable {
| "order-asc"
>;
type Plugin = (options: { exposed: Ref }) => void;
interface Config<T = any> {
columns: Column<T>[];
autoHeight: boolean;
height: string | number;
height: any;
contextMenu: ContextMenu;
defaultSort: {
prop: string;
@ -364,6 +371,7 @@ declare namespace ClTable {
sortRefresh: boolean;
emptyText: string;
rowKey: string;
plugins?: Plugin[];
onRowContextmenu?(row: T, column: any, event: any): void;
}
@ -412,7 +420,7 @@ declare namespace ClFormTabs {
}
declare namespace ClForm {
type CloseAction = "close" | "save";
type CloseAction = "close" | "save" | AnyString;
interface Rule {
type?:
@ -456,7 +464,7 @@ declare namespace ClForm {
| "splitJoin"
| "json"
| "empty"
| (string & {});
| AnyString;
type HookPipe = HookKey | HookFn;
@ -512,8 +520,8 @@ declare namespace ClForm {
interface Config<T = any> {
title?: any;
height?: string;
width?: string;
height?: any;
width?: any;
props: ElementPlus.FormProps;
items: Item[];
form: obj;
@ -535,7 +543,7 @@ declare namespace ClForm {
height?: string;
width?: string;
hideHeader?: boolean;
controls?: Array<"fullscreen" | "close">;
controls?: Array<"fullscreen" | "close" | AnyString>;
[key: string]: any;
};
[key: string]: any;
@ -616,7 +624,7 @@ declare namespace ClUpsert {
}
interface Ref<T = any> extends ClForm.Ref<T> {
mode: "add" | "update" | "info";
mode: "add" | "update" | "info" | AnyString;
}
interface Options<T = any> extends DeepPartial<Config<T>> {
@ -720,6 +728,7 @@ declare interface Config {
labelPosition: ElementPlus.FormProps["labelPosition"];
labelWidth: ElementPlus.FormProps["labelWidth"];
span: number;
plugins: ClForm.Plugin[];
};
table: {
stripe: boolean;
@ -733,6 +742,7 @@ declare interface Config {
align: ElementPlus.Align;
headerAlign: ElementPlus.Align;
};
plugins: ClTable.Plugin[];
};
};
}

View File

@ -1,6 +1,6 @@
{
"name": "@cool-vue/crud",
"version": "7.1.10",
"version": "7.1.11",
"private": false,
"main": "./dist/index.umd.min.js",
"typings": "types/index.d.ts",
@ -13,10 +13,10 @@
"dependencies": {
"array.prototype.flat": "^1.2.4",
"core-js": "^3.21.1",
"element-plus": "^2.4.3",
"element-plus": "^2.5.4",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"vue": "^3.3.9"
"vue": "^3.4.15"
},
"devDependencies": {
"@types/array.prototype.flat": "^1.2.1",
@ -28,7 +28,7 @@
"prettier": "^3.1.0",
"sass": "^1.55.0",
"sass-loader": "^12.6.0",
"typescript": "^4.6.2"
"typescript": "^5.3.3"
},
"files": [
"dist",

View File

@ -9,7 +9,7 @@
<cl-flex1 />
<cl-search-key v-model="v1" @change="onChange" refreshOnInput></cl-search-key>
<cl-search-key refreshOnInput></cl-search-key>
</cl-row>
<cl-row>
@ -30,7 +30,6 @@
<script setup lang="tsx">
import { useTable, useForm, useUpsert, useCrud } from "./hooks";
import { EditPen } from "@element-plus/icons-vue";
import { ref } from "vue";
interface Data {
name?: string;
@ -38,12 +37,6 @@ interface Data {
[key: string]: any;
}
const v1 = ref();
function onChange() {
console.log(1111);
}
const Upsert = useUpsert<Data>({
items: [
{

View File

@ -64,7 +64,7 @@ export function useAction({
break;
}
} else {
console.error(`Prop[${prop}] is not found`);
console.error(`[set] ${prop} is not found`);
}
}
}

View File

@ -1,12 +1,14 @@
import { Ref, WatchStopHandle, getCurrentInstance, watch } from "vue";
import { useConfig } from "../../../hooks";
export function usePlugins({ visible }: { visible: Ref<boolean> }) {
const that: any = getCurrentInstance();
const { style } = useConfig();
interface Event {
onOpen: (() => void)[];
onClose: (() => void)[];
onSubmit: ((data: obj) => obj)[];
onSubmit: ((data: obj) => Promise<obj> | obj)[];
[key: string]: any;
}
@ -21,47 +23,46 @@ export function usePlugins({ visible }: { visible: Ref<boolean> }) {
let timer: WatchStopHandle | null = null;
// 插件创建
function create(plugins?: ClForm.Plugin[]) {
function create(plugins: ClForm.Plugin[] = []) {
for (const i in ev) {
ev[i] = [];
}
// 停止监听
if (timer) {
timer();
}
if (plugins) {
plugins.forEach((p) => {
p({
exposed: that.exposed,
onOpen(cb: any) {
ev.onOpen.push(cb);
},
onClose(cb: any) {
ev.onClose.push(cb);
},
onSubmit(cb: any) {
ev.onSubmit.push(cb);
}
});
});
// 执行
[...(style.form.plugins || []), ...plugins].forEach((p) => {
const d: any = {
exposed: that.exposed
};
timer = watch(
visible,
(val) => {
if (val) {
setTimeout(() => {
ev.onOpen.forEach((e) => e());
}, 10);
} else {
ev.onClose.forEach((e) => e());
}
},
{
immediate: true
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
}
);
}
// 表单提交

View File

@ -387,7 +387,7 @@ export default defineComponent({
scope: form,
render: "slot",
slots
})
})
: e.label;
},
default() {
@ -555,14 +555,15 @@ export default defineComponent({
return renderNode(e, {
scope: form,
slots,
custom({ scope }) {
custom() {
return (
<el-button
text
type={e.type}
bg
{...e.props}
onClick={() => {
e.onClick({ scope });
e.onClick({ scope: form });
}}>
{e.label}
</el-button>
@ -584,6 +585,7 @@ export default defineComponent({
}
expose({
refs,
Form,
visible,
saving,

View File

@ -0,0 +1,21 @@
import { getCurrentInstance } from "vue";
import { useConfig } from "../../../hooks";
export function usePlugins() {
const that: any = getCurrentInstance();
const { style } = useConfig();
// 插件创建
function create(plugins: ClTable.Plugin[] = []) {
// 执行
[...(style.table.plugins || []), ...plugins].forEach((p) => {
p({
exposed: that.exposed
});
});
}
return {
create
};
}

View File

@ -10,6 +10,7 @@ import {
useTable
} from "./helper";
import { useCore, useProxy, useElApi, useConfig } from "../../hooks";
import { usePlugins } from "./helper/plugins";
export default defineComponent({
name: "cl-table",
@ -54,6 +55,7 @@ export default defineComponent({
const { crud } = useCore();
const { style } = useConfig();
const { Table, config } = useTable(props);
const plugin = usePlugins();
// 排序
const Sort = useSort({ config, emit, Table });
@ -111,6 +113,7 @@ export default defineComponent({
useProxy(ctx);
expose(ctx);
plugin.create(config.plugins);
return () => {
const { renderColumn, renderAppend, renderEmpty } = useRender();

View File

@ -27,7 +27,7 @@ export function setFocus(prop?: string): ClForm.Plugin {
deep(exposed.config.items);
onOpen(() => {
refs[name]?.focus();
refs[name]?.focus?.();
});
}
};

View File

@ -372,6 +372,7 @@
display: inline-flex;
white-space: nowrap;
margin: 0;
padding: 0;
li {
display: inline-flex;
@ -479,6 +480,7 @@
display: flex;
flex-direction: column;
border-radius: 6px;
padding: 0;
.el-dialog {
&__header {

View File

@ -89,13 +89,13 @@ function parse(method: "submit" | "bind", { value, hook: pipe, form, prop }: any
return false;
}
let pipes = [];
let pipes: any[] = [];
if (isString(pipe)) {
if (format[pipe]) {
pipes = [pipe];
} else {
console.error(`Hook[${pipe}] is not found`);
console.error(`[hook] ${pipe} is not found`);
}
} else if (isArray(pipe)) {
pipes = pipe;
@ -105,13 +105,13 @@ function parse(method: "submit" | "bind", { value, hook: pipe, form, prop }: any
} else if (isFunction(pipe)) {
pipes = [pipe];
} else {
console.error(`Hook error`);
console.error(`[hook] ${pipe} format error`);
}
let v = value;
pipes.forEach((e: any) => {
let f = null;
pipes.forEach((e) => {
let f: any = null;
if (isString(e)) {
f = format[e];

View File

@ -59,7 +59,7 @@ export function dataset(obj: any, key: string, value: any): any {
return obj;
} catch (e) {
console.error("Format error", `${key}`);
console.error("[dataset] format error", `${key}`);
return {};
}
}

View File

@ -39,7 +39,7 @@ export function parseTableDict(value: any, item: ClTable.Column) {
}
// 绑定值
let values = [];
let values: any[] = [];
// 格式化值
if (isArray(value)) {

View File

@ -39,7 +39,7 @@ export function parseNode(vnode: any, options: Options): VNode {
let comp: VNode | null = null;
// 插槽模式渲染
if (vnode.name.includes("slot-")) {
if (vnode.name?.includes("slot-")) {
const rn = slots[vnode.name];
if (rn) {

View File

@ -3,6 +3,7 @@
"target": "esnext",
"module": "esnext",
"strict": true,
"noImplicitAny": false,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",

View File

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

View File

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

View File

@ -15,7 +15,7 @@ declare const _default: import("vue").DefineComponent<{
default: () => string[];
};
onSearch: FunctionConstructor;
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("clear" | "reset")[], "clear" | "reset", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
}, () => any, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("clear" | "reset")[], "clear" | "reset", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
items: {
type: PropType<ClForm.Item<any>[]>;
default: () => never[];

View File

@ -9,7 +9,7 @@ declare const ClContextMenu: import("vue").DefineComponent<{
type: ObjectConstructor;
default: () => {};
};
}, () => false | JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
}, () => any, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
show: BooleanConstructor;
options: {
type: ObjectConstructor;

View File

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

View File

@ -31,7 +31,7 @@ declare const _default: import("vue").DefineComponent<{
};
}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "fullscreen-change")[], "update:modelValue" | "fullscreen-change", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
}>, unknown, {}, {}, 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;

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ declare const _default: import("vue").DefineComponent<{
type: BooleanConstructor;
default: boolean;
};
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
}, () => any, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
label: StringConstructor;
expand: {
type: BooleanConstructor;

View File

@ -6,21 +6,21 @@ declare const _default: import("vue").DefineComponent<{
default: () => never[];
};
justify: {
type: PropType<"center" | "justify" | "left" | "right" | "end" | "start" | "match-parent">;
type: PropType<"center" | "justify" | "left" | "right" | "start" | "end" | "match-parent">;
default: string;
};
type: {
type: PropType<"default" | "card">;
default: string;
};
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("change" | "update:modelValue")[], "change" | "update:modelValue", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
}, () => any, unknown, {}, {}, 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: () => never[];
};
justify: {
type: PropType<"center" | "justify" | "left" | "right" | "end" | "start" | "match-parent">;
type: PropType<"center" | "justify" | "left" | "right" | "start" | "end" | "match-parent">;
default: string;
};
type: {
@ -33,6 +33,6 @@ declare const _default: import("vue").DefineComponent<{
}, {
type: "default" | "card";
labels: unknown[];
justify: "center" | "justify" | "left" | "right" | "end" | "start" | "match-parent";
justify: "center" | "justify" | "left" | "right" | "start" | "end" | "match-parent";
}, {}>;
export default _default;

View File

@ -4,8 +4,8 @@ export declare function useForm(): {
config: {
[x: string]: any;
title?: any;
height?: string | undefined;
width?: string | undefined;
height?: any;
width?: any;
props: {
[x: string]: any;
inline?: boolean | undefined;
@ -215,7 +215,7 @@ export declare function useForm(): {
height?: string | undefined;
width?: string | undefined;
hideHeader?: boolean | undefined;
controls?: ("close" | "fullscreen")[] | undefined;
controls?: ("close" | AnyString | "fullscreen")[] | undefined;
};
};
form: obj;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { PropType } from "vue";
import { type PropType } from "vue";
declare const _default: import("vue").DefineComponent<{
modelValue: StringConstructor;
field: {
@ -18,7 +18,8 @@ declare const _default: import("vue").DefineComponent<{
type: (NumberConstructor | StringConstructor)[];
default: number;
};
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("change" | "update:modelValue" | "field-change")[], "change" | "update:modelValue" | "field-change", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
refreshOnInput: BooleanConstructor;
}, () => any, unknown, {}, {}, 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;
@ -37,6 +38,7 @@ declare const _default: import("vue").DefineComponent<{
type: (NumberConstructor | StringConstructor)[];
default: number;
};
refreshOnInput: BooleanConstructor;
}>> & {
onChange?: ((...args: any[]) => any) | undefined;
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
@ -48,5 +50,6 @@ declare const _default: import("vue").DefineComponent<{
label: string;
value: string;
}[];
refreshOnInput: boolean;
}, {}>;
export default _default;

View File

@ -15,7 +15,7 @@ declare const _default: import("vue").DefineComponent<{
};
onLoad: FunctionConstructor;
onSearch: FunctionConstructor;
}, () => true | JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "reset"[], "reset", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
}, () => any, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "reset"[], "reset", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
data: {
type: ObjectConstructor;
default: () => {};

View File

@ -4,7 +4,7 @@ export declare function useTable(props: any): {
config: {
columns: {
[x: string]: any;
type: "op" | "expand" | "selection" | "index";
type: ClTable.ColumnType;
hidden: boolean | {
value: boolean;
};
@ -98,7 +98,7 @@ export declare function useTable(props: any): {
dictAllLevels: boolean;
buttons: ((options: {
scope: any;
}) => ClTable.OpButton) | ("info" | "delete" | "edit" | `slot-${string}` | {
}) => ClTable.OpButton) | ("info" | "delete" | "edit" | AnyString | `slot-${string}` | {
[x: string]: any;
label: string;
type?: string | undefined;
@ -139,7 +139,7 @@ export declare function useTable(props: any): {
children: any[];
}[];
autoHeight: boolean;
height: string | number;
height: any;
contextMenu: ("info" | "update" | "delete" | "edit" | "refresh" | {
[x: string]: any;
label: string;
@ -159,6 +159,7 @@ export declare function useTable(props: any): {
sortRefresh: boolean;
emptyText: string;
rowKey: string;
plugins?: ClTable.Plugin[] | undefined;
onRowContextmenu?: ((row: any, column: any, event: any) => void) | undefined;
};
};

View File

@ -0,0 +1,4 @@
/// <reference types="../index" />
export declare function usePlugins(): {
create: (plugins?: ClTable.Plugin[]) => void;
};

View File

@ -3,6 +3,6 @@ export declare function useRender(): {
renderColumn: (columns: ClTable.Column[]) => (import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}> | null)[];
renderEmpty: (emptyText: String) => JSX.Element;
renderAppend: () => JSX.Element;
renderEmpty: (emptyText: String) => any;
renderAppend: () => any;
};

View File

@ -24,7 +24,7 @@ declare const _default: import("vue").DefineComponent<{
};
}, () => false | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any;
}>, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("selection-change" | "sort-change")[], "selection-change" | "sort-change", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
}>, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("selection-change" | "sort-change")[], "selection-change" | "sort-change", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
columns: {
type: ArrayConstructor;
default: () => never[];

View File

@ -13,7 +13,7 @@ declare const _default: import("vue").DefineComponent<{
onClosed: FunctionConstructor;
onInfo: FunctionConstructor;
onSubmit: FunctionConstructor;
}, () => JSX.Element, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("opened" | "closed")[], "opened" | "closed", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
}, () => any, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("opened" | "closed")[], "opened" | "closed", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
items: {
type: ArrayConstructor;
default: () => never[];

View File

@ -17,7 +17,7 @@ export declare function parseTableOpButtons(buttons: any[], { scope }: any): any
*
*/
export declare function parseExtensionComponent(vnode: any): {
children: JSX.Element;
children: any;
} | {
children?: undefined;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
export const proxy = {
"/dev/": {
target: "http://127.0.0.1:8001",
// target: "http://127.0.0.1:8001",
target: "https://test-admin.cool-js.cloud",
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/dev/, "")
},

View File

@ -4,7 +4,15 @@ import { Router as VueRouter, RouteRecordRaw } from "vue-router";
export declare type Merge<A, B> = Omit<A, keyof B> & B;
export declare interface ModuleConfig {
name?: string;
label?: string;
description?: string;
order?: number;
version?: string;
logo?: string;
author?: string;
updateTime?: string;
demo?: { name: string; component: Component }[] | string;
options?: {
[key: string]: any;
};

View File

@ -20,9 +20,9 @@
</template>
<script lang="ts" name="cl-menu-select" setup>
import { useForm, useUpsert } from "@cool-vue/crud";
import { useForm } from "@cool-vue/crud";
import { cloneDeep } from "lodash-es";
import { computed, ref, useModel } from "vue";
import { computed, ref, useModel, onMounted } from "vue";
import { useCool } from "/@/cool";
import { deepTree } from "/@/cool/utils";
@ -53,25 +53,24 @@ const list = ref<any[]>([]);
//
const tree = computed(() => {
return deepTree(cloneDeep(list.value)).filter((e) => !e.parentId);
//
const data = list.value.filter(
(e) =>
e.id != Form.value?.form.id && (props.type === 0 ? e.type == 0 : props.type > e.type!)
);
return deepTree(cloneDeep(data)).filter((e) => !e.parentId);
});
//
async function refresh() {
return service.base.sys.menu.list().then((res) => {
//
list.value = res.filter(
(e) =>
e.id != Form.value?.form.id &&
(props.type === 0 ? e.type == 0 : props.type > e.type!)
);
function refresh() {
service.base.sys.menu.list().then((res) => {
list.value = res;
});
}
useUpsert({
onOpened() {
refresh();
}
onMounted(() => {
refresh();
});
</script>

View File

@ -51,7 +51,7 @@
<script lang="ts" name="app-topbar" setup>
import { computed, markRaw, onMounted, reactive } from "vue";
import { orderBy } from "lodash-es";
import { isFunction, orderBy } from "lodash-es";
import { useBase } from "/$/base";
import { module, useCool } from "/@/cool";
import RouteNav from "./route-nav.vue";
@ -90,9 +90,11 @@ const toolbar = reactive({
this.list = await Promise.all(
arr.map(async (e) => {
if (e) {
const c = await (isFunction(e.component) ? e.component() : e.component);
return {
...e,
component: markRaw((await e.component).default)
component: markRaw(c.default)
};
}
})

View File

@ -25,7 +25,7 @@
</template>
<script lang="ts" name="sys-param" setup>
import { setFocus, useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { Document } from "@element-plus/icons-vue";
import { reactive } from "vue";
import { useCool } from "/@/cool";
@ -202,8 +202,6 @@ const Upsert = useUpsert({
data_1: undefined,
data_2: undefined
});
},
plugins: [setFocus()]
}
});
</script>

View File

@ -47,8 +47,8 @@
</cl-crud>
</template>
<script lang="ts" name="sys-role" setup>
import { useTable, useUpsert, useCrud, setFocus } from "@cool-vue/crud";
<script lang="ts" setup name="sys-role">
import { useTable, useUpsert, useCrud } from "@cool-vue/crud";
import { useCool } from "/@/cool";
const { service } = useCool();
@ -117,9 +117,7 @@ const Upsert = useUpsert({
...data,
departmentIdList: data.departmentIdList || []
});
},
plugins: [setFocus()]
}
});
// cl-table

View File

@ -79,7 +79,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
import { useCool } from "/@/cool";
import { deepTree, revDeepTree } from "/@/cool/utils";
import { isArray } from "lodash-es";
import { ContextMenu, setFocus, useForm } from "@cool-vue/crud";
import { ContextMenu, useForm } from "@cool-vue/crud";
import { Refresh as RefreshIcon, Operation, MoreFilled } from "@element-plus/icons-vue";
import { checkPerm } from "/$/base";
import { useViewGroup } from "/@/plugins/view";
@ -160,70 +160,67 @@ function rowClick(item?: Eps.BaseSysDepartmentEntity) {
function rowEdit(item: Eps.BaseSysDepartmentEntity) {
const method = item.id ? "update" : "add";
Form.value?.open(
{
title: "编辑部门",
width: "550px",
props: {
labelWidth: "100px"
Form.value?.open({
title: "编辑部门",
width: "550px",
props: {
labelWidth: "100px"
},
items: [
{
label: "部门名称",
prop: "name",
component: {
name: "el-input"
},
required: true
},
items: [
{
label: "部门名称",
prop: "name",
component: {
name: "el-input"
},
required: true
},
{
label: "上级部门",
prop: "parentName",
component: {
name: "el-input",
props: {
disabled: true
}
}
},
{
label: "排序",
prop: "orderNum",
component: {
name: "el-input-number",
props: {
"controls-position": "right",
min: 0,
max: 100
}
{
label: "上级部门",
prop: "parentName",
component: {
name: "el-input",
props: {
disabled: true
}
}
],
form: {
...item
},
on: {
submit(data, { done, close }) {
service.base.sys.department[method]({
id: item.id,
parentId: item.parentId,
name: data.name,
orderNum: data.orderNum
})
.then(() => {
ElMessage.success(`新增部门 “${data.name}” 成功`);
close();
refresh();
})
.catch((err) => {
ElMessage.error(err.message);
done();
});
{
label: "排序",
prop: "orderNum",
component: {
name: "el-input-number",
props: {
"controls-position": "right",
min: 0,
max: 100
}
}
}
],
form: {
...item
},
[setFocus()]
);
on: {
submit(data, { done, close }) {
service.base.sys.department[method]({
id: item.id,
parentId: item.parentId,
name: data.name,
orderNum: data.orderNum
})
.then(() => {
ElMessage.success(`新增部门 “${data.name}” 成功`);
close();
refresh();
})
.catch((err) => {
ElMessage.error(err.message);
done();
});
}
}
});
}
//

View File

@ -10,7 +10,7 @@
<cl-dialog
v-model="visible"
title="聊天窗口"
height="700px"
height="70vh"
width="1200px"
padding="0"
keep-alive

View File

@ -36,7 +36,7 @@
</template>
<script lang="ts" name="cloud-func-info" setup>
import { setFocus, useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { useCool } from "/@/cool";
import { Status, CodeSnippets } from "../../dict";
import FuncLogs from "../../components/func-logs.vue";
@ -89,8 +89,7 @@ const Upsert = useUpsert({
...data,
content
});
},
plugins: [setFocus("name")]
}
});
// cl-table

View File

@ -1,67 +0,0 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">cl-context-menu</el-tag>
右键菜单
</div>
<div class="c">
<el-button type="success" @contextmenu.stop.prevent="open">右键点击</el-button>
</div>
<div class="f">
<span class="date">2019/10/23</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { ContextMenu } from "@cool-vue/crud";
import { ElMessage } from "element-plus";
function open(e: MouseEvent) {
ContextMenu.open(e, {
list: [
{
label: "新增",
suffixIcon: "el-icon-plus",
callback(done) {
ElMessage.info("点击了新增");
done();
}
},
{
label: "编辑",
suffixIcon: "el-icon-edit",
callback(done) {
ElMessage.info("点击了编辑");
done();
}
},
{
label: "删除",
suffixIcon: "el-icon-delete"
},
{
label: "二级",
suffixIcon: "el-icon-right",
children: [
{
label: "文本超出隐藏,有一天晚上",
ellipsis: true
},
{
label: "禁用",
disabled: true
},
{
label: "更多",
callback(done) {
ElMessage.warning("开发中");
done();
}
}
]
}
]
});
}
</script>

View File

@ -1,27 +0,0 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">v-copy</el-tag>
复制到剪贴板
</div>
<div class="c">
<el-button type="success" @click="toCopy"> https://cool-js.com 点击复制</el-button>
</div>
<div class="f">
<span class="date">2019/09/25</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { useClipboard } from "@vueuse/core";
import { ElMessage } from "element-plus";
const { copy } = useClipboard();
function toCopy() {
copy("https://cool-js.com");
ElMessage.success("保存成功");
}
</script>

View File

@ -1,14 +0,0 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">cl-crud</el-tag>
增删改查超快的
</div>
<div class="c">
<router-link to="/demo/crud">传送门</router-link>
</div>
<div class="f">
<span class="date">2019/09/25</span>
</div>
</div>
</template>

View File

@ -1,14 +0,0 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">design-page</el-tag>
页面设计
</div>
<div class="c">
<router-link to="/design/page">传送门</router-link>
</div>
<div class="f">
<span class="date">2023/02/01</span>
</div>
</div>
</template>

View File

@ -1,14 +0,0 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">cl-editor</el-tag>
编辑器
</div>
<div class="c">
<router-link to="/demo/editor">传送门</router-link>
</div>
<div class="f">
<span class="date">2019/11/07</span>
</div>
</div>
</template>

View File

@ -1,14 +0,0 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">file</el-tag>
文件管理
</div>
<div class="c">
<router-link to="/upload/list">传送门</router-link>
</div>
<div class="f">
<span class="date">2023/01/01</span>
</div>
</div>
</template>

View File

@ -1,14 +0,0 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">cl-form</el-tag>
很强的表单
</div>
<div class="c">
<router-link to="/demo/crud?key=cl-form">传送门</router-link>
</div>
<div class="f">
<span class="date">2019/01/01</span>
</div>
</div>
</template>

View File

@ -1,36 +0,0 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">cl-svg</el-tag>
svg图片库
</div>
<div class="c _svg">
<el-tooltip v-for="(item, index) in list" :key="index" :content="`icon-${item}`">
<cl-svg :size="18" :name="`icon-${item}`" />
</el-tooltip>
</div>
<div class="f">
<span class="date">2019/09/25</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const list = ref(["like", "video", "rank", "menu", "favor"]);
</script>
<style lang="scss" scoped>
._svg {
color: #000;
.cl-svg {
cursor: pointer;
&:hover {
color: #666;
}
}
}
</style>

View File

@ -1,14 +0,0 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">cl-upload</el-tag>
图片上传
</div>
<div class="c">
<router-link to="/demo/upload">传送门</router-link>
</div>
<div class="f">
<span class="date">2019/09/25</span>
</div>
</div>
</template>

View File

@ -120,14 +120,12 @@
</template>
<script lang="tsx" name="demo-crud" setup>
import { useCrud, useUpsert, useTable, useAdvSearch, setFocus, useSearch } from "@cool-vue/crud";
import { useCrud, useUpsert, useTable, useAdvSearch, useSearch } from "@cool-vue/crud";
import { useDict } from "/$/dict";
import { reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { useCool } from "/@/cool";
const v = ref();
//
const { service, refs, setRefs } = useCool();
@ -315,12 +313,6 @@ const Upsert = useUpsert<Eps.UserInfoEntity>({
}
],
//
plugins: [
//
setFocus("account")
],
//
onInfo(data, { next, done }) {
// info

View File

@ -50,7 +50,7 @@
</template>
<script setup lang="ts">
import { useCrud, useForm, useTable, useUpsert, setFocus } from "@cool-vue/crud";
import { useCrud, useForm, useTable, useUpsert } from "@cool-vue/crud";
import { useCool } from "/@/cool";
const { service } = useCool();
@ -72,8 +72,7 @@ const Upsert = useUpsert({
name: "el-date-picker"
}
}
],
plugins: [setFocus()]
]
});
// cl-table

View File

@ -21,7 +21,7 @@
</template>
<script setup lang="ts">
import { setFocus, useForm } from "@cool-vue/crud";
import { useForm } from "@cool-vue/crud";
import { setRole } from "./role";
const Form = useForm();
@ -101,9 +101,6 @@ function open(role: string) {
}
},
[
// item
setFocus(),
//
setRole(role)
]

View File

@ -0,0 +1,48 @@
import { merge } from "lodash";
import { defineComponent } from "vue";
const columns = {
UserInfo: {
label: "用户信息",
minWidth: 200,
component: {
vm: defineComponent({
name: "user-info",
props: {
scope: null
},
setup(props) {
return () => {
return (
<div>
<p>{props.scope.name}</p>
<p>{props.scope.phone}</p>
</div>
);
};
}
})
}
}
} as { [key: string]: DeepPartial<ClTable.Column> };
/**
* 便
* @returns
*/
export function setColumn(): ClTable.Plugin {
return ({ exposed }) => {
function deep(arr: ClTable.Column[]) {
arr.forEach((e) => {
if (e.tag) {
merge(e, columns[e.tag]);
}
deep(e.children || []);
});
}
deep(exposed.columns);
};
}

View File

@ -0,0 +1,86 @@
<template>
<div class="scope">
<div class="h">
<el-tag size="small" effect="dark">plugin</el-tag>
<span>插件的使用</span>
</div>
<div class="c">
<el-button @click="open">预览</el-button>
<demo-code :files="['table/plugin/index.vue', 'table/plugin/column.tsx']" />
<!-- 自定义表格组件 -->
<cl-dialog v-model="visible" title="插件的使用" width="80%">
<cl-crud ref="Crud">
<cl-row>
<cl-table ref="Table" />
</cl-row>
<cl-row>
<cl-flex1 />
<cl-pagination />
</cl-row>
</cl-crud>
</cl-dialog>
</div>
<div class="f">
<span class="date">2024-01-01</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useCrud, useTable } from "@cool-vue/crud";
import { ref } from "vue";
import { useDict } from "/$/dict";
import { setColumn } from "./column";
const { dict } = useDict();
// cl-crud
const Crud = useCrud(
{
service: "test"
},
(app) => {
app.refresh();
}
);
// cl-table
const Table = useTable({
autoHeight: false,
contextMenu: ["refresh"],
columns: [
{
type: "selection"
},
{
tag: "UserInfo"
},
{
label: "工作",
prop: "occupation",
dict: dict.get("occupation"),
minWidth: 140
},
{
label: "创建时间",
prop: "createTime",
minWidth: 160,
sortable: "desc"
}
],
//
plugins: [setColumn()]
});
const visible = ref(false);
function open() {
visible.value = true;
}
</script>

View File

@ -60,6 +60,7 @@ import TableDict from "./components/table/dict.vue";
import TableSpanMethod from "./components/table/span-method.vue";
import TableColumnCustom from "./components/table/column-custom.vue";
import TableComponent from "./components/table/component/index.vue";
import TablePlugin from "./components/table/plugin/index.vue";
import UpsertBase from "./components/upsert/base.vue";
import UpsertEvent from "./components/upsert/event.vue";
@ -115,7 +116,7 @@ const list = [
},
{
label: "高级",
children: [TableColumnCustom, TableComponent]
children: [TableColumnCustom, TableComponent, TablePlugin]
}
]
},

View File

@ -1,78 +0,0 @@
<template>
<div class="demo">
<el-row :gutter="10">
<el-col v-for="(item, index) in list" :key="index" :xs="24" :sm="12" :md="8" :lg="6">
<component :is="item" />
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup name="demo">
import ContextMenu from "../components/context-menu.vue";
import Crud from "../components/crud.vue";
import Upload from "../components/upload.vue";
import Editor from "../components/editor.vue";
import Svg from "../components/svg.vue";
import Copy from "../components/copy.vue";
import File from "../components/file.vue";
import Design from "../components/design.vue";
import ClForm from "../components/form.vue";
const list = [ContextMenu, ClForm, Crud, Upload, Editor, Svg, Copy, File, Design];
</script>
<style lang="scss" scoped>
.demo {
overflow-x: hidden;
:deep(.scope) {
background-color: #fff;
border-radius: 4px;
margin-bottom: 10px;
white-space: nowrap;
.h {
display: flex;
align-items: center;
height: 30px;
padding: 10px;
font-size: 12px;
color: #666;
.el-tag {
margin-right: 10px;
}
}
.c {
padding: 10px;
height: 50px;
box-sizing: border-box;
a {
font-size: 12px;
}
&._svg {
.cl-svg {
margin-right: 15px;
}
}
}
.f {
height: 30px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
font-size: 12px;
.date {
color: #999;
}
}
}
}
</style>

View File

@ -1,40 +0,0 @@
<template>
<div class="editor">
<el-tabs type="card">
<el-tab-pane label="Monaco">
<cl-editor-monaco v-model="monaco" language="typescript" />
</el-tab-pane>
<el-tab-pane label="Wang" lazy>
<cl-editor-wang v-model="wang" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="ts" name="demo-editor" setup>
import { ref } from "vue";
const wang = ref(
'<p><span style="font-size: 22px;"><em>富文本编</em></span><span style="color: rgb(216, 68, 147); font-size: 22px;"><em>辑器</em></span></p>'
);
const monaco = ref(`class User {
main() {
console.log('Name', '神仙都没用')
}
}
const user = new User();
user.main();
`);
</script>
<style lang="scss" scoped>
.editor {
background-color: var(--el-bg-color);
padding: 10px;
min-height: 100%;
box-sizing: border-box;
}
</style>

View File

@ -1,144 +0,0 @@
<template>
<div class="demo">
<el-tabs type="card">
<el-tab-pane label="普通上传">
<cl-upload v-model="v1" />
</el-tab-pane>
<el-tab-pane label="多图上传" lazy>
<cl-upload v-model="v2" text="选择图片" multiple />
</el-tab-pane>
<el-tab-pane label="文件上传" lazy>
<cl-upload v-model="v3" multiple text="文件上传" type="file" />
</el-tab-pane>
<el-tab-pane label="拖拽">
<div>
<el-divider content-position="left"> 拖拽排序 </el-divider>
<cl-upload multiple draggable />
</div>
<div>
<el-divider content-position="left"> 拖拽上传 </el-divider>
<cl-upload drag :size="[160, 300]" />
</div>
</el-tab-pane>
<el-tab-pane label="自定义内容">
<cl-upload type="file" multiple draggable custom-class="custom-upload">
<el-button :icon="Upload">上传</el-button>
<template #item="{ item }">
<div class="item" v-show="item.url">{{ item.url }}</div>
</template>
</cl-upload>
</el-tab-pane>
<el-tab-pane label="上传校验">
<cl-upload :before-upload="onBeforeUpload" />
</el-tab-pane>
<el-tab-pane label="文件空间">
<div>
<el-divider content-position="left"> 单选 </el-divider>
<cl-upload-space v-model="v4" :multiple="false" accept="image/*" />
</div>
<div>
<el-divider content-position="left"> 多选 </el-divider>
<cl-upload-space v-model="v5" :limit="3" accept="image/*" />
</div>
<div>
<el-divider content-position="left"> 自定义 </el-divider>
<cl-upload-space
v-model="v6"
:multiple="false"
:show-btn="false"
accept="image/*"
:ref="setRefs('uploadSpace')"
>
<div class="space-custom" @click="refs.uploadSpace?.open">
<cl-avatar :size="50" :src="v6" />
<p>选择头像</p>
</div>
</cl-upload-space>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="ts" name="demo-upload" setup>
import { ref } from "vue";
import { Upload } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { useCool } from "/@/cool";
const { refs, setRefs } = useCool();
const v1 = ref("");
const v2 = ref([]);
const v3 = ref("");
const v4 = ref<string[]>([]);
const v5 = ref<string[]>([]);
const v6 = ref("");
function onBeforeUpload(file: any) {
return new Promise((resolve) => {
if (file.size > 100000) {
ElMessage.warning("文件不能大于100k");
} else {
resolve(true);
}
});
}
</script>
<style lang="scss" scoped>
.demo {
background-color: var(--el-bg-color);
padding: 10px;
min-height: 100%;
box-sizing: border-box;
:deep(.custom-upload) {
.item {
border: 1px solid var(--el-border-color);
border-radius: 4px;
padding: 5px 10px;
margin-bottom: 10px;
font-size: 12px;
width: 100%;
box-sizing: border-box;
}
}
.space-custom {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
padding: 5px 10px;
font-size: 14px;
box-sizing: border-box;
height: 120px;
width: 120px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
p {
margin-top: 10px;
}
&:hover {
border-color: var(--el-color-primary);
}
}
}
</style>

View File

@ -57,8 +57,8 @@ export function useAi() {
// 拼接内容
content += msg.content || "";
} catch (e) {
console.error(e);
} catch (err) {
console.error(err);
}
}

View File

@ -0,0 +1,109 @@
.plugins {
overflow-x: hidden;
background-color: var(--el-bg-color);
padding: 10px;
height: 100%;
box-sizing: border-box;
.scope {
border-radius: 8px;
margin-bottom: 10px;
border: 1px solid var(--el-border-color-light);
height: 200px;
width: 100%;
box-sizing: border-box;
cursor: pointer;
.c {
display: flex;
box-sizing: border-box;
padding: 15px;
height: calc(100% - 50px);
position: relative;
.set {
position: absolute;
right: 10px;
top: 10px;
font-size: 18px;
color: var(--el-color-info);
}
.logo {
height: 40px;
width: 40px;
margin-right: 15px;
}
.det {
display: flex;
flex-direction: column;
flex: 1;
.tag {
margin-bottom: 10px;
.el-tag {
margin-right: 5px;
}
}
.title {
display: flex;
align-items: center;
margin-bottom: 5px;
font-size: 14px;
line-height: 1;
font-weight: bold;
}
.desc {
font-size: 12px;
flex: 1;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
color: var(--el-text-color-regular);
}
.link {
display: flex;
align-items: center;
}
.author {
font-size: 12px;
color: var(--el-text-color-secondary);
}
}
}
.f {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
height: 30px;
}
&.is-add {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--el-disabled-bg-color);
border-color: var(--el-disabled-bg-color);
width: 180px;
.el-icon {
font-size: 36px;
color: #666;
}
}
&:not(.is-add):hover {
box-shadow: 0px 0px 10px 1px var(--el-color-info-light-9);
}
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
t="1706786624724"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4208"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="200"
height="200"
>
<path
d="M615.6 123.6h165.5L512 589.7 242.9 123.6H63.5L512 900.4l448.5-776.9z"
p-id="4209"
></path>
<path d="M781.1 123.6H615.6L512 303 408.4 123.6H242.9L512 589.7z" p-id="4210"></path>
</svg>

After

Width:  |  Height:  |  Size: 536 B

View File

@ -6,32 +6,38 @@
</div>
<div class="form">
<el-form :disabled="temp.disabled">
<el-form :disabled="temp.disabled" size="large">
<div class="label required">CRUD</div>
<div class="row">
<cl-select
class="module"
placeholder="请选择模块"
size="large"
v-model="form.module"
:options="module.dirs"
label-key="name"
value-key="name"
allow-create
/>
<el-input
class="name"
v-model="form.name"
placeholder="实体名称,如:收货地址"
/>
<el-input
class="columns"
size="large"
v-model="form.columns"
placeholder="请填写字段,如:姓名、年龄、状态"
/>
</div>
<el-row :gutter="10">
<el-col :lg="6" :xs="24" :sm="12">
<cl-select
class="module"
placeholder="请选择模块"
v-model="form.module"
:options="module.dirs"
label-key="name"
value-key="name"
allow-create
/>
</el-col>
<el-col :lg="6" :xs="24" :sm="12">
<el-input
class="name"
v-model="form.name"
placeholder="实体名称,如:收货地址"
/>
</el-col>
<el-col :lg="12" :xs="24" :sm="24">
<el-input
class="columns"
v-model="form.columns"
placeholder="请填写字段,如:姓名、年龄、状态"
/>
</el-col>
</el-row>
<div class="label">其他你想做的事</div>
@ -75,7 +81,13 @@
<el-button round size="small" @click="copyCode('entity')"
>Copy</el-button
>
<el-button round type="success" size="small" @click="createVue()">
<el-button
round
type="success"
size="small"
:loading="!codes.vue"
@click="createVue()"
>
生成Vue代码
</el-button>
</template>
@ -161,7 +173,7 @@
</div>
</template>
<script lang="tsx" name="helper-ai-code" setup>
<script lang="tsx" setup name="helper-ai-code">
import { onMounted, reactive, watch } from "vue";
import { module, useCool, storage } from "/@/cool";
import { Promotion, Loading, Close, Check } from "@element-plus/icons-vue";
@ -169,10 +181,10 @@ import { ElLoading, ElMessage, ElMessageBox } from "element-plus";
import { debounce, isEmpty } from "lodash-es";
import { useClipboard } from "@vueuse/core";
import { useMenu, useAi, useScroll } from "../hooks";
import { useForm } from "@cool-vue/crud";
import Text2 from "../components/text.vue";
import type { CodeType } from "../types";
import { isDev } from "/@/config";
import { useForm } from "@cool-vue/crud";
import type { CodeType } from "../types";
import Text2 from "../components/text.vue";
const { service, mitt, refs, setRefs } = useCool();
const { copy } = useClipboard();
@ -184,7 +196,7 @@ const Form = useForm();
//
const editor = reactive({
options: {
fontSize: 16
fontSize: 15
}
});
@ -433,7 +445,12 @@ function createFile() {
const createVue = debounce((auto?: boolean) => {
async function next() {
if (codes.entity) {
// ai
await ai.matchType({ columns: temp.data.columns, name: form.name });
//
codes.vue = menu.createVue(temp.data);
stop();
setTimeout(() => {
@ -449,13 +466,35 @@ const createVue = debounce((auto?: boolean) => {
ElMessageBox.confirm("此操作将会重新生成vue代码是否继续", "提示", {
type: "warning"
})
.then(() => {
.then(async () => {
temp.coding = "vue";
codes.vue = "";
await parseEntity();
next();
})
.catch(() => null);
}
}, 300);
//
async function parseEntity() {
await service.base.sys.menu
.parse({
entity: codes.entity,
module: form.module
})
.then((res) => {
temp.data = {
...form,
...res,
router: res.path.replace("/admin", ""),
prefix: res.path,
api: temp.api
};
});
}
//
watch(
() => form,
@ -469,27 +508,16 @@ onMounted(() => {
onMessage(content) {
codes[temp.coding] = content;
},
onComplete() {
if (temp.coding == "entity") {
// controller
service.base.sys.menu
.parse({
entity: codes.entity,
module: form.module
})
.then((res) => {
temp.data = {
...form,
...res,
router: res.path.replace("/admin", ""),
prefix: res.path,
api: temp.api
};
async onComplete() {
switch (temp.coding) {
case "entity":
await parseEntity();
send("controller", temp.data);
break;
send("controller", temp.data);
});
} else if (temp.coding == "controller") {
createVue(true);
case "controller":
createVue(true);
break;
}
}
});
@ -501,8 +529,6 @@ onMounted(() => {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
box-sizing: border-box;
position: relative;
.head {
@ -511,6 +537,7 @@ onMounted(() => {
.container {
width: 1040px;
max-width: 100%;
}
.form {
@ -529,33 +556,8 @@ onMounted(() => {
}
}
.row {
display: flex;
margin-bottom: 30px;
.module {
width: 160px;
}
.columns {
flex: 1;
margin-left: 10px;
}
.name {
width: 200px;
margin-left: 10px;
}
.balance {
font-size: 15px;
margin-left: 10px;
white-space: nowrap;
.el-icon {
margin-left: 10px;
}
}
.el-col {
margin-bottom: 10px;
}
}
@ -577,6 +579,7 @@ onMounted(() => {
font-size: 18px;
font-weight: bold;
flex: 1;
line-height: 1;
}
.el-button {

View File

@ -1,5 +1,5 @@
<template>
<div class="plugin" @dragover="onDragover" @drop="onDrop">
<div class="plugins" @dragover="onDragover" @drop="onDrop">
<el-tabs v-model="tab.active" type="card" @tab-change="tab.onChange">
<el-tab-pane label="已安装插件" name="installed"> </el-tab-pane>
<el-tab-pane label="插件市场" name="shop"> </el-tab-pane>
@ -16,11 +16,17 @@
<img class="logo" :src="'data:image/jpg;base64,' + item.logo" />
<div class="det">
<div class="title">
<div class="tag">
<el-tag size="small" effect="dark">{{ item.keyName }}</el-tag>
{{ item.name || "-" }} <span>{{ item.version }}</span>
<el-tag size="small" effect="dark" type="success"
>v{{ item.version }}</el-tag
>
</div>
<p class="title">
{{ item.name || "未知" }}
</p>
<p class="desc">{{ item.description || "暂无描述" }}</p>
<div class="author">
@ -72,7 +78,7 @@
</div>
</template>
<script lang="ts" setup name="helper-plugin">
<script lang="ts" setup name="helper-plugins-serve">
import { onActivated, reactive, ref, nextTick } from "vue";
import { useCool } from "/@/cool";
import { ElMessage, ElMessageBox } from "element-plus";
@ -272,112 +278,7 @@ onActivated(() => {
</script>
<style lang="scss" scoped>
.plugin {
overflow-x: hidden;
background-color: var(--el-bg-color);
padding: 10px;
height: 100%;
box-sizing: border-box;
.scope {
border-radius: 8px;
margin-bottom: 10px;
border: 1px solid var(--el-border-color-light);
height: 180px;
width: 100%;
box-sizing: border-box;
cursor: pointer;
.c {
display: flex;
box-sizing: border-box;
padding: 15px;
height: 130px;
position: relative;
.set {
position: absolute;
right: 10px;
top: 10px;
font-size: 18px;
color: var(--el-color-info);
}
.logo {
height: 40px;
width: 40px;
margin-right: 15px;
}
.det {
display: flex;
flex-direction: column;
flex: 1;
.title {
display: flex;
align-items: center;
margin-bottom: 10px;
font-size: 14px;
.el-tag {
margin-right: 5px;
}
.version {
margin-left: 10px;
}
}
.desc {
font-size: 12px;
color: #666;
flex: 1;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.link {
display: flex;
align-items: center;
}
.author {
font-size: 12px;
color: #999;
}
}
}
.f {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
height: 30px;
}
&.is-add {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--el-disabled-bg-color);
border-color: var(--el-disabled-bg-color);
width: 180px;
.el-icon {
font-size: 36px;
color: #666;
}
}
&:not(.is-add):hover {
box-shadow: 0px 0px 10px 1px var(--el-color-info-light-9);
}
}
}
@import "../../static/index.scss";
.info-header {
display: flex;

View File

@ -0,0 +1,133 @@
<template>
<div class="plugins">
<el-tabs v-model="tab.active" type="card" @tab-change="tab.onChange">
<el-tab-pane label="已安装插件" name="installed"> </el-tab-pane>
<el-tab-pane label="插件市场" name="shop"> </el-tab-pane>
</el-tabs>
<el-row :gutter="10">
<el-col v-for="(item, index) in list" :key="index" :xs="24" :sm="12" :md="8" :lg="6">
<div class="scope">
<div class="c">
<img class="logo" :src="item.logo" />
<div class="det">
<div class="tag">
<el-tag size="small" effect="dark">{{ item.name }}</el-tag>
<el-tag size="small" effect="dark" type="success"
>v{{ item.version || "1.0.0" }}</el-tag
>
</div>
<p class="title">
{{ item.label || "未知" }}
</p>
<p class="desc">{{ item.description || "暂无描述" }}</p>
<div class="author">
<span>{{ item.author || "Ta" }}</span>
<span>{{ item.updateTime || "2024-01-01" }}</span>
</div>
</div>
</div>
<div class="f">
<cl-flex1 />
<el-button
round
@click="det.open(item)"
v-if="item.demo && !isEmpty(item.demo)"
>示例</el-button
>
</div>
</div>
</el-col>
</el-row>
<cl-dialog v-model="det.visible" :title="det.title" width="60%">
<el-tabs v-model="det.active" type="card" @tab-change="tab.onChange">
<el-tab-pane
v-for="(item, index) in det.tabs"
:key="index"
:label="item.name"
:name="index"
>
<component :is="item.component" />
</el-tab-pane>
</el-tabs>
</cl-dialog>
</div>
</template>
<script lang="ts" setup name="helper-plugins-vue">
import { reactive, nextTick, markRaw } from "vue";
import { module, useCool } from "/@/cool";
import { isEmpty, isFunction, isString } from "lodash-es";
const { router } = useCool();
//
const tab = reactive({
active: "installed",
onChange(val: string) {
if (val == "shop") {
nextTick(() => {
tab.active = "installed";
window.open("https://cool-js.com/");
});
}
}
});
//
const list = module.list
.filter((e) => e.type == "plugins")
.map((e) => {
if (e.author == "COOL") {
e.logo = "https://cool-js.com/logo.png";
}
return {
...e,
children: e.views
};
});
//
const det = reactive({
visible: false,
title: "",
active: 0,
tabs: [] as any[],
async open(item: any) {
det.active = 0;
if (isString(item.demo)) {
router.push(item.demo);
} else {
det.visible = true;
det.title = item.label;
det.tabs = await Promise.all(
(item.demo || []).map(async (e: any) => {
if (e) {
const c = await (isFunction(e.component) ? e.component() : e.component);
return {
...e,
component: markRaw(c.default)
};
}
})
);
}
}
});
</script>
<style lang="scss" scoped>
@import "../../static/index.scss";
</style>

View File

@ -18,6 +18,7 @@
width="1070px"
padding="0"
keep-alive
:scrollbar="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
>

View File

@ -91,7 +91,7 @@
import { onActivated, ref } from "vue";
import { useBrowser, useCool } from "/@/cool";
import { VideoPlay, VideoPause, Plus, Tickets, Delete } from "@element-plus/icons-vue";
import { ContextMenu, setFocus, useForm } from "@cool-vue/crud";
import { ContextMenu, useForm } from "@cool-vue/crud";
import { ElMessage, ElMessageBox } from "element-plus";
import TaskLogs from "../components/logs.vue";
@ -179,135 +179,132 @@ async function edit(item?: Eps.TaskInfoEntity) {
return false;
}
Form.value?.open(
{
title: "编辑计划任务",
width: "600px",
props: {
labelWidth: "80px"
Form.value?.open({
title: "编辑计划任务",
width: "600px",
props: {
labelWidth: "80px"
},
items: [
{
label: "名称",
prop: "name",
component: {
name: "el-input",
props: {
placeholder: "请输入名称"
}
},
required: true
},
items: [
{
label: "名称",
prop: "name",
component: {
name: "el-input",
props: {
placeholder: "请输入名称"
}
},
required: true
},
{
label: "类型",
prop: "taskType",
value: 0,
component: {
name: "el-radio-group",
options: [
{
label: "cron",
value: 0
},
{
label: "时间间隔",
value: 1
}
]
},
required: true
},
{
label: "cron",
prop: "cron",
hidden: ({ scope }) => scope.taskType == 1,
component: {
name: "el-input",
props: {
placeholder: "* * * * * *"
}
},
required: true
},
{
label: "间隔(秒)",
prop: "every",
hidden: ({ scope }) => scope.taskType == 0,
hook: {
bind(value) {
return value / 1000;
{
label: "类型",
prop: "taskType",
value: 0,
component: {
name: "el-radio-group",
options: [
{
label: "cron",
value: 0
},
submit(value) {
return value * 1000;
{
label: "时间间隔",
value: 1
}
},
component: {
name: "el-input-number",
props: {
min: 1,
max: 100000000
}
},
required: true
]
},
{
label: "service",
prop: "service",
component: {
name: "el-input",
props: {
placeholder: "taskDemoService.test([1, 2])"
}
required: true
},
{
label: "cron",
prop: "cron",
hidden: ({ scope }) => scope.taskType == 1,
component: {
name: "el-input",
props: {
placeholder: "* * * * * *"
}
},
{
label: "开始时间",
prop: "startDate",
hidden: ({ scope }) => scope.taskType == 1,
component: {
name: "el-date-picker",
props: {
type: "datetime",
"value-format": "YYYY-MM-DD HH:mm:ss"
}
required: true
},
{
label: "间隔(秒)",
prop: "every",
hidden: ({ scope }) => scope.taskType == 0,
hook: {
bind(value) {
return value / 1000;
},
submit(value) {
return value * 1000;
}
},
{
label: "备注",
prop: "remark",
component: {
name: "el-input",
props: {
type: "textarea",
rows: 3
}
component: {
name: "el-input-number",
props: {
min: 1,
max: 100000000
}
},
required: true
},
{
label: "service",
prop: "service",
component: {
name: "el-input",
props: {
placeholder: "taskDemoService.test([1, 2])"
}
}
],
form: {
...item
},
on: {
submit: (data, { close, done }) => {
if (!data.limit) {
data.limit = null;
{
label: "开始时间",
prop: "startDate",
hidden: ({ scope }) => scope.taskType == 1,
component: {
name: "el-date-picker",
props: {
type: "datetime",
"value-format": "YYYY-MM-DD HH:mm:ss"
}
}
},
{
label: "备注",
prop: "remark",
component: {
name: "el-input",
props: {
type: "textarea",
rows: 3
}
service.task.info[item?.id ? "update" : "add"](data)
.then(() => {
refresh();
ElMessage.success("保存成功");
close();
})
.catch((err) => {
ElMessage.error(err.message);
done();
});
}
}
],
form: {
...item
},
[setFocus()]
);
on: {
submit: (data, { close, done }) => {
if (!data.limit) {
data.limit = null;
}
service.task.info[item?.id ? "update" : "add"](data)
.then(() => {
refresh();
ElMessage.success("保存成功");
close();
})
.catch((err) => {
ElMessage.error(err.message);
done();
});
}
}
});
}
//

View File

@ -13,6 +13,10 @@
<cl-filter label="性别">
<cl-select :options="options.gender" prop="gender" :width="120" />
</cl-filter>
<!-- 状态 -->
<cl-filter label="状态">
<cl-select :options="options.status" prop="status" :width="120" />
</cl-filter>
<cl-flex1 />
<!-- 关键字搜索 -->
<cl-search-key placeholder="搜索昵称、手机号" />
@ -78,12 +82,19 @@ const options = reactive({
],
status: [
{
label: "启用",
value: 1
label: "禁用",
value: 0,
type: "danger"
},
{
label: "禁用",
value: 0
label: "正常",
value: 1,
type: "success"
},
{
label: "已注销",
value: 2,
type: "warning"
}
]
});
@ -128,10 +139,8 @@ const Table = useTable({
{
label: "状态",
prop: "status",
minWidth: 100,
component: {
name: "cl-switch"
}
minWidth: 120,
dict: options.status
},
{
label: "创建时间",

View File

@ -1,15 +1,22 @@
import { Merge, ModuleConfig } from "/@/cool";
// npm
// import Crud, { locale } from "@cool-vue/crud";
// import "@cool-vue/crud/dist/index.css";
import Crud, { locale, setFocus } from "@cool-vue/crud";
import "@cool-vue/crud/dist/index.css";
// 调试、自定义crud
import Crud, { locale } from "../../../packages/crud/src";
import "../../../packages/crud/src/static/index.scss";
// import Crud, { locale } from "../../../packages/crud/src";
// import "../../../packages/crud/src/static/index.scss";
export default (): Merge<ModuleConfig, CrudOptions> => {
return {
label: "CRUD",
description: "快速增删改查及一系列辅助组件",
author: "COOL",
version: "7.1.11",
updateTime: "2024-02-01",
demo: "/demo/crud",
// 组件全注册
components: Object.values(import.meta.glob("./components/**/*.{vue,tsx}")),
@ -17,7 +24,16 @@ export default (): Merge<ModuleConfig, CrudOptions> => {
options: {
style: {
table: {
// 插件列表
plugins: []
// contextMenu: [], 是否关闭表格右键菜单
},
form: {
// 插件列表
plugins: [
// 自动聚焦插件
setFocus()
]
}
},
dict: {

View File

@ -16,6 +16,18 @@ registerFormHook("pca", (value, { method, form, prop }) => {
export default (): ModuleConfig => {
return {
label: "省市区选择器",
description: "快速增删改查及一系列辅助组件",
author: "COOL",
version: "1.0.0",
updateTime: "2024-02-01",
demo: [
{
name: "基础用法",
component: () => import("./demo/base.vue")
}
],
components: [
// 省市区选择 https://github.com/modood/Administrative-divisions-of-China
() => import("./components/index")

View File

@ -0,0 +1,10 @@
<template>
<cl-distpicker v-model="value" />
<span :style="{ marginLeft: '20px' }">{{ value }}</span>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const value = ref([]);
</script>

View File

@ -26,7 +26,7 @@ const props = defineProps({
options: Object,
height: {
type: [String, Number],
default: 400
default: 500
},
autofocus: {
type: Boolean,
@ -86,7 +86,7 @@ function create() {
theme: "default",
language: props.language,
minimap: {
enabled: true
enabled: false
},
automaticLayout: true,
scrollbar: {

View File

@ -2,9 +2,21 @@ import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
label: "代码编辑器",
description: "基于 monaco 封装的代码编辑器",
author: "COOL",
version: "1.0.0",
updateTime: "2024-02-01",
demo: [
{
name: "基础用法",
component: () => import("./demo/base.vue")
}
],
components: [
// 代码编辑器 https://www.npmjs.com/package/monaco-editor
() => import("./components/index.vue")
() => import("./components/monaco.vue")
]
};
};

View File

@ -0,0 +1,28 @@
<template>
<cl-editor-monaco v-model="value" />
</template>
<script lang="ts" setup>
import { ref } from "vue";
const value = ref([
{
label: "A",
children: [
{
label: "B",
children: [
{
label: "C",
children: [
{
label: "D"
}
]
}
]
}
]
}
]);
</script>

View File

@ -2,6 +2,18 @@ import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
label: "编辑器内容预览",
description: "基于 monaco、wang 等编辑器的内容预览组件",
author: "COOL",
version: "1.0.0",
updateTime: "2024-02-01",
demo: [
{
name: "基础用法",
component: () => import("./demo/base.vue")
}
],
components: [() => import("./components/preview.vue")]
};
};

View File

@ -0,0 +1,29 @@
<template>
<cl-editor-preview v-model="wang" name="wang" text="查看内容" />
<cl-editor-preview
v-model="monaco"
name="monaco"
:props="{
language: 'typescript'
}"
text="查看代码"
/>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const wang = ref(
'<p><span style="font-size: 22px;"><em>富文本编</em></span><span style="color: rgb(216, 68, 147); font-size: 22px;"><em>辑器</em></span></p>'
);
const monaco = ref(`class User {
main() {
console.log('Name', 'COOL')
}
}
const user = new User();
user.main();
`);
</script>

View File

@ -68,7 +68,7 @@ export default defineComponent({
//
height: {
type: [String, Number],
default: 400
default: 500
},
//
disabled: Boolean,

View File

@ -2,6 +2,18 @@ import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
label: "富文本编辑器",
description: "基于 wangEditor 封装的富文本编辑器",
author: "COOL",
version: "1.0.0",
updateTime: "2024-02-01",
demo: [
{
name: "基础用法",
component: () => import("./demo/base.vue")
}
],
components: [() => import("./components/wang.vue")]
};
};

View File

@ -0,0 +1,11 @@
<template>
<cl-editor-wang v-model="value" />
</template>
<script lang="ts" setup>
import { ref } from "vue";
const value = ref(
'<p><span style="font-size: 22px;"><em>富文本编</em></span><span style="color: rgb(216, 68, 147); font-size: 22px;"><em>辑器</em></span></p>'
);
</script>

View File

@ -141,7 +141,7 @@ export default defineComponent({
function open() {
if (!props.columns) {
return console.error("columns is required");
return console.error("<cl-export-btn /> columns is required");
}
// 表格列

View File

@ -3,7 +3,7 @@
<cl-form ref="Form">
<template #slot-upload>
<div class="upload">
<div class="upload" v-if="!upload.filename">
<div class="tips">
<span>{{ tips }}</span>
<el-button type="primary" text bg @click="download">下载模版</el-button>
@ -21,9 +21,95 @@
:size="[220, '100%']"
/>
</div>
</div>
</template>
<div class="progress" v-if="progress > 0">
<el-progress type="dashboard" :percentage="progress" />
<template #slot-list>
<div class="data-table" v-if="list.length">
<div class="head">
<el-button type="success" @click="clear">重新上传</el-button>
<el-button
type="danger"
:disabled="table.selection.length == 0"
@click="table.del()"
>批量删除</el-button
>
</div>
<div class="cl-table">
<el-table
border
small
:data="list"
max-height="600px"
@selection-change="table.onSelectionChange"
@row-click="
(row) => {
row._edit = true;
}
"
>
<el-table-column
type="selection"
width="60px"
align="center"
fixed="left"
/>
<el-table-column
label="序号"
type="index"
width="80px"
align="center"
fixed="left"
:index="table.onIndex"
/>
<el-table-column
v-for="item in table.header"
:key="item"
:prop="item"
:label="item"
min-width="160px"
align="center"
>
<template #default="scope">
<span v-if="!scope.row._edit">{{ scope.row[item] }}</span>
<template v-else>
<el-input
type="textarea"
v-model="scope.row[item]"
clearable
:placeholder="item"
/>
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="100px" align="center" fixed="right">
<template #default="scope">
<el-button
text
bg
type="danger"
@click.stop="table.del(scope.$index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination">
<el-pagination
background
layout="total, prev, pager, next"
:total="upload.list.length"
:page-size="pagination.size"
v-model:current-page="pagination.page"
@current-change="pagination.onCurrentChange"
/>
</div>
</div>
</template>
@ -33,7 +119,7 @@
<script lang="ts" setup name="cl-import-btn">
import { useForm } from "@cool-vue/crud";
import { ElMessage } from "element-plus";
import { reactive, ref, type PropType } from "vue";
import { reactive, type PropType, computed } from "vue";
import * as XLSX from "xlsx";
import chardet from "chardet";
import { extname } from "/@/cool/utils";
@ -75,17 +161,65 @@ const Form = useForm();
//
const upload = reactive({
filename: "",
data: [] as any[]
file: null as File | null,
list: [] as any[]
});
//
const progress = ref(0);
//
const pagination = reactive({
size: 20,
page: 1,
onCurrentChange(page: number) {
pagination.page = page;
}
});
//
const table = reactive({
//
header: [] as string[],
//
selection: [] as any[],
//
del(index?: number) {
if (index !== undefined) {
upload.list.splice(index, 1);
} else {
upload.list = upload.list.filter((a) => {
return !table.selection.includes(a._index);
});
}
},
//
onIndex(index: number) {
return index + 1 + (pagination.page - 1) * pagination.size;
},
//
onSelectionChange(arr: any[]) {
table.selection = arr.map((e) => e._index);
}
});
//
const list = computed(() => {
return upload.list.filter((_, i) => {
return (
i >= (pagination.page - 1) * pagination.size && i < pagination.page * pagination.size
);
});
});
//
function clear() {
progress.value = 0;
upload.filename = "";
upload.data = [];
upload.file = null;
upload.list = [];
table.header = [];
table.selection = [];
}
//
@ -94,49 +228,46 @@ function open() {
Form.value?.open({
title: "导入",
width: "800px",
width: computed(() => (upload.filename ? "80%" : "800px")),
dialog: {
"close-on-press-escape": false
},
items: [
...(props.onConfig ? props.onConfig(Form) : []),
{
label: "",
prop: "file",
props: {
labelWidth: "0"
},
component: {
name: "slot-upload"
}
},
{
component: {
name: "slot-list"
}
}
],
op: {
saveButtonText: "提交"
},
on: {
submit(data, { done, close }) {
submit(_, { done, close }) {
if (!upload.filename) {
done();
return ElMessage.error("请选择文件");
}
if (props.onSubmit) {
props.onSubmit(
{
...data,
list: upload.data
},
{ done, close, setProgress }
);
props.onSubmit(upload, { done, close });
} else {
console.error("cl-import-btn 未配置 onSubmit");
ElMessage.error("<cl-import-btn /> 未配置 onSubmit 参数");
done();
}
}
}
});
}
//
function onUpload(raw: File, _: any, { next }: any) {
const reader = new FileReader();
const ext = extname(raw.name);
@ -161,8 +292,26 @@ function onUpload(raw: File, _: any, { next }: any) {
}
}
upload.data = json;
upload.list = json.map((e, i) => {
return {
...e,
_index: i
};
});
upload.filename = raw.name;
upload.file = raw;
const sheet = workbook.Sheets[Object.keys(workbook.Sheets)[0]];
for (let i in sheet) {
if (i[0] === "!") continue;
const row = i.match(/[0-9]+/)?.[0];
if (row == "1") {
table.header.push(sheet[i].v);
}
}
emit("change", json);
};
@ -186,15 +335,9 @@ function download() {
link.click();
}
//
function setProgress(val: string) {
progress.value = parseInt(val);
}
defineExpose({
open,
clear,
setProgress,
Form
});
</script>
@ -229,9 +372,15 @@ defineExpose({
}
}
.progress {
margin-top: 20px;
display: flex;
justify-content: center;
.data-table {
.head {
margin-bottom: 10px;
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
}
</style>

View File

@ -2,6 +2,18 @@ import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
label: "Excel",
description: "表格的导入、导出组件",
author: "COOL",
version: "1.0.0",
updateTime: "2024-02-01",
demo: [
{
name: "基础用法",
component: () => import("./demo/base.vue")
}
],
components: [
() => import("./components/import-btn.vue"),
() => import("./components/export-btn")

View File

@ -0,0 +1,80 @@
<template>
<cl-crud ref="Crud">
<cl-row>
<!-- 刷新按钮 -->
<cl-refresh-btn />
<cl-flex1 />
<!-- 导入 -->
<cl-import-btn template="/用户导入模版.xlsx" :on-submit="onImpSubmit" />
<!-- 导出 -->
<cl-export-btn :columns="Table?.columns" />
</cl-row>
<cl-row>
<!-- 表格 -->
<cl-table ref="Table" :auto-height="false" />
</cl-row>
<cl-row>
<cl-flex1 />
<!-- 分页 -->
<cl-pagination />
</cl-row>
</cl-crud>
</template>
<script lang="tsx" setup>
import { useCrud, useTable } from "@cool-vue/crud";
import { useCool } from "/@/cool";
import { useDict } from "/$/dict";
import { ElMessage } from "element-plus";
const { service } = useCool();
const { dict } = useDict();
// crud
const Crud = useCrud(
{
service: service.test
},
(app) => {
app.refresh({ size: 10 });
}
);
//
const Table = useTable({
columns: [
{
label: "姓名",
prop: "name"
},
{
label: "手机号",
prop: "phone"
},
{
label: "账号",
prop: "account"
},
{
label: "存款(元)",
prop: "wages"
},
{
label: "工作",
prop: "occupation",
dict: dict.get("occupation")
}
]
});
function onImpSubmit(data: { list: any[]; file: File }, { done, close }: any) {
close();
ElMessage.success(`已提交${data.list.length}条数据`);
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<div class="file-viewer">
<div class="viewer-image">
<!-- 图片 -->
<el-image-viewer
v-if="img.visible"
@ -8,34 +8,38 @@
teleported
@close="close"
/>
<!-- Wps -->
<wps :ref="setRefs('wps')" />
</div>
<!-- 文档 -->
<cl-dialog v-model="doc.visible" title="文档预览" height="70vh" width="80%" :scrollbar="false">
<div class="viewer-doc" v-loading="doc.loading">
<iframe :src="doc.url" :ref="setRefs('docIframe')" />
</div>
</cl-dialog>
</template>
<script lang="ts" setup name="file-viewer">
import { reactive, toRaw } from "vue";
import { has } from "lodash-es";
import { useCool } from "/@/cool";
import Wps from "./wps.vue";
import { reactive, nextTick } from "vue";
import { getType } from "../../utils";
import type { Upload } from "../../types";
enum WpsType {
word = "Writer",
excel = "Spreadsheet",
ppt = "Presentation",
pdf = "Pdf"
}
import { useCool } from "/@/cool";
const { refs, setRefs } = useCool();
//
const img = reactive({
visible: false,
url: ""
});
//
const doc = reactive({
visible: false,
loading: false,
url: ""
});
//
function open(item: Upload.Item) {
if (item?.type) {
//
@ -44,7 +48,7 @@ function open(item: Upload.Item) {
//
const type = getType(url);
//
//
if (type == "image") {
img.visible = true;
img.url = url;
@ -52,16 +56,26 @@ function open(item: Upload.Item) {
return true;
}
// WPS
if (has(WpsType, type)) {
// @ts-ignore
return refs.wps?.open(Object.assign(toRaw(item), { officeType: WpsType[item.type] }));
//
if (["word", "excel", "ppt", "pdf"].includes(type)) {
doc.visible = true;
doc.loading = true;
doc.url = `https://view.officeapps.live.com/op/view.aspx?src=${decodeURIComponent(url)}`;
nextTick(() => {
refs.docIframe.onload = () => {
doc.loading = false;
};
});
return true;
}
window.open(item.url);
}
}
//
function close() {
img.visible = false;
}
@ -72,7 +86,18 @@ defineExpose({
</script>
<style lang="scss" scoped>
.file-viewer {
.viewer-image {
position: absolute;
}
.viewer-doc {
height: 100%;
width: 100%;
iframe {
border: 0;
height: 100%;
width: 100%;
}
}
</style>

View File

@ -1,87 +0,0 @@
<template>
<cl-dialog v-model="visible" width="80%" @closed="onClosed">
<div class="file-wps"></div>
</cl-dialog>
</template>
<script lang="ts" setup>
/**
* wps预览组件
* @param {string} officeType Writer | Spreadsheet | Presentation | Pdf
* @param {string} fileId 文件 id用户自定义
* @param {string} mount 挂载节点
* @param {string} token 授权
*/
import { ElMessage } from "element-plus";
import WebOfficeSDK, { IAppConfig } from "ts-wps";
import { ref } from "vue";
import { useBase } from "/$/base";
import { useCool } from "/@/cool";
const { service } = useCool();
const { user } = useBase();
// ID
const appId = ref("");
//
const visible = ref(false);
// ID
async function getAppId() {
if (!appId.value) {
await service.space.info.getConfig().then((res) => {
appId.value = res.appId;
});
}
}
let wps: any = null;
/**
* 打开wps预览
* @param data WebOfficeSDK.IAppConfig
*/
async function open(data: IAppConfig) {
visible.value = true;
await getAppId();
if (!appId.value) {
return ElMessage.error("请先配置WPS的应用ID");
}
wps = WebOfficeSDK.init({
officeType: WebOfficeSDK.OfficeType[data.officeType],
appId: appId.value,
fileId: data.fileId,
mount: document.querySelector(".file-wps") as HTMLElement,
fileToken: {
token: user.token,
timeout: 10 * 60 * 1000
}
});
wps.ready();
}
function onClosed() {
wps.destroy();
}
defineExpose({
open
});
</script>
<style lang="scss">
.file-wps {
height: 70vh;
iframe {
height: calc(100% - 5px) !important;
width: 100% !important;
}
}
</style>

View File

@ -1,5 +1,42 @@
export default () => {
return {
label: "文件上传",
description: "基于 el-upload 封装的文件上传组件",
author: "COOL",
version: "1.0.0",
updateTime: "2024-02-01",
demo: [
{
name: "基础用法",
component: () => import("./demo/base.vue")
},
{
name: "多图上传",
component: () => import("./demo/multiple.vue")
},
{
name: "文件上传",
component: () => import("./demo/file.vue")
},
{
name: "可拖拽",
component: () => import("./demo/drag.vue")
},
{
name: "自定义内容",
component: () => import("./demo/custom.vue")
},
{
name: "上传校验",
component: () => import("./demo/check.vue")
},
{
name: "文件空间",
component: () => import("./demo/space.vue")
}
],
// 参数
options: {
// 尺寸
size: 120,

View File

@ -0,0 +1,9 @@
<template>
<cl-upload v-model="value" />
</template>
<script lang="ts" setup>
import { ref } from "vue";
const value = ref("");
</script>

View File

@ -0,0 +1,17 @@
<template>
<cl-upload :before-upload="onBeforeUpload" />
</template>
<script lang="ts" setup>
import { ElMessage } from "element-plus";
function onBeforeUpload(file: any) {
return new Promise((resolve) => {
if (file.size > 100000) {
ElMessage.warning("文件不能大于100k");
} else {
resolve(true);
}
});
}
</script>

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