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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ export const config = {
// 忽略规则
ignore: {
// 不显示请求进度条
NProgress: ["/sys/info/record"],
NProgress: ["/base/comm/upload", "/base/comm/uploadMode"],
// 页面不需要登录验证
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 { onBeforeUpdate, ref, inject } from "vue";
import { isNumber } from "lodash-es";
import { inject, getCurrentInstance, Ref, reactive } from "vue";
import { useRoute, useRouter } from "vue-router";
import { service } from "../service";
import { Data } from "../utils";
import { useBrowser } from "./browser";
export function useService(): Eps.Service {
return Data.get("service" || service);
}
export function useRefs() {
const refs: any = ref<any[]>([]);
onBeforeUpdate(() => {
refs.value = [];
});
const setRefs = (index: string) => (el: any) => {
refs.value[index] = el;
};
const refs = reactive<{ [key: string]: any }>({});
function setRefs(name: string) {
return (el: any) => {
refs[name] = el;
};
}
return { refs, setRefs };
}
export function useComm() {
function px(val: string | number) {
return isNumber(val) ? `${val}px` : val;
export function useParent(name: string, r: Ref) {
const d = getCurrentInstance();
if (d) {
let parent = d.proxy?.$.parent;
if (parent) {
while (parent && parent.type?.name != name && parent.type?.name != "cl-crud") {
parent = parent?.parent;
}
if (parent) {
if (parent.type.name == name) {
r.value = parent.proxy;
}
}
}
}
return {
px
};
return r;
}
export function useCool() {
@ -39,6 +48,9 @@ export function useCool() {
route: useRoute(),
router: useRouter(),
mitt: inject("mitt") as Emitter<any>,
...useBrowser(),
...useRefs()
};
}
export * from "./browser";

View File

@ -21,6 +21,10 @@ const routes: RouteRecordRaw[] = [
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();
rs.forEach((e) => {
if (e.name) {
if (e.name && e.meta?.dynamic) {
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";
// 首字母大写
@ -292,6 +293,21 @@ export function mergeService(list: any[]) {
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 * from "./data";
export * from "./loading";

View File

@ -1,8 +1,2 @@
import { resize } from "./resize";
window.onload = function () {
resize();
};
export * from "./theme";
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">
import { computed, defineComponent, PropType } from "vue";
import { isNumber } from "lodash-es";
import { User } from "@element-plus/icons-vue";
import { parsePx } from "/@/cool/utils";
export default defineComponent({
name: "cl-avatar",
@ -44,7 +44,7 @@ export default defineComponent({
},
setup(props) {
const size = isNumber(props.size) ? props.size + "px" : props.size;
const size = parsePx(props.size);
const style = computed(() => {
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>
<script lang="ts">
import { computed, defineComponent, onMounted, ref } from "vue";
import { useCrud } from "@cool-vue/crud";
import { computed, defineComponent, onMounted, ref, PropType, nextTick } from "vue";
import store from "store";
export default defineComponent({
@ -37,16 +36,13 @@ export default defineComponent({
props: {
name: String,
columns: {
type: Array,
type: Array as PropType<any[]>,
required: true,
default: () => []
}
},
setup(props) {
// cl-crud
const Crud = useCrud();
//
const visible = ref(false);
@ -59,8 +55,8 @@ export default defineComponent({
//
const list = computed(() => {
return props.columns
.filter((e: any) => !e.type)
.map((e: any) => {
.filter((e) => !e.type && e.prop)
.map((e) => {
return {
label: e.label,
value: e.prop
@ -76,17 +72,17 @@ export default defineComponent({
//
function change() {
const names = getNames();
nextTick(() => {
const names = getNames();
if (store.get(name)) {
Crud.value!["cl-table"].reBuild(() => {
props.columns.map((e: any) => {
if (store.get(name)) {
props.columns.map((e) => {
if (!e.type) {
e.hidden = !names.includes(e.prop);
}
});
});
}
}
});
}
//

View File

@ -30,7 +30,7 @@
import { computed, defineComponent } from "vue";
import { isArray, isNumber, isString } from "lodash-es";
import { PictureFilled } from "@element-plus/icons-vue";
import { useComm } from "/@/cool";
import { parsePx } from "/@/cool/utils";
export default defineComponent({
name: "cl-image",
@ -58,8 +58,6 @@ export default defineComponent({
},
setup(props) {
const { px } = useComm();
const urls = computed(() => {
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;
return {
h: px(h),
w: px(w)
h: parsePx(h),
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 { computed, defineComponent, isRef, ref, watch } from "vue";
import { computed, defineComponent, isRef, Ref, ref, watch } from "vue";
export default defineComponent({
name: "cl-select",
@ -23,10 +10,6 @@ export default defineComponent({
type: [Array, Object],
default: () => []
},
clearable: {
type: Boolean,
default: true
},
prop: String
},
@ -40,9 +23,9 @@ export default defineComponent({
const value = ref();
// 列表
const list = computed<any>(() =>
const list = computed(() =>
isRef(props.options) ? props.options.value : props.options
);
) as Ref<any[]>;
// 值改变
function onChange(val: string) {
@ -64,11 +47,14 @@ export default defineComponent({
}
);
return {
list,
value,
onChange
return () => {
return (
<el-select v-model={value.value} clearable filterable onChange={onChange}>
{list.value.map((e) => {
return <el-option {...e} />;
})}
</el-select>
);
};
}
});
</script>

View File

@ -71,20 +71,15 @@ export default defineComponent({
}
}
return {
status,
onChange
return () => {
return (
<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>
<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__left" :class="[isExpand ? '_expand' : '_collapse']">
<div class="cl-view-group__left">
<slot name="left"></slot>
<!-- 收起按钮 -->
<div class="collapse-btn" @click="expand(false)" v-if="browser.isMini">
<el-icon>
<arrow-right />
</el-icon>
</div>
</div>
<!-- 内容 -->
<div class="cl-view-group__right">
<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-else><arrow-right /></el-icon>
</div>
<span>{{ title }}</span>
<span>{{ label }}</span>
</div>
<div class="cl-view-group__right-content">
@ -28,7 +35,7 @@
<script lang="ts">
import { defineComponent, provide, ref, watch } from "vue";
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
import { useStore } from "../../store";
import { useBrowser } from "/@/cool";
export default defineComponent({
name: "cl-view-group",
@ -39,47 +46,67 @@ export default defineComponent({
},
props: {
title: String
title: String,
leftWidth: {
type: String,
default: "300px"
}
},
setup() {
const { app } = useStore();
setup(props) {
const { browser, onScreenChange } = useBrowser();
//
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;
}
//
function checkExpand(value?: boolean) {
if (app.browser.isMini) {
toExpand(value);
//
function select(data?: any) {
selected.value = data;
if (browser.isMini) {
expand(false);
}
}
//
watch(
() => app.browser.isMini,
(val: boolean) => {
isExpand.value = !val;
},
{
immediate: true
}
);
//
function setTitle(value?: string) {
label.value = value || "";
}
provide("viewGroup", {
checkExpand
//
onScreenChange(() => {
expand(!browser.isMini);
});
return {
//
watch(() => props.title, setTitle, {
immediate: true
});
const ctx = {
label,
selected,
isExpand,
toExpand,
checkExpand
setTitle,
expand,
select,
browser
};
provide("viewGroup", ctx);
return ctx;
}
});
</script>
@ -89,6 +116,8 @@ export default defineComponent({
height: 100%;
width: 100%;
$left-width: v-bind("leftWidth");
&__wrap {
display: flex;
height: 100%;
@ -98,25 +127,39 @@ export default defineComponent({
}
&__left {
position: relative;
height: 100%;
width: 300px;
transition: width 0.3s;
flex-shrink: 0;
overflow: hidden;
border-right: 1px solid var(--el-border-color);
box-sizing: border-box;
width: $left-width;
background-color: #fff;
&._collapse {
margin-right: 0;
width: 0 !important;
border-right: 0;
.collapse-btn {
display: flex;
align-items: center;
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 {
width: calc(100% - 310px);
flex: 1;
overflow: hidden;
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 100%;
background-color: #fff;
transition: width 0.3s;
&-head {
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) {
.cl-view-group__left {
width: 100%;
overflow: hidden;
transition: width 0.2s;
width: 0;
z-index: 9;
}
.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 => {
return {
order: 99,
components: Object.values(import.meta.glob("./components/**/*")),
components: Object.values(import.meta.glob("./components/**/*.{vue,tsx}")),
views: [
{
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 "./hooks";
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 { Menu } from "../../types";
import { useCool } from "/@/cool";
export default {
export default defineComponent({
name: "b-menu",
setup() {
const { router, route } = useCool();
const { router, route, browser } = useCool();
const { menu, app } = useStore();
// 页面跳转
@ -16,89 +16,87 @@ export default {
router.push(url);
}
// 移动端点击收起左侧菜单
if (app.browser.isMini) {
// 小屏下点击收起左侧菜单
if (browser.isMini) {
app.fold(true);
}
}
return {
route,
toView,
menu
};
},
// 渲染子菜单
function renderMenu() {
function deep(list: Menu.Item[], index: number) {
return list
.filter((e) => e.isShow)
.map((e) => {
let html = null;
render(ctx: any) {
const { app } = useStore();
// 设置子菜单
function deep(list: Menu.Item[], index: number) {
return list
.filter((e) => e.isShow)
.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>
);
if (e.type == 0) {
html = h(
<el-sub-menu />,
{
index: String(e.id),
key: e.id,
popperClass: "app-slider__menu"
},
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 {
html = h(
<el-menu-item />,
{
index: e.path,
key: e.id
},
{
default() {
return (
<div class="wrap">
<cl-svg name={e.icon} />
<span v-show={!app.isFold || index != 1}>{e.name}</span>
</div>
);
);
} else {
html = h(
<el-menu-item />,
{
index: e.path,
key: e.id
},
{
default() {
return (
<div class="wrap">
<cl-svg name={e.icon} />
<span v-show={!app.isFold || index != 1}>
{e.name}
</span>
</div>
);
}
}
}
);
}
);
}
return html;
});
return html;
});
}
return deep(menu.list, 1);
}
const children = deep(ctx.menu.list, 1);
return (
<div class="app-slider__menu">
<el-menu
default-active={ctx.route.path}
background-color="transparent"
collapse-transition={true}
collapse={app.browser.isMini ? false : app.isFold}
onSelect={ctx.toView}
>
{children}
</el-menu>
</div>
);
return () => {
return (
<div class="app-slider__menu">
<el-menu
default-active={route.path}
background-color="transparent"
collapse-transition={true}
collapse={browser.isMini ? false : app.isFold}
onSelect={toView}
>
{renderMenu()}
</el-menu>
</div>
);
};
}
};
});

View File

@ -54,7 +54,7 @@ function toPath() {
//
function scrollTo(left: number) {
refs.value.scroller.scrollTo({
refs.scroller.scrollTo({
left,
behavior: "smooth"
});
@ -62,10 +62,10 @@ function scrollTo(left: number) {
//
function adScroll(index: number) {
const el = refs.value[`item-${index}`];
const el = refs[`item-${index}`];
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>
<div class="route-nav">
<p v-if="app.browser.isMini" class="title">
<p v-if="browser.isMini" class="title">
{{ lastName }}
</p>
@ -20,8 +20,8 @@ import { flattenDeep, last } from "lodash-es";
import { useCool } from "/@/cool";
import { useBase } from "/$/base";
const { route } = useCool();
const { app, menu } = useBase();
const { route, browser } = useCool();
const { menu } = useBase();
//
const list = computed(() => {
@ -58,21 +58,18 @@ const lastName = computed(() => last(list.value)?.name);
.route-nav {
white-space: nowrap;
.el-breadcrumb {
:deep(.el-breadcrumb) {
margin: 0 10px;
&__inner {
.el-breadcrumb__inner {
font-size: 13px;
padding: 0 10px;
font-weight: normal;
letter-spacing: 1px;
}
}
.title {
font-size: 15px;
font-weight: 500;
margin-left: 5px;
margin-left: 8px;
}
}
</style>

View File

@ -2,7 +2,7 @@
<div class="app-slider">
<div class="app-slider__logo" @click="toHome">
<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 class="app-slider__container">
@ -13,8 +13,10 @@
<script lang="ts" setup>
import { useBase } from "/$/base";
import { useBrowser } from "/@/cool";
import BMenu from "./bmenu";
const { browser } = useBrowser();
const { app } = useBase();
function toHome() {

View File

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

View File

@ -7,7 +7,7 @@
</div>
<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="用户名">
<input
v-model="form.username"
@ -105,7 +105,7 @@ async function toLogin() {
//
router.push("/");
} catch (err: any) {
refs.value.captcha.refresh();
refs.captcha.refresh();
ElMessage.error(err.message);
}
@ -178,6 +178,7 @@ async function toLogin() {
-webkit-text-fill-color: #fff;
font-size: 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 0;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px transparent inset !important;
@ -213,7 +214,9 @@ async function toLogin() {
margin-top: 50px;
:deep(.el-button) {
height: 40px;
width: 140px;
font-size: 16px;
}
}
}

View File

@ -1,4 +1,4 @@
// customize style
// customize
.scroller1 {
overflow: auto;
position: relative;
@ -19,7 +19,7 @@
}
}
// Element-plus theme
// Element-plus
.el-input-number {
.el-input-number__decrease,
.el-input-number__increase {
@ -29,5 +29,7 @@
}
.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 { reactive, ref } from "vue";
import { config } from "/@/cool";
import { deepMerge, getBrowser, storage } from "/@/cool/utils";
import { config, useBrowser } from "/@/cool";
import { deepMerge, storage } from "/@/cool/utils";
export const useAppStore = defineStore("app", function () {
const { browser, onScreenChange } = useBrowser();
// 基本信息
const info = reactive({
...config.app
});
// 浏览器信息
const browser = ref(getBrowser());
// 是否折叠
const isFold = ref(browser.value.isMini || false);
const isFold = ref(false);
// 事件
const events = reactive<{ [key: string]: any[] }>({
@ -35,11 +34,6 @@ export const useAppStore = defineStore("app", function () {
storage.set("__app__", info);
}
// 设置浏览器信息
function setBrowser() {
browser.value = getBrowser();
}
// 添加事件
function addEvent(name: string, func: any) {
if (func) {
@ -47,14 +41,17 @@ export const useAppStore = defineStore("app", function () {
}
}
// 监听屏幕变化
onScreenChange(() => {
isFold.value = browser.isMini;
});
return {
info,
browser,
isFold,
fold,
events,
set,
setBrowser,
addEvent
};
});

View File

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

View File

@ -39,3 +39,16 @@ export declare namespace Process {
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";
const { service } = useCool();
// cl-form
const Form = useForm();
// cl-crud
const Crud = useCrud();
//
async function open(ids: any[]) {
Form.value?.open({
title: "部门转移",
width: "600px",
width: "500px",
props: {
labelWidth: "80px"
},
@ -42,7 +37,7 @@ async function open(ids: any[]) {
return done();
}
ElMessageBox.confirm("是否转移部门?", "提示", {
ElMessageBox.confirm("转移到新部门,是否继续", "提示", {
type: "warning"
})
.then(() => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,10 @@
<template>
<cl-crud ref="Crud">
<el-row>
<cl-row>
<cl-refresh-btn />
<cl-add-btn />
<form-crud />
<cl-multi-delete-btn />
<form-btn />
<cl-filter label="字典筛选">
<cl-select :options="dict.get('brand')" prop="brand" />
@ -12,28 +13,49 @@
<cl-flex1 />
<cl-column-custom :columns="Table?.columns" />
<cl-search-key />
</el-row>
<cl-adv-btn />
</cl-row>
<el-row>
<cl-table ref="Table" />
</el-row>
<cl-row>
<cl-table ref="Table" show-summary :summary-method="onSummaryMethod">
<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-pagination />
</el-row>
</cl-row>
<cl-upsert ref="Upsert" />
<cl-adv-search ref="AdvSearch" />
</cl-crud>
</template>
<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 FormCrud from "../components/form-crud.vue";
import FormBtn from "../components/form.vue";
import { reactive } from "vue";
const { dict } = useDict();
const options = reactive({
status: [
{
label: "开启",
value: 1
},
{
label: "关闭",
type: "danger",
value: 0
}
]
});
const Crud = useCrud(
{
service: "test"
@ -43,19 +65,13 @@ const Crud = useCrud(
}
);
//
const Upsert = useUpsert({
items: [
{
label: "姓名",
prop: "name",
required: true,
component: {
name: "el-input"
}
},
{
type: "tabs",
props: {
type: "card",
labels: [
{
label: "基础",
@ -68,6 +84,15 @@ const Upsert = useUpsert({
]
}
},
{
label: "姓名",
prop: "name",
required: true,
group: "base",
component: {
name: "el-input"
}
},
{
label: "认证类型",
prop: "authType",
@ -106,36 +131,39 @@ const Upsert = useUpsert({
}
});
//
const Table = useTable({
columns: [
{
type: "selection"
type: "selection",
width: 60
},
() => {
return {
label: "#",
type: "expand",
prop: "detail"
};
},
{
label: "姓名",
prop: "name"
},
{
label: "存款",
prop: "price",
formatter(row) {
return `¥${row.price}`;
}
label: "基础信息",
prop: "baseInfo",
children: [
{
label: "姓名",
prop: "name"
},
{
label: "存款(元)",
prop: "price",
sortable: true
}
]
},
{
label: "状态",
prop: "status",
dict: [
{
label: "开启",
value: 1
},
{
label: "关闭",
type: "danger",
value: 0
}
]
dict: options.status
},
{
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>

View File

@ -12,11 +12,12 @@
import ContextMenu from "../components/context-menu.vue";
import Crud from "../components/crud.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 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>
<style lang="scss">

View File

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

View File

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

View File

@ -2,19 +2,23 @@
<div class="demo">
<el-tabs type="card">
<el-tab-pane label="普通上传">
<cl-upload v-model="urls" />
<cl-upload v-model="v1" />
</el-tab-pane>
<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 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 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>
<template #item="{ item }">
@ -24,11 +28,19 @@
</el-tab-pane>
<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 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-tabs>
</div>
@ -37,8 +49,31 @@
<script lang="ts" name="upload" setup>
import { ref } from "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>
<style lang="scss" scoped>
@ -53,14 +88,20 @@ const urls = ref("");
border: 1px solid var(--el-border-color);
border-radius: 3px;
padding: 5px 10px;
margin-top: 10px;
margin-bottom: 10px;
font-size: 12px;
width: 100%;
box-sizing: border-box;
}
}
.cl-upload__list {
width: 100%;
.space-upload {
margin-top: 10px;
.el-image {
margin-right: 10px;
height: 100px;
width: 100px;
}
}
}

View File

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

View File

@ -1,32 +1,24 @@
<template>
<cl-view-group :title="title">
<cl-view-group ref="ViewGroup">
<template #left>
<dict-group />
<dict-group @refresh="refresh" />
</template>
<template #right>
<cl-crud ref="Crud">
<el-row>
<cl-row>
<!-- 刷新按钮 -->
<cl-refresh-btn />
<!-- 新增按钮 -->
<cl-add-btn :disabled="!group" />
<cl-add-btn :disabled="!ViewGroup?.selected" />
<cl-flex1 />
<!-- 关键字搜索 -->
<cl-search-key />
</el-row>
</cl-row>
<el-row>
<cl-row>
<!-- 数据表格 -->
<cl-table
ref="Table"
:default-sort="{
prop: 'orderNum',
order: 'ascending'
}"
row-key="id"
@row-click="onRowClick"
>
<cl-table ref="Table" row-key="id" @row-click="onRowClick">
<template #slot-btn="{ scope }">
<el-button
text
@ -38,11 +30,11 @@
>
</template>
</cl-table>
</el-row>
</cl-row>
<el-row>
<cl-row>
<cl-flex1 />
</el-row>
</cl-row>
<!-- 新增编辑 -->
<cl-upsert ref="Upsert" />
@ -55,19 +47,13 @@
import { useCrud, useTable, useUpsert } from "@cool-vue/crud";
import { useCool } from "/@/cool";
import DictGroup from "../components/group.vue";
import { computed, provide, ref } from "vue";
import { computed } from "vue";
import { deepTree } from "/@/cool/utils";
import { cloneDeep } from "lodash-es";
import { useViewGroup } from "/$/base";
const { service } = useCool();
//
const group = ref();
//
const title = computed(() => {
return group.value ? `字典列表(${group.value.name}` : "字典列表";
});
const { ViewGroup } = useViewGroup();
// cl-upsert
const Upsert = useUpsert({
@ -141,7 +127,7 @@ const Upsert = useUpsert({
onSubmit(data, { next }) {
next({
...data,
typeId: group.value.id
typeId: ViewGroup.value?.selected?.id
});
}
});
@ -167,7 +153,7 @@ const Table = useTable({
],
columns: [
{ 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: "createTime", sortable: "custom", minWidth: 160 },
{ label: "更新时间", prop: "updateTime", sortable: "custom", minWidth: 160 },
@ -185,7 +171,6 @@ const Crud = useCrud({
onRefresh(params, { render }) {
service.dict.info
.list({
typeId: group.value?.id,
...params,
page: undefined,
size: undefined
@ -201,11 +186,6 @@ function refresh(params?: any) {
Crud.value?.refresh(params);
}
//
function setGroup(data: any) {
group.value = data;
}
//
function onRowClick(row: any, column: any) {
if (column?.property && row.children) {
@ -220,10 +200,4 @@ function append(row: any) {
orderNum: 1
});
}
provide("dict", {
group,
refresh,
setGroup
});
</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"
accept="image/*"
:show-btn="false"
@confirm="onSpaceConfirm"
@confirm="onFileConfirm"
/>
<!-- 视频 -->
@ -15,7 +15,7 @@
ref="VideoSpace"
accept="video/*"
:show-btn="false"
@confirm="onSpaceConfirm"
@confirm="onFileConfirm"
/>
</div>
</template>
@ -24,7 +24,7 @@
import { defineComponent, onMounted, ref, watch } from "vue";
import Quill from "quill";
import "quill/dist/quill.snow.css";
import { useComm } from "/@/cool";
import { parsePx } from "/@/cool/utils";
export default defineComponent({
name: "cl-editor-quill",
@ -42,8 +42,6 @@ export default defineComponent({
emits: ["update:modelValue", "load"],
setup(props, { emit }) {
const { px } = useComm();
let quill: any = null;
//
@ -81,7 +79,7 @@ export default defineComponent({
}
//
function onSpaceConfirm(files: any[]) {
function onFileConfirm(files: any[]) {
if (files.length > 0) {
//
files.forEach((file, i) => {
@ -107,7 +105,7 @@ export default defineComponent({
//
function setHeight() {
quill.container.style.height = px(props.height);
quill.container.style.height = parsePx(props.height);
}
//
@ -187,7 +185,7 @@ export default defineComponent({
quill,
cursorIndex,
setContent,
onSpaceConfirm
onFileConfirm
};
}
});

View File

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

View File

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

View File

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

View File

@ -26,10 +26,10 @@ const props = defineProps({
const { space } = useSpace();
const info = computed<Eps.SpaceInfoEntity>(() => props.data || {});
const Video = ref<HTMLVideoElement>();
const info = computed<Eps.SpaceInfoEntity>(() => props.data || {});
const loaded = computed(() => {
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"
:title="title"
height="650px"
width="1080px"
width="1070px"
keep-alive
custom-class="cl-upload-space__dialog"
:close-on-click-modal="false"
append-to-body
:controls="['slot-expand', 'cl-flex1', 'fullscreen', 'close']"
>
<div
class="cl-upload-space"
:class="{
'is-mini': app.browser.isMini
}"
@dragover="onDragover"
@drop="onDrop"
>
<!-- 类目 -->
<space-category />
<cl-upload-panel :limit="limit" :accept="accept" ref="Panel" />
<!-- 内容 -->
<div class="cl-upload-space__content">
<!-- 操作栏 -->
<div class="cl-upload-space__header scroller1">
<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 #footer>
<el-button @click="close">取消</el-button>
<el-button :disabled="selection.length == 0" type="success" @click="confirm"
>选择 {{ selection.length }}/{{ limit }}</el-button
>
</template>
</cl-dialog>
</div>
</template>
<script lang="ts" setup name="cl-upload-space">
import { computed, provide, reactive, ref, watch } 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";
import { computed, nextTick, ref } from "vue";
const props = defineProps({
//
modelValue: String,
defineProps({
//
title: {
type: String,
default: "文件空间"
},
//
limit: Number,
//
disabled: Boolean,
limit: {
type: Number,
default: 9
},
//
accept: String,
//
showBtn: {
type: Boolean,
default: true
},
//
title: {
type: String,
default: "文件空间"
}
});
const emit = defineEmits(["update:modelValue", "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 emit = defineEmits(["confirm"]);
//
const visible = ref(false);
//
const loading = ref(false);
// cl-upload-panel
const Panel = ref();
//
const selection = ref<Eps.SpaceInfoEntity[]>([]);
//
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;
//
const selection = computed<any[]>(() => Panel.value?.selection || []);
function open() {
visible.value = true;
if (!lock) {
lock = true;
refresh();
}
}
//
function clear() {
selection.value = [];
nextTick(() => {
Panel.value?.clear();
});
}
//
function close() {
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() {
emit("update:modelValue", selection.value.map((e) => e.url).join(","));
emit("confirm", selection.value);
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({
open,
close,
clear,
refresh
close
});
</script>
<style lang="scss">
.cl-upload-space {
display: flex;
height: 100%;
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;
}
.cl-upload-space__dialog {
.el-dialog__footer {
border-top: 1px solid #f7f7f7;
}
}
</style>

View File

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

View File

@ -1,6 +1,10 @@
export default () => {
return {
components: [import("./components/index.vue"), import("./components/space.vue")],
components: [
import("./components/upload.vue"),
import("./components/space.vue"),
import("./components/panel.vue")
],
options: {
// 尺寸
@ -11,11 +15,19 @@ export default () => {
limit: {
// 上传最大数量
upload: 9,
// 文件空间选择数
select: 9,
// 上传大小限制
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) {
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 vueJsx from "@vitejs/plugin-vue-jsx";
import compression from "vite-plugin-compression";
import { visualizer } from "rollup-plugin-visualizer";
import { proxy } from "./src/cool/config/proxy";
import { cool } from "./build/cool";
@ -15,7 +16,17 @@ function resolve(dir: string) {
export default (): UserConfig => {
return {
base: "/",
plugins: [vue(), compression(), vueJsx(), cool()],
plugins: [
vue(),
compression(),
vueJsx(),
cool(),
visualizer({
open: false,
gzipSize: true,
brotliSize: true
})
],
css: {
preprocessorOptions: {
scss: {

260
yarn.lock
View File

@ -965,101 +965,14 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@codemirror/autocomplete@^6.0.0":
version "6.4.0"
resolved "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz#76ac9a2a411a4cc6e13103014dba5e0fe601da5a"
integrity sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==
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==
"@cool-vue/crud@^6.0.1":
version "6.0.1"
resolved "https://registry.npmjs.org/@cool-vue/crud/-/crud-6.0.1.tgz#26f89b05a939d3dcd316bf00fd4cf88c14e4c193"
integrity sha512-LjcS5dvOpE/RUzIk2bSBawVk+NMMxj7MFPYXMdCPST8loPwOSqoCeIfkCwkJP8m4S4fdhvCCpSoRmY97ezhrAA==
dependencies:
array.prototype.flat "^1.2.4"
core-js "^3.21.1"
element-plus "^2.2.27"
element-plus "^2.2.28"
merge "^2.1.1"
mitt "^3.0.0"
vue "^3.2.45"
@ -1290,33 +1203,6 @@
"@jridgewell/resolve-uri" "3.1.0"
"@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":
version "2.0.3"
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"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/quill@^2.0.9":
"@types/quill@^2.0.10":
version "2.0.10"
resolved "https://registry.npmjs.org/@types/quill/-/quill-2.0.10.tgz#bd3df7b59e0cfeb90f3d38892e4d1866261e7c0e"
integrity sha512-L6OHONEj2v4NRbWQOsn7j1N0SyzhRR3M4g1M6j/uuIwIsIW2ShWHhwbqNvH8hSmVktzqu0lITfdnqVOQ4qkrhA==
@ -2168,7 +2054,7 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.1.0:
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
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"
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:
version "1.0.4"
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"
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:
version "1.15.0"
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"
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:
version "6.0.5"
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"
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"
resolved "https://registry.npmjs.org/element-plus/-/element-plus-2.2.28.tgz#855441976e82da597faecaf6ed74fc4650a970b2"
integrity sha512-BsxF7iEaBydmRfw1Tt++EO9jRBjbtJr7ZRIrnEwz4J3Cwa1IzHCNCcx3ZwcYTlJq9CYFxv94JnbNr1EbkTou3A==
@ -2703,6 +2580,11 @@ element-plus@^2.2.27, element-plus@^2.2.28:
memoize-one "^6.0.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:
version "3.0.0"
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"
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:
version "1.1.3"
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"
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:
version "4.0.3"
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
@ -3974,6 +3866,11 @@ mockjs@^1.1.0:
dependencies:
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:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@ -4107,7 +4004,7 @@ onetime@^5.1.0:
dependencies:
mimic-fn "^2.1.0"
open@^8.0.2:
open@^8.0.2, open@^8.4.0:
version "8.4.0"
resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8"
integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==
@ -4445,6 +4342,11 @@ regjsparser@^0.9.1:
dependencies:
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:
version "0.3.0"
resolved "https://registry.npmjs.org/resize-detector/-/resize-detector-0.3.0.tgz#fe495112e184695500a8f51e0389f15774cb1cfc"
@ -4484,6 +4386,16 @@ rimraf@^3.0.2:
dependencies:
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:
version "3.9.0"
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"
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:
version "1.4.8"
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"
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:
version "1.0.6"
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"
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:
version "5.5.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@ -5038,16 +4959,6 @@ vite@^4.0.3:
optionalDependencies:
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:
version "0.13.11"
resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
@ -5099,11 +5010,6 @@ vuedraggable@^4.1.0:
dependencies:
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:
version "2.4.0"
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"
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:
version "1.0.2"
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"
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:
version "3.1.1"
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"
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:
version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"