发布 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 { join } from "path";
import { Entity, DistPath } from "./config"; import { Entity, DistPath } from "./config";
import axios from "axios"; 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 { createWriteStream } from "fs";
import prettier from "prettier"; import prettier from "prettier";
import { proxy } from "../../../src/config/proxy"; import { proxy } from "../../../src/config/proxy";

View File

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

View File

@ -10,7 +10,7 @@ Vue.js 是一套用于构建用户界面的渐进式框架。与其它大型框
<img src='https://cool-js.com/assets/home.1706ac70.png' /> <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' /> <img src='https://cool-js.com/assets/ai-code2.9a122008.png' />

View File

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

View File

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

View File

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

View File

@ -64,7 +64,7 @@ export function useAction({
break; break;
} }
} else { } 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 { Ref, WatchStopHandle, getCurrentInstance, watch } from "vue";
import { useConfig } from "../../../hooks";
export function usePlugins({ visible }: { visible: Ref<boolean> }) { export function usePlugins({ visible }: { visible: Ref<boolean> }) {
const that: any = getCurrentInstance(); const that: any = getCurrentInstance();
const { style } = useConfig();
interface Event { interface Event {
onOpen: (() => void)[]; onOpen: (() => void)[];
onClose: (() => void)[]; onClose: (() => void)[];
onSubmit: ((data: obj) => obj)[]; onSubmit: ((data: obj) => Promise<obj> | obj)[];
[key: string]: any; [key: string]: any;
} }
@ -21,47 +23,46 @@ export function usePlugins({ visible }: { visible: Ref<boolean> }) {
let timer: WatchStopHandle | null = null; let timer: WatchStopHandle | null = null;
// 插件创建 // 插件创建
function create(plugins?: ClForm.Plugin[]) { function create(plugins: ClForm.Plugin[] = []) {
for (const i in ev) { for (const i in ev) {
ev[i] = []; ev[i] = [];
} }
// 停止监听
if (timer) { if (timer) {
timer(); timer();
} }
if (plugins) { // 执行
plugins.forEach((p) => { [...(style.form.plugins || []), ...plugins].forEach((p) => {
p({ const d: any = {
exposed: that.exposed, exposed: that.exposed
onOpen(cb: any) { };
ev.onOpen.push(cb);
},
onClose(cb: any) {
ev.onClose.push(cb);
},
onSubmit(cb: any) {
ev.onSubmit.push(cb);
}
});
});
timer = watch( for (const i in ev) {
visible, d[i] = (cb: any) => {
(val) => { ev[i].push(cb);
if (val) { };
setTimeout(() => { }
ev.onOpen.forEach((e) => e());
}, 10); p(d);
} else { });
ev.onClose.forEach((e) => e());
} timer = watch(
}, visible,
{ (val) => {
immediate: true 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, scope: form,
render: "slot", render: "slot",
slots slots
}) })
: e.label; : e.label;
}, },
default() { default() {
@ -555,14 +555,15 @@ export default defineComponent({
return renderNode(e, { return renderNode(e, {
scope: form, scope: form,
slots, slots,
custom({ scope }) { custom() {
return ( return (
<el-button <el-button
text text
type={e.type} type={e.type}
bg bg
{...e.props}
onClick={() => { onClick={() => {
e.onClick({ scope }); e.onClick({ scope: form });
}}> }}>
{e.label} {e.label}
</el-button> </el-button>
@ -584,6 +585,7 @@ export default defineComponent({
} }
expose({ expose({
refs,
Form, Form,
visible, visible,
saving, 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 useTable
} from "./helper"; } from "./helper";
import { useCore, useProxy, useElApi, useConfig } from "../../hooks"; import { useCore, useProxy, useElApi, useConfig } from "../../hooks";
import { usePlugins } from "./helper/plugins";
export default defineComponent({ export default defineComponent({
name: "cl-table", name: "cl-table",
@ -54,6 +55,7 @@ export default defineComponent({
const { crud } = useCore(); const { crud } = useCore();
const { style } = useConfig(); const { style } = useConfig();
const { Table, config } = useTable(props); const { Table, config } = useTable(props);
const plugin = usePlugins();
// 排序 // 排序
const Sort = useSort({ config, emit, Table }); const Sort = useSort({ config, emit, Table });
@ -111,6 +113,7 @@ export default defineComponent({
useProxy(ctx); useProxy(ctx);
expose(ctx); expose(ctx);
plugin.create(config.plugins);
return () => { return () => {
const { renderColumn, renderAppend, renderEmpty } = useRender(); const { renderColumn, renderAppend, renderEmpty } = useRender();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
"target": "esnext", "target": "esnext",
"module": "esnext", "module": "esnext",
"strict": true, "strict": true,
"noImplicitAny": false,
"jsx": "preserve", "jsx": "preserve",
"importHelpers": true, "importHelpers": true,
"moduleResolution": "node", "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; 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; export default _default;

View File

@ -15,7 +15,7 @@ declare const _default: import("vue").DefineComponent<{
default: () => string[]; default: () => string[];
}; };
onSearch: FunctionConstructor; 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: { items: {
type: PropType<ClForm.Item<any>[]>; type: PropType<ClForm.Item<any>[]>;
default: () => never[]; default: () => never[];

View File

@ -9,7 +9,7 @@ declare const ClContextMenu: import("vue").DefineComponent<{
type: ObjectConstructor; type: ObjectConstructor;
default: () => {}; 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; show: BooleanConstructor;
options: { options: {
type: ObjectConstructor; type: ObjectConstructor;

View File

@ -5,7 +5,7 @@ declare const _default: import("vue").DefineComponent<{
type: StringConstructor; type: StringConstructor;
default: string; 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; name: StringConstructor;
border: BooleanConstructor; border: BooleanConstructor;
padding: { padding: {

View File

@ -31,7 +31,7 @@ declare const _default: import("vue").DefineComponent<{
}; };
}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, { }, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any; [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: { modelValue: {
type: BooleanConstructor; type: BooleanConstructor;
default: boolean; default: boolean;

View File

@ -1,6 +1,6 @@
declare const _default: import("vue").DefineComponent<{ declare const _default: import("vue").DefineComponent<{
title: StringConstructor; 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; title: StringConstructor;
}>>, {}, {}>; }>>, {}, {}>;
export default _default; export default _default;

View File

@ -1,6 +1,6 @@
declare const _default: import("vue").DefineComponent<{ declare const _default: import("vue").DefineComponent<{
label: StringConstructor; 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; label: StringConstructor;
}>>, {}, {}>; }>>, {}, {}>;
export default _default; 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; export default _default;

View File

@ -8,7 +8,7 @@ declare const _default: import("vue").DefineComponent<{
type: BooleanConstructor; type: BooleanConstructor;
default: boolean; 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; label: StringConstructor;
expand: { expand: {
type: BooleanConstructor; type: BooleanConstructor;

View File

@ -6,21 +6,21 @@ declare const _default: import("vue").DefineComponent<{
default: () => never[]; default: () => never[];
}; };
justify: { justify: {
type: PropType<"center" | "justify" | "left" | "right" | "end" | "start" | "match-parent">; type: PropType<"center" | "justify" | "left" | "right" | "start" | "end" | "match-parent">;
default: string; default: string;
}; };
type: { type: {
type: PropType<"default" | "card">; type: PropType<"default" | "card">;
default: string; 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)[]; modelValue: (NumberConstructor | StringConstructor)[];
labels: { labels: {
type: ArrayConstructor; type: ArrayConstructor;
default: () => never[]; default: () => never[];
}; };
justify: { justify: {
type: PropType<"center" | "justify" | "left" | "right" | "end" | "start" | "match-parent">; type: PropType<"center" | "justify" | "left" | "right" | "start" | "end" | "match-parent">;
default: string; default: string;
}; };
type: { type: {
@ -33,6 +33,6 @@ declare const _default: import("vue").DefineComponent<{
}, { }, {
type: "default" | "card"; type: "default" | "card";
labels: unknown[]; labels: unknown[];
justify: "center" | "justify" | "left" | "right" | "end" | "start" | "match-parent"; justify: "center" | "justify" | "left" | "right" | "start" | "end" | "match-parent";
}, {}>; }, {}>;
export default _default; export default _default;

View File

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

View File

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

View File

@ -1,4 +1,4 @@
declare const _default: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, { declare const _default: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any; [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; 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; 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; export default _default;

View File

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

View File

@ -15,7 +15,7 @@ declare const _default: import("vue").DefineComponent<{
}; };
onLoad: FunctionConstructor; onLoad: FunctionConstructor;
onSearch: 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: { data: {
type: ObjectConstructor; type: ObjectConstructor;
default: () => {}; default: () => {};

View File

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

View File

@ -24,7 +24,7 @@ declare const _default: import("vue").DefineComponent<{
}; };
}, () => false | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, { }, () => false | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
[key: string]: any; [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: { columns: {
type: ArrayConstructor; type: ArrayConstructor;
default: () => never[]; default: () => never[];

View File

@ -13,7 +13,7 @@ declare const _default: import("vue").DefineComponent<{
onClosed: FunctionConstructor; onClosed: FunctionConstructor;
onInfo: FunctionConstructor; onInfo: FunctionConstructor;
onSubmit: 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: { items: {
type: ArrayConstructor; type: ArrayConstructor;
default: () => never[]; default: () => never[];

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
export const proxy = { export const proxy = {
"/dev/": { "/dev/": {
target: "http://127.0.0.1:8001", // target: "http://127.0.0.1:8001",
target: "https://test-admin.cool-js.cloud",
changeOrigin: true, changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/dev/, "") 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 type Merge<A, B> = Omit<A, keyof B> & B;
export declare interface ModuleConfig { export declare interface ModuleConfig {
name?: string;
label?: string;
description?: string;
order?: number; order?: number;
version?: string;
logo?: string;
author?: string;
updateTime?: string;
demo?: { name: string; component: Component }[] | string;
options?: { options?: {
[key: string]: any; [key: string]: any;
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,7 +36,7 @@
</template> </template>
<script lang="ts" name="cloud-func-info" setup> <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 { useCool } from "/@/cool";
import { Status, CodeSnippets } from "../../dict"; import { Status, CodeSnippets } from "../../dict";
import FuncLogs from "../../components/func-logs.vue"; import FuncLogs from "../../components/func-logs.vue";
@ -89,8 +89,7 @@ const Upsert = useUpsert({
...data, ...data,
content content
}); });
}, }
plugins: [setFocus("name")]
}); });
// cl-table // 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> </template>
<script lang="tsx" name="demo-crud" setup> <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 { useDict } from "/$/dict";
import { reactive, ref } from "vue"; import { reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
const v = ref();
// //
const { service, refs, setRefs } = useCool(); const { service, refs, setRefs } = useCool();
@ -315,12 +313,6 @@ const Upsert = useUpsert<Eps.UserInfoEntity>({
} }
], ],
//
plugins: [
//
setFocus("account")
],
// //
onInfo(data, { next, done }) { onInfo(data, { next, done }) {
// info // info

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<template> <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-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="installed"> </el-tab-pane>
<el-tab-pane label="插件市场" name="shop"> </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" /> <img class="logo" :src="'data:image/jpg;base64,' + item.logo" />
<div class="det"> <div class="det">
<div class="title"> <div class="tag">
<el-tag size="small" effect="dark">{{ item.keyName }}</el-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> </div>
<p class="title">
{{ item.name || "未知" }}
</p>
<p class="desc">{{ item.description || "暂无描述" }}</p> <p class="desc">{{ item.description || "暂无描述" }}</p>
<div class="author"> <div class="author">
@ -72,7 +78,7 @@
</div> </div>
</template> </template>
<script lang="ts" setup name="helper-plugin"> <script lang="ts" setup name="helper-plugins-serve">
import { onActivated, reactive, ref, nextTick } from "vue"; import { onActivated, reactive, ref, nextTick } from "vue";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
@ -272,112 +278,7 @@ onActivated(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.plugin { @import "../../static/index.scss";
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);
}
}
}
.info-header { .info-header {
display: flex; 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" width="1070px"
padding="0" padding="0"
keep-alive keep-alive
:scrollbar="false"
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
> >

View File

@ -91,7 +91,7 @@
import { onActivated, ref } from "vue"; import { onActivated, ref } from "vue";
import { useBrowser, useCool } from "/@/cool"; import { useBrowser, useCool } from "/@/cool";
import { VideoPlay, VideoPause, Plus, Tickets, Delete } from "@element-plus/icons-vue"; 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 { ElMessage, ElMessageBox } from "element-plus";
import TaskLogs from "../components/logs.vue"; import TaskLogs from "../components/logs.vue";
@ -179,135 +179,132 @@ async function edit(item?: Eps.TaskInfoEntity) {
return false; return false;
} }
Form.value?.open( Form.value?.open({
{ title: "编辑计划任务",
title: "编辑计划任务", width: "600px",
width: "600px", props: {
props: { labelWidth: "80px"
labelWidth: "80px" },
items: [
{
label: "名称",
prop: "name",
component: {
name: "el-input",
props: {
placeholder: "请输入名称"
}
},
required: true
}, },
items: [ {
{ label: "类型",
label: "名称", prop: "taskType",
prop: "name", value: 0,
component: { component: {
name: "el-input", name: "el-radio-group",
props: { options: [
placeholder: "请输入名称" {
} label: "cron",
}, value: 0
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;
}, },
submit(value) { {
return value * 1000; label: "时间间隔",
value: 1
} }
}, ]
component: {
name: "el-input-number",
props: {
min: 1,
max: 100000000
}
},
required: true
}, },
{ required: true
label: "service", },
prop: "service", {
component: { label: "cron",
name: "el-input", prop: "cron",
props: { hidden: ({ scope }) => scope.taskType == 1,
placeholder: "taskDemoService.test([1, 2])" component: {
} name: "el-input",
props: {
placeholder: "* * * * * *"
} }
}, },
{ required: true
label: "开始时间", },
prop: "startDate", {
hidden: ({ scope }) => scope.taskType == 1, label: "间隔(秒)",
component: { prop: "every",
name: "el-date-picker", hidden: ({ scope }) => scope.taskType == 0,
props: { hook: {
type: "datetime", bind(value) {
"value-format": "YYYY-MM-DD HH:mm:ss" return value / 1000;
} },
submit(value) {
return value * 1000;
} }
}, },
{ component: {
label: "备注", name: "el-input-number",
prop: "remark", props: {
component: { min: 1,
name: "el-input", max: 100000000
props: { }
type: "textarea", },
rows: 3 required: true
} },
{
label: "service",
prop: "service",
component: {
name: "el-input",
props: {
placeholder: "taskDemoService.test([1, 2])"
} }
} }
],
form: {
...item
}, },
on: { {
submit: (data, { close, done }) => { label: "开始时间",
if (!data.limit) { prop: "startDate",
data.limit = null; 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-filter label="性别">
<cl-select :options="options.gender" prop="gender" :width="120" /> <cl-select :options="options.gender" prop="gender" :width="120" />
</cl-filter> </cl-filter>
<!-- 状态 -->
<cl-filter label="状态">
<cl-select :options="options.status" prop="status" :width="120" />
</cl-filter>
<cl-flex1 /> <cl-flex1 />
<!-- 关键字搜索 --> <!-- 关键字搜索 -->
<cl-search-key placeholder="搜索昵称、手机号" /> <cl-search-key placeholder="搜索昵称、手机号" />
@ -78,12 +82,19 @@ const options = reactive({
], ],
status: [ status: [
{ {
label: "启用", label: "禁用",
value: 1 value: 0,
type: "danger"
}, },
{ {
label: "禁用", label: "正常",
value: 0 value: 1,
type: "success"
},
{
label: "已注销",
value: 2,
type: "warning"
} }
] ]
}); });
@ -128,10 +139,8 @@ const Table = useTable({
{ {
label: "状态", label: "状态",
prop: "status", prop: "status",
minWidth: 100, minWidth: 120,
component: { dict: options.status
name: "cl-switch"
}
}, },
{ {
label: "创建时间", label: "创建时间",

View File

@ -1,15 +1,22 @@
import { Merge, ModuleConfig } from "/@/cool"; import { Merge, ModuleConfig } from "/@/cool";
// npm // npm
// import Crud, { locale } from "@cool-vue/crud"; import Crud, { locale, setFocus } from "@cool-vue/crud";
// import "@cool-vue/crud/dist/index.css"; import "@cool-vue/crud/dist/index.css";
// 调试、自定义crud // 调试、自定义crud
import Crud, { locale } from "../../../packages/crud/src"; // import Crud, { locale } from "../../../packages/crud/src";
import "../../../packages/crud/src/static/index.scss"; // import "../../../packages/crud/src/static/index.scss";
export default (): Merge<ModuleConfig, CrudOptions> => { export default (): Merge<ModuleConfig, CrudOptions> => {
return { 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}")), components: Object.values(import.meta.glob("./components/**/*.{vue,tsx}")),
@ -17,7 +24,16 @@ export default (): Merge<ModuleConfig, CrudOptions> => {
options: { options: {
style: { style: {
table: { table: {
// 插件列表
plugins: []
// contextMenu: [], 是否关闭表格右键菜单 // contextMenu: [], 是否关闭表格右键菜单
},
form: {
// 插件列表
plugins: [
// 自动聚焦插件
setFocus()
]
} }
}, },
dict: { dict: {

View File

@ -16,6 +16,18 @@ registerFormHook("pca", (value, { method, form, prop }) => {
export default (): ModuleConfig => { export default (): ModuleConfig => {
return { return {
label: "省市区选择器",
description: "快速增删改查及一系列辅助组件",
author: "COOL",
version: "1.0.0",
updateTime: "2024-02-01",
demo: [
{
name: "基础用法",
component: () => import("./demo/base.vue")
}
],
components: [ components: [
// 省市区选择 https://github.com/modood/Administrative-divisions-of-China // 省市区选择 https://github.com/modood/Administrative-divisions-of-China
() => import("./components/index") () => 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, options: Object,
height: { height: {
type: [String, Number], type: [String, Number],
default: 400 default: 500
}, },
autofocus: { autofocus: {
type: Boolean, type: Boolean,
@ -86,7 +86,7 @@ function create() {
theme: "default", theme: "default",
language: props.language, language: props.language,
minimap: { minimap: {
enabled: true enabled: false
}, },
automaticLayout: true, automaticLayout: true,
scrollbar: { scrollbar: {

View File

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

View File

@ -2,6 +2,18 @@ import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => { export default (): ModuleConfig => {
return { 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")] 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() { function open() {
if (!props.columns) { 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"> <cl-form ref="Form">
<template #slot-upload> <template #slot-upload>
<div class="upload"> <div class="upload" v-if="!upload.filename">
<div class="tips"> <div class="tips">
<span>{{ tips }}</span> <span>{{ tips }}</span>
<el-button type="primary" text bg @click="download">下载模版</el-button> <el-button type="primary" text bg @click="download">下载模版</el-button>
@ -21,9 +21,95 @@
:size="[220, '100%']" :size="[220, '100%']"
/> />
</div> </div>
</div>
</template>
<div class="progress" v-if="progress > 0"> <template #slot-list>
<el-progress type="dashboard" :percentage="progress" /> <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>
</div> </div>
</template> </template>
@ -33,7 +119,7 @@
<script lang="ts" setup name="cl-import-btn"> <script lang="ts" setup name="cl-import-btn">
import { useForm } from "@cool-vue/crud"; import { useForm } from "@cool-vue/crud";
import { ElMessage } from "element-plus"; 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 * as XLSX from "xlsx";
import chardet from "chardet"; import chardet from "chardet";
import { extname } from "/@/cool/utils"; import { extname } from "/@/cool/utils";
@ -75,17 +161,65 @@ const Form = useForm();
// //
const upload = reactive({ const upload = reactive({
filename: "", 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() { function clear() {
progress.value = 0;
upload.filename = ""; upload.filename = "";
upload.data = []; upload.file = null;
upload.list = [];
table.header = [];
table.selection = [];
} }
// //
@ -94,49 +228,46 @@ function open() {
Form.value?.open({ Form.value?.open({
title: "导入", title: "导入",
width: "800px", width: computed(() => (upload.filename ? "80%" : "800px")),
dialog: { dialog: {
"close-on-press-escape": false "close-on-press-escape": false
}, },
items: [ items: [
...(props.onConfig ? props.onConfig(Form) : []), ...(props.onConfig ? props.onConfig(Form) : []),
{ {
label: "",
prop: "file", prop: "file",
props: {
labelWidth: "0"
},
component: { component: {
name: "slot-upload" name: "slot-upload"
} }
},
{
component: {
name: "slot-list"
}
} }
], ],
op: { op: {
saveButtonText: "提交" saveButtonText: "提交"
}, },
on: { on: {
submit(data, { done, close }) { submit(_, { done, close }) {
if (!upload.filename) { if (!upload.filename) {
done(); done();
return ElMessage.error("请选择文件"); return ElMessage.error("请选择文件");
} }
if (props.onSubmit) { if (props.onSubmit) {
props.onSubmit( props.onSubmit(upload, { done, close });
{
...data,
list: upload.data
},
{ done, close, setProgress }
);
} else { } else {
console.error("cl-import-btn 未配置 onSubmit"); ElMessage.error("<cl-import-btn /> 未配置 onSubmit 参数");
done();
} }
} }
} }
}); });
} }
//
function onUpload(raw: File, _: any, { next }: any) { function onUpload(raw: File, _: any, { next }: any) {
const reader = new FileReader(); const reader = new FileReader();
const ext = extname(raw.name); 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.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); emit("change", json);
}; };
@ -186,15 +335,9 @@ function download() {
link.click(); link.click();
} }
//
function setProgress(val: string) {
progress.value = parseInt(val);
}
defineExpose({ defineExpose({
open, open,
clear, clear,
setProgress,
Form Form
}); });
</script> </script>
@ -229,9 +372,15 @@ defineExpose({
} }
} }
.progress { .data-table {
margin-top: 20px; .head {
display: flex; margin-bottom: 10px;
justify-content: center; }
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
} }
</style> </style>

View File

@ -2,6 +2,18 @@ import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => { export default (): ModuleConfig => {
return { return {
label: "Excel",
description: "表格的导入、导出组件",
author: "COOL",
version: "1.0.0",
updateTime: "2024-02-01",
demo: [
{
name: "基础用法",
component: () => import("./demo/base.vue")
}
],
components: [ components: [
() => import("./components/import-btn.vue"), () => import("./components/import-btn.vue"),
() => import("./components/export-btn") () => 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> <template>
<div class="file-viewer"> <div class="viewer-image">
<!-- 图片 --> <!-- 图片 -->
<el-image-viewer <el-image-viewer
v-if="img.visible" v-if="img.visible"
@ -8,34 +8,38 @@
teleported teleported
@close="close" @close="close"
/> />
<!-- Wps -->
<wps :ref="setRefs('wps')" />
</div> </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> </template>
<script lang="ts" setup name="file-viewer"> <script lang="ts" setup name="file-viewer">
import { reactive, toRaw } from "vue"; import { reactive, nextTick } from "vue";
import { has } from "lodash-es";
import { useCool } from "/@/cool";
import Wps from "./wps.vue";
import { getType } from "../../utils"; import { getType } from "../../utils";
import type { Upload } from "../../types"; import type { Upload } from "../../types";
import { useCool } from "/@/cool";
enum WpsType {
word = "Writer",
excel = "Spreadsheet",
ppt = "Presentation",
pdf = "Pdf"
}
const { refs, setRefs } = useCool(); const { refs, setRefs } = useCool();
//
const img = reactive({ const img = reactive({
visible: false, visible: false,
url: "" url: ""
}); });
//
const doc = reactive({
visible: false,
loading: false,
url: ""
});
//
function open(item: Upload.Item) { function open(item: Upload.Item) {
if (item?.type) { if (item?.type) {
// //
@ -44,7 +48,7 @@ function open(item: Upload.Item) {
// //
const type = getType(url); const type = getType(url);
// //
if (type == "image") { if (type == "image") {
img.visible = true; img.visible = true;
img.url = url; img.url = url;
@ -52,16 +56,26 @@ function open(item: Upload.Item) {
return true; return true;
} }
// WPS //
if (has(WpsType, type)) { if (["word", "excel", "ppt", "pdf"].includes(type)) {
// @ts-ignore doc.visible = true;
return refs.wps?.open(Object.assign(toRaw(item), { officeType: WpsType[item.type] })); 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); window.open(item.url);
} }
} }
//
function close() { function close() {
img.visible = false; img.visible = false;
} }
@ -72,7 +86,18 @@ defineExpose({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.file-viewer { .viewer-image {
position: absolute; position: absolute;
} }
.viewer-doc {
height: 100%;
width: 100%;
iframe {
border: 0;
height: 100%;
width: 100%;
}
}
</style> </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 () => { export default () => {
return { 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: { options: {
// 尺寸 // 尺寸
size: 120, 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