mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 16:00:51 +00:00
feat: Optimize frontend structure (#12475)
This commit is contained in:
@@ -7,7 +7,8 @@
|
||||
"dev": "node --max-old-space-size=8192 ./node_modules/.bin/vite --host",
|
||||
"serve": "vite",
|
||||
"build:dev": "vite build --mode development",
|
||||
"build:test": "vue-tsc --noEmit && vite build --mode test",
|
||||
"type-check": "node --max-old-space-size=8192 ./node_modules/vue-tsc/bin/vue-tsc.js --noEmit",
|
||||
"build:test": "npm run type-check && vite build --mode test",
|
||||
"build:pro": "vite build --mode production",
|
||||
"preview": "vite preview",
|
||||
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
|
||||
@@ -68,6 +69,7 @@
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"commitizen": "^4.2.4",
|
||||
"esbuild": "^0.27.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
@@ -88,7 +90,6 @@
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-eslint2": "^5.0.3",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue-tsc": "^0.29.8"
|
||||
|
||||
@@ -8,8 +8,7 @@ import { MsgError } from '@/utils/message';
|
||||
import { Base64 } from 'js-base64';
|
||||
import i18n from '@/lang';
|
||||
import { changeToLocal } from '@/utils/node';
|
||||
import { getCookie } from '@/utils/util';
|
||||
|
||||
import { getCookie } from '@/utils/auth';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const config = {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import http from '@/api';
|
||||
import { ResPage } from '@/api/interface';
|
||||
import { Alert } from '../interface/alert';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
export const SearchAlerts = (req: Alert.AlertSearch) => {
|
||||
return http.post<ResPage<Alert.AlertInfo>>(`/alert/search`, req);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import http from '@/api';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { ResPage } from '../interface';
|
||||
import { Backup } from '../interface/backup';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import http from '@/api';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { ResPage, DescriptionUpdate } from '../interface';
|
||||
import { Database } from '../interface/database';
|
||||
|
||||
@@ -2,7 +2,7 @@ import http from '@/api';
|
||||
import { ResPage, ReqPage } from '../interface';
|
||||
import { Host } from '../interface/host';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
// firewall
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import http from '@/api';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { ResPage, SearchWithPage, DescriptionUpdate, ReqPage } from '../interface';
|
||||
import { Setting } from '../interface/setting';
|
||||
|
||||
@@ -2,8 +2,7 @@ import http from '@/api';
|
||||
import { ResPage } from '../interface';
|
||||
import { Host } from '../interface/host';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
export const searchHosts = (params: Host.SearchWithPage) => {
|
||||
return http.postLocalNode<ResPage<Host.Host>>(`/hosts/search`, params);
|
||||
};
|
||||
|
||||
@@ -3,8 +3,7 @@ import { ReqPage, ResPage, UpdateByFile } from '../interface';
|
||||
import { Toolbox } from '../interface/toolbox';
|
||||
import { Base64 } from 'js-base64';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
// device
|
||||
export const getDeviceBase = () => {
|
||||
return http.post<Toolbox.DeviceBaseInfo>(`/toolbox/device/base`, {}, TimeoutEnum.T_60S);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ReqPage, ResPage } from '../interface';
|
||||
import { Website } from '../interface/website';
|
||||
import { File } from '../interface/file';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
import { deepCopy } from '@/utils/misc';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
export const searchWebsites = (req: Website.WebSiteSearch, node?: string) => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
type FuLocaleMessage = Record<string, unknown>;
|
||||
|
||||
const fuLocales: Record<string, FuLocaleMessage> = {
|
||||
en: {
|
||||
fu: {
|
||||
table: {
|
||||
more: 'More',
|
||||
custom_table_rows: 'Custom columns',
|
||||
},
|
||||
steps: {
|
||||
cancel: 'Cancel',
|
||||
prev: 'Previous',
|
||||
next: 'Next',
|
||||
finish: 'Finish',
|
||||
},
|
||||
},
|
||||
},
|
||||
'es-ES': {
|
||||
fu: {
|
||||
table: {
|
||||
more: 'Más',
|
||||
custom_table_rows: 'Columnas personalizadas',
|
||||
},
|
||||
steps: {
|
||||
cancel: 'Cancelar',
|
||||
prev: 'Anterior',
|
||||
next: 'Siguiente',
|
||||
finish: 'Finalizar',
|
||||
},
|
||||
},
|
||||
},
|
||||
ja: {
|
||||
fu: {
|
||||
table: {
|
||||
more: 'もっと',
|
||||
custom_table_rows: 'カスタム列',
|
||||
},
|
||||
steps: {
|
||||
cancel: 'キャンセル',
|
||||
prev: '前へ',
|
||||
next: '次へ',
|
||||
finish: '完了',
|
||||
},
|
||||
},
|
||||
},
|
||||
ko: {
|
||||
fu: {
|
||||
table: {
|
||||
more: '더보기',
|
||||
custom_table_rows: '사용자 지정 열',
|
||||
},
|
||||
steps: {
|
||||
cancel: '취소',
|
||||
prev: '이전',
|
||||
next: '다음',
|
||||
finish: '완료',
|
||||
},
|
||||
},
|
||||
},
|
||||
ms: {
|
||||
fu: {
|
||||
table: {
|
||||
more: 'Lagi',
|
||||
custom_table_rows: 'Lajur tersuai',
|
||||
},
|
||||
steps: {
|
||||
cancel: 'Batal',
|
||||
prev: 'Sebelumnya',
|
||||
next: 'Seterusnya',
|
||||
finish: 'Selesai',
|
||||
},
|
||||
},
|
||||
},
|
||||
'pt-BR': {
|
||||
fu: {
|
||||
table: {
|
||||
more: 'Mais',
|
||||
custom_table_rows: 'Colunas personalizadas',
|
||||
},
|
||||
steps: {
|
||||
cancel: 'Cancelar',
|
||||
prev: 'Anterior',
|
||||
next: 'Próximo',
|
||||
finish: 'Concluir',
|
||||
},
|
||||
},
|
||||
},
|
||||
ru: {
|
||||
fu: {
|
||||
table: {
|
||||
more: 'Еще',
|
||||
custom_table_rows: 'Настройка колонок',
|
||||
},
|
||||
steps: {
|
||||
cancel: 'Отмена',
|
||||
prev: 'Назад',
|
||||
next: 'Далее',
|
||||
finish: 'Готово',
|
||||
},
|
||||
},
|
||||
},
|
||||
tr: {
|
||||
fu: {
|
||||
table: {
|
||||
more: 'Daha fazla',
|
||||
custom_table_rows: 'Özel sütunlar',
|
||||
},
|
||||
steps: {
|
||||
cancel: 'İptal',
|
||||
prev: 'Önceki',
|
||||
next: 'Sonraki',
|
||||
finish: 'Tamamla',
|
||||
},
|
||||
},
|
||||
},
|
||||
zh: {
|
||||
fu: {
|
||||
table: {
|
||||
more: '更多',
|
||||
custom_table_rows: '自定义列',
|
||||
},
|
||||
steps: {
|
||||
cancel: '取消',
|
||||
prev: '上一步',
|
||||
next: '下一步',
|
||||
finish: '完成',
|
||||
},
|
||||
},
|
||||
},
|
||||
'zh-Hant': {
|
||||
fu: {
|
||||
table: {
|
||||
more: '更多',
|
||||
custom_table_rows: '自訂欄位',
|
||||
},
|
||||
steps: {
|
||||
cancel: '取消',
|
||||
prev: '上一步',
|
||||
next: '下一步',
|
||||
finish: '完成',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const getFuLocaleMessage = (locale: string) => {
|
||||
return fuLocales[locale] || fuLocales.en;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4468,6 +4468,6 @@ const message = {
|
||||
};
|
||||
|
||||
export default {
|
||||
...fit2cloudEnLocale,
|
||||
...getFuLocaleMessage('en'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudEsEsLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4413,6 +4413,6 @@ const message = {
|
||||
};
|
||||
|
||||
export default {
|
||||
...fit2cloudEsEsLocale,
|
||||
...getFuLocaleMessage('es-ES'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/ja';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4409,6 +4409,6 @@ const message = {
|
||||
},
|
||||
};
|
||||
export default {
|
||||
...fit2cloudEnLocale,
|
||||
...getFuLocaleMessage('ja'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudKoLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4306,6 +4306,6 @@ const message = {
|
||||
};
|
||||
|
||||
export default {
|
||||
...fit2cloudKoLocale,
|
||||
...getFuLocaleMessage('ko'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/ms';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4465,6 +4465,6 @@ const message = {
|
||||
};
|
||||
|
||||
export default {
|
||||
...fit2cloudEnLocale,
|
||||
...getFuLocaleMessage('ms'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudPtBrLocale from 'fit2cloud-ui-plus/src/locale/lang/pt-br';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4617,6 +4617,6 @@ const message = {
|
||||
};
|
||||
|
||||
export default {
|
||||
...fit2cloudPtBrLocale,
|
||||
...getFuLocaleMessage('pt-BR'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/ru';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4474,6 +4474,6 @@ const message = {
|
||||
};
|
||||
|
||||
export default {
|
||||
...fit2cloudEnLocale,
|
||||
...getFuLocaleMessage('ru'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/en';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4463,6 +4463,6 @@ const message = {
|
||||
};
|
||||
|
||||
export default {
|
||||
...fit2cloudEnLocale,
|
||||
...getFuLocaleMessage('tr'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudTwLocale from 'fit2cloud-ui-plus/src/locale/lang/zh-tw';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4067,6 +4067,6 @@ const message = {
|
||||
},
|
||||
};
|
||||
export default {
|
||||
...fit2cloudTwLocale,
|
||||
...getFuLocaleMessage('zh-Hant'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fit2cloudZhLocale from 'fit2cloud-ui-plus/src/locale/lang/zh-cn';
|
||||
import { getFuLocaleMessage } from '@/lang/fu';
|
||||
|
||||
const message = {
|
||||
commons: {
|
||||
@@ -4130,6 +4130,6 @@ const message = {
|
||||
},
|
||||
};
|
||||
export default {
|
||||
...fit2cloudZhLocale,
|
||||
...getFuLocaleMessage('zh'),
|
||||
...message,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { computed, createApp } from 'vue';
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
import ElementPlus from 'element-plus';
|
||||
import 'element-plus/dist/index.css';
|
||||
import * as Icons from '@element-plus/icons-vue';
|
||||
|
||||
import '@/styles/index.scss';
|
||||
import '@/styles/common.scss';
|
||||
import '@/assets/iconfont/iconfont.css';
|
||||
@@ -16,10 +20,6 @@ import pinia from '@/store/index';
|
||||
import SvgIcon from './components/svg-icon/svg-icon.vue';
|
||||
import Components from '@/components';
|
||||
|
||||
import ElementPlus from 'element-plus';
|
||||
import Fit2CloudPlus from 'fit2cloud-ui-plus';
|
||||
import * as Icons from '@element-plus/icons-vue';
|
||||
|
||||
import directives from '@/directives/index';
|
||||
|
||||
const bootstrap = async () => {
|
||||
@@ -28,10 +28,8 @@ const bootstrap = async () => {
|
||||
await Promise.all([loadLocaleMessages(currentLocale), ensureFallbackLocale()]);
|
||||
|
||||
const app = createApp(App);
|
||||
const fit2cloudLocale = computed(() => i18n.global.getLocaleMessage(i18n.global.locale.value));
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
app.use(ElementPlus);
|
||||
app.use(Fit2CloudPlus, { locale: fit2cloudLocale });
|
||||
|
||||
Object.keys(Icons).forEach((key) => {
|
||||
app.component(key, Icons[key as keyof typeof Icons]);
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
.fu-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fu-table-header {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fu-table-operations {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
justify-content: flex-start;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.fu-table-operations__action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.fu-table-operations__button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-width: 0;
|
||||
padding-inline: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fu-table-more-button {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.fu-table-operations__dropdown-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fu-search-bar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fu-search-bar-button {
|
||||
padding-inline: 8px;
|
||||
}
|
||||
|
||||
.fu-table-column-select__panel {
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.fu-table-column-select__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.fu-table-column-select__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fu-table-column-select__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fu-table-column-select__drag {
|
||||
cursor: move;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.fu-read-write-switch,
|
||||
.fu-input-rw-switch,
|
||||
.fu-select-rw-switch {
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.fu-read-write-switch__read,
|
||||
.fu-input-rw-switch__read,
|
||||
.fu-select-rw-switch__read {
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fu-read-write-switch__text,
|
||||
.fu-input-rw-switch__text,
|
||||
.fu-select-rw-switch__text {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fu-steps {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.fu-steps--vertical {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.fu-steps__nav {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.fu-steps--vertical .fu-steps__nav {
|
||||
flex-direction: column;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.fu-steps__item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: var(--el-text-color-regular);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.fu-steps__item.is-active {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fu-steps__item.is-finished .fu-steps__index {
|
||||
background: var(--el-color-success);
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
|
||||
.fu-steps__item:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.fu-steps__index {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fu-steps__title {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.fu-steps__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fu-steps__footer {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@use 'fit2cloud-ui-plus/src/styles/index.scss' as *;
|
||||
@use './element.scss';
|
||||
@use './element-dark.scss';
|
||||
@use './fu.scss';
|
||||
@use './moblie.scss';
|
||||
@use './reset.scss';
|
||||
@use './var.scss';
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import i18n from '@/lang';
|
||||
|
||||
export function getLanguage() {
|
||||
return localStorage.getItem('lang') || 'zh';
|
||||
}
|
||||
|
||||
function normalizeAppLocaleKey(language: string) {
|
||||
const localeMap: Record<string, string> = {
|
||||
tw: 'zh-hant',
|
||||
'zh-Hant': 'zh-hant',
|
||||
'pt-BR': 'pt-br',
|
||||
'es-ES': 'es-es',
|
||||
};
|
||||
return localeMap[language] || language.toLowerCase();
|
||||
}
|
||||
|
||||
export function getLabel(row: any) {
|
||||
const language = localStorage.getItem('lang') || 'zh';
|
||||
const lang = normalizeAppLocaleKey(language);
|
||||
if (row.label && typeof row.label[lang] === 'string' && row.label[lang] !== '') {
|
||||
return row.label[lang];
|
||||
}
|
||||
if (language == 'zh' || language == 'tw') {
|
||||
return row.labelZh;
|
||||
}
|
||||
return row.labelEn;
|
||||
}
|
||||
|
||||
export function getDescription(row: any) {
|
||||
const language = localStorage.getItem('lang') || 'zh';
|
||||
const lang = normalizeAppLocaleKey(language);
|
||||
if (row.description && typeof row.description[lang] === 'string' && row.description[lang] !== '') {
|
||||
return row.description[lang];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getDBName(length: number): string {
|
||||
const chars = 'abcdefhijkmnprstwxyz2345678';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getRuntimeLabel(type: string) {
|
||||
if (type == 'appstore') {
|
||||
return i18n.global.t('menu.apps');
|
||||
}
|
||||
if (type == 'local') {
|
||||
return i18n.global.t('commons.table.local');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { jumpToPath } from './util';
|
||||
import { jumpToPath } from './router';
|
||||
import router from '@/routers';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import CryptoJS from 'crypto-js';
|
||||
import JSEncrypt from 'jsencrypt';
|
||||
|
||||
export function getCookie(name: string) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift();
|
||||
}
|
||||
|
||||
function rsaEncrypt(data: string, publicKey: string) {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
const jsEncrypt = new JSEncrypt();
|
||||
jsEncrypt.setPublicKey(publicKey);
|
||||
return jsEncrypt.encrypt(data);
|
||||
}
|
||||
|
||||
function aesEncrypt(data: string, key: string) {
|
||||
const keyBytes = CryptoJS.enc.Utf8.parse(key);
|
||||
const iv = CryptoJS.lib.WordArray.random(16);
|
||||
const encrypted = CryptoJS.AES.encrypt(data, keyBytes, {
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
return iv.toString(CryptoJS.enc.Base64) + ':' + encrypted.toString();
|
||||
}
|
||||
|
||||
function urlDecode(value: string): string {
|
||||
return decodeURIComponent(value.replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
function generateAESKey(): string {
|
||||
const keyLength = 16;
|
||||
const randomBytes = new Uint8Array(keyLength);
|
||||
crypto.getRandomValues(randomBytes);
|
||||
return Array.from(randomBytes)
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
export const encryptPassword = (password: string) => {
|
||||
if (!password) {
|
||||
return '';
|
||||
}
|
||||
let rsaPublicKeyText = getCookie('panel_public_key');
|
||||
if (!rsaPublicKeyText) {
|
||||
return password;
|
||||
}
|
||||
rsaPublicKeyText = urlDecode(rsaPublicKeyText);
|
||||
|
||||
const aesKey = generateAESKey();
|
||||
rsaPublicKeyText = rsaPublicKeyText.replaceAll('"', '');
|
||||
const rsaPublicKey = atob(rsaPublicKeyText);
|
||||
const keyCipher = rsaEncrypt(aesKey, rsaPublicKey);
|
||||
const passwordCipher = aesEncrypt(password, aesKey);
|
||||
return `${keyCipher}:${passwordCipher}`;
|
||||
};
|
||||
|
||||
export function base64UrlToBuffer(base64url: string): ArrayBuffer {
|
||||
let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const pad = base64.length % 4;
|
||||
if (pad) {
|
||||
base64 += '='.repeat(4 - pad);
|
||||
}
|
||||
const binary = window.atob(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
export function bufferToBase64Url(buffer: ArrayBuffer): string {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
let binary = '';
|
||||
bytes.forEach((byte) => {
|
||||
binary += String.fromCharCode(byte);
|
||||
});
|
||||
const base64 = window.btoa(binary);
|
||||
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export const toLink = (link: string) => {
|
||||
const ipv6Regex = /^https?:\/\/([a-f0-9:]+):(\d+)(\/?.*)?$/i;
|
||||
try {
|
||||
if (ipv6Regex.test(link)) {
|
||||
const match = link.match(ipv6Regex);
|
||||
if (match) {
|
||||
const ipv6 = match[1];
|
||||
const port = match[2];
|
||||
const path = match[3] || '';
|
||||
link = `${link.startsWith('https') ? 'https' : 'http'}://[${ipv6}]:${port}${path}`;
|
||||
}
|
||||
}
|
||||
window.open(link, '_blank');
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
export const preloadImage = (url: string): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(url);
|
||||
img.onerror = () => resolve(url);
|
||||
img.src = url;
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import i18n from '@/lang';
|
||||
import useClipboard from 'vue-clipboard3';
|
||||
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
|
||||
const { toClipboard } = useClipboard();
|
||||
|
||||
export async function copyText(content: string) {
|
||||
try {
|
||||
await toClipboard(content);
|
||||
MsgSuccess(i18n.global.t('commons.msg.copySuccess'));
|
||||
} catch (e) {
|
||||
MsgError(i18n.global.t('commons.msg.copyFailed'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import i18n from '@/lang';
|
||||
|
||||
export function loadUpTime(timeSince: string) {
|
||||
if (!timeSince) {
|
||||
return '';
|
||||
}
|
||||
const targetTime = new Date(timeSince);
|
||||
const currentTime = new Date();
|
||||
const uptime = (currentTime.getTime() - targetTime.getTime()) / 1000;
|
||||
if (uptime <= 0) {
|
||||
return '';
|
||||
}
|
||||
let days = Math.floor(uptime / 86400);
|
||||
let hours = Math.floor((uptime % 86400) / 3600);
|
||||
let minutes = Math.floor((uptime % 3600) / 60);
|
||||
let seconds = Math.floor(uptime % 60);
|
||||
if (days !== 0) {
|
||||
return (
|
||||
days +
|
||||
i18n.global.t('commons.units.day') +
|
||||
' ' +
|
||||
hours +
|
||||
i18n.global.t('commons.units.hour') +
|
||||
' ' +
|
||||
minutes +
|
||||
i18n.global.t('commons.units.minute') +
|
||||
' ' +
|
||||
seconds +
|
||||
i18n.global.t('commons.units.second')
|
||||
);
|
||||
}
|
||||
if (hours !== 0) {
|
||||
return (
|
||||
hours +
|
||||
i18n.global.t('commons.units.hour') +
|
||||
' ' +
|
||||
minutes +
|
||||
i18n.global.t('commons.units.minute') +
|
||||
' ' +
|
||||
seconds +
|
||||
i18n.global.t('commons.units.second')
|
||||
);
|
||||
}
|
||||
if (minutes !== 0) {
|
||||
return minutes + i18n.global.t('commons.units.minute') + ' ' + seconds + i18n.global.t('commons.units.second');
|
||||
}
|
||||
return seconds + i18n.global.t('commons.units.second');
|
||||
}
|
||||
|
||||
export function getCurrentDateFormatted() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
export function dateFormat(row: any, col: any, dataStr: any) {
|
||||
const date = new Date(dataStr);
|
||||
const y = date.getFullYear();
|
||||
let m: string | number = date.getMonth() + 1;
|
||||
m = m < 10 ? `0${String(m)}` : m;
|
||||
let d: string | number = date.getDate();
|
||||
d = d < 10 ? `0${String(d)}` : d;
|
||||
let h: string | number = date.getHours();
|
||||
h = h < 10 ? `0${String(h)}` : h;
|
||||
let minute: string | number = date.getMinutes();
|
||||
minute = minute < 10 ? `0${String(minute)}` : minute;
|
||||
let second: string | number = date.getSeconds();
|
||||
second = second < 10 ? `0${String(second)}` : second;
|
||||
return `${String(y)}-${String(m)}-${String(d)} ${String(h)}:${String(minute)}:${String(second)}`;
|
||||
}
|
||||
|
||||
export function dateFormatSimple(dataStr: any) {
|
||||
const date = new Date(dataStr);
|
||||
const y = date.getFullYear();
|
||||
let m: string | number = date.getMonth() + 1;
|
||||
m = m < 10 ? `0${String(m)}` : m;
|
||||
let d: string | number = date.getDate();
|
||||
d = d < 10 ? `0${String(d)}` : d;
|
||||
return `${String(y)}-${String(m)}-${String(d)}`;
|
||||
}
|
||||
|
||||
export function dateFormatForName(dataStr: any) {
|
||||
const date = new Date(dataStr);
|
||||
const y = date.getFullYear();
|
||||
let m: string | number = date.getMonth() + 1;
|
||||
m = m < 10 ? `0${String(m)}` : m;
|
||||
let d: string | number = date.getDate();
|
||||
d = d < 10 ? `0${String(d)}` : d;
|
||||
let h: string | number = date.getHours();
|
||||
h = h < 10 ? `0${String(h)}` : h;
|
||||
let minute: string | number = date.getMinutes();
|
||||
minute = minute < 10 ? `0${String(minute)}` : minute;
|
||||
let second: string | number = date.getSeconds();
|
||||
second = second < 10 ? `0${String(second)}` : second;
|
||||
return `${String(y)}${String(m)}${String(d)}${String(h)}${String(minute)}${String(second)}`;
|
||||
}
|
||||
|
||||
export function dateFormatWithoutYear(dataStr: any) {
|
||||
const date = new Date(dataStr);
|
||||
let m: string | number = date.getMonth() + 1;
|
||||
m = m < 10 ? `0${String(m)}` : m;
|
||||
let d: string | number = date.getDate();
|
||||
d = d < 10 ? `0${String(d)}` : d;
|
||||
let h: string | number = date.getHours();
|
||||
h = h < 10 ? `0${String(h)}` : h;
|
||||
let minute: string | number = date.getMinutes();
|
||||
minute = minute < 10 ? `0${String(minute)}` : minute;
|
||||
let s: string | number = date.getSeconds();
|
||||
s = s < 10 ? `0${String(s)}` : s;
|
||||
return `${String(m)}-${String(d)}\n${String(h)}:${String(minute)}:${String(s)}`;
|
||||
}
|
||||
|
||||
export function dateFormatForSecond(dataStr: any) {
|
||||
const date = new Date(dataStr);
|
||||
let h: string | number = date.getHours();
|
||||
h = h < 10 ? `0${String(h)}` : h;
|
||||
let minute: string | number = date.getMinutes();
|
||||
minute = minute < 10 ? `0${String(minute)}` : minute;
|
||||
let second: string | number = date.getSeconds();
|
||||
second = second < 10 ? `0${String(second)}` : second;
|
||||
return `${String(h)}:${String(minute)}:${String(second)}`;
|
||||
}
|
||||
|
||||
export function loadZero(i: number) {
|
||||
return i < 10 ? '0' + i : '' + i;
|
||||
}
|
||||
|
||||
export function getAge(d1: string): string {
|
||||
const dateBegin = new Date(d1);
|
||||
const dateEnd = new Date();
|
||||
const dateDiff = dateEnd.getTime() - dateBegin.getTime();
|
||||
const dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000));
|
||||
const leave1 = dateDiff % (24 * 3600 * 1000);
|
||||
const hours = Math.floor(leave1 / (3600 * 1000));
|
||||
const leave2 = leave1 % (3600 * 1000);
|
||||
const minutes = Math.floor(leave2 / (60 * 1000));
|
||||
|
||||
let res = '';
|
||||
if (dayDiff > 0) {
|
||||
res += String(dayDiff) + ' ' + i18n.global.t('commons.units.day', dayDiff) + ' ';
|
||||
if (hours <= 0) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
if (hours > 0) {
|
||||
res += String(hours) + ' ' + i18n.global.t('commons.units.hour', hours);
|
||||
return res;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
res += String(minutes) + ' ' + i18n.global.t('commons.units.minute', minutes);
|
||||
return res;
|
||||
}
|
||||
return i18n.global.t('app.less1Minute');
|
||||
}
|
||||
|
||||
export function getDateStr() {
|
||||
let now: Date = new Date();
|
||||
let year: number = now.getFullYear();
|
||||
let month: number = now.getMonth() + 1;
|
||||
let date: number = now.getDate();
|
||||
let hours: number = now.getHours();
|
||||
let minutes: number = now.getMinutes();
|
||||
let seconds: number = now.getSeconds();
|
||||
return `${year}-${month < 10 ? '0' + month : month}-${date < 10 ? '0' + date : date}-${
|
||||
hours < 10 ? '0' + hours : hours
|
||||
}-${minutes < 10 ? '0' + minutes : minutes}-${seconds < 10 ? '0' + seconds : seconds}`;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isJson } from './util';
|
||||
import { isJson } from './misc';
|
||||
|
||||
export function formatImageStdout(stdout: string) {
|
||||
let lines = stdout.split('\r\n');
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
const icons = new Map([
|
||||
['.zip', 'p-file-zip'],
|
||||
['.gz', 'p-file-zip'],
|
||||
['.tar.bz2', 'p-file-zip'],
|
||||
['.bz2', 'p-file-zip'],
|
||||
['.xz', 'p-file-zip'],
|
||||
['.tar.xz', 'p-file-zip'],
|
||||
['.tar', 'p-file-zip'],
|
||||
['.tar.gz', 'p-file-zip'],
|
||||
['.war', 'p-file-zip'],
|
||||
['.tgz', 'p-file-zip'],
|
||||
['.7z', 'p-file-zip'],
|
||||
['.rar', 'p-file-zip'],
|
||||
['.mp3', 'p-file-mp3'],
|
||||
['.svg', 'p-file-svg'],
|
||||
['.txt', 'p-file-txt'],
|
||||
['.html', 'p-file-html'],
|
||||
['.word', 'p-file-word'],
|
||||
['.ppt', 'p-file-ppt'],
|
||||
['.jpg', 'p-file-jpg'],
|
||||
['.jpeg', 'p-file-jpg'],
|
||||
['.png', 'p-file-png'],
|
||||
['.xlsx', 'p-file-excel'],
|
||||
['.doc', 'p-file-word'],
|
||||
['.xls', 'p-file-excel'],
|
||||
['.docx', 'p-file-word'],
|
||||
['.pdf', 'p-file-pdf'],
|
||||
['.bmp', 'p-file-png'],
|
||||
['.gif', 'p-file-png'],
|
||||
['.tiff', 'p-file-png'],
|
||||
['.ico', 'p-file-png'],
|
||||
['.webp', 'p-file-png'],
|
||||
['.mp4', 'p-file-video'],
|
||||
['.webm', 'p-file-video'],
|
||||
['.mov', 'p-file-video'],
|
||||
['.wmv', 'p-file-video'],
|
||||
['.mkv', 'p-file-video'],
|
||||
['.avi', 'p-file-video'],
|
||||
['.wma', 'p-file-video'],
|
||||
['.flv', 'p-file-video'],
|
||||
['.wav', 'p-file-mp3'],
|
||||
['.wma', 'p-file-mp3'],
|
||||
['.ape', 'p-file-mp3'],
|
||||
['.acc', 'p-file-mp3'],
|
||||
['.ogg', 'p-file-mp3'],
|
||||
['.flac', 'p-file-mp3'],
|
||||
]);
|
||||
|
||||
const fileTypes = {
|
||||
image: ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.ico', '.svg', '.webp'],
|
||||
compress: ['.zip', '.rar', '.gz', '.war', '.tgz', '.7z', '.tar.gz', '.tar', '.bz2', '.xz', '.tar.bz2', '.tar.xz'],
|
||||
video: ['.mp4', '.webm', '.mov', '.wmv', '.mkv', '.avi', '.wma', '.flv'],
|
||||
audio: ['.mp3', '.wav', '.wma', '.ape', '.acc', '.ogg', '.flac'],
|
||||
pdf: ['.pdf'],
|
||||
word: ['.doc', '.docx'],
|
||||
excel: ['.xls', '.xlsx'],
|
||||
text: ['.iso', '.tiff', '.exe', '.so', '.bz', '.dmg', '.apk', '.pptx', '.ppt', '.xlsb'],
|
||||
};
|
||||
|
||||
export function getIcon(extension: string): string {
|
||||
if (icons.get(extension) != undefined) {
|
||||
return String(icons.get(extension));
|
||||
}
|
||||
return 'p-file-normal';
|
||||
}
|
||||
|
||||
export const getFileType = (extension: string) => {
|
||||
let type = 'text';
|
||||
Object.entries(fileTypes).forEach(([key, extensions]) => {
|
||||
if (extensions.includes(extension.toLowerCase())) {
|
||||
type = key;
|
||||
}
|
||||
});
|
||||
return type;
|
||||
};
|
||||
|
||||
const convertTypes = ['image', 'video', 'audio'] as const;
|
||||
type ConvertType = (typeof convertTypes)[number];
|
||||
|
||||
export function isConvertible(extension: string, mimeType: string): boolean {
|
||||
return convertTypes.includes(getFileType(extension) as ConvertType) && /^(image|audio|video)\//.test(mimeType);
|
||||
}
|
||||
|
||||
export const isSensitiveLinuxPath = (path: string) => {
|
||||
const sensitivePath = [
|
||||
'/',
|
||||
'/bin',
|
||||
'/sbin',
|
||||
'/usr/bin',
|
||||
'/usr/sbin',
|
||||
'/usr/local/bin',
|
||||
'/etc',
|
||||
'/lib',
|
||||
'/lib64',
|
||||
'/usr/lib',
|
||||
'/home',
|
||||
'/tmp',
|
||||
'/var',
|
||||
'/dev',
|
||||
'/proc',
|
||||
'/sys',
|
||||
];
|
||||
return sensitivePath.indexOf(path) !== -1;
|
||||
};
|
||||
|
||||
function buildFileDownloadUrl(filePath: string, currentNode: string): string {
|
||||
const base = `${import.meta.env.VITE_API_URL as string}/files/download?operateNode=${currentNode}&`;
|
||||
return base + 'path=' + encodeURIComponent(filePath);
|
||||
}
|
||||
|
||||
export function buildFileSharePageUrl(code: string, currentNode: string): string {
|
||||
const shareUrl = new URL(`/s/${encodeURIComponent(code)}`, window.location.origin);
|
||||
shareUrl.searchParams.set('operateNode', currentNode);
|
||||
return shareUrl.toString();
|
||||
}
|
||||
|
||||
export function buildFileShareDownloadUrl(code: string, currentNode: string, password?: string): string {
|
||||
const apiBase = import.meta.env.VITE_API_URL as string;
|
||||
const normalizedBase = apiBase.replace(/\/$/, '');
|
||||
const shareUrl = /^https?:\/\//i.test(normalizedBase)
|
||||
? new URL(`${normalizedBase}/files/share/download`)
|
||||
: new URL(`${normalizedBase}/files/share/download`, window.location.origin);
|
||||
shareUrl.searchParams.set('operateNode', currentNode);
|
||||
shareUrl.searchParams.set('code', code);
|
||||
if (password && password.length > 0) {
|
||||
shareUrl.searchParams.set('password', password);
|
||||
}
|
||||
return shareUrl.toString();
|
||||
}
|
||||
|
||||
export function buildFileShareQrCodeUrl(code: string, currentNode: string): string {
|
||||
const apiBase = import.meta.env.VITE_API_URL as string;
|
||||
const normalizedBase = apiBase.replace(/\/$/, '');
|
||||
const shareUrl = /^https?:\/\//i.test(normalizedBase)
|
||||
? new URL(`${normalizedBase}/files/share/qrcode`)
|
||||
: new URL(`${normalizedBase}/files/share/qrcode`, window.location.origin);
|
||||
shareUrl.searchParams.set('operateNode', currentNode);
|
||||
shareUrl.searchParams.set('code', code);
|
||||
return shareUrl.toString();
|
||||
}
|
||||
|
||||
export function downloadFile(filePath: string, currentNode: string) {
|
||||
window.open(buildFileDownloadUrl(filePath, currentNode), '_blank');
|
||||
}
|
||||
|
||||
export function downloadWithContent(content: string, fileName: string) {
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([content]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
a.download = fileName;
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export function getRandomStr(length: number): string {
|
||||
const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const newUUID = () => {
|
||||
return uuidv4();
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import { toUnicode } from 'punycode';
|
||||
|
||||
export function deepCopy<T>(obj: any): T {
|
||||
let newObj: any;
|
||||
try {
|
||||
newObj = obj.push ? [] : {};
|
||||
} catch (error) {
|
||||
newObj = {};
|
||||
}
|
||||
for (let attr in obj) {
|
||||
if (typeof obj[attr] === 'object') {
|
||||
newObj[attr] = deepCopy(obj[attr]);
|
||||
} else {
|
||||
newObj[attr] = obj[attr];
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
export function debounce(func: Function, wait: number) {
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
return function executedFunction(...args: any[]) {
|
||||
const later = () => {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
export function isJson(str: string) {
|
||||
try {
|
||||
if (typeof JSON.parse(str) === 'object') {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function compareById(a: any, b: any) {
|
||||
return a.sort - b.sort;
|
||||
}
|
||||
|
||||
export function sortMenu(arr: any[]) {
|
||||
if (!arr || arr.length === 0) {
|
||||
return;
|
||||
}
|
||||
arr.forEach((item) => {
|
||||
if (item.children && Array.isArray(item.children)) {
|
||||
item.children.sort(compareById);
|
||||
}
|
||||
});
|
||||
arr.sort(compareById);
|
||||
}
|
||||
|
||||
export function isPunycoded(domain: string): boolean {
|
||||
return domain.includes('xn--');
|
||||
}
|
||||
|
||||
export function GetPunyCodeDomain(domain: string): string {
|
||||
if (!domain || typeof domain !== 'string') {
|
||||
return '';
|
||||
}
|
||||
const lowerDomain = domain.toLowerCase();
|
||||
if (!lowerDomain.includes('xn--')) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const decoded = toUnicode(domain);
|
||||
if (decoded === domain) {
|
||||
return '';
|
||||
}
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,10 @@ export const routerToPathWithQuery = async (path: string, query: any) => {
|
||||
tabStoreMiddleWare();
|
||||
};
|
||||
|
||||
export const jumpToPath = (router: any, path: string) => {
|
||||
routerToPathWithQuery(path, { uncached: 'true' });
|
||||
};
|
||||
|
||||
export const routerToNameWithParams = async (name: string, params: any) => {
|
||||
await router.push({ name: name, params: params });
|
||||
tabStoreMiddleWare();
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
function formattedNumber(num: string) {
|
||||
return num.endsWith('.00') ? Number(num.slice(0, -3)) : Number(num);
|
||||
}
|
||||
|
||||
export function computeSize(size: number): string {
|
||||
const num = 1024.0;
|
||||
if (size < num) return size + ' B';
|
||||
if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KB';
|
||||
if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MB';
|
||||
if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GB';
|
||||
return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TB';
|
||||
}
|
||||
|
||||
export function computeSizeForDocker(size: number): string {
|
||||
const num = 1024.0;
|
||||
if (size < num) return size + ' B';
|
||||
if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KiB';
|
||||
if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MiB';
|
||||
if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GiB';
|
||||
return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TiB';
|
||||
}
|
||||
|
||||
export function computeSize2(size: number): string {
|
||||
const num = 1000.0;
|
||||
if (size < num) return size + ' B';
|
||||
if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KB';
|
||||
if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MB';
|
||||
if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GB';
|
||||
return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TB';
|
||||
}
|
||||
|
||||
export function computeCPU(size: number): string {
|
||||
const num = 1000;
|
||||
if (size < num) return size + ' ns';
|
||||
if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' μs';
|
||||
if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' ms';
|
||||
return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' s';
|
||||
}
|
||||
|
||||
export function splitSize(size: number): any {
|
||||
const num = 1024.0;
|
||||
if (size < num) return { size: Number(size), unit: 'B' };
|
||||
if (size < Math.pow(num, 2)) return { size: formattedNumber((size / num).toFixed(2)), unit: 'KB' };
|
||||
if (size < Math.pow(num, 3))
|
||||
return { size: formattedNumber((size / Number(Math.pow(num, 2).toFixed(2))).toFixed(2)), unit: 'MB' };
|
||||
if (size < Math.pow(num, 4))
|
||||
return { size: formattedNumber((size / Number(Math.pow(num, 3))).toFixed(2)), unit: 'GB' };
|
||||
return { size: formattedNumber((size / Number(Math.pow(num, 4))).toFixed(2)), unit: 'TB' };
|
||||
}
|
||||
|
||||
export function computeSizeFromMB(size: number): string {
|
||||
const num = 1024.0;
|
||||
if (size < num) return size + ' MB';
|
||||
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' GB';
|
||||
return (size / Math.pow(num, 3)).toFixed(2) + ' TB';
|
||||
}
|
||||
|
||||
export function computeSizeFromKB(size: number): string {
|
||||
const num = 1024.0;
|
||||
if (size < num) return size + ' KB';
|
||||
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' MB';
|
||||
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB';
|
||||
return (size / Math.pow(num, 3)).toFixed(2) + ' TB';
|
||||
}
|
||||
|
||||
export function computeSizeFromByte(size: number): string {
|
||||
const num = 1024.0;
|
||||
if (size < num) return size + ' B';
|
||||
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' KB';
|
||||
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' MB';
|
||||
if (size < Math.pow(num, 4)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB';
|
||||
return (size / Math.pow(num, 5)).toFixed(2) + ' TB';
|
||||
}
|
||||
|
||||
export function computeSizeFromKBs(size: number): string {
|
||||
const num = 1024.0;
|
||||
if (size < num) return size + ' KB/s';
|
||||
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' MB/s';
|
||||
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB/s';
|
||||
return (size / Math.pow(num, 3)).toFixed(2) + ' TB/s';
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { AcmeAccountTypes, DNSTypes, KeyTypes } from '@/global/mimetype';
|
||||
import i18n from '@/lang';
|
||||
|
||||
export function getAccountName(type: string) {
|
||||
for (const item of AcmeAccountTypes) {
|
||||
if (item.value === type) {
|
||||
return item.label;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getKeyName(type: string) {
|
||||
for (const item of KeyTypes) {
|
||||
if (item.value === type) {
|
||||
return item.label;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getDNSName(type: string) {
|
||||
for (const item of DNSTypes) {
|
||||
if (item.value === type) {
|
||||
return item.label;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getProvider(provider: string): string {
|
||||
switch (provider) {
|
||||
case 'dnsAccount':
|
||||
return i18n.global.t('website.dnsAccount');
|
||||
case 'dnsManual':
|
||||
return i18n.global.t('website.dnsManual');
|
||||
case 'http':
|
||||
return 'HTTP';
|
||||
case 'selfSigned':
|
||||
return i18n.global.t('ssl.selfSigned');
|
||||
case 'fromMaster':
|
||||
return i18n.global.t('ssl.fromMaster');
|
||||
default:
|
||||
return i18n.global.t('ssl.manualCreate');
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,184 @@
|
||||
import i18n from '@/lang';
|
||||
|
||||
export function checkIp(value: string): boolean {
|
||||
if (value === '') {
|
||||
return true;
|
||||
}
|
||||
const reg =
|
||||
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
|
||||
return !reg.test(value) && value !== '';
|
||||
}
|
||||
|
||||
export function checkDomain(value: string): boolean {
|
||||
if (value === '') {
|
||||
return true;
|
||||
}
|
||||
const reg = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
|
||||
return !reg.test(value) && value !== '';
|
||||
}
|
||||
|
||||
export function isDomain(value: string): boolean {
|
||||
if (value === '') {
|
||||
return true;
|
||||
}
|
||||
const reg = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i;
|
||||
return value !== '' && reg.test(value);
|
||||
}
|
||||
|
||||
export function checkIpV4V6(value: string): boolean {
|
||||
if (value === '') {
|
||||
return true;
|
||||
}
|
||||
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
|
||||
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
|
||||
const IPv4AddressRegExp = new RegExp(`^${IPv4AddressFormat}$`);
|
||||
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
|
||||
const IPv6AddressRegExp = new RegExp(
|
||||
'^(' +
|
||||
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
|
||||
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
|
||||
')(%[0-9a-zA-Z-.:]{1,})?$',
|
||||
);
|
||||
return !IPv4AddressRegExp.test(value) && !IPv6AddressRegExp.test(value) && value !== '';
|
||||
}
|
||||
|
||||
export function checkIpV6(value: string): boolean {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
return true;
|
||||
}
|
||||
const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
|
||||
const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
|
||||
const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
|
||||
const IPv6AddressRegExp = new RegExp(
|
||||
'^(' +
|
||||
`(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
|
||||
`(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
|
||||
`(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
|
||||
')(%[0-9a-zA-Z-.:]{1,})?$',
|
||||
);
|
||||
return !IPv6AddressRegExp.test(value) && value !== '';
|
||||
}
|
||||
|
||||
export function checkCidr(value: string): boolean {
|
||||
if (value === '') {
|
||||
return true;
|
||||
}
|
||||
const reg =
|
||||
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\/([0-9]|[1-2][0-9]|3[0-2]))?$/;
|
||||
return !reg.test(value) && value !== '';
|
||||
}
|
||||
|
||||
export function checkCidrV6(value: string): boolean {
|
||||
if (value === '') {
|
||||
return true;
|
||||
}
|
||||
if (checkIpV6(value.split('/')[0])) {
|
||||
return true;
|
||||
}
|
||||
const reg = /^(?:[0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/;
|
||||
return !reg.test(value.split('/')[1]);
|
||||
}
|
||||
|
||||
export function checkPort(value: string): boolean {
|
||||
if (Number(value) <= 0) {
|
||||
return true;
|
||||
}
|
||||
const reg = /^([1-9](\d{0,3}))$|^([1-5]\d{4})$|^(6[0-4]\d{3})$|^(65[0-4]\d{2})$|^(655[0-2]\d)$|^(6553[0-5])$/;
|
||||
return !reg.test(value) && value !== '';
|
||||
}
|
||||
|
||||
export function transferTimeToSecond(item: string): any {
|
||||
if (item.indexOf('s') !== -1) {
|
||||
return Number(item.replaceAll('s', ''));
|
||||
}
|
||||
if (item.indexOf('m') !== -1) {
|
||||
return Number(item.replaceAll('m', '')) * 60;
|
||||
}
|
||||
if (item.indexOf('h') !== -1) {
|
||||
return Number(item.replaceAll('h', '')) * 60 * 60;
|
||||
}
|
||||
if (item.indexOf('d') !== -1) {
|
||||
return Number(item.replaceAll('d', '')) * 60 * 60 * 24;
|
||||
}
|
||||
return Number(item);
|
||||
}
|
||||
|
||||
export function splitTime(item: string): any {
|
||||
if (item.indexOf('s') !== -1) {
|
||||
return { time: Number(item.replaceAll('s', '')), unit: 's' };
|
||||
}
|
||||
if (item.indexOf('m') !== -1) {
|
||||
return { time: Number(item.replaceAll('m', '')), unit: 'm' };
|
||||
}
|
||||
if (item.indexOf('h') !== -1) {
|
||||
return { time: Number(item.replaceAll('h', '')), unit: 'h' };
|
||||
}
|
||||
if (item.indexOf('d') !== -1) {
|
||||
return { time: Number(item.replaceAll('d', '')), unit: 'd' };
|
||||
}
|
||||
if (item.indexOf('y') !== -1) {
|
||||
return { time: Number(item.replaceAll('y', '')), unit: 'y' };
|
||||
}
|
||||
return { time: Number(item), unit: 's' };
|
||||
}
|
||||
|
||||
export function transTimeUnit(val: string): any {
|
||||
if (val.indexOf('s') !== -1) {
|
||||
return val.replaceAll('s', i18n.global.t('commons.units.second'));
|
||||
}
|
||||
if (val.indexOf('m') !== -1) {
|
||||
return val.replaceAll('m', i18n.global.t('commons.units.minute'));
|
||||
}
|
||||
if (val.indexOf('h') !== -1) {
|
||||
return val.replaceAll('h', i18n.global.t('commons.units.hour'));
|
||||
}
|
||||
if (val.indexOf('d') !== -1) {
|
||||
return val.replaceAll('d', i18n.global.t('commons.units.day'));
|
||||
}
|
||||
if (val.indexOf('y') !== -1) {
|
||||
return val.replaceAll('y', i18n.global.t('commons.units.year'));
|
||||
}
|
||||
return val + i18n.global.t('commons.units.second');
|
||||
}
|
||||
|
||||
export function splitTimeFromSecond(item: number): any {
|
||||
if (item < 60) return { timeItem: item, timeUnit: 's' };
|
||||
if (item < 3600) return { timeItem: item / 60, timeUnit: 'm' };
|
||||
return { timeItem: item / 3600, timeUnit: 'h' };
|
||||
}
|
||||
|
||||
export function splitHttp(url: string) {
|
||||
if (url.indexOf('https://') != -1) {
|
||||
return { proto: 'https', url: url.replaceAll('https://', '') };
|
||||
}
|
||||
if (url.indexOf('http://') != -1) {
|
||||
return { proto: 'http', url: url.replaceAll('http://', '') };
|
||||
}
|
||||
return { proto: '', url: url };
|
||||
}
|
||||
|
||||
export function spliceHttp(proto: string, url: string) {
|
||||
return proto + '://' + url.replaceAll('https://', '').replaceAll('http://', '');
|
||||
}
|
||||
|
||||
export function emptyLineFilter(str: string, spilt: string) {
|
||||
let list = str.split(spilt);
|
||||
let results = [];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].trim() !== '') {
|
||||
results.push(list[i]);
|
||||
}
|
||||
}
|
||||
return results.join(spilt);
|
||||
}
|
||||
@@ -109,7 +109,7 @@ import { createAgent, getAgentProviders, pageAgentAccounts } from '@/api/modules
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { getAppByKey, getAppDetail } from '@/api/modules/app';
|
||||
import { getAgentSettingByKey } from '@/api/modules/setting';
|
||||
import { getRandomStr, newUUID } from '@/utils/util';
|
||||
import { getRandomStr, newUUID } from '@/utils/id';
|
||||
import {
|
||||
buildDefaultAllowedOrigin,
|
||||
getAgentProviderDisplayName,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ref } from 'vue';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { checkAgentPlugin, installAgentPlugin, uninstallAgentPlugin, upgradeAgentPlugin } from '@/api/modules/ai';
|
||||
import { newUUID } from '@/utils/util';
|
||||
|
||||
import { newUUID } from '@/utils/id';
|
||||
export const useAgentPluginChannel = (pluginType: AI.AgentPluginInstallReq['type']) => {
|
||||
const agentId = ref(0);
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { loginAgentWeixinChannel } from '@/api/modules/ai';
|
||||
import { newUUID } from '@/utils/util';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { isOpenclawCurrentHTTPVersion } from '@/utils/agent';
|
||||
import PluginInstall from './components/plugin-install.vue';
|
||||
|
||||
@@ -147,7 +147,7 @@ import { AI } from '@/api/interface/ai';
|
||||
import { installAgentSkill, listAgentSkills, searchAgentSkills, updateAgentSkill } from '@/api/modules/ai';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { newUUID } from '@/utils/util';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { isOpenclawCurrentHTTPVersion } from '@/utils/agent';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import VersionSupport from './components/version-support.vue';
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import { deleteAgent } from '@/api/modules/ai';
|
||||
import { newUUID } from '@/utils/util';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
|
||||
const open = ref(false);
|
||||
|
||||
@@ -210,7 +210,8 @@ import { AI } from '@/api/interface/ai';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { SearchWithPage } from '@/api/interface';
|
||||
import { dateFormat, newUUID } from '@/utils/util';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
import AddDialog from '@/views/ai/agents/agent/add/index.vue';
|
||||
|
||||
@@ -57,7 +57,7 @@ import AddDialog from '@/views/ai/agents/model/add/index.vue';
|
||||
import ModelPoolDialog from '@/views/ai/agents/model/pool/index.vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { getAgentProviderDisplayName } from '@/utils/agent';
|
||||
|
||||
const items = ref<AI.AgentAccountItem[]>([]);
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { loadGPUMonitor, getGPUOptions } from '@/api/modules/host';
|
||||
import { dateFormatWithoutYear } from '@/utils/util';
|
||||
import { dateFormatWithoutYear } from '@/utils/date';
|
||||
import RouterMenu from '@/views/ai/gpu/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { shortcuts } from '@/utils/shortcuts';
|
||||
|
||||
@@ -93,7 +93,8 @@ import { listSSL, searchAcmeAccount } from '@/api/modules/website';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { FormInstance, FormRules } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { getAccountName, dateFormatSimple } from '@/utils/util';
|
||||
import { getAccountName } from '@/utils/ssl';
|
||||
import { dateFormatSimple } from '@/utils/date';
|
||||
import { bindMcpDomain, getMcpDomain, updateMcpDomain } from '@/api/modules/ai';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
|
||||
@@ -101,7 +101,7 @@ import { AI } from '@/api/interface/ai';
|
||||
import { deleteMcpServer, operateMcpServer, pageMcpServer } from '@/api/modules/ai';
|
||||
import RouterMenu from '@/views/ai/mcp/index.vue';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import McpServerOperate from './operate/index.vue';
|
||||
import ComposeLogs from '@/components/log/compose/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
@@ -42,8 +42,7 @@ import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { createOllamaModel } from '@/api/modules/ai';
|
||||
import { newUUID } from '@/utils/util';
|
||||
|
||||
import { newUUID } from '@/utils/id';
|
||||
const drawerVisible = ref(false);
|
||||
const form = reactive({
|
||||
name: '',
|
||||
|
||||
@@ -98,7 +98,8 @@ import { listSSL, searchAcmeAccount } from '@/api/modules/website';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { FormInstance, FormRules } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { getAccountName, dateFormatSimple } from '@/utils/util';
|
||||
import { getAccountName } from '@/utils/ssl';
|
||||
import { dateFormatSimple } from '@/utils/date';
|
||||
import { bindDomain, getBindDomain, updateBindDomain } from '@/api/modules/ai';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
|
||||
@@ -193,7 +193,8 @@ import {
|
||||
} from '@/api/modules/ai';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { getAppPort } from '@/api/modules/app';
|
||||
import { dateFormat, newUUID } from '@/utils/util';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { MsgInfo, MsgSuccess } from '@/utils/message';
|
||||
import BindDomain from '@/views/ai/model/ollama/domain/index.vue';
|
||||
import { routerToNameWithQuery } from '@/utils/router';
|
||||
|
||||
@@ -83,7 +83,7 @@ import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||
import PortJump from '@/views/website/runtime/components/port-jump.vue';
|
||||
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { deleteTensorRTLLM, operateTensorRTLLM, pageTensorRTLLM } from '@/api/modules/ai';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
|
||||
@@ -74,7 +74,7 @@ import { searchApp, syncApp, syncCutomAppStore, syncLocalApp, getCurrentNodeCust
|
||||
import Install from '../detail/install/index.vue';
|
||||
import router from '@/routers';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { newUUID } from '@/utils/util';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import Detail from '../detail/index.vue';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import bus from '@/global/bus';
|
||||
@@ -185,7 +185,9 @@ const sync = async () => {
|
||||
} else {
|
||||
res = await syncApp(syncReq);
|
||||
}
|
||||
console.log(res);
|
||||
if (res.message != '' && res.message != 'success') {
|
||||
console.log(res.message);
|
||||
MsgSuccess(res.message);
|
||||
} else {
|
||||
openTaskLog(taskID);
|
||||
|
||||
@@ -154,7 +154,7 @@ import { ref, watch } from 'vue';
|
||||
import Params from '../params/index.vue';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||
import { computeSizeFromMB } from '@/utils/util';
|
||||
import { computeSizeFromMB } from '@/utils/size';
|
||||
import { loadResourceLimit } from '@/api/modules/container';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { loadOptionalComponent } from '@/extensions/optional';
|
||||
|
||||
@@ -78,7 +78,7 @@ import MarkDownEditor from '@/components/mkdown-editor/index.vue';
|
||||
import { getAppByKey, getAppDetail, getAppIconUrl } from '@/api/modules/app';
|
||||
import { ref } from 'vue';
|
||||
import Install from './install/index.vue';
|
||||
import { computeSizeFromMB } from '@/utils/util';
|
||||
import { computeSizeFromMB } from '@/utils/size';
|
||||
import { jumpToInstall } from '@/utils/app';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { currentNode } = useGlobalStore();
|
||||
|
||||
@@ -25,7 +25,7 @@ import { useRouter } from 'vue-router';
|
||||
import AppInstallForm from '@/views/app-store/detail/form/index.vue';
|
||||
import { installApp } from '@/api/modules/app';
|
||||
import { MsgError } from '@/utils/message';
|
||||
import { newUUID } from '@/utils/util';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { routerToName } from '@/utils/router';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import i18n from '@/lang';
|
||||
|
||||
@@ -133,11 +133,11 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { getRandomStr } from '@/utils/util';
|
||||
import { getRandomStr } from '@/utils/id';
|
||||
import { getAppService } from '@/api/modules/app';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { getDBName, getLabel, getDescription } from '@/utils/util';
|
||||
import { getDBName, getLabel, getDescription } from '@/utils/app-store';
|
||||
import { getPathByType } from '@/api/modules/files';
|
||||
import { loadFormatCollations } from '@/api/modules/database';
|
||||
|
||||
|
||||
@@ -73,8 +73,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { App } from '@/api/interface/app';
|
||||
import { getAge, toLink } from '@/utils/util';
|
||||
|
||||
import { getAge } from '@/utils/date';
|
||||
import { toLink } from '@/utils/browser';
|
||||
interface Props {
|
||||
installed: App.AppInstalled;
|
||||
defaultLink: string;
|
||||
|
||||
@@ -187,7 +187,8 @@ import { reactive, ref } from 'vue';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { getLabel, splitHttp, checkIpV4V6, checkDomain } from '@/utils/util';
|
||||
import { getLabel } from '@/utils/app-store';
|
||||
import { splitHttp, checkIpV4V6, checkDomain } from '@/utils/validate';
|
||||
import i18n from '@/lang';
|
||||
import { loadResourceLimit } from '@/api/modules/container';
|
||||
import { Container } from '@/api/interface/container';
|
||||
|
||||
@@ -154,7 +154,7 @@ import Sortable from 'sortablejs';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { jumpToPath } from '@/utils/util';
|
||||
import { jumpToPath } from '@/utils/router';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { getAgentSettingByKey } from '@/api/modules/setting';
|
||||
|
||||
@@ -491,7 +491,8 @@ import i18n from '@/lang';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import { routerToFileWithPath } from '@/utils/router';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { computeCPU, computeSize2, computeSizeForDocker, newUUID } from '@/utils/util';
|
||||
import { computeCPU, computeSize2, computeSizeForDocker } from '@/utils/size';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { loadBaseDir } from '@/api/modules/setting';
|
||||
import { ElCheckbox, ElForm } from 'element-plus';
|
||||
|
||||
@@ -53,8 +53,7 @@ import i18n from '@/lang';
|
||||
import { commitContainer } from '@/api/modules/container';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { newUUID } from '@/utils/util';
|
||||
|
||||
import { newUUID } from '@/utils/id';
|
||||
const drawerVisible = ref<boolean>(false);
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -124,8 +124,7 @@ import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
import { ElInput, ElMessageBox, UploadFile, UploadInstance } from 'element-plus';
|
||||
import { Document, FolderOpened, HomeFilled } from '@element-plus/icons-vue';
|
||||
import { computeSize2 } from '@/utils/util';
|
||||
|
||||
import { computeSize2 } from '@/utils/size';
|
||||
const visible = ref(false);
|
||||
const title = ref('');
|
||||
const containerID = ref('');
|
||||
|
||||
@@ -418,7 +418,8 @@ import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { routerToName, routerToNameWithQuery } from '@/utils/router';
|
||||
import router from '@/routers';
|
||||
import { computeSize2, computeSizeForDocker, computeCPU, newUUID } from '@/utils/util';
|
||||
import { computeSize2, computeSizeForDocker, computeCPU } from '@/utils/size';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { updateCommonDescription } from '@/api/modules/setting';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
import { ref } from 'vue';
|
||||
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
|
||||
import { routerToFileWithPath } from '@/utils/router';
|
||||
import { copyText } from '@/utils/util';
|
||||
import { copyText } from '@/utils/clipboard';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const visible = ref(false);
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { containerStats } from '@/api/modules/container';
|
||||
import { dateFormatForSecond } from '@/utils/util';
|
||||
import { dateFormatForSecond } from '@/utils/date';
|
||||
import VCharts from '@/components/v-charts/index.vue';
|
||||
import i18n from '@/lang';
|
||||
|
||||
|
||||
@@ -357,7 +357,8 @@ import {
|
||||
import { Container } from '@/api/interface/container';
|
||||
import { MsgError } from '@/utils/message';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { checkIpV4V6, checkPort, newUUID } from '@/utils/util';
|
||||
import { checkIpV4V6, checkPort } from '@/utils/validate';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import router from '@/routers';
|
||||
import TerminalDialog from '@/views/host/file-management/terminal/index.vue';
|
||||
import { routerToName, routerToNameWithQuery } from '@/utils/router';
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
import { containerPrune } from '@/api/modules/container';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { ref } from 'vue';
|
||||
import { newUUID } from '@/utils/util';
|
||||
|
||||
import { newUUID } from '@/utils/id';
|
||||
const loading = ref(false);
|
||||
const open = ref<boolean>(false);
|
||||
const taskLogRef = ref();
|
||||
|
||||
@@ -69,7 +69,7 @@ import { Rules } from '@/global/form-rules';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { newUUID } from '@/utils/util';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { CheckboxValueType, ElForm } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
|
||||
@@ -197,7 +197,8 @@
|
||||
import { containerItemStats, containerPrune, loadContainerStatus, loadDaemonJson } from '@/api/modules/container';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
import { getSettingInfo } from '@/api/modules/setting';
|
||||
import { computeSize2, newUUID } from '@/utils/util';
|
||||
import { computeSize2 } from '@/utils/size';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { routerToName } from '@/utils/router';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
|
||||
@@ -60,7 +60,7 @@ import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { imageBuild } from '@/api/modules/container';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { newUUID } from '@/utils/util';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
|
||||
@@ -157,7 +157,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import { dateFormat, newUUID, computeSize2 } from '@/utils/util';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { computeSize2 } from '@/utils/size';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import Pull from '@/views/container/image/pull/index.vue';
|
||||
import Tag from '@/views/container/image/tag/index.vue';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user