Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
export * from './types';
|
||||
export { default as ZqDialog } from './zq-dialog.vue';
|
||||
@@ -0,0 +1,57 @@
|
||||
/* 覆盖 el-dialog 的默认样式 */
|
||||
|
||||
.zq-dialog .el-dialog {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.zq-dialog .el-dialog__header {
|
||||
padding: 0 20px 16px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.zq-dialog .el-dialog__body {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.zq-dialog .el-dialog__footer {
|
||||
padding: 16px 20px 0;
|
||||
}
|
||||
|
||||
/* 限制 dialog 最大高度 */
|
||||
.zq-dialog .el-dialog {
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.zq-dialog .el-dialog__body {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.zq-dialog .zq-dialog-body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 全屏模式下的样式调整 */
|
||||
.zq-dialog.is-fullscreen .el-dialog {
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.zq-dialog.is-fullscreen .el-dialog__body {
|
||||
height: calc(100vh - 130px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.zq-dialog.is-fullscreen .zq-dialog-body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.zq-dialog.is-fullscreen .el-scrollbar {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.zq-dialog.is-fullscreen .el-scrollbar__wrap {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import type { DialogProps } from 'element-plus';
|
||||
|
||||
export interface ZqDialogProps extends Partial<DialogProps> {
|
||||
/** v-model 控制显隐 */
|
||||
modelValue?: boolean;
|
||||
|
||||
/** 标题 */
|
||||
title?: string;
|
||||
|
||||
/** 宽度,默认 '50%' */
|
||||
width?: number | string;
|
||||
|
||||
/** 内容区固定高度 */
|
||||
contentHeight?: number | string;
|
||||
|
||||
/** 内容区最大高度(超出滚动) */
|
||||
maxHeight?: number | string;
|
||||
|
||||
/** 内容区 loading 遮罩 */
|
||||
loading?: boolean;
|
||||
|
||||
/** 确认按钮 loading */
|
||||
confirmLoading?: boolean;
|
||||
|
||||
/** 是否显示底部,默认 true */
|
||||
showFooter?: boolean;
|
||||
|
||||
/** 显示确认按钮,默认 true */
|
||||
showConfirmButton?: boolean;
|
||||
|
||||
/** 显示取消按钮,默认 true */
|
||||
showCancelButton?: boolean;
|
||||
|
||||
/** 确认按钮文字,默认 '确定' */
|
||||
confirmText?: string;
|
||||
|
||||
/** 取消按钮文字,默认 '取消' */
|
||||
cancelText?: string;
|
||||
|
||||
/** 确认按钮类型,默认 'primary' */
|
||||
confirmButtonType?:
|
||||
| 'danger'
|
||||
| 'default'
|
||||
| 'info'
|
||||
| 'primary'
|
||||
| 'success'
|
||||
| 'warning';
|
||||
|
||||
/** 显示全屏按钮,默认 true */
|
||||
showFullscreenButton?: boolean;
|
||||
|
||||
/** 默认全屏,默认 false */
|
||||
defaultFullscreen?: boolean;
|
||||
|
||||
/** 显示关闭按钮,默认 true */
|
||||
showCloseButton?: boolean;
|
||||
|
||||
/** 可拖拽,默认 true */
|
||||
draggable?: boolean;
|
||||
|
||||
/** 关闭销毁,默认 true */
|
||||
destroyOnClose?: boolean;
|
||||
|
||||
/** 点击遮罩关闭,默认 false */
|
||||
closeOnClickModal?: boolean;
|
||||
|
||||
/** 插入 body,默认 true */
|
||||
appendToBody?: boolean;
|
||||
|
||||
/** 关闭前的回调,调用 done() 才真正关闭 */
|
||||
beforeClose?: (done: () => void) => void;
|
||||
}
|
||||
|
||||
export interface ZqDialogEmits {
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'confirm'): void;
|
||||
(e: 'cancel'): void;
|
||||
(e: 'open'): void;
|
||||
(e: 'opened'): void;
|
||||
(e: 'close'): void;
|
||||
(e: 'closed'): void;
|
||||
}
|
||||
|
||||
export interface ZqDialogExpose {
|
||||
/** 打开对话框 */
|
||||
open: () => void;
|
||||
/** 关闭对话框 */
|
||||
close: () => void;
|
||||
/** 设置内容区 loading */
|
||||
setLoading: (val: boolean) => void;
|
||||
/** 设置确认按钮 loading */
|
||||
setConfirmLoading: (val: boolean) => void;
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
<script setup lang="ts">
|
||||
import type { ZqDialogEmits, ZqDialogExpose, ZqDialogProps } from './types';
|
||||
|
||||
import { computed, ref, useAttrs, watch } from 'vue';
|
||||
|
||||
import { Maximize, Minimize, X } from '@vben/icons';
|
||||
|
||||
import { ElButton, ElDialog, ElScrollbar } from 'element-plus';
|
||||
|
||||
import './style.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'ZqDialog',
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<ZqDialogProps>(), {
|
||||
modelValue: false,
|
||||
title: '',
|
||||
width: '50%',
|
||||
contentHeight: undefined,
|
||||
maxHeight: undefined,
|
||||
loading: false,
|
||||
confirmLoading: false,
|
||||
showFooter: true,
|
||||
showConfirmButton: true,
|
||||
showCancelButton: true,
|
||||
confirmText: '确定',
|
||||
cancelText: '取消',
|
||||
confirmButtonType: 'primary',
|
||||
showFullscreenButton: true,
|
||||
defaultFullscreen: false,
|
||||
showCloseButton: true,
|
||||
draggable: true,
|
||||
destroyOnClose: true,
|
||||
closeOnClickModal: false,
|
||||
appendToBody: true,
|
||||
beforeClose: undefined,
|
||||
});
|
||||
|
||||
const emit = defineEmits<ZqDialogEmits>();
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
// 内部状态
|
||||
const visible = ref(props.modelValue);
|
||||
const isFullscreen = ref(props.defaultFullscreen);
|
||||
const innerLoading = ref(props.loading);
|
||||
const innerConfirmLoading = ref(props.confirmLoading);
|
||||
|
||||
// 监听 props 变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
visible.value = val;
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.loading,
|
||||
(val) => {
|
||||
innerLoading.value = val;
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.confirmLoading,
|
||||
(val) => {
|
||||
innerConfirmLoading.value = val;
|
||||
},
|
||||
);
|
||||
|
||||
// 监听内部状态变化,同步到外部
|
||||
watch(visible, (val) => {
|
||||
emit('update:modelValue', val);
|
||||
// 关闭时重置全屏状态为默认值
|
||||
if (!val) {
|
||||
isFullscreen.value = props.defaultFullscreen;
|
||||
}
|
||||
});
|
||||
|
||||
// 计算 dialog class
|
||||
const dialogClass = computed(() => {
|
||||
const classes = ['zq-dialog'];
|
||||
if (isFullscreen.value) {
|
||||
classes.push('is-fullscreen');
|
||||
}
|
||||
return classes.join(' ');
|
||||
});
|
||||
|
||||
// 计算滚动区域样式
|
||||
const scrollbarStyle = computed(() => {
|
||||
const style: Record<string, string> = {};
|
||||
if (props.contentHeight) {
|
||||
style.height =
|
||||
typeof props.contentHeight === 'number'
|
||||
? `${props.contentHeight}px`
|
||||
: props.contentHeight;
|
||||
}
|
||||
return style;
|
||||
});
|
||||
|
||||
// 计算滚动区域最大高度
|
||||
const scrollbarMaxHeight = computed(() => {
|
||||
if (isFullscreen.value) {
|
||||
return undefined; // 全屏时由 CSS 控制
|
||||
}
|
||||
if (props.maxHeight) {
|
||||
return typeof props.maxHeight === 'number'
|
||||
? `${props.maxHeight}px`
|
||||
: props.maxHeight;
|
||||
}
|
||||
// 默认最大高度为视口高度减去 header、footer 和边距的空间
|
||||
// 预留约 200px 给 header、footer 和边距
|
||||
return 'calc(100vh - 200px)';
|
||||
});
|
||||
|
||||
// 切换全屏
|
||||
function toggleFullscreen() {
|
||||
isFullscreen.value = !isFullscreen.value;
|
||||
}
|
||||
|
||||
// 关闭对话框
|
||||
function handleClose() {
|
||||
if (props.beforeClose) {
|
||||
props.beforeClose(() => {
|
||||
visible.value = false;
|
||||
});
|
||||
} else {
|
||||
visible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
emit('cancel');
|
||||
handleClose();
|
||||
}
|
||||
|
||||
// 确认
|
||||
function handleConfirm() {
|
||||
emit('confirm');
|
||||
}
|
||||
|
||||
// 事件处理
|
||||
function handleOpen() {
|
||||
emit('open');
|
||||
}
|
||||
|
||||
function handleOpened() {
|
||||
emit('opened');
|
||||
}
|
||||
|
||||
function handleCloseEvent() {
|
||||
emit('close');
|
||||
}
|
||||
|
||||
function handleClosed() {
|
||||
emit('closed');
|
||||
}
|
||||
|
||||
// 暴露方法
|
||||
const expose: ZqDialogExpose = {
|
||||
open: () => {
|
||||
visible.value = true;
|
||||
},
|
||||
close: () => {
|
||||
visible.value = false;
|
||||
},
|
||||
setLoading: (val: boolean) => {
|
||||
innerLoading.value = val;
|
||||
},
|
||||
setConfirmLoading: (val: boolean) => {
|
||||
innerConfirmLoading.value = val;
|
||||
},
|
||||
};
|
||||
|
||||
defineExpose(expose);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="visible"
|
||||
:class="dialogClass"
|
||||
:title="undefined"
|
||||
:width="typeof props.width === 'number' ? `${props.width}px` : props.width"
|
||||
:show-close="false"
|
||||
:draggable="draggable"
|
||||
:destroy-on-close="destroyOnClose"
|
||||
:close-on-click-modal="closeOnClickModal"
|
||||
:append-to-body="appendToBody"
|
||||
:fullscreen="isFullscreen"
|
||||
align-center
|
||||
v-bind="attrs"
|
||||
@open="handleOpen"
|
||||
@opened="handleOpened"
|
||||
@close="handleCloseEvent"
|
||||
@closed="handleClosed"
|
||||
header-class="!px-1"
|
||||
>
|
||||
<!-- 自定义 Header -->
|
||||
<template #header>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<span
|
||||
class="flex-1 truncate text-base font-medium text-[var(--el-text-color-primary)]"
|
||||
>
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</span>
|
||||
<slot name="header-extra"></slot>
|
||||
<div class="ml-4 flex items-center gap-2">
|
||||
<!-- 全屏按钮 -->
|
||||
<span
|
||||
v-if="showFullscreenButton"
|
||||
class="flex h-7 w-7 cursor-pointer items-center justify-center rounded text-[var(--el-text-color-regular)] transition-all duration-200 hover:bg-[var(--el-fill-color-light)] hover:text-[var(--el-text-color-primary)]"
|
||||
title="全屏"
|
||||
@click="toggleFullscreen"
|
||||
>
|
||||
<Minimize v-if="isFullscreen" class="h-4 w-4" />
|
||||
<Maximize v-else class="h-4 w-4" />
|
||||
</span>
|
||||
<!-- 关闭按钮 -->
|
||||
<span
|
||||
v-if="showCloseButton"
|
||||
class="flex h-7 w-7 cursor-pointer items-center justify-center rounded text-[var(--el-text-color-regular)] transition-all duration-200 hover:bg-[var(--el-color-danger-light-9)] hover:text-[var(--el-color-danger)]"
|
||||
title="关闭"
|
||||
@click="handleClose"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区:ElScrollbar + Loading -->
|
||||
<div v-loading="innerLoading" class="zq-dialog-body relative">
|
||||
<ElScrollbar :style="scrollbarStyle" :max-height="scrollbarMaxHeight">
|
||||
<div class="px-1">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<template v-if="showFooter" #footer>
|
||||
<slot name="footer">
|
||||
<ElButton v-if="showCancelButton" @click="handleCancel">
|
||||
{{ cancelText }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="showConfirmButton"
|
||||
:type="confirmButtonType"
|
||||
:loading="innerConfirmLoading"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ confirmText }}
|
||||
</ElButton>
|
||||
</slot>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
Reference in New Issue
Block a user