1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-13 17:24:44 +08:00

feat(component): Replace Drawer Component (#7428)

This commit is contained in:
zhengkunwang 2024-12-18 18:08:16 +08:00 committed by GitHub
parent 55a6cdcf10
commit 07e4f34913
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 138 additions and 494 deletions

View File

@ -1,33 +0,0 @@
<template>
<el-page-header @back="props.back">
<template #content>
<span>{{ header }}</span>
<span v-if="resource && !hideResource">
-
<el-tooltip v-if="resource.length > 25" :content="resource" placement="bottom">
<el-tag effect="dark" type="success">{{ resource.substring(0, 23) + '...' }}</el-tag>
</el-tooltip>
<el-tag v-else effect="dark" type="success">{{ resource }}</el-tag>
</span>
<el-divider v-if="slots.buttons" direction="vertical" />
<slot v-if="slots.buttons" name="buttons"></slot>
</template>
<template #extra>
<slot v-if="slots.extra" name="extra"></slot>
</template>
</el-page-header>
</template>
<script lang="ts" setup>
import { useSlots } from 'vue';
const slots = useSlots();
defineOptions({ name: 'DrawerHeader' });
const props = defineProps({
header: String,
back: Function,
resource: String,
hideResource: Boolean,
});
</script>

View File

@ -42,11 +42,9 @@
<div v-else class="flex flex-wrap gap-4 sm:justify-between">
<div class="flex gap-2 flex-wrap items-center justify-start">
<slot name="leftToolBar" v-if="slots.leftToolBar"></slot>
<slot name="buttons" v-if="slots.buttons"></slot>
</div>
<div class="flex flex-wrap gap-3" v-if="slots.rightToolBar || slots.rightButton">
<div class="flex flex-wrap gap-3" v-if="slots.rightToolBar">
<slot name="rightToolBar"></slot>
<slot name="rightButton"></slot>
</div>
</div>

View File

@ -1,38 +1,11 @@
<template>
<el-drawer
v-model="open"
:destroy-on-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleClose"
:size="globalStore.isFullScreen ? '100%' : '50%'"
>
<template #header>
<DrawerHeader :header="$t('website.log')" :back="handleClose">
<template #extra v-if="!mobile">
<el-tooltip :content="loadTooltip()" placement="top">
<el-button @click="toggleFullscreen" class="fullScreen" icon="FullScreen" plain></el-button>
</el-tooltip>
</template>
</DrawerHeader>
</template>
<div>
<LogFile :config="config" :height-diff="config.heightDiff"></LogFile>
</div>
</el-drawer>
<DrawerPro v-model="open" :header="$t('website.log')" size="large" :back="handleClose">
<LogFile :config="config" :height-diff="config.heightDiff"></LogFile>
</DrawerPro>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { ref } from 'vue';
import LogFile from '@/components/log-file/index.vue';
import { GlobalStore } from '@/store';
import screenfull from 'screenfull';
import i18n from '@/lang';
const globalStore = GlobalStore();
const mobile = computed(() => {
return globalStore.isMobile();
});
interface LogProps {
id: number;
@ -50,30 +23,11 @@ const em = defineEmits(['close']);
const handleClose = () => {
open.value = false;
em('close', false);
globalStore.isFullScreen = false;
};
watch(open, (val) => {
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
});
function toggleFullscreen() {
globalStore.isFullScreen = !globalStore.isFullScreen;
}
const loadTooltip = () => {
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
};
const acceptParams = (props: LogProps) => {
config.value = props;
open.value = true;
if (!mobile.value) {
screenfull.on('change', () => {
globalStore.isFullScreen = screenfull.isFullscreen;
});
}
};
defineExpose({ acceptParams });

View File

@ -1,15 +1,11 @@
<template>
<el-drawer
:close-on-click-modal="false"
:close-on-press-escape="false"
:key="refresh"
<DrawerPro
v-model="drawerVisible"
size="50%"
append-to-body
:header="$t('commons.button.upgrade')"
:back="handleClose"
size="large"
:key="refresh"
>
<template #header>
<DrawerHeader :header="$t('commons.button.upgrade')" :back="handleClose" />
</template>
<div class="panel-MdEditor">
<el-alert :closable="false">
<span class="line-height">{{ $t('setting.versionHelper') }}</span>
@ -38,11 +34,10 @@
<el-button type="primary" @click="onUpgrade">{{ $t('setting.upgradeNow') }}</el-button>
</span>
</template>
</el-drawer>
</DrawerPro>
</template>
<script setup lang="ts">
import DrawerHeader from '@/components/drawer-header/index.vue';
import { loadReleaseNotes, upgrade } from '@/api/modules/setting';
import MdEditor from 'md-editor-v3';
import i18n from '@/lang';

View File

@ -1,4 +0,0 @@
// ? 全局不动配置项 只做导出不做修改
// * 首页地址(默认)
export const HOME_URL: string = '/';

View File

@ -1,15 +1,9 @@
import { PersistedStateOptions } from 'pinia-plugin-persistedstate';
/**
* @description pinia持久化参数配置
* @param {String} key 存储到持久化的 name
* @return persist
* */
const piniaPersistConfig = (key: string) => {
const persist: PersistedStateOptions = {
key,
storage: window.localStorage,
// storage: window.sessionStorage,
};
return persist;
};

View File

@ -1,17 +0,0 @@
import { App } from 'vue';
import copy from './modules/copy';
const directivesList: any = {
copy,
};
const directives = {
install: function (app: App<Element>) {
Object.keys(directivesList).forEach((key) => {
// 注册所有自定义指令
app.directive(key, directivesList[key]);
});
},
};
export default directives;

View File

@ -1,35 +0,0 @@
/**
* v-copy
* 复制某个值至剪贴板
* 接收参数string类型/Ref<string>类型/Reactive<string>类型
*/
import { MsgSuccess } from '@/utils/message';
import type { Directive, DirectiveBinding } from 'vue';
interface ElType extends HTMLElement {
copyData: string | number;
__handleClick__: any;
}
const copy: Directive = {
mounted(el: ElType, binding: DirectiveBinding) {
el.copyData = binding.value;
el.addEventListener('click', handleClick);
},
updated(el: ElType, binding: DirectiveBinding) {
el.copyData = binding.value;
},
beforeUnmount(el: ElType) {
el.removeEventListener('click', el.__handleClick__);
},
};
function handleClick(this: any) {
const input = document.createElement('input');
input.value = this.copyData.toLocaleString();
document.body.appendChild(input);
input.select();
document.execCommand('Copy');
document.body.removeChild(input);
MsgSuccess('复制成功');
}
export default copy;

View File

@ -2,7 +2,6 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@ -14,7 +14,6 @@ for (const path in styleModule) {
styleModule[path]?.();
}
import directives from '@/directives/index';
import router from '@/routers/index';
import i18n from '@/lang/index';
import pinia from '@/store/index';
@ -39,6 +38,5 @@ Object.keys(Icons).forEach((key) => {
app.use(router);
app.use(i18n);
app.use(pinia);
app.use(directives);
app.use(Components);
app.mount('#app');

View File

@ -1,118 +0,0 @@
const toString = Object.prototype.toString;
/**
* @description: 判断值是否未某个类型
*/
export function is(val: unknown, type: string) {
return toString.call(val) === `[object ${type}]`;
}
/**
* @description: 是否为函数
*/
export function isFunction<T = Function>(val: unknown): val is T {
return is(val, 'Function');
}
/**
* @description: 是否已定义
*/
export const isDef = <T = unknown>(val?: T): val is T => {
return typeof val !== 'undefined';
};
export const isUnDef = <T = unknown>(val?: T): val is T => {
return !isDef(val);
};
/**
* @description: 是否为对象
*/
export const isObject = (val: any): val is Record<any, any> => {
return val !== null && is(val, 'Object');
};
/**
* @description: 是否为时间
*/
export function isDate(val: unknown): val is Date {
return is(val, 'Date');
}
/**
* @description: 是否为数值
*/
export function isNumber(val: unknown): val is number {
return is(val, 'Number');
}
/**
* @description: 是否为AsyncFunction
*/
export function isAsyncFunction<T = any>(val: unknown): val is Promise<T> {
return is(val, 'AsyncFunction');
}
/**
* @description: 是否为promise
*/
export function isPromise<T = any>(val: unknown): val is Promise<T> {
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch);
}
/**
* @description: 是否为字符串
*/
export function isString(val: unknown): val is string {
return is(val, 'String');
}
/**
* @description: 是否为boolean类型
*/
export function isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean');
}
/**
* @description: 是否为数组
*/
export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val);
}
/**
* @description: 是否客户端
*/
export const isClient = () => {
return typeof window !== 'undefined';
};
/**
* @description: 是否为浏览器
*/
export const isWindow = (val: any): val is Window => {
return typeof window !== 'undefined' && is(val, 'Window');
};
export const isElement = (val: unknown): val is Element => {
return isObject(val) && !!val.tagName;
};
export const isServer = typeof window === 'undefined';
// 是否为图片节点
export function isImageDom(o: Element) {
return o && ['IMAGE', 'IMG'].includes(o.tagName);
}
export function isNull(val: unknown): val is null {
return val === null;
}
export function isNullAndUnDef(val: unknown): val is null | undefined {
return isUnDef(val) && isNull(val);
}
export function isNullOrUnDef(val: unknown): val is null | undefined {
return isUnDef(val) || isNull(val);
}

View File

@ -1,59 +0,0 @@
import { MsgWarning } from '../message';
/**
* hex颜色转rgb颜色
* @param str 颜色值字符串
* @returns 返回处理后的颜色值
*/
export function hexToRgb(str: any) {
let hexs: any = '';
let reg = /^\#?[0-9A-Fa-f]{6}$/;
if (!reg.test(str)) return MsgWarning('输入错误的hex');
str = str.replace('#', '');
hexs = str.match(/../g);
for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16);
return hexs;
}
/**
* rgb颜色转Hex颜色
* @param r 代表红色
* @param g 代表绿色
* @param b 代表蓝色
* @returns 返回处理后的颜色值
*/
export function rgbToHex(r: any, g: any, b: any) {
let reg = /^\d{1,3}$/;
if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return MsgWarning('输入错误的rgb颜色值');
let hexs = [r.toString(16), g.toString(16), b.toString(16)];
for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`;
return `#${hexs.join('')}`;
}
/**
* 加深颜色值
* @param color 颜色值字符串
* @param level 加深的程度限0-1之间
* @returns 返回处理后的颜色值
*/
export function getDarkColor(color: string, level: number) {
let reg = /^\#?[0-9A-Fa-f]{6}$/;
if (!reg.test(color)) return MsgWarning('输入错误的hex颜色值');
let rgb = hexToRgb(color);
for (let i = 0; i < 3; i++) rgb[i] = Math.floor(rgb[i] * (1 - level));
return rgbToHex(rgb[0], rgb[1], rgb[2]);
}
/**
* 变浅颜色值
* @param color 颜色值字符串
* @param level 加深的程度限0-1之间
* @returns 返回处理后的颜色值
*/
export function getLightColor(color: string, level: number) {
let reg = /^\#?[0-9A-Fa-f]{6}$/;
if (!reg.test(color)) return MsgWarning('输入错误的hex颜色值');
let rgb = hexToRgb(color);
for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]);
return rgbToHex(rgb[0], rgb[1], rgb[2]);
}

View File

@ -1,8 +1,5 @@
<template>
<el-drawer v-model="open" width="30%" :close-on-click-modal="false" :close-on-press-escape="false">
<template #header>
<DrawerHeader :header="$t('file.info')" :back="handleClose" />
</template>
<DrawerPro v-model="open" :header="$t('file.info')" size="small" :back="handleClose">
<el-descriptions :column="1" border>
<el-descriptions-item label-class-name="detail-label" :label="$t('file.fileName')">
{{ data.name }}
@ -41,7 +38,7 @@
{{ dateFormatSimple(data.modTime) }}
</el-descriptions-item>
</el-descriptions>
</el-drawer>
</DrawerPro>
</template>
<script lang="ts" setup>
@ -49,7 +46,6 @@ import { ComputeDirSize, GetFileContent } from '@/api/modules/files';
import { computeSize } from '@/utils/util';
import { ref } from 'vue';
import { dateFormatSimple } from '@/utils/util';
import DrawerHeader from '@/components/drawer-header/index.vue';
interface InfoProps {
path: string;

View File

@ -1,19 +1,11 @@
<template>
<el-drawer
<DrawerPro
v-model="drawerVisible"
:destroy-on-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
size="50%"
:header="title"
size="large"
:resource="dialogData.title === 'add' ? '' : dialogData.rowData?.name"
:back="handleClose"
>
<template #header>
<DrawerHeader
:header="title"
:hideResource="dialogData.title === 'add'"
:resource="dialogData.rowData?.name"
:back="handleClose"
/>
</template>
<el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules" v-loading="loading">
<el-row type="flex" justify="center">
<el-col :span="22">
@ -136,7 +128,7 @@
</span>
</template>
<LicenseImport ref="licenseRef" />
</el-drawer>
</DrawerPro>
</template>
<script lang="ts" setup>
@ -146,7 +138,6 @@ import FileList from '@/components/file-list/index.vue';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import LicenseImport from '@/components/license-import/index.vue';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgError, MsgSuccess } from '@/utils/message';
import { Toolbox } from '@/api/interface/toolbox';
import { createClam, updateClam } from '@/api/modules/toolbox';

View File

@ -6,7 +6,7 @@
</template>
<template #title>
<back-button name="Clam" header="ClamAV">
<template #buttons>
<template #leftToolBar>
<el-button type="primary" :plain="activeName !== 'clamd'" @click="search('clamd')">
{{ $t('toolbox.clam.clamConf') }}
</el-button>

View File

@ -1,142 +1,128 @@
<template>
<el-drawer
:destroy-on-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
<DrawerPro
v-model="open"
size="50%"
:header="$t('runtime.' + mode)"
size="large"
:resource="mode === 'edit' ? runtime.name : ''"
:back="handleClose"
>
<template #header>
<DrawerHeader
:header="$t('runtime.' + mode)"
:hideResource="mode == 'create'"
:resource="runtime.name"
:back="handleClose"
/>
</template>
<el-row v-loading="loading">
<el-col :span="22" :offset="1">
<el-form
ref="runtimeForm"
label-position="top"
:model="runtime"
label-width="125px"
:rules="rules"
:validate-on-rule-change="false"
>
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input :disabled="mode === 'edit'" v-model="runtime.name"></el-input>
<el-form
v-loading="loading"
ref="runtimeForm"
label-position="top"
:model="runtime"
label-width="125px"
:rules="rules"
:validate-on-rule-change="false"
>
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input :disabled="mode === 'edit'" v-model="runtime.name"></el-input>
</el-form-item>
<el-form-item :label="$t('runtime.app')" prop="appID">
<el-row :gutter="20">
<el-col :span="12">
<el-select
v-model="runtime.appID"
:disabled="mode === 'edit'"
@change="changeApp(runtime.appID)"
class="p-w-200"
>
<el-option
v-for="(app, index) in apps"
:key="index"
:label="app.name"
:value="app.id"
></el-option>
</el-select>
</el-col>
<el-col :span="12">
<el-select
v-model="runtime.version"
:disabled="mode === 'edit'"
@change="changeVersion()"
class="p-w-200"
>
<el-option
v-for="(version, index) in appVersions"
:key="index"
:label="version"
:value="version"
></el-option>
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item :label="$t('tool.supervisor.dir')" prop="codeDir">
<el-input v-model.trim="runtime.codeDir" :disabled="mode === 'edit'">
<template #prepend>
<FileList
:disabled="mode === 'edit'"
:path="runtime.codeDir"
@choose="getPath"
:dir="true"
></FileList>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.donetHelper') }}
</span>
</el-form-item>
<el-row :gutter="20">
<el-col :span="7">
<el-form-item :label="$t('runtime.appPort')" prop="params.APP_PORT">
<el-input v-model.number="runtime.params['APP_PORT']" />
<span class="input-help">{{ $t('runtime.appPortHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('runtime.app')" prop="appID">
<el-row :gutter="20">
<el-col :span="12">
<el-select
v-model="runtime.appID"
:disabled="mode === 'edit'"
@change="changeApp(runtime.appID)"
class="p-w-200"
>
<el-option
v-for="(app, index) in apps"
:key="index"
:label="app.name"
:value="app.id"
></el-option>
</el-select>
</el-col>
<el-col :span="12">
<el-select
v-model="runtime.version"
:disabled="mode === 'edit'"
@change="changeVersion()"
class="p-w-200"
>
<el-option
v-for="(version, index) in appVersions"
:key="index"
:label="version"
:value="version"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="7">
<el-form-item :label="$t('runtime.externalPort')" prop="port">
<el-input v-model.number="runtime.port" />
<span class="input-help">{{ $t('runtime.externalPortHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('tool.supervisor.dir')" prop="codeDir">
<el-input v-model.trim="runtime.codeDir" :disabled="mode === 'edit'">
<template #prepend>
<FileList
:disabled="mode === 'edit'"
:path="runtime.codeDir"
@choose="getPath"
:dir="true"
></FileList>
</template>
</el-input>
</el-col>
<el-col :span="4">
<el-form-item :label="$t('commons.button.add') + $t('commons.table.port')">
<el-button @click="addPort">
<el-icon><Plus /></el-icon>
</el-button>
</el-form-item>
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.donetHelper') }}
</span>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('app.allowPort')" prop="params.HOST_IP">
<el-switch
v-model="runtime.params['HOST_IP']"
:active-value="'0.0.0.0'"
:inactive-value="'127.0.0.1'"
/>
</el-form-item>
<el-row :gutter="20">
<el-col :span="7">
<el-form-item :label="$t('runtime.appPort')" prop="params.APP_PORT">
<el-input v-model.number="runtime.params['APP_PORT']" />
<span class="input-help">{{ $t('runtime.appPortHelper') }}</span>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item :label="$t('runtime.externalPort')" prop="port">
<el-input v-model.number="runtime.port" />
<span class="input-help">{{ $t('runtime.externalPortHelper') }}</span>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item :label="$t('commons.button.add') + $t('commons.table.port')">
<el-button @click="addPort">
<el-icon><Plus /></el-icon>
</el-button>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('app.allowPort')" prop="params.HOST_IP">
<el-switch
v-model="runtime.params['HOST_IP']"
:active-value="'0.0.0.0'"
:inactive-value="'127.0.0.1'"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-for="(port, index) of runtime.exposedPorts" :key="index">
<el-col :span="7">
<el-form-item
:prop="'exposedPorts.' + index + '.containerPort'"
:rules="rules.params.APP_PORT"
>
<el-input v-model.number="port.containerPort" :placeholder="$t('runtime.appPort')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item :prop="'exposedPorts.' + index + '.hostPort'" :rules="rules.params.APP_PORT">
<el-input v-model.number="port.hostPort" :placeholder="$t('runtime.externalPort')" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item>
<el-button type="primary" @click="removePort(index)" link>
{{ $t('commons.button.delete') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
</el-col>
</el-row>
<el-row :gutter="20" v-for="(port, index) of runtime.exposedPorts" :key="index">
<el-col :span="7">
<el-form-item :prop="'exposedPorts.' + index + '.containerPort'" :rules="rules.params.APP_PORT">
<el-input v-model.number="port.containerPort" :placeholder="$t('runtime.appPort')" />
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-col>
<el-col :span="7">
<el-form-item :prop="'exposedPorts.' + index + '.hostPort'" :rules="rules.params.APP_PORT">
<el-input v-model.number="port.hostPort" :placeholder="$t('runtime.externalPort')" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item>
<el-button type="primary" @click="removePort(index)" link>
{{ $t('commons.button.delete') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span>
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
@ -145,7 +131,7 @@
</el-button>
</span>
</template>
</el-drawer>
</DrawerPro>
</template>
<script lang="ts" setup>
@ -158,7 +144,6 @@ import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { reactive, ref, watch } from 'vue';
import DrawerHeader from '@/components/drawer-header/index.vue';
interface OperateRrops {
id?: number;