mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-26 00:01:11 +00:00
feat: Optimize frontend structure (#12475)
This commit is contained in:
@@ -173,7 +173,11 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { computeSize, dateFormat, downloadFile, newUUID, transferTimeToSecond } from '@/utils/util';
|
||||
import { computeSize } from '@/utils/size';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { downloadFile } from '@/utils/file';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { transferTimeToSecond } from '@/utils/validate';
|
||||
import {
|
||||
getLocalBackupDir,
|
||||
handleBackup,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { copyText } from '@/utils/util';
|
||||
import { copyText } from '@/utils/clipboard';
|
||||
defineOptions({ name: 'CopyButton' });
|
||||
|
||||
defineProps({
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="404">
|
||||
import { copyText } from '@/utils/util';
|
||||
import { copyText } from '@/utils/clipboard';
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="404">
|
||||
import { copyText } from '@/utils/util';
|
||||
import { copyText } from '@/utils/clipboard';
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="404">
|
||||
import { copyText } from '@/utils/util';
|
||||
import { copyText } from '@/utils/clipboard';
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { computeSize } from '@/utils/util';
|
||||
|
||||
import { computeSize } from '@/utils/size';
|
||||
const dialogVisible = ref();
|
||||
const existFiles = ref<DialogProps[]>([]);
|
||||
|
||||
|
||||
@@ -178,8 +178,8 @@ import { nextTick, onUpdated, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import { useSearchableForSelect } from '@/views/host/file-management/hooks/searchable';
|
||||
import { computeSize, debounce } from '@/utils/util';
|
||||
|
||||
import { computeSize } from '@/utils/size';
|
||||
import { debounce } from '@/utils/misc';
|
||||
const data = ref([]);
|
||||
const loading = ref(false);
|
||||
const paths = ref<string[]>([]);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="fu-input-rw-switch">
|
||||
<div
|
||||
v-if="!isWrite"
|
||||
class="fu-input-rw-switch__read"
|
||||
@click.stop="handleReadClick"
|
||||
@dblclick.stop="handleReadDblClick"
|
||||
>
|
||||
<span class="fu-input-rw-switch__text">{{ displayValue }}</span>
|
||||
</div>
|
||||
<el-input
|
||||
v-else
|
||||
ref="inputRef"
|
||||
v-bind="$attrs"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="handleUpdate"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@keyup.enter="handleEnter"
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
defineOptions({ name: 'FuInputRwSwitch', inheritAttrs: false });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
writeTrigger: {
|
||||
type: String,
|
||||
default: 'onClick',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'input', 'blur', 'enter']);
|
||||
|
||||
const inputRef = ref();
|
||||
const isWrite = ref(false);
|
||||
|
||||
const displayValue = computed(() => {
|
||||
return props.modelValue === '' || props.modelValue === undefined || props.modelValue === null
|
||||
? '-'
|
||||
: String(props.modelValue);
|
||||
});
|
||||
|
||||
const openWrite = async () => {
|
||||
isWrite.value = true;
|
||||
await nextTick();
|
||||
inputRef.value?.focus?.();
|
||||
inputRef.value?.select?.();
|
||||
};
|
||||
|
||||
const closeWrite = () => {
|
||||
isWrite.value = false;
|
||||
};
|
||||
|
||||
const handleReadClick = () => {
|
||||
if (props.writeTrigger === 'onClick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
const handleReadDblClick = () => {
|
||||
if (props.writeTrigger === 'onDblclick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = (value: string | number) => {
|
||||
emit('update:modelValue', value);
|
||||
};
|
||||
|
||||
const handleInput = (value: string) => {
|
||||
emit('input', value);
|
||||
};
|
||||
|
||||
const handleBlur = (event: FocusEvent) => {
|
||||
emit('blur', event);
|
||||
closeWrite();
|
||||
};
|
||||
|
||||
const handleEnter = (event: KeyboardEvent) => {
|
||||
emit('enter', event);
|
||||
closeWrite();
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="fu-read-write-switch">
|
||||
<div
|
||||
v-if="!isWrite"
|
||||
class="fu-read-write-switch__read"
|
||||
@click.stop="handleReadClick"
|
||||
@dblclick.stop="handleReadDblClick"
|
||||
>
|
||||
<slot name="read" :write="openWrite">
|
||||
<span class="fu-read-write-switch__text">{{ displayValue }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
<div v-else class="fu-read-write-switch__write" @click.stop>
|
||||
<slot :read="closeWrite"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
defineOptions({ name: 'FuReadWriteSwitch' });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean, Object, Array],
|
||||
default: '',
|
||||
},
|
||||
data: {
|
||||
type: [String, Number, Boolean, Object, Array],
|
||||
default: '',
|
||||
},
|
||||
writeTrigger: {
|
||||
type: String,
|
||||
default: 'onClick',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change']);
|
||||
|
||||
const isWrite = ref(false);
|
||||
|
||||
const displayValue = computed(() => {
|
||||
const value = props.modelValue !== '' && props.modelValue !== undefined ? props.modelValue : props.data;
|
||||
return value === '' || value === undefined || value === null ? '-' : String(value);
|
||||
});
|
||||
|
||||
const openWrite = () => {
|
||||
isWrite.value = true;
|
||||
};
|
||||
|
||||
const closeWrite = (value?: any) => {
|
||||
if (value !== undefined) {
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
}
|
||||
isWrite.value = false;
|
||||
};
|
||||
|
||||
const handleReadClick = () => {
|
||||
if (props.writeTrigger === 'onClick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
const handleReadDblClick = () => {
|
||||
if (props.writeTrigger === 'onDblclick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
write: openWrite,
|
||||
read: closeWrite,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="fu-select-rw-switch">
|
||||
<div
|
||||
v-if="!isWrite"
|
||||
class="fu-select-rw-switch__read"
|
||||
@click.stop="handleReadClick"
|
||||
@dblclick.stop="handleReadDblClick"
|
||||
>
|
||||
<slot name="read">
|
||||
<span class="fu-select-rw-switch__text">{{ displayValue }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
<el-select
|
||||
v-else
|
||||
ref="selectRef"
|
||||
v-bind="$attrs"
|
||||
:model-value="modelValue"
|
||||
@update:model-value="handleUpdate"
|
||||
@change="handleChange"
|
||||
@blur="handleBlur"
|
||||
@visible-change="handleVisibleChange"
|
||||
@click.stop
|
||||
>
|
||||
<slot></slot>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
defineOptions({ name: 'FuSelectRwSwitch', inheritAttrs: false });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean],
|
||||
default: '',
|
||||
},
|
||||
blurTime: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
writeTrigger: {
|
||||
type: String,
|
||||
default: 'onClick',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'input', 'blur', 'change']);
|
||||
|
||||
const selectRef = ref();
|
||||
const isWrite = ref(false);
|
||||
|
||||
const displayValue = computed(() => {
|
||||
return props.modelValue === '' || props.modelValue === undefined || props.modelValue === null
|
||||
? '-'
|
||||
: String(props.modelValue);
|
||||
});
|
||||
|
||||
const openWrite = async () => {
|
||||
isWrite.value = true;
|
||||
await nextTick();
|
||||
selectRef.value?.focus?.();
|
||||
};
|
||||
|
||||
const closeWrite = () => {
|
||||
window.setTimeout(() => {
|
||||
isWrite.value = false;
|
||||
}, props.blurTime);
|
||||
};
|
||||
|
||||
const handleReadClick = () => {
|
||||
if (props.writeTrigger === 'onClick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
const handleReadDblClick = () => {
|
||||
if (props.writeTrigger === 'onDblclick') {
|
||||
openWrite();
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = (value: string | number | boolean) => {
|
||||
emit('update:modelValue', value);
|
||||
emit('input', value);
|
||||
};
|
||||
|
||||
const handleChange = (value: string | number | boolean) => {
|
||||
emit('change', value);
|
||||
closeWrite();
|
||||
};
|
||||
|
||||
const handleBlur = (event: FocusEvent) => {
|
||||
emit('blur', event);
|
||||
closeWrite();
|
||||
};
|
||||
|
||||
const handleVisibleChange = (visible: boolean) => {
|
||||
if (!visible) {
|
||||
closeWrite();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineComponent, h } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FuStep',
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(_props, { slots }) {
|
||||
return () => h('div', slots.default?.());
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,213 @@
|
||||
import { computed, defineComponent, h, ref, watch, type PropType, type VNode } from 'vue';
|
||||
|
||||
import { flattenVNodes, getVNodeComponentName } from './shared';
|
||||
|
||||
interface FuStepItem {
|
||||
id: string;
|
||||
index: number;
|
||||
title: string;
|
||||
vnode: VNode;
|
||||
}
|
||||
|
||||
const buildStepChildren = (vnode: VNode) => {
|
||||
const children = vnode.children as Record<string, (() => VNode[]) | undefined> | null;
|
||||
return children?.default?.() || [];
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FuSteps',
|
||||
props: {
|
||||
active: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'horizontal',
|
||||
},
|
||||
space: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
finishButtonText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
beforeLeave: {
|
||||
type: Function as PropType<
|
||||
(step: { id: string; index: number; title: string }) => boolean | Promise<boolean>
|
||||
>,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
emits: ['change'],
|
||||
setup(props, { emit, slots, expose }) {
|
||||
const activeIndex = ref(props.active);
|
||||
const isRunningBeforeLeave = ref(false);
|
||||
|
||||
const stepItems = computed<FuStepItem[]>(() =>
|
||||
flattenVNodes(slots.default?.() || [])
|
||||
.filter((vnode) => getVNodeComponentName(vnode) === 'FuStep')
|
||||
.map((vnode, index) => {
|
||||
const vnodeProps = (vnode.props || {}) as Record<string, any>;
|
||||
return {
|
||||
id: String(vnodeProps.id || index),
|
||||
index,
|
||||
title: String(vnodeProps.title || ''),
|
||||
vnode,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const emitChange = () => {
|
||||
const currentStep = stepItems.value[activeIndex.value];
|
||||
if (!currentStep) {
|
||||
return;
|
||||
}
|
||||
emit('change', {
|
||||
id: currentStep.id,
|
||||
index: currentStep.index,
|
||||
title: currentStep.title,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.active,
|
||||
(value) => {
|
||||
activeIndex.value = value;
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
stepItems,
|
||||
(items) => {
|
||||
if (items.length === 0) {
|
||||
activeIndex.value = 0;
|
||||
return;
|
||||
}
|
||||
if (activeIndex.value > items.length - 1) {
|
||||
activeIndex.value = items.length - 1;
|
||||
}
|
||||
emitChange();
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
const runBeforeLeave = async () => {
|
||||
if (!props.beforeLeave) {
|
||||
return true;
|
||||
}
|
||||
const currentStep = stepItems.value[activeIndex.value];
|
||||
if (!currentStep) {
|
||||
return true;
|
||||
}
|
||||
if (isRunningBeforeLeave.value) {
|
||||
return true;
|
||||
}
|
||||
isRunningBeforeLeave.value = true;
|
||||
try {
|
||||
return (
|
||||
(await props.beforeLeave({
|
||||
id: currentStep.id,
|
||||
index: currentStep.index,
|
||||
title: currentStep.title,
|
||||
})) !== false
|
||||
);
|
||||
} finally {
|
||||
isRunningBeforeLeave.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const changeTo = async (nextIndex: number, runGuard = true) => {
|
||||
if (nextIndex < 0 || nextIndex >= stepItems.value.length || nextIndex === activeIndex.value) {
|
||||
return false;
|
||||
}
|
||||
const currentIndex = activeIndex.value;
|
||||
if (runGuard) {
|
||||
const canLeave = await runBeforeLeave();
|
||||
if (activeIndex.value !== currentIndex) {
|
||||
return true;
|
||||
}
|
||||
if (!canLeave) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
activeIndex.value = nextIndex;
|
||||
emitChange();
|
||||
return true;
|
||||
};
|
||||
|
||||
const next = async () => {
|
||||
if (isRunningBeforeLeave.value) {
|
||||
const nextIndex = Math.min(activeIndex.value + 1, stepItems.value.length - 1);
|
||||
if (nextIndex !== activeIndex.value) {
|
||||
activeIndex.value = nextIndex;
|
||||
emitChange();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return changeTo(activeIndex.value + 1, true);
|
||||
};
|
||||
|
||||
const prev = async () => {
|
||||
return changeTo(activeIndex.value - 1, false);
|
||||
};
|
||||
|
||||
expose({
|
||||
next,
|
||||
prev,
|
||||
active: activeIndex,
|
||||
});
|
||||
|
||||
return () => {
|
||||
const currentStep = stepItems.value[activeIndex.value];
|
||||
return h('div', { class: ['fu-steps', `fu-steps--${props.direction}`] }, [
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
class: 'fu-steps__nav',
|
||||
style: props.direction === 'vertical' && props.space ? { gap: `${props.space}px` } : undefined,
|
||||
},
|
||||
stepItems.value.map((step, index) =>
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
key: step.id,
|
||||
class: [
|
||||
'fu-steps__item',
|
||||
{
|
||||
'is-active': index === activeIndex.value,
|
||||
'is-finished': index < activeIndex.value,
|
||||
},
|
||||
],
|
||||
type: 'button',
|
||||
disabled: props.isLoading,
|
||||
onClick: () => changeTo(step.index, step.index > activeIndex.value),
|
||||
},
|
||||
[
|
||||
h('span', { class: 'fu-steps__index' }, index + 1),
|
||||
h('span', { class: 'fu-steps__title' }, step.title),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
h('div', { class: 'fu-steps__content' }, currentStep ? buildStepChildren(currentStep.vnode) : []),
|
||||
slots.footer
|
||||
? h(
|
||||
'div',
|
||||
{ class: 'fu-steps__footer' },
|
||||
slots.footer({
|
||||
active: currentStep,
|
||||
next,
|
||||
prev,
|
||||
}),
|
||||
)
|
||||
: null,
|
||||
]);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,213 @@
|
||||
import { ElTable } from 'element-plus';
|
||||
import { computed, defineComponent, h, onMounted, ref, watch, type PropType, type VNode } from 'vue';
|
||||
|
||||
import {
|
||||
FU_TABLE_STORAGE_PREFIX,
|
||||
flattenVNodes,
|
||||
isElTableColumnVNode,
|
||||
type FuTableColumnConfig,
|
||||
updateArrayInPlace,
|
||||
} from './shared';
|
||||
|
||||
const buildColumnKey = (vnode: VNode, index: number) => {
|
||||
const props = (vnode.props || {}) as Record<string, any>;
|
||||
return String(props.columnKey || props.prop || props.label || `column-${index}`);
|
||||
};
|
||||
|
||||
const buildColumnConfig = (vnode: VNode, index: number): FuTableColumnConfig => {
|
||||
const props = (vnode.props || {}) as Record<string, any>;
|
||||
return {
|
||||
key: buildColumnKey(vnode, index),
|
||||
label: String(props.label || props.prop || `column-${index}`),
|
||||
prop: props.prop,
|
||||
show: true,
|
||||
fixed: props.fixed ?? props.fix,
|
||||
};
|
||||
};
|
||||
|
||||
const isConfigurableColumn = (vnode: VNode) => {
|
||||
if (!isElTableColumnVNode(vnode)) {
|
||||
return false;
|
||||
}
|
||||
const props = (vnode.props || {}) as Record<string, any>;
|
||||
if (['selection', 'index', 'expand'].includes(props.type)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(props.label || props.prop);
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FuTable',
|
||||
props: {
|
||||
columns: {
|
||||
type: Array as PropType<FuTableColumnConfig[]>,
|
||||
default: undefined,
|
||||
},
|
||||
localKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
refresh: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
refTable: {
|
||||
type: Object as PropType<{ value?: unknown } | null>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props, { attrs, slots, expose }) {
|
||||
const refElTable = ref();
|
||||
const storageLoaded = ref(false);
|
||||
const storedColumns = ref<FuTableColumnConfig[]>([]);
|
||||
|
||||
const slotChildren = computed(() => flattenVNodes(slots.default?.() || []));
|
||||
|
||||
const slotColumns = computed(() =>
|
||||
slotChildren.value.filter(isConfigurableColumn).map((vnode, index) => buildColumnConfig(vnode, index)),
|
||||
);
|
||||
|
||||
const ensureStoredColumns = () => {
|
||||
if (storageLoaded.value || !props.localKey || typeof window === 'undefined') {
|
||||
storageLoaded.value = true;
|
||||
return;
|
||||
}
|
||||
storageLoaded.value = true;
|
||||
try {
|
||||
const raw = localStorage.getItem(`${FU_TABLE_STORAGE_PREFIX}${props.localKey}`);
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) {
|
||||
storedColumns.value = parsed;
|
||||
}
|
||||
} catch {
|
||||
storedColumns.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const syncColumns = (nextColumns: FuTableColumnConfig[]) => {
|
||||
if (!props.columns) {
|
||||
return;
|
||||
}
|
||||
ensureStoredColumns();
|
||||
const currentColumns = props.columns as FuTableColumnConfig[];
|
||||
const sourceColumns = currentColumns.length > 0 ? currentColumns : storedColumns.value;
|
||||
const sourceByKey = new Map(sourceColumns.map((column) => [column.key, column]));
|
||||
const sourceOrder = new Map(sourceColumns.map((column, index) => [column.key, index]));
|
||||
|
||||
const mergedColumns = nextColumns
|
||||
.map((column, index) => ({
|
||||
...column,
|
||||
show: sourceByKey.get(column.key)?.show ?? true,
|
||||
fixed: sourceByKey.get(column.key)?.fixed ?? column.fixed,
|
||||
__order: sourceOrder.get(column.key) ?? index,
|
||||
}))
|
||||
.sort((a, b) => a.__order - b.__order)
|
||||
.map((column) => {
|
||||
const normalizedColumn = { ...column };
|
||||
delete (normalizedColumn as Partial<typeof normalizedColumn> & { __order?: number }).__order;
|
||||
return normalizedColumn;
|
||||
});
|
||||
|
||||
updateArrayInPlace(currentColumns, mergedColumns);
|
||||
};
|
||||
|
||||
const resolvedColumns = computed(() => {
|
||||
if (props.columns && props.columns.length > 0) {
|
||||
return props.columns;
|
||||
}
|
||||
if (storedColumns.value.length > 0) {
|
||||
const storageMap = new Map(storedColumns.value.map((column) => [column.key, column]));
|
||||
const orderMap = new Map(storedColumns.value.map((column, index) => [column.key, index]));
|
||||
return [...slotColumns.value]
|
||||
.map((column, index) => ({
|
||||
...column,
|
||||
show: storageMap.get(column.key)?.show ?? true,
|
||||
__order: orderMap.get(column.key) ?? index,
|
||||
}))
|
||||
.sort((a, b) => a.__order - b.__order)
|
||||
.map((column) => {
|
||||
const normalizedColumn = { ...column };
|
||||
delete (normalizedColumn as Partial<typeof normalizedColumn> & { __order?: number }).__order;
|
||||
return normalizedColumn;
|
||||
});
|
||||
}
|
||||
return slotColumns.value;
|
||||
});
|
||||
|
||||
const orderedConfigurableChildren = computed(() => {
|
||||
const columnNodeMap = new Map(
|
||||
slotChildren.value
|
||||
.filter(isConfigurableColumn)
|
||||
.map((vnode, index) => [buildColumnKey(vnode, index), vnode]),
|
||||
);
|
||||
return resolvedColumns.value
|
||||
.filter((column) => column.show !== false)
|
||||
.map((column) => columnNodeMap.get(column.key))
|
||||
.filter((column): column is VNode => Boolean(column));
|
||||
});
|
||||
|
||||
const renderedChildren = computed(() => {
|
||||
if (orderedConfigurableChildren.value.length === 0) {
|
||||
return slotChildren.value;
|
||||
}
|
||||
const children: VNode[] = [];
|
||||
let hasInsertedConfigurableColumns = false;
|
||||
for (const child of slotChildren.value) {
|
||||
if (isConfigurableColumn(child)) {
|
||||
if (!hasInsertedConfigurableColumns) {
|
||||
children.push(...orderedConfigurableChildren.value);
|
||||
hasInsertedConfigurableColumns = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
children.push(child);
|
||||
}
|
||||
return children;
|
||||
});
|
||||
|
||||
watch(slotColumns, (nextColumns) => syncColumns(nextColumns), {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.columns,
|
||||
(nextColumns) => {
|
||||
if (!props.localKey || !nextColumns || typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(`${FU_TABLE_STORAGE_PREFIX}${props.localKey}`, JSON.stringify(nextColumns));
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
ensureStoredColumns();
|
||||
if (props.refTable) {
|
||||
props.refTable.value = refElTable.value;
|
||||
}
|
||||
});
|
||||
|
||||
expose({ refElTable });
|
||||
|
||||
return () =>
|
||||
h(
|
||||
ElTable as any,
|
||||
{
|
||||
...attrs,
|
||||
ref: refElTable,
|
||||
class: ['fu-table', attrs.class],
|
||||
headerRowClassName: 'fu-table-header',
|
||||
},
|
||||
{
|
||||
...slots,
|
||||
default: () => renderedChildren.value,
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="fu-search-bar fu-table-column-select">
|
||||
<el-popover
|
||||
v-model:visible="visible"
|
||||
:trigger="trigger"
|
||||
:popper-class="resolvedPopperClass"
|
||||
placement="bottom-end"
|
||||
:width="260"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button class="fu-search-bar-button" :icon="iconComponent" @click.stop>
|
||||
<span v-if="!onlyIcon">{{ resolvedTitle }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="fu-table-column-select__panel" @click.stop>
|
||||
<div class="fu-table-column-select__title">{{ resolvedTitle }}</div>
|
||||
<div ref="listRef" class="fu-table-column-select__list">
|
||||
<div
|
||||
v-for="column in columns"
|
||||
:key="column.key"
|
||||
class="fu-table-column-select__item"
|
||||
:data-key="column.key"
|
||||
>
|
||||
<el-icon class="fu-table-column-select__drag"><Rank /></el-icon>
|
||||
<el-checkbox v-model="column.show" :disabled="isFixedColumn(column)">
|
||||
{{ column.label }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Sortable from 'sortablejs';
|
||||
import { Rank, Setting } from '@element-plus/icons-vue';
|
||||
import { computed, nextTick, onBeforeUnmount, ref, watch, type PropType } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import type { FuTableColumnConfig } from './shared';
|
||||
|
||||
defineOptions({ name: 'FuTableColumnSelect' });
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
type: Array as PropType<FuTableColumnConfig[]>,
|
||||
default: () => [],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: [String, Object],
|
||||
default: undefined,
|
||||
},
|
||||
onlyIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
trigger: {
|
||||
type: String,
|
||||
default: 'click',
|
||||
},
|
||||
popperClass: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const visible = ref(false);
|
||||
const listRef = ref<HTMLElement | null>(null);
|
||||
let sortable: Sortable | undefined;
|
||||
|
||||
const resolvedTitle = computed(() => props.title || t('fu.table.custom_table_rows'));
|
||||
const resolvedPopperClass = computed(() =>
|
||||
['fu-table-column-select__popover', props.popperClass].filter(Boolean).join(' '),
|
||||
);
|
||||
const iconComponent = computed(() => props.icon || Setting);
|
||||
|
||||
const initSortable = async () => {
|
||||
await nextTick();
|
||||
sortable?.destroy();
|
||||
if (!listRef.value) {
|
||||
return;
|
||||
}
|
||||
sortable = Sortable.create(listRef.value, {
|
||||
animation: 150,
|
||||
handle: '.fu-table-column-select__drag',
|
||||
onEnd: ({ oldIndex, newIndex }) => {
|
||||
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) {
|
||||
return;
|
||||
}
|
||||
const nextColumns = [...props.columns];
|
||||
const [movedColumn] = nextColumns.splice(oldIndex, 1);
|
||||
if (!movedColumn) {
|
||||
return;
|
||||
}
|
||||
nextColumns.splice(newIndex, 0, movedColumn);
|
||||
props.columns.splice(0, props.columns.length, ...nextColumns);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const isFixedColumn = (column: FuTableColumnConfig) => {
|
||||
return Boolean(column.fixed);
|
||||
};
|
||||
|
||||
watch(visible, (value) => {
|
||||
if (value) {
|
||||
initSortable();
|
||||
return;
|
||||
}
|
||||
sortable?.destroy();
|
||||
sortable = undefined;
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
sortable?.destroy();
|
||||
sortable = undefined;
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<el-table-column v-bind="$attrs" :label="label" :width="resolvedWidth" :align="align" :fixed="resolvedFixed">
|
||||
<template #default="{ row }">
|
||||
<div class="fu-table-operations" @click.stop>
|
||||
<span
|
||||
v-for="button in getInlineButtons(row)"
|
||||
:key="buildButtonKey(button)"
|
||||
class="fu-table-operations__action"
|
||||
>
|
||||
<el-button
|
||||
class="fu-table-operations__button"
|
||||
link
|
||||
:type="button.type || 'primary'"
|
||||
:disabled="isButtonDisabled(button, row)"
|
||||
@click.stop="handleButtonClick(button, row)"
|
||||
>
|
||||
<el-icon v-if="button.icon" class="mr-1">
|
||||
<component :is="button.icon" />
|
||||
</el-icon>
|
||||
<span>{{ button.label }}</span>
|
||||
</el-button>
|
||||
</span>
|
||||
<span v-if="getMoreButtons(row).length > 0" class="fu-table-operations__action">
|
||||
<el-dropdown
|
||||
class="fu-table-more-button"
|
||||
:trigger="trigger"
|
||||
@command="(button) => handleButtonClick(button, row)"
|
||||
>
|
||||
<span class="fu-table-operations__dropdown-trigger">
|
||||
<el-button class="fu-table-operations__button" link type="primary" @click.stop>
|
||||
{{ t('fu.table.more') }}
|
||||
</el-button>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu :style="dropdownStyle">
|
||||
<el-dropdown-item
|
||||
v-for="button in getMoreButtons(row)"
|
||||
:key="buildButtonKey(button)"
|
||||
:command="button"
|
||||
:disabled="isButtonDisabled(button, row)"
|
||||
:divided="button.divided"
|
||||
>
|
||||
<el-icon v-if="button.icon" class="mr-1">
|
||||
<component :is="button.icon" />
|
||||
</el-icon>
|
||||
<span>{{ button.label }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, type PropType } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { resolveMaybeFn, type FuTableOperationButton } from './shared';
|
||||
|
||||
defineOptions({ name: 'FuTableOperations' });
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const normalizeWidth = (value?: string | number) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || trimmed === 'auto') {
|
||||
return undefined;
|
||||
}
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
buttons: {
|
||||
type: Array as PropType<FuTableOperationButton[]>,
|
||||
default: () => [],
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: undefined,
|
||||
},
|
||||
minWidth: {
|
||||
type: [Number, String],
|
||||
default: undefined,
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: 'center',
|
||||
},
|
||||
ellipsis: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
trigger: {
|
||||
type: String,
|
||||
default: 'hover',
|
||||
},
|
||||
fixed: {
|
||||
type: [Boolean, String],
|
||||
default: undefined,
|
||||
},
|
||||
fix: {
|
||||
type: [Boolean, String],
|
||||
default: undefined,
|
||||
},
|
||||
maxHeight: {
|
||||
type: [Number, String],
|
||||
default: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const resolvedFixed = computed(() => {
|
||||
if (props.fixed !== undefined) {
|
||||
return props.fixed;
|
||||
}
|
||||
if (typeof props.fix === 'string') {
|
||||
return props.fix;
|
||||
}
|
||||
return props.fix ? 'right' : false;
|
||||
});
|
||||
|
||||
const hasDynamicShow = computed(() => {
|
||||
return props.buttons.some((button) => typeof button.show === 'function');
|
||||
});
|
||||
|
||||
const staticVisibleCount = computed(() => {
|
||||
return props.buttons.filter((button) => button.show !== false).length;
|
||||
});
|
||||
|
||||
const hasMoreStatic = computed(() => {
|
||||
return staticVisibleCount.value > props.ellipsis + 1;
|
||||
});
|
||||
|
||||
const estimatedButtonCount = computed(() => {
|
||||
if (staticVisibleCount.value === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (props.ellipsis <= 0) {
|
||||
return 1;
|
||||
}
|
||||
if (hasDynamicShow.value || hasMoreStatic.value) {
|
||||
return props.ellipsis + 1;
|
||||
}
|
||||
return staticVisibleCount.value;
|
||||
});
|
||||
|
||||
const estimatedWidth = computed(() => {
|
||||
const buttonCount = Math.max(estimatedButtonCount.value, 1);
|
||||
const buttonsWidth = 24 + buttonCount * 56 + Math.max(buttonCount - 1, 0) * 5;
|
||||
const minWidth = normalizeWidth(props.minWidth);
|
||||
if (typeof minWidth === 'number') {
|
||||
return Math.max(buttonsWidth, minWidth);
|
||||
}
|
||||
if (typeof minWidth === 'string') {
|
||||
const parsed = Number(minWidth.replace('px', ''));
|
||||
if (!Number.isNaN(parsed)) {
|
||||
return Math.max(buttonsWidth, parsed);
|
||||
}
|
||||
}
|
||||
return buttonsWidth;
|
||||
});
|
||||
|
||||
const resolvedWidth = computed(() => {
|
||||
return normalizeWidth(props.width) ?? estimatedWidth.value;
|
||||
});
|
||||
|
||||
const dropdownStyle = computed(() => {
|
||||
if (props.maxHeight === undefined || props.maxHeight === null || props.maxHeight === '') {
|
||||
return undefined;
|
||||
}
|
||||
const maxHeight = typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight;
|
||||
return {
|
||||
maxHeight,
|
||||
overflowY: 'auto',
|
||||
};
|
||||
});
|
||||
|
||||
const getVisibleButtons = (row: any) => {
|
||||
return props.buttons.filter((button) => resolveMaybeFn(button.show ?? true, row));
|
||||
};
|
||||
|
||||
const hasMore = (row: any) => {
|
||||
return getVisibleButtons(row).length > props.ellipsis + 1;
|
||||
};
|
||||
|
||||
const getInlineButtons = (row: any) => {
|
||||
const visibleButtons = getVisibleButtons(row);
|
||||
if (props.ellipsis <= 0) {
|
||||
return [];
|
||||
}
|
||||
return hasMore(row) ? visibleButtons.slice(0, props.ellipsis) : visibleButtons;
|
||||
};
|
||||
|
||||
const getMoreButtons = (row: any) => {
|
||||
const visibleButtons = getVisibleButtons(row);
|
||||
return hasMore(row) ? visibleButtons.slice(props.ellipsis) : [];
|
||||
};
|
||||
|
||||
const isButtonDisabled = (button: FuTableOperationButton, row: any) => {
|
||||
return Boolean(resolveMaybeFn(button.disabled ?? false, row));
|
||||
};
|
||||
|
||||
const handleButtonClick = (button: FuTableOperationButton, row: any) => {
|
||||
if (isButtonDisabled(button, row)) {
|
||||
return;
|
||||
}
|
||||
button.click?.(row);
|
||||
};
|
||||
|
||||
const buildButtonKey = (button: FuTableOperationButton) => {
|
||||
return String(button.label || button.command || button.type || button.icon?.name || 'button');
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<el-pagination
|
||||
v-bind="$attrs"
|
||||
class="fu-table-pagination"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type PropType } from 'vue';
|
||||
|
||||
defineOptions({ name: 'FuTablePagination' });
|
||||
|
||||
const props = defineProps({
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array as PropType<number[]>,
|
||||
default: () => [10, 20, 50, 100],
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:pageSize', 'update:currentPage', 'size-change', 'current-change', 'change']);
|
||||
|
||||
const handleSizeChange = (pageSize: number) => {
|
||||
emit('update:pageSize', pageSize);
|
||||
emit('size-change', pageSize);
|
||||
emit('change', { currentPage: props.currentPage, pageSize });
|
||||
};
|
||||
|
||||
const handleCurrentChange = (currentPage: number) => {
|
||||
emit('update:currentPage', currentPage);
|
||||
emit('current-change', currentPage);
|
||||
emit('change', { currentPage, pageSize: props.pageSize });
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
import FuCompatComponents from '@/components/fu';
|
||||
|
||||
void FuCompatComponents;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { type App } from 'vue';
|
||||
|
||||
import FuInputRwSwitch from './FuInputRwSwitch.vue';
|
||||
import FuReadWriteSwitch from './FuReadWriteSwitch.vue';
|
||||
import FuSelectRwSwitch from './FuSelectRwSwitch.vue';
|
||||
import FuStep from './FuStep';
|
||||
import FuSteps from './FuSteps';
|
||||
import FuTable from './FuTable';
|
||||
import FuTableColumnSelect from './FuTableColumnSelect.vue';
|
||||
import FuTableOperations from './FuTableOperations.vue';
|
||||
import FuTablePagination from './FuTablePagination.vue';
|
||||
|
||||
const components = [
|
||||
FuTable,
|
||||
FuTableOperations,
|
||||
FuTablePagination,
|
||||
FuInputRwSwitch,
|
||||
FuReadWriteSwitch,
|
||||
FuSelectRwSwitch,
|
||||
FuSteps,
|
||||
FuStep,
|
||||
FuTableColumnSelect,
|
||||
];
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
components.forEach((component) => {
|
||||
app.component((component as any).name, component as any);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Comment, Fragment, Text, type VNode } from 'vue';
|
||||
|
||||
export interface FuTableColumnConfig {
|
||||
key: string;
|
||||
label: string;
|
||||
prop?: string;
|
||||
show: boolean;
|
||||
fixed?: boolean | string;
|
||||
}
|
||||
|
||||
export interface FuTableOperationButton {
|
||||
label?: string | number;
|
||||
click?: (row: any) => void;
|
||||
disabled?: boolean | ((row: any) => boolean);
|
||||
show?: boolean | ((row: any) => boolean);
|
||||
type?: string;
|
||||
icon?: any;
|
||||
divided?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const FU_TABLE_STORAGE_PREFIX = 'FU-T-';
|
||||
|
||||
export const flattenVNodes = (nodes: VNode[] = []): VNode[] => {
|
||||
const flattened: VNode[] = [];
|
||||
for (const node of nodes) {
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(node)) {
|
||||
flattened.push(...flattenVNodes(node));
|
||||
continue;
|
||||
}
|
||||
if (node.type === Fragment) {
|
||||
flattened.push(...flattenVNodes((node.children as VNode[]) || []));
|
||||
continue;
|
||||
}
|
||||
if (node.type === Comment || node.type === Text) {
|
||||
continue;
|
||||
}
|
||||
flattened.push(node);
|
||||
}
|
||||
return flattened;
|
||||
};
|
||||
|
||||
export const getVNodeComponentName = (vnode: VNode) => {
|
||||
const type = vnode.type as any;
|
||||
return type?.name || type?.__name || type;
|
||||
};
|
||||
|
||||
export const isElTableColumnVNode = (vnode: VNode) => {
|
||||
const type = vnode.type as any;
|
||||
return type === 'el-table-column' || type?.name === 'ElTableColumn' || type?.__name === 'ElTableColumn';
|
||||
};
|
||||
|
||||
export const resolveMaybeFn = <T, R>(value: R | ((row: T) => R), row: T): R => {
|
||||
if (typeof value === 'function') {
|
||||
return (value as (row: T) => R)(row);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const updateArrayInPlace = <T>(target: T[], next: T[]) => {
|
||||
if (target.length === next.length && target.every((item, index) => item === next[index])) {
|
||||
return;
|
||||
}
|
||||
target.splice(0, target.length, ...next);
|
||||
};
|
||||
@@ -10,8 +10,10 @@ import CopyButton from '@/components/copy-button/index.vue';
|
||||
import MsgInfo from '@/components/msg-info/index.vue';
|
||||
import DrawerPro from '@/components/drawer-pro/index.vue';
|
||||
import DialogPro from '@/components/dialog-pro/index.vue';
|
||||
import FuComponents from '@/components/fu';
|
||||
export default {
|
||||
install(app: App) {
|
||||
app.use(FuComponents);
|
||||
app.component(LayoutContent.name, LayoutContent);
|
||||
app.component(RouterButton.name, RouterButton);
|
||||
app.component(ComplexTable.name, ComplexTable);
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { copyText } from '@/utils/util';
|
||||
import { copyText } from '@/utils/clipboard';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
const em = defineEmits(['update:tags']);
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ import { FitAddon } from '@xterm/addon-fit';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import i18n from '@/lang';
|
||||
import { dateFormatForName } from '@/utils/util';
|
||||
import { dateFormatForName } from '@/utils/date';
|
||||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, onUnmounted, reactive, ref, computed } from 'vue';
|
||||
import { downloadFile } from '@/utils/util';
|
||||
import { downloadFile } from '@/utils/file';
|
||||
import { readByLine } from '@/api/modules/files';
|
||||
import { GlobalStore } from '@/store';
|
||||
import bus from '@/global/bus';
|
||||
|
||||
@@ -19,7 +19,7 @@ import { ref } from 'vue';
|
||||
import { getAgentSettingInfo } from '@/api/modules/setting';
|
||||
import i18n from '@/lang';
|
||||
import { MsgError, MsgWarning } from '@/utils/message';
|
||||
import { jumpToPath } from '@/utils/util';
|
||||
import { jumpToPath } from '@/utils/router';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import NodeSelect from '@/components/node-select/index.vue';
|
||||
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { searchTasks } from '@/api/modules/log';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { Log } from '@/api/interface/log';
|
||||
|
||||
@@ -135,7 +135,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { computeSize, newUUID, transferTimeToSecond } from '@/utils/util';
|
||||
import { computeSize } from '@/utils/size';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { transferTimeToSecond } from '@/utils/validate';
|
||||
import i18n from '@/lang';
|
||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile, genFileId } from 'element-plus';
|
||||
import { File } from '@/api/interface/file';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { onMounted, nextTick, watch, onBeforeUnmount, ref } from 'vue';
|
||||
import echarts from '@/utils/echarts';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { computeSizeFromKBs, computeSizeFromKB, computeSizeFromMB } from '@/utils/util';
|
||||
import { computeSizeFromKBs, computeSizeFromKB, computeSizeFromMB } from '@/utils/size';
|
||||
import i18n from '@/lang';
|
||||
const globalStore = GlobalStore();
|
||||
const isDarkTheme = ref(false);
|
||||
|
||||
Reference in New Issue
Block a user