This commit is contained in:
icssoa 2023-01-09 15:37:56 +08:00
parent 7783fb5962
commit 95bcfd5990
78 changed files with 8553 additions and 1821 deletions

View File

@ -5,7 +5,7 @@
"body": [ "body": [
"<template>", "<template>",
" <cl-crud ref=\"Crud\">", " <cl-crud ref=\"Crud\">",
" <el-row>", " <cl-row>",
" <!-- 刷新按钮 -->", " <!-- 刷新按钮 -->",
" <cl-refresh-btn />", " <cl-refresh-btn />",
" <!-- 新增按钮 -->", " <!-- 新增按钮 -->",
@ -15,18 +15,18 @@
" <cl-flex1 />", " <cl-flex1 />",
" <!-- 关键字搜索 -->", " <!-- 关键字搜索 -->",
" <cl-search-key />", " <cl-search-key />",
" </el-row>", " </cl-row>",
"", "",
" <el-row>", " <cl-row>",
" <!-- 数据表格 -->", " <!-- 数据表格 -->",
" <cl-table ref=\"Table\" />", " <cl-table ref=\"Table\" />",
" </el-row>", " </cl-row>",
"", "",
" <el-row>", " <cl-row>",
" <cl-flex1 />", " <cl-flex1 />",
" <!-- 分页控件 -->", " <!-- 分页控件 -->",
" <cl-pagination />", " <cl-pagination />",
" </el-row>", " </cl-row>",
"", "",
" <!-- 新增、编辑 -->", " <!-- 新增、编辑 -->",
" <cl-upsert ref=\"Upsert\" />", " <cl-upsert ref=\"Upsert\" />",

View File

@ -232,9 +232,7 @@ export async function createMenu({ router, columns, prefix, api, filePath }: any
upsert.items.push(format(item)); upsert.items.push(format(item));
} }
if ( if (!column.component?.name.includes("cl-editor-")) {
!["cl-codemirror", "cl-editor-quill", "cl-editor-wang"].includes(column.component?.name)
) {
table.columns.push(format(column)); table.columns.push(format(column));
} }
}); });
@ -288,7 +286,7 @@ export async function createMenu({ router, columns, prefix, api, filePath }: any
// 代码模板 // 代码模板
const temp = `<template> const temp = `<template>
<cl-crud ref="Crud"> <cl-crud ref="Crud">
<el-row> <cl-row>
<!-- 刷新按钮 --> <!-- 刷新按钮 -->
<cl-refresh-btn /> <cl-refresh-btn />
${permission.add ? "<!-- 新增按钮 -->\n<cl-add-btn />" : ""} ${permission.add ? "<!-- 新增按钮 -->\n<cl-add-btn />" : ""}
@ -296,18 +294,18 @@ export async function createMenu({ router, columns, prefix, api, filePath }: any
<cl-flex1 /> <cl-flex1 />
<!-- 关键字搜索 --> <!-- 关键字搜索 -->
<cl-search-key /> <cl-search-key />
</el-row> </cl-row>
<el-row> <cl-row>
<!-- 数据表格 --> <!-- 数据表格 -->
<cl-table ref="Table" /> <cl-table ref="Table" />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-flex1 /> <cl-flex1 />
<!-- 分页控件 --> <!-- 分页控件 -->
<cl-pagination /> <cl-pagination />
</el-row> </cl-row>
<!-- 新增、编辑 --> <!-- 新增、编辑 -->
<cl-upsert ref="Upsert" /> <cl-upsert ref="Upsert" />

View File

@ -178,19 +178,13 @@ export default [
{ {
test: ["rich", "text", "html", "content", "introduce", "description", "desc"], test: ["rich", "text", "html", "content", "introduce", "description", "desc"],
form: { form: {
name: "cl-editor-wang", name: "cl-editor-wang"
props: {
height: 400
}
} }
}, },
{ {
test: ["code", "codes"], test: ["code", "codes"],
form: { form: {
name: "cl-codemirror", name: "cl-editor-monaco"
props: {
height: 400
}
} }
}, },
{ {

View File

@ -1,6 +1,6 @@
{ {
"name": "front-next", "name": "front-next",
"version": "5.12.1", "version": "5.12.2",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"build": "vite build", "build": "vite build",
@ -9,15 +9,12 @@
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix" "lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix"
}, },
"dependencies": { "dependencies": {
"@codemirror/lang-javascript": "^6.1.2", "@cool-vue/crud": "^6.0.1",
"@codemirror/theme-one-dark": "^6.1.0",
"@cool-vue/crud": "^5.9.4",
"@element-plus/icons-vue": "^2.0.10", "@element-plus/icons-vue": "^2.0.10",
"@vueuse/core": "^9.1.0", "@vueuse/core": "^9.1.0",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^0.27.2", "axios": "^0.27.2",
"codemirror": "^6.0.1",
"core-js": "^3.23.5", "core-js": "^3.23.5",
"echarts": "^5.3.3", "echarts": "^5.3.3",
"element-plus": "^2.2.28", "element-plus": "^2.2.28",
@ -25,13 +22,13 @@
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"monaco-editor": "^0.34.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.28", "pinia": "^2.0.28",
"quill": "^1.3.7", "quill": "^1.3.7",
"socket.io-client": "^4.5.1", "socket.io-client": "^4.5.1",
"store": "^2.0.12", "store": "^2.0.12",
"vue": "^3.2.45", "vue": "^3.2.45",
"vue-codemirror": "^6.1.1",
"vue-echarts": "^6.2.3", "vue-echarts": "^6.2.3",
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
@ -42,7 +39,7 @@
"@types/mockjs": "^1.0.6", "@types/mockjs": "^1.0.6",
"@types/node": "^18.0.6", "@types/node": "^18.0.6",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/quill": "^2.0.9", "@types/quill": "^2.0.10",
"@types/store": "^2.0.2", "@types/store": "^2.0.2",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/eslint-plugin": "^5.30.6",
@ -60,6 +57,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"magic-string": "^0.26.2", "magic-string": "^0.26.2",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"rollup-plugin-visualizer": "^5.9.0",
"sass": "^1.53.0", "sass": "^1.53.0",
"sass-loader": "^13.0.2", "sass-loader": "^13.0.2",
"typescript": "^4.7.4", "typescript": "^4.7.4",

View File

@ -35,7 +35,7 @@ export const config = {
// 忽略规则 // 忽略规则
ignore: { ignore: {
// 不显示请求进度条 // 不显示请求进度条
NProgress: ["/sys/info/record"], NProgress: ["/base/comm/upload", "/base/comm/uploadMode"],
// 页面不需要登录验证 // 页面不需要登录验证
token: ["/login", "/401", "/403", "/404", "/500", "/502"] token: ["/login", "/401", "/403", "/404", "/500", "/502"]
}, },

30
src/cool/hook/browser.ts Normal file
View File

@ -0,0 +1,30 @@
import { useEventListener } from "@vueuse/core";
import { reactive, watch } from "vue";
import { getBrowser } from "../utils";
const browser = reactive(getBrowser());
const events: (() => void)[] = [];
watch(
() => browser.screen,
() => {
events.forEach((ev) => ev());
}
);
useEventListener(window, "resize", () => {
Object.assign(browser, getBrowser());
});
export function useBrowser() {
return {
browser,
onScreenChange(ev: () => void, immediate = true) {
events.push(ev);
if (immediate) {
ev();
}
}
};
}

View File

@ -1,36 +1,45 @@
import { Emitter } from "mitt"; import { Emitter } from "mitt";
import { onBeforeUpdate, ref, inject } from "vue"; import { inject, getCurrentInstance, Ref, reactive } from "vue";
import { isNumber } from "lodash-es";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { service } from "../service"; import { service } from "../service";
import { Data } from "../utils"; import { Data } from "../utils";
import { useBrowser } from "./browser";
export function useService(): Eps.Service { export function useService(): Eps.Service {
return Data.get("service" || service); return Data.get("service" || service);
} }
export function useRefs() { export function useRefs() {
const refs: any = ref<any[]>([]); const refs = reactive<{ [key: string]: any }>({});
function setRefs(name: string) {
onBeforeUpdate(() => { return (el: any) => {
refs.value = []; refs[name] = el;
}); };
}
const setRefs = (index: string) => (el: any) => {
refs.value[index] = el;
};
return { refs, setRefs }; return { refs, setRefs };
} }
export function useComm() { export function useParent(name: string, r: Ref) {
function px(val: string | number) { const d = getCurrentInstance();
return isNumber(val) ? `${val}px` : val;
if (d) {
let parent = d.proxy?.$.parent;
if (parent) {
while (parent && parent.type?.name != name && parent.type?.name != "cl-crud") {
parent = parent?.parent;
}
if (parent) {
if (parent.type.name == name) {
r.value = parent.proxy;
}
}
}
} }
return { return r;
px
};
} }
export function useCool() { export function useCool() {
@ -39,6 +48,9 @@ export function useCool() {
route: useRoute(), route: useRoute(),
router: useRouter(), router: useRouter(),
mitt: inject("mitt") as Emitter<any>, mitt: inject("mitt") as Emitter<any>,
...useBrowser(),
...useRefs() ...useRefs()
}; };
} }
export * from "./browser";

View File

@ -21,6 +21,10 @@ const routes: RouteRecordRaw[] = [
component: config.app.router.home component: config.app.router.home
} }
] ]
},
{
path: "/:pathMatch(.*)*",
component: () => import("/$/base/pages/error/404.vue")
} }
]; ];
@ -81,7 +85,7 @@ router.clear = function () {
const rs = router.getRoutes(); const rs = router.getRoutes();
rs.forEach((e) => { rs.forEach((e) => {
if (e.name) { if (e.name && e.meta?.dynamic) {
router.removeRoute(e.name); router.removeRoute(e.name);
} }
}); });

View File

@ -1,4 +1,5 @@
import { isArray, orderBy } from "lodash-es"; import { isArray, isNumber, isString, orderBy } from "lodash-es";
import { resolveComponent } from "vue";
import storage from "./storage"; import storage from "./storage";
// 首字母大写 // 首字母大写
@ -292,6 +293,21 @@ export function mergeService(list: any[]) {
return data; return data;
} }
// 是否是组件
export function isComponent(name: string) {
return !isString(resolveComponent(name));
}
// 是否Promise
export function isPromise(val: any) {
return val && Object.prototype.toString.call(val) === "[object Promise]";
}
// 单位转换
export function parsePx(val: string | number) {
return isNumber(val) ? `${val}px` : val;
}
export { storage }; export { storage };
export * from "./data"; export * from "./data";
export * from "./loading"; export * from "./loading";

View File

@ -1,8 +1,2 @@
import { resize } from "./resize";
window.onload = function () {
resize();
};
export * from "./theme"; export * from "./theme";
export * from "./permission"; export * from "./permission";

View File

@ -1,13 +0,0 @@
import { useEventListener } from "@vueuse/core";
import { useStore } from "../store";
function update() {
const { app } = useStore();
app.setBrowser();
app.isFold = app.browser.isMini;
}
export function resize() {
useEventListener(window, "resize", update);
update();
}

View File

@ -12,8 +12,8 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, PropType } from "vue"; import { computed, defineComponent, PropType } from "vue";
import { isNumber } from "lodash-es";
import { User } from "@element-plus/icons-vue"; import { User } from "@element-plus/icons-vue";
import { parsePx } from "/@/cool/utils";
export default defineComponent({ export default defineComponent({
name: "cl-avatar", name: "cl-avatar",
@ -44,7 +44,7 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
const size = isNumber(props.size) ? props.size + "px" : props.size; const size = parsePx(props.size);
const style = computed(() => { const style = computed(() => {
return { return {

View File

@ -1,126 +0,0 @@
<template>
<div class="cl-codemirror" :class="{ disabled }">
<codemirror
ref="Editor"
v-model="content"
:placeholder="placeholder"
:style="style"
autofocus
:disabled="disabled"
indent-with-tab
:tab-size="4"
:extensions="extensions"
@change="onChange"
/>
</div>
</template>
<script lang="ts">
import { Codemirror } from "vue-codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { oneDark } from "@codemirror/theme-one-dark";
import { ref, watch, computed, defineComponent } from "vue";
import { useDark } from "@vueuse/core";
import { useComm } from "/@/cool";
export default defineComponent({
name: "cl-codemirror",
components: {
Codemirror
},
props: {
modelValue: String,
placeholder: {
type: String,
default: "请输入"
},
height: {
type: [String, Number],
default: 400
},
fontSize: {
type: [String, Number],
default: 14
},
disabled: Boolean
},
emits: ["update:modelValue", "change"],
setup(props, { emit }) {
const { px } = useComm();
const Editor = ref();
//
const isDark = ref(useDark());
//
const extensions: any[] = [javascript()];
if (isDark.value) {
extensions.push(oneDark);
}
//
const content = ref("");
//
const style = computed(() => {
return { height: px(props.height), fontSize: px(props.fontSize) };
});
//
function onChange(value: string) {
emit("update:modelValue", value);
emit("change", value);
}
//
watch(
() => props.modelValue,
(val) => {
content.value = val || "";
},
{
immediate: true
}
);
return {
Editor,
isDark,
style,
content,
extensions,
onChange
};
}
});
</script>
<style lang="scss" scoped>
.cl-codemirror {
border: 1px solid var(--el-border-color);
box-sizing: border-box;
:deep(.cm-editor) {
.cm-foldGutter {
width: 12px;
text-align: center;
}
&.cm-focused {
outline: 0;
}
}
&.disabled {
:deep(.cm-content) {
background-color: var(--el-disabled-bg-color);
}
}
}
</style>

View File

@ -27,8 +27,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, onMounted, ref } from "vue"; import { computed, defineComponent, onMounted, ref, PropType, nextTick } from "vue";
import { useCrud } from "@cool-vue/crud";
import store from "store"; import store from "store";
export default defineComponent({ export default defineComponent({
@ -37,16 +36,13 @@ export default defineComponent({
props: { props: {
name: String, name: String,
columns: { columns: {
type: Array, type: Array as PropType<any[]>,
required: true, required: true,
default: () => [] default: () => []
} }
}, },
setup(props) { setup(props) {
// cl-crud
const Crud = useCrud();
// //
const visible = ref(false); const visible = ref(false);
@ -59,8 +55,8 @@ export default defineComponent({
// //
const list = computed(() => { const list = computed(() => {
return props.columns return props.columns
.filter((e: any) => !e.type) .filter((e) => !e.type && e.prop)
.map((e: any) => { .map((e) => {
return { return {
label: e.label, label: e.label,
value: e.prop value: e.prop
@ -76,17 +72,17 @@ export default defineComponent({
// //
function change() { function change() {
const names = getNames(); nextTick(() => {
const names = getNames();
if (store.get(name)) { if (store.get(name)) {
Crud.value!["cl-table"].reBuild(() => { props.columns.map((e) => {
props.columns.map((e: any) => {
if (!e.type) { if (!e.type) {
e.hidden = !names.includes(e.prop); e.hidden = !names.includes(e.prop);
} }
}); });
}); }
} });
} }
// //

View File

@ -30,7 +30,7 @@
import { computed, defineComponent } from "vue"; import { computed, defineComponent } from "vue";
import { isArray, isNumber, isString } from "lodash-es"; import { isArray, isNumber, isString } from "lodash-es";
import { PictureFilled } from "@element-plus/icons-vue"; import { PictureFilled } from "@element-plus/icons-vue";
import { useComm } from "/@/cool"; import { parsePx } from "/@/cool/utils";
export default defineComponent({ export default defineComponent({
name: "cl-image", name: "cl-image",
@ -58,8 +58,6 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
const { px } = useComm();
const urls = computed(() => { const urls = computed(() => {
const urls: any = props.modelValue || props.src; const urls: any = props.modelValue || props.src;
@ -78,8 +76,8 @@ export default defineComponent({
const [h, w]: any = isNumber(props.size) ? [props.size, props.size] : props.size; const [h, w]: any = isNumber(props.size) ? [props.size, props.size] : props.size;
return { return {
h: px(h), h: parsePx(h),
w: px(w) w: parsePx(w)
}; };
}); });

View File

@ -1,18 +1,5 @@
<template>
<el-select v-model="value" :clearable="clearable" @change="onChange">
<el-option
v-for="(item, index) in list"
:key="index"
:label="item.label"
:value="item.value"
:disabled="item.disabled"
></el-option>
</el-select>
</template>
<script lang="ts">
import { useCrud } from "@cool-vue/crud"; import { useCrud } from "@cool-vue/crud";
import { computed, defineComponent, isRef, ref, watch } from "vue"; import { computed, defineComponent, isRef, Ref, ref, watch } from "vue";
export default defineComponent({ export default defineComponent({
name: "cl-select", name: "cl-select",
@ -23,10 +10,6 @@ export default defineComponent({
type: [Array, Object], type: [Array, Object],
default: () => [] default: () => []
}, },
clearable: {
type: Boolean,
default: true
},
prop: String prop: String
}, },
@ -40,9 +23,9 @@ export default defineComponent({
const value = ref(); const value = ref();
// 列表 // 列表
const list = computed<any>(() => const list = computed(() =>
isRef(props.options) ? props.options.value : props.options isRef(props.options) ? props.options.value : props.options
); ) as Ref<any[]>;
// 值改变 // 值改变
function onChange(val: string) { function onChange(val: string) {
@ -64,11 +47,14 @@ export default defineComponent({
} }
); );
return { return () => {
list, return (
value, <el-select v-model={value.value} clearable filterable onChange={onChange}>
onChange {list.value.map((e) => {
return <el-option {...e} />;
})}
</el-select>
);
}; };
} }
}); });
</script>

View File

@ -71,20 +71,15 @@ export default defineComponent({
} }
} }
return { return () => {
status, return (
onChange <el-switch
v-model={status.value}
active-value={props.activeValue}
inactive-value={props.inactiveValue}
onChange={onChange}
/>
);
}; };
},
render(ctx: any) {
return (
<el-switch
v-model={ctx.status}
active-value={ctx.activeValue}
inactive-value={ctx.inactiveValue}
onChange={ctx.onChange}
/>
);
} }
}); });

View File

@ -1,20 +1,27 @@
<template> <template>
<div class="cl-view-group"> <div class="cl-view-group" :class="[isExpand ? 'is-expand' : 'is-collapse']">
<div class="cl-view-group__wrap"> <div class="cl-view-group__wrap">
<!-- --> <!-- -->
<div class="cl-view-group__left" :class="[isExpand ? '_expand' : '_collapse']"> <div class="cl-view-group__left">
<slot name="left"></slot> <slot name="left"></slot>
<!-- 收起按钮 -->
<div class="collapse-btn" @click="expand(false)" v-if="browser.isMini">
<el-icon>
<arrow-right />
</el-icon>
</div>
</div> </div>
<!-- 内容 --> <!-- 内容 -->
<div class="cl-view-group__right"> <div class="cl-view-group__right">
<div class="cl-view-group__right-head"> <div class="cl-view-group__right-head">
<div class="icon" @click="toExpand()"> <div class="icon" @click="expand()">
<el-icon v-if="isExpand"><arrow-left /></el-icon> <el-icon v-if="isExpand"><arrow-left /></el-icon>
<el-icon v-else><arrow-right /></el-icon> <el-icon v-else><arrow-right /></el-icon>
</div> </div>
<span>{{ title }}</span> <span>{{ label }}</span>
</div> </div>
<div class="cl-view-group__right-content"> <div class="cl-view-group__right-content">
@ -28,7 +35,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, provide, ref, watch } from "vue"; import { defineComponent, provide, ref, watch } from "vue";
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue"; import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
import { useStore } from "../../store"; import { useBrowser } from "/@/cool";
export default defineComponent({ export default defineComponent({
name: "cl-view-group", name: "cl-view-group",
@ -39,47 +46,67 @@ export default defineComponent({
}, },
props: { props: {
title: String title: String,
leftWidth: {
type: String,
default: "300px"
}
}, },
setup() { setup(props) {
const { app } = useStore(); const { browser, onScreenChange } = useBrowser();
// //
const isExpand = ref(true); const isExpand = ref(true);
//
const selected = ref();
//
const label = ref(props.title);
// //
function toExpand(value?: boolean) { function expand(value?: boolean) {
isExpand.value = value === undefined ? !isExpand.value : value; isExpand.value = value === undefined ? !isExpand.value : value;
} }
// //
function checkExpand(value?: boolean) { function select(data?: any) {
if (app.browser.isMini) { selected.value = data;
toExpand(value);
if (browser.isMini) {
expand(false);
} }
} }
// //
watch( function setTitle(value?: string) {
() => app.browser.isMini, label.value = value || "";
(val: boolean) => { }
isExpand.value = !val;
},
{
immediate: true
}
);
provide("viewGroup", { //
checkExpand onScreenChange(() => {
expand(!browser.isMini);
}); });
return { //
watch(() => props.title, setTitle, {
immediate: true
});
const ctx = {
label,
selected,
isExpand, isExpand,
toExpand, setTitle,
checkExpand expand,
select,
browser
}; };
provide("viewGroup", ctx);
return ctx;
} }
}); });
</script> </script>
@ -89,6 +116,8 @@ export default defineComponent({
height: 100%; height: 100%;
width: 100%; width: 100%;
$left-width: v-bind("leftWidth");
&__wrap { &__wrap {
display: flex; display: flex;
height: 100%; height: 100%;
@ -98,25 +127,39 @@ export default defineComponent({
} }
&__left { &__left {
position: relative;
height: 100%; height: 100%;
width: 300px; width: $left-width;
transition: width 0.3s; background-color: #fff;
flex-shrink: 0;
overflow: hidden;
border-right: 1px solid var(--el-border-color);
box-sizing: border-box;
&._collapse { .collapse-btn {
margin-right: 0; display: flex;
width: 0 !important; align-items: center;
border-right: 0; justify-content: center;
position: absolute;
right: 20px;
bottom: 30px;
height: 40px;
width: 40px;
border-radius: 100%;
background-color: var(--color-primary);
box-shadow: 0 0 10px 1px #eee;
.el-icon {
color: #fff;
font-size: 24px;
}
} }
} }
&__right { &__right {
width: calc(100% - 310px); position: absolute;
flex: 1; right: 0;
overflow: hidden; top: 0;
height: 100%;
width: 100%;
background-color: #fff;
transition: width 0.3s;
&-head { &-head {
display: flex; display: flex;
@ -150,13 +193,29 @@ export default defineComponent({
} }
} }
&.is-expand {
.cl-view-group__right {
width: calc(100% - $left-width);
border-left: 1px solid var(--el-border-color);
}
}
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
.cl-view-group__left { .cl-view-group__left {
width: 100%; overflow: hidden;
transition: width 0.2s;
width: 0;
z-index: 9;
} }
.cl-view-group__right { .cl-view-group__right {
width: calc(100% - 100px); width: 100% !important;
}
&.is-expand {
.cl-view-group__left {
width: 100%;
}
} }
} }
} }

View File

@ -5,7 +5,7 @@ import "./static/css/index.scss";
export default (): ModuleConfig => { export default (): ModuleConfig => {
return { return {
order: 99, order: 99,
components: Object.values(import.meta.glob("./components/**/*")), components: Object.values(import.meta.glob("./components/**/*.{vue,tsx}")),
views: [ views: [
{ {
path: "/my/info", path: "/my/info",

View File

@ -0,0 +1,9 @@
import { ref } from "vue";
import { ClViewGroup } from "../types";
import { useParent } from "/@/cool";
export function useViewGroup() {
const ViewGroup = ref<ClViewGroup>();
useParent("cl-view-group", ViewGroup);
return { ViewGroup };
}

View File

@ -0,0 +1 @@
export * from "./component";

View File

@ -7,4 +7,5 @@ export function useBase() {
} }
export * from "./common"; export * from "./common";
export * from "./hooks";
export * from "./types/index.d"; export * from "./types/index.d";

View File

@ -1,13 +1,13 @@
import { h } from "vue"; import { defineComponent, h } from "vue";
import { useStore } from "../../store"; import { useStore } from "../../store";
import { Menu } from "../../types"; import { Menu } from "../../types";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
export default { export default defineComponent({
name: "b-menu", name: "b-menu",
setup() { setup() {
const { router, route } = useCool(); const { router, route, browser } = useCool();
const { menu, app } = useStore(); const { menu, app } = useStore();
// 页面跳转 // 页面跳转
@ -16,89 +16,87 @@ export default {
router.push(url); router.push(url);
} }
// 移动端点击收起左侧菜单 // 小屏下点击收起左侧菜单
if (app.browser.isMini) { if (browser.isMini) {
app.fold(true); app.fold(true);
} }
} }
return { // 渲染子菜单
route, function renderMenu() {
toView, function deep(list: Menu.Item[], index: number) {
menu return list
}; .filter((e) => e.isShow)
}, .map((e) => {
let html = null;
render(ctx: any) { if (e.type == 0) {
const { app } = useStore(); html = h(
<el-sub-menu />,
// 设置子菜单 {
function deep(list: Menu.Item[], index: number) { index: String(e.id),
return list key: e.id,
.filter((e) => e.isShow) popperClass: "app-slider__menu"
.map((e) => {
let html = null;
if (e.type == 0) {
html = h(
<el-sub-menu></el-sub-menu>,
{
index: String(e.id),
key: e.id,
popperClass: "app-slider__menu"
},
{
title() {
return (
<div class="wrap">
<cl-svg name={e.icon} />
<span v-show={!app.isFold || index != 1}>{e.name}</span>
</div>
);
}, },
default() { {
return deep(e.children || [], index + 1); title() {
return (
<div class="wrap">
<cl-svg name={e.icon} />
<span v-show={!app.isFold || index != 1}>
{e.name}
</span>
</div>
);
},
default() {
return deep(e.children || [], index + 1);
}
} }
} );
); } else {
} else { html = h(
html = h( <el-menu-item />,
<el-menu-item />, {
{ index: e.path,
index: e.path, key: e.id
key: e.id },
}, {
{ default() {
default() { return (
return ( <div class="wrap">
<div class="wrap"> <cl-svg name={e.icon} />
<cl-svg name={e.icon} /> <span v-show={!app.isFold || index != 1}>
<span v-show={!app.isFold || index != 1}>{e.name}</span> {e.name}
</div> </span>
); </div>
);
}
} }
} );
); }
}
return html; return html;
}); });
}
return deep(menu.list, 1);
} }
const children = deep(ctx.menu.list, 1); return () => {
return (
return ( <div class="app-slider__menu">
<div class="app-slider__menu"> <el-menu
<el-menu default-active={route.path}
default-active={ctx.route.path} background-color="transparent"
background-color="transparent" collapse-transition={true}
collapse-transition={true} collapse={browser.isMini ? false : app.isFold}
collapse={app.browser.isMini ? false : app.isFold} onSelect={toView}
onSelect={ctx.toView} >
> {renderMenu()}
{children} </el-menu>
</el-menu> </div>
</div> );
); };
} }
}; });

View File

@ -54,7 +54,7 @@ function toPath() {
// //
function scrollTo(left: number) { function scrollTo(left: number) {
refs.value.scroller.scrollTo({ refs.scroller.scrollTo({
left, left,
behavior: "smooth" behavior: "smooth"
}); });
@ -62,10 +62,10 @@ function scrollTo(left: number) {
// //
function adScroll(index: number) { function adScroll(index: number) {
const el = refs.value[`item-${index}`]; const el = refs[`item-${index}`];
if (el) { if (el) {
scrollTo(el.offsetLeft + el.clientWidth - refs.value.scroller.clientWidth); scrollTo(el.offsetLeft + el.clientWidth - refs.scroller.clientWidth);
} }
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="route-nav"> <div class="route-nav">
<p v-if="app.browser.isMini" class="title"> <p v-if="browser.isMini" class="title">
{{ lastName }} {{ lastName }}
</p> </p>
@ -20,8 +20,8 @@ import { flattenDeep, last } from "lodash-es";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import { useBase } from "/$/base"; import { useBase } from "/$/base";
const { route } = useCool(); const { route, browser } = useCool();
const { app, menu } = useBase(); const { menu } = useBase();
// //
const list = computed(() => { const list = computed(() => {
@ -58,21 +58,18 @@ const lastName = computed(() => last(list.value)?.name);
.route-nav { .route-nav {
white-space: nowrap; white-space: nowrap;
.el-breadcrumb { :deep(.el-breadcrumb) {
margin: 0 10px; margin: 0 10px;
&__inner { .el-breadcrumb__inner {
font-size: 13px; font-size: 13px;
padding: 0 10px;
font-weight: normal;
letter-spacing: 1px;
} }
} }
.title { .title {
font-size: 15px; font-size: 15px;
font-weight: 500; font-weight: 500;
margin-left: 5px; margin-left: 8px;
} }
} }
</style> </style>

View File

@ -2,7 +2,7 @@
<div class="app-slider"> <div class="app-slider">
<div class="app-slider__logo" @click="toHome"> <div class="app-slider__logo" @click="toHome">
<img src="/logo.png" /> <img src="/logo.png" />
<span v-if="!app.isFold || app.browser.isMini">{{ app.info.name }}</span> <span v-if="!app.isFold || browser.isMini">{{ app.info.name }}</span>
</div> </div>
<div class="app-slider__container"> <div class="app-slider__container">
@ -13,8 +13,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useBase } from "/$/base"; import { useBase } from "/$/base";
import { useBrowser } from "/@/cool";
import BMenu from "./bmenu"; import BMenu from "./bmenu";
const { browser } = useBrowser();
const { app } = useBase(); const { app } = useBase();
function toHome() { function toHome() {

View File

@ -62,7 +62,6 @@ import AMenu from "./amenu.vue";
const { router, service } = useCool(); const { router, service } = useCool();
const { user, app } = useBase(); const { user, app } = useBase();
//
async function onCommand(name: string) { async function onCommand(name: string) {
switch (name) { switch (name) {
case "my": case "my":

View File

@ -7,7 +7,7 @@
</div> </div>
<p class="desc">一款快速开发后台权限管理系统</p> <p class="desc">一款快速开发后台权限管理系统</p>
<el-form label-position="top" class="form" :disabled="saving" size="large"> <el-form label-position="top" class="form" :disabled="saving">
<el-form-item label="用户名"> <el-form-item label="用户名">
<input <input
v-model="form.username" v-model="form.username"
@ -105,7 +105,7 @@ async function toLogin() {
// //
router.push("/"); router.push("/");
} catch (err: any) { } catch (err: any) {
refs.value.captcha.refresh(); refs.captcha.refresh();
ElMessage.error(err.message); ElMessage.error(err.message);
} }
@ -178,6 +178,7 @@ async function toLogin() {
-webkit-text-fill-color: #fff; -webkit-text-fill-color: #fff;
font-size: 15px; font-size: 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.5); border-bottom: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 0;
&:-webkit-autofill { &:-webkit-autofill {
box-shadow: 0 0 0px 1000px transparent inset !important; box-shadow: 0 0 0px 1000px transparent inset !important;
@ -213,7 +214,9 @@ async function toLogin() {
margin-top: 50px; margin-top: 50px;
:deep(.el-button) { :deep(.el-button) {
height: 40px;
width: 140px; width: 140px;
font-size: 16px;
} }
} }
} }

View File

@ -1,4 +1,4 @@
// customize style // customize
.scroller1 { .scroller1 {
overflow: auto; overflow: auto;
position: relative; position: relative;
@ -19,7 +19,7 @@
} }
} }
// Element-plus theme // Element-plus
.el-input-number { .el-input-number {
.el-input-number__decrease, .el-input-number__decrease,
.el-input-number__increase { .el-input-number__increase {
@ -29,5 +29,7 @@
} }
.el-dialog { .el-dialog {
--el-dialog-border-radius: 10px !important; &:not(.is-fullscreen) {
--el-dialog-border-radius: 10px !important;
}
} }

View File

@ -1,19 +1,18 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { reactive, ref } from "vue"; import { reactive, ref } from "vue";
import { config } from "/@/cool"; import { config, useBrowser } from "/@/cool";
import { deepMerge, getBrowser, storage } from "/@/cool/utils"; import { deepMerge, storage } from "/@/cool/utils";
export const useAppStore = defineStore("app", function () { export const useAppStore = defineStore("app", function () {
const { browser, onScreenChange } = useBrowser();
// 基本信息 // 基本信息
const info = reactive({ const info = reactive({
...config.app ...config.app
}); });
// 浏览器信息
const browser = ref(getBrowser());
// 是否折叠 // 是否折叠
const isFold = ref(browser.value.isMini || false); const isFold = ref(false);
// 事件 // 事件
const events = reactive<{ [key: string]: any[] }>({ const events = reactive<{ [key: string]: any[] }>({
@ -35,11 +34,6 @@ export const useAppStore = defineStore("app", function () {
storage.set("__app__", info); storage.set("__app__", info);
} }
// 设置浏览器信息
function setBrowser() {
browser.value = getBrowser();
}
// 添加事件 // 添加事件
function addEvent(name: string, func: any) { function addEvent(name: string, func: any) {
if (func) { if (func) {
@ -47,14 +41,17 @@ export const useAppStore = defineStore("app", function () {
} }
} }
// 监听屏幕变化
onScreenChange(() => {
isFold.value = browser.isMini;
});
return { return {
info, info,
browser,
isFold, isFold,
fold, fold,
events, events,
set, set,
setBrowser,
addEvent addEvent
}; };
}); });

View File

@ -63,7 +63,7 @@ export const useUserStore = defineStore("user", function () {
// 退出 // 退出
async function logout() { async function logout() {
clear(); clear();
// router.clear(); router.clear();
router.push("/login"); router.push("/login");
} }

View File

@ -39,3 +39,16 @@ export declare namespace Process {
type List = Item[]; type List = Item[];
} }
export declare interface ClViewGroup {
selected:
| {
id?: number;
[key: string]: any;
}
| undefined;
isExpand: boolean;
setTitle(value?: string): void;
select(data?: any): void;
expand(value?: boolean): void;
}

View File

@ -11,18 +11,13 @@ import { useCrud, useForm } from "@cool-vue/crud";
import DeptSelect from "./select.vue"; import DeptSelect from "./select.vue";
const { service } = useCool(); const { service } = useCool();
// cl-form
const Form = useForm(); const Form = useForm();
// cl-crud
const Crud = useCrud(); const Crud = useCrud();
//
async function open(ids: any[]) { async function open(ids: any[]) {
Form.value?.open({ Form.value?.open({
title: "部门转移", title: "部门转移",
width: "600px", width: "500px",
props: { props: {
labelWidth: "80px" labelWidth: "80px"
}, },
@ -42,7 +37,7 @@ async function open(ids: any[]) {
return done(); return done();
} }
ElMessageBox.confirm("是否转移部门?", "提示", { ElMessageBox.confirm("转移到新部门,是否继续", "提示", {
type: "warning" type: "warning"
}) })
.then(() => { .then(() => {

View File

@ -12,7 +12,7 @@
</el-tooltip> </el-tooltip>
</li> </li>
<li v-if="drag && !app.browser.isMini" @click="isDrag = true"> <li v-if="drag && !browser.isMini" @click="isDrag = true">
<el-tooltip content="拖动排序"> <el-tooltip content="拖动排序">
<el-icon> <el-icon>
<operation /> <operation />
@ -48,13 +48,13 @@
<span <span
class="dept-tree__node-label" class="dept-tree__node-label"
:class="{ :class="{
'is-active': data.id == info?.id 'is-active': data.id == ViewGroup?.selected?.id
}" }"
@click="rowClick(data)" @click="rowClick(data)"
>{{ node.label }}</span >{{ node.label }}</span
> >
<span <span
v-if="app.browser.isMini" v-if="browser.isMini"
class="dept-tree__node-icon" class="dept-tree__node-icon"
@click="onContextMenu($event, data, node)" @click="onContextMenu($event, data, node)"
> >
@ -73,14 +73,14 @@
</template> </template>
<script lang="ts" name="dept-tree" setup> <script lang="ts" name="dept-tree" setup>
import { inject, onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { useCool } from "/@/cool"; import { useBrowser, 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, 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 { useBase, checkPerm } from "/$/base"; import { checkPerm, useViewGroup } from "/$/base";
const props = defineProps({ const props = defineProps({
drag: { drag: {
@ -93,28 +93,20 @@ const props = defineProps({
} }
}); });
const emit = defineEmits(["list-change", "row-click", "user-add"]); const emit = defineEmits(["list-change", "refresh", "user-add"]);
const { service } = useCool(); const { service, browser } = useCool();
const { ViewGroup } = useViewGroup();
const { app } = useBase();
//
const list = ref<any[]>([]);
//
const info = ref();
//
const loading = ref<boolean>(false);
//
const isDrag = ref<boolean>(false);
// cl-form
const Form = useForm(); const Form = useForm();
const viewGroup = inject<any>("viewGroup"); //
const list = ref<Eps.BaseSysDepartmentEntity[]>([]);
//
const loading = ref(false);
//
const isDrag = ref(false);
// //
function allowDrag({ data }: any) { function allowDrag({ data }: any) {
@ -131,34 +123,39 @@ async function refresh() {
loading.value = true; loading.value = true;
isDrag.value = false; isDrag.value = false;
await service.base.sys.department.list().then((res: any[]) => { await service.base.sys.department.list().then((res) => {
list.value = deepTree(res); list.value = deepTree(res);
if (!info.value) { if (!ViewGroup.value?.selected) {
info.value = list.value[0]; rowClick();
} }
//
rowClick(info.value);
}); });
loading.value = false; loading.value = false;
} }
// ids // ids
function rowClick(e: any) { function rowClick(item?: Eps.BaseSysDepartmentEntity) {
if (e) { if (!item) {
const ids = e.children ? revDeepTree(e.children).map((e) => e.id) : []; item = list.value[0];
ids.unshift(e.id); }
info.value = e;
viewGroup.checkExpand(false); if (item) {
emit("row-click", { item: e, ids }); const ids = item.children ? revDeepTree(item.children).map((e) => e.id) : [];
ids.unshift(item.id);
//
emit("refresh", { page: 1, departmentIds: ids });
//
ViewGroup.value?.select(item);
ViewGroup.value?.setTitle(`成员列表(${item.name}`);
} }
} }
// //
function rowEdit(e: any) { function rowEdit(item: Eps.BaseSysDepartmentEntity) {
const method = e.id ? "update" : "add"; const method = item.id ? "update" : "add";
Form.value?.open({ Form.value?.open({
title: "编辑部门", title: "编辑部门",
@ -198,12 +195,14 @@ function rowEdit(e: any) {
} }
} }
], ],
form: e, form: {
...item
},
on: { on: {
submit(data, { done, close }) { submit(data, { done, close }) {
service.base.sys.department[method]({ service.base.sys.department[method]({
id: e.id, id: item.id,
parentId: e.parentId, parentId: item.parentId,
name: data.name, name: data.name,
orderNum: data.orderNum orderNum: data.orderNum
}) })
@ -222,23 +221,24 @@ function rowEdit(e: any) {
} }
// //
function rowDel(e: any) { function rowDel(item: Eps.BaseSysDepartmentEntity) {
async function del(f: boolean) { async function del(f: boolean) {
await service.base.sys.department await service.base.sys.department
.delete({ .delete({
ids: [e.id], ids: [item.id],
deleteUser: f deleteUser: f
}) })
.then(() => { .then(() => {
if (e.id == info.value.id) { //
info.value = null; if (ViewGroup.value?.selected?.id == item.id) {
rowClick();
} }
if (f) { if (f) {
ElMessage.success("删除成功"); ElMessage.success("删除成功");
} else { } else {
ElMessageBox.confirm( ElMessageBox.confirm(
`${e.name}” 部门的用户已成功转移到 “${e.parentName}” 部门。`, `${item.name}” 部门的用户已成功转移到 “${item.parentName}” 部门。`,
"删除成功" "删除成功"
); );
} }
@ -247,7 +247,7 @@ function rowDel(e: any) {
refresh(); refresh();
} }
ElMessageBox.confirm(`该操作会删除 “${e.name}” 部门的所有用户,是否确认?`, "提示", { ElMessageBox.confirm(`该操作会删除 “${item.name}” 部门的所有用户,是否确认?`, "提示", {
type: "warning", type: "warning",
confirmButtonText: "直接删除", confirmButtonText: "直接删除",
cancelButtonText: "保留用户", cancelButtonText: "保留用户",
@ -256,7 +256,7 @@ function rowDel(e: any) {
.then(() => { .then(() => {
del(true); del(true);
}) })
.catch((action: string) => { .catch((action) => {
if (action == "cancel") { if (action == "cancel") {
del(false); del(false);
} }
@ -449,11 +449,15 @@ onMounted(function () {
} }
&-icon { &-icon {
height: 28px; display: flex;
width: 28px; align-items: center;
line-height: 28px; justify-content: center;
background-color: #eee;
height: 26px;
width: 26px;
text-align: center; text-align: center;
margin-right: 5px; margin-right: 5px;
border-radius: 5px;
} }
} }
} }

View File

@ -26,7 +26,7 @@ watch(
onMounted(() => { onMounted(() => {
loading.value = true; loading.value = true;
refs.value.iframe.onload = () => { refs.iframe.onload = () => {
loading.value = false; loading.value = false;
}; };
}); });

View File

@ -1,6 +1,6 @@
<template> <template>
<cl-crud ref="Crud"> <cl-crud ref="Crud">
<el-row> <cl-row>
<cl-refresh-btn /> <cl-refresh-btn />
<el-button <el-button
@ -22,17 +22,17 @@
</cl-filter> </cl-filter>
<cl-flex1 /> <cl-flex1 />
<cl-search-key placeholder="请求地址、参数、ip" /> <cl-search-key placeholder="搜索请求地址、参数、ip" />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-table ref="Table" :default-sort="{ prop: 'createTime', order: 'descending' }" /> <cl-table ref="Table" />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-flex1 /> <cl-flex1 />
<cl-pagination /> <cl-pagination />
</el-row> </cl-row>
</cl-crud> </cl-crud>
</template> </template>
@ -45,7 +45,7 @@ import { useCrud, useTable } from "@cool-vue/crud";
const { service } = useCool(); const { service } = useCool();
// //
const day = ref<number>(1); const day = ref(1);
// cl-crud // cl-crud
const Crud = useCrud({ service: service.base.sys.log }, (app) => { const Crud = useCrud({ service: service.base.sys.log }, (app) => {
@ -96,21 +96,26 @@ const Table = useTable({
prop: "createTime", prop: "createTime",
label: "创建时间", label: "创建时间",
minWidth: 160, minWidth: 160,
sortable: true sortable: "desc"
} }
] ]
}); });
// //
function saveDay() { function saveDay() {
service.base.sys.log.setKeep({ value: day.value }).then(() => { service.base.sys.log
ElMessage.success("保存成功"); .setKeep({ value: day.value })
}); .then(() => {
ElMessage.success("保存成功");
})
.catch((err) => {
ElMessage.error(err.message);
});
} }
// //
function clear() { function clear() {
ElMessageBox.confirm("是否要清空日志", "提示", { ElMessageBox.confirm("是否要清空日志", "提示", {
type: "warning" type: "warning"
}) })
.then(() => { .then(() => {
@ -129,7 +134,7 @@ function clear() {
onMounted(() => { onMounted(() => {
// //
service.base.sys.log.getKeep().then((res: number) => { service.base.sys.log.getKeep().then((res) => {
day.value = Number(res); day.value = Number(res);
}); });
}); });

View File

@ -1,12 +1,12 @@
<template> <template>
<cl-crud ref="Crud"> <cl-crud ref="Crud">
<el-row> <cl-row>
<cl-refresh-btn /> <cl-refresh-btn />
<cl-add-btn /> <cl-add-btn />
<menu-create v-if="isDev" /> <menu-create v-if="isDev" />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-table ref="Table" row-key="id" @row-click="onRowClick"> <cl-table ref="Table" row-key="id" @row-click="onRowClick">
<!-- 名称 --> <!-- 名称 -->
<template #column-name="{ scope }"> <template #column-name="{ scope }">
@ -70,12 +70,7 @@
> >
</template> </template>
</cl-table> </cl-table>
</el-row> </cl-row>
<el-row>
<cl-flex1 />
<cl-pagination layout="total" />
</el-row>
<!-- 新增编辑 --> <!-- 新增编辑 -->
<cl-upsert ref="Upsert"> <cl-upsert ref="Upsert">

View File

@ -1,34 +1,42 @@
<template> <template>
<cl-crud ref="Crud"> <cl-crud ref="Crud">
<el-row> <cl-row>
<cl-refresh-btn /> <cl-refresh-btn />
<cl-add-btn /> <cl-add-btn />
<cl-multi-delete-btn /> <cl-multi-delete-btn />
<cl-flex1 /> <cl-flex1 />
<cl-search-key /> <cl-search-key />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-table ref="Table" /> <cl-table ref="Table" />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-flex1 /> <cl-flex1 />
<cl-pagination /> <cl-pagination />
</el-row> </cl-row>
<cl-upsert ref="Upsert"> <cl-upsert ref="Upsert">
<template #slot-content="{ scope }"> <template #slot-content="{ scope }">
<div class="editor"> <div>
<template v-for="(item, index) in tab.list" :key="index"> <el-radio-group :model-value="tab.value" @change="onTabChange">
<template v-if="tab.index == index"> <el-radio
<el-button class="change-btn" @click="changeTab(item.to)">{{ v-for="(item, index) in tab.list"
item.label :key="index"
}}</el-button> :label="item.value"
>{{ item.label }}</el-radio
>
</el-radio-group>
<component :is="item.component" v-model="scope.data" /> <el-input
</template> placeholder="请输入"
</template> v-model="scope.data"
type="textarea"
:rows="4"
v-if="componentName == 'el-input'"
/>
<component :is="componentName" v-model="scope.data" v-else />
</div> </div>
</template> </template>
</cl-upsert> </cl-upsert>
@ -38,29 +46,30 @@
<script lang="ts" name="sys-param" setup> <script lang="ts" name="sys-param" setup>
import { useCrud, useTable, useUpsert } from "@cool-vue/crud"; import { useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { ElMessageBox } from "element-plus"; import { ElMessageBox } from "element-plus";
import { nextTick, reactive } from "vue"; import { computed, reactive } from "vue";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import { isComponent } from "/@/cool/utils";
const { service } = useCool(); const { service } = useCool();
// const tab = reactive({
const tab = reactive<any>({ value: "el-input",
index: null,
list: [ list: [
{ {
label: "切换富文本编辑器", label: "代码编辑器",
to: 1, value: "cl-editor-monaco"
component: "cl-codemirror"
}, },
{ {
label: "切换代码编辑器", label: "富文本编辑器",
to: 0, value: "cl-editor-wang"
component: "cl-editor-wang"
} }
] ]
}); });
const componentName = computed(() => {
return isComponent(tab.value) ? tab.value : "el-input";
});
// cl-crud // cl-crud
const Crud = useCrud({ service: service.base.sys.param }, (app) => { const Crud = useCrud({ service: service.base.sys.param }, (app) => {
app.refresh(); app.refresh();
@ -153,41 +162,19 @@ const Upsert = useUpsert({
], ],
onOpened(data) { onOpened(data) {
tab.index = null; tab.value = /<*>/g.test(data.data) ? tab.list[1].value : tab.list[0].value;
nextTick(() => {
if (Upsert.value?.mode == "add") {
tab.index = 1;
} else {
tab.index = /<*>/g.test(data.data) ? 1 : 0;
}
});
} }
}); });
// //
function changeTab(i: number) { function onTabChange(name: any) {
ElMessageBox.confirm("切换编辑器会清空输入内容,是否继续?", "提示", { ElMessageBox.confirm("切换编辑器会清空输入内容,是否继续?", "提示", {
type: "warning" type: "warning"
}) })
.then(() => { .then(() => {
tab.index = i; tab.value = name;
Upsert.value?.setForm("data", ""); Upsert.value?.setForm("data", "");
}) })
.catch(() => null); .catch(() => null);
} }
</script> </script>
<style lang="scss" scoped>
.change-btn {
display: flex;
position: absolute;
right: 10px;
bottom: 10px;
z-index: 9;
}
.editor {
transition: all 0.3s;
}
</style>

View File

@ -1,14 +1,14 @@
<template> <template>
<cl-crud ref="Crud"> <cl-crud ref="Crud">
<el-row> <cl-row>
<cl-refresh-btn /> <cl-refresh-btn />
<cl-add-btn /> <cl-add-btn />
<cl-multi-delete-btn /> <cl-multi-delete-btn />
<cl-flex1 /> <cl-flex1 />
<cl-search-key /> <cl-search-key />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-table <cl-table
ref="Table" ref="Table"
:default-sort="{ :default-sort="{
@ -16,12 +16,12 @@
order: 'descending' order: 'descending'
}" }"
/> />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-flex1 /> <cl-flex1 />
<cl-pagination /> <cl-pagination />
</el-row> </cl-row>
<cl-upsert ref="Upsert"> <cl-upsert ref="Upsert">
<template #slot-relevance="{ scope }"> <template #slot-relevance="{ scope }">

View File

@ -1,35 +1,28 @@
<template> <template>
<cl-view-group :title="title"> <cl-view-group ref="ViewGroup">
<template #left> <template #left>
<dept-tree @row-click="onDeptRowClick" @user-add="onDeptUserAdd" /> <dept-tree @refresh="refresh" @user-add="onUserAdd" />
</template> </template>
<template #right> <template #right>
<cl-crud ref="Crud"> <cl-crud ref="Crud">
<el-row> <cl-row>
<cl-refresh-btn /> <cl-refresh-btn />
<cl-add-btn /> <cl-add-btn />
<cl-multi-delete-btn /> <cl-multi-delete-btn />
<el-button <el-button
v-permission="service.base.sys.user.permission.move" v-permission="service.base.sys.user.permission.move"
type="success" type="success"
:disabled="selects.ids.length == 0" :disabled="Table?.selection.length == 0"
@click="toMove()" @click="toMove()"
>转移</el-button >转移</el-button
> >
<cl-flex1 /> <cl-flex1 />
<cl-search-key /> <cl-search-key placeholder="搜索用户名、姓名" />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-table <cl-table ref="Table">
ref="Table"
:default-sort="{
prop: 'createTime',
order: 'descending'
}"
@selection-change="onSelectionChange"
>
<!-- 权限 --> <!-- 权限 -->
<template #column-roleName="{ scope }"> <template #column-roleName="{ scope }">
<template v-if="scope.row.roleName"> <template v-if="scope.row.roleName">
@ -56,12 +49,12 @@
> >
</template> </template>
</cl-table> </cl-table>
</el-row> </cl-row>
<el-row> <cl-row>
<cl-flex1 /> <cl-flex1 />
<cl-pagination /> <cl-pagination />
</el-row> </cl-row>
<!-- 新增编辑 --> <!-- 新增编辑 -->
<cl-upsert ref="Upsert" /> <cl-upsert ref="Upsert" />
@ -75,23 +68,13 @@
<script lang="ts" name="sys-user" setup> <script lang="ts" name="sys-user" setup>
import { useTable, useUpsert, useCrud } from "@cool-vue/crud"; import { useTable, useUpsert, useCrud } from "@cool-vue/crud";
import { computed, reactive } from "vue";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import { useViewGroup } from "../hooks";
import DeptMove from "./components/dept/move.vue"; import DeptMove from "./components/dept/move.vue";
import DeptTree from "./components/dept/tree.vue"; import DeptTree from "./components/dept/tree.vue";
const { service, refs, setRefs } = useCool(); const { service, refs, setRefs } = useCool();
const { ViewGroup } = useViewGroup();
//
const selects = reactive<any>({
dept: {},
ids: []
});
//
const title = computed(() => {
return `成员列表(${selects.dept?.name || ""}`;
});
// cl-crud // cl-crud
const Crud = useCrud({ const Crud = useCrud({
@ -113,13 +96,13 @@ const Table = useTable({
} }
}, },
{ {
prop: "name", prop: "username",
label: "名", label: "用户名",
minWidth: 150 minWidth: 150
}, },
{ {
prop: "username", prop: "name",
label: "用户名", label: "名",
minWidth: 150 minWidth: 150
}, },
{ {
@ -159,7 +142,7 @@ const Table = useTable({
{ {
prop: "createTime", prop: "createTime",
label: "创建时间", label: "创建时间",
sortable: "custom", sortable: "desc",
minWidth: 160 minWidth: 160
}, },
{ {
@ -299,63 +282,48 @@ const Upsert = useUpsert({
onSubmit(data, { next }) { onSubmit(data, { next }) {
next({ next({
...data, ...data,
departmentId: selects.dept?.id departmentId: ViewGroup.value?.selected?.id
}); });
}, },
async onOpen() { async onOpen() {
const list = await service.base.sys.role.list();
// //
Upsert.value?.setOptions( service.base.sys.role.list().then((res) => {
"roleIdList", Upsert.value?.setOptions(
list.map((e) => { "roleIdList",
return { res.map((e) => {
label: e.name || "", return {
value: e.id label: e.name || "",
}; value: e.id
}) };
); })
);
});
} }
}); });
// //
function refresh(params: any) { function refresh(params?: any) {
Crud.value?.refresh(params); Crud.value?.refresh(params);
} }
// //
function onSelectionChange(selection: any[]) { function onUserAdd({ id }: Eps.BaseSysDepartmentEntity) {
selects.ids = selection.map((e) => e.id);
}
//
function onDeptRowClick({ item, ids }: any) {
selects.dept = item;
refresh({
page: 1,
departmentIds: ids
});
}
//
function onDeptUserAdd(item: any) {
Crud.value?.rowAppend({ Crud.value?.rowAppend({
departmentId: item.id departmentId: id
}); });
} }
// //
async function toMove(e?: any) { async function toMove(item?: Eps.BaseSysDepartmentEntity) {
let ids = []; let ids = [];
if (!e) { if (item) {
ids = selects.ids; ids = [item.id];
} else { } else {
ids = [e.id]; ids = Table.value?.selection.map((e) => e.id) || [];
} }
refs.value.deptMove.open(ids); refs.deptMove.open(ids);
} }
</script> </script>

View File

@ -13,8 +13,6 @@
height="630px" height="630px"
width="1200px" width="1200px"
keep-alive keep-alive
:draggable="false"
custom-class="cl-chat__dialog"
:close-on-click-modal="false" :close-on-click-modal="false"
close-on-press-escape close-on-press-escape
append-to-body append-to-body
@ -23,7 +21,7 @@
<div <div
class="cl-chat" class="cl-chat"
:class="{ :class="{
'is-mini': app.browser.isMini, 'is-mini': browser.isMini,
'is-expand': isExpand 'is-expand': isExpand
}" }"
> >
@ -52,9 +50,9 @@
</template> </template>
<script lang="ts" name="cl-chat" setup> <script lang="ts" name="cl-chat" setup>
import { nextTick, provide, ref, watch } from "vue"; import { nextTick, provide, ref } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useCool, config, module } from "/@/cool"; import { useCool, config, module, useBrowser } from "/@/cool";
import { useBase } from "/$/base"; import { useBase } from "/$/base";
import { Notebook, ArrowLeft, BellFilled } from "@element-plus/icons-vue"; import { Notebook, ArrowLeft, BellFilled } from "@element-plus/icons-vue";
import { debounce } from "lodash-es"; import { debounce } from "lodash-es";
@ -66,12 +64,13 @@ import { Chat } from "../types";
import { useStore } from "../store"; import { useStore } from "../store";
const { mitt } = useCool(); const { mitt } = useCool();
const { browser, onScreenChange } = useBrowser();
// //
const { session, message } = useStore(); const { session, message } = useStore();
// //
const { app, user } = useBase(); const { user } = useBase();
// //
const { options } = module.get("chat"); const { options } = module.get("chat");
@ -83,7 +82,7 @@ const visible = ref(false);
const isExpand = ref(true); const isExpand = ref(true);
// //
const unCount = ref(parseInt(Math.random() * 100)); const unCount = ref(parseInt(String(Math.random() * 100)));
// Socket // Socket
let socket: Socket; let socket: Socket;
@ -128,6 +127,11 @@ function close() {
visible.value = false; visible.value = false;
} }
//
function expand(value?: boolean) {
isExpand.value = value === undefined ? !isExpand.value : value;
}
// //
function send(data: Chat.Message, isAppend?: boolean) { function send(data: Chat.Message, isAppend?: boolean) {
// socket.emit("message", {}); // socket.emit("message", {});
@ -174,19 +178,15 @@ provide("chat", {
return socket; return socket;
}, },
send, send,
append,
expand,
scrollToBottom scrollToBottom
}); });
// //
watch( onScreenChange(() => {
() => app.browser.isMini, isExpand.value = browser.isMini ? false : true;
(val) => { });
isExpand.value = val ? false : true;
},
{
immediate: true
}
);
defineExpose({ defineExpose({
open, open,
@ -212,12 +212,6 @@ defineExpose({
} }
} }
&__dialog {
.el-dialog__body {
padding: 0;
}
}
&__footer { &__footer {
padding: 9px 0; padding: 9px 0;
} }

View File

@ -140,7 +140,7 @@ const previewUrls = computed(() =>
// //
function onTextSend() { function onTextSend() {
chat?.send( chat.send(
{ {
contentType: 0, contentType: 0,
content: { content: {
@ -154,7 +154,7 @@ function onTextSend() {
// //
function onImageSend(res: any) { function onImageSend(res: any) {
chat?.send( chat.send(
{ {
contentType: 1, contentType: 1,
content: { content: {

View File

@ -67,9 +67,10 @@ const list = computed(() => session?.list.filter((e) => e.nickName?.includes(key
// //
async function toDetail(item: Chat.Session) { async function toDetail(item: Chat.Session) {
chat.expand(false);
session.set(item); session.set(item);
await message.get({ page: 1 }); await message.get({ page: 1 });
chat?.scrollToBottom(); chat.scrollToBottom();
} }
</script> </script>

View File

@ -2,7 +2,7 @@ import { inject } from "vue";
import { Chat } from "../types"; import { Chat } from "../types";
export function useChat() { export function useChat() {
const chat = inject<Chat.Provide>("chat"); const chat = inject("chat") as Chat.Provide;
return { return {
chat chat

View File

@ -30,6 +30,7 @@ export namespace Chat {
socket?: Socket; socket?: Socket;
send(data: Message, isAppend?: boolean): void; send(data: Message, isAppend?: boolean): void;
append(data: Message): void; append(data: Message): void;
expand(boolean?: boolean): void;
scrollToBottom(): void; scrollToBottom(): void;
} }
} }

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="scope"> <div class="scope">
<div class="h"> <div class="h">
<span>cl-editor-quill</span> <span>cl-editor</span>
Quill 富文本编辑器 编辑器
</div> </div>
<div class="c"> <div class="c">
<router-link to="/editor-quill">传送门</router-link> <router-link to="/editor-quill">传送门</router-link>

View File

@ -0,0 +1,14 @@
<template>
<div class="scope">
<div class="h">
<span>file</span>
文件列表
</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

@ -3,8 +3,8 @@
<cl-form ref="Form"> <cl-form ref="Form">
<template #slot-crud> <template #slot-crud>
<cl-crud ref="Crud"> <cl-crud ref="Crud" padding="0">
<el-row> <cl-row>
<!-- 刷新按钮 --> <!-- 刷新按钮 -->
<cl-refresh-btn /> <cl-refresh-btn />
<!-- 新增按钮 --> <!-- 新增按钮 -->
@ -14,18 +14,18 @@
<cl-flex1 /> <cl-flex1 />
<!-- 关键字搜索 --> <!-- 关键字搜索 -->
<cl-search-key /> <cl-search-key />
</el-row> </cl-row>
<el-row> <cl-row>
<!-- 数据表格 --> <!-- 数据表格 -->
<cl-table ref="Table" /> <cl-table ref="Table" />
</el-row> </cl-row>
<el-row> <cl-row>
<cl-flex1 /> <cl-flex1 />
<!-- 分页控件 --> <!-- 分页控件 -->
<cl-pagination /> <cl-pagination />
</el-row> </cl-row>
<!-- 新增编辑 --> <!-- 新增编辑 -->
<cl-upsert ref="Upsert" /> <cl-upsert ref="Upsert" />
@ -36,6 +36,9 @@
<script lang="ts" name="菜单名称" setup> <script lang="ts" name="菜单名称" setup>
import { useCrud, useForm, useTable, useUpsert } from "@cool-vue/crud"; import { useCrud, useForm, useTable, useUpsert } from "@cool-vue/crud";
import { useCool } from "/@/cool";
const { refs, setRefs } = useCool();
// cl-upsert // cl-upsert
const Upsert = useUpsert({ const Upsert = useUpsert({
@ -92,18 +95,34 @@ const Form = useForm();
function open() { function open() {
Form.value?.open({ Form.value?.open({
title: "内嵌crud", title: "自定义表单",
props: {
labelPosition: "top"
},
items: [ items: [
{ {
label: "", label: "获取 ref打开后聚焦",
props: { prop: "name",
labelWidth: "0" component: {
}, name: "el-input",
ref: setRefs("name"),
props: {
placeholder: "请填写昵称"
}
}
},
{
label: "内嵌 cl-crud",
component: { component: {
name: "slot-crud" name: "slot-crud"
} }
} }
] ],
on: {
open() {
refs.name.focus();
}
}
}); });
} }
</script> </script>

View File

@ -1,9 +1,10 @@
<template> <template>
<cl-crud ref="Crud"> <cl-crud ref="Crud">
<el-row> <cl-row>
<cl-refresh-btn /> <cl-refresh-btn />
<cl-add-btn /> <cl-add-btn />
<form-crud /> <cl-multi-delete-btn />
<form-btn />
<cl-filter label="字典筛选"> <cl-filter label="字典筛选">
<cl-select :options="dict.get('brand')" prop="brand" /> <cl-select :options="dict.get('brand')" prop="brand" />
@ -12,28 +13,49 @@
<cl-flex1 /> <cl-flex1 />
<cl-column-custom :columns="Table?.columns" /> <cl-column-custom :columns="Table?.columns" />
<cl-search-key /> <cl-search-key />
</el-row> <cl-adv-btn />
</cl-row>
<el-row> <cl-row>
<cl-table ref="Table" /> <cl-table ref="Table" show-summary :summary-method="onSummaryMethod">
</el-row> <template #column-detail="{ scope }">
<div style="padding: 0 10px">展开信息 - {{ scope.row.name }}</div>
</template>
</cl-table>
</cl-row>
<el-row> <cl-row>
<cl-flex1 /> <cl-flex1 />
<cl-pagination /> <cl-pagination />
</el-row> </cl-row>
<cl-upsert ref="Upsert" /> <cl-upsert ref="Upsert" />
<cl-adv-search ref="AdvSearch" />
</cl-crud> </cl-crud>
</template> </template>
<script lang="tsx" setup name="crud"> <script lang="tsx" setup name="crud">
import { useCrud, useUpsert, useTable } from "@cool-vue/crud"; import { useCrud, useUpsert, useTable, useAdvSearch } from "@cool-vue/crud";
import { useDict } from "/$/dict"; import { useDict } from "/$/dict";
import FormCrud from "../components/form-crud.vue"; import FormBtn from "../components/form.vue";
import { reactive } from "vue";
const { dict } = useDict(); const { dict } = useDict();
const options = reactive({
status: [
{
label: "开启",
value: 1
},
{
label: "关闭",
type: "danger",
value: 0
}
]
});
const Crud = useCrud( const Crud = useCrud(
{ {
service: "test" service: "test"
@ -43,19 +65,13 @@ const Crud = useCrud(
} }
); );
//
const Upsert = useUpsert({ const Upsert = useUpsert({
items: [ items: [
{
label: "姓名",
prop: "name",
required: true,
component: {
name: "el-input"
}
},
{ {
type: "tabs", type: "tabs",
props: { props: {
type: "card",
labels: [ labels: [
{ {
label: "基础", label: "基础",
@ -68,6 +84,15 @@ const Upsert = useUpsert({
] ]
} }
}, },
{
label: "姓名",
prop: "name",
required: true,
group: "base",
component: {
name: "el-input"
}
},
{ {
label: "认证类型", label: "认证类型",
prop: "authType", prop: "authType",
@ -106,36 +131,39 @@ const Upsert = useUpsert({
} }
}); });
//
const Table = useTable({ const Table = useTable({
columns: [ columns: [
{ {
type: "selection" type: "selection",
width: 60
},
() => {
return {
label: "#",
type: "expand",
prop: "detail"
};
}, },
{ {
label: "姓名", label: "基础信息",
prop: "name" prop: "baseInfo",
}, children: [
{ {
label: "存款", label: "姓名",
prop: "price", prop: "name"
formatter(row) { },
return `¥${row.price}`; {
} label: "存款(元)",
prop: "price",
sortable: true
}
]
}, },
{ {
label: "状态", label: "状态",
prop: "status", prop: "status",
dict: [ dict: options.status
{
label: "开启",
value: 1
},
{
label: "关闭",
type: "danger",
value: 0
}
]
}, },
{ {
label: "创建时间", label: "创建时间",
@ -149,4 +177,21 @@ const Table = useTable({
} }
] ]
}); });
function onSummaryMethod({ data }: { data: any[] }) {
return ["合计", "", "", data.reduce((a, b) => parseFloat(a + Number(b.price)), 0).toFixed(2)];
}
//
const AdvSearch = useAdvSearch({
items: [
{
label: "昵称",
prop: "name",
component: {
name: "el-input"
}
}
]
});
</script> </script>

View File

@ -12,11 +12,12 @@
import ContextMenu from "../components/context-menu.vue"; import ContextMenu from "../components/context-menu.vue";
import Crud from "../components/crud.vue"; import Crud from "../components/crud.vue";
import Upload from "../components/upload.vue"; import Upload from "../components/upload.vue";
import EditorQuill from "../components/editor-quill.vue"; import Editor from "../components/editor.vue";
import Svg from "../components/svg.vue"; import Svg from "../components/svg.vue";
import Copy from "../components/copy.vue"; import Copy from "../components/copy.vue";
import File from "../components/file.vue";
const list = [ContextMenu, Crud, Upload, EditorQuill, Svg, Copy]; const list = [ContextMenu, Crud, Upload, Editor, Svg, Copy, File];
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -1,12 +1,16 @@
<template> <template>
<div class="editor"> <div class="editor">
<el-tabs type="card"> <el-tabs type="card">
<el-tab-pane label="WangEditor"> <el-tab-pane label="Wang">
<cl-editor-wang v-model="w" :height="400" /> <cl-editor-wang v-model="v1" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="Quill" lazy> <el-tab-pane label="Quill" lazy>
<cl-editor-quill v-model="q" :height="400" /> <cl-editor-quill v-model="v2" />
</el-tab-pane>
<el-tab-pane label="Monaco" lazy>
<cl-editor-monaco v-model="v3" language="typescript" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
@ -14,8 +18,9 @@
<script lang="ts" setup name="editor-quill"> <script lang="ts" setup name="editor-quill">
import { ref } from "vue"; import { ref } from "vue";
const q = ref("Quill"); const v1 = ref("富文本编辑器 cl-editor-wang");
const w = ref("Wang"); const v2 = ref("富文本编辑器 cl-editor-quill");
const v3 = ref(`// 代码编辑器\r\n const data = { name: "cl-editor-monaco" }`);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -43,9 +43,9 @@
<div class="hot-search__table"> <div class="hot-search__table">
<cl-crud ref="Crud" padding="0"> <cl-crud ref="Crud" padding="0">
<el-row> <cl-row>
<cl-table ref="Table" :border="false" /> <cl-table ref="Table" :border="false" />
</el-row> </cl-row>
</cl-crud> </cl-crud>
</div> </div>
</div> </div>

View File

@ -2,19 +2,23 @@
<div class="demo"> <div class="demo">
<el-tabs type="card"> <el-tabs type="card">
<el-tab-pane label="普通上传"> <el-tab-pane label="普通上传">
<cl-upload v-model="urls" /> <cl-upload v-model="v1" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="多图上传" lazy> <el-tab-pane label="多图上传" lazy>
<cl-upload text="选择图片" v-model="urls" multiple drag /> <cl-upload v-model="v2" text="选择图片" multiple />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="文件上传" lazy> <el-tab-pane label="文件上传" lazy>
<cl-upload v-model="urls" multiple text="文件上传" type="file" /> <cl-upload v-model="v3" multiple text="文件上传" type="file" />
</el-tab-pane>
<el-tab-pane label="可拖拽">
<cl-upload multiple draggable />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="自定义内容"> <el-tab-pane label="自定义内容">
<cl-upload text="选择图片" multiple drag custom-class="custom-upload"> <cl-upload type="file" multiple draggable custom-class="custom-upload">
<el-button :icon="Upload">上传</el-button> <el-button :icon="Upload">上传</el-button>
<template #item="{ item }"> <template #item="{ item }">
@ -24,11 +28,19 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="自定义大小"> <el-tab-pane label="自定义大小">
<cl-upload text="选择图片" :size="[120, 200]" /> <cl-upload :size="[120, 200]" />
</el-tab-pane>
<el-tab-pane label="上传校验">
<cl-upload :before-upload="onBeforeUpload" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="文件空间"> <el-tab-pane label="文件空间">
<cl-upload-space /> <cl-upload-space v-model="v4" :limit="3" @confirm="onConfirm" />
<div class="space-upload">
<el-image fit="cover" v-for="(item, index) in v4" :key="index" :src="item" />
</div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
@ -37,8 +49,31 @@
<script lang="ts" name="upload" setup> <script lang="ts" name="upload" setup>
import { ref } from "vue"; import { ref } from "vue";
import { Upload } from "@element-plus/icons-vue"; import { Upload } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
const urls = ref(""); const v1 = ref("");
const v2 = ref(
"https://show.cool-admin.com/api/public/uploads/20230106/903f6398-63ab-4a6b-b01b-2ae4c0fd4261_u=3956957950%2C1274376364&fm=253&fmt=auto&app=138&f=JPEG.webp,https://show.cool-admin.com/api/public/uploads/20230106/1c6e70a7-37f5-41c9-881e-46b625a9696a_u=672347980%2C1207046361&fm=253&fmt=auto&app=138&f=JPEG.webp,https://show.cool-admin.com/api/public/uploads/20230106/8c06206a-dc3e-48c0-8d13-7f7905a6dc6c_u=1536642037%2C1165863875&fm=253&fmt=auto&app=138&f=JPEG.webp,https://show.cool-admin.com/api/public/uploads/20230106/387e8865-4a29-4e91-b491-90a58a870469_src=http___img.zcool.cn_community_01bb3b590aa3c3a8012145509011b7.jpg@1280w_1l_2o_100sh.jpg&refer=http___img.zcool.webp,https://show.cool-admin.com/api/public/uploads/20230106/88ecaa19-7ab1-4354-bd0d-f755db322f9b_src=http___img.zcool.cn_community_0113e05efed248a801215aa0326aec.jpg@2o.jpg&refer=http___img.zcool.webp,https://show.cool-admin.com/api/public/uploads/20230106/2a472e03-8a72-4d08-9171-74e3e3d4f3e3_src=http___img.zcool.cn_community_0120d15e01c068a80120a895b5bf80.png@1280w_1l_2o_100sh.png&refer=http___img.zcool.webp"
);
const v3 = ref("");
const v4 = ref<string[]>([]);
function onConfirm(list: any[]) {
v4.value = list.map((e) => e.url);
}
function onBeforeUpload(file: any, item: any) {
console.log(file, item);
ElMessage.warning("文件检测中");
return new Promise((resolve) => {
setTimeout(() => {
ElMessage.success("文件检测通过");
resolve(true);
}, 2000);
});
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -53,14 +88,20 @@ const urls = ref("");
border: 1px solid var(--el-border-color); border: 1px solid var(--el-border-color);
border-radius: 3px; border-radius: 3px;
padding: 5px 10px; padding: 5px 10px;
margin-top: 10px; margin-bottom: 10px;
font-size: 12px; font-size: 12px;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
}
.cl-upload__list { .space-upload {
width: 100%; margin-top: 10px;
.el-image {
margin-right: 10px;
height: 100px;
width: 100px;
} }
} }
} }

View File

@ -20,7 +20,7 @@
:key="index" :key="index"
class="item" class="item"
:class="{ :class="{
'is-active': active == item.id 'is-active': ViewGroup?.selected?.id == item.id
}" }"
@click="select(item)" @click="select(item)"
@contextmenu=" @contextmenu="
@ -30,7 +30,9 @@
" "
> >
<span>{{ item.name }} - {{ item.key }}</span> <span>{{ item.name }} - {{ item.key }}</span>
<el-icon v-show="active == item.id"><arrow-right-bold /></el-icon> <el-icon v-show="ViewGroup?.selected?.id == item.id"
><arrow-right-bold
/></el-icon>
</li> </li>
<el-empty v-if="list.length == 0" :image-size="80" /> <el-empty v-if="list.length == 0" :image-size="80" />
@ -46,37 +48,32 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ContextMenu, 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 { inject, onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import { ArrowRightBold } from "@element-plus/icons-vue"; import { ArrowRightBold } from "@element-plus/icons-vue";
import { checkPerm } from "/$/base"; import { checkPerm, useViewGroup } from "/$/base";
const emit = defineEmits(["refresh"]);
const { service } = useCool(); const { service } = useCool();
const { ViewGroup } = useViewGroup();
const Form = useForm(); const Form = useForm();
//
const dict = inject<any>("dict");
//
const active = ref("");
// //
const list = ref<any[]>([]); const list = ref<Eps.DictTypeEntity[]>([]);
// //
const loading = ref(false); const loading = ref(false);
const viewGroup = inject<any>("viewGroup");
// //
async function refresh() { async function refresh() {
loading.value = true; loading.value = true;
await service.dict.type.list().then((res) => { await service.dict.type.list({ order: "createTime", sort: "asc" }).then((res) => {
list.value = res; list.value = res;
if (!active.value) { if (!ViewGroup.value?.selected) {
select(res[0]); select();
} }
}); });
@ -84,23 +81,24 @@ async function refresh() {
} }
// //
function select(item: any) { function select(item?: Eps.DictTypeEntity) {
active.value = item?.id; if (!item) {
item = list.value[0];
}
// if (item) {
dict.setGroup(item); emit("refresh", {
typeId: item.id,
page: 1
});
// ViewGroup.value?.select(item);
dict.refresh({ ViewGroup.value?.setTitle(`字典列表(${item.name}`);
typeId: active.value, }
page: 1
});
viewGroup.checkExpand(false);
} }
// //
function edit(item?: any) { function edit(item?: Eps.DictTypeEntity) {
Form.value?.open({ Form.value?.open({
title: item ? "编辑类型" : "添加类型", title: item ? "编辑类型" : "添加类型",
width: "500px", width: "500px",
@ -152,7 +150,7 @@ function edit(item?: any) {
} }
// //
function onContextMenu(e: any, item: any) { function onContextMenu(e: any, item: Eps.DictTypeEntity) {
ContextMenu.open(e, { ContextMenu.open(e, {
hover: { hover: {
target: "item" target: "item"
@ -187,8 +185,8 @@ function onContextMenu(e: any, item: any) {
await refresh(); await refresh();
// //
if (active.value == item.id) { if (ViewGroup.value?.selected?.id == item.id) {
select(list.value[0]); select();
} }
}) })
.catch((err) => { .catch((err) => {
@ -225,42 +223,45 @@ onMounted(() => {
.list { .list {
height: calc(100% - 40px); height: calc(100% - 40px);
padding: 10px; padding: 0 10px;
box-sizing: border-box; box-sizing: border-box;
}
ul { ul {
li { height: 100%;
display: flex;
align-items: center;
width: 100%;
list-style: none;
box-sizing: border-box;
padding: 10px 35px 10px 10px;
cursor: pointer;
font-size: 14px;
margin-bottom: 10px;
border-radius: 3px;
color: #666;
position: relative;
background-color: #f7f7f7;
.el-icon { li {
position: absolute; display: flex;
right: 10px !important; align-items: center;
list-style: none;
box-sizing: border-box;
padding: 10px 35px 10px 10px;
cursor: pointer;
font-size: 13px;
margin-bottom: 10px;
border-radius: 3px;
color: #666;
position: relative;
background-color: #f7f7f7;
.el-icon {
position: absolute;
right: 10px !important;
}
&.is-active {
background-color: var(--color-primary);
color: #fff;
}
&:hover {
opacity: 0.8;
}
} }
&:last-child { &::after {
margin-bottom: 0; display: block;
} content: "";
height: 1px;
&.is-active {
background-color: var(--color-primary);
color: #fff;
}
&:hover {
opacity: 0.8;
} }
} }
} }

View File

@ -1,32 +1,24 @@
<template> <template>
<cl-view-group :title="title"> <cl-view-group ref="ViewGroup">
<template #left> <template #left>
<dict-group /> <dict-group @refresh="refresh" />
</template> </template>
<template #right> <template #right>
<cl-crud ref="Crud"> <cl-crud ref="Crud">
<el-row> <cl-row>
<!-- 刷新按钮 --> <!-- 刷新按钮 -->
<cl-refresh-btn /> <cl-refresh-btn />
<!-- 新增按钮 --> <!-- 新增按钮 -->
<cl-add-btn :disabled="!group" /> <cl-add-btn :disabled="!ViewGroup?.selected" />
<cl-flex1 /> <cl-flex1 />
<!-- 关键字搜索 --> <!-- 关键字搜索 -->
<cl-search-key /> <cl-search-key />
</el-row> </cl-row>
<el-row> <cl-row>
<!-- 数据表格 --> <!-- 数据表格 -->
<cl-table <cl-table ref="Table" row-key="id" @row-click="onRowClick">
ref="Table"
:default-sort="{
prop: 'orderNum',
order: 'ascending'
}"
row-key="id"
@row-click="onRowClick"
>
<template #slot-btn="{ scope }"> <template #slot-btn="{ scope }">
<el-button <el-button
text text
@ -38,11 +30,11 @@
> >
</template> </template>
</cl-table> </cl-table>
</el-row> </cl-row>
<el-row> <cl-row>
<cl-flex1 /> <cl-flex1 />
</el-row> </cl-row>
<!-- 新增编辑 --> <!-- 新增编辑 -->
<cl-upsert ref="Upsert" /> <cl-upsert ref="Upsert" />
@ -55,19 +47,13 @@
import { useCrud, useTable, useUpsert } from "@cool-vue/crud"; import { useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import DictGroup from "../components/group.vue"; import DictGroup from "../components/group.vue";
import { computed, provide, ref } from "vue"; import { computed } from "vue";
import { deepTree } from "/@/cool/utils"; import { deepTree } from "/@/cool/utils";
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
import { useViewGroup } from "/$/base";
const { service } = useCool(); const { service } = useCool();
const { ViewGroup } = useViewGroup();
//
const group = ref();
//
const title = computed(() => {
return group.value ? `字典列表(${group.value.name}` : "字典列表";
});
// cl-upsert // cl-upsert
const Upsert = useUpsert({ const Upsert = useUpsert({
@ -141,7 +127,7 @@ const Upsert = useUpsert({
onSubmit(data, { next }) { onSubmit(data, { next }) {
next({ next({
...data, ...data,
typeId: group.value.id typeId: ViewGroup.value?.selected?.id
}); });
} }
}); });
@ -167,7 +153,7 @@ const Table = useTable({
], ],
columns: [ columns: [
{ label: "名称", prop: "name", align: "left", minWidth: 200 }, { label: "名称", prop: "name", align: "left", minWidth: 200 },
{ label: "排序", prop: "orderNum", sortable: "custom", width: 100 }, { label: "排序", prop: "orderNum", sortable: "desc", width: 100 },
{ label: "备注", prop: "remark", showOverflowTooltip: true, minWidth: 150 }, { label: "备注", prop: "remark", showOverflowTooltip: true, minWidth: 150 },
{ label: "创建时间", prop: "createTime", sortable: "custom", minWidth: 160 }, { label: "创建时间", prop: "createTime", sortable: "custom", minWidth: 160 },
{ label: "更新时间", prop: "updateTime", sortable: "custom", minWidth: 160 }, { label: "更新时间", prop: "updateTime", sortable: "custom", minWidth: 160 },
@ -185,7 +171,6 @@ const Crud = useCrud({
onRefresh(params, { render }) { onRefresh(params, { render }) {
service.dict.info service.dict.info
.list({ .list({
typeId: group.value?.id,
...params, ...params,
page: undefined, page: undefined,
size: undefined size: undefined
@ -201,11 +186,6 @@ function refresh(params?: any) {
Crud.value?.refresh(params); Crud.value?.refresh(params);
} }
//
function setGroup(data: any) {
group.value = data;
}
// //
function onRowClick(row: any, column: any) { function onRowClick(row: any, column: any) {
if (column?.property && row.children) { if (column?.property && row.children) {
@ -220,10 +200,4 @@ function append(row: any) {
orderNum: 1 orderNum: 1
}); });
} }
provide("dict", {
group,
refresh,
setGroup
});
</script> </script>

View File

@ -0,0 +1,121 @@
<template>
<div
class="cl-editor-monaco"
:class="{
disabled
}"
ref="Editor"
:style="{ height: parsePx(height) }"
></div>
</template>
<script lang="ts" setup name="cl-editor-monaco">
import { onMounted, onUnmounted, ref, watch } from "vue";
import * as Monaco from "monaco-editor/esm/vs/editor/editor.api";
import "./worker";
import "./theme";
import { deepMerge, parsePx } from "/@/cool/utils";
const props = defineProps({
modelValue: String,
options: Object,
height: {
type: [String, Number],
default: 400
},
autofocus: {
type: Boolean,
default: true
},
language: {
type: String,
default: "json"
},
disabled: Boolean
});
const emit = defineEmits(["update:modelValue", "change"]);
let monaco: Monaco.editor.IStandaloneCodeEditor | null = null;
const Editor = ref();
function setContent(value?: string) {
if (value != monaco?.getValue()) {
monaco?.setValue(value || "");
}
}
function formatCode() {
monaco?.getAction("editor.action.formatDocument").run();
}
function init() {
monaco = Monaco.editor.create(
Editor.value,
deepMerge(
{
language: props.language,
minimap: {
enabled: true
},
automaticLayout: true,
scrollbar: {
verticalScrollbarSize: 6
},
lineNumbersMinChars: 4,
fontSize: 14,
theme: "default",
scrollBeyondLastLine: false,
readOnly: props.disabled
},
props.options
)
);
monaco.onDidChangeModelContent(() => {
const value = monaco?.getValue();
emit("update:modelValue", value);
emit("change", value);
});
setContent(props.modelValue);
if (props.autofocus) {
setTimeout(() => {
monaco?.focus();
}, 10);
}
}
watch(() => props.modelValue, setContent);
watch(
() => props.disabled,
(val: boolean) => {
monaco?.updateOptions({
readOnly: val
});
}
);
onMounted(() => {
init();
});
onUnmounted(() => {
monaco?.dispose();
});
defineExpose({
monaco,
setContent,
formatCode
});
</script>
<style lang="scss" scoped>
.cl-editor-monaco {
border: 1px solid var(--el-border-color);
}
</style>

View File

@ -0,0 +1,350 @@
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
monaco.editor.defineTheme("default", {
base: "vs",
inherit: true,
rules: [
{
background: "ffffff",
token: ""
},
{
foreground: "6a737d",
token: "comment"
},
{
foreground: "6a737d",
token: "punctuation.definition.comment"
},
{
foreground: "6a737d",
token: "string.comment"
},
{
foreground: "005cc5",
token: "constant"
},
{
foreground: "005cc5",
token: "entity.name.constant"
},
{
foreground: "005cc5",
token: "variable.other.constant"
},
{
foreground: "005cc5",
token: "variable.language"
},
{
foreground: "6f42c1",
token: "entity"
},
{
foreground: "6f42c1",
token: "entity.name"
},
{
foreground: "24292e",
token: "variable.parameter.function"
},
{
foreground: "22863a",
token: "entity.name.tag"
},
{
foreground: "d73a49",
token: "keyword"
},
{
foreground: "d73a49",
token: "storage"
},
{
foreground: "d73a49",
token: "storage.type"
},
{
foreground: "24292e",
token: "storage.modifier.package"
},
{
foreground: "24292e",
token: "storage.modifier.import"
},
{
foreground: "24292e",
token: "storage.type.java"
},
{
foreground: "032f62",
token: "string"
},
{
foreground: "032f62",
token: "punctuation.definition.string"
},
{
foreground: "032f62",
token: "string punctuation.section.embedded source"
},
{
foreground: "005cc5",
token: "support"
},
{
foreground: "005cc5",
token: "meta.property-name"
},
{
foreground: "e36209",
token: "variable"
},
{
foreground: "24292e",
token: "variable.other"
},
{
foreground: "b31d28",
fontStyle: "bold italic underline",
token: "invalid.broken"
},
{
foreground: "b31d28",
fontStyle: "bold italic underline",
token: "invalid.deprecated"
},
{
foreground: "fafbfc",
background: "b31d28",
fontStyle: "italic underline",
token: "invalid.illegal"
},
{
foreground: "fafbfc",
background: "d73a49",
fontStyle: "italic underline",
token: "carriage-return"
},
{
foreground: "b31d28",
fontStyle: "bold italic underline",
token: "invalid.unimplemented"
},
{
foreground: "b31d28",
token: "message.error"
},
{
foreground: "24292e",
token: "string source"
},
{
foreground: "005cc5",
token: "string variable"
},
{
foreground: "032f62",
token: "source.regexp"
},
{
foreground: "032f62",
token: "string.regexp"
},
{
foreground: "032f62",
token: "string.regexp.character-class"
},
{
foreground: "032f62",
token: "string.regexp constant.character.escape"
},
{
foreground: "032f62",
token: "string.regexp source.ruby.embedded"
},
{
foreground: "032f62",
token: "string.regexp string.regexp.arbitrary-repitition"
},
{
foreground: "22863a",
fontStyle: "bold",
token: "string.regexp constant.character.escape"
},
{
foreground: "005cc5",
token: "support.constant"
},
{
foreground: "005cc5",
token: "support.variable"
},
{
foreground: "005cc5",
token: "meta.module-reference"
},
{
foreground: "735c0f",
token: "markup.list"
},
{
foreground: "005cc5",
fontStyle: "bold",
token: "markup.heading"
},
{
foreground: "005cc5",
fontStyle: "bold",
token: "markup.heading entity.name"
},
{
foreground: "22863a",
token: "markup.quote"
},
{
foreground: "24292e",
fontStyle: "italic",
token: "markup.italic"
},
{
foreground: "24292e",
fontStyle: "bold",
token: "markup.bold"
},
{
foreground: "005cc5",
token: "markup.raw"
},
{
foreground: "b31d28",
background: "ffeef0",
token: "markup.deleted"
},
{
foreground: "b31d28",
background: "ffeef0",
token: "meta.diff.header.from-file"
},
{
foreground: "b31d28",
background: "ffeef0",
token: "punctuation.definition.deleted"
},
{
foreground: "22863a",
background: "f0fff4",
token: "markup.inserted"
},
{
foreground: "22863a",
background: "f0fff4",
token: "meta.diff.header.to-file"
},
{
foreground: "22863a",
background: "f0fff4",
token: "punctuation.definition.inserted"
},
{
foreground: "e36209",
background: "ffebda",
token: "markup.changed"
},
{
foreground: "e36209",
background: "ffebda",
token: "punctuation.definition.changed"
},
{
foreground: "f6f8fa",
background: "005cc5",
token: "markup.ignored"
},
{
foreground: "f6f8fa",
background: "005cc5",
token: "markup.untracked"
},
{
foreground: "6f42c1",
fontStyle: "bold",
token: "meta.diff.range"
},
{
foreground: "005cc5",
token: "meta.diff.header"
},
{
foreground: "005cc5",
fontStyle: "bold",
token: "meta.separator"
},
{
foreground: "005cc5",
token: "meta.output"
},
{
foreground: "586069",
token: "brackethighlighter.tag"
},
{
foreground: "586069",
token: "brackethighlighter.curly"
},
{
foreground: "586069",
token: "brackethighlighter.round"
},
{
foreground: "586069",
token: "brackethighlighter.square"
},
{
foreground: "586069",
token: "brackethighlighter.angle"
},
{
foreground: "586069",
token: "brackethighlighter.quote"
},
{
foreground: "b31d28",
token: "brackethighlighter.unmatched"
},
{
foreground: "b31d28",
token: "sublimelinter.mark.error"
},
{
foreground: "e36209",
token: "sublimelinter.mark.warning"
},
{
foreground: "959da5",
token: "sublimelinter.gutter-mark"
},
{
foreground: "032f62",
fontStyle: "underline",
token: "constant.other.reference.link"
},
{
foreground: "032f62",
fontStyle: "underline",
token: "string.other.link"
}
],
colors: {
"editor.foreground": "#24292e",
"editor.background": "#ffffff",
"editor.selectionBackground": "#c8c8fa",
"editor.inactiveSelectionBackground": "#fafbfc",
"editor.lineHighlightBackground": "#fafbfc",
"editorCursor.foreground": "#24292e",
"editorWhitespace.foreground": "#959da5",
"editorIndentGuide.background": "#959da5",
"editorIndentGuide.activeBackground": "#24292e",
"editor.selectionHighlightBorder": "#fafbfc"
}
});

View File

@ -0,0 +1,35 @@
import * as monaco from "monaco-editor";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
self.MonacoEnvironment = {
getWorker(_: any, label: string) {
if (label === "json") {
return new jsonWorker();
}
if (label === "css" || label === "scss" || label === "less") {
return new cssWorker();
}
if (label === "html" || label === "handlebars" || label === "razor") {
return new htmlWorker();
}
if (label === "typescript" || label === "javascript") {
return new tsWorker();
}
return new editorWorker();
}
};
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2016,
allowNonTsExtensions: true,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
module: monaco.languages.typescript.ModuleKind.CommonJS,
noEmit: true,
allowJs: false
});

View File

@ -7,7 +7,7 @@
ref="ImageSpace" ref="ImageSpace"
accept="image/*" accept="image/*"
:show-btn="false" :show-btn="false"
@confirm="onSpaceConfirm" @confirm="onFileConfirm"
/> />
<!-- 视频 --> <!-- 视频 -->
@ -15,7 +15,7 @@
ref="VideoSpace" ref="VideoSpace"
accept="video/*" accept="video/*"
:show-btn="false" :show-btn="false"
@confirm="onSpaceConfirm" @confirm="onFileConfirm"
/> />
</div> </div>
</template> </template>
@ -24,7 +24,7 @@
import { defineComponent, onMounted, ref, watch } from "vue"; import { defineComponent, onMounted, ref, watch } from "vue";
import Quill from "quill"; import Quill from "quill";
import "quill/dist/quill.snow.css"; import "quill/dist/quill.snow.css";
import { useComm } from "/@/cool"; import { parsePx } from "/@/cool/utils";
export default defineComponent({ export default defineComponent({
name: "cl-editor-quill", name: "cl-editor-quill",
@ -42,8 +42,6 @@ export default defineComponent({
emits: ["update:modelValue", "load"], emits: ["update:modelValue", "load"],
setup(props, { emit }) { setup(props, { emit }) {
const { px } = useComm();
let quill: any = null; let quill: any = null;
// //
@ -81,7 +79,7 @@ export default defineComponent({
} }
// //
function onSpaceConfirm(files: any[]) { function onFileConfirm(files: any[]) {
if (files.length > 0) { if (files.length > 0) {
// //
files.forEach((file, i) => { files.forEach((file, i) => {
@ -107,7 +105,7 @@ export default defineComponent({
// //
function setHeight() { function setHeight() {
quill.container.style.height = px(props.height); quill.container.style.height = parsePx(props.height);
} }
// //
@ -187,7 +185,7 @@ export default defineComponent({
quill, quill,
cursorIndex, cursorIndex,
setContent, setContent,
onSpaceConfirm onFileConfirm
}; };
} }
}); });

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="cl-editor-wang" :class="{ disabled }"> <div class="cl-editor-wang" :class="{ disabled }">
<!-- 工具栏 --> <!-- 工具栏 -->
<toolbar :editor="editorRef" :mode="mode" /> <toolbar :editor="Editor" :mode="mode" />
<!-- 编辑框 --> <!-- 编辑框 -->
<editor <editor
@ -17,37 +17,29 @@
<!-- 图片 --> <!-- 图片 -->
<cl-upload-space <cl-upload-space
ref="ImageSpace" :ref="setRefs('image')"
accept="image/*" accept="image/*"
:show-btn="false" :show-btn="false"
@confirm="onSpaceConfirm" @confirm="onFileConfirm"
/> />
<!-- 视频 --> <!-- 视频 -->
<cl-upload-space <cl-upload-space
ref="VideoSpace" :ref="setRefs('video')"
accept="video/*" accept="video/*"
:show-btn="false" :show-btn="false"
@confirm="onSpaceConfirm" @confirm="onFileConfirm"
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import "@wangeditor/editor/dist/css/style.css"; import "@wangeditor/editor/dist/css/style.css";
import { import { onBeforeUnmount, ref, shallowRef, watch, PropType, computed, defineComponent } from "vue";
onBeforeUnmount,
ref,
shallowRef,
watch,
PropType,
reactive,
computed,
defineComponent
} from "vue";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue"; import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { IEditorConfig } from "@wangeditor/editor"; import { IEditorConfig } from "@wangeditor/editor";
import { useComm } from "/@/cool"; import { useCool } from "/@/cool";
import { parsePx } from "/@/cool/utils";
export default defineComponent({ export default defineComponent({
name: "cl-editor-wang", name: "cl-editor-wang",
@ -73,16 +65,10 @@ export default defineComponent({
emits: ["update:modelValue", "change", "focus", "blur"], emits: ["update:modelValue", "change", "focus", "blur"],
setup(props, { emit }) { setup(props, { emit }) {
const { px } = useComm(); const { refs, setRefs } = useCool();
//
const ImageSpace = ref();
//
const VideoSpace = ref();
// //
const editorRef = shallowRef(); const Editor = shallowRef();
// //
const value = ref(); const value = ref();
@ -90,7 +76,7 @@ export default defineComponent({
// //
const style = computed(() => { const style = computed(() => {
return { return {
height: px(props.height) height: parsePx(props.height)
}; };
}); });
@ -104,13 +90,39 @@ export default defineComponent({
} }
); );
function onCreated(editor: any) { const temp: { insertFn: ((url: string) => void) | null } = {
editorRef.value = editor; insertFn: null
};
//
const editorConfig: Partial<IEditorConfig> = {
placeholder: "请输入",
MENU_CONF: {
uploadImage: {
customBrowseAndUpload(fn: any) {
temp.insertFn = fn;
refs.image.open();
}
},
uploadVideo: {
customBrowseAndUpload(fn: any) {
temp.insertFn = fn;
refs.video.open();
}
}
}
};
function onCreated(editor: any) {
Editor.value = editor;
onDisabled();
}
function onDisabled() {
if (props.disabled) { if (props.disabled) {
editor.disable(); Editor.value?.disable();
} else { } else {
editor.enable(); Editor.value?.enable();
} }
} }
@ -127,48 +139,28 @@ export default defineComponent({
emit("change", value.value); emit("change", value.value);
} }
const temp = reactive<any>({ function onFileConfirm(files: any[]) {
insertFn: null
});
//
const editorConfig: Partial<IEditorConfig> = {
placeholder: "请输入",
MENU_CONF: {
uploadImage: {
customBrowseAndUpload(insertFn: any) {
temp.insertFn = insertFn;
ImageSpace.value.open();
}
},
uploadVideo: {
customBrowseAndUpload(insertFn: any) {
temp.insertFn = insertFn;
VideoSpace.value.open();
}
}
}
};
//
function onSpaceConfirm(files: any[]) {
if (files.length > 0) { if (files.length > 0) {
files.forEach((file) => { files.forEach((file) => {
temp.insertFn(file.url); if (temp.insertFn) {
temp.insertFn(file.url);
}
}); });
} }
} }
onBeforeUnmount(() => { onBeforeUnmount(() => {
const editor = editorRef.value; const editor = Editor.value;
if (editor == null) return; if (editor == null) return;
editor.destroy(); editor.destroy();
}); });
watch(() => props.disabled, onDisabled);
return { return {
ImageSpace, refs,
VideoSpace, setRefs,
editorRef, Editor,
value, value,
style, style,
onCreated, onCreated,
@ -176,7 +168,7 @@ export default defineComponent({
onBlur, onBlur,
onChange, onChange,
editorConfig, editorConfig,
onSpaceConfirm onFileConfirm
}; };
} }
}); });
@ -198,5 +190,9 @@ export default defineComponent({
background-color: var(--el-disabled-bg-color); background-color: var(--el-disabled-bg-color);
} }
} }
&.w-e-full-screen-container {
z-index: 999;
}
} }
</style> </style>

View File

@ -0,0 +1,12 @@
import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => {
return {
// 根据使用情况选择组件,避免资源过大问题
components: [
() => import("./components/wang.vue"),
() => import("./components/quill.vue"),
() => import("./components/monaco/index.vue")
]
};
};

View File

@ -0,0 +1,158 @@
import { defineComponent, PropType } from "vue";
import { useCrud } from "@cool-vue/crud";
import { ElMessage } from "element-plus";
import { isRef, ref } from "vue";
import { currentDate, export_json_to_excel } from "../utils";
export default defineComponent({
name: "cl-export-btn",
props: {
filename: [Function, String],
autoWidth: {
type: Boolean,
default: true
},
bookType: {
type: String,
default: "xlsx"
},
header: Array,
columns: {
type: Array as PropType<any[]>,
required: true
},
data: [Function, Array],
maxExportLimit: Number // 最大导出条数不传或者小于等于0为不限制
},
setup(props) {
// 加载状态
const loading = ref(false);
// crud
const Crud = useCrud();
// 获取表头数据
async function getHeader(columns: any[], fields: any[]) {
return columns.filter((e) => !e.hidden && fields.includes(e.prop)).map((e) => e.label);
}
// 获取表格数据
function getData() {
if (typeof props.data === "function") {
return props.data();
} else {
if (props.data) {
return props.data;
} else {
return Crud.value?.service
.page({
...Crud.value?.paramsReplace(Crud.value.params),
maxExportLimit: props.maxExportLimit,
isExport: true
})
.then((res) => {
return res.list.map((e) => {
for (const i in e) {
const col = props.columns.find((c) => c.prop == i);
if (col) {
if (col.formatter) {
e = col.formatter(e);
}
if (col.dict) {
const d = (
isRef(col.dict) ? col.dict.value : col.dict
).find((d: any) => d.value == e[i]);
if (d) {
e[i] = d.label;
}
}
}
}
return e;
});
})
.catch((err) => {
ElMessage.error(err.message);
return null;
});
}
}
}
// 获取文件名
async function getFileName() {
if (typeof props.filename === "function") {
return await props?.filename();
} else {
const { year, month, day, hour, minu, sec } = currentDate();
return props.filename || `报表(${year}-${month}-${day} ${hour}_${minu}_${sec}`;
}
}
// 导出
async function toExport() {
if (!props.columns) {
return console.error("columns is required");
}
// 加载
loading.value = true;
// 表格列
const columns = props.columns.filter(
(e: any) =>
!(
e.hidden === true ||
["selection", "expand", "index"].includes(e.type) ||
e.filterExport ||
e["filter-export"]
)
);
// 字段
const fields = columns.map((e: any) => e.prop).filter(Boolean);
// 表头
const header = await getHeader(columns, fields);
// 数据
let data = await getData();
if (!data) {
loading.value = false;
return console.error("导出数据异常");
}
// 文件名
const filename = await getFileName();
// 过滤
data = data.map((d: any) => fields.map((f) => d[f]));
// 导出 excel
export_json_to_excel({
header,
data,
filename,
autoWidth: props.autoWidth,
bookType: props.bookType
});
loading.value = false;
}
return () => {
return (
<el-button loading={loading.value} onClick={toExport}>
<slot></slot>
</el-button>
);
};
}
});

View File

@ -1,158 +0,0 @@
<template>
<el-button
:size="size"
:type="type"
:plain="plain"
:round="round"
:circle="circle"
:icon="icon"
:loading="loading"
:disabled="disabled"
@click="toExport"
>
<slot>导出</slot>
</el-button>
</template>
<script lang="ts" name="cl-export-btn" setup>
import { useCrud } from "@cool-vue/crud";
import { ElMessage } from "element-plus";
import { ref } from "vue";
import { currentDate, export_json_to_excel } from "../utils";
const props = defineProps({
filename: [Function, String],
autoWidth: {
type: Boolean,
default: true
},
bookType: {
type: String,
default: "xlsx"
},
header: Array,
columns: {
type: Array,
required: true
},
data: [Function, Array],
maxExportLimit: Number, // 0
size: String,
disabled: Boolean,
type: String,
plain: Boolean,
round: Boolean,
circle: Boolean,
icon: String
});
//
const loading = ref<boolean>(false);
// crud
const Crud = useCrud();
async function getHeader(columns: any[], fields: any[]) {
return columns.filter((e) => !e.hidden && fields.includes(e.prop)).map((e) => e.label);
}
function getData() {
if (typeof props.data === "function") {
return props.data();
} else {
if (props.data) {
return props.data;
} else {
return Crud.value?.service
.page({
...Crud.value?.paramsReplace(Crud.value.params),
maxExportLimit: props.maxExportLimit,
isExport: true
})
.then((res) => {
return res.list.map((e: any) => {
for (const i in e) {
const col: any = props.columns.find((c: any) => c.prop == i);
if (col) {
if (col.dict) {
const d = col.dict.find((d: any) => d.value == e[i]);
if (d) {
e[i] = d.label;
}
}
}
}
return e;
});
})
.catch((err) => {
ElMessage.error(err.message);
return null;
});
}
}
}
async function getFileName() {
if (typeof props.filename === "function") {
return await props?.filename();
} else {
const { year, month, day, hour, minu, sec } = currentDate();
return props.filename || `报表(${year}-${month}-${day} ${hour}_${minu}_${sec}`;
}
}
async function toExport() {
if (!props.columns) {
return console.error("cl-export-btn.columns 不能为空!");
}
//
loading.value = true;
//
const columns = props.columns.filter(
(e: any) =>
!(
e.hidden === true ||
["selection", "expand", "index"].includes(e.type) ||
e.filterExport ||
e["filter-export"]
)
);
//
const fields = columns.map((e: any) => e.prop).filter(Boolean);
//
let header = await getHeader(columns, fields);
//
let data = await getData();
if (!data) {
loading.value = false;
return console.error("导出数据异常");
}
//
let filename = await getFileName();
//
data = data.map((d: any) => fields.map((f) => d[f]));
// excel
export_json_to_excel({
header,
data,
filename,
autoWidth: props.autoWidth,
bookType: props.bookType
});
loading.value = false;
}
</script>

View File

@ -2,6 +2,6 @@ import { ModuleConfig } from "/@/cool";
export default (): ModuleConfig => { export default (): ModuleConfig => {
return { return {
components: [import("./components/export-btn.vue")] components: [import("./components/export-btn")]
}; };
}; };

View File

@ -364,7 +364,7 @@ function refreshTask(params?: any, options?: any) {
moreList(res, item); moreList(res, item);
if (!more) { if (!more) {
refs.value[`${item.key}-scroller`].scroll({ refs[`${item.key}-scroller`].scroll({
top: 0, top: 0,
behavior: "smooth" behavior: "smooth"
}); });
@ -652,7 +652,7 @@ async function refreshLog(newParams: any, options?: any) {
moreList(res, logs); moreList(res, logs);
if (!more) { if (!more) {
refs.value["log-scroller"].scroll({ refs["log-scroller"].scroll({
top: 0, top: 0,
behavior: "smooth" behavior: "smooth"
}); });

View File

@ -1,33 +1,27 @@
<template> <template>
<div <div class="item-category">
class="cl-upload-space-category" <div class="item-category__head">
:class="{ <span>类型</span>
'is-position': app.browser.isMini, <el-button type="success" bg size="small" @click="edit()">添加</el-button>
'is-show': space.category.visible
}"
>
<div class="cl-upload-space-category__search">
<el-input v-model="keyword" placeholder="搜索分类" clearable />
<el-button type="success" @click="edit()">添加</el-button>
</div> </div>
<div class="cl-upload-space-category__list"> <div class="item-category__list">
<el-scrollbar> <el-scrollbar>
<ul> <ul>
<li <li
v-for="(item, index) in flist" v-for="(item, index) in flist"
:key="index" :key="index"
class="item"
:class="{ :class="{
'is-active': item.id == space.category.id 'is-active': item.id == ViewGroup?.selected?.id
}" }"
@click="select(item.id)" @click="select(item)"
@contextmenu.stop.prevent="onContextMenu($event, item)" @contextmenu.stop.prevent="onContextMenu($event, item)"
> >
<el-icon class="icon">
<folder-opened v-if="ViewGroup?.selected?.id == item.id" />
<folder v-else />
</el-icon>
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
<el-icon v-show="space.category.id == item.id"
><arrow-right-bold
/></el-icon>
</li> </li>
<el-empty v-if="flist.length == 0" :image-size="80" /> <el-empty v-if="flist.length == 0" :image-size="80" />
@ -39,19 +33,19 @@
<cl-form ref="Form" /> <cl-form ref="Form" />
</template> </template>
<script lang="ts" setup name="space-category"> <script lang="ts" setup name="item-category">
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { ArrowRightBold } from "@element-plus/icons-vue"; import { Folder, FolderOpened } from "@element-plus/icons-vue";
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { isEmpty } from "lodash-es";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import { ContextMenu, useForm } from "@cool-vue/crud"; import { ContextMenu, useForm } from "@cool-vue/crud";
import { useBase } from "/$/base"; import { useViewGroup } from "/$/base";
import { useSpace } from "../../hooks"; import { useSpace } from "../../hooks";
const { service } = useCool(); const { service } = useCool();
const { app } = useBase();
const { space } = useSpace(); const { space } = useSpace();
const { ViewGroup } = useViewGroup();
const Form = useForm();
// //
const list = ref<Eps.SpaceTypeEntity[]>([]); const list = ref<Eps.SpaceTypeEntity[]>([]);
@ -66,29 +60,36 @@ const flist = computed(() => {
// //
async function refresh() { async function refresh() {
return service.space.type.list().then((res) => { return service.space.type
res.unshift({ .list({
name: "全部文件", order: "createTime",
id: undefined sort: "asc"
}); })
.then((res) => {
res.unshift({
name: "全部文件",
id: undefined
});
list.value = res; list.value = res;
if (!isEmpty(res)) { if (!ViewGroup.value?.selected) {
if (!space.category.id && res[0].id) { select();
space.category.id = res[0].id;
} }
} });
});
} }
const Form = useForm();
// //
function edit(item: Eps.SpaceTypeEntity = {}) { function edit(item: Eps.SpaceTypeEntity = {}) {
Form.value?.open({ Form.value?.open({
title: "添加分类", title: "添加分类",
width: "400px", width: "400px",
props: {
labelPosition: "top"
},
dialog: {
controls: ["close"]
},
items: [ items: [
{ {
label: "分类名称", label: "分类名称",
@ -96,7 +97,11 @@ function edit(item: Eps.SpaceTypeEntity = {}) {
value: "", value: "",
required: true, required: true,
component: { component: {
name: "el-input" name: "el-input",
props: {
maxlength: 20,
clearable: true
}
} }
} }
], ],
@ -129,18 +134,21 @@ function edit(item: Eps.SpaceTypeEntity = {}) {
} }
// //
function select(id?: number) { function select(item?: Eps.SpaceTypeEntity) {
// if (!item) {
if (app.browser.isMini) { item = list.value[0];
space.category.visible = false;
} }
space.category.id = id; if (item) {
space.refresh({ page: 1 }); space.refresh({ page: 1, classifyId: item.id });
ViewGroup.value?.select(item);
ViewGroup.value?.setTitle(item.name);
}
} }
// //
function onContextMenu(e: any, { id, name }: any) { function onContextMenu(e: any, { id, name }: Eps.SpaceTypeEntity) {
if (!id) { if (!id) {
return false; return false;
} }
@ -179,8 +187,8 @@ function onContextMenu(e: any, { id, name }: any) {
ElMessage.success("删除成功"); ElMessage.success("删除成功");
// //
if (id == space.category.id) { if (id == ViewGroup.value?.selected?.id) {
space.category.id = undefined; select();
} }
refresh(); refresh();
@ -204,49 +212,29 @@ onMounted(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.cl-upload-space-category { .item-category {
height: 100%; height: 100%;
width: 0; width: 100%;
background-color: var(--el-bg-color);
overflow: hidden;
transition: width 0.2s ease-in-out;
border-radius: 5px;
&.is-show { &__head {
width: 220px;
margin-right: 5px;
}
&.is-position {
position: absolute;
left: 5px;
top: 51px;
height: calc(100% - 56px);
z-index: 3000;
&.is-show {
width: calc(100% - 10px);
}
}
&__search {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px; justify-content: space-between;
height: 40px;
.el-button { font-size: 14px;
margin-left: 10px; padding: 0 10px;
} white-space: nowrap;
} }
&__list { &__list {
height: calc(100% - 48px); height: calc(100% - 40px);
padding: 0 10px; padding: 0 10px;
box-sizing: border-box;
ul { ul {
height: 100%; height: 100%;
.item { li {
display: flex; display: flex;
align-items: center; align-items: center;
list-style: none; list-style: none;
@ -260,7 +248,12 @@ onMounted(() => {
color: #666; color: #666;
position: relative; position: relative;
.el-icon { .icon {
margin-right: 10px;
font-size: 16px;
}
.arrow {
position: absolute; position: absolute;
right: 10px; right: 10px;
} }
@ -274,6 +267,12 @@ onMounted(() => {
background-color: #f7f7f7; background-color: #f7f7f7;
} }
} }
&::after {
display: block;
content: "";
height: 1px;
}
} }
} }
} }

View File

@ -1,14 +1,14 @@
<template> <template>
<div class="cl-upload-space-file__wrap"> <div class="item-file__wrap">
<div <div
class="cl-upload-space-file" class="item-file"
:class="[`is-${info.type}`]" :class="[`is-${info.type}`]"
@click="select" @click="select"
@contextmenu.stop.prevent="onContextMenu" @contextmenu.stop.prevent="onContextMenu"
> >
<!-- 错误 --> <!-- 错误 -->
<template v-if="info.error"> <template v-if="info.error">
<div class="cl-upload-space-file__error">上传失败{{ info.error }}</div> <div class="item-file__error">上传失败{{ info.error }}</div>
</template> </template>
<!-- 成功 --> <!-- 成功 -->
@ -32,14 +32,12 @@
<!-- 其他 --> <!-- 其他 -->
<template v-else> <template v-else>
<!-- 文件名 --> <!-- 文件名 -->
<span class="cl-upload-space-file__name" <span class="item-file__name">{{ fileName(url) }}.{{ extname(url) }}</span>
>{{ fileName(url) }}.{{ extname(url) }}</span
>
</template> </template>
<!-- 文件类型 --> <!-- 文件类型 -->
<span <span
class="cl-upload-space-file__type" class="item-file__type"
:style="{ :style="{
backgroundColor: type?.color backgroundColor: type?.color
}" }"
@ -49,44 +47,48 @@
<!-- 上传中 --> <!-- 上传中 -->
<template v-if="info.progress > 0 && info.progress < 100"> <template v-if="info.progress > 0 && info.progress < 100">
<!-- 进度条 --> <!-- 进度条 -->
<div class="cl-upload-space-file__progress-bar"> <div class="item-file__progress-bar">
<el-progress :percentage="info.progress" :show-text="false"></el-progress> <el-progress :percentage="info.progress" :show-text="false"></el-progress>
</div> </div>
<!-- 进度值 --> <!-- 进度值 -->
<span class="cl-upload-space-file__progress-value">{{ info.progress }}</span> <span class="item-file__progress-value">{{ info.progress }}</span>
</template> </template>
</template> </template>
<!-- 遮罩层 --> <!-- 遮罩层 -->
<div v-if="isSelected" class="cl-upload-space-file__mask"> <div v-if="isSelected" class="item-file__mask">
<span>{{ index + 1 }}</span> <span>{{ index + 1 }}</span>
</div> </div>
</div> </div>
</div> </div>
<item-viewer ref="Viewer" />
</template> </template>
<script lang="ts" setup name="space-file"> <script lang="ts" setup name="item-file">
import { computed } from "vue"; import { computed, PropType, ref } from "vue";
import { ContextMenu } from "@cool-vue/crud"; import { ContextMenu } from "@cool-vue/crud";
import { extname } from "/@/cool/utils"; import { extname } from "/@/cool/utils";
import { fileName, fileRule } from "../../utils"; import { fileName, fileRule } from "../../utils";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useSpace } from "../../hooks"; import { useSpace } from "../../hooks";
import ItemVideo from "./item-video.vue"; import ItemVideo from "./video.vue";
import ItemViewer from "./viewer.vue";
const { copy } = useClipboard();
const props = defineProps({ const props = defineProps({
data: Object, data: Object,
list: Array list: Array as PropType<Eps.SpaceInfoEntity[]>
}); });
const emit = defineEmits(["select", "remove"]); const emit = defineEmits(["select", "remove"]);
const { copy } = useClipboard();
const { space } = useSpace(); const { space } = useSpace();
const Viewer = ref();
// //
const info = computed<Eps.SpaceInfoEntity>(() => props.data || {}); const info = computed<Eps.SpaceInfoEntity>(() => props.data || {});
@ -100,7 +102,7 @@ const isSelected = computed(() => index.value >= 0);
const url = computed(() => info.value.preload || info.value.url); const url = computed(() => info.value.preload || info.value.url);
// //
const type = computed(() => fileRule(info.value.type)); const type = computed(() => fileRule(info.value.type || ""));
// //
function select() { function select() {
@ -116,13 +118,21 @@ function remove() {
function onContextMenu(e: any) { function onContextMenu(e: any) {
ContextMenu.open(e, { ContextMenu.open(e, {
hover: { hover: {
target: "cl-upload-space-file__wrap" target: "item-file__wrap"
}, },
list: [ list: [
{ {
label: "预览", label: "预览",
callback(done) { callback(done) {
window.open(info.value.url); if (info.value.type == "image") {
Viewer.value?.open(
info.value.url,
props.list?.filter((e) => e.type == "image").map((e) => e.url)
);
} else {
window.open(info.value.url);
}
done(); done();
} }
}, },
@ -157,7 +167,7 @@ function onContextMenu(e: any) {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.cl-upload-space-file { .item-file {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@ -165,7 +175,6 @@ function onContextMenu(e: any) {
height: 100%; height: 100%;
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
position: relative;
border-radius: 5px; border-radius: 5px;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
@ -173,6 +182,9 @@ function onContextMenu(e: any) {
margin-bottom: 10px; margin-bottom: 10px;
&__wrap { &__wrap {
position: absolute;
left: 0;
top: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
@ -207,7 +219,7 @@ function onContextMenu(e: any) {
&:not(.is-image):not(.is-video) { &:not(.is-image):not(.is-video) {
padding: 10px; padding: 10px;
.cl-upload-space-file { .item-file {
&__icon { &__icon {
display: flex; display: flex;
align-items: center; align-items: center;
@ -294,6 +306,7 @@ function onContextMenu(e: any) {
text-align: center; text-align: center;
line-height: 20px; line-height: 20px;
border-radius: 20px; border-radius: 20px;
font-size: 14px;
} }
} }
} }

View File

@ -26,10 +26,10 @@ const props = defineProps({
const { space } = useSpace(); const { space } = useSpace();
const info = computed<Eps.SpaceInfoEntity>(() => props.data || {});
const Video = ref<HTMLVideoElement>(); const Video = ref<HTMLVideoElement>();
const info = computed<Eps.SpaceInfoEntity>(() => props.data || {});
const loaded = computed(() => { const loaded = computed(() => {
return info.value.progress === undefined || info.value.progress === 100; return info.value.progress === undefined || info.value.progress === 100;
}); });

View File

@ -0,0 +1,35 @@
<template>
<div class="item-viewer">
<el-image-viewer
v-if="visible"
:url-list="urls"
:initial-index="index"
infinite
teleported
@close="close"
></el-image-viewer>
</div>
</template>
<script lang="ts" setup name="item-viewer">
import { ref } from "vue";
const visible = ref(false);
const urls = ref<string[]>([]);
const index = ref(0);
function open(url: string, list: string[]) {
visible.value = true;
urls.value = list;
index.value = list.findIndex((e) => e == url);
}
function close() {
visible.value = false;
}
defineExpose({
open
});
</script>

View File

@ -0,0 +1,371 @@
<template>
<div class="cl-upload-panel" @dragover="onDragover" @drop="onDrop">
<cl-view-group ref="ViewGroup" title="全部文件">
<template #left>
<item-category />
</template>
<template #right>
<div class="cl-upload-panel__right" ref="Content">
<!-- 操作栏 -->
<div class="cl-upload-panel__header">
<el-button @click="refresh({ page: 1 })">刷新</el-button>
<div :style="{ margin: '0px 10px' }">
<cl-upload
ref="Upload"
type="file"
:show-file-list="false"
:limit="limit"
:limit-upload="false"
:accept="accept"
multiple
@success="onSuccess"
@upload="onUpload"
>
<el-button type="primary">点击上传</el-button>
</cl-upload>
</div>
<template v-if="!selectable">
<el-button
type="danger"
:disabled="selection.length == 0"
@click="remove()"
>删除选中文件</el-button
>
</template>
</div>
<!-- 文件区域 -->
<el-scrollbar class="cl-upload-panel__file" v-loading="loading">
<div v-infinite-scroll="loadmore" :infinite-scroll-immediate="false">
<!-- 文件列表 -->
<template v-if="list.length > 0">
<div
class="list"
:class="{
'is-mini': browser.isMini
}"
>
<div
class="item"
v-for="item in list"
:key="item.preload || item.url"
>
<item-file
:data="item"
:list="list"
@select="select"
@remove="remove"
/>
</div>
</div>
</template>
<!-- 空态 -->
<div v-else class="empty">
<el-icon class="el-icon--upload">
<upload-filled />
</el-icon>
<p>将文件拖到此处或点击按钮上传</p>
</div>
</div>
</el-scrollbar>
</div>
</template>
</cl-view-group>
</div>
</template>
<script lang="ts" setup name="cl-upload-panel">
import { provide, reactive, ref, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { UploadFilled } from "@element-plus/icons-vue";
import { useCool } from "/@/cool";
import { useViewGroup } from "/$/base";
import ItemCategory from "./items/category.vue";
import ItemFile from "./items/file.vue";
const props = defineProps({
limit: {
type: Number,
default: 99
},
accept: String,
selectable: Boolean
});
const emit = defineEmits(["selection-change"]);
const { service, browser } = useCool();
const { ViewGroup } = useViewGroup();
// cl-upload
const Upload = ref();
//
const Content = ref();
//
const loading = ref(false);
//
const selection = ref<Eps.SpaceInfoEntity[]>([]);
//
const list = ref<Eps.SpaceInfoEntity[]>([]);
//
const pagination = reactive({
page: 1,
size: 50,
total: 0
});
//
function clear() {
selection.value = [];
}
//
function onSuccess(data: any) {
service.space.info
.add({
classifyId: ViewGroup.value?.selected?.id,
...data
})
.then((res) => {
data.id = res.id;
})
.catch((err) => {
ElMessage.error(err.message);
});
}
//
function onUpload(data: any) {
list.value.unshift(data);
}
//
const reqParams = {
page: 1
};
//
async function refresh(params?: any) {
//
clear();
//
Object.assign(reqParams, {
type: props.accept?.split("/")[0],
...pagination,
...params
});
//
if (reqParams.page == 1) {
loading.value = true;
}
await service.space.info.page(reqParams).then((res) => {
//
Object.assign(pagination, res.pagination);
if (reqParams.page == 1) {
list.value = [];
}
list.value.push(...res.list);
});
//
loading.value = false;
}
//
function select(item: Eps.SpaceInfoEntity) {
const index = selection.value.findIndex((e) => e.id === item.id);
if (index >= 0) {
selection.value.splice(index, 1);
} else {
if (props.limit == 1) {
selection.value = [item];
} else {
if (selection.value.length < props.limit) {
selection.value.push(item);
}
}
}
}
//
function remove(item?: Eps.SpaceInfoEntity) {
// id
const ids = item ? [item.id] : selection.value.map((e) => e.id);
ElMessageBox.confirm("此操作将删除文件, 是否继续?", "提示", {
type: "warning"
})
.then(() => {
ElMessage.success("删除成功");
//
ids.forEach((id) => {
[list.value, selection.value].forEach((list) => {
const index = list.findIndex((e) => e.id === id);
list.splice(index, 1);
});
});
//
service.space.info
.delete({
ids
})
.catch((err) => {
ElMessage.error(err.message);
});
})
.catch(() => null);
}
//
watch(
selection,
(val) => {
emit("selection-change", val);
},
{
deep: true
}
);
//
function loadmore() {
if (list.value.length && list.value.length < pagination.total) {
refresh({
page: pagination.page + 1
});
}
}
function onDragover(e: any) {
e.preventDefault();
}
function onDrop(e: any) {
e.preventDefault();
e.dataTransfer.files.forEach((f: File, i: number) => {
setTimeout(() => {
Upload.value.upload(f);
}, i * 10);
});
}
provide("space", {
selection,
refresh,
loading,
list
});
defineExpose({
selection,
open,
close,
clear,
refresh
});
</script>
<style lang="scss">
.cl-upload-panel {
height: 100%;
user-select: none;
&__right {
height: 100%;
width: 100%;
}
&__header {
display: flex;
align-items: center;
height: 50px;
padding: 0 10px;
}
&__file {
height: calc(100% - 50px);
padding: 0 10px;
box-sizing: border-box;
position: relative;
.list {
display: flex;
flex-wrap: wrap;
.item {
height: 0;
min-height: 140px;
min-width: 140px;
width: calc(12.5% - 10px);
padding-top: calc(12.5% - 10px);
margin: 0 10px 10px 0;
position: relative;
box-sizing: border-box;
}
&.is-mini {
.item {
width: calc(50% - 10px);
}
}
}
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
height: 180px;
width: 360px;
max-width: 80%;
border-radius: 5px;
border: 1px dashed var(--el-border-color);
box-sizing: border-box;
cursor: pointer;
&:hover {
border-color: var(--color-primary);
}
i {
font-size: 67px;
color: #c0c4cc;
}
p {
font-size: 14px;
margin-top: 15px;
color: #999;
}
}
}
&__footer {
padding: 9px 0;
}
}
</style>

View File

@ -9,451 +9,87 @@
v-model="visible" v-model="visible"
:title="title" :title="title"
height="650px" height="650px"
width="1080px" width="1070px"
keep-alive keep-alive
custom-class="cl-upload-space__dialog" custom-class="cl-upload-space__dialog"
:close-on-click-modal="false" :close-on-click-modal="false"
append-to-body append-to-body
:controls="['slot-expand', 'cl-flex1', 'fullscreen', 'close']"
> >
<div <cl-upload-panel :limit="limit" :accept="accept" ref="Panel" />
class="cl-upload-space"
:class="{
'is-mini': app.browser.isMini
}"
@dragover="onDragover"
@drop="onDrop"
>
<!-- 类目 -->
<space-category />
<!-- 内容 --> <template #footer>
<div class="cl-upload-space__content"> <el-button @click="close">取消</el-button>
<!-- 操作栏 --> <el-button :disabled="selection.length == 0" type="success" @click="confirm"
<div class="cl-upload-space__header scroller1"> >选择 {{ selection.length }}/{{ limit }}</el-button
<el-button @click="refresh({ page: 1 })">刷新</el-button> >
<div :style="{ margin: '0px 10px' }">
<cl-upload
ref="Upload"
type="file"
:show-file-list="false"
:disabled="disabled"
:limit="9999"
:accept="accept"
multiple
@success="onSuccess"
@upload="onUpload"
>
<el-button type="primary">点击上传</el-button>
</cl-upload>
</div>
<cl-flex1 />
<el-button type="success" :disabled="!isSelected" @click="confirm()"
>使用选中文件 {{ selection.length }}/{{ limit }}</el-button
>
<el-button type="danger" :disabled="!isSelected" @click="remove()"
>删除选中文件</el-button
>
</div>
<!-- 文件区域 -->
<div
class="cl-upload-space__file scroller1"
v-infinite-scroll="loadmore"
v-loading="loading"
>
<!-- 文件列表 -->
<template v-if="list.length > 0">
<div class="cl-upload-space__file-list">
<div
class="cl-upload-space__file-item"
v-for="item in list"
:key="item.preload || item.url"
>
<space-file
:data="item"
:list="list"
@select="select"
@remove="remove"
/>
</div>
</div>
</template>
<!-- 空态 -->
<div v-else class="cl-upload-space__file-empty">
<el-icon class="el-icon--upload">
<upload-filled />
</el-icon>
<p>将文件拖到此处或点击按钮上传</p>
</div>
</div>
</div>
</div>
<!-- 展开按钮 -->
<template #slot-expand>
<button class="cl-dialog__controls-icon">
<el-icon @click="category.visible = false" v-if="category.visible">
<notebook />
</el-icon>
<el-icon @click="category.visible = true" v-else>
<arrow-left />
</el-icon>
</button>
</template> </template>
</cl-dialog> </cl-dialog>
</div> </div>
</template> </template>
<script lang="ts" setup name="cl-upload-space"> <script lang="ts" setup name="cl-upload-space">
import { computed, provide, reactive, ref, watch } from "vue"; import { computed, nextTick, ref } from "vue";
import { isEmpty } from "lodash-es";
import { ElMessage, ElMessageBox } from "element-plus";
import { Notebook, ArrowLeft, UploadFilled } from "@element-plus/icons-vue";
import { module, useCool } from "/@/cool";
import { useBase } from "/$/base";
import SpaceCategory from "./space/category.vue";
import SpaceFile from "./space/file.vue";
const props = defineProps({ defineProps({
// //
modelValue: String, title: {
type: String,
default: "文件空间"
},
// //
limit: Number, limit: {
// type: Number,
disabled: Boolean, default: 9
},
// //
accept: String, accept: String,
// //
showBtn: { showBtn: {
type: Boolean, type: Boolean,
default: true default: true
},
//
title: {
type: String,
default: "文件空间"
} }
}); });
const emit = defineEmits(["update:modelValue", "confirm"]); const emit = defineEmits(["confirm"]);
const { service } = useCool();
//
const { app } = useBase();
//
const { options } = module.get("upload");
// cl-upload
const Upload = ref();
//
const limit = props.limit || options.limit?.select;
// //
const visible = ref(false); const visible = ref(false);
// // cl-upload-panel
const loading = ref(false); const Panel = ref();
// //
const selection = ref<Eps.SpaceInfoEntity[]>([]); const selection = computed<any[]>(() => Panel.value?.selection || []);
//
const list = ref<Eps.SpaceInfoEntity[]>([]);
//
const category = reactive({
id: null,
visible: true
});
//
const pagination = reactive({
page: 1,
size: 20,
total: 0
});
//
watch(
() => app.browser.isMini,
(val) => {
category.visible = val ? false : true;
},
{
immediate: true
}
);
//
const isSelected = computed(() => !isEmpty(selection.value));
//
let lock = false;
function open() { function open() {
visible.value = true; visible.value = true;
if (!lock) { nextTick(() => {
lock = true; Panel.value?.clear();
refresh(); });
}
}
//
function clear() {
selection.value = [];
} }
// //
function close() { function close() {
visible.value = false; visible.value = false;
clear();
} }
// //
function onSuccess(data: any) {
service.space.info
.add({
classifyId: category.id,
...data
})
.then((res) => {
data.id = res.id;
})
.catch((err) => {
ElMessage.error(err.message);
});
}
//
function onUpload(data: any) {
list.value.unshift(data);
}
const reqParams = {
page: 1
};
//
async function refresh(params: any = {}) {
//
clear();
//
Object.assign(reqParams, {
type: props.accept?.split("/")[0],
...pagination,
...params,
classifyId: category.id
});
//
if (reqParams.page == 1) {
loading.value = true;
}
await service.space.info.page(reqParams).then((res) => {
//
Object.assign(pagination, res.pagination);
if (reqParams.page == 1) {
list.value = [];
}
list.value.push(...res.list);
});
//
loading.value = false;
}
//
function confirm() { function confirm() {
emit("update:modelValue", selection.value.map((e) => e.url).join(","));
emit("confirm", selection.value); emit("confirm", selection.value);
close(); close();
} }
//
function select(item: Eps.SpaceInfoEntity) {
const index = selection.value.findIndex((e) => e.id === item.id);
if (index >= 0) {
selection.value.splice(index, 1);
} else {
if (selection.value.length < limit) {
selection.value.push(item);
}
}
}
//
function remove(item?: Eps.SpaceInfoEntity) {
// id
const ids = item ? [item.id] : selection.value.map((e) => e.id);
ElMessageBox.confirm("此操作将删除文件, 是否继续?", "提示", {
type: "warning"
})
.then(() => {
ElMessage.success("删除成功");
//
ids.forEach((id) => {
[list.value, selection.value].forEach((list) => {
const index = list.findIndex((e) => e.id === id);
list.splice(index, 1);
});
});
//
service.space.info
.delete({
ids
})
.catch((err) => {
ElMessage.error(err.message);
});
})
.catch(() => null);
}
function onDragover(e: any) {
e.preventDefault();
}
function onDrop(e: any) {
e.preventDefault();
e.dataTransfer.files.forEach((f: File, i: number) => {
setTimeout(() => {
Upload.value.upload(f);
}, i * 10);
});
}
//
function loadmore() {
if (list.value.length && list.value.length < pagination.total) {
refresh({
page: pagination.page + 1
});
}
}
//
provide("space", {
category,
selection,
refresh,
loading,
list
});
defineExpose({ defineExpose({
open, open,
close, close
clear,
refresh
}); });
</script> </script>
<style lang="scss"> <style lang="scss">
.cl-upload-space { .cl-upload-space__dialog {
display: flex; .el-dialog__footer {
height: 100%; border-top: 1px solid #f7f7f7;
box-sizing: border-box;
background-color: #f7f7f7;
padding: 5px;
user-select: none;
&__dialog {
.el-dialog__body {
padding: 0;
}
}
&__content {
flex: 1;
max-width: 100%;
box-sizing: border-box;
background-color: var(--el-bg-color);
border-radius: 5px;
}
&__header {
display: flex;
align-items: center;
height: 50px;
overflow: auto hidden;
padding: 0 10px;
}
&__file {
height: calc(100% - 50px);
padding: 0 10px;
box-sizing: border-box;
position: relative;
&-list {
display: flex;
flex-wrap: wrap;
}
&-item {
height: 150px;
width: 150px;
margin: 0 10px 10px 0;
}
&-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
top: calc(50% - 90px);
left: calc(50% - 180px);
height: 180px;
width: 360px;
border-radius: 5px;
border: 2px dashed var(--el-border-color);
cursor: pointer;
&:hover {
border-color: var(--color-primary);
}
i {
font-size: 67px;
color: #c0c4cc;
}
p {
font-size: 14px;
margin-top: 15px;
}
}
}
&__footer {
padding: 9px 0;
}
&.is-mini {
.cl-upload-space__file-list {
justify-content: center;
}
} }
} }
</style> </style>

View File

@ -5,12 +5,11 @@
:class="[ :class="[
`cl-upload--${type}`, `cl-upload--${type}`,
{ {
'is-slot': $slots.default,
'is-disabled': disabled 'is-disabled': disabled
} }
]" ]"
> >
<div class="cl-upload__header"> <div class="cl-upload__file-btn" v-if="type == 'file'">
<el-upload <el-upload
ref="Upload" ref="Upload"
action="" action=""
@ -23,18 +22,7 @@
:disabled="disabled" :disabled="disabled"
> >
<slot> <slot>
<template v-if="type == 'image' && isAdd"> <el-button type="success">{{ text }}</el-button>
<div class="cl-upload__item">
<el-icon :size="24"><picture-filled /></el-icon>
<span class="cl-upload__text">{{ text }}</span>
</div>
</template>
<template v-if="type == 'file'">
<div class="cl-upload__btn">
<el-button type="success">{{ text }}</el-button>
</div>
</template>
</slot> </slot>
</el-upload> </el-upload>
</div> </div>
@ -47,7 +35,31 @@
v-bind="drag.options" v-bind="drag.options"
item-key="uid" item-key="uid"
@end="update" @end="update"
v-if="showFileList"
> >
<template #footer>
<div class="cl-upload__footer" v-if="type == 'image' && isAdd">
<el-upload
ref="Upload"
action=""
:accept="accept"
:show-file-list="false"
:before-upload="beforeUpload"
:http-request="httpRequest"
:headers="headers"
:multiple="multiple"
:disabled="disabled"
>
<slot>
<div class="cl-upload__item">
<el-icon :size="24"><picture-filled /></el-icon>
<span class="cl-upload__text">{{ text }}</span>
</div>
</slot>
</el-upload>
</div>
</template>
<template #item="{ element: item, index }"> <template #item="{ element: item, index }">
<el-upload <el-upload
action="" action=""
@ -126,18 +138,7 @@
</div> </div>
</div> </div>
<el-image-viewer <item-viewer ref="Viewer" />
v-if="pv.visible"
:url-list="pv.urls"
:initial-index="pv.index"
infinite
teleported
@close="
() => {
pv.visible = false;
}
"
></el-image-viewer>
</template> </template>
<script lang="ts" setup name="cl-upload"> <script lang="ts" setup name="cl-upload">
@ -148,11 +149,11 @@ import Draggable from "vuedraggable";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { PictureFilled, ZoomIn, Delete } from "@element-plus/icons-vue"; import { PictureFilled, ZoomIn, Delete } from "@element-plus/icons-vue";
import { useCool, module } from "/@/cool"; import { useCool, module } from "/@/cool";
import { extname, uuid } from "/@/cool/utils"; import { extname, uuid, isPromise } from "/@/cool/utils";
import { useBase } from "/$/base"; import { useBase } from "/$/base";
import { fileSize, fileName, fileType } from "../utils"; import { fileSize, fileName, fileType, getUrls } from "../utils";
import { useForm } from "@cool-vue/crud";
import { Upload } from "../types"; import { Upload } from "../types";
import ItemViewer from "./items/viewer.vue";
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -167,6 +168,10 @@ const props = defineProps({
multiple: Boolean, multiple: Boolean,
limit: Number, limit: Number,
limitSize: Number, limitSize: Number,
limitUpload: {
type: Boolean,
default: true
},
size: [String, Number, Array], size: [String, Number, Array],
text: String, text: String,
prefixPath: { prefixPath: {
@ -177,30 +182,30 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: true default: true
}, },
drag: Boolean, draggable: Boolean,
disabled: Boolean, disabled: Boolean,
customClass: String, customClass: String,
beforeUpload: Function,
// 穿 // 穿
isEdit: null, isEdit: null,
scope: null scope: null,
isDisabled: Boolean
}); });
const emit = defineEmits(["update:modelValue", "upload", "success", "error", "progress"]); const emit = defineEmits(["update:modelValue", "upload", "success", "error", "progress"]);
const { service } = useCool(); const { service } = useCool();
//
const { user } = useBase(); const { user } = useBase();
//
const Form = useForm();
// //
const { options } = module.get("upload"); const { options } = module.get("upload");
// el-upload // el-upload
const Upload = ref<any>(); const Upload = ref();
// item-viewer
const Viewer = ref();
// //
const size = computed(() => { const size = computed(() => {
@ -210,7 +215,7 @@ const size = computed(() => {
// //
const disabled = computed(() => { const disabled = computed(() => {
return Form.value?.disabled || props.disabled; return props.isDisabled || props.disabled;
}); });
// //
@ -229,25 +234,18 @@ const headers = computed(() => {
}; };
}); });
//
const pv = reactive<{ visible: boolean; urls: string[]; index: number }>({
visible: false,
urls: [],
index: 0
});
// //
const list = ref<Upload.Item[]>([]); const list = ref<Upload.Item[]>([]);
// //
const drag = reactive<any>({ const drag = reactive({
options: { options: {
group: "Upload", group: "Upload",
animation: 300, animation: 300,
ghostClass: "Ghost", ghostClass: "Ghost",
dragClass: "Drag", dragClass: "Drag",
draggable: ".is-drag", draggable: ".is-drag",
disabled: !props.drag disabled: !props.draggable
} }
}); });
@ -271,35 +269,56 @@ function getType(path: string) {
} }
// //
function beforeUpload(file: any, item?: Upload.Item) { async function beforeUpload(file: any, item?: Upload.Item) {
if (file.size / 1024 / 1024 >= limitSize) { function next() {
ElMessage.error(`上传文件大小不能超过 ${limitSize}MB!`); const d = {
return false; type: getType(file.name),
} preload: "",
progress: 0,
url: "",
uid: file.uid,
size: file.size
};
const d = { d.preload = d.type == "image" ? window.webkitURL.createObjectURL(file) : file.name;
type: getType(file.name),
preload: "",
progress: 0,
url: "",
uid: file.uid,
size: file.size
};
d.preload = d.type == "image" ? window.webkitURL.createObjectURL(file) : file.name; if (!item) {
if (props.multiple) {
if (!item) { if (isAdd.value || !props.limitUpload) {
if (isAdd.value) { list.value.push(d);
list.value.push(d); }
} else {
list.value = [d];
}
} else { } else {
list.value = [d]; Object.assign(item, d);
} }
} else {
Object.assign(item, d); emit("upload", d);
return true;
} }
emit("upload", d); if (props.beforeUpload) {
return true; const r = props.beforeUpload(file, item);
if (isPromise(r)) {
r.then(next).catch(() => null);
} else {
if (r) {
next();
}
}
return r;
} else {
if (file.size / 1024 / 1024 >= limitSize) {
ElMessage.error(`上传文件大小不能超过 ${limitSize}MB!`);
return false;
}
return next();
}
} }
// //
@ -316,9 +335,10 @@ function clear() {
// //
function preview(item: Upload.Item) { function preview(item: Upload.Item) {
if (item.type == "image") { if (item.type == "image") {
pv.visible = true; Viewer.value?.open(
pv.urls = list.value.map((e) => e.preload); item.preload,
pv.index = pv.urls.indexOf(item.preload); list.value.map((e) => e.preload)
);
} else { } else {
window.open(item.url); window.open(item.url);
} }
@ -330,6 +350,10 @@ async function httpRequest(req: any, item?: any) {
item = list.value.find((e) => e.uid == req.file.uid); item = list.value.find((e) => e.uid == req.file.uid);
} }
if (!item) {
return false;
}
try { try {
// uuid + filename // uuid + filename
let fileName = uuid() + "_" + req.file.name; let fileName = uuid() + "_" + req.file.name;
@ -450,10 +474,7 @@ function update() {
const check = list.value.find((e) => !e.url); const check = list.value.find((e) => !e.url);
if (!check) { if (!check) {
emit( emit("update:modelValue", getUrls(list.value));
"update:modelValue",
list.value.map((e) => e.url.replace(/,/g, encodeURIComponent(","))).join(",")
);
} }
} }
@ -506,17 +527,8 @@ defineExpose({
<style lang="scss" scoped> <style lang="scss" scoped>
.cl-upload { .cl-upload {
display: flex;
flex-wrap: wrap;
line-height: normal; line-height: normal;
&.is-disabled {
:deep(.cl-upload__item) {
cursor: not-allowed;
background-color: var(--el-disabled-bg-color);
}
}
.Ghost { .Ghost {
opacity: 0.7; opacity: 0.7;
} }
@ -527,26 +539,13 @@ defineExpose({
box-sizing: border-box; box-sizing: border-box;
} }
&--file { &__file {
.cl-upload__list { width: 100%;
width: 100%;
margin-top: 10px;
}
}
&__header {
.cl-upload__item {
margin-right: 5px;
}
} }
&__list { &__list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
.cl-upload__item {
margin-right: 5px;
}
} }
&__text { &__text {
@ -596,6 +595,7 @@ defineExpose({
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
user-select: none; user-select: none;
margin: 0 5px 5px 0;
&:hover { &:hover {
border-color: currentColor; border-color: currentColor;
@ -658,13 +658,16 @@ defineExpose({
box-sizing: border-box; box-sizing: border-box;
} }
&.is-slot { &--file {
.cl-upload__list { .cl-upload__list {
margin: 0; margin-top: 10px;
} }
}
.un-drag { &.is-disabled {
display: flex; :deep(.cl-upload__item) {
cursor: not-allowed;
background-color: var(--el-disabled-bg-color);
} }
} }
} }

View File

@ -1,6 +1,10 @@
export default () => { export default () => {
return { return {
components: [import("./components/index.vue"), import("./components/space.vue")], components: [
import("./components/upload.vue"),
import("./components/space.vue"),
import("./components/panel.vue")
],
options: { options: {
// 尺寸 // 尺寸
@ -11,11 +15,19 @@ export default () => {
limit: { limit: {
// 上传最大数量 // 上传最大数量
upload: 9, upload: 9,
// 文件空间选择数
select: 9,
// 上传大小限制 // 上传大小限制
size: 100 size: 100
} }
} },
views: [
{
meta: {
label: "图片空间"
},
path: "/upload/list",
component: () => import("./views/list.vue")
}
]
}; };
}; };

View File

@ -60,3 +60,8 @@ export function fileType(path: string) {
export function fileRule(value: string) { export function fileRule(value: string) {
return fileRules.find((e) => e.value == value); return fileRules.find((e) => e.value == value);
} }
// 拼接数组下的url
export function getUrls(list: any[]) {
return list.map((e) => e.url.replace(/,/g, encodeURIComponent(","))).join(",");
}

View File

@ -0,0 +1,7 @@
<template>
<panel />
</template>
<script lang="ts" setup name="upload-list">
import Panel from "../components/panel.vue";
</script>

6177
stats.html Normal file

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,7 @@ import { UserConfig } from "vite";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx"; import vueJsx from "@vitejs/plugin-vue-jsx";
import compression from "vite-plugin-compression"; import compression from "vite-plugin-compression";
import { visualizer } from "rollup-plugin-visualizer";
import { proxy } from "./src/cool/config/proxy"; import { proxy } from "./src/cool/config/proxy";
import { cool } from "./build/cool"; import { cool } from "./build/cool";
@ -15,7 +16,17 @@ function resolve(dir: string) {
export default (): UserConfig => { export default (): UserConfig => {
return { return {
base: "/", base: "/",
plugins: [vue(), compression(), vueJsx(), cool()], plugins: [
vue(),
compression(),
vueJsx(),
cool(),
visualizer({
open: false,
gzipSize: true,
brotliSize: true
})
],
css: { css: {
preprocessorOptions: { preprocessorOptions: {
scss: { scss: {

260
yarn.lock
View File

@ -965,101 +965,14 @@
"@babel/helper-validator-identifier" "^7.19.1" "@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@codemirror/autocomplete@^6.0.0": "@cool-vue/crud@^6.0.1":
version "6.4.0" version "6.0.1"
resolved "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz#76ac9a2a411a4cc6e13103014dba5e0fe601da5a" resolved "https://registry.npmjs.org/@cool-vue/crud/-/crud-6.0.1.tgz#26f89b05a939d3dcd316bf00fd4cf88c14e4c193"
integrity sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA== integrity sha512-LjcS5dvOpE/RUzIk2bSBawVk+NMMxj7MFPYXMdCPST8loPwOSqoCeIfkCwkJP8m4S4fdhvCCpSoRmY97ezhrAA==
dependencies:
"@codemirror/language" "^6.0.0"
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.6.0"
"@lezer/common" "^1.0.0"
"@codemirror/commands@6.x", "@codemirror/commands@^6.0.0":
version "6.1.3"
resolved "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.3.tgz#401d0b6d18e7d5eb9a96f6c8ae4ea56a08e8fd06"
integrity sha512-wUw1+vb34Ultv0Q9m/OVB7yizGXgtoDbkI5f5ErM8bebwLyUYjicdhJTKhTvPTpgkv8dq/BK0lQ3K5pRf2DAJw==
dependencies:
"@codemirror/language" "^6.0.0"
"@codemirror/state" "^6.2.0"
"@codemirror/view" "^6.0.0"
"@lezer/common" "^1.0.0"
"@codemirror/lang-javascript@^6.1.2":
version "6.1.2"
resolved "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.2.tgz#a11812ca1d21301cdeb80e51b4c007edcf55f813"
integrity sha512-OcwLfZXdQ1OHrLiIcKCn7MqZ7nx205CMKlhe+vL88pe2ymhT9+2P+QhwkYGxMICj8TDHyp8HFKVwpiisUT7iEQ==
dependencies:
"@codemirror/autocomplete" "^6.0.0"
"@codemirror/language" "^6.0.0"
"@codemirror/lint" "^6.0.0"
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
"@lezer/common" "^1.0.0"
"@lezer/javascript" "^1.0.0"
"@codemirror/language@6.x", "@codemirror/language@^6.0.0":
version "6.3.2"
resolved "https://registry.npmjs.org/@codemirror/language/-/language-6.3.2.tgz#a3d5796d17a2cd3110bac0f5126db67c7e90a0f3"
integrity sha512-g42uHhOcEMAXjmozGG+rdom5UsbyfMxQFh7AbkeoaNImddL6Xt4cQDL0+JxmG7+as18rUAvZaqzP/TjsciVIrA==
dependencies:
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
"@lezer/common" "^1.0.0"
"@lezer/highlight" "^1.0.0"
"@lezer/lr" "^1.0.0"
style-mod "^4.0.0"
"@codemirror/lint@^6.0.0":
version "6.1.0"
resolved "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.0.tgz#f006142d3a580fdb8ffc2faa3361b2232c08e079"
integrity sha512-mdvDQrjRmYPvQ3WrzF6Ewaao+NWERYtpthJvoQ3tK3t/44Ynhk8ZGjTSL9jMEv8CgSMogmt75X8ceOZRDSXHtQ==
dependencies:
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
crelt "^1.0.5"
"@codemirror/search@^6.0.0":
version "6.2.3"
resolved "https://registry.npmjs.org/@codemirror/search/-/search-6.2.3.tgz#fab933fef1b1de8ef40cda275c73d9ac7a1ff40f"
integrity sha512-V9n9233lopQhB1dyjsBK2Wc1i+8hcCqxl1wQ46c5HWWLePoe4FluV3TGHoZ04rBRlGjNyz9DTmpJErig8UE4jw==
dependencies:
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
crelt "^1.0.5"
"@codemirror/state@6.x", "@codemirror/state@^6.0.0", "@codemirror/state@^6.1.4", "@codemirror/state@^6.2.0":
version "6.2.0"
resolved "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz#a0fb08403ced8c2a68d1d0acee926bd20be922f2"
integrity sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==
"@codemirror/theme-one-dark@^6.1.0":
version "6.1.0"
resolved "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.0.tgz#6f8b3c7fc22e9fec59edd573f4ba9546db42e007"
integrity sha512-AiTHtFRu8+vWT9wWUWDM+cog6ZwgivJogB1Tm/g40NIpLwph7AnmxrSzWfvJN5fBVufsuwBxecQCNmdcR5D7Aw==
dependencies:
"@codemirror/language" "^6.0.0"
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
"@lezer/highlight" "^1.0.0"
"@codemirror/view@6.x", "@codemirror/view@^6.0.0", "@codemirror/view@^6.6.0":
version "6.7.1"
resolved "https://registry.npmjs.org/@codemirror/view/-/view-6.7.1.tgz#370e95d6f001e7f5cadc459807974b4f0a6eb225"
integrity sha512-kYtS+uqYw/q/0ytYxpkqE1JVuK5NsbmBklWYhwLFTKO9gVuTdh/kDEeZPKorbqHcJ+P+ucrhcsS1czVweOpT2g==
dependencies:
"@codemirror/state" "^6.1.4"
style-mod "^4.0.0"
w3c-keyname "^2.2.4"
"@cool-vue/crud@^5.9.4":
version "5.9.4"
resolved "https://registry.npmjs.org/@cool-vue/crud/-/crud-5.9.4.tgz#df5d3a28565a0b4fb7167449a2ace1c14cbf6a2c"
integrity sha512-PoAs2yyP4NunchnlgD7iIj5H64Ed2D7s1x3FvE7wpxPsGZs6LDEwJwETI85A6MbNi32uw72KA0t4mUNkRGR8HQ==
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.2.27" element-plus "^2.2.28"
merge "^2.1.1" merge "^2.1.1"
mitt "^3.0.0" mitt "^3.0.0"
vue "^3.2.45" vue "^3.2.45"
@ -1290,33 +1203,6 @@
"@jridgewell/resolve-uri" "3.1.0" "@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14" "@jridgewell/sourcemap-codec" "1.4.14"
"@lezer/common@^1.0.0":
version "1.0.2"
resolved "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz#8fb9b86bdaa2ece57e7d59e5ffbcb37d71815087"
integrity sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==
"@lezer/highlight@^1.0.0":
version "1.1.3"
resolved "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz#bf5a36c2ee227f526d74997ac91f7777e29bd25d"
integrity sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==
dependencies:
"@lezer/common" "^1.0.0"
"@lezer/javascript@^1.0.0":
version "1.4.0"
resolved "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.0.tgz#fe71474fcadc6112fb0978310faed0788d0af824"
integrity sha512-MQ3oLJGEtpUgZ03LOLI60tDnjSkKO6h9hZSe31qJ1UQV+I9bpv3pwSnPUnX0+e+3E1PBVkox0GB2/MXkxg0M2w==
dependencies:
"@lezer/highlight" "^1.0.0"
"@lezer/lr" "^1.0.0"
"@lezer/lr@^1.0.0":
version "1.2.5"
resolved "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz#e9088164a711690596f17378665e0554157c9b03"
integrity sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==
dependencies:
"@lezer/common" "^1.0.0"
"@node-ipc/js-queue@2.0.3": "@node-ipc/js-queue@2.0.3":
version "2.0.3" version "2.0.3"
resolved "https://registry.npmjs.org/@node-ipc/js-queue/-/js-queue-2.0.3.tgz#ac7fe33d766fa53e233ef8fedaf3443a01c5a4cd" resolved "https://registry.npmjs.org/@node-ipc/js-queue/-/js-queue-2.0.3.tgz#ac7fe33d766fa53e233ef8fedaf3443a01c5a4cd"
@ -1450,7 +1336,7 @@
resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/quill@^2.0.9": "@types/quill@^2.0.10":
version "2.0.10" version "2.0.10"
resolved "https://registry.npmjs.org/@types/quill/-/quill-2.0.10.tgz#bd3df7b59e0cfeb90f3d38892e4d1866261e7c0e" resolved "https://registry.npmjs.org/@types/quill/-/quill-2.0.10.tgz#bd3df7b59e0cfeb90f3d38892e4d1866261e7c0e"
integrity sha512-L6OHONEj2v4NRbWQOsn7j1N0SyzhRR3M4g1M6j/uuIwIsIW2ShWHhwbqNvH8hSmVktzqu0lITfdnqVOQ4qkrhA== integrity sha512-L6OHONEj2v4NRbWQOsn7j1N0SyzhRR3M4g1M6j/uuIwIsIW2ShWHhwbqNvH8hSmVktzqu0lITfdnqVOQ4qkrhA==
@ -2168,7 +2054,7 @@ ansi-styles@^3.2.1:
dependencies: dependencies:
color-convert "^1.9.0" color-convert "^1.9.0"
ansi-styles@^4.1.0: ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
@ -2424,6 +2310,15 @@ cli-spinners@^2.5.0:
resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a"
integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
clone@^1.0.2: clone@^1.0.2:
version "1.0.4" version "1.0.4"
resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
@ -2434,19 +2329,6 @@ clone@^2.1.1:
resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
codemirror@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29"
integrity sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==
dependencies:
"@codemirror/autocomplete" "^6.0.0"
"@codemirror/commands" "^6.0.0"
"@codemirror/language" "^6.0.0"
"@codemirror/lint" "^6.0.0"
"@codemirror/search" "^6.0.0"
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.0.0"
codepage@~1.15.0: codepage@~1.15.0:
version "1.15.0" version "1.15.0"
resolved "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab" resolved "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab"
@ -2541,11 +2423,6 @@ crc-32@~1.2.0, crc-32@~1.2.1:
resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
crelt@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94"
integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==
cross-spawn@^6.0.0: cross-spawn@^6.0.0:
version "6.0.5" version "6.0.5"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@ -2682,7 +2559,7 @@ electron-to-chromium@^1.4.251:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==
element-plus@^2.2.27, element-plus@^2.2.28: element-plus@^2.2.28:
version "2.2.28" version "2.2.28"
resolved "https://registry.npmjs.org/element-plus/-/element-plus-2.2.28.tgz#855441976e82da597faecaf6ed74fc4650a970b2" resolved "https://registry.npmjs.org/element-plus/-/element-plus-2.2.28.tgz#855441976e82da597faecaf6ed74fc4650a970b2"
integrity sha512-BsxF7iEaBydmRfw1Tt++EO9jRBjbtJr7ZRIrnEwz4J3Cwa1IzHCNCcx3ZwcYTlJq9CYFxv94JnbNr1EbkTou3A== integrity sha512-BsxF7iEaBydmRfw1Tt++EO9jRBjbtJr7ZRIrnEwz4J3Cwa1IzHCNCcx3ZwcYTlJq9CYFxv94JnbNr1EbkTou3A==
@ -2703,6 +2580,11 @@ element-plus@^2.2.27, element-plus@^2.2.28:
memoize-one "^6.0.0" memoize-one "^6.0.0"
normalize-wheel-es "^1.2.0" normalize-wheel-es "^1.2.0"
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
emojis-list@^3.0.0: emojis-list@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
@ -3263,6 +3145,11 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385"
@ -3546,6 +3433,11 @@ is-extglob@^2.1.1:
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3" version "4.0.3"
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
@ -3974,6 +3866,11 @@ mockjs@^1.1.0:
dependencies: dependencies:
commander "*" commander "*"
monaco-editor@^0.34.1:
version "0.34.1"
resolved "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz#1b75c4ad6bc4c1f9da656d740d98e0b850a22f87"
integrity sha512-FKc80TyiMaruhJKKPz5SpJPIjL+dflGvz4CpuThaPMc94AyN7SeC9HQ8hrvaxX7EyHdJcUY5i4D0gNyJj1vSZQ==
ms@2.1.2: ms@2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@ -4107,7 +4004,7 @@ onetime@^5.1.0:
dependencies: dependencies:
mimic-fn "^2.1.0" mimic-fn "^2.1.0"
open@^8.0.2: open@^8.0.2, open@^8.4.0:
version "8.4.0" version "8.4.0"
resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==
@ -4445,6 +4342,11 @@ regjsparser@^0.9.1:
dependencies: dependencies:
jsesc "~0.5.0" jsesc "~0.5.0"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
resize-detector@^0.3.0: resize-detector@^0.3.0:
version "0.3.0" version "0.3.0"
resolved "https://registry.npmjs.org/resize-detector/-/resize-detector-0.3.0.tgz#fe495112e184695500a8f51e0389f15774cb1cfc" resolved "https://registry.npmjs.org/resize-detector/-/resize-detector-0.3.0.tgz#fe495112e184695500a8f51e0389f15774cb1cfc"
@ -4484,6 +4386,16 @@ rimraf@^3.0.2:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rollup-plugin-visualizer@^5.9.0:
version "5.9.0"
resolved "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz#013ac54fb6a9d7c9019e7eb77eced673399e5a0b"
integrity sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==
dependencies:
open "^8.4.0"
picomatch "^2.3.1"
source-map "^0.7.4"
yargs "^17.5.1"
rollup@^3.7.0: rollup@^3.7.0:
version "3.9.0" version "3.9.0"
resolved "https://registry.npmjs.org/rollup/-/rollup-3.9.0.tgz#0ff7ab7cd71ce3a6ab140c5cf661f2b35eb6aab8" resolved "https://registry.npmjs.org/rollup/-/rollup-3.9.0.tgz#0ff7ab7cd71ce3a6ab140c5cf661f2b35eb6aab8"
@ -4697,6 +4609,11 @@ source-map@^0.6.0, source-map@^0.6.1:
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.4:
version "0.7.4"
resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
sourcemap-codec@^1.4.8: sourcemap-codec@^1.4.8:
version "1.4.8" version "1.4.8"
resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
@ -4745,6 +4662,15 @@ store@^2.0.12:
resolved "https://registry.npmjs.org/store/-/store-2.0.12.tgz#8c534e2a0b831f72b75fc5f1119857c44ef5d593" resolved "https://registry.npmjs.org/store/-/store-2.0.12.tgz#8c534e2a0b831f72b75fc5f1119857c44ef5d593"
integrity sha512-eO9xlzDpXLiMr9W1nQ3Nfp9EzZieIQc10zPPMP5jsVV7bLOziSFFBP0XoDXACEIFtdI+rIz0NwWVA/QVJ8zJtw== integrity sha512-eO9xlzDpXLiMr9W1nQ3Nfp9EzZieIQc10zPPMP5jsVV7bLOziSFFBP0XoDXACEIFtdI+rIz0NwWVA/QVJ8zJtw==
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string.prototype.trimend@^1.0.6: string.prototype.trimend@^1.0.6:
version "1.0.6" version "1.0.6"
resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533"
@ -4787,11 +4713,6 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
style-mod@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz#97e7c2d68b592975f2ca7a63d0dd6fcacfe35a01"
integrity sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==
supports-color@^5.3.0: supports-color@^5.3.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@ -5038,16 +4959,6 @@ vite@^4.0.3:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
vue-codemirror@^6.1.1:
version "6.1.1"
resolved "https://registry.npmjs.org/vue-codemirror/-/vue-codemirror-6.1.1.tgz#246697ef4cfa6b2448dd592ade214bb7ff86611f"
integrity sha512-rTAYo44owd282yVxKtJtnOi7ERAcXTeviwoPXjIc6K/IQYUsoDkzPvw/JDFtSP6T7Cz/2g3EHaEyeyaQCKoDMg==
dependencies:
"@codemirror/commands" "6.x"
"@codemirror/language" "6.x"
"@codemirror/state" "6.x"
"@codemirror/view" "6.x"
vue-demi@*, vue-demi@^0.13.2: vue-demi@*, vue-demi@^0.13.2:
version "0.13.11" version "0.13.11"
resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99" resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
@ -5099,11 +5010,6 @@ vuedraggable@^4.1.0:
dependencies: dependencies:
sortablejs "1.14.0" sortablejs "1.14.0"
w3c-keyname@^2.2.4:
version "2.2.6"
resolved "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz#8412046116bc16c5d73d4e612053ea10a189c85f"
integrity sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==
watchpack@^2.4.0: watchpack@^2.4.0:
version "2.4.0" version "2.4.0"
resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
@ -5212,6 +5118,15 @@ word@~0.3.0:
resolved "https://registry.npmjs.org/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961" resolved "https://registry.npmjs.org/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961"
integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrappy@1: wrappy@1:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@ -5245,6 +5160,11 @@ xmlhttprequest-ssl@~2.0.0:
resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yallist@^3.0.2: yallist@^3.0.2:
version "3.1.1" version "3.1.1"
resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
@ -5260,6 +5180,24 @@ yaml@^1.7.2:
resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^17.5.1:
version "17.6.2"
resolved "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541"
integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==
dependencies:
cliui "^8.0.1"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.3"
y18n "^5.0.5"
yargs-parser "^21.1.1"
yocto-queue@^0.1.0: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"