feat: lighten ai agent admin frontend
This commit is contained in:
@@ -1,26 +1,18 @@
|
||||
export * from './announcement';
|
||||
export * from './api-token';
|
||||
export * from './application';
|
||||
export * from './auth';
|
||||
export * from './data-source';
|
||||
export * from './database-connection';
|
||||
export * from './database-manager';
|
||||
export * from './dept';
|
||||
export * from './device';
|
||||
export * from './dict';
|
||||
export * from './file';
|
||||
export * from './field-permission';
|
||||
export * from './menu';
|
||||
export * from './message';
|
||||
export * from './org-chart';
|
||||
export * from './oauth';
|
||||
export * from './permission';
|
||||
export * from './post';
|
||||
export * from './role';
|
||||
export * from './resource-scope';
|
||||
export * from './system-config';
|
||||
export * from './server-health';
|
||||
export * from './server-monitor';
|
||||
export * from './ui-config';
|
||||
export * from './user';
|
||||
export * from './websocket';
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardConfig } from './types';
|
||||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElEmpty } from 'element-plus';
|
||||
|
||||
import WidgetRenderer from './components/WidgetRenderer.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
config: DashboardConfig | string;
|
||||
}>();
|
||||
|
||||
const dashboardConfig = ref<DashboardConfig | null>(null);
|
||||
|
||||
const parseConfig = () => {
|
||||
if (!props.config) {
|
||||
dashboardConfig.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof props.config === 'string') {
|
||||
try {
|
||||
dashboardConfig.value = JSON.parse(props.config);
|
||||
} catch {
|
||||
console.error('Invalid dashboard config JSON');
|
||||
dashboardConfig.value = null;
|
||||
}
|
||||
} else {
|
||||
dashboardConfig.value = props.config;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(parseConfig);
|
||||
watch(() => props.config, parseConfig);
|
||||
|
||||
const layout = computed(() => {
|
||||
if (!dashboardConfig.value) return [];
|
||||
const columns = dashboardConfig.value.columns || 12;
|
||||
return dashboardConfig.value.widgets.map((w) => ({
|
||||
...w,
|
||||
columnEnd: `span ${Math.min(w.w, columns)}`,
|
||||
columnStart: Math.max(1, w.x + 1),
|
||||
i: w.i,
|
||||
minHeight: `${w.h * dashboardConfig.value!.rowHeight}px`,
|
||||
rowEnd: `span ${w.h}`,
|
||||
rowStart: Math.max(1, w.y + 1),
|
||||
}));
|
||||
});
|
||||
|
||||
const getWidget = (i: string) => {
|
||||
if (!dashboardConfig.value) return null;
|
||||
return dashboardConfig.value.widgets.find((w) => w.i === i);
|
||||
};
|
||||
|
||||
const getAnimationDelay = (i: string) => {
|
||||
if (!dashboardConfig.value) return 0;
|
||||
const index = dashboardConfig.value.widgets.findIndex((w) => w.i === i);
|
||||
return index * 80;
|
||||
};
|
||||
|
||||
const outerMarginStyle = computed(() => {
|
||||
const config = dashboardConfig.value;
|
||||
if (!config || config.showOuterMargin !== false) return {};
|
||||
const margin = config.margin || [12, 12];
|
||||
return {
|
||||
marginLeft: `-${margin[0]}px`,
|
||||
marginRight: `-${margin[0]}px`,
|
||||
marginTop: `-${margin[1]}px`,
|
||||
width: `calc(100% + ${margin[0] * 2}px)`,
|
||||
};
|
||||
});
|
||||
|
||||
const gridStyle = computed(() => {
|
||||
const config = dashboardConfig.value;
|
||||
if (!config) return {};
|
||||
const margin = config.margin || [12, 12];
|
||||
return {
|
||||
...outerMarginStyle.value,
|
||||
display: 'grid',
|
||||
gap: `${margin[1]}px ${margin[0]}px`,
|
||||
gridAutoFlow: 'dense',
|
||||
gridAutoRows: `${config.rowHeight}px`,
|
||||
gridTemplateColumns: `repeat(${config.columns || 12}, minmax(0, 1fr))`,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="dashboard-renderer h-full"
|
||||
:style="
|
||||
dashboardConfig?.backgroundColor?.includes('gradient')
|
||||
? { background: dashboardConfig.backgroundColor }
|
||||
: { backgroundColor: dashboardConfig?.backgroundColor || '' }
|
||||
"
|
||||
>
|
||||
<div v-if="dashboardConfig && layout.length > 0">
|
||||
<div :style="gridStyle">
|
||||
<div
|
||||
v-for="item in layout"
|
||||
:key="item.i"
|
||||
class="dashboard-widget"
|
||||
:style="{
|
||||
gridColumnEnd: item.columnEnd,
|
||||
gridColumnStart: item.columnStart,
|
||||
gridRowEnd: item.rowEnd,
|
||||
gridRowStart: item.rowStart,
|
||||
minHeight: item.minHeight,
|
||||
}"
|
||||
>
|
||||
<WidgetRenderer
|
||||
v-if="getWidget(item.i)"
|
||||
:widget="getWidget(item.i)!"
|
||||
:is-design-mode="false"
|
||||
:animation-delay="getAnimationDelay(item.i)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ElEmpty
|
||||
v-else
|
||||
:description="$t('dashboard-design.noConfigTip')"
|
||||
class="py-20"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-renderer {
|
||||
background-color: var(--el-bg-color-page);
|
||||
}
|
||||
|
||||
.dashboard-widget {
|
||||
overflow: hidden;
|
||||
background: var(--el-bg-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
|
||||
}
|
||||
</style>
|
||||
@@ -1,319 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
DashboardWidget,
|
||||
WidgetStyle,
|
||||
} from '../types';
|
||||
|
||||
import {
|
||||
computed,
|
||||
defineAsyncComponent,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { Loader2 } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { useDashboardRuntime } from '../runtime';
|
||||
import { createRefreshTimer, fetchWidgetData } from '../utils/dataFetcher';
|
||||
|
||||
const props = defineProps<{
|
||||
animationDelay?: number; // 入场动画延迟(毫秒)
|
||||
isDesignMode?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
// 入场动画状态
|
||||
const isEntered = ref(false);
|
||||
// 数据更新动画状态
|
||||
const isUpdating = ref(false);
|
||||
|
||||
// 组件映射
|
||||
const widgetComponents: Record<
|
||||
string,
|
||||
ReturnType<typeof defineAsyncComponent>
|
||||
> = {
|
||||
'announcement-list': defineAsyncComponent(
|
||||
() => import('./widgets/AnnouncementList.vue'),
|
||||
),
|
||||
'notice-list': defineAsyncComponent(() => import('./widgets/NoticeList.vue')),
|
||||
'quick-links': defineAsyncComponent(() => import('./widgets/QuickLinks.vue')),
|
||||
'server-monitor': defineAsyncComponent(
|
||||
() => import('./widgets/ServerMonitor.vue'),
|
||||
),
|
||||
weather: defineAsyncComponent(() => import('./widgets/WeatherWidget.vue')),
|
||||
'welcome-card': defineAsyncComponent(
|
||||
() => import('./widgets/WelcomeCard.vue'),
|
||||
),
|
||||
};
|
||||
|
||||
const defaultWidgetStyle: WidgetStyle = {
|
||||
backgroundColor: '',
|
||||
borderWidth: 0,
|
||||
borderColor: '',
|
||||
borderStyle: 'solid',
|
||||
borderRadius: 8,
|
||||
shadowEnabled: true,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 4,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 1,
|
||||
padding: 16,
|
||||
titleShow: true,
|
||||
titleFontSize: 14,
|
||||
titleColor: '',
|
||||
titleAlign: 'left',
|
||||
titleFontWeight: 'normal',
|
||||
};
|
||||
|
||||
const { globalParams, globalParamsVersion } = useDashboardRuntime();
|
||||
|
||||
const currentComponent = computed(() => {
|
||||
return widgetComponents[props.widget.type];
|
||||
});
|
||||
|
||||
// 从 paramBindings 解析出实际参数值
|
||||
function resolveParams(): Record<string, any> | undefined {
|
||||
const bindings = props.widget.dataSource?.paramBindings;
|
||||
if (!bindings || bindings.length === 0) return undefined;
|
||||
const params: Record<string, any> = {};
|
||||
for (const b of bindings) {
|
||||
if (b.paramName && b.globalKey) {
|
||||
const val = globalParams.value[b.globalKey];
|
||||
if (val !== undefined && val !== '') {
|
||||
params[b.paramName] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(params).length > 0 ? params : undefined;
|
||||
}
|
||||
|
||||
// 动态数据
|
||||
const dynamicProps = ref<Record<string, any>>({});
|
||||
const isLoading = ref(false);
|
||||
const hasError = ref(false);
|
||||
|
||||
// 合并后的 widget(静态 props + 动态 props)
|
||||
const mergedWidget = computed(() => {
|
||||
return {
|
||||
...props.widget,
|
||||
props: {
|
||||
...props.widget.props,
|
||||
...dynamicProps.value,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// 计算样式(只应用容器级别的样式,不影响组件内部)
|
||||
const widgetStyle = computed(() => {
|
||||
const style = { ...defaultWidgetStyle, ...props.widget.style };
|
||||
const css: Record<string, string> = {};
|
||||
|
||||
// 背景(默认使用主题背景色,支持渐变色)
|
||||
const bgValue = style.backgroundColor || 'var(--el-bg-color)';
|
||||
if (bgValue.includes('gradient')) {
|
||||
css.background = bgValue;
|
||||
} else {
|
||||
css.backgroundColor = bgValue;
|
||||
}
|
||||
|
||||
// 边框
|
||||
if (style.borderWidth && style.borderWidth > 0) {
|
||||
css.borderWidth = `${style.borderWidth}px`;
|
||||
css.borderStyle = style.borderStyle || 'solid';
|
||||
css.borderColor = style.borderColor || 'var(--el-border-color)';
|
||||
}
|
||||
|
||||
// 圆角
|
||||
if (style.borderRadius !== undefined) {
|
||||
css.borderRadius = `${style.borderRadius}px`;
|
||||
}
|
||||
|
||||
// 阴影
|
||||
if (style.shadowEnabled) {
|
||||
const color = style.shadowColor || 'rgba(0, 0, 0, 0.1)';
|
||||
const blur = style.shadowBlur || 4;
|
||||
const x = style.shadowOffsetX || 0;
|
||||
const y = style.shadowOffsetY || 1;
|
||||
css.boxShadow = `${x}px ${y}px ${blur}px ${color}`;
|
||||
} else {
|
||||
css.boxShadow = 'none';
|
||||
}
|
||||
|
||||
return css;
|
||||
});
|
||||
|
||||
// 加载数据
|
||||
const loadData = async () => {
|
||||
if (!props.widget.dataSource || props.widget.dataSource.type === 'static') {
|
||||
dynamicProps.value = {};
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
hasError.value = false;
|
||||
|
||||
try {
|
||||
const params = resolveParams();
|
||||
const result = await fetchWidgetData(
|
||||
props.widget.dataSource,
|
||||
props.widget.props,
|
||||
params,
|
||||
);
|
||||
|
||||
// 数据更新动画
|
||||
if (isEntered.value && Object.keys(dynamicProps.value).length > 0) {
|
||||
isUpdating.value = true;
|
||||
setTimeout(() => {
|
||||
isUpdating.value = false;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
dynamicProps.value = result.props;
|
||||
} catch (error) {
|
||||
console.error('[WidgetRenderer] loadData error', error);
|
||||
hasError.value = true;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 刷新定时器清理函数
|
||||
let cleanupTimer: (() => void) | null = null;
|
||||
|
||||
// 设置刷新定时器
|
||||
const setupRefreshTimer = () => {
|
||||
if (cleanupTimer) {
|
||||
cleanupTimer();
|
||||
cleanupTimer = null;
|
||||
}
|
||||
|
||||
// 支持 api 和 dataSource 类型的自动刷新
|
||||
const dsType = props.widget.dataSource?.type;
|
||||
if (!props.isDesignMode && (dsType === 'api' || dsType === 'dataSource')) {
|
||||
cleanupTimer = createRefreshTimer(props.widget.dataSource, loadData);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听数据源变化
|
||||
watch(
|
||||
() => props.widget.dataSource,
|
||||
() => {
|
||||
loadData();
|
||||
setupRefreshTimer();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// 监听全局筛选参数变化,有 paramBindings 的组件重新加载数据
|
||||
watch(
|
||||
() => globalParamsVersion.value,
|
||||
() => {
|
||||
if (props.isDesignMode) return;
|
||||
const bindings = props.widget.dataSource?.paramBindings;
|
||||
if (bindings && bindings.length > 0) {
|
||||
loadData();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
setupRefreshTimer();
|
||||
|
||||
// 入场动画
|
||||
const delay = props.animationDelay || 0;
|
||||
setTimeout(() => {
|
||||
isEntered.value = true;
|
||||
}, delay);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (cleanupTimer) {
|
||||
cleanupTimer();
|
||||
}
|
||||
});
|
||||
|
||||
// 动画类名
|
||||
const animationClass = computed(() => {
|
||||
return {
|
||||
'widget-enter': true,
|
||||
'widget-entered': isEntered.value,
|
||||
'widget-updating': isUpdating.value,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="widget-renderer relative h-full w-full overflow-hidden"
|
||||
:class="animationClass"
|
||||
:style="widgetStyle"
|
||||
>
|
||||
<!-- 加载状态 -->
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="absolute inset-0 z-10 flex items-center justify-center bg-white/50"
|
||||
>
|
||||
<Loader2 class="text-primary h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-if="hasError && !isLoading" class="absolute right-2 top-2 z-10">
|
||||
<span class="text-xs text-red-500">{{
|
||||
$t('dashboard-design.loadDataError')
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<!-- 组件内容 -->
|
||||
<component
|
||||
:is="currentComponent"
|
||||
v-if="currentComponent"
|
||||
:widget="mergedWidget"
|
||||
:is-design-mode="isDesignMode"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="flex h-full w-full items-center justify-center text-gray-400"
|
||||
>
|
||||
{{ $t('dashboard-design.unknownWidget') }}: {{ widget.type }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@keyframes pulse-update {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 0 0 2px var(--el-color-primary-light-5);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.widget-enter {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.95);
|
||||
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.widget-entered {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* 数据更新动画 */
|
||||
.widget-updating {
|
||||
animation: pulse-update 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* 入场动画 */
|
||||
</style>
|
||||
-298
@@ -1,298 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import type { UserAnnouncement } from '#/api/core/announcement';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { CheckCheck, Megaphone } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElButton, ElEmpty, ElScrollbar, ElTag } from 'element-plus';
|
||||
|
||||
import {
|
||||
getUserAnnouncementDetailApi,
|
||||
getUserAnnouncementListApi,
|
||||
} from '#/api/core/announcement';
|
||||
import { ZqDialog } from '#/components/zq-dialog';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
// 实际公告数据
|
||||
const announcements = ref<UserAnnouncement[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
// 详情弹窗
|
||||
const detailVisible = ref(false);
|
||||
const currentAnnouncement = ref<null | UserAnnouncement>(null);
|
||||
const detailLoading = ref(false);
|
||||
|
||||
// 未读数量
|
||||
const unreadCount = computed(
|
||||
() => announcements.value.filter((a) => !a.is_read).length,
|
||||
);
|
||||
|
||||
// 获取优先级配置
|
||||
const getPriorityConfig = (priority: number) => {
|
||||
switch (priority) {
|
||||
case 1: {
|
||||
return {
|
||||
type: 'warning' as const,
|
||||
label: $t('dashboard-design.widgets.announcement.priority.important'),
|
||||
};
|
||||
}
|
||||
case 2: {
|
||||
return {
|
||||
type: 'danger' as const,
|
||||
label: $t('dashboard-design.widgets.announcement.priority.urgent'),
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return {
|
||||
type: 'info' as const,
|
||||
label: $t('dashboard-design.widgets.announcement.priority.normal'),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (dateStr?: string) => {
|
||||
if (!dateStr) return '';
|
||||
// 处理 "2025-12-01 21:47:29" 格式,替换空格为T以兼容所有浏览器
|
||||
const date = new Date(dateStr.replace(' ', 'T'));
|
||||
if (isNaN(date.getTime())) return dateStr;
|
||||
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const minutes = Math.floor(diff / 60_000);
|
||||
const hours = Math.floor(diff / 3_600_000);
|
||||
const days = Math.floor(diff / 86_400_000);
|
||||
|
||||
if (minutes < 1)
|
||||
return $t('dashboard-design.widgets.announcement.time.justNow');
|
||||
if (minutes < 60)
|
||||
return `${minutes}${$t('dashboard-design.widgets.announcement.time.minutesAgo')}`;
|
||||
if (hours < 24)
|
||||
return `${hours}${$t('dashboard-design.widgets.announcement.time.hoursAgo')}`;
|
||||
if (days < 7)
|
||||
return `${days}${$t('dashboard-design.widgets.announcement.time.daysAgo')}`;
|
||||
return date.toLocaleDateString();
|
||||
};
|
||||
|
||||
// 加载公告数据
|
||||
const loadAnnouncements = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const limit = props.widget.props.limit || 5;
|
||||
const res = await getUserAnnouncementListApi({ page: 1, pageSize: limit });
|
||||
announcements.value = res.items || [];
|
||||
} catch (error) {
|
||||
console.error('Failed to load announcements:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 查看公告详情(会自动标记已读)
|
||||
const viewDetail = async (item: UserAnnouncement) => {
|
||||
detailVisible.value = true;
|
||||
detailLoading.value = true;
|
||||
|
||||
try {
|
||||
// 获取详情会自动标记已读
|
||||
const detail = await getUserAnnouncementDetailApi(item.id);
|
||||
currentAnnouncement.value = detail;
|
||||
// 更新列表中的已读状态
|
||||
item.is_read = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to load announcement detail:', error);
|
||||
currentAnnouncement.value = item;
|
||||
} finally {
|
||||
detailLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 全部已读(逐个标记)
|
||||
const markAllRead = async () => {
|
||||
if (unreadCount.value === 0) return;
|
||||
|
||||
const unreadItems = announcements.value.filter((a) => !a.is_read);
|
||||
for (const item of unreadItems) {
|
||||
try {
|
||||
await getUserAnnouncementDetailApi(item.id);
|
||||
item.is_read = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to mark as read:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadAnnouncements();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="announcement-list flex h-full flex-col p-3">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Megaphone class="text-muted-foreground h-4 w-4" />
|
||||
<span class="text-muted-foreground text-sm font-medium">{{
|
||||
widget.props.title
|
||||
}}</span>
|
||||
<ElTag v-if="unreadCount > 0" type="danger" size="small" round>
|
||||
{{ unreadCount }}
|
||||
</ElTag>
|
||||
</div>
|
||||
<ElButton
|
||||
v-if="unreadCount > 0"
|
||||
type="primary"
|
||||
text
|
||||
size="small"
|
||||
@click="markAllRead"
|
||||
>
|
||||
<CheckCheck class="mr-1 h-3.5 w-3.5" />
|
||||
{{ $t('dashboard-design.widgets.announcement.markAllRead') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
<ElScrollbar class="flex-1">
|
||||
<div v-if="announcements.length > 0" class="space-y-2">
|
||||
<div
|
||||
v-for="item in announcements"
|
||||
:key="item.id"
|
||||
class="cursor-pointer rounded-md p-2 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
:class="{ 'opacity-60': item.is_read }"
|
||||
@click="viewDetail(item)"
|
||||
>
|
||||
<div class="flex items-start gap-2">
|
||||
<ElTag
|
||||
v-if="item.is_top"
|
||||
type="danger"
|
||||
size="small"
|
||||
effect="dark"
|
||||
class="flex-shrink-0"
|
||||
>
|
||||
{{ $t('dashboard-design.widgets.announcement.top') }}
|
||||
</ElTag>
|
||||
<ElTag
|
||||
:type="getPriorityConfig(item.priority).type"
|
||||
size="small"
|
||||
class="flex-shrink-0"
|
||||
>
|
||||
{{ getPriorityConfig(item.priority).label }}
|
||||
</ElTag>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm font-medium">{{ item.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.summary"
|
||||
class="text-muted-foreground mt-1 line-clamp-2 text-xs"
|
||||
>
|
||||
{{ item.summary }}
|
||||
</div>
|
||||
<div
|
||||
class="text-muted-foreground mt-1 flex items-center justify-between text-xs"
|
||||
>
|
||||
<span>{{ item.publisher_name }}</span>
|
||||
<span>{{ formatTime(item.publish_time) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ElEmpty
|
||||
v-else
|
||||
:description="$t('dashboard-design.widgets.announcement.noData')"
|
||||
:image-size="60"
|
||||
/>
|
||||
</ElScrollbar>
|
||||
|
||||
<!-- 公告详情弹窗 -->
|
||||
<ZqDialog
|
||||
v-model="detailVisible"
|
||||
:title="currentAnnouncement?.title"
|
||||
width="800px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
>
|
||||
<div class="min-h-[500px]" v-loading="detailLoading">
|
||||
<!-- <div v-if="detailLoading" class="py-8 text-center text-gray-400">
|
||||
{{ $t('dashboard-design.widgets.announcement.loading') }}
|
||||
</div> -->
|
||||
<div v-if="currentAnnouncement" class="space-y-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<ElTag
|
||||
v-if="currentAnnouncement.is_top"
|
||||
type="danger"
|
||||
size="small"
|
||||
effect="dark"
|
||||
>
|
||||
{{ $t('dashboard-design.widgets.announcement.top') }}
|
||||
</ElTag>
|
||||
<ElTag
|
||||
:type="getPriorityConfig(currentAnnouncement.priority).type"
|
||||
size="small"
|
||||
>
|
||||
{{ getPriorityConfig(currentAnnouncement.priority).label }}
|
||||
</ElTag>
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
formatTime(currentAnnouncement.publish_time)
|
||||
}}</span>
|
||||
</div>
|
||||
<!-- 摘要 -->
|
||||
<div
|
||||
v-if="currentAnnouncement.summary"
|
||||
class="border-l-4 border-[var(--el-color-primary)] bg-[var(--el-fill-color-light)] py-3 pl-4 pr-3 text-sm text-[var(--el-text-color-regular)]"
|
||||
>
|
||||
{{ currentAnnouncement.summary }}
|
||||
</div>
|
||||
<!-- 富文本内容 -->
|
||||
<div
|
||||
class="announcement-content prose max-w-none"
|
||||
v-html="
|
||||
currentAnnouncement.content ||
|
||||
$t('dashboard-design.widgets.announcement.noContent')
|
||||
"
|
||||
></div>
|
||||
<div class="text-muted-foreground text-xs">
|
||||
{{ $t('dashboard-design.widgets.announcement.publisher')
|
||||
}}{{ currentAnnouncement.publisher_name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ZqDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景色由 WidgetRenderer 控制 */
|
||||
|
||||
.announcement-content :deep(img) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.announcement-content :deep(table) {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.announcement-content :deep(td),
|
||||
.announcement-content :deep(th) {
|
||||
border: 1px solid var(--el-border-color);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.announcement-content :deep(blockquote) {
|
||||
border-left: 4px solid var(--el-border-color);
|
||||
padding-left: 16px;
|
||||
margin: 8px 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.announcement-content :deep(a) {
|
||||
color: var(--el-color-primary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '#/components/dashboard-design';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import {
|
||||
ChevronRight,
|
||||
ClipboardCheck,
|
||||
ClipboardList,
|
||||
FilePen,
|
||||
Play,
|
||||
Send,
|
||||
UserCheck,
|
||||
} from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
// 审批中心菜单项
|
||||
const menuItems = computed(() => [
|
||||
{
|
||||
key: 'initiated',
|
||||
title: $t('dashboard-design.widgets.approvalCenter.initiated'),
|
||||
icon: Send,
|
||||
color: 'rgba(59, 130, 246, 0.85)',
|
||||
path: '/workflow/initiated',
|
||||
},
|
||||
{
|
||||
key: 'pending',
|
||||
title: $t('dashboard-design.widgets.approvalCenter.pending'),
|
||||
icon: ClipboardList,
|
||||
color: 'rgba(245, 158, 11, 0.85)',
|
||||
path: '/workflow/pending',
|
||||
},
|
||||
{
|
||||
key: 'handling',
|
||||
title: $t('dashboard-design.widgets.approvalCenter.handling'),
|
||||
icon: FilePen,
|
||||
color: 'rgba(139, 92, 246, 0.85)',
|
||||
path: '/workflow/pending',
|
||||
},
|
||||
{
|
||||
key: 'handled',
|
||||
title: $t('dashboard-design.widgets.approvalCenter.handled'),
|
||||
icon: ClipboardCheck,
|
||||
color: 'rgba(14, 165, 233, 0.85)',
|
||||
path: '/workflow/handled',
|
||||
},
|
||||
{
|
||||
key: 'copy',
|
||||
title: $t('dashboard-design.widgets.approvalCenter.copy'),
|
||||
icon: UserCheck,
|
||||
color: 'rgba(249, 115, 22, 0.85)',
|
||||
path: '/workflow/copy',
|
||||
},
|
||||
{
|
||||
key: 'start',
|
||||
title: $t('dashboard-design.widgets.approvalCenter.start'),
|
||||
icon: Play,
|
||||
color: 'rgba(20, 184, 166, 0.85)',
|
||||
path: '/workflow/start',
|
||||
},
|
||||
]);
|
||||
|
||||
// 路由前缀
|
||||
const routePrefix = computed(
|
||||
() => props.widget.props.routePrefix || '/app/workflow_center',
|
||||
);
|
||||
|
||||
// 点击菜单项
|
||||
const handleClick = (item: { path: string }) => {
|
||||
window.open(`${routePrefix.value}${item.path}`, '_blank');
|
||||
};
|
||||
|
||||
// 点击更多
|
||||
const handleMore = () => {
|
||||
window.open(`${routePrefix.value}/workflow/pending`, '_blank');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="approval-center flex h-full flex-col p-4">
|
||||
<!-- 头部 -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<span class="text-sm font-medium">{{ widget.props.title }}</span>
|
||||
<button
|
||||
v-if="widget.props.showMore"
|
||||
type="button"
|
||||
class="text-muted-foreground hover:text-primary flex items-center gap-0.5 text-xs transition-colors"
|
||||
@click="handleMore"
|
||||
>
|
||||
{{ $t('dashboard-design.widgets.approvalCenter.more') }}
|
||||
<ChevronRight class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<!-- 菜单网格 -->
|
||||
<div class="flex flex-1 items-center justify-around gap-2">
|
||||
<div
|
||||
v-for="item in menuItems"
|
||||
:key="item.key"
|
||||
class="flex cursor-pointer flex-col items-center gap-2 transition-transform hover:scale-105"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<div
|
||||
class="flex h-11 w-11 items-center justify-center rounded-full"
|
||||
:style="{ backgroundColor: item.color }"
|
||||
>
|
||||
<component :is="item.icon" class="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<span class="text-muted-foreground whitespace-nowrap text-xs">{{
|
||||
item.title
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Calendar } from '@vben/icons';
|
||||
|
||||
import { ElCalendar } from 'element-plus';
|
||||
|
||||
defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const selectedDate = ref(new Date());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="calendar-widget flex h-full flex-col p-3">
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<Calendar class="text-muted-foreground h-4 w-4" />
|
||||
<span class="text-muted-foreground text-sm font-medium">{{
|
||||
widget.props.title
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="min-h-0 flex-1 overflow-hidden">
|
||||
<ElCalendar v-model="selectedDate" class="compact-calendar" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景色由 WidgetRenderer 控制 */
|
||||
|
||||
.compact-calendar {
|
||||
--el-calendar-border: none;
|
||||
}
|
||||
|
||||
:deep(.el-calendar) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-calendar__header) {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
:deep(.el-calendar__body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-calendar-table thead th) {
|
||||
padding: 4px 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-calendar-table .el-calendar-day) {
|
||||
height: 32px;
|
||||
padding: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-calendar-table td.is-selected .el-calendar-day) {
|
||||
color: white;
|
||||
background-color: var(--el-color-primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-calendar-table td.is-today .el-calendar-day) {
|
||||
font-weight: bold;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -1,125 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const locale = computed(() => preferences.app.locale);
|
||||
|
||||
const currentTime = ref('');
|
||||
const currentDate = ref('');
|
||||
|
||||
let timer: null | ReturnType<typeof setInterval> = null;
|
||||
|
||||
const updateTime = () => {
|
||||
const now = new Date();
|
||||
|
||||
// 处理时区
|
||||
const date = now;
|
||||
if (props.widget.props.timezone && props.widget.props.timezone !== 'local') {
|
||||
try {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
timeZone: props.widget.props.timezone,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: props.widget.props.showSeconds ? '2-digit' : undefined,
|
||||
hour12: !props.widget.props.format24,
|
||||
};
|
||||
currentTime.value = now.toLocaleTimeString(locale.value, options);
|
||||
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
timeZone: props.widget.props.timezone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
weekday: 'short',
|
||||
};
|
||||
currentDate.value = now.toLocaleDateString(locale.value, dateOptions);
|
||||
return;
|
||||
} catch {
|
||||
// 时区无效,使用本地时间
|
||||
}
|
||||
}
|
||||
|
||||
// 本地时间
|
||||
const hours = props.widget.props.format24
|
||||
? String(date.getHours()).padStart(2, '0')
|
||||
: String(date.getHours() % 12 || 12).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
const ampm = props.widget.props.format24
|
||||
? ''
|
||||
: (date.getHours() >= 12
|
||||
? ' PM'
|
||||
: ' AM');
|
||||
|
||||
currentTime.value = props.widget.props.showSeconds
|
||||
? `${hours}:${minutes}:${seconds}${ampm}`
|
||||
: `${hours}:${minutes}${ampm}`;
|
||||
|
||||
const weekdays = $t(
|
||||
'dashboard-design.widgets.clock.weekdays',
|
||||
) as unknown as string[];
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const weekday = weekdays[date.getDay()];
|
||||
|
||||
const yearSuffix = $t('dashboard-design.widgets.clock.year');
|
||||
const monthSuffix = $t('dashboard-design.widgets.clock.month');
|
||||
const daySuffix = $t('dashboard-design.widgets.clock.day');
|
||||
|
||||
currentDate.value = `${year}${yearSuffix}${month}${monthSuffix}${day}${daySuffix} ${weekday}`;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
updateTime();
|
||||
timer = setInterval(updateTime, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="clock-widget flex h-full flex-col items-center justify-center p-3"
|
||||
>
|
||||
<div v-if="widget.props.title" class="text-muted-foreground mb-2 text-sm">
|
||||
{{ widget.props.title }}
|
||||
</div>
|
||||
|
||||
<div class="time-display">
|
||||
{{ currentTime }}
|
||||
</div>
|
||||
|
||||
<div v-if="widget.props.showDate" class="date-display">
|
||||
{{ currentDate }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.time-display {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.date-display {
|
||||
margin-top: 8px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
// 剩余时间
|
||||
const remainingTime = ref({
|
||||
days: 0,
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
finished: false,
|
||||
});
|
||||
|
||||
let timer: null | ReturnType<typeof setInterval> = null;
|
||||
|
||||
const calculateRemaining = () => {
|
||||
const targetTime = new Date(props.widget.props.targetTime).getTime();
|
||||
const now = Date.now();
|
||||
const diff = targetTime - now;
|
||||
|
||||
if (diff <= 0) {
|
||||
remainingTime.value = {
|
||||
days: 0,
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
finished: true,
|
||||
};
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
||||
|
||||
remainingTime.value = { days, hours, minutes, seconds, finished: false };
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
calculateRemaining();
|
||||
timer = setInterval(calculateRemaining, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
});
|
||||
|
||||
const padZero = (num: number) => String(num).padStart(2, '0');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="countdown-widget flex h-full flex-col p-3">
|
||||
<div
|
||||
v-if="widget.props.title"
|
||||
class="text-muted-foreground mb-2 text-sm font-medium"
|
||||
>
|
||||
{{ widget.props.title }}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 items-center justify-center">
|
||||
<template v-if="!remainingTime.finished">
|
||||
<div class="flex items-center gap-2">
|
||||
<template v-if="widget.props.showDays">
|
||||
<div class="time-block">
|
||||
<div class="time-value">{{ remainingTime.days }}</div>
|
||||
<div class="time-label">
|
||||
{{ $t('dashboard-design.widgets.countdown.day') }}
|
||||
</div>
|
||||
</div>
|
||||
<span class="time-separator">:</span>
|
||||
</template>
|
||||
|
||||
<template v-if="widget.props.showHours">
|
||||
<div class="time-block">
|
||||
<div class="time-value">{{ padZero(remainingTime.hours) }}</div>
|
||||
<div class="time-label">
|
||||
{{ $t('dashboard-design.widgets.countdown.hour') }}
|
||||
</div>
|
||||
</div>
|
||||
<span class="time-separator">:</span>
|
||||
</template>
|
||||
|
||||
<template v-if="widget.props.showMinutes">
|
||||
<div class="time-block">
|
||||
<div class="time-value">{{ padZero(remainingTime.minutes) }}</div>
|
||||
<div class="time-label">
|
||||
{{ $t('dashboard-design.widgets.countdown.minute') }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="widget.props.showSeconds" class="time-separator">:</span>
|
||||
</template>
|
||||
|
||||
<template v-if="widget.props.showSeconds">
|
||||
<div class="time-block">
|
||||
<div class="time-value">{{ padZero(remainingTime.seconds) }}</div>
|
||||
<div class="time-label">
|
||||
{{ $t('dashboard-design.widgets.countdown.second') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else class="text-muted-foreground text-lg">
|
||||
{{
|
||||
widget.props.finishedText ||
|
||||
$t('dashboard-design.widgets.countdown.finished')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.time-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.time-label {
|
||||
margin-top: 4px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.time-separator {
|
||||
margin-bottom: 16px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
@@ -1,285 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElEmpty, ElTable, ElTableColumn } from 'element-plus';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const columns = computed(() => props.widget.props.columns || []);
|
||||
const tableData = computed(() => props.widget.props.data || []);
|
||||
|
||||
// 表格高度配置
|
||||
const tableHeight = computed(() => {
|
||||
if (props.widget.props.maxHeight) return undefined;
|
||||
return props.widget.props.height || '100%';
|
||||
});
|
||||
|
||||
// 状态颜色映射
|
||||
const getStatusClass = (status: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
[$t('dashboard-design.widgets.dataTable.status.normal')]: 'status-success',
|
||||
[$t('dashboard-design.widgets.dataTable.status.warning')]: 'status-warning',
|
||||
[$t('dashboard-design.widgets.dataTable.status.error')]: 'status-danger',
|
||||
[$t('dashboard-design.widgets.dataTable.status.success')]: 'status-success',
|
||||
[$t('dashboard-design.widgets.dataTable.status.failed')]: 'status-danger',
|
||||
[$t('dashboard-design.widgets.dataTable.status.processing')]: 'status-info',
|
||||
};
|
||||
return statusMap[status] || '';
|
||||
};
|
||||
|
||||
// 统计类型国际化标签
|
||||
const summaryTypeLabels = computed(() => ({
|
||||
sum: $t('dashboard-design.widgets.dataTable.summary.sum'),
|
||||
avg: $t('dashboard-design.widgets.dataTable.summary.avg'),
|
||||
count: $t('dashboard-design.widgets.dataTable.summary.count'),
|
||||
max: $t('dashboard-design.widgets.dataTable.summary.max'),
|
||||
min: $t('dashboard-design.widgets.dataTable.summary.min'),
|
||||
}));
|
||||
|
||||
// 表尾统计方法
|
||||
const getSummaryMethod = (param: { columns: any[]; data: any[] }) => {
|
||||
const { columns: tableCols, data } = param;
|
||||
const sums: string[] = [];
|
||||
const summaryColumns = props.widget.props.summaryColumns || [];
|
||||
const summaryType = props.widget.props.summaryType || 'sum';
|
||||
const precision = props.widget.props.summaryPrecision ?? 2;
|
||||
|
||||
tableCols.forEach((column, index) => {
|
||||
// 第一列显示统计类型名称
|
||||
if (index === 0) {
|
||||
sums[index] =
|
||||
summaryTypeLabels.value[
|
||||
summaryType as keyof typeof summaryTypeLabels.value
|
||||
] || summaryType;
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找该列的统计配置
|
||||
const summaryConfig = summaryColumns.find(
|
||||
(sc: any) => sc.field === column.property,
|
||||
);
|
||||
|
||||
if (!summaryConfig || !summaryConfig.enabled) {
|
||||
sums[index] = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取该列的所有数值
|
||||
const values = data.map((item) => Number(item[column.property]));
|
||||
const validValues = values.filter((value) => !Number.isNaN(value));
|
||||
|
||||
if (validValues.length === 0) {
|
||||
sums[index] = '-';
|
||||
return;
|
||||
}
|
||||
|
||||
let result: number;
|
||||
switch (summaryType) {
|
||||
case 'avg': {
|
||||
result =
|
||||
validValues.reduce((acc, val) => acc + val, 0) / validValues.length;
|
||||
break;
|
||||
}
|
||||
case 'count': {
|
||||
result = validValues.length;
|
||||
break;
|
||||
}
|
||||
case 'max': {
|
||||
result = Math.max(...validValues);
|
||||
break;
|
||||
}
|
||||
case 'min': {
|
||||
result = Math.min(...validValues);
|
||||
break;
|
||||
}
|
||||
case 'sum': {
|
||||
result = validValues.reduce((acc, val) => acc + val, 0);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
result = validValues.reduce((acc, val) => acc + val, 0);
|
||||
}
|
||||
}
|
||||
|
||||
sums[index] = result.toFixed(precision);
|
||||
});
|
||||
|
||||
return sums;
|
||||
};
|
||||
|
||||
// 合并单元格方法
|
||||
const getSpanMethod = ({ row, column, rowIndex, columnIndex }: any) => {
|
||||
const mergeConfig = props.widget.props.mergeConfig;
|
||||
if (!mergeConfig || !mergeConfig.enabled) return;
|
||||
|
||||
const mergeRules = mergeConfig.rules || [];
|
||||
|
||||
for (const rule of mergeRules) {
|
||||
if (rule.type === 'row' && rule.field === column.property) {
|
||||
// 行合并:相同值的相邻行合并
|
||||
const data = tableData.value;
|
||||
const currentValue = row[rule.field];
|
||||
|
||||
// 检查是否是合并组的第一行
|
||||
if (rowIndex === 0 || data[rowIndex - 1][rule.field] !== currentValue) {
|
||||
let rowspan = 1;
|
||||
for (let i = rowIndex + 1; i < data.length; i++) {
|
||||
if (data[i][rule.field] === currentValue) {
|
||||
rowspan++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { rowspan, colspan: 1 };
|
||||
} else {
|
||||
// 被合并的行
|
||||
return { rowspan: 0, colspan: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.type === 'column' && rowIndex === rule.rowIndex) {
|
||||
// 列合并
|
||||
if (columnIndex === rule.startCol) {
|
||||
return { rowspan: 1, colspan: rule.colspan || 1 };
|
||||
} else if (
|
||||
columnIndex > rule.startCol &&
|
||||
columnIndex < rule.startCol + (rule.colspan || 1)
|
||||
) {
|
||||
return { rowspan: 0, colspan: 0 };
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="data-table-widget flex h-full w-full flex-col p-3">
|
||||
<div
|
||||
v-if="widget.props.title"
|
||||
class="text-muted-foreground mb-2 text-sm font-medium"
|
||||
>
|
||||
{{ widget.props.title }}
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<ElTable
|
||||
:data="tableData"
|
||||
:stripe="widget.props.stripe !== false"
|
||||
:border="widget.props.border"
|
||||
:size="widget.props.size || 'default'"
|
||||
:height="tableHeight"
|
||||
:max-height="widget.props.maxHeight"
|
||||
:highlight-current-row="widget.props.highlightCurrentRow"
|
||||
:show-header="widget.props.showHeader !== false"
|
||||
:empty-text="widget.props.emptyText || $t('common.noData')"
|
||||
:show-summary="widget.props.showSummary"
|
||||
:summary-method="
|
||||
widget.props.showSummary ? getSummaryMethod : undefined
|
||||
"
|
||||
:span-method="
|
||||
widget.props.mergeConfig?.enabled ? getSpanMethod : undefined
|
||||
"
|
||||
:header-cell-style="{
|
||||
textAlign: widget.props.headerAlign || 'left',
|
||||
...(widget.props.headerBgColor?.includes('gradient')
|
||||
? { background: widget.props.headerBgColor }
|
||||
: {
|
||||
backgroundColor:
|
||||
widget.props.headerBgColor || 'var(--el-fill-color-light)',
|
||||
}),
|
||||
}"
|
||||
:cell-style="{
|
||||
textAlign: widget.props.cellAlign || 'left',
|
||||
}"
|
||||
v-loading="widget.props.loading"
|
||||
>
|
||||
<ElTableColumn
|
||||
v-if="widget.props.showIndex"
|
||||
type="index"
|
||||
label="#"
|
||||
width="50"
|
||||
/>
|
||||
<ElTableColumn
|
||||
v-for="col in columns"
|
||||
:key="col.prop"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:width="col.width"
|
||||
:min-width="col.minWidth"
|
||||
:align="col.align || widget.props.cellAlign || 'left'"
|
||||
:header-align="col.headerAlign || widget.props.headerAlign || 'left'"
|
||||
:fixed="col.fixed"
|
||||
:sortable="col.sortable"
|
||||
:show-overflow-tooltip="col.showOverflowTooltip !== false"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span
|
||||
v-if="col.prop === 'status'"
|
||||
:class="getStatusClass(row[col.prop])"
|
||||
>
|
||||
{{ row[col.prop] }}
|
||||
</span>
|
||||
<span v-else>{{ row[col.prop] }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<template #empty>
|
||||
<ElEmpty
|
||||
:description="widget.props.emptyText || $t('common.noData')"
|
||||
:image-size="80"
|
||||
/>
|
||||
</template>
|
||||
</ElTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.data-table-widget {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.data-table-widget :deep(.el-table) {
|
||||
width: 100% !important;
|
||||
--el-table-header-bg-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.data-table-widget :deep(.el-table__inner-wrapper) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.data-table-widget :deep(.el-table__header-wrapper),
|
||||
.data-table-widget :deep(.el-table__body-wrapper) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
|
||||
.status-danger {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.status-info {
|
||||
color: var(--el-color-info);
|
||||
}
|
||||
</style>
|
||||
@@ -1,127 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElDatePicker } from 'element-plus';
|
||||
|
||||
import { useDashboardRuntime } from '../../runtime';
|
||||
|
||||
const props = defineProps<{
|
||||
isDesignMode?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const { updateGlobalParam } = useDashboardRuntime();
|
||||
const dateValue = ref<string>(props.widget.props.defaultValue || '');
|
||||
|
||||
const label = computed(() => props.widget.props.label || '');
|
||||
const placeholder = computed(
|
||||
() =>
|
||||
props.widget.props.placeholder ||
|
||||
$t('dashboard-design.material.defaultProps.filterDatePlaceholder'),
|
||||
);
|
||||
const paramKey = computed(() => props.widget.props.paramKey || '');
|
||||
const dateFormat = computed(
|
||||
() => props.widget.props.dateFormat || 'YYYY-MM-DD',
|
||||
);
|
||||
|
||||
const labelPosition = computed(
|
||||
() => props.widget.props.labelPosition || 'left',
|
||||
);
|
||||
const labelWidth = computed(() => props.widget.props.labelWidth ?? 80);
|
||||
const labelAlign = computed(() => props.widget.props.labelAlign || 'right');
|
||||
const componentSize = computed(
|
||||
() => props.widget.props.componentSize || 'default',
|
||||
);
|
||||
const showBorder = computed(() => props.widget.props.showBorder ?? false);
|
||||
const borderRadius = computed(() => props.widget.props.borderRadius ?? 4);
|
||||
|
||||
const isTopLabel = computed(() => labelPosition.value === 'top');
|
||||
const isHiddenLabel = computed(() => labelPosition.value === 'hidden');
|
||||
|
||||
const containerClass = computed(() => [
|
||||
'filter-widget h-full w-full',
|
||||
isTopLabel.value ? 'flex flex-col' : 'flex items-center',
|
||||
showBorder.value ? 'filter-widget--bordered' : '',
|
||||
]);
|
||||
|
||||
const containerStyle = computed(() => ({
|
||||
padding: isTopLabel.value ? '8px 12px' : '0 12px',
|
||||
borderRadius: showBorder.value ? `${borderRadius.value}px` : undefined,
|
||||
}));
|
||||
|
||||
const labelStyle = computed(() => ({
|
||||
width: isTopLabel.value ? 'auto' : `${labelWidth.value}px`,
|
||||
textAlign: labelAlign.value as 'center' | 'left' | 'right',
|
||||
flexShrink: 0,
|
||||
marginBottom: isTopLabel.value ? '4px' : undefined,
|
||||
marginRight: isTopLabel.value ? undefined : '8px',
|
||||
}));
|
||||
|
||||
function handleChange(val: any) {
|
||||
if (props.isDesignMode) return;
|
||||
if (paramKey.value) {
|
||||
updateGlobalParam(paramKey.value, val || '');
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.widget.props.defaultValue,
|
||||
(val) => {
|
||||
if (!props.isDesignMode && val !== undefined) {
|
||||
dateValue.value = val;
|
||||
if (paramKey.value) {
|
||||
updateGlobalParam(paramKey.value, val);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="containerClass" :style="containerStyle">
|
||||
<span
|
||||
v-if="label && !isHiddenLabel"
|
||||
class="text-foreground text-sm font-medium"
|
||||
:style="labelStyle"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
<ElDatePicker
|
||||
v-model="dateValue"
|
||||
type="date"
|
||||
:placeholder="placeholder"
|
||||
:disabled="isDesignMode"
|
||||
:format="dateFormat"
|
||||
:value-format="dateFormat"
|
||||
:size="componentSize"
|
||||
clearable
|
||||
class="!flex-1"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<span
|
||||
v-if="isDesignMode && !paramKey"
|
||||
class="ml-2 flex-shrink-0 text-xs text-orange-500"
|
||||
>
|
||||
{{ $t('dashboard-design.filter.noParamKey') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.filter-widget--bordered {
|
||||
border: 1px solid var(--el-border-color);
|
||||
background-color: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.filter-widget.flex-col :deep(.el-input),
|
||||
.filter-widget.flex-col :deep(.el-select),
|
||||
.filter-widget.flex-col :deep(.el-date-editor) {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElDatePicker } from 'element-plus';
|
||||
|
||||
import { useDashboardRuntime } from '../../runtime';
|
||||
|
||||
const props = defineProps<{
|
||||
isDesignMode?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const { updateGlobalParam } = useDashboardRuntime();
|
||||
const dateRange = ref<[string, string] | []>(
|
||||
props.widget.props.defaultValue || [],
|
||||
);
|
||||
|
||||
const label = computed(() => props.widget.props.label || '');
|
||||
const startPlaceholder = computed(
|
||||
() =>
|
||||
props.widget.props.startPlaceholder ||
|
||||
$t('dashboard-design.material.defaultProps.filterStartDate'),
|
||||
);
|
||||
const endPlaceholder = computed(
|
||||
() =>
|
||||
props.widget.props.endPlaceholder ||
|
||||
$t('dashboard-design.material.defaultProps.filterEndDate'),
|
||||
);
|
||||
const startParamKey = computed(
|
||||
() => props.widget.props.startParamKey || '',
|
||||
);
|
||||
const endParamKey = computed(
|
||||
() => props.widget.props.endParamKey || '',
|
||||
);
|
||||
const dateFormat = computed(
|
||||
() => props.widget.props.dateFormat || 'YYYY-MM-DD',
|
||||
);
|
||||
|
||||
const labelPosition = computed(
|
||||
() => props.widget.props.labelPosition || 'left',
|
||||
);
|
||||
const labelWidth = computed(() => props.widget.props.labelWidth ?? 80);
|
||||
const labelAlign = computed(() => props.widget.props.labelAlign || 'right');
|
||||
const componentSize = computed(
|
||||
() => props.widget.props.componentSize || 'default',
|
||||
);
|
||||
const showBorder = computed(() => props.widget.props.showBorder ?? false);
|
||||
const borderRadius = computed(() => props.widget.props.borderRadius ?? 4);
|
||||
|
||||
const isTopLabel = computed(() => labelPosition.value === 'top');
|
||||
const isHiddenLabel = computed(() => labelPosition.value === 'hidden');
|
||||
|
||||
const containerClass = computed(() => [
|
||||
'filter-widget h-full w-full',
|
||||
isTopLabel.value ? 'flex flex-col' : 'flex items-center',
|
||||
showBorder.value ? 'filter-widget--bordered' : '',
|
||||
]);
|
||||
|
||||
const containerStyle = computed(() => ({
|
||||
padding: isTopLabel.value ? '8px 12px' : '0 12px',
|
||||
borderRadius: showBorder.value ? `${borderRadius.value}px` : undefined,
|
||||
}));
|
||||
|
||||
const labelStyle = computed(() => ({
|
||||
width: isTopLabel.value ? 'auto' : `${labelWidth.value}px`,
|
||||
textAlign: labelAlign.value as 'center' | 'left' | 'right',
|
||||
flexShrink: 0,
|
||||
marginBottom: isTopLabel.value ? '4px' : undefined,
|
||||
marginRight: isTopLabel.value ? undefined : '8px',
|
||||
}));
|
||||
|
||||
function handleChange(val: [string, string] | null) {
|
||||
if (props.isDesignMode) return;
|
||||
if (val && val.length === 2) {
|
||||
if (startParamKey.value) {
|
||||
updateGlobalParam(startParamKey.value, val[0]);
|
||||
}
|
||||
if (endParamKey.value) {
|
||||
updateGlobalParam(endParamKey.value, val[1]);
|
||||
}
|
||||
} else {
|
||||
if (startParamKey.value) {
|
||||
updateGlobalParam(startParamKey.value, '');
|
||||
}
|
||||
if (endParamKey.value) {
|
||||
updateGlobalParam(endParamKey.value, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.widget.props.defaultValue,
|
||||
(val) => {
|
||||
if (!props.isDesignMode && Array.isArray(val) && val.length === 2) {
|
||||
dateRange.value = val as [string, string];
|
||||
if (startParamKey.value) {
|
||||
updateGlobalParam(startParamKey.value, val[0]);
|
||||
}
|
||||
if (endParamKey.value) {
|
||||
updateGlobalParam(endParamKey.value, val[1]);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const hasParamKey = computed(
|
||||
() => startParamKey.value || endParamKey.value,
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="containerClass" :style="containerStyle">
|
||||
<span
|
||||
v-if="label && !isHiddenLabel"
|
||||
class="text-foreground text-sm font-medium"
|
||||
:style="labelStyle"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
<ElDatePicker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
:start-placeholder="startPlaceholder"
|
||||
:end-placeholder="endPlaceholder"
|
||||
:disabled="isDesignMode"
|
||||
:format="dateFormat"
|
||||
:value-format="dateFormat"
|
||||
:size="componentSize"
|
||||
clearable
|
||||
class="!flex-1"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<span
|
||||
v-if="isDesignMode && !hasParamKey"
|
||||
class="ml-2 flex-shrink-0 text-xs text-orange-500"
|
||||
>
|
||||
{{ $t('dashboard-design.filter.noParamKey') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.filter-widget--bordered {
|
||||
border: 1px solid var(--el-border-color);
|
||||
background-color: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.filter-widget.flex-col :deep(.el-input),
|
||||
.filter-widget.flex-col :deep(.el-select),
|
||||
.filter-widget.flex-col :deep(.el-date-editor) {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,139 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Search } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElInput } from 'element-plus';
|
||||
|
||||
import { useDashboardRuntime } from '../../runtime';
|
||||
|
||||
const props = defineProps<{
|
||||
isDesignMode?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const { updateGlobalParam } = useDashboardRuntime();
|
||||
const inputValue = ref(props.widget.props.defaultValue || '');
|
||||
|
||||
const label = computed(() => props.widget.props.label || '');
|
||||
const placeholder = computed(
|
||||
() =>
|
||||
props.widget.props.placeholder ||
|
||||
$t('dashboard-design.material.defaultProps.filterInputPlaceholder'),
|
||||
);
|
||||
const paramKey = computed(() => props.widget.props.paramKey || '');
|
||||
|
||||
const labelPosition = computed(
|
||||
() => props.widget.props.labelPosition || 'left',
|
||||
);
|
||||
const labelWidth = computed(() => props.widget.props.labelWidth ?? 80);
|
||||
const labelAlign = computed(() => props.widget.props.labelAlign || 'right');
|
||||
const componentSize = computed(
|
||||
() => props.widget.props.componentSize || 'default',
|
||||
);
|
||||
const showBorder = computed(() => props.widget.props.showBorder ?? false);
|
||||
const borderRadius = computed(() => props.widget.props.borderRadius ?? 4);
|
||||
|
||||
const isTopLabel = computed(() => labelPosition.value === 'top');
|
||||
const isHiddenLabel = computed(() => labelPosition.value === 'hidden');
|
||||
|
||||
const containerClass = computed(() => [
|
||||
'filter-widget h-full w-full',
|
||||
isTopLabel.value ? 'flex flex-col' : 'flex items-center',
|
||||
showBorder.value ? 'filter-widget--bordered' : '',
|
||||
]);
|
||||
|
||||
const containerStyle = computed(() => ({
|
||||
padding: isTopLabel.value ? '8px 12px' : '0 12px',
|
||||
borderRadius: showBorder.value ? `${borderRadius.value}px` : undefined,
|
||||
}));
|
||||
|
||||
const labelStyle = computed(() => ({
|
||||
width: isTopLabel.value ? 'auto' : `${labelWidth.value}px`,
|
||||
textAlign: labelAlign.value as 'center' | 'left' | 'right',
|
||||
flexShrink: 0,
|
||||
marginBottom: isTopLabel.value ? '4px' : undefined,
|
||||
marginRight: isTopLabel.value ? undefined : '8px',
|
||||
}));
|
||||
|
||||
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function handleInput(val: string | number) {
|
||||
if (props.isDesignMode) return;
|
||||
if (debounceTimer) clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
if (paramKey.value) {
|
||||
updateGlobalParam(paramKey.value, val);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
if (props.isDesignMode) return;
|
||||
if (paramKey.value) {
|
||||
updateGlobalParam(paramKey.value, '');
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.widget.props.defaultValue,
|
||||
(val) => {
|
||||
if (!props.isDesignMode && val !== undefined) {
|
||||
inputValue.value = val;
|
||||
if (paramKey.value) {
|
||||
updateGlobalParam(paramKey.value, val);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="containerClass" :style="containerStyle">
|
||||
<span
|
||||
v-if="label && !isHiddenLabel"
|
||||
class="text-foreground text-sm font-medium"
|
||||
:style="labelStyle"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
<ElInput
|
||||
v-model="inputValue"
|
||||
:placeholder="placeholder"
|
||||
:disabled="isDesignMode"
|
||||
clearable
|
||||
:size="componentSize"
|
||||
class="flex-1"
|
||||
@input="handleInput"
|
||||
@clear="handleClear"
|
||||
>
|
||||
<template #prefix>
|
||||
<Search class="h-4 w-4 opacity-50" />
|
||||
</template>
|
||||
</ElInput>
|
||||
<span
|
||||
v-if="isDesignMode && !paramKey"
|
||||
class="ml-2 flex-shrink-0 text-xs text-orange-500"
|
||||
>
|
||||
{{ $t('dashboard-design.filter.noParamKey') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.filter-widget--bordered {
|
||||
border: 1px solid var(--el-border-color);
|
||||
background-color: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.filter-widget.flex-col :deep(.el-input),
|
||||
.filter-widget.flex-col :deep(.el-select),
|
||||
.filter-widget.flex-col :deep(.el-date-editor) {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,180 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElOption, ElSelect } from 'element-plus';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
import { useDashboardRuntime } from '../../runtime';
|
||||
|
||||
const props = defineProps<{
|
||||
isDesignMode?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const { updateGlobalParam } = useDashboardRuntime();
|
||||
const selectValue = ref<any>(props.widget.props.defaultValue || '');
|
||||
const dynamicOptions = ref<{ label: string; value: any }[]>([]);
|
||||
const loadingOptions = ref(false);
|
||||
|
||||
const label = computed(() => props.widget.props.label || '');
|
||||
const placeholder = computed(
|
||||
() =>
|
||||
props.widget.props.placeholder ||
|
||||
$t('dashboard-design.material.defaultProps.filterSelectPlaceholder'),
|
||||
);
|
||||
const paramKey = computed(() => props.widget.props.paramKey || '');
|
||||
const multiple = computed(() => props.widget.props.multiple || false);
|
||||
const clearable = computed(() => props.widget.props.clearable !== false);
|
||||
|
||||
const labelPosition = computed(
|
||||
() => props.widget.props.labelPosition || 'left',
|
||||
);
|
||||
const labelWidth = computed(() => props.widget.props.labelWidth ?? 80);
|
||||
const labelAlign = computed(() => props.widget.props.labelAlign || 'right');
|
||||
const componentSize = computed(
|
||||
() => props.widget.props.componentSize || 'default',
|
||||
);
|
||||
const showBorder = computed(() => props.widget.props.showBorder ?? false);
|
||||
const borderRadius = computed(() => props.widget.props.borderRadius ?? 4);
|
||||
|
||||
const isTopLabel = computed(() => labelPosition.value === 'top');
|
||||
const isHiddenLabel = computed(() => labelPosition.value === 'hidden');
|
||||
|
||||
const containerClass = computed(() => [
|
||||
'filter-widget h-full w-full',
|
||||
isTopLabel.value ? 'flex flex-col' : 'flex items-center',
|
||||
showBorder.value ? 'filter-widget--bordered' : '',
|
||||
]);
|
||||
|
||||
const containerStyle = computed(() => ({
|
||||
padding: isTopLabel.value ? '8px 12px' : '0 12px',
|
||||
borderRadius: showBorder.value ? `${borderRadius.value}px` : undefined,
|
||||
}));
|
||||
|
||||
const labelStyle = computed(() => ({
|
||||
width: isTopLabel.value ? 'auto' : `${labelWidth.value}px`,
|
||||
textAlign: labelAlign.value as 'center' | 'left' | 'right',
|
||||
flexShrink: 0,
|
||||
marginBottom: isTopLabel.value ? '4px' : undefined,
|
||||
marginRight: isTopLabel.value ? undefined : '8px',
|
||||
}));
|
||||
|
||||
const options = computed(() => {
|
||||
if (props.widget.props.optionSource === 'dataSource') {
|
||||
return dynamicOptions.value;
|
||||
}
|
||||
return props.widget.props.options || [];
|
||||
});
|
||||
|
||||
async function loadDynamicOptions() {
|
||||
const code = props.widget.props.optionDataSourceCode;
|
||||
if (!code) return;
|
||||
try {
|
||||
loadingOptions.value = true;
|
||||
const response = await requestClient.get(
|
||||
`/api/core/data-source/execute/${code}`,
|
||||
);
|
||||
const rawData = response?.data ?? response;
|
||||
const data = Array.isArray(rawData) ? rawData : [];
|
||||
const labelField = props.widget.props.optionLabelField || 'label';
|
||||
const valueField = props.widget.props.optionValueField || 'value';
|
||||
dynamicOptions.value = data.map((item: any) => ({
|
||||
label: String(item[labelField] ?? ''),
|
||||
value: item[valueField],
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to load filter options:', error);
|
||||
} finally {
|
||||
loadingOptions.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleChange(val: any) {
|
||||
if (props.isDesignMode) return;
|
||||
if (paramKey.value) {
|
||||
updateGlobalParam(paramKey.value, val);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
if (props.isDesignMode) return;
|
||||
if (paramKey.value) {
|
||||
updateGlobalParam(paramKey.value, multiple.value ? [] : '');
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.widget.props.defaultValue,
|
||||
(val) => {
|
||||
if (!props.isDesignMode && val !== undefined) {
|
||||
selectValue.value = val;
|
||||
if (paramKey.value) {
|
||||
updateGlobalParam(paramKey.value, val);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.widget.props.optionSource === 'dataSource') {
|
||||
loadDynamicOptions();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="containerClass" :style="containerStyle">
|
||||
<span
|
||||
v-if="label && !isHiddenLabel"
|
||||
class="text-foreground text-sm font-medium"
|
||||
:style="labelStyle"
|
||||
>
|
||||
{{ label }}
|
||||
</span>
|
||||
<ElSelect
|
||||
v-model="selectValue"
|
||||
:placeholder="placeholder"
|
||||
:disabled="isDesignMode"
|
||||
:multiple="multiple"
|
||||
:clearable="clearable"
|
||||
:loading="loadingOptions"
|
||||
:size="componentSize"
|
||||
class="flex-1"
|
||||
@change="handleChange"
|
||||
@clear="handleClear"
|
||||
>
|
||||
<ElOption
|
||||
v-for="opt in options"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
<span
|
||||
v-if="isDesignMode && !paramKey"
|
||||
class="ml-2 flex-shrink-0 text-xs text-orange-500"
|
||||
>
|
||||
{{ $t('dashboard-design.filter.noParamKey') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.filter-widget--bordered {
|
||||
border: 1px solid var(--el-border-color);
|
||||
background-color: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.filter-widget.flex-col :deep(.el-input),
|
||||
.filter-widget.flex-col :deep(.el-select),
|
||||
.filter-widget.flex-col :deep(.el-date-editor) {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,202 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AlertCircle, ExternalLink, RefreshCw } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
const props = defineProps<{
|
||||
isDesignMode?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const iframeRef = ref<HTMLIFrameElement | null>(null);
|
||||
const isLoading = ref(true);
|
||||
const hasError = ref(false);
|
||||
|
||||
const iframeUrl = computed(() => props.widget.props.url || '');
|
||||
|
||||
const handleLoad = () => {
|
||||
isLoading.value = false;
|
||||
hasError.value = false;
|
||||
};
|
||||
|
||||
const handleError = () => {
|
||||
isLoading.value = false;
|
||||
hasError.value = true;
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
if (iframeRef.value) {
|
||||
isLoading.value = true;
|
||||
hasError.value = false;
|
||||
iframeRef.value.src = iframeUrl.value;
|
||||
}
|
||||
};
|
||||
|
||||
const openInNewTab = () => {
|
||||
if (iframeUrl.value) {
|
||||
window.open(iframeUrl.value, '_blank');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="iframe-widget flex h-full flex-col">
|
||||
<!-- 标题栏 -->
|
||||
<div v-if="widget.props.title" class="iframe-header">
|
||||
<span class="text-muted-foreground text-sm font-medium">{{
|
||||
widget.props.title
|
||||
}}</span>
|
||||
<div class="header-actions">
|
||||
<button
|
||||
class="action-btn"
|
||||
:title="$t('dashboard-design.widgets.iframe.refresh')"
|
||||
@click="refresh"
|
||||
>
|
||||
<RefreshCw class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
:title="$t('dashboard-design.widgets.iframe.openNew')"
|
||||
@click="openInNewTab"
|
||||
>
|
||||
<ExternalLink class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- iframe 容器 -->
|
||||
<div class="iframe-container relative min-h-0 flex-1">
|
||||
<!-- 设计模式下显示占位 -->
|
||||
<div v-if="isDesignMode" class="design-placeholder">
|
||||
<ExternalLink class="h-8 w-8 text-gray-400" />
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
{{ $t('dashboard-design.widgets.iframe.placeholder') }}
|
||||
</div>
|
||||
<div class="mt-1 max-w-full truncate px-4 text-xs text-gray-400">
|
||||
{{ iframeUrl || $t('dashboard-design.widgets.iframe.noUrl') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实际 iframe -->
|
||||
<template v-else>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="isLoading" class="loading-overlay">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
{{ $t('dashboard-design.widgets.iframe.loading') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-if="hasError && !isLoading" class="error-overlay">
|
||||
<AlertCircle class="h-8 w-8 text-red-400" />
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
{{ $t('dashboard-design.widgets.iframe.loadFailed') }}
|
||||
</div>
|
||||
<button class="text-primary mt-2 text-xs" @click="refresh">
|
||||
{{ $t('dashboard-design.widgets.iframe.retry') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- iframe -->
|
||||
<iframe
|
||||
v-show="!hasError"
|
||||
ref="iframeRef"
|
||||
:src="iframeUrl"
|
||||
:class="{ 'with-border': widget.props.showBorder }"
|
||||
:allowfullscreen="widget.props.allowFullscreen"
|
||||
frameborder="0"
|
||||
class="iframe-content"
|
||||
@load="handleLoad"
|
||||
@error="handleError"
|
||||
></iframe>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.iframe-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: var(--el-text-color-secondary);
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
color: var(--el-text-color-primary);
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.iframe-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.design-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
|
||||
.loading-overlay,
|
||||
.error-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid var(--el-border-color-lighter);
|
||||
border-top-color: var(--el-color-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.iframe-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.iframe-content.with-border {
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
</style>
|
||||
-252
@@ -1,252 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { ChevronLeft, ChevronRight } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { getFileUrl } from '#/composables/useFileUrl';
|
||||
|
||||
const props = defineProps<{
|
||||
isDesignMode?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const currentIndex = ref(0);
|
||||
let autoplayTimer: null | ReturnType<typeof setInterval> = null;
|
||||
|
||||
// 处理后的图片列表(响应式)
|
||||
const images = ref<any[]>([]);
|
||||
|
||||
// 异步解析图片URL
|
||||
async function resolveImageUrl(url: string): Promise<string> {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('file://')) {
|
||||
const fileId = url.slice(7);
|
||||
return await getFileUrl(fileId);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// 加载图片列表
|
||||
async function loadImages() {
|
||||
const rawImages = props.widget.props.images || [];
|
||||
const resolvedImages = await Promise.all(
|
||||
rawImages.map(async (img: any) => ({
|
||||
...img,
|
||||
url: await resolveImageUrl(img.url || ''),
|
||||
})),
|
||||
);
|
||||
images.value = resolvedImages;
|
||||
}
|
||||
|
||||
// 监听图片列表变化
|
||||
watch(
|
||||
() => props.widget.props.images,
|
||||
() => {
|
||||
loadImages();
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
const nextSlide = () => {
|
||||
if (images.value.length === 0) return;
|
||||
currentIndex.value = (currentIndex.value + 1) % images.value.length;
|
||||
};
|
||||
|
||||
const prevSlide = () => {
|
||||
if (images.value.length === 0) return;
|
||||
currentIndex.value =
|
||||
(currentIndex.value - 1 + images.value.length) % images.value.length;
|
||||
};
|
||||
|
||||
const goToSlide = (index: number) => {
|
||||
currentIndex.value = index;
|
||||
};
|
||||
|
||||
const startAutoplay = () => {
|
||||
if (autoplayTimer) {
|
||||
clearInterval(autoplayTimer);
|
||||
}
|
||||
if (props.widget.props.autoplay && !props.isDesignMode) {
|
||||
const interval = props.widget.props.interval || 3000;
|
||||
autoplayTimer = setInterval(nextSlide, interval);
|
||||
}
|
||||
};
|
||||
|
||||
const stopAutoplay = () => {
|
||||
if (autoplayTimer) {
|
||||
clearInterval(autoplayTimer);
|
||||
autoplayTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageClick = (image: any) => {
|
||||
if (image.link && !props.isDesignMode) {
|
||||
window.open(image.link, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
startAutoplay();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopAutoplay();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.widget.props.autoplay,
|
||||
() => {
|
||||
if (props.widget.props.autoplay) {
|
||||
startAutoplay();
|
||||
} else {
|
||||
stopAutoplay();
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="image-carousel relative h-full w-full overflow-hidden"
|
||||
@mouseenter="stopAutoplay"
|
||||
@mouseleave="startAutoplay"
|
||||
>
|
||||
<!-- 图片容器 -->
|
||||
<div
|
||||
class="carousel-track flex h-full transition-transform duration-500 ease-in-out"
|
||||
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
|
||||
>
|
||||
<div
|
||||
v-for="(image, index) in images"
|
||||
:key="index"
|
||||
class="carousel-slide h-full w-full flex-shrink-0"
|
||||
:class="{ 'cursor-pointer': image.link && !isDesignMode }"
|
||||
@click="handleImageClick(image)"
|
||||
>
|
||||
<img
|
||||
:src="image.url"
|
||||
:alt="
|
||||
image.title ||
|
||||
`${$t('dashboard-design.widgets.image.title')} ${Number(index) + 1}`
|
||||
"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
<div v-if="image.title" class="slide-title">
|
||||
{{ image.title }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 左右箭头 -->
|
||||
<template v-if="widget.props.showArrow && images.length > 1">
|
||||
<button class="arrow-btn arrow-left" @click.stop="prevSlide">
|
||||
<ChevronLeft class="h-5 w-5" />
|
||||
</button>
|
||||
<button class="arrow-btn arrow-right" @click.stop="nextSlide">
|
||||
<ChevronRight class="h-5 w-5" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- 指示器 -->
|
||||
<div
|
||||
v-if="widget.props.showIndicator && images.length > 1"
|
||||
class="indicators"
|
||||
>
|
||||
<button
|
||||
v-for="(_, index) in images"
|
||||
:key="index"
|
||||
class="indicator"
|
||||
:class="{ active: currentIndex === Number(index) }"
|
||||
@click.stop="goToSlide(Number(index))"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div
|
||||
v-if="images.length === 0"
|
||||
class="flex h-full w-full items-center justify-center text-gray-400"
|
||||
>
|
||||
{{ $t('dashboard-design.widgets.image.noData') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.carousel-track {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.slide-title {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 8px 12px;
|
||||
font-size: 0.875rem;
|
||||
color: white;
|
||||
background: linear-gradient(transparent, rgb(0 0 0 / 60%));
|
||||
}
|
||||
|
||||
.arrow-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
background: rgb(255 255 255 / 80%);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
transform: translateY(-50%);
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.image-carousel:hover .arrow-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.arrow-btn:hover {
|
||||
background: rgb(255 255 255 / 100%);
|
||||
}
|
||||
|
||||
.arrow-left {
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.indicators {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
left: 50%;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
cursor: pointer;
|
||||
background: rgb(255 255 255 / 50%);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.indicator.active {
|
||||
width: 20px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,117 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { ElImage } from 'element-plus';
|
||||
|
||||
import { getFileUrl } from '#/composables/useFileUrl';
|
||||
|
||||
const props = defineProps<{
|
||||
isDesignMode?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const widgetProps = computed(() => props.widget.props);
|
||||
|
||||
// 主图URL(响应式)
|
||||
const imageSrc = ref('');
|
||||
|
||||
// 预览图片列表(响应式)
|
||||
const previewList = ref<string[]>([]);
|
||||
|
||||
// 异步解析图片URL
|
||||
async function resolveImageUrl(url: string): Promise<string> {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('file://')) {
|
||||
const fileId = url.slice(7);
|
||||
return await getFileUrl(fileId);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// 加载图片URL
|
||||
async function loadImageUrls() {
|
||||
// 加载主图
|
||||
imageSrc.value = await resolveImageUrl(widgetProps.value.src || '');
|
||||
|
||||
// 加载预览列表
|
||||
const list = widgetProps.value.previewSrcList || [];
|
||||
previewList.value =
|
||||
list.length === 0 && imageSrc.value
|
||||
? [imageSrc.value]
|
||||
: await Promise.all(list.map((url: string) => resolveImageUrl(url)));
|
||||
}
|
||||
|
||||
// 监听图片源变化
|
||||
watch(
|
||||
() => [widgetProps.value.src, widgetProps.value.previewSrcList],
|
||||
() => {
|
||||
loadImageUrls();
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="image-widget flex h-full w-full flex-col">
|
||||
<!-- 标题 -->
|
||||
<div
|
||||
v-if="widgetProps.title"
|
||||
class="mb-2 flex-shrink-0 text-sm font-medium"
|
||||
style="color: var(--el-text-color-primary)"
|
||||
>
|
||||
{{ widgetProps.title }}
|
||||
</div>
|
||||
|
||||
<!-- 图片容器 -->
|
||||
<div class="relative min-h-0 flex-1 overflow-hidden rounded">
|
||||
<ElImage
|
||||
:src="imageSrc"
|
||||
:alt="widgetProps.alt || '图片'"
|
||||
:fit="widgetProps.fit || 'cover'"
|
||||
:lazy="widgetProps.lazy !== false"
|
||||
:preview-src-list="isDesignMode ? [] : previewList"
|
||||
:z-index="widgetProps.zIndex || 2000"
|
||||
:hide-on-click-modal="widgetProps.hideOnClickModal || false"
|
||||
class="h-full w-full"
|
||||
:preview-teleported="true"
|
||||
>
|
||||
<template #error>
|
||||
<div
|
||||
class="flex h-full w-full items-center justify-center"
|
||||
style="background-color: var(--el-fill-color-light)"
|
||||
>
|
||||
<span style="color: var(--el-text-color-secondary)">
|
||||
加载失败
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #placeholder>
|
||||
<div
|
||||
class="flex h-full w-full items-center justify-center"
|
||||
style="background-color: var(--el-fill-color-lighter)"
|
||||
>
|
||||
<span style="color: var(--el-text-color-placeholder)">
|
||||
加载中...
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</ElImage>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.image-widget :deep(.el-image) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-widget :deep(.el-image__inner) {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.image-widget:hover :deep(.el-image__inner) {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
</style>
|
||||
@@ -1,130 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '#/components/dashboard-design';
|
||||
import type { ApplicationListItem } from '#/api/core/application';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { AppWindow, ChevronRight, IconifyIcon } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElEmpty, ElScrollbar } from 'element-plus';
|
||||
|
||||
import { getApplicationListApi } from '#/api/core/application';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
type AppLink = Pick<ApplicationListItem, 'code' | 'icon' | 'id' | 'name'> & {
|
||||
path?: string;
|
||||
};
|
||||
|
||||
// 应用列表
|
||||
const appList = ref<AppLink[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const configuredApps = computed<AppLink[]>(() => props.widget.props.apps || []);
|
||||
|
||||
// 最大显示数量
|
||||
const maxCount = computed(() => props.widget.props.maxCount || 8);
|
||||
|
||||
// 显示的应用列表
|
||||
const displayApps = computed(() =>
|
||||
(configuredApps.value.length > 0 ? configuredApps.value : appList.value).slice(
|
||||
0,
|
||||
maxCount.value,
|
||||
),
|
||||
);
|
||||
|
||||
// 加载已发布的应用列表
|
||||
const loadApps = async () => {
|
||||
if (configuredApps.value.length > 0) {
|
||||
appList.value = configuredApps.value;
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getApplicationListApi({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
status: 'published',
|
||||
});
|
||||
appList.value = res.items || [];
|
||||
} catch (error) {
|
||||
console.error('Failed to load applications:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 点击应用
|
||||
const handleClick = (app: AppLink) => {
|
||||
const path = app.path || `/app/${app.code}`;
|
||||
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||||
window.open(path, '_blank');
|
||||
return;
|
||||
}
|
||||
window.open(`${window.location.origin}${path}`, '_blank');
|
||||
};
|
||||
|
||||
// 点击更多
|
||||
const handleMore = () => {
|
||||
window.open(`${window.location.origin}/application`, '_blank');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadApps();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-apps flex h-full flex-col p-4" v-loading="loading">
|
||||
<!-- 头部 -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<span class="text-sm font-medium">{{ widget.props.title }}</span>
|
||||
<button
|
||||
v-if="widget.props.showMore"
|
||||
type="button"
|
||||
class="text-muted-foreground hover:text-primary flex items-center gap-0.5 text-xs transition-colors"
|
||||
@click="handleMore"
|
||||
>
|
||||
{{ $t('dashboard-design.widgets.myApps.more') }}
|
||||
<ChevronRight class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<!-- 应用网格 -->
|
||||
<ElScrollbar class="flex-1">
|
||||
<div
|
||||
v-if="displayApps.length > 0"
|
||||
class="flex flex-wrap gap-4"
|
||||
>
|
||||
<div
|
||||
v-for="app in displayApps"
|
||||
:key="app.id"
|
||||
class="flex cursor-pointer flex-col items-center gap-2 transition-transform hover:scale-105 m-4"
|
||||
style="width: 72px"
|
||||
@click="handleClick(app)"
|
||||
>
|
||||
<div
|
||||
class="flex h-12 w-12 items-center justify-center rounded-xl"
|
||||
style="background: linear-gradient(135deg, var(--el-color-primary-light-3), var(--el-color-primary))"
|
||||
>
|
||||
<IconifyIcon
|
||||
v-if="app.icon"
|
||||
:icon="app.icon"
|
||||
class="h-6 w-6 text-white"
|
||||
/>
|
||||
<AppWindow v-else class="h-6 w-6 text-white" />
|
||||
</div>
|
||||
<span class="text-muted-foreground w-full truncate text-center text-xs">{{ app.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ElEmpty
|
||||
v-else-if="!loading"
|
||||
:description="$t('dashboard-design.widgets.myApps.noData')"
|
||||
:image-size="60"
|
||||
/>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,269 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import type { Message } from '#/api/core/message';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { Bell, CheckCheck, ExternalLink } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElButton, ElDialog, ElEmpty, ElScrollbar, ElTag } from 'element-plus';
|
||||
|
||||
import {
|
||||
getMessageListApi,
|
||||
markAllAsReadApi,
|
||||
markAsReadApi,
|
||||
} from '#/api/core/message';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
// 实际消息数据
|
||||
const messages = ref<Message[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
// 详情弹窗
|
||||
const detailVisible = ref(false);
|
||||
const currentMessage = ref<Message | null>(null);
|
||||
|
||||
// 未读数量
|
||||
const unreadCount = computed(
|
||||
() => messages.value.filter((m) => m.status === 'unread').length,
|
||||
);
|
||||
|
||||
// 获取消息类型配置
|
||||
const getTypeConfig = (type: string) => {
|
||||
switch (type) {
|
||||
case 'announcement': {
|
||||
return {
|
||||
type: 'success' as const,
|
||||
label: $t('message.typeMap.announcement'),
|
||||
};
|
||||
}
|
||||
case 'system': {
|
||||
return { type: 'info' as const, label: $t('message.typeMap.system') };
|
||||
}
|
||||
case 'todo': {
|
||||
return { type: 'warning' as const, label: $t('message.typeMap.todo') };
|
||||
}
|
||||
case 'workflow': {
|
||||
return {
|
||||
type: 'primary' as const,
|
||||
label: $t('message.typeMap.workflow'),
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return { type: 'info' as const, label: $t('message.type') };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到审批链接
|
||||
const handleGoToLink = (message: Message) => {
|
||||
if (!message.link_type || !message.link_id) return;
|
||||
|
||||
// 根据 link_type 跳转到不同页面
|
||||
let url = '';
|
||||
if (message.link_type === 'workflow_task') {
|
||||
// 跳转到待办任务页面,带上任务ID
|
||||
url = `/app/workflow_center/workflow/pending?id=${message.link_id}`;
|
||||
} else if (message.link_type === 'workflow_instance') {
|
||||
// 跳转到我发起的页面,带上实例ID
|
||||
url = `/app/workflow_center/workflow/initiated?id=${message.link_id}`;
|
||||
}
|
||||
|
||||
if (url) {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
// 处理 "2025-12-01 21:47:29" 格式,替换空格为T以兼容所有浏览器
|
||||
const date = new Date(dateStr.replace(' ', 'T'));
|
||||
if (isNaN(date.getTime())) return dateStr;
|
||||
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const minutes = Math.floor(diff / 60_000);
|
||||
const hours = Math.floor(diff / 3_600_000);
|
||||
const days = Math.floor(diff / 86_400_000);
|
||||
|
||||
if (minutes < 1)
|
||||
return $t('dashboard-design.widgets.announcement.time.justNow');
|
||||
if (minutes < 60)
|
||||
return `${minutes}${$t('dashboard-design.widgets.announcement.time.minutesAgo')}`;
|
||||
if (hours < 24)
|
||||
return `${hours}${$t('dashboard-design.widgets.announcement.time.hoursAgo')}`;
|
||||
if (days < 7)
|
||||
return `${days}${$t('dashboard-design.widgets.announcement.time.daysAgo')}`;
|
||||
return date.toLocaleDateString();
|
||||
};
|
||||
|
||||
// 加载消息数据
|
||||
const loadMessages = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const limit = props.widget.props.limit || 5;
|
||||
const res = await getMessageListApi({ page: 1, pageSize: limit });
|
||||
messages.value = res.items || [];
|
||||
} catch (error) {
|
||||
console.error('Failed to load messages:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 查看消息详情
|
||||
const viewDetail = async (msg: Message) => {
|
||||
currentMessage.value = msg;
|
||||
detailVisible.value = true;
|
||||
|
||||
// 标记为已读
|
||||
if (msg.status === 'unread') {
|
||||
try {
|
||||
await markAsReadApi(msg.id);
|
||||
msg.status = 'read';
|
||||
} catch (error) {
|
||||
console.error('Failed to mark as read:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 全部已读
|
||||
const markAllRead = async () => {
|
||||
if (unreadCount.value === 0) return;
|
||||
|
||||
try {
|
||||
await markAllAsReadApi();
|
||||
messages.value.forEach((msg) => {
|
||||
msg.status = 'read';
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to mark all as read:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadMessages();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="notice-list flex h-full flex-col p-3">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<Bell class="text-muted-foreground h-4 w-4" />
|
||||
<span class="text-muted-foreground text-sm font-medium">{{
|
||||
widget.props.title
|
||||
}}</span>
|
||||
<ElTag v-if="unreadCount > 0" type="danger" size="small" round>
|
||||
{{ unreadCount }}
|
||||
</ElTag>
|
||||
</div>
|
||||
<ElButton
|
||||
v-if="unreadCount > 0"
|
||||
type="primary"
|
||||
text
|
||||
size="small"
|
||||
@click="markAllRead"
|
||||
>
|
||||
<CheckCheck class="mr-1 h-3.5 w-3.5" />
|
||||
{{ $t('dashboard-design.widgets.announcement.markAllRead') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
<ElScrollbar class="flex-1">
|
||||
<div v-if="messages.length > 0" class="space-y-2">
|
||||
<div
|
||||
v-for="msg in messages"
|
||||
:key="msg.id"
|
||||
class="flex cursor-pointer items-start gap-2 rounded-md p-2 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
:class="{ 'opacity-60': msg.status === 'read' }"
|
||||
@click="viewDetail(msg)"
|
||||
>
|
||||
<ElTag
|
||||
:type="getTypeConfig(msg.msg_type).type"
|
||||
size="small"
|
||||
class="flex-shrink-0"
|
||||
>
|
||||
{{ getTypeConfig(msg.msg_type).label }}
|
||||
</ElTag>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm">{{ msg.title }}</div>
|
||||
<div class="text-muted-foreground mt-1 flex items-center text-xs">
|
||||
<span v-if="msg.content" class="flex-1 truncate">{{
|
||||
msg.content
|
||||
}}</span>
|
||||
<span class="ml-2 flex-shrink-0">{{
|
||||
formatTime(msg.created_at)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ElEmpty
|
||||
v-else
|
||||
:description="
|
||||
$t('message.noData') ||
|
||||
$t('dashboard-design.widgets.announcement.noData')
|
||||
"
|
||||
:image-size="60"
|
||||
/>
|
||||
</ElScrollbar>
|
||||
|
||||
<!-- 消息详情弹窗 -->
|
||||
<ElDialog
|
||||
v-model="detailVisible"
|
||||
:title="currentMessage?.title"
|
||||
width="600px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
>
|
||||
<div v-if="currentMessage" class="min-h-[500px] space-y-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<ElTag
|
||||
:type="getTypeConfig(currentMessage.msg_type).type"
|
||||
size="small"
|
||||
>
|
||||
{{ getTypeConfig(currentMessage.msg_type).label }}
|
||||
</ElTag>
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
formatTime(currentMessage.created_at)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="whitespace-pre-wrap text-sm leading-relaxed">
|
||||
{{
|
||||
currentMessage.content ||
|
||||
$t('dashboard-design.widgets.announcement.noContent')
|
||||
}}
|
||||
</div>
|
||||
|
||||
<!-- 审批链接按钮 -->
|
||||
<div
|
||||
v-if="currentMessage.link_type && currentMessage.link_id"
|
||||
class="mt-4"
|
||||
>
|
||||
<ElButton type="text" @click="handleGoToLink(currentMessage)">
|
||||
<ExternalLink class="mr-1 h-3.5 w-3.5" />
|
||||
查看详情
|
||||
</ElButton>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="currentMessage.sender_name"
|
||||
class="text-muted-foreground text-xs"
|
||||
>
|
||||
{{ $t('message.sender') || '发送者:'
|
||||
}}{{ currentMessage.sender_name }}
|
||||
</div>
|
||||
</div>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景色由 WidgetRenderer 控制 */
|
||||
</style>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { ElProgress } from 'element-plus';
|
||||
|
||||
defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="progress-card flex h-full flex-col justify-between p-4">
|
||||
<div class="text-muted-foreground text-sm">{{ widget.props.title }}</div>
|
||||
<div class="flex flex-1 items-center justify-center">
|
||||
<ElProgress
|
||||
type="dashboard"
|
||||
:percentage="widget.props.percentage"
|
||||
:status="widget.props.status || undefined"
|
||||
:stroke-width="widget.props.strokeWidth"
|
||||
:show-text="widget.props.showText"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景色由 WidgetRenderer 控制 */
|
||||
</style>
|
||||
@@ -1,120 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '#/components/dashboard-design';
|
||||
|
||||
import { computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Grid, IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { useAppContextStore } from '#/store/app-context';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
const appContextStore = useAppContextStore();
|
||||
|
||||
// 计算网格样式
|
||||
const gridStyle = computed(() => {
|
||||
const cols = props.widget.props.columns || 4;
|
||||
const rows = props.widget.props.rows || 2;
|
||||
return {
|
||||
gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
|
||||
gridTemplateRows: `repeat(${rows}, minmax(0, 1fr))`,
|
||||
};
|
||||
});
|
||||
|
||||
// 计算总格子数
|
||||
const totalCells = computed(() => {
|
||||
const cols = props.widget.props.columns || 4;
|
||||
const rows = props.widget.props.rows || 2;
|
||||
return cols * rows;
|
||||
});
|
||||
|
||||
// 获取指定位置的菜单
|
||||
const getMenuAt = (index: number) => {
|
||||
const menus = props.widget.props.menus || [];
|
||||
return menus[index] || null;
|
||||
};
|
||||
|
||||
// 图标颜色样式
|
||||
const iconColorStyle = computed(() => {
|
||||
const color = props.widget.props.iconColor;
|
||||
return color ? { color } : {};
|
||||
});
|
||||
|
||||
// 获取 item 背景样式(支持渐变色)
|
||||
const getItemBgStyle = (menu: any) => {
|
||||
if (!menu?.bgColor) return {};
|
||||
if (menu.bgColor.includes('gradient')) {
|
||||
return { background: menu.bgColor };
|
||||
}
|
||||
return { backgroundColor: menu.bgColor };
|
||||
};
|
||||
|
||||
// 点击菜单项
|
||||
const handleClick = (menu: any) => {
|
||||
if (!menu || !menu.path) return;
|
||||
|
||||
// 外链
|
||||
if (menu.path.startsWith('http://') || menu.path.startsWith('https://')) {
|
||||
window.open(menu.path, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
// 路由跳转(子应用模式下始终使用 /app/{code} 前缀)
|
||||
const code = appContextStore.appCode;
|
||||
const targetPath = code ? `/app/${code}${menu.path}` : menu.path;
|
||||
router.push(targetPath);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="quick-links flex h-full flex-col p-3">
|
||||
<div class="mb-3 flex items-center gap-2">
|
||||
<Grid class="text-muted-foreground h-4 w-4" />
|
||||
<span class="text-muted-foreground text-sm font-medium">{{
|
||||
widget.props.title
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="grid flex-1 gap-2" :style="gridStyle">
|
||||
<template v-for="idx in totalCells" :key="idx">
|
||||
<div
|
||||
v-if="getMenuAt(idx - 1)"
|
||||
class="flex cursor-pointer flex-col items-center justify-center gap-1 rounded-lg p-2 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
:style="getItemBgStyle(getMenuAt(idx - 1))"
|
||||
@click="handleClick(getMenuAt(idx - 1))"
|
||||
>
|
||||
<IconifyIcon
|
||||
v-if="getMenuAt(idx - 1)?.icon"
|
||||
:icon="getMenuAt(idx - 1).icon"
|
||||
class="h-6 w-6"
|
||||
:class="{ 'text-primary': !widget.props.iconColor }"
|
||||
:style="iconColorStyle"
|
||||
/>
|
||||
<Grid
|
||||
v-else
|
||||
class="h-6 w-6"
|
||||
:class="{ 'text-primary': !widget.props.iconColor }"
|
||||
:style="iconColorStyle"
|
||||
/>
|
||||
<span
|
||||
class="text-muted-foreground w-full truncate text-center text-xs"
|
||||
>{{ getMenuAt(idx - 1)?.title }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col items-center justify-center gap-1 rounded-lg p-2"
|
||||
>
|
||||
<!-- 空位占位 -->
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景色由 WidgetRenderer 控制 */
|
||||
</style>
|
||||
@@ -1,71 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { Award } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElScrollbar } from 'element-plus';
|
||||
|
||||
defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const getRankClass = (rank: number) => {
|
||||
switch (rank) {
|
||||
case 1: {
|
||||
return 'bg-yellow-500 text-white';
|
||||
}
|
||||
case 2: {
|
||||
return 'bg-gray-400 text-white';
|
||||
}
|
||||
case 3: {
|
||||
return 'bg-amber-600 text-white';
|
||||
}
|
||||
default: {
|
||||
return 'bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-300';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const formatValue = (value: number) => {
|
||||
if (value >= 10_000) {
|
||||
return `${(value / 10_000).toFixed(1)}${$t('dashboard-design.widgets.ranking.tenThousand')}`;
|
||||
}
|
||||
return value.toLocaleString();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ranking-list flex h-full flex-col p-3">
|
||||
<div class="mb-3 flex items-center gap-2">
|
||||
<Award class="text-muted-foreground h-4 w-4" />
|
||||
<span class="text-muted-foreground text-sm font-medium">{{
|
||||
widget.props.title
|
||||
}}</span>
|
||||
</div>
|
||||
<ElScrollbar class="flex-1">
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="item in widget.props.items"
|
||||
:key="item.rank"
|
||||
class="flex items-center gap-3 rounded-md p-2 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
>
|
||||
<div
|
||||
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded text-xs font-medium"
|
||||
:class="getRankClass(item.rank)"
|
||||
>
|
||||
{{ item.rank }}
|
||||
</div>
|
||||
<div class="flex-1 truncate text-sm">{{ item.name }}</div>
|
||||
<div class="text-muted-foreground text-sm font-medium">
|
||||
{{ formatValue(item.value) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景色由 WidgetRenderer 控制 */
|
||||
</style>
|
||||
-294
@@ -1,294 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
RealtimeStats,
|
||||
ServerMonitorResponse,
|
||||
} from '#/api/core/server-monitor';
|
||||
import type { DashboardWidget } from '#/components/dashboard-design';
|
||||
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import { Cpu, Database, HardDrive, Network } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElProgress } from 'element-plus';
|
||||
|
||||
import {
|
||||
getRealtimeStatsApi,
|
||||
getServerOverviewApi,
|
||||
} from '#/api/core/server-monitor';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const serverData = ref<null | ServerMonitorResponse>(null);
|
||||
const realtimeData = ref<null | RealtimeStats>(null);
|
||||
let timer: null | ReturnType<typeof setInterval> = null;
|
||||
|
||||
// 刷新间隔
|
||||
const refreshInterval = computed(
|
||||
() => props.widget.props.refreshInterval || 5000,
|
||||
);
|
||||
|
||||
// 区域背景色
|
||||
function getAreaStyle(colorProp: string) {
|
||||
const color = props.widget.props[colorProp];
|
||||
if (!color) return {};
|
||||
if (color.includes('gradient')) return { background: color };
|
||||
return { backgroundColor: color };
|
||||
}
|
||||
|
||||
// 格式化字节
|
||||
function formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${(bytes / k ** i).toFixed(1)} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
// 格式化内存(后端返回 GB)
|
||||
function formatMemory(gb: number): string {
|
||||
if (gb === 0) return '0 GB';
|
||||
if (gb < 1) return `${(gb * 1024).toFixed(0)} MB`;
|
||||
return `${gb.toFixed(1)} GB`;
|
||||
}
|
||||
|
||||
// 格式化速度
|
||||
function formatSpeed(bytesPerSecond: number): string {
|
||||
return `${formatBytes(bytesPerSecond)}/s`;
|
||||
}
|
||||
|
||||
// 使用率颜色
|
||||
function getProgressColor(percent: number): string {
|
||||
if (percent >= 90) return 'var(--el-color-danger)';
|
||||
if (percent >= 70) return 'var(--el-color-warning)';
|
||||
return 'var(--el-color-success)';
|
||||
}
|
||||
|
||||
// 格式化运行时间
|
||||
function formatUptime(seconds: number): string {
|
||||
const days = Math.floor(seconds / 86_400);
|
||||
const hours = Math.floor((seconds % 86_400) / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const parts = [];
|
||||
if (days > 0)
|
||||
parts.push(`${days}${$t('dashboard-design.widgets.serverMonitor.days')}`);
|
||||
if (hours > 0)
|
||||
parts.push(`${hours}${$t('dashboard-design.widgets.serverMonitor.hours')}`);
|
||||
if (minutes > 0)
|
||||
parts.push(
|
||||
`${minutes}${$t('dashboard-design.widgets.serverMonitor.minutes')}`,
|
||||
);
|
||||
return parts.join(' ') || '-';
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
async function loadData(showLoading = false) {
|
||||
if (showLoading) loading.value = true;
|
||||
try {
|
||||
const [overview, realtime] = await Promise.all([
|
||||
getServerOverviewApi(),
|
||||
getRealtimeStatsApi(),
|
||||
]);
|
||||
serverData.value = overview;
|
||||
realtimeData.value = realtime;
|
||||
} catch (error) {
|
||||
console.error('Failed to load server monitor data:', error);
|
||||
} finally {
|
||||
if (showLoading) loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 自动刷新
|
||||
function startAutoRefresh() {
|
||||
if (timer) return;
|
||||
timer = setInterval(() => {
|
||||
getRealtimeStatsApi()
|
||||
.then((data) => {
|
||||
realtimeData.value = data;
|
||||
})
|
||||
.catch(() => {});
|
||||
}, refreshInterval.value);
|
||||
}
|
||||
|
||||
function stopAutoRefresh() {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData(true);
|
||||
startAutoRefresh();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopAutoRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="server-monitor flex h-full flex-col p-4" v-loading="loading">
|
||||
<!-- 头部 -->
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<span class="text-sm font-medium">{{ widget.props.title }}</span>
|
||||
<span class="text-muted-foreground text-xs">
|
||||
{{ serverData?.basic_info?.hostname || '-' }}
|
||||
({{ serverData?.basic_info?.ip_address || '-' }})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 指标网格 -->
|
||||
<div class="grid max-h-[150px] flex-1 grid-cols-2 gap-3 lg:grid-cols-4">
|
||||
<!-- CPU -->
|
||||
<div
|
||||
class="flex flex-col justify-between rounded-lg p-4"
|
||||
:class="{ 'bg-secondary/50': !widget.props.cpuBgColor }"
|
||||
:style="getAreaStyle('cpuBgColor')"
|
||||
>
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<div
|
||||
class="flex h-7 w-7 items-center justify-center rounded-md"
|
||||
style="background: rgba(59, 130, 246, 0.15)"
|
||||
>
|
||||
<Cpu class="h-4 w-4" style="color: var(--el-color-primary)" />
|
||||
</div>
|
||||
<span class="text-muted-foreground text-xs">CPU</span>
|
||||
</div>
|
||||
<div class="mb-1 text-xl font-bold">
|
||||
{{ realtimeData?.cpu_percent?.toFixed(1) || '0.0' }}%
|
||||
</div>
|
||||
<ElProgress
|
||||
:percentage="Number(realtimeData?.cpu_percent?.toFixed(1) || 0)"
|
||||
:color="getProgressColor(realtimeData?.cpu_percent || 0)"
|
||||
:show-text="false"
|
||||
:stroke-width="4"
|
||||
/>
|
||||
<div class="text-muted-foreground mt-1 text-xs">
|
||||
{{ serverData?.cpu_info?.physical_cores || 0
|
||||
}}{{ $t('dashboard-design.widgets.serverMonitor.core') }}
|
||||
{{ serverData?.cpu_info?.total_cores || 0
|
||||
}}{{ $t('dashboard-design.widgets.serverMonitor.thread') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内存 -->
|
||||
<div
|
||||
class="flex flex-col justify-between rounded-lg p-4"
|
||||
:class="{ 'bg-secondary/50': !widget.props.memoryBgColor }"
|
||||
:style="getAreaStyle('memoryBgColor')"
|
||||
>
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<div
|
||||
class="flex h-7 w-7 items-center justify-center rounded-md"
|
||||
style="background: rgba(34, 197, 94, 0.15)"
|
||||
>
|
||||
<Database class="h-4 w-4" style="color: var(--el-color-success)" />
|
||||
</div>
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
$t('dashboard-design.widgets.serverMonitor.memory')
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="mb-1 text-xl font-bold">
|
||||
{{ realtimeData?.memory_percent?.toFixed(1) || '0.0' }}%
|
||||
</div>
|
||||
<ElProgress
|
||||
:percentage="Number(realtimeData?.memory_percent?.toFixed(1) || 0)"
|
||||
:color="getProgressColor(realtimeData?.memory_percent || 0)"
|
||||
:show-text="false"
|
||||
:stroke-width="4"
|
||||
/>
|
||||
<div class="text-muted-foreground mt-1 text-xs">
|
||||
{{ formatMemory(realtimeData?.memory_details?.used || 0) }}
|
||||
/ {{ formatMemory(realtimeData?.memory_details?.total || 0) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 磁盘 -->
|
||||
<div
|
||||
class="flex flex-col justify-between rounded-lg p-4"
|
||||
:class="{ 'bg-secondary/50': !widget.props.diskBgColor }"
|
||||
:style="getAreaStyle('diskBgColor')"
|
||||
>
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<div
|
||||
class="flex h-7 w-7 items-center justify-center rounded-md"
|
||||
style="background: rgba(139, 92, 246, 0.15)"
|
||||
>
|
||||
<HardDrive class="h-4 w-4" style="color: var(--el-color-warning)" />
|
||||
</div>
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
$t('dashboard-design.widgets.serverMonitor.disk')
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="space-y-0.5 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
$t('dashboard-design.widgets.serverMonitor.read')
|
||||
}}</span>
|
||||
<span class="text-xs font-medium">{{
|
||||
formatSpeed(realtimeData?.disk_io?.read_speed || 0)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
$t('dashboard-design.widgets.serverMonitor.write')
|
||||
}}</span>
|
||||
<span class="text-xs font-medium">{{
|
||||
formatSpeed(realtimeData?.disk_io?.write_speed || 0)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted-foreground mt-1 text-xs">
|
||||
{{ $t('dashboard-design.widgets.serverMonitor.totalRW') }}:
|
||||
{{ formatBytes(realtimeData?.disk_total?.read_bytes || 0) }} /
|
||||
{{ formatBytes(realtimeData?.disk_total?.write_bytes || 0) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网络 -->
|
||||
<div
|
||||
class="flex flex-col justify-between rounded-lg p-4"
|
||||
:class="{ 'bg-secondary/50': !widget.props.networkBgColor }"
|
||||
:style="getAreaStyle('networkBgColor')"
|
||||
>
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<div
|
||||
class="flex h-7 w-7 items-center justify-center rounded-md"
|
||||
style="background: rgba(249, 115, 22, 0.15)"
|
||||
>
|
||||
<Network class="h-4 w-4" style="color: var(--el-color-danger)" />
|
||||
</div>
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
$t('dashboard-design.widgets.serverMonitor.network')
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="space-y-0.5 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
$t('dashboard-design.widgets.serverMonitor.upload')
|
||||
}}</span>
|
||||
<span class="text-xs font-medium">{{
|
||||
formatSpeed(realtimeData?.network_io?.upload_speed || 0)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
$t('dashboard-design.widgets.serverMonitor.download')
|
||||
}}</span>
|
||||
<span class="text-xs font-medium">{{
|
||||
formatSpeed(realtimeData?.network_io?.download_speed || 0)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted-foreground mt-1 text-xs">
|
||||
{{ $t('dashboard-design.widgets.serverMonitor.uptime') }}:
|
||||
{{ formatUptime(serverData?.boot_time?.uptime_seconds || 0) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,91 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import {
|
||||
Activity,
|
||||
Award,
|
||||
Bell,
|
||||
CreditCard,
|
||||
TrendingUp,
|
||||
Users,
|
||||
} from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
const props = defineProps<{
|
||||
showTitle?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
// 图标映射
|
||||
const iconMap: Record<string, any> = {
|
||||
TrendingUp,
|
||||
CreditCard,
|
||||
Users,
|
||||
Activity,
|
||||
Bell,
|
||||
Award,
|
||||
};
|
||||
|
||||
const iconComponent = computed(() => {
|
||||
return iconMap[props.widget.props.iconName] || TrendingUp;
|
||||
});
|
||||
|
||||
const trendClass = computed(() => {
|
||||
const trend = props.widget.props.trend || 0;
|
||||
if (trend > 0) return 'text-green-500';
|
||||
if (trend < 0) return 'text-red-500';
|
||||
return 'text-gray-500';
|
||||
});
|
||||
|
||||
const trendIcon = computed(() => {
|
||||
const trend = props.widget.props.trend || 0;
|
||||
if (trend > 0) return '↑';
|
||||
if (trend < 0) return '↓';
|
||||
return '';
|
||||
});
|
||||
|
||||
const formatValue = (value: number) => {
|
||||
if (value >= 10_000) {
|
||||
return `${(value / 10_000).toFixed(1)}${$t('dashboard-design.widgets.ranking.tenThousand')}`;
|
||||
}
|
||||
return value.toLocaleString();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="stat-card flex h-full flex-col justify-between p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<div class="text-muted-foreground text-sm">
|
||||
{{ widget.props.title }}
|
||||
</div>
|
||||
<div class="mt-2 text-2xl font-bold">
|
||||
{{ widget.props.prefix }}{{ formatValue(widget.props.value)
|
||||
}}{{ widget.props.suffix }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-lg"
|
||||
:style="{ backgroundColor: `${widget.props.iconColor}20` }"
|
||||
>
|
||||
<component
|
||||
:is="iconComponent"
|
||||
class="h-5 w-5"
|
||||
:style="{ color: widget.props.iconColor }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-1 text-sm">
|
||||
<span :class="trendClass">
|
||||
{{ trendIcon }} {{ Math.abs(widget.props.trend || 0) }}%
|
||||
</span>
|
||||
<span class="text-muted-foreground">{{ widget.props.trendLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景色由 WidgetRenderer 控制 */
|
||||
</style>
|
||||
@@ -1,81 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { CheckSquare } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElCheckbox, ElScrollbar, ElTag } from 'element-plus';
|
||||
|
||||
defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const getPriorityType = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'high': {
|
||||
return 'danger';
|
||||
}
|
||||
case 'low': {
|
||||
return 'info';
|
||||
}
|
||||
case 'medium': {
|
||||
return 'warning';
|
||||
}
|
||||
default: {
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getPriorityLabel = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'high': {
|
||||
return $t('dashboard-design.widgets.todo.priority.high');
|
||||
}
|
||||
case 'low': {
|
||||
return $t('dashboard-design.widgets.todo.priority.low');
|
||||
}
|
||||
case 'medium': {
|
||||
return $t('dashboard-design.widgets.todo.priority.medium');
|
||||
}
|
||||
default: {
|
||||
return priority;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="todo-list flex h-full flex-col p-3">
|
||||
<div class="mb-3 flex items-center gap-2">
|
||||
<CheckSquare class="text-muted-foreground h-4 w-4" />
|
||||
<span class="text-muted-foreground text-sm font-medium">{{
|
||||
widget.props.title
|
||||
}}</span>
|
||||
</div>
|
||||
<ElScrollbar class="flex-1">
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="item in widget.props.items"
|
||||
:key="item.id"
|
||||
class="flex items-center gap-2 rounded-md p-2 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
>
|
||||
<ElCheckbox :model-value="item.done" size="small" />
|
||||
<span
|
||||
class="flex-1 text-sm"
|
||||
:class="{ 'text-muted-foreground line-through': item.done }"
|
||||
>
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<ElTag :type="getPriorityType(item.priority)" size="small">
|
||||
{{ getPriorityLabel(item.priority) }}
|
||||
</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景色由 WidgetRenderer 控制 */
|
||||
</style>
|
||||
@@ -1,277 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Maximize, Pause, Play, Volume2, VolumeX } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { getFileUrl } from '#/composables/useFileUrl';
|
||||
|
||||
const props = defineProps<{
|
||||
isDesignMode?: boolean;
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const videoRef = ref<HTMLVideoElement | null>(null);
|
||||
const isPlaying = ref(false);
|
||||
const isMuted = ref(false);
|
||||
const currentTime = ref(0);
|
||||
const duration = ref(0);
|
||||
const progress = ref(0);
|
||||
|
||||
// 视频和封面URL(响应式)
|
||||
const videoUrl = ref('');
|
||||
const posterUrl = ref('');
|
||||
|
||||
// 异步解析URL
|
||||
async function resolveUrl(url: string): Promise<string> {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('file://')) {
|
||||
const fileId = url.slice(7);
|
||||
return await getFileUrl(fileId);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// 加载URL
|
||||
async function loadUrls() {
|
||||
videoUrl.value = await resolveUrl(props.widget.props.url || '');
|
||||
posterUrl.value = await resolveUrl(props.widget.props.poster || '');
|
||||
}
|
||||
|
||||
// 监听URL变化
|
||||
watch(
|
||||
() => [props.widget.props.url, props.widget.props.poster],
|
||||
() => {
|
||||
loadUrls();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const togglePlay = () => {
|
||||
if (!videoRef.value) return;
|
||||
|
||||
if (isPlaying.value) {
|
||||
videoRef.value.pause();
|
||||
} else {
|
||||
videoRef.value.play();
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
if (!videoRef.value) return;
|
||||
videoRef.value.muted = !videoRef.value.muted;
|
||||
isMuted.value = videoRef.value.muted;
|
||||
};
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
if (!videoRef.value) return;
|
||||
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
videoRef.value.requestFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
const handleTimeUpdate = () => {
|
||||
if (!videoRef.value) return;
|
||||
currentTime.value = videoRef.value.currentTime;
|
||||
progress.value = (currentTime.value / duration.value) * 100;
|
||||
};
|
||||
|
||||
const handleLoadedMetadata = () => {
|
||||
if (!videoRef.value) return;
|
||||
duration.value = videoRef.value.duration;
|
||||
};
|
||||
|
||||
const handlePlay = () => {
|
||||
isPlaying.value = true;
|
||||
};
|
||||
|
||||
const handlePause = () => {
|
||||
isPlaying.value = false;
|
||||
};
|
||||
|
||||
const handleSeek = (e: MouseEvent) => {
|
||||
if (!videoRef.value) return;
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
const rect = target.getBoundingClientRect();
|
||||
const percent = (e.clientX - rect.left) / rect.width;
|
||||
videoRef.value.currentTime = percent * duration.value;
|
||||
};
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (videoRef.value) {
|
||||
isMuted.value = props.widget.props.muted || false;
|
||||
videoRef.value.muted = isMuted.value;
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.widget.props.url,
|
||||
() => {
|
||||
if (videoRef.value) {
|
||||
videoRef.value.load();
|
||||
isPlaying.value = false;
|
||||
currentTime.value = 0;
|
||||
progress.value = 0;
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="video-player flex h-full flex-col">
|
||||
<div
|
||||
v-if="widget.props.title"
|
||||
class="text-muted-foreground p-2 text-sm font-medium"
|
||||
>
|
||||
{{ widget.props.title }}
|
||||
</div>
|
||||
|
||||
<div class="video-container relative min-h-0 flex-1 bg-black">
|
||||
<video
|
||||
ref="videoRef"
|
||||
:src="videoUrl"
|
||||
:poster="posterUrl"
|
||||
:autoplay="widget.props.autoplay && !isDesignMode"
|
||||
:loop="widget.props.loop"
|
||||
:controls="false"
|
||||
class="h-full w-full object-contain"
|
||||
@timeupdate="handleTimeUpdate"
|
||||
@loadedmetadata="handleLoadedMetadata"
|
||||
@play="handlePlay"
|
||||
@pause="handlePause"
|
||||
></video>
|
||||
|
||||
<!-- 自定义控制栏 -->
|
||||
<div v-if="!widget.props.controls" class="custom-controls">
|
||||
<button class="control-btn" @click="togglePlay">
|
||||
<Play v-if="!isPlaying" class="h-5 w-5" />
|
||||
<Pause v-else class="h-5 w-5" />
|
||||
</button>
|
||||
|
||||
<div class="progress-bar" @click="handleSeek">
|
||||
<div class="progress-fill" :style="{ width: `${progress}%` }"></div>
|
||||
</div>
|
||||
|
||||
<span class="time-display">
|
||||
{{ formatTime(currentTime) }} / {{ formatTime(duration) }}
|
||||
</span>
|
||||
|
||||
<button class="control-btn" @click="toggleMute">
|
||||
<VolumeX v-if="isMuted" class="h-4 w-4" />
|
||||
<Volume2 v-else class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<button class="control-btn" @click="toggleFullscreen">
|
||||
<Maximize class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 原生控制栏 -->
|
||||
<video
|
||||
v-if="widget.props.controls"
|
||||
ref="videoRef"
|
||||
:src="videoUrl"
|
||||
:poster="posterUrl"
|
||||
:autoplay="widget.props.autoplay && !isDesignMode"
|
||||
:loop="widget.props.loop"
|
||||
:muted="widget.props.muted"
|
||||
controls
|
||||
class="h-full w-full object-contain"
|
||||
></video>
|
||||
|
||||
<!-- 设计模式遮罩 -->
|
||||
<div v-if="isDesignMode" class="design-overlay">
|
||||
<Play class="h-12 w-12 text-white/80" />
|
||||
<div class="mt-2 text-sm text-white/80">
|
||||
{{ $t('dashboard-design.widgets.video.placeholder') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.video-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.custom-controls {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: linear-gradient(transparent, rgb(0 0 0 / 70%));
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.video-container:hover .custom-controls {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
background: rgb(255 255 255 / 20%);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: rgb(255 255 255 / 30%);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--el-color-primary);
|
||||
transition: width 0.1s;
|
||||
}
|
||||
|
||||
.time-display {
|
||||
min-width: 80px;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.design-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgb(0 0 0 / 50%);
|
||||
}
|
||||
</style>
|
||||
-364
@@ -1,364 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
Cloud,
|
||||
CloudDrizzle,
|
||||
CloudFog,
|
||||
CloudRain,
|
||||
CloudSnow,
|
||||
CloudSun,
|
||||
Sun,
|
||||
Zap,
|
||||
} from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
const props = defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
// 实时天气数据
|
||||
const weatherData = ref<null | {
|
||||
humidity: number;
|
||||
temperature: number;
|
||||
weatherCode: number;
|
||||
windDirection: number;
|
||||
windSpeed: number;
|
||||
}>(null);
|
||||
const error = ref(false);
|
||||
const locatedCityName = ref('');
|
||||
let timer: null | ReturnType<typeof setInterval> = null;
|
||||
|
||||
// 显示的城市名:优先手动配置,其次自动定位
|
||||
const displayCityName = computed(() => {
|
||||
return props.widget.props.cityName || locatedCityName.value || '-';
|
||||
});
|
||||
|
||||
// 反向地理编码获取城市名
|
||||
async function reverseGeocode(lat: number, lon: number) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lon}&localityLanguage=zh`,
|
||||
);
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
locatedCityName.value =
|
||||
data?.city || data?.locality || data?.principalSubdivision || '';
|
||||
} catch {
|
||||
// 反向编码失败不影响天气显示
|
||||
}
|
||||
}
|
||||
|
||||
// WMO Weather Code 映射
|
||||
function getWeatherInfo(code: number): {
|
||||
color: string;
|
||||
icon: any;
|
||||
label: string;
|
||||
} {
|
||||
// 晴天
|
||||
if (code === 0)
|
||||
return {
|
||||
icon: Sun,
|
||||
color: '#f59e0b',
|
||||
label: $t('dashboard-design.widgets.weather.codes.clear'),
|
||||
};
|
||||
// 少云/多云
|
||||
if (code === 1)
|
||||
return {
|
||||
icon: CloudSun,
|
||||
color: '#60a5fa',
|
||||
label: $t('dashboard-design.widgets.weather.codes.mainlyClear'),
|
||||
};
|
||||
if (code === 2)
|
||||
return {
|
||||
icon: CloudSun,
|
||||
color: '#60a5fa',
|
||||
label: $t('dashboard-design.widgets.weather.codes.partlyCloudy'),
|
||||
};
|
||||
if (code === 3)
|
||||
return {
|
||||
icon: Cloud,
|
||||
color: '#9ca3af',
|
||||
label: $t('dashboard-design.widgets.weather.codes.overcast'),
|
||||
};
|
||||
// 雾
|
||||
if (code === 45 || code === 48)
|
||||
return {
|
||||
icon: CloudFog,
|
||||
color: '#9ca3af',
|
||||
label: $t('dashboard-design.widgets.weather.codes.fog'),
|
||||
};
|
||||
// 毛毛雨
|
||||
if (code >= 51 && code <= 57)
|
||||
return {
|
||||
icon: CloudDrizzle,
|
||||
color: '#60a5fa',
|
||||
label: $t('dashboard-design.widgets.weather.codes.drizzle'),
|
||||
};
|
||||
// 雨
|
||||
if (code >= 61 && code <= 67)
|
||||
return {
|
||||
icon: CloudRain,
|
||||
color: '#3b82f6',
|
||||
label: $t('dashboard-design.widgets.weather.codes.rain'),
|
||||
};
|
||||
// 雪
|
||||
if (code >= 71 && code <= 77)
|
||||
return {
|
||||
icon: CloudSnow,
|
||||
color: '#a5b4fc',
|
||||
label: $t('dashboard-design.widgets.weather.codes.snow'),
|
||||
};
|
||||
// 阵雨
|
||||
if (code >= 80 && code <= 82)
|
||||
return {
|
||||
icon: CloudRain,
|
||||
color: '#3b82f6',
|
||||
label: $t('dashboard-design.widgets.weather.codes.showers'),
|
||||
};
|
||||
// 阵雪
|
||||
if (code >= 85 && code <= 86)
|
||||
return {
|
||||
icon: CloudSnow,
|
||||
color: '#a5b4fc',
|
||||
label: $t('dashboard-design.widgets.weather.codes.snowShowers'),
|
||||
};
|
||||
// 雷暴
|
||||
if (code >= 95 && code <= 99)
|
||||
return {
|
||||
icon: Zap,
|
||||
color: '#eab308',
|
||||
label: $t('dashboard-design.widgets.weather.codes.thunderstorm'),
|
||||
};
|
||||
return {
|
||||
icon: Sun,
|
||||
color: '#f59e0b',
|
||||
label: $t('dashboard-design.widgets.weather.codes.clear'),
|
||||
};
|
||||
}
|
||||
|
||||
// 风向角度转文字
|
||||
function getWindDirection(degree: number): string {
|
||||
const dirs = [
|
||||
$t('dashboard-design.widgets.weather.windDir.n'),
|
||||
$t('dashboard-design.widgets.weather.windDir.ne'),
|
||||
$t('dashboard-design.widgets.weather.windDir.e'),
|
||||
$t('dashboard-design.widgets.weather.windDir.se'),
|
||||
$t('dashboard-design.widgets.weather.windDir.s'),
|
||||
$t('dashboard-design.widgets.weather.windDir.sw'),
|
||||
$t('dashboard-design.widgets.weather.windDir.w'),
|
||||
$t('dashboard-design.widgets.weather.windDir.nw'),
|
||||
];
|
||||
const index = Math.round(degree / 45) % 8;
|
||||
return dirs[index] || '';
|
||||
}
|
||||
|
||||
// 当前天气信息
|
||||
const currentWeather = computed(() => {
|
||||
if (!weatherData.value) return null;
|
||||
const info = getWeatherInfo(weatherData.value.weatherCode);
|
||||
return {
|
||||
...info,
|
||||
temperature: Math.round(weatherData.value.temperature),
|
||||
humidity: weatherData.value.humidity,
|
||||
wind: `${getWindDirection(weatherData.value.windDirection)} ${weatherData.value.windSpeed.toFixed(0)} km/h`,
|
||||
};
|
||||
});
|
||||
|
||||
// 获取天气数据
|
||||
async function fetchWeather() {
|
||||
const lat = props.widget.props.latitude;
|
||||
const lon = props.widget.props.longitude;
|
||||
if (!lat || !lon) return;
|
||||
|
||||
try {
|
||||
error.value = false;
|
||||
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m,wind_direction_10m&timezone=auto`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error('API error');
|
||||
const data = await res.json();
|
||||
if (data.current) {
|
||||
weatherData.value = {
|
||||
temperature: data.current.temperature_2m,
|
||||
humidity: data.current.relative_humidity_2m,
|
||||
weatherCode: data.current.weather_code,
|
||||
windSpeed: data.current.wind_speed_10m,
|
||||
windDirection: data.current.wind_direction_10m,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
error.value = true;
|
||||
console.error('Failed to fetch weather data');
|
||||
}
|
||||
}
|
||||
|
||||
// 自动刷新
|
||||
function startAutoRefresh() {
|
||||
stopAutoRefresh();
|
||||
const interval = (props.widget.props.refreshInterval || 30) * 60 * 1000; // 分钟转毫秒
|
||||
timer = setInterval(fetchWeather, interval);
|
||||
}
|
||||
|
||||
function stopAutoRefresh() {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听经纬度变化重新获取
|
||||
watch(
|
||||
() => [props.widget.props.latitude, props.widget.props.longitude],
|
||||
() => {
|
||||
fetchWeather();
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
// 如果开启自动定位且没有经纬度
|
||||
if (props.widget.props.autoLocate && !props.widget.props.latitude) {
|
||||
try {
|
||||
const pos = await new Promise<GeolocationPosition>((resolve, reject) => {
|
||||
navigator.geolocation.getCurrentPosition(resolve, reject, {
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
const lat = pos.coords.latitude;
|
||||
const lon = pos.coords.longitude;
|
||||
// 并行获取天气数据和城市名
|
||||
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m,wind_direction_10m&timezone=auto`;
|
||||
const [res] = await Promise.all([fetch(url), reverseGeocode(lat, lon)]);
|
||||
const data = await res.json();
|
||||
if (data.current) {
|
||||
weatherData.value = {
|
||||
temperature: data.current.temperature_2m,
|
||||
humidity: data.current.relative_humidity_2m,
|
||||
weatherCode: data.current.weather_code,
|
||||
windSpeed: data.current.wind_speed_10m,
|
||||
windDirection: data.current.wind_direction_10m,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// 定位失败,使用默认经纬度
|
||||
await fetchWeather();
|
||||
}
|
||||
} else {
|
||||
await fetchWeather();
|
||||
}
|
||||
startAutoRefresh();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopAutoRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="weather-widget flex h-full flex-col p-3">
|
||||
<div
|
||||
v-if="widget.props.title"
|
||||
class="text-muted-foreground mb-2 text-sm font-medium"
|
||||
>
|
||||
{{ widget.props.title }}
|
||||
</div>
|
||||
|
||||
<!-- 加载/错误状态 -->
|
||||
<div
|
||||
v-if="!currentWeather && !error"
|
||||
class="flex flex-1 items-center justify-center"
|
||||
>
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
$t('dashboard-design.widgets.weather.loading')
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="error && !currentWeather"
|
||||
class="flex flex-1 items-center justify-center"
|
||||
>
|
||||
<span class="text-muted-foreground text-xs">{{
|
||||
$t('dashboard-design.widgets.weather.error')
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<!-- 天气数据 -->
|
||||
<div v-else-if="currentWeather" class="flex flex-1 items-center gap-4">
|
||||
<!-- 天气图标和温度 -->
|
||||
<div class="flex items-center gap-3">
|
||||
<component
|
||||
:is="currentWeather.icon"
|
||||
class="h-12 w-12"
|
||||
:style="{ color: currentWeather.color }"
|
||||
/>
|
||||
<div>
|
||||
<div class="temperature">{{ currentWeather.temperature }}°</div>
|
||||
<div class="weather-text">
|
||||
{{ currentWeather.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<div class="weather-details">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">{{
|
||||
$t('dashboard-design.widgets.weather.city')
|
||||
}}</span>
|
||||
<span class="detail-value">{{ displayCityName }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">{{
|
||||
$t('dashboard-design.widgets.weather.humidity')
|
||||
}}</span>
|
||||
<span class="detail-value">{{ currentWeather.humidity }}%</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">{{
|
||||
$t('dashboard-design.widgets.weather.wind')
|
||||
}}</span>
|
||||
<span class="detail-value">{{ currentWeather.wind }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.temperature {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.weather-text {
|
||||
margin-top: 4px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.weather-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding-left: 16px;
|
||||
border-left: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
min-width: 32px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -1,115 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { DashboardWidget } from '../../types';
|
||||
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
|
||||
import { Smile } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { UserAvatar } from '#/components/user-avatar';
|
||||
|
||||
defineProps<{
|
||||
widget: DashboardWidget;
|
||||
}>();
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 用户信息
|
||||
const userInfo = computed(() => userStore.userInfo);
|
||||
// 用户名
|
||||
const userName = computed(() => userStore.userInfo?.realName || '');
|
||||
|
||||
const currentTime = ref(new Date());
|
||||
let timer: null | ReturnType<typeof setInterval> = null;
|
||||
|
||||
onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
currentTime.value = new Date();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
});
|
||||
|
||||
const greeting = computed(() => {
|
||||
const hour = currentTime.value.getHours();
|
||||
if (hour < 6) return $t('dashboard-design.widgets.welcome.greeting.night');
|
||||
if (hour < 9) return $t('dashboard-design.widgets.welcome.greeting.morning');
|
||||
if (hour < 12)
|
||||
return $t('dashboard-design.widgets.welcome.greeting.morning2');
|
||||
if (hour < 14) return $t('dashboard-design.widgets.welcome.greeting.noon');
|
||||
if (hour < 18)
|
||||
return $t('dashboard-design.widgets.welcome.greeting.afternoon');
|
||||
if (hour < 22) return $t('dashboard-design.widgets.welcome.greeting.evening');
|
||||
return $t('dashboard-design.widgets.welcome.greeting.night');
|
||||
});
|
||||
|
||||
const formattedTime = computed(() => {
|
||||
return currentTime.value.toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
});
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
return currentTime.value.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
weekday: 'long',
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="welcome-card flex h-full items-center justify-between p-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- 用户头像 -->
|
||||
<UserAvatar
|
||||
v-if="userInfo"
|
||||
:name="userName"
|
||||
:avatar="userInfo.avatar"
|
||||
:size="48"
|
||||
:font-size="20"
|
||||
:show-popover="false"
|
||||
:shadow="false"
|
||||
class="flex-shrink-0"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full"
|
||||
style="
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--el-color-primary-light-3),
|
||||
var(--el-color-primary)
|
||||
);
|
||||
"
|
||||
>
|
||||
<Smile class="h-6 w-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-lg font-semibold">
|
||||
{{ greeting }},{{ userName
|
||||
}}{{ widget.props.title ? `,${widget.props.title}` : '' }}
|
||||
</div>
|
||||
<div class="text-muted-foreground text-sm">
|
||||
{{ widget.props.subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="widget.props.showTime" class="text-right">
|
||||
<div class="text-2xl font-bold tabular-nums">{{ formattedTime }}</div>
|
||||
<div class="text-muted-foreground text-sm">{{ formattedDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 背景色由 WidgetRenderer 控制 */
|
||||
</style>
|
||||
@@ -1,10 +0,0 @@
|
||||
export { default as DashboardRenderer } from './DashboardRenderer.vue';
|
||||
export type {
|
||||
DashboardConfig,
|
||||
DashboardWidget,
|
||||
DataSourceConfig,
|
||||
DataSourceType,
|
||||
WidgetStyle,
|
||||
WidgetType,
|
||||
} from './types';
|
||||
export { createRefreshTimer, fetchWidgetData } from './utils/dataFetcher';
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
const globalParams = ref<Record<string, any>>({});
|
||||
const globalParamsVersion = ref(0);
|
||||
|
||||
function updateGlobalParam(key: string, value: any) {
|
||||
globalParams.value = { ...globalParams.value, [key]: value };
|
||||
globalParamsVersion.value++;
|
||||
}
|
||||
|
||||
function clearGlobalParams() {
|
||||
globalParams.value = {};
|
||||
globalParamsVersion.value++;
|
||||
}
|
||||
|
||||
export function useDashboardRuntime() {
|
||||
return {
|
||||
clearGlobalParams,
|
||||
globalParams,
|
||||
globalParamsVersion,
|
||||
updateGlobalParam,
|
||||
};
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
export type DataSourceType = 'api' | 'dataSource' | 'static' | 'upload';
|
||||
|
||||
export interface FieldMapping {
|
||||
source: string;
|
||||
target: string;
|
||||
}
|
||||
|
||||
export interface ParamBinding {
|
||||
paramName: string;
|
||||
globalKey: string;
|
||||
}
|
||||
|
||||
export interface DataSourceConfig {
|
||||
type: DataSourceType;
|
||||
dataSourceCode?: string;
|
||||
apiUrl?: string;
|
||||
apiMethod?: 'GET' | 'POST';
|
||||
apiHeaders?: Record<string, string>;
|
||||
apiParams?: Record<string, any>;
|
||||
apiBody?: Record<string, any>;
|
||||
dataPath?: string;
|
||||
fieldMappings?: FieldMapping[];
|
||||
paramBindings?: ParamBinding[];
|
||||
refreshInterval?: number;
|
||||
refreshEnabled?: boolean;
|
||||
}
|
||||
|
||||
export type WidgetType =
|
||||
| 'announcement-list'
|
||||
| 'approval-center'
|
||||
| 'calendar'
|
||||
| 'chart-area'
|
||||
| 'chart-bar'
|
||||
| 'chart-funnel'
|
||||
| 'chart-gauge'
|
||||
| 'chart-heatmap'
|
||||
| 'chart-kline'
|
||||
| 'chart-line'
|
||||
| 'chart-pie'
|
||||
| 'chart-radar'
|
||||
| 'chart-ring'
|
||||
| 'chart-sankey'
|
||||
| 'chart-scatter'
|
||||
| 'clock'
|
||||
| 'countdown'
|
||||
| 'data-table'
|
||||
| 'filter-date'
|
||||
| 'filter-date-range'
|
||||
| 'filter-input'
|
||||
| 'filter-select'
|
||||
| 'form-render'
|
||||
| 'iframe'
|
||||
| 'image'
|
||||
| 'image-carousel'
|
||||
| 'my-apps'
|
||||
| 'notice-list'
|
||||
| 'progress-card'
|
||||
| 'quick-links'
|
||||
| 'ranking-list'
|
||||
| 'server-monitor'
|
||||
| 'stat-card'
|
||||
| 'todo-list'
|
||||
| 'video-player'
|
||||
| 'weather'
|
||||
| 'welcome-card';
|
||||
|
||||
export interface WidgetStyle {
|
||||
backgroundColor?: string;
|
||||
backgroundImage?: string;
|
||||
backgroundSize?: 'auto' | 'contain' | 'cover';
|
||||
borderWidth?: number;
|
||||
borderColor?: string;
|
||||
borderStyle?: 'dashed' | 'dotted' | 'none' | 'solid';
|
||||
borderRadius?: number;
|
||||
shadowEnabled?: boolean;
|
||||
shadowColor?: string;
|
||||
shadowBlur?: number;
|
||||
shadowOffsetX?: number;
|
||||
shadowOffsetY?: number;
|
||||
padding?: number;
|
||||
titleShow?: boolean;
|
||||
titleFontSize?: number;
|
||||
titleColor?: string;
|
||||
titleAlign?: 'center' | 'left' | 'right';
|
||||
titleFontWeight?: 'bold' | 'normal';
|
||||
}
|
||||
|
||||
export interface DashboardWidget {
|
||||
id: string;
|
||||
type: WidgetType;
|
||||
i: string;
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
minW?: number;
|
||||
minH?: number;
|
||||
maxW?: number;
|
||||
maxH?: number;
|
||||
title?: string;
|
||||
props: Record<string, any>;
|
||||
style?: WidgetStyle;
|
||||
dataSource?: DataSourceConfig;
|
||||
}
|
||||
|
||||
export interface DashboardConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
columns: number;
|
||||
rowHeight: number;
|
||||
margin: [number, number];
|
||||
backgroundColor?: string;
|
||||
showOuterMargin?: boolean;
|
||||
widgets: DashboardWidget[];
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
import type {
|
||||
DataSourceConfig,
|
||||
FieldMapping,
|
||||
} from '../types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 根据路径获取对象中的值
|
||||
* @param obj 对象
|
||||
* @param path 路径,如 'data.list' 或 'data.items[0].name'
|
||||
*/
|
||||
export function getValueByPath(obj: any, path: string): any {
|
||||
if (!obj || !path) return obj;
|
||||
|
||||
const keys = path.replaceAll(/\[(\d+)\]/g, '.$1').split('.');
|
||||
let result = obj;
|
||||
|
||||
for (const key of keys) {
|
||||
if (result === null || result === undefined) return undefined;
|
||||
result = result[key];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路径设置对象中的值
|
||||
*/
|
||||
export function setValueByPath(obj: any, path: string, value: any): void {
|
||||
if (!obj || !path) return;
|
||||
|
||||
const keys = path.split('.');
|
||||
let current = obj;
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i]!;
|
||||
if (current[key] === undefined) {
|
||||
current[key] = {};
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
|
||||
current[keys[keys.length - 1]!] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用字段映射
|
||||
*/
|
||||
export function applyFieldMappings(
|
||||
data: any,
|
||||
mappings: FieldMapping[] | undefined,
|
||||
targetProps: Record<string, any>,
|
||||
): Record<string, any> {
|
||||
if (!mappings || mappings.length === 0) {
|
||||
return { ...targetProps, ...data };
|
||||
}
|
||||
|
||||
const result = { ...targetProps };
|
||||
|
||||
for (const mapping of mappings) {
|
||||
const value = getValueByPath(data, mapping.source);
|
||||
if (value !== undefined) {
|
||||
setValueByPath(result, mapping.target, value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据获取工具
|
||||
*/
|
||||
export async function fetchWidgetData(
|
||||
dataSource: DataSourceConfig | undefined,
|
||||
defaultProps: Record<string, any>,
|
||||
params?: Record<string, any>,
|
||||
): Promise<{ data: any; props: Record<string, any> }> {
|
||||
if (!dataSource || dataSource.type === 'static') {
|
||||
return { data: null, props: defaultProps };
|
||||
}
|
||||
|
||||
// 通用数据源类型
|
||||
if (dataSource.type === 'dataSource' && dataSource.dataSourceCode) {
|
||||
try {
|
||||
const response = await requestClient.get(
|
||||
`/api/core/data-source/execute/${dataSource.dataSourceCode}`,
|
||||
{ params: params && Object.keys(params).length > 0 ? params : undefined },
|
||||
);
|
||||
|
||||
// 后端返回格式是 {data: ...},需要提取 data 字段
|
||||
const rawData = response?.data ?? response;
|
||||
|
||||
// 数据源返回的数据可能是数组或对象(图表数据源返回 {xAxisData, seriesData} 格式)
|
||||
const extractedData = dataSource.dataPath
|
||||
? getValueByPath(rawData, dataSource.dataPath)
|
||||
: rawData;
|
||||
|
||||
// 检查是否是图表数据格式(包含 xAxisData 或 seriesData 或 indicator 或 value)
|
||||
if (
|
||||
extractedData &&
|
||||
typeof extractedData === 'object' &&
|
||||
!Array.isArray(extractedData) &&
|
||||
('xAxisData' in extractedData ||
|
||||
'seriesData' in extractedData ||
|
||||
'indicator' in extractedData ||
|
||||
'yAxisData' in extractedData ||
|
||||
('value' in extractedData && 'max' in extractedData))
|
||||
) {
|
||||
// 图表数据格式,直接合并到 props
|
||||
const mappedProps = { ...defaultProps, ...extractedData };
|
||||
// 如果有额外的字段映射,也应用
|
||||
if (dataSource.fieldMappings && dataSource.fieldMappings.length > 0) {
|
||||
return {
|
||||
data: extractedData,
|
||||
props: applyFieldMappings(
|
||||
extractedData,
|
||||
dataSource.fieldMappings,
|
||||
mappedProps,
|
||||
),
|
||||
};
|
||||
}
|
||||
return { data: extractedData, props: mappedProps };
|
||||
}
|
||||
|
||||
// 普通数据,应用字段映射
|
||||
const mappedProps = applyFieldMappings(
|
||||
extractedData,
|
||||
dataSource.fieldMappings,
|
||||
defaultProps,
|
||||
);
|
||||
|
||||
return { data: extractedData, props: mappedProps };
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch data source:', error);
|
||||
return { data: null, props: defaultProps };
|
||||
}
|
||||
}
|
||||
|
||||
// API 类型
|
||||
if (dataSource.type === 'api' && dataSource.apiUrl) {
|
||||
try {
|
||||
const method = dataSource.apiMethod || 'GET';
|
||||
const params = dataSource.apiParams || {};
|
||||
const body = dataSource.apiBody || {};
|
||||
const headers = dataSource.apiHeaders || {};
|
||||
|
||||
let response: any;
|
||||
|
||||
response = await (method === 'GET'
|
||||
? requestClient.get(dataSource.apiUrl, {
|
||||
params,
|
||||
headers,
|
||||
})
|
||||
: requestClient.post(dataSource.apiUrl, body, {
|
||||
params,
|
||||
headers,
|
||||
}));
|
||||
|
||||
// 根据 dataPath 提取数据
|
||||
const extractedData = dataSource.dataPath
|
||||
? getValueByPath(response, dataSource.dataPath)
|
||||
: response;
|
||||
|
||||
// 应用字段映射
|
||||
const mappedProps = applyFieldMappings(
|
||||
extractedData,
|
||||
dataSource.fieldMappings,
|
||||
defaultProps,
|
||||
);
|
||||
|
||||
return { data: extractedData, props: mappedProps };
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch widget data:', error);
|
||||
return { data: null, props: defaultProps };
|
||||
}
|
||||
}
|
||||
|
||||
return { data: null, props: defaultProps };
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建自动刷新定时器
|
||||
*/
|
||||
export function createRefreshTimer(
|
||||
dataSource: DataSourceConfig | undefined,
|
||||
callback: () => void,
|
||||
): (() => void) | null {
|
||||
if (
|
||||
!dataSource?.refreshEnabled ||
|
||||
!dataSource.refreshInterval ||
|
||||
dataSource.refreshInterval <= 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timer = setInterval(callback, dataSource.refreshInterval * 1000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Maximize, Minimize } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElButton, ElInput } from 'element-plus';
|
||||
|
||||
export interface AppDesignData {
|
||||
type: 'app_design';
|
||||
title: string;
|
||||
data: {
|
||||
content: string;
|
||||
title: string;
|
||||
};
|
||||
nodeId?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
design?: AppDesignData;
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
confirm: [data: Record<string, any>];
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
const isFullscreen = ref(false);
|
||||
const editContent = ref('');
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
});
|
||||
|
||||
const panelTitle = computed(
|
||||
() => props.design?.data?.title || props.design?.title || '应用设计方案',
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.design,
|
||||
(design) => {
|
||||
editContent.value = design?.data?.content || '';
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function handleConfirm() {
|
||||
emit('confirm', {
|
||||
content: editContent.value,
|
||||
title: panelTitle.value,
|
||||
});
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
dialogVisible.value = false;
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="border-border bg-card flex flex-col rounded-lg"
|
||||
:class="[isFullscreen ? 'fixed inset-0 z-50 ml-0' : 'ml-3 h-full w-full']"
|
||||
>
|
||||
<div
|
||||
class="border-border bg-muted/50 flex shrink-0 items-center justify-between border-b px-4 py-3"
|
||||
>
|
||||
<div class="text-foreground font-medium">{{ panelTitle }}</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ElButton size="small" @click="handleClose">
|
||||
{{ $t('common.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton size="small" type="primary" @click="handleConfirm">
|
||||
{{ $t('common.confirmAndContinue') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
link
|
||||
:icon="isFullscreen ? Minimize : Maximize"
|
||||
:title="isFullscreen ? '退出全屏' : '全屏'"
|
||||
@click="isFullscreen = !isFullscreen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden p-4">
|
||||
<div class="rounded-md border bg-muted/30 p-3 text-sm text-muted-foreground">
|
||||
AI 已生成应用设计方案。确认前可以直接调整正文,确认后将继续后续编排节点。
|
||||
</div>
|
||||
<ElInput
|
||||
v-model="editContent"
|
||||
class="min-h-0 flex-1"
|
||||
resize="none"
|
||||
type="textarea"
|
||||
placeholder="请输入应用设计方案"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-textarea),
|
||||
:deep(.el-textarea__inner) {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,536 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 应用设置面板
|
||||
* 用于配置应用设置,包括应用配置、Logo配置、内置主题和布局
|
||||
*/
|
||||
import type { BuiltinThemeType, LayoutType, ThemeModeType } from '@vben/types';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Maximize, Minimize } from '@vben/icons';
|
||||
import {
|
||||
Block,
|
||||
BuiltinTheme,
|
||||
Layout,
|
||||
Theme,
|
||||
} from '@vben/layouts/preferences-blocks';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElDivider,
|
||||
ElInput,
|
||||
ElMessage,
|
||||
ElOption,
|
||||
ElScrollbar,
|
||||
ElSelect,
|
||||
ElSwitch,
|
||||
} from 'element-plus';
|
||||
|
||||
import { uploadFile } from '#/api/core/file';
|
||||
import { getFileUrlPublic } from '#/composables/useFileUrl';
|
||||
|
||||
// 应用设置数据接口
|
||||
export interface AppSettingsData {
|
||||
type: 'app_settings';
|
||||
title: string;
|
||||
data: {
|
||||
app?: {
|
||||
defaultHomePath?: string;
|
||||
dynamicTitle?: boolean;
|
||||
enableCheckUpdates?: boolean;
|
||||
enablePreferences?: boolean;
|
||||
layout?: string;
|
||||
locale?: string;
|
||||
name?: string;
|
||||
watermark?: boolean;
|
||||
watermarkContent?: string;
|
||||
};
|
||||
breadcrumb?: Record<string, any>;
|
||||
copyright?: Record<string, any>;
|
||||
footer?: Record<string, any>;
|
||||
header?: Record<string, any>;
|
||||
logo?: {
|
||||
enable?: boolean;
|
||||
fit?: string;
|
||||
source?: string;
|
||||
};
|
||||
navigation?: Record<string, any>;
|
||||
shortcutKeys?: Record<string, any>;
|
||||
sidebar?: Record<string, any>;
|
||||
tabbar?: Record<string, any>;
|
||||
theme?: {
|
||||
builtinType?: string;
|
||||
colorPrimary?: string;
|
||||
mode?: string;
|
||||
radius?: string;
|
||||
semiDarkHeader?: boolean;
|
||||
semiDarkSidebar?: boolean;
|
||||
};
|
||||
transition?: Record<string, any>;
|
||||
widget?: Record<string, any>;
|
||||
};
|
||||
nodeId?: string;
|
||||
layoutOptions?: Array<{ label: string; value: string }>;
|
||||
themeOptions?: Array<{ label: string; value: string }>;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
settings?: AppSettingsData;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
confirm: [data: Record<string, any>];
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
// 全屏状态
|
||||
const isFullscreen = ref(false);
|
||||
|
||||
// 切换全屏
|
||||
const toggleFullscreen = () => {
|
||||
isFullscreen.value = !isFullscreen.value;
|
||||
};
|
||||
|
||||
// 应用配置
|
||||
const appName = ref('');
|
||||
const appDefaultHomePath = ref('/analytics');
|
||||
const appEnablePreferences = ref(true);
|
||||
const appLayout = ref<LayoutType>('sidebar-nav');
|
||||
|
||||
// Logo配置
|
||||
const logoEnable = ref(true);
|
||||
const logoSource = ref('');
|
||||
const logoFit = ref('contain');
|
||||
const logoDisplayUrl = ref('');
|
||||
|
||||
// 主题配置
|
||||
const themeMode = ref<ThemeModeType>('light');
|
||||
const themeBuiltinType = ref<BuiltinThemeType>('default');
|
||||
const themeColorPrimary = ref('hsl(212 100% 45%)');
|
||||
const themeSemiDarkSidebar = ref(false);
|
||||
const themeSemiDarkHeader = ref(false);
|
||||
|
||||
// 适应方式选项
|
||||
const fitOptions = computed(() => [
|
||||
{ label: $t('ai-platform.appSettings.fitOptions.contain'), value: 'contain' },
|
||||
{ label: $t('ai-platform.appSettings.fitOptions.cover'), value: 'cover' },
|
||||
{ label: $t('ai-platform.appSettings.fitOptions.fill'), value: 'fill' },
|
||||
{ label: $t('ai-platform.appSettings.fitOptions.none'), value: 'none' },
|
||||
{
|
||||
label: $t('ai-platform.appSettings.fitOptions.scale-down'),
|
||||
value: 'scale-down',
|
||||
},
|
||||
]);
|
||||
|
||||
// 面板标题
|
||||
const panelTitle = computed(() => {
|
||||
return props.settings?.title || $t('ai-platform.appSettings.title');
|
||||
});
|
||||
|
||||
// 监听设置数据变化
|
||||
watch(
|
||||
() => props.settings,
|
||||
(newSettings) => {
|
||||
if (newSettings?.data) {
|
||||
const data = newSettings.data;
|
||||
|
||||
// 应用配置
|
||||
appName.value = data.app?.name || '';
|
||||
appDefaultHomePath.value = data.app?.defaultHomePath || '/analytics';
|
||||
appEnablePreferences.value = data.app?.enablePreferences ?? true;
|
||||
appLayout.value = (data.app?.layout || 'sidebar-nav') as LayoutType;
|
||||
|
||||
// Logo配置
|
||||
logoEnable.value = data.logo?.enable ?? true;
|
||||
logoSource.value = data.logo?.source || '';
|
||||
logoFit.value = data.logo?.fit || 'contain';
|
||||
loadLogoDisplayUrl(logoSource.value);
|
||||
|
||||
// 主题配置
|
||||
themeMode.value = (data.theme?.mode || 'light') as ThemeModeType;
|
||||
themeBuiltinType.value = (data.theme?.builtinType ||
|
||||
'default') as BuiltinThemeType;
|
||||
themeColorPrimary.value = data.theme?.colorPrimary || 'hsl(212 100% 45%)';
|
||||
themeSemiDarkSidebar.value = data.theme?.semiDarkSidebar ?? false;
|
||||
themeSemiDarkHeader.value = data.theme?.semiDarkHeader ?? false;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// 监听logoSource变化,加载显示URL
|
||||
watch(logoSource, (newSource) => {
|
||||
loadLogoDisplayUrl(newSource);
|
||||
});
|
||||
|
||||
// 判断是否为文件ID
|
||||
function isFileId(value: string): boolean {
|
||||
return (
|
||||
/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(value) ||
|
||||
/^\d+$/.test(value) ||
|
||||
/^[\w-]{10,30}$/.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
// 加载Logo显示URL
|
||||
function loadLogoDisplayUrl(source: string) {
|
||||
if (!source) {
|
||||
logoDisplayUrl.value = '';
|
||||
return;
|
||||
}
|
||||
if (
|
||||
source.startsWith('http://') ||
|
||||
source.startsWith('https://') ||
|
||||
source.startsWith('/') ||
|
||||
source.startsWith('data:')
|
||||
) {
|
||||
logoDisplayUrl.value = source;
|
||||
return;
|
||||
}
|
||||
if (isFileId(source)) {
|
||||
logoDisplayUrl.value = getFileUrlPublic(source);
|
||||
return;
|
||||
}
|
||||
logoDisplayUrl.value = source;
|
||||
}
|
||||
|
||||
// 文件上传
|
||||
const uploadInputRef = ref<HTMLInputElement>();
|
||||
|
||||
function openFileSelector() {
|
||||
uploadInputRef.value?.click();
|
||||
}
|
||||
|
||||
async function handleFileInputChange(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const file = target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
const response = await uploadFile(file, {
|
||||
isPublic: true,
|
||||
source: 'avatar',
|
||||
});
|
||||
if (response?.id) {
|
||||
logoSource.value = getFileUrlPublic(String(response.id));
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error($t('ui-config.loadError'));
|
||||
}
|
||||
target.value = '';
|
||||
}
|
||||
|
||||
function clearLogo() {
|
||||
logoSource.value = '';
|
||||
}
|
||||
|
||||
// 构建设置数据
|
||||
function buildSettingsData(): Record<string, any> {
|
||||
return {
|
||||
app: {
|
||||
name: appName.value,
|
||||
defaultHomePath: appDefaultHomePath.value,
|
||||
enablePreferences: appEnablePreferences.value,
|
||||
layout: appLayout.value,
|
||||
},
|
||||
logo: {
|
||||
enable: logoEnable.value,
|
||||
source: logoSource.value,
|
||||
fit: logoFit.value,
|
||||
},
|
||||
theme: {
|
||||
mode: themeMode.value,
|
||||
builtinType: themeBuiltinType.value,
|
||||
colorPrimary: themeColorPrimary.value,
|
||||
semiDarkSidebar: themeSemiDarkSidebar.value,
|
||||
semiDarkHeader: themeSemiDarkHeader.value,
|
||||
},
|
||||
// 保留原有的其他配置
|
||||
sidebar: props.settings?.data?.sidebar,
|
||||
header: props.settings?.data?.header,
|
||||
footer: props.settings?.data?.footer,
|
||||
copyright: props.settings?.data?.copyright,
|
||||
navigation: props.settings?.data?.navigation,
|
||||
tabbar: props.settings?.data?.tabbar,
|
||||
breadcrumb: props.settings?.data?.breadcrumb,
|
||||
transition: props.settings?.data?.transition,
|
||||
widget: props.settings?.data?.widget,
|
||||
shortcutKeys: props.settings?.data?.shortcutKeys,
|
||||
};
|
||||
}
|
||||
|
||||
// 确认并继续
|
||||
function handleConfirm() {
|
||||
emit('confirm', buildSettingsData());
|
||||
}
|
||||
|
||||
// 关闭
|
||||
function handleClose() {
|
||||
emit('update:visible', false);
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="app-settings-panel border-border bg-card flex flex-col rounded-lg"
|
||||
:class="[isFullscreen ? 'fixed inset-0 z-50 ml-0' : 'ml-3 h-full w-full']"
|
||||
>
|
||||
<!-- 头部 -->
|
||||
<div
|
||||
class="border-border bg-muted/50 flex items-center justify-between border-b px-4 py-3"
|
||||
>
|
||||
<div class="text-foreground font-medium">
|
||||
{{ panelTitle }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ElButton size="small" @click="handleClose">
|
||||
{{ $t('common.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton size="small" type="primary" @click="handleConfirm">
|
||||
{{ $t('common.confirmAndContinue') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
link
|
||||
:icon="isFullscreen ? Minimize : Maximize"
|
||||
:title="isFullscreen ? '退出全屏' : '全屏'"
|
||||
@click="toggleFullscreen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<ElScrollbar class="flex-1">
|
||||
<div class="grid grid-cols-2 gap-6 p-6">
|
||||
<!-- 左列:应用配置 + Logo配置 -->
|
||||
<div class="space-y-6">
|
||||
<!-- 应用配置 -->
|
||||
<div class="config-section">
|
||||
<h4 class="mb-4 text-sm font-semibold">
|
||||
{{ $t('ai-platform.appSettings.appConfig') }}
|
||||
</h4>
|
||||
<div class="space-y-4">
|
||||
<Block :title="$t('ai-platform.appSettings.appName')">
|
||||
<ElInput
|
||||
v-model="appName"
|
||||
:placeholder="
|
||||
$t('ai-platform.appSettings.appNamePlaceholder')
|
||||
"
|
||||
clearable
|
||||
/>
|
||||
</Block>
|
||||
|
||||
<Block :title="$t('ai-platform.appSettings.defaultHomePath')">
|
||||
<ElInput
|
||||
v-model="appDefaultHomePath"
|
||||
:placeholder="
|
||||
$t('ai-platform.appSettings.defaultHomePathPlaceholder')
|
||||
"
|
||||
clearable
|
||||
/>
|
||||
</Block>
|
||||
|
||||
<Block :title="$t('ai-platform.appSettings.enablePreferences')">
|
||||
<ElSwitch v-model="appEnablePreferences" />
|
||||
</Block>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ElDivider />
|
||||
|
||||
<!-- Logo配置 -->
|
||||
<div class="config-section">
|
||||
<h4 class="mb-4 text-sm font-semibold">
|
||||
{{ $t('ai-platform.appSettings.logoConfig') }}
|
||||
</h4>
|
||||
<div class="space-y-4">
|
||||
<Block :title="$t('ai-platform.appSettings.logoEnable')">
|
||||
<ElSwitch v-model="logoEnable" />
|
||||
</Block>
|
||||
|
||||
<template v-if="logoEnable">
|
||||
<Block :title="$t('ai-platform.appSettings.logoSource')">
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- Logo 预览和上传 -->
|
||||
<div class="logo-preview-container flex">
|
||||
<div class="flex items-center gap-4">
|
||||
<div>
|
||||
<div
|
||||
v-if="logoDisplayUrl"
|
||||
class="logo-preview"
|
||||
@click="openFileSelector"
|
||||
>
|
||||
<img
|
||||
:src="logoDisplayUrl"
|
||||
alt="Logo"
|
||||
class="logo-image"
|
||||
/>
|
||||
<div class="logo-overlay">
|
||||
<span class="text-sm text-white">{{
|
||||
$t('common.replace')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="logo-placeholder"
|
||||
@click="openFileSelector"
|
||||
>
|
||||
<span class="text-muted-foreground text-sm">{{
|
||||
$t(
|
||||
'ai-platform.appSettings.logoSourcePlaceholder',
|
||||
)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ElButton
|
||||
v-if="logoDisplayUrl"
|
||||
size="small"
|
||||
@click="clearLogo"
|
||||
>
|
||||
{{ $t('common.clear') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref="uploadInputRef"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
@change="handleFileInputChange"
|
||||
/>
|
||||
<ElInput
|
||||
v-model="logoSource"
|
||||
:placeholder="
|
||||
$t('ai-platform.appSettings.logoSourcePlaceholder')
|
||||
"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
|
||||
<Block :title="$t('ai-platform.appSettings.logoFit')">
|
||||
<ElSelect v-model="logoFit" class="w-full">
|
||||
<ElOption
|
||||
v-for="item in fitOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</Block>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右列:布局配置 + 主题配置 -->
|
||||
<div class="space-y-6">
|
||||
<!-- 布局配置 -->
|
||||
<div class="config-section">
|
||||
<h4 class="mb-4 text-sm font-semibold">
|
||||
{{ $t('ai-platform.appSettings.layoutConfig') }}
|
||||
</h4>
|
||||
<div class="space-y-4">
|
||||
<Block :title="$t('ai-platform.appSettings.layout')">
|
||||
<Layout v-model="appLayout" />
|
||||
</Block>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ElDivider />
|
||||
|
||||
<!-- 主题配置 -->
|
||||
<div class="config-section">
|
||||
<h4 class="mb-4 text-sm font-semibold">
|
||||
{{ $t('ai-platform.appSettings.themeConfig') }}
|
||||
</h4>
|
||||
<div class="space-y-4">
|
||||
<Block :title="$t('ai-platform.appSettings.themeMode')">
|
||||
<Theme
|
||||
v-model="themeMode"
|
||||
v-model:theme-semi-dark-sidebar="themeSemiDarkSidebar"
|
||||
v-model:theme-semi-dark-header="themeSemiDarkHeader"
|
||||
/>
|
||||
</Block>
|
||||
|
||||
<Block :title="$t('ai-platform.appSettings.builtinTheme')">
|
||||
<BuiltinTheme
|
||||
v-model="themeBuiltinType"
|
||||
v-model:theme-color-primary="themeColorPrimary"
|
||||
:is-dark="themeMode === 'dark'"
|
||||
/>
|
||||
</Block>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-settings-panel {
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
.logo-preview-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.logo-preview {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.logo-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.logo-preview:hover .logo-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.logo-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 2px dashed hsl(var(--border));
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.logo-placeholder:hover {
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
</style>
|
||||
@@ -1,117 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 表单基础信息编辑组件
|
||||
* 可用于:表单管理器、工作流设计预览编辑
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElOption,
|
||||
ElSelect,
|
||||
} from 'element-plus';
|
||||
|
||||
export interface BasicFormData {
|
||||
name: string;
|
||||
code: string;
|
||||
form_type: 'normal' | 'workflow';
|
||||
sort: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
modelValue: BasicFormData;
|
||||
disabled?: boolean;
|
||||
showTitle?: boolean;
|
||||
labelWidth?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
showTitle: true,
|
||||
labelWidth: '100px',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: BasicFormData];
|
||||
}>();
|
||||
|
||||
const formData = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
});
|
||||
|
||||
// 更新单个字段
|
||||
function updateField<K extends keyof BasicFormData>(key: K, value: BasicFormData[K]) {
|
||||
emit('update:modelValue', { ...props.modelValue, [key]: value });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="basic-info-editor">
|
||||
<h3 v-if="showTitle" class="mb-6 text-center text-lg font-medium">
|
||||
{{ $t('form-manager.editor.steps.basic') }}
|
||||
</h3>
|
||||
<ElForm
|
||||
:model="formData"
|
||||
:label-width="labelWidth"
|
||||
label-position="right"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<ElFormItem :label="$t('form-manager.name')" required>
|
||||
<ElInput
|
||||
:model-value="formData.name"
|
||||
:placeholder="$t('form-manager.placeholder.name')"
|
||||
clearable
|
||||
@update:model-value="updateField('name', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('form-manager.code')" required>
|
||||
<ElInput
|
||||
:model-value="formData.code"
|
||||
:placeholder="$t('form-manager.placeholder.code')"
|
||||
clearable
|
||||
@update:model-value="updateField('code', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('form-manager.type')" required>
|
||||
<ElSelect
|
||||
:model-value="formData.form_type"
|
||||
:placeholder="$t('form-manager.placeholder.type')"
|
||||
class="w-full"
|
||||
@update:model-value="updateField('form_type', $event)"
|
||||
>
|
||||
<ElOption :label="$t('form-manager.typeMap.normal')" value="normal" />
|
||||
<ElOption :label="$t('form-manager.typeMap.workflow')" value="workflow" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('common.sort')">
|
||||
<ElInput
|
||||
:model-value="formData.sort"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
@update:model-value="updateField('sort', Number($event))"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('form-manager.description')">
|
||||
<ElInput
|
||||
:model-value="formData.description"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:placeholder="$t('form-manager.editor.placeholder.remark')"
|
||||
@update:model-value="updateField('description', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.basic-info-editor {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,122 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 仪表盘基础信息确认面板
|
||||
* 复用 PageBasicInfoEditor 公共组件
|
||||
*/
|
||||
import type { PageBasicInfo } from './PageBasicInfoEditor.vue';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Maximize, Minimize } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElButton } from 'element-plus';
|
||||
|
||||
import PageBasicInfoEditor from './PageBasicInfoEditor.vue';
|
||||
|
||||
export interface DashboardBasicInfoData {
|
||||
type: string;
|
||||
title: string;
|
||||
data: {
|
||||
category: string;
|
||||
code: string;
|
||||
description: string;
|
||||
name: string;
|
||||
sort: number;
|
||||
};
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
basicInfo?: DashboardBasicInfoData;
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
confirm: [data: Record<string, any>];
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
// 全屏状态
|
||||
const isFullscreen = ref(false);
|
||||
|
||||
// 切换全屏
|
||||
const toggleFullscreen = () => {
|
||||
isFullscreen.value = !isFullscreen.value;
|
||||
};
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
});
|
||||
|
||||
const form = ref<PageBasicInfo>({
|
||||
name: '',
|
||||
code: '',
|
||||
category: 'dashboard',
|
||||
description: '',
|
||||
sort: 0,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.basicInfo,
|
||||
(info) => {
|
||||
if (info?.data) {
|
||||
form.value = {
|
||||
name: info.data.name || '',
|
||||
code: info.data.code || '',
|
||||
category: info.data.category || 'dashboard',
|
||||
description: info.data.description || '',
|
||||
sort: info.data.sort || 0,
|
||||
};
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', { ...form.value });
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false;
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="border-border bg-card flex flex-col rounded-lg"
|
||||
:class="[isFullscreen ? 'fixed inset-0 z-50 ml-0' : 'ml-3 h-full w-full']"
|
||||
>
|
||||
<!-- 头部 -->
|
||||
<div
|
||||
class="border-border bg-muted/50 flex items-center justify-between border-b px-4 py-3"
|
||||
>
|
||||
<div class="text-foreground font-medium">
|
||||
{{ basicInfo?.title || $t('ai-platform.dashboard.basicInfo.title') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ElButton size="small" @click="handleClose">
|
||||
{{ $t('common.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton size="small" type="primary" @click="handleConfirm">
|
||||
{{ $t('common.confirmAndContinue') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
link
|
||||
:icon="isFullscreen ? Minimize : Maximize"
|
||||
:title="isFullscreen ? '退出全屏' : '全屏'"
|
||||
@click="toggleFullscreen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<PageBasicInfoEditor v-model="form" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,137 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Maximize, Minimize } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElButton, ElInput, ElMessage } from 'element-plus';
|
||||
|
||||
export interface DashboardDesignData {
|
||||
type: string;
|
||||
title: string;
|
||||
data: {
|
||||
dashboard_code?: string;
|
||||
design_suggestion?: string;
|
||||
design_title?: string;
|
||||
page_config?: Record<string, any>;
|
||||
};
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
design?: DashboardDesignData;
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
confirm: [data: Record<string, any>];
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
const isFullscreen = ref(false);
|
||||
const configText = ref('{}');
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.design,
|
||||
(design) => {
|
||||
configText.value = JSON.stringify(design?.data?.page_config || {}, null, 2);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function handleConfirm() {
|
||||
try {
|
||||
emit('confirm', {
|
||||
design_title:
|
||||
props.design?.data?.design_title ||
|
||||
$t('ai-platform.dashboard.design.title'),
|
||||
page_config: JSON.parse(configText.value || '{}'),
|
||||
});
|
||||
} catch {
|
||||
ElMessage.error('页面配置不是有效 JSON');
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
dialogVisible.value = false;
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="border-border bg-card flex flex-col rounded-lg"
|
||||
:class="[isFullscreen ? 'fixed inset-0 z-50 ml-0' : 'ml-3 h-full w-full']"
|
||||
>
|
||||
<div
|
||||
class="border-border bg-muted/50 flex shrink-0 items-center justify-between border-b px-4 py-3"
|
||||
>
|
||||
<div class="text-foreground flex items-center gap-3 font-medium">
|
||||
<div class="bg-primary flex h-8 w-8 items-center justify-center rounded">
|
||||
<span class="text-sm font-bold text-white">D</span>
|
||||
</div>
|
||||
<span>{{ design?.title || $t('ai-platform.dashboard.design.title') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ElButton size="small" @click="handleClose">
|
||||
{{ $t('common.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton size="small" type="primary" @click="handleConfirm">
|
||||
{{ $t('common.confirmAndContinue') }}
|
||||
</ElButton>
|
||||
<div class="bg-border mx-1 h-5 w-px"></div>
|
||||
<ElButton
|
||||
link
|
||||
:icon="isFullscreen ? Minimize : Maximize"
|
||||
:title="isFullscreen ? '退出全屏' : '全屏'"
|
||||
@click="isFullscreen = !isFullscreen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid min-h-0 flex-1 grid-cols-[320px_minmax(0,1fr)] gap-4 overflow-hidden p-4">
|
||||
<div class="space-y-4 overflow-auto rounded-md border bg-muted/20 p-4">
|
||||
<div>
|
||||
<div class="text-sm font-medium">仪表盘编码</div>
|
||||
<div class="mt-1 text-sm text-muted-foreground">
|
||||
{{ design?.data?.dashboard_code || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-medium">设计标题</div>
|
||||
<div class="mt-1 text-sm text-muted-foreground">
|
||||
{{ design?.data?.design_title || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-medium">设计建议</div>
|
||||
<div class="mt-1 whitespace-pre-wrap text-sm text-muted-foreground">
|
||||
{{ design?.data?.design_suggestion || '暂无设计建议' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ElInput
|
||||
v-model="configText"
|
||||
class="min-h-0"
|
||||
resize="none"
|
||||
type="textarea"
|
||||
placeholder="页面配置 JSON"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-textarea),
|
||||
:deep(.el-textarea__inner) {
|
||||
height: 100%;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -1,137 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 仪表盘发布确认面板
|
||||
* 复用 PagePublishInfoEditor 公共组件
|
||||
*/
|
||||
import type { PagePublishInfo } from './PagePublishInfoEditor.vue';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { Maximize, Minimize } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElButton } from 'element-plus';
|
||||
|
||||
import PagePublishInfoEditor from './PagePublishInfoEditor.vue';
|
||||
|
||||
export interface DashboardPublishData {
|
||||
type: string;
|
||||
title: string;
|
||||
data: {
|
||||
application_id?: string;
|
||||
dashboard_code: string;
|
||||
dashboard_id: string;
|
||||
menu_icon: string;
|
||||
menu_name: string;
|
||||
menu_order: number;
|
||||
menu_parent_id?: string;
|
||||
};
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
publishData?: DashboardPublishData;
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
confirm: [data: Record<string, any>];
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
// 全屏状态
|
||||
const isFullscreen = ref(false);
|
||||
|
||||
// 切换全屏
|
||||
const toggleFullscreen = () => {
|
||||
isFullscreen.value = !isFullscreen.value;
|
||||
};
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
});
|
||||
|
||||
const form = ref<PagePublishInfo>({
|
||||
menu_name: '',
|
||||
menu_parent_id: undefined,
|
||||
menu_icon: 'lucide:layout-dashboard',
|
||||
menu_order: 0,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.publishData,
|
||||
(data) => {
|
||||
if (data?.data) {
|
||||
form.value = {
|
||||
menu_name: data.data.menu_name || '',
|
||||
menu_parent_id: data.data.menu_parent_id || undefined,
|
||||
menu_icon: data.data.menu_icon || 'lucide:layout-dashboard',
|
||||
menu_order: data.data.menu_order || 0,
|
||||
};
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const handleConfirm = () => {
|
||||
const confirmData = {
|
||||
dashboard_id: props.publishData?.data.dashboard_id,
|
||||
dashboard_code: props.publishData?.data.dashboard_code,
|
||||
menu_name: form.value.menu_name,
|
||||
menu_parent_id: form.value.menu_parent_id || '',
|
||||
menu_icon: form.value.menu_icon,
|
||||
menu_order: form.value.menu_order,
|
||||
application_id: props.publishData?.data.application_id || '',
|
||||
};
|
||||
console.log('[DashboardPublishConfirmPanel] 确认数据:', confirmData);
|
||||
emit('confirm', confirmData);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false;
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="border-border bg-card flex flex-col rounded-lg"
|
||||
:class="[isFullscreen ? 'fixed inset-0 z-50 ml-0' : 'ml-3 h-full w-full']"
|
||||
>
|
||||
<!-- 头部 -->
|
||||
<div
|
||||
class="border-border bg-muted/50 flex items-center justify-between border-b px-4 py-3"
|
||||
>
|
||||
<div class="text-foreground font-medium">
|
||||
{{ publishData?.title || $t('ai-platform.dashboard.publish.title') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ElButton size="small" @click="handleClose">
|
||||
{{ $t('common.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton size="small" type="primary" @click="handleConfirm">
|
||||
{{ $t('common.confirmAndContinue') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
link
|
||||
:icon="isFullscreen ? Minimize : Maximize"
|
||||
:title="isFullscreen ? '退出全屏' : '全屏'"
|
||||
@click="toggleFullscreen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<PagePublishInfoEditor
|
||||
v-model="form"
|
||||
:page-code="publishData?.data.dashboard_code || ''"
|
||||
:show-route-info="true"
|
||||
route-prefix="/page-render/"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,288 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 设计编辑面板
|
||||
* 用于工作流中每步设计完成后的编辑
|
||||
* 使用 FormEditorContent 公共组件,根据当前步骤显示对应内容
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { Maximize, Minimize } from '@vben/icons';
|
||||
|
||||
import { ElButton } from 'element-plus';
|
||||
|
||||
import FormEditorContent from './FormEditorContent.vue';
|
||||
|
||||
// 设计类型
|
||||
export type DesignType = 'form_basic_info' | 'database_design' | 'form_ui_design' | 'list_config' | 'form_publish' | 'dashboard_basic_info' | 'dashboard_design' | 'dashboard_publish';
|
||||
|
||||
// 设计数据
|
||||
export interface DesignData {
|
||||
type: DesignType;
|
||||
title: string;
|
||||
data: Record<string, any>;
|
||||
nodeId: string;
|
||||
form_fields?: any[]; // 表单字段列表(用于列表设计)
|
||||
table_configs?: any[]; // 数据表配置(用于表单设计)
|
||||
}
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
design?: DesignData;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean];
|
||||
'save': [data: Record<string, any>];
|
||||
'confirm': [data: Record<string, any>];
|
||||
'close': [];
|
||||
}>();
|
||||
|
||||
// 编辑器组件引用
|
||||
const editorRef = ref<InstanceType<typeof FormEditorContent>>();
|
||||
|
||||
// 根据设计类型映射到步骤索引
|
||||
const stepMap: Record<DesignType, number> = {
|
||||
'form_basic_info': 0,
|
||||
'database_design': 1,
|
||||
'form_ui_design': 2,
|
||||
'list_config': 3,
|
||||
'form_publish': 4,
|
||||
'dashboard_basic_info': 0,
|
||||
'dashboard_design': 0,
|
||||
'dashboard_publish': 0,
|
||||
};
|
||||
|
||||
// 当前步骤
|
||||
const currentStep = ref(0);
|
||||
|
||||
// 面板标题
|
||||
const panelTitle = computed(() => {
|
||||
if (!props.design) return '';
|
||||
return props.design.title;
|
||||
});
|
||||
|
||||
// 全屏状态
|
||||
const isFullscreen = ref(false);
|
||||
|
||||
// 切换全屏
|
||||
const toggleFullscreen = () => {
|
||||
isFullscreen.value = !isFullscreen.value;
|
||||
};
|
||||
|
||||
// 监听设计数据变化,设置当前步骤和初始化数据
|
||||
watch(
|
||||
() => props.design,
|
||||
(newDesign) => {
|
||||
if (!newDesign) return;
|
||||
|
||||
// 根据设计类型设置当前步骤
|
||||
currentStep.value = stepMap[newDesign.type] ?? 0;
|
||||
|
||||
// 初始化编辑器数据
|
||||
if (editorRef.value) {
|
||||
initEditorData(newDesign);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听编辑器引用变化,初始化数据
|
||||
watch(
|
||||
() => editorRef.value,
|
||||
(editor) => {
|
||||
if (editor && props.design) {
|
||||
initEditorData(props.design);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 初始化编辑器数据
|
||||
function initEditorData(design: DesignData) {
|
||||
if (!editorRef.value) return;
|
||||
|
||||
switch (design.type) {
|
||||
case 'form_basic_info': {
|
||||
const data = design.data || {};
|
||||
editorRef.value.setData({
|
||||
basicForm: {
|
||||
name: data.name || '',
|
||||
code: data.code || '',
|
||||
form_type: data.form_type || 'normal',
|
||||
sort: data.sort || 0,
|
||||
description: data.description || '',
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'database_design': {
|
||||
const tableData = design.data?.table || design.data || {};
|
||||
// 优先从 meta 中读取 schema,兼容旧数据从顶层读取
|
||||
const metaData = tableData.meta || {};
|
||||
editorRef.value.setData({
|
||||
tableConfigs: [{
|
||||
id: 'main-table',
|
||||
type: 'main',
|
||||
tableName: tableData.tableName || '',
|
||||
alias: tableData.alias || tableData.tableName || '',
|
||||
fields: tableData.fields || [],
|
||||
meta: {
|
||||
schema: metaData.schema || tableData.schema || '',
|
||||
schemaRaw: metaData.schemaRaw || tableData.schemaRaw || '',
|
||||
database: metaData.database || tableData.database || '',
|
||||
},
|
||||
}],
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'form_ui_design': {
|
||||
const formConfig = design.data || {};
|
||||
// 从 design 中获取 table_configs(后端在 waiting_config 中传递)
|
||||
const tableConfigs = design.table_configs || design.data?.table_configs || [];
|
||||
editorRef.value.setData({
|
||||
tableConfigs: tableConfigs,
|
||||
formConfig: {
|
||||
items: formConfig.items || [],
|
||||
labelWidth: formConfig.labelWidth || 120,
|
||||
labelPosition: formConfig.labelPosition || 'right',
|
||||
size: formConfig.size || 'default',
|
||||
formPadding: formConfig.formPadding || 20,
|
||||
formMargin: formConfig.formMargin || 0,
|
||||
itemSpacing: formConfig.itemSpacing || 18,
|
||||
formWidth: formConfig.formWidth || '100%',
|
||||
formMaxWidth: formConfig.formMaxWidth || 0,
|
||||
formBackground: formConfig.formBackground || false,
|
||||
formBorder: formConfig.formBorder || false,
|
||||
formBorderRadius: formConfig.formBorderRadius || 4,
|
||||
formShadow: formConfig.formShadow || false,
|
||||
disabled: formConfig.disabled || false,
|
||||
tableConfigs: tableConfigs,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'list_config': {
|
||||
// 从 design 中获取 form_fields(后端在 waiting_config 中传递)
|
||||
const formFields = design.form_fields || design.data?.form_fields || [];
|
||||
editorRef.value.setData({
|
||||
listConfig: design.data || {},
|
||||
formFields: formFields,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'form_publish': {
|
||||
const publishData = design.data || {};
|
||||
editorRef.value.setData({
|
||||
publishData: {
|
||||
menu_name: publishData.menu_name || '',
|
||||
menu_parent_id: publishData.menu_parent_id,
|
||||
menu_icon: publishData.menu_icon || 'lucide:file-text',
|
||||
menu_order: publishData.menu_order ?? 1,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前编辑的数据
|
||||
function getCurrentData(): Record<string, any> {
|
||||
if (!editorRef.value) return {};
|
||||
|
||||
const data = editorRef.value.getData();
|
||||
const designType = props.design?.type;
|
||||
|
||||
switch (designType) {
|
||||
case 'form_basic_info':
|
||||
return { ...data.basicForm };
|
||||
case 'database_design':
|
||||
return {
|
||||
type: data.tableConfigs[0]?.type || 'main', // 添加 type 字段,默认为 main
|
||||
table: data.tableConfigs[0] ? {
|
||||
tableName: data.tableConfigs[0].tableName,
|
||||
alias: data.tableConfigs[0].alias,
|
||||
fields: data.tableConfigs[0].fields,
|
||||
meta: {
|
||||
schema: data.tableConfigs[0].meta?.schema || '',
|
||||
schemaRaw: data.tableConfigs[0].meta?.schemaRaw || '',
|
||||
database: data.tableConfigs[0].meta?.database || '',
|
||||
},
|
||||
} : {},
|
||||
dbConfig: props.design?.data?.dbConfig || 'default', // 保留原有的 dbConfig
|
||||
};
|
||||
case 'form_ui_design':
|
||||
return {
|
||||
items: data.formConfig.items,
|
||||
labelWidth: data.formConfig.labelWidth,
|
||||
labelPosition: data.formConfig.labelPosition,
|
||||
};
|
||||
case 'list_config':
|
||||
return { ...data.listConfig };
|
||||
case 'form_publish':
|
||||
return { ...data.publishData };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// 确认并继续
|
||||
function handleConfirm() {
|
||||
const data = getCurrentData();
|
||||
emit('confirm', data);
|
||||
}
|
||||
|
||||
// 关闭
|
||||
function handleClose() {
|
||||
emit('update:visible', false);
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
:class="[
|
||||
'border-border bg-card flex flex-col rounded-lg',
|
||||
isFullscreen ? 'fixed inset-0 z-50 ml-0' : 'ml-3 h-full w-full'
|
||||
]"
|
||||
>
|
||||
<!-- 头部 -->
|
||||
<div
|
||||
class="border-border bg-muted/50 flex items-center justify-between border-b px-4 py-3"
|
||||
>
|
||||
<div class="text-foreground font-medium">
|
||||
{{ panelTitle }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ElButton size="small" @click="handleClose">{{ $t('common.cancel') }}</ElButton>
|
||||
<ElButton size="small" type="primary" @click="handleConfirm">{{ $t('common.confirmAndContinue') }}</ElButton>
|
||||
<ElButton
|
||||
link
|
||||
:icon="isFullscreen ? Minimize : Maximize"
|
||||
:title="isFullscreen ? '退出全屏' : '全屏'"
|
||||
@click="toggleFullscreen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域:使用 FormEditorContent 公共组件 -->
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<FormEditorContent
|
||||
ref="editorRef"
|
||||
:step="currentStep"
|
||||
:show-steps="true"
|
||||
:show-actions="false"
|
||||
:workflow-mode="true"
|
||||
@update:step="currentStep = $event"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.design-editor-panel {
|
||||
/* box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1); */
|
||||
}
|
||||
</style>
|
||||
@@ -1,329 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElInputNumber,
|
||||
ElMessage,
|
||||
ElOption,
|
||||
ElSelect,
|
||||
ElSteps,
|
||||
ElStep,
|
||||
} from 'element-plus';
|
||||
|
||||
import PublishInfoEditor, { type FormPublishInput } from './PublishInfoEditor.vue';
|
||||
|
||||
export interface BasicFormData {
|
||||
code: string;
|
||||
description: string;
|
||||
form_type: 'normal' | 'workflow';
|
||||
name: string;
|
||||
sort: number;
|
||||
}
|
||||
|
||||
export interface TableConfig {
|
||||
alias?: string;
|
||||
fields?: any[];
|
||||
id?: string;
|
||||
meta?: Record<string, any>;
|
||||
tableName?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface FormEditorData {
|
||||
basicForm: BasicFormData;
|
||||
formConfig: {
|
||||
disabled: boolean;
|
||||
formBackground: boolean;
|
||||
formBorder: boolean;
|
||||
formBorderRadius: number;
|
||||
formMargin: number;
|
||||
formMaxWidth: number;
|
||||
formPadding: number;
|
||||
formShadow: boolean;
|
||||
formWidth: string;
|
||||
items: any[];
|
||||
itemSpacing: number;
|
||||
labelPosition: 'left' | 'right' | 'top';
|
||||
labelWidth: number;
|
||||
lifecycleHooks?: any[];
|
||||
size: 'default' | 'large' | 'small';
|
||||
tableConfigs: TableConfig[];
|
||||
};
|
||||
formFields: any[];
|
||||
listConfig: Record<string, any>;
|
||||
publishData?: FormPublishInput;
|
||||
tableConfigs: TableConfig[];
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
initialBasicForm?: BasicFormData;
|
||||
initialListDesign?: Record<string, any>;
|
||||
initialTableConfigs?: TableConfig[];
|
||||
readonly?: boolean;
|
||||
showActions?: boolean;
|
||||
showSteps?: boolean;
|
||||
step?: number;
|
||||
workflowMode?: boolean;
|
||||
}>(),
|
||||
{
|
||||
initialBasicForm: undefined,
|
||||
initialListDesign: undefined,
|
||||
initialTableConfigs: undefined,
|
||||
readonly: false,
|
||||
showActions: true,
|
||||
showSteps: true,
|
||||
step: undefined,
|
||||
workflowMode: false,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
cancel: [];
|
||||
save: [data: FormEditorData];
|
||||
'step-change': [step: number];
|
||||
'update:step': [step: number];
|
||||
}>();
|
||||
|
||||
const internalStep = ref(0);
|
||||
const currentStep = computed({
|
||||
get: () => (props.step === undefined ? internalStep.value : props.step),
|
||||
set: (val) => {
|
||||
if (props.step === undefined) internalStep.value = val;
|
||||
else emit('update:step', val);
|
||||
emit('step-change', val);
|
||||
},
|
||||
});
|
||||
|
||||
const basicForm = ref<BasicFormData>({
|
||||
code: '',
|
||||
description: '',
|
||||
form_type: 'normal',
|
||||
name: '',
|
||||
sort: 0,
|
||||
});
|
||||
const tableConfigs = ref<TableConfig[]>([]);
|
||||
const formConfigText = ref('{}');
|
||||
const listConfigText = ref('{}');
|
||||
const formFields = ref<any[]>([]);
|
||||
const publishData = ref<FormPublishInput>({
|
||||
menu_icon: 'lucide:file-text',
|
||||
menu_name: '',
|
||||
menu_order: 0,
|
||||
menu_parent_id: undefined,
|
||||
});
|
||||
|
||||
const steps = computed(() => {
|
||||
const base = [
|
||||
{ title: $t('form-manager.editor.steps.basic') || '基础信息' },
|
||||
{ title: $t('form-manager.editor.steps.database') || '数据表' },
|
||||
{ title: $t('form-manager.editor.steps.form') || '表单设计' },
|
||||
{ title: $t('form-manager.editor.steps.list') || '列表设计' },
|
||||
];
|
||||
if (props.workflowMode) {
|
||||
base.push({ title: $t('form-manager.editor.steps.publish') || '发布配置' });
|
||||
}
|
||||
return base;
|
||||
});
|
||||
|
||||
const canGoPrev = computed(() => currentStep.value > 0);
|
||||
const isLastStep = computed(() => currentStep.value === steps.value.length - 1);
|
||||
|
||||
watch(
|
||||
() => props.initialBasicForm,
|
||||
(val) => {
|
||||
if (val) basicForm.value = { ...val };
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.initialTableConfigs,
|
||||
(val) => {
|
||||
if (val) tableConfigs.value = [...val];
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.initialListDesign,
|
||||
(val) => {
|
||||
if (val) listConfigText.value = JSON.stringify(val, null, 2);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => basicForm.value.name,
|
||||
(name) => {
|
||||
if (props.workflowMode && name && !publishData.value.menu_name) {
|
||||
publishData.value.menu_name = name;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function parseJson(text: string, fallback: any) {
|
||||
if (!text.trim()) return fallback;
|
||||
return JSON.parse(text);
|
||||
}
|
||||
|
||||
function buildDefaultFormConfig(items: any[] = []) {
|
||||
return {
|
||||
disabled: false,
|
||||
formBackground: false,
|
||||
formBorder: false,
|
||||
formBorderRadius: 4,
|
||||
formMargin: 0,
|
||||
formMaxWidth: 0,
|
||||
formPadding: 20,
|
||||
formShadow: false,
|
||||
formWidth: '100%',
|
||||
items,
|
||||
itemSpacing: 18,
|
||||
labelPosition: 'right' as const,
|
||||
labelWidth: 120,
|
||||
size: 'default' as const,
|
||||
tableConfigs: tableConfigs.value,
|
||||
};
|
||||
}
|
||||
|
||||
function getData(): FormEditorData {
|
||||
const formConfig = {
|
||||
...buildDefaultFormConfig(),
|
||||
...parseJson(formConfigText.value, {}),
|
||||
};
|
||||
const listConfig = parseJson(listConfigText.value, {});
|
||||
return {
|
||||
basicForm: { ...basicForm.value },
|
||||
formConfig,
|
||||
formFields: [...formFields.value],
|
||||
listConfig,
|
||||
publishData: props.workflowMode ? { ...publishData.value } : undefined,
|
||||
tableConfigs: [...tableConfigs.value],
|
||||
};
|
||||
}
|
||||
|
||||
function setData(data: Partial<FormEditorData>) {
|
||||
if (data.basicForm) basicForm.value = { ...data.basicForm };
|
||||
if (data.tableConfigs) tableConfigs.value = [...data.tableConfigs];
|
||||
if (data.formConfig) {
|
||||
formConfigText.value = JSON.stringify(data.formConfig, null, 2);
|
||||
formFields.value = data.formFields || data.formConfig.items || [];
|
||||
}
|
||||
if (data.listConfig) listConfigText.value = JSON.stringify(data.listConfig, null, 2);
|
||||
if (data.formFields) formFields.value = [...data.formFields];
|
||||
if (data.publishData) publishData.value = { ...publishData.value, ...data.publishData };
|
||||
}
|
||||
|
||||
function handlePrev() {
|
||||
if (canGoPrev.value) currentStep.value -= 1;
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
if (!isLastStep.value) currentStep.value += 1;
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
try {
|
||||
emit('save', getData());
|
||||
} catch {
|
||||
ElMessage.error('JSON 配置格式不正确');
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ getData, setData });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
<div v-if="showSteps" class="border-border border-b px-6 py-4">
|
||||
<ElSteps :active="currentStep" align-center finish-status="success">
|
||||
<ElStep v-for="item in steps" :key="item.title" :title="item.title" />
|
||||
</ElSteps>
|
||||
</div>
|
||||
|
||||
<div class="min-h-0 flex-1 overflow-auto p-6">
|
||||
<ElForm v-if="currentStep === 0" :model="basicForm" label-width="100px">
|
||||
<ElFormItem label="名称" required>
|
||||
<ElInput v-model="basicForm.name" :disabled="readonly" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="编码" required>
|
||||
<ElInput v-model="basicForm.code" :disabled="readonly" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="类型">
|
||||
<ElSelect v-model="basicForm.form_type" :disabled="readonly" class="w-full">
|
||||
<ElOption label="普通表单" value="normal" />
|
||||
<ElOption label="流程表单" value="workflow" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="排序">
|
||||
<ElInputNumber v-model="basicForm.sort" :disabled="readonly" class="w-full" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="描述">
|
||||
<ElInput v-model="basicForm.description" :disabled="readonly" type="textarea" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
<div v-else-if="currentStep === 1" class="space-y-3">
|
||||
<div class="text-sm text-muted-foreground">
|
||||
确认 AI 生成的数据表结构,保存后将作为后续表单和列表配置的数据基础。
|
||||
</div>
|
||||
<ElInput
|
||||
:model-value="JSON.stringify(tableConfigs, null, 2)"
|
||||
readonly
|
||||
resize="none"
|
||||
type="textarea"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentStep === 2" class="h-full min-h-[420px]">
|
||||
<ElInput
|
||||
v-model="formConfigText"
|
||||
:readonly="readonly"
|
||||
class="h-full"
|
||||
resize="none"
|
||||
type="textarea"
|
||||
placeholder="表单配置 JSON"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentStep === 3" class="h-full min-h-[420px]">
|
||||
<ElInput
|
||||
v-model="listConfigText"
|
||||
:readonly="readonly"
|
||||
class="h-full"
|
||||
resize="none"
|
||||
type="textarea"
|
||||
placeholder="列表配置 JSON"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PublishInfoEditor
|
||||
v-else
|
||||
v-model="publishData"
|
||||
:form-code="basicForm.code"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="showActions" class="border-border flex justify-end gap-2 border-t px-6 py-4">
|
||||
<ElButton @click="emit('cancel')">{{ $t('common.cancel') }}</ElButton>
|
||||
<ElButton :disabled="!canGoPrev" @click="handlePrev">上一步</ElButton>
|
||||
<ElButton v-if="!isLastStep" type="primary" @click="handleNext">下一步</ElButton>
|
||||
<ElButton v-else type="primary" @click="handleSave">{{ $t('common.save') }}</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-textarea),
|
||||
:deep(.el-textarea__inner) {
|
||||
height: 100%;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -1,140 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 页面基础信息编辑器
|
||||
* 供 page-manager 和工作流节点共用
|
||||
*/
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElInputNumber,
|
||||
ElOption,
|
||||
ElSelect,
|
||||
} from 'element-plus';
|
||||
|
||||
export interface PageBasicInfo {
|
||||
name: string;
|
||||
code: string;
|
||||
category: string;
|
||||
description: string;
|
||||
sort: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
modelValue: PageBasicInfo;
|
||||
isEditMode?: boolean;
|
||||
showTitle?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isEditMode: false,
|
||||
showTitle: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: PageBasicInfo];
|
||||
}>();
|
||||
|
||||
// 更新表单数据
|
||||
function updateFormData(key: keyof PageBasicInfo, value: any) {
|
||||
emit('update:modelValue', {
|
||||
...props.modelValue,
|
||||
[key]: value,
|
||||
});
|
||||
}
|
||||
|
||||
// 分类选项
|
||||
const categoryOptions = [
|
||||
{ label: $t('page-manager.categoryMap.dashboard'), value: 'dashboard' },
|
||||
{ label: $t('page-manager.categoryMap.portal'), value: 'portal' },
|
||||
{ label: $t('page-manager.categoryMap.databoard'), value: 'databoard' },
|
||||
{ label: $t('page-manager.categoryMap.other'), value: 'other' },
|
||||
];
|
||||
|
||||
// 监听 modelValue 变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3 v-if="showTitle" class="mb-6 text-center text-lg font-medium">
|
||||
{{ $t('page-manager.editor.steps.basic') }}
|
||||
</h3>
|
||||
<ElForm :model="modelValue" label-width="100px" label-position="right">
|
||||
<ElFormItem :label="$t('page-manager.name')" required>
|
||||
<ElInput
|
||||
:model-value="modelValue.name"
|
||||
:placeholder="$t('page-manager.placeholder.name')"
|
||||
clearable
|
||||
@update:model-value="updateFormData('name', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('page-manager.code')" required>
|
||||
<ElInput
|
||||
:model-value="modelValue.code"
|
||||
:placeholder="$t('page-manager.placeholder.code')"
|
||||
clearable
|
||||
:disabled="isEditMode"
|
||||
@update:model-value="updateFormData('code', $event)"
|
||||
/>
|
||||
<template #error>
|
||||
<div
|
||||
v-if="
|
||||
modelValue.code &&
|
||||
!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(modelValue.code)
|
||||
"
|
||||
class="el-form-item__error"
|
||||
>
|
||||
{{ $t('page-manager.codeFormatError') }}
|
||||
</div>
|
||||
</template>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('page-manager.category')">
|
||||
<ElSelect
|
||||
:model-value="modelValue.category"
|
||||
:placeholder="$t('page-manager.placeholder.category')"
|
||||
class="w-full"
|
||||
clearable
|
||||
@update:model-value="updateFormData('category', $event)"
|
||||
>
|
||||
<ElOption
|
||||
v-for="opt in categoryOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('common.sort')">
|
||||
<ElInputNumber
|
||||
:model-value="modelValue.sort"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="w-full"
|
||||
@update:model-value="updateFormData('sort', $event ?? 0)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('page-manager.description')">
|
||||
<ElInput
|
||||
:model-value="modelValue.description"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:placeholder="$t('page-manager.placeholder.description')"
|
||||
@update:model-value="updateFormData('description', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,210 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PageBasicInfo } from './PageBasicInfoEditor.vue';
|
||||
import type { PagePublishInfo } from './PagePublishInfoEditor.vue';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElButton, ElInput, ElMessage, ElStep, ElSteps } from 'element-plus';
|
||||
|
||||
import PageBasicInfoEditor from './PageBasicInfoEditor.vue';
|
||||
import PagePublishInfoEditor from './PagePublishInfoEditor.vue';
|
||||
|
||||
export interface PageEditorData {
|
||||
basicForm: PageBasicInfo;
|
||||
pageConfig: Record<string, any>;
|
||||
publishData?: PagePublishInfo;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
initialBasicForm?: PageBasicInfo;
|
||||
initialPageConfig?: Record<string, any>;
|
||||
initialPublishData?: PagePublishInfo;
|
||||
isEditMode?: boolean;
|
||||
readonly?: boolean;
|
||||
showActions?: boolean;
|
||||
showSteps?: boolean;
|
||||
step?: number;
|
||||
workflowMode?: boolean;
|
||||
}>(),
|
||||
{
|
||||
initialBasicForm: undefined,
|
||||
initialPageConfig: undefined,
|
||||
initialPublishData: undefined,
|
||||
isEditMode: false,
|
||||
readonly: false,
|
||||
showActions: true,
|
||||
showSteps: true,
|
||||
step: undefined,
|
||||
workflowMode: false,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
cancel: [];
|
||||
save: [data: PageEditorData];
|
||||
'step-change': [step: number];
|
||||
'update:step': [step: number];
|
||||
}>();
|
||||
|
||||
const internalStep = ref(0);
|
||||
const currentStep = computed({
|
||||
get: () => (props.step === undefined ? internalStep.value : props.step),
|
||||
set: (val) => {
|
||||
if (props.step === undefined) internalStep.value = val;
|
||||
else emit('update:step', val);
|
||||
emit('step-change', val);
|
||||
},
|
||||
});
|
||||
|
||||
const basicForm = ref<PageBasicInfo>({
|
||||
category: 'dashboard',
|
||||
code: '',
|
||||
description: '',
|
||||
name: '',
|
||||
sort: 0,
|
||||
});
|
||||
|
||||
const pageConfigText = ref('{}');
|
||||
const publishData = ref<PagePublishInfo>({
|
||||
menu_icon: 'lucide:layout-dashboard',
|
||||
menu_name: '',
|
||||
menu_order: 0,
|
||||
menu_parent_id: undefined,
|
||||
});
|
||||
|
||||
const steps = computed(() => {
|
||||
const base = [
|
||||
{ title: $t('page-manager.editor.steps.basic') || '基础信息' },
|
||||
{ title: $t('page-manager.editor.steps.design') || '页面设计' },
|
||||
];
|
||||
if (props.workflowMode) {
|
||||
base.push({ title: $t('page-manager.editor.steps.publish') || '发布配置' });
|
||||
}
|
||||
return base;
|
||||
});
|
||||
|
||||
const canGoPrev = computed(() => currentStep.value > 0);
|
||||
const isLastStep = computed(() => currentStep.value === steps.value.length - 1);
|
||||
|
||||
watch(
|
||||
() => props.initialBasicForm,
|
||||
(val) => {
|
||||
if (val) basicForm.value = { ...val };
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.initialPageConfig,
|
||||
(val) => {
|
||||
if (val) pageConfigText.value = JSON.stringify(val, null, 2);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.initialPublishData,
|
||||
(val) => {
|
||||
if (val) publishData.value = { ...val };
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => basicForm.value.name,
|
||||
(name) => {
|
||||
if (props.workflowMode && name && !publishData.value.menu_name) {
|
||||
publishData.value.menu_name = name;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function getData(): PageEditorData {
|
||||
const pageConfig = pageConfigText.value.trim()
|
||||
? JSON.parse(pageConfigText.value)
|
||||
: {};
|
||||
return {
|
||||
basicForm: { ...basicForm.value },
|
||||
pageConfig,
|
||||
publishData: props.workflowMode ? { ...publishData.value } : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function setData(data: Partial<PageEditorData>) {
|
||||
if (data.basicForm) basicForm.value = { ...data.basicForm };
|
||||
if (data.pageConfig) pageConfigText.value = JSON.stringify(data.pageConfig, null, 2);
|
||||
if (data.publishData) publishData.value = { ...publishData.value, ...data.publishData };
|
||||
}
|
||||
|
||||
function handlePrev() {
|
||||
if (canGoPrev.value) currentStep.value -= 1;
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
if (!isLastStep.value) currentStep.value += 1;
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
try {
|
||||
emit('save', getData());
|
||||
} catch {
|
||||
ElMessage.error('页面配置不是有效 JSON');
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ getData, setData });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
<div v-if="showSteps" class="border-border border-b px-6 py-4">
|
||||
<ElSteps :active="currentStep" align-center finish-status="success">
|
||||
<ElStep v-for="item in steps" :key="item.title" :title="item.title" />
|
||||
</ElSteps>
|
||||
</div>
|
||||
|
||||
<div class="min-h-0 flex-1 overflow-auto p-6">
|
||||
<PageBasicInfoEditor
|
||||
v-if="currentStep === 0"
|
||||
v-model="basicForm"
|
||||
:is-edit-mode="isEditMode"
|
||||
/>
|
||||
<div v-else-if="currentStep === 1" class="h-full min-h-[460px]">
|
||||
<div class="mb-3 text-sm text-muted-foreground">
|
||||
确认 AI 生成的页面配置,保存后将作为仪表盘发布和菜单挂载的数据基础。
|
||||
</div>
|
||||
<ElInput
|
||||
v-model="pageConfigText"
|
||||
:readonly="readonly"
|
||||
class="h-full"
|
||||
resize="none"
|
||||
type="textarea"
|
||||
placeholder="页面配置 JSON"
|
||||
/>
|
||||
</div>
|
||||
<PagePublishInfoEditor
|
||||
v-else
|
||||
v-model="publishData"
|
||||
:page-code="basicForm.code"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="showActions" class="border-border flex justify-end gap-2 border-t px-6 py-4">
|
||||
<ElButton @click="emit('cancel')">{{ $t('common.cancel') }}</ElButton>
|
||||
<ElButton :disabled="!canGoPrev" @click="handlePrev">上一步</ElButton>
|
||||
<ElButton v-if="!isLastStep" type="primary" @click="handleNext">下一步</ElButton>
|
||||
<ElButton v-else type="primary" @click="handleSave">{{ $t('common.save') }}</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-textarea),
|
||||
:deep(.el-textarea__inner) {
|
||||
height: 100%;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -1,129 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 页面发布信息编辑器
|
||||
* 供 page-manager 和工作流节点共用
|
||||
*/
|
||||
import type { MenuItem } from '#/components/zq-form/zq-menu-selector/types';
|
||||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElForm, ElFormItem, ElInput, ElInputNumber } from 'element-plus';
|
||||
|
||||
import { ZqIconPicker } from '#/components/zq-form/zq-icon-picker';
|
||||
import { ZqMenuSelector } from '#/components/zq-form/zq-menu-selector';
|
||||
import { useAppContextStore } from '#/store/app-context';
|
||||
|
||||
export interface PagePublishInfo {
|
||||
menu_name: string;
|
||||
menu_parent_id?: string;
|
||||
menu_icon: string;
|
||||
menu_order: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
modelValue: PagePublishInfo;
|
||||
pageCode?: string;
|
||||
showRouteInfo?: boolean;
|
||||
routePrefix?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
pageCode: '',
|
||||
showRouteInfo: true,
|
||||
routePrefix: '/page/',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: PagePublishInfo];
|
||||
}>();
|
||||
|
||||
const appContextStore = useAppContextStore();
|
||||
|
||||
// 更新表单数据
|
||||
function updateFormData(key: keyof PagePublishInfo, value: any) {
|
||||
emit('update:modelValue', {
|
||||
...props.modelValue,
|
||||
[key]: value,
|
||||
});
|
||||
}
|
||||
|
||||
// 菜单选择回调
|
||||
function handleMenuChange(menu: MenuItem | MenuItem[] | null) {
|
||||
if (menu && !Array.isArray(menu)) {
|
||||
updateFormData('menu_parent_id', menu.id);
|
||||
} else {
|
||||
updateFormData('menu_parent_id', undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 modelValue 变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElForm :model="modelValue" label-width="100px">
|
||||
<!-- 菜单配置 -->
|
||||
<div class="mb-4 font-medium">
|
||||
{{ $t('page-manager.publishDialog.menuConfig') }}
|
||||
</div>
|
||||
|
||||
<ElFormItem :label="$t('page-manager.publishDialog.menuName')" required>
|
||||
<ElInput
|
||||
:model-value="modelValue.menu_name"
|
||||
:placeholder="$t('page-manager.placeholder.name')"
|
||||
@update:model-value="updateFormData('menu_name', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('page-manager.publishDialog.parentMenu')">
|
||||
<ZqMenuSelector
|
||||
:model-value="modelValue.menu_parent_id || null"
|
||||
mode="dialog"
|
||||
:placeholder="$t('page-manager.publishDialog.parentMenuPlaceholder')"
|
||||
:application-id="appContextStore.currentApp?.id"
|
||||
@change="handleMenuChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('page-manager.publishDialog.menuIcon')">
|
||||
<ZqIconPicker
|
||||
:model-value="modelValue.menu_icon"
|
||||
prefix="lucide"
|
||||
:auto-fetch-api="false"
|
||||
class="w-full"
|
||||
@update:model-value="updateFormData('menu_icon', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('common.sort')">
|
||||
<ElInputNumber
|
||||
:model-value="modelValue.menu_order"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="w-full"
|
||||
@update:model-value="updateFormData('menu_order', $event ?? 0)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<!-- 路由信息 -->
|
||||
<template v-if="showRouteInfo && pageCode">
|
||||
<div class="mb-4 mt-6 font-medium">
|
||||
{{ $t('page-manager.publishDialog.routeInfo') }}
|
||||
</div>
|
||||
|
||||
<ElFormItem :label="$t('page-manager.publishDialog.accessPath')">
|
||||
<ElInput
|
||||
:model-value="`${routePrefix}${pageCode}`"
|
||||
disabled
|
||||
class="text-muted-foreground"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</ElForm>
|
||||
</template>
|
||||
@@ -1,122 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { MenuItem } from '#/components/zq-form/zq-menu-selector/types';
|
||||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElForm, ElFormItem, ElInput, ElInputNumber } from 'element-plus';
|
||||
|
||||
import { ZqIconPicker } from '#/components/zq-form/zq-icon-picker';
|
||||
import { ZqMenuSelector } from '#/components/zq-form/zq-menu-selector';
|
||||
import { useAppContextStore } from '#/store/app-context';
|
||||
|
||||
interface Props {
|
||||
modelValue: FormPublishInput;
|
||||
formCode?: string;
|
||||
showRouteInfo?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
formCode: '',
|
||||
showRouteInfo: true,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: FormPublishInput];
|
||||
}>();
|
||||
|
||||
const appContextStore = useAppContextStore();
|
||||
|
||||
// 更新表单数据
|
||||
function updateFormData(key: keyof FormPublishInput, value: any) {
|
||||
emit('update:modelValue', {
|
||||
...props.modelValue,
|
||||
[key]: value,
|
||||
});
|
||||
}
|
||||
|
||||
// 菜单选择回调
|
||||
function handleMenuChange(menu: MenuItem | MenuItem[] | null) {
|
||||
if (menu && !Array.isArray(menu)) {
|
||||
updateFormData('menu_parent_id', menu.id);
|
||||
} else {
|
||||
updateFormData('menu_parent_id', undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 modelValue 变化,确保响应式
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElForm :model="modelValue" label-width="100px">
|
||||
<!-- 菜单配置 -->
|
||||
<div class="mb-4 font-medium">
|
||||
{{ $t('form-manager.publishDialog.menuConfig') }}
|
||||
</div>
|
||||
|
||||
<ElFormItem :label="$t('form-manager.publishDialog.menuName')" required>
|
||||
<ElInput
|
||||
:model-value="modelValue.menu_name"
|
||||
:placeholder="$t('form-manager.placeholder.name')"
|
||||
@update:model-value="updateFormData('menu_name', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('form-manager.publishDialog.parentMenu')">
|
||||
<ZqMenuSelector
|
||||
:model-value="modelValue.menu_parent_id || null"
|
||||
mode="dialog"
|
||||
:placeholder="$t('form-manager.publishDialog.parentMenuPlaceholder')"
|
||||
:application-id="appContextStore.currentApp?.id"
|
||||
@change="handleMenuChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('form-manager.publishDialog.menuIcon')">
|
||||
<ZqIconPicker
|
||||
:model-value="modelValue.menu_icon"
|
||||
prefix="lucide"
|
||||
:auto-fetch-api="false"
|
||||
class="w-full"
|
||||
@update:model-value="updateFormData('menu_icon', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('common.sort')">
|
||||
<ElInputNumber
|
||||
:model-value="modelValue.menu_order"
|
||||
:min="0"
|
||||
:max="999"
|
||||
class="w-full"
|
||||
@update:model-value="updateFormData('menu_order', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<!-- 路由信息 -->
|
||||
<template v-if="showRouteInfo && formCode">
|
||||
<div class="mb-4 mt-6 font-medium">
|
||||
{{ $t('form-manager.publishDialog.routeInfo') }}
|
||||
</div>
|
||||
|
||||
<ElFormItem :label="$t('form-manager.publishDialog.accessPath')">
|
||||
<ElInput
|
||||
:model-value="`/form-render/${formCode}`"
|
||||
disabled
|
||||
class="text-muted-foreground"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</template>
|
||||
</ElForm>
|
||||
</template>
|
||||
export interface FormPublishInput {
|
||||
menu_icon: string;
|
||||
menu_name: string;
|
||||
menu_order: number;
|
||||
menu_parent_id?: string;
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import {
|
||||
AppWindow,
|
||||
CircleCheck,
|
||||
ExternalLink,
|
||||
FileText,
|
||||
LayoutDashboard,
|
||||
Maximize,
|
||||
Minimize,
|
||||
} from '@vben/icons';
|
||||
import { useI18n } from '@vben/locales';
|
||||
|
||||
import { ElButton, ElCard, ElEmpty } from 'element-plus';
|
||||
|
||||
export interface SystemSummaryData {
|
||||
type: 'system_summary';
|
||||
title: string;
|
||||
data: {
|
||||
app?: {
|
||||
code: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
id: string;
|
||||
link: string;
|
||||
name: string;
|
||||
};
|
||||
base_url?: string;
|
||||
created_at: string;
|
||||
dashboard?: {
|
||||
code: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
id: string;
|
||||
link: string;
|
||||
name: string;
|
||||
};
|
||||
forms: Array<{
|
||||
code: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
id: string;
|
||||
link: string;
|
||||
name: string;
|
||||
}>;
|
||||
statistics?: {
|
||||
forms_count: number;
|
||||
has_app: boolean;
|
||||
has_dashboard: boolean;
|
||||
total_modules: number;
|
||||
};
|
||||
title: string;
|
||||
};
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data?: SystemSummaryData;
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
confirm: [data: any];
|
||||
'update:visible': [value: boolean];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 全屏状态
|
||||
const isFullscreen = ref(false);
|
||||
|
||||
// 切换全屏
|
||||
const toggleFullscreen = () => {
|
||||
isFullscreen.value = !isFullscreen.value;
|
||||
};
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
});
|
||||
|
||||
const summaryData = computed(() => props.data?.data || ({} as any));
|
||||
const statistics = computed(() => summaryData.value.statistics);
|
||||
const app = computed(() => summaryData.value.app);
|
||||
const forms = computed(() => summaryData.value.forms || []);
|
||||
const dashboard = computed(() => summaryData.value.dashboard);
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', summaryData.value);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false;
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const openLink = (link: string) => {
|
||||
if (link) {
|
||||
const baseUrl = summaryData.value.base_url || '';
|
||||
const fullUrl = baseUrl ? `${baseUrl}${link}` : link;
|
||||
window.open(fullUrl, '_blank');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="border-border bg-card flex flex-col rounded-lg"
|
||||
:class="[isFullscreen ? 'fixed inset-0 z-50 ml-0' : 'ml-3 h-full w-full']"
|
||||
>
|
||||
<!-- 头部 -->
|
||||
<div
|
||||
class="border-border bg-muted/50 flex items-center justify-between border-b px-4 py-3"
|
||||
>
|
||||
<div class="text-foreground font-medium">
|
||||
{{ data?.title || t('ai.systemSummary.title') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ElButton size="small" @click="handleClose">
|
||||
{{ t('common.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton size="small" type="primary" @click="handleConfirm">
|
||||
<CircleCheck class="mr-1 size-4" />
|
||||
{{ t('ai.systemSummary.complete') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
link
|
||||
:icon="isFullscreen ? Minimize : Maximize"
|
||||
:title="isFullscreen ? '退出全屏' : '全屏'"
|
||||
@click="toggleFullscreen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="flex-1 overflow-auto p-6">
|
||||
<div
|
||||
class="mb-6 flex items-center gap-3 border-b border-green-200 pb-4 dark:border-green-800"
|
||||
>
|
||||
<div
|
||||
class="flex size-12 items-center justify-center rounded-full bg-green-100 dark:bg-green-900"
|
||||
>
|
||||
<CircleCheck class="size-6 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-green-700 dark:text-green-300">
|
||||
{{ summaryData.title || t('ai.systemSummary.title') }}
|
||||
</h2>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
{{ t('ai.systemSummary.subtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="statistics" class="mb-6 grid grid-cols-3 gap-4">
|
||||
<div
|
||||
class="rounded-lg border bg-blue-50 p-4 text-center dark:bg-blue-950"
|
||||
>
|
||||
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
||||
{{ statistics.has_app ? 1 : 0 }}
|
||||
</div>
|
||||
<div class="text-muted-foreground mt-1 text-sm">
|
||||
{{ t('ai.systemSummary.app') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg border bg-purple-50 p-4 text-center dark:bg-purple-950"
|
||||
>
|
||||
<div class="text-3xl font-bold text-purple-600 dark:text-purple-400">
|
||||
{{ statistics.forms_count }}
|
||||
</div>
|
||||
<div class="text-muted-foreground mt-1 text-sm">
|
||||
{{ t('ai.systemSummary.formModules') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg border bg-orange-50 p-4 text-center dark:bg-orange-950"
|
||||
>
|
||||
<div class="text-3xl font-bold text-orange-600 dark:text-orange-400">
|
||||
{{ statistics.has_dashboard ? 1 : 0 }}
|
||||
</div>
|
||||
<div class="text-muted-foreground mt-1 text-sm">
|
||||
{{ t('ai.systemSummary.dashboard') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="app" class="mb-6">
|
||||
<h3 class="mb-3 flex items-center gap-2 font-medium">
|
||||
<AppWindow class="size-5 text-blue-500" />
|
||||
{{ t('ai.systemSummary.appInfo') }}
|
||||
</h3>
|
||||
<ElCard
|
||||
shadow="hover"
|
||||
class="cursor-pointer"
|
||||
@click="openLink(app.link)"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex size-10 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900"
|
||||
>
|
||||
<AppWindow class="size-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">{{ app.name }}</div>
|
||||
<div class="text-muted-foreground text-sm">
|
||||
{{ app.description || app.code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ElButton
|
||||
v-if="app.link"
|
||||
type="primary"
|
||||
link
|
||||
@click.stop="openLink(app.link)"
|
||||
>
|
||||
<ExternalLink class="mr-1 size-4" />
|
||||
{{ t('ai.systemSummary.visit') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
|
||||
<div v-if="forms.length > 0" class="mb-6">
|
||||
<h3 class="mb-3 flex items-center gap-2 font-medium">
|
||||
<FileText class="size-5 text-purple-500" />
|
||||
{{ t('ai.systemSummary.formModulesTitle') }}
|
||||
<span class="text-muted-foreground text-sm">({{ forms.length }})</span>
|
||||
</h3>
|
||||
<div class="space-y-3">
|
||||
<ElCard
|
||||
v-for="form in forms"
|
||||
:key="form.id || form.code"
|
||||
shadow="hover"
|
||||
class="cursor-pointer"
|
||||
@click="openLink(form.link)"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex size-10 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900"
|
||||
>
|
||||
<FileText
|
||||
class="size-5 text-purple-600 dark:text-purple-400"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">{{ form.name }}</div>
|
||||
<div class="text-muted-foreground text-sm">
|
||||
{{ form.description || form.code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ElButton
|
||||
v-if="form.link"
|
||||
type="primary"
|
||||
link
|
||||
@click.stop="openLink(form.link)"
|
||||
>
|
||||
<ExternalLink class="mr-1 size-4" />
|
||||
{{ t('ai.systemSummary.visit') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="dashboard" class="mb-6">
|
||||
<h3 class="mb-3 flex items-center gap-2 font-medium">
|
||||
<LayoutDashboard class="size-5 text-orange-500" />
|
||||
{{ t('ai.systemSummary.dashboardTitle') }}
|
||||
</h3>
|
||||
<ElCard
|
||||
shadow="hover"
|
||||
class="cursor-pointer"
|
||||
@click="openLink(dashboard.link)"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex size-10 items-center justify-center rounded-lg bg-orange-100 dark:bg-orange-900"
|
||||
>
|
||||
<LayoutDashboard
|
||||
class="size-5 text-orange-600 dark:text-orange-400"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">{{ dashboard.name }}</div>
|
||||
<div class="text-muted-foreground text-sm">
|
||||
{{ dashboard.description || dashboard.code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ElButton
|
||||
v-if="dashboard.link"
|
||||
type="primary"
|
||||
link
|
||||
@click.stop="openLink(dashboard.link)"
|
||||
>
|
||||
<ExternalLink class="mr-1 size-4" />
|
||||
{{ t('ai.systemSummary.visit') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
|
||||
<ElEmpty
|
||||
v-if="!app && forms.length === 0 && !dashboard"
|
||||
:description="t('ai.systemSummary.noData')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* 表单编辑器公共组件
|
||||
* 可用于:表单管理器、工作流设计预览编辑
|
||||
*/
|
||||
|
||||
export { default as BasicInfoEditor } from './BasicInfoEditor.vue';
|
||||
export type { BasicFormData } from './BasicInfoEditor.vue';
|
||||
|
||||
export { default as DesignEditorPanel } from './DesignEditorPanel.vue';
|
||||
export type { DesignData, DesignType } from './DesignEditorPanel.vue';
|
||||
|
||||
export { default as FormEditorContent } from './FormEditorContent.vue';
|
||||
export type { FormEditorData, BasicFormData as FormBasicData } from './FormEditorContent.vue';
|
||||
|
||||
export { default as AppDesignPanel } from './AppDesignPanel.vue';
|
||||
export type { AppDesignData } from './AppDesignPanel.vue';
|
||||
|
||||
export { default as AppSettingsPanel } from './AppSettingsPanel.vue';
|
||||
export type { AppSettingsData } from './AppSettingsPanel.vue';
|
||||
|
||||
export { default as DashboardBasicInfoConfirmPanel } from './DashboardBasicInfoConfirmPanel.vue';
|
||||
export type { DashboardBasicInfoData } from './DashboardBasicInfoConfirmPanel.vue';
|
||||
|
||||
export { default as DashboardDesignConfirmPanel } from './DashboardDesignConfirmPanel.vue';
|
||||
export type { DashboardDesignData } from './DashboardDesignConfirmPanel.vue';
|
||||
|
||||
export { default as PageBasicInfoEditor } from './PageBasicInfoEditor.vue';
|
||||
export type { PageBasicInfo } from './PageBasicInfoEditor.vue';
|
||||
|
||||
export { default as PagePublishInfoEditor } from './PagePublishInfoEditor.vue';
|
||||
export type { PagePublishInfo } from './PagePublishInfoEditor.vue';
|
||||
|
||||
export { default as PageEditorContent } from './PageEditorContent.vue';
|
||||
export type { PageEditorData } from './PageEditorContent.vue';
|
||||
|
||||
export { default as DashboardPublishConfirmPanel } from './DashboardPublishConfirmPanel.vue';
|
||||
export type { DashboardPublishData } from './DashboardPublishConfirmPanel.vue';
|
||||
|
||||
export { default as SystemSummaryConfirmPanel } from './SystemSummaryConfirmPanel.vue';
|
||||
export type { SystemSummaryData } from './SystemSummaryConfirmPanel.vue';
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { Page } from '@vben/common-ui/es/page';
|
||||
import { PanelLeft } from '@vben/icons';
|
||||
|
||||
import { ElCard, ElScrollbar, ElSplitter, ElSplitterPanel } from 'element-plus';
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
|
||||
import { ZqDialog } from '#/components/zq-dialog';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'UserProfileDialog',
|
||||
});
|
||||
|
||||
const OrgChartPanel = defineAsyncComponent(
|
||||
() => import('#/views/_core/org-chart/modules/OrgChartPanel.vue'),
|
||||
);
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
userId: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
}>();
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ZqDialog
|
||||
v-model="visible"
|
||||
:title="$t('user-avatar.profile.organization')"
|
||||
width="80%"
|
||||
:show-footer="false"
|
||||
content-height="70vh"
|
||||
destroy-on-close
|
||||
>
|
||||
<div class="org-chart-wrapper">
|
||||
<OrgChartPanel :user-id="userId" :show-mode-toggle="true" />
|
||||
</div>
|
||||
</ZqDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.org-chart-wrapper {
|
||||
height: 65vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"title": "About Project",
|
||||
"basicInfo": "Basic Information",
|
||||
"productionDependencies": "Production Dependencies",
|
||||
"devDependencies": "Development Dependencies",
|
||||
"version": "Version",
|
||||
"license": "License",
|
||||
"buildTime": "Build Time",
|
||||
"homepage": "Homepage",
|
||||
"docUrl": "Documentation",
|
||||
"previewUrl": "Preview",
|
||||
"github": "Github",
|
||||
"author": "Author",
|
||||
"viewDetails": "View Details"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"passwordLengthHint": "Password length must be 6-20 characters"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"title": "Title",
|
||||
"status": "Status",
|
||||
"priority": "Priority",
|
||||
"targetType": "Target Type",
|
||||
"readCount": "Read Count",
|
||||
"publisher": "Publisher",
|
||||
"publishTime": "Publish Time",
|
||||
"actions": "Actions",
|
||||
"keyword": "Keyword",
|
||||
"keywordPlaceholder": "Enter title keyword",
|
||||
"statusAll": "All",
|
||||
"statusDraft": "Draft",
|
||||
"statusPublished": "Published",
|
||||
"statusExpired": "Expired",
|
||||
"priorityNormal": "Normal",
|
||||
"priorityImportant": "Important",
|
||||
"priorityUrgent": "Urgent",
|
||||
"targetTypeAll": "All",
|
||||
"targetTypeDept": "Specified Department",
|
||||
"targetTypeRole": "Specified Role",
|
||||
"targetTypeUser": "Specified User",
|
||||
"topTag": "Top",
|
||||
"createButton": "Create Announcement",
|
||||
"editButton": "Edit",
|
||||
"deleteButton": "Delete",
|
||||
"publishButton": "Publish",
|
||||
"statsButton": "Read Statistics",
|
||||
"moreButton": "More",
|
||||
"createTitle": "Create Announcement",
|
||||
"editTitle": "Edit Announcement",
|
||||
"deleteConfirm": "Are you sure you want to delete the announcement \"{title}\"?",
|
||||
"deleteConfirmTitle": "Delete Confirmation",
|
||||
"publishConfirm": "Are you sure you want to publish this announcement? Relevant users will be notified after publishing.",
|
||||
"publishConfirmTitle": "Publish Confirmation",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"publishSuccess": "Published successfully",
|
||||
"updateSuccess": "Updated successfully",
|
||||
"createSuccess": "Created successfully",
|
||||
"formTitleLabel": "Title",
|
||||
"formTitlePlaceholder": "Enter announcement title",
|
||||
"formSummaryLabel": "Summary",
|
||||
"formSummaryPlaceholder": "Enter summary (optional)",
|
||||
"formContentLabel": "Content",
|
||||
"formContentPlaceholder": "Enter announcement content",
|
||||
"formTitleRequired": "Please enter announcement title",
|
||||
"formContentRequired": "Please enter announcement content",
|
||||
"formPriorityLabel": "Priority",
|
||||
"formTopLabel": "Top",
|
||||
"formTargetTypeLabel": "Target Type",
|
||||
"formExpireTimeLabel": "Expire Time",
|
||||
"formExpireTimePlaceholder": "Select expire time (optional)",
|
||||
"formCancelButton": "Cancel",
|
||||
"formSaveButton": "Save",
|
||||
"statsTitle": "Read Statistics",
|
||||
"statsReadCount": "Read: {count} people",
|
||||
"statsUserLabel": "User",
|
||||
"statsReadTimeLabel": "Read Time",
|
||||
"unreadOnly": "Unread Only",
|
||||
"unreadOnlyAll": "All",
|
||||
"unreadOnlyUnread": "Unread",
|
||||
"unreadCount": "{count} unread",
|
||||
"viewButton": "View",
|
||||
"publisherLabel": "Publisher",
|
||||
"publishTimeLabel": "Publish Time",
|
||||
"emptyList": "No announcements",
|
||||
"loadingMore": "Loading...",
|
||||
"noMore": "No more data",
|
||||
"detailTitle": "Announcement Detail",
|
||||
"selectHint": "Please select an announcement to view",
|
||||
"unreadLabel": "Unread",
|
||||
"summary": "Summary"
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"title": "API Token",
|
||||
"deviceManagement": "Device Management",
|
||||
"description": "Create personal access tokens for automation calls.",
|
||||
"loadError": "Failed to load tokens",
|
||||
"nameRequired": "Please enter token name",
|
||||
"createError": "Failed to create token",
|
||||
"createToken": "Create Token",
|
||||
"tokenName": "Token Name",
|
||||
"tokenNamePlaceholder": "Enter token name",
|
||||
"expirationDate": "Expiration",
|
||||
"neverExpiresHint": "Leave empty for no expiration",
|
||||
"tokenDescription": "Description",
|
||||
"tokenDescriptionPlaceholder": "Enter description",
|
||||
"tokenCreated": "Token Created",
|
||||
"tokenWarning": "Copy and save this token now. It will not be shown again.",
|
||||
"revokeTitle": "Revoke Token",
|
||||
"revokeConfirm": "Revoke token \"{0}\"?",
|
||||
"confirmRevoke": "Revoke",
|
||||
"revokeSuccess": "Token revoked",
|
||||
"revokeToken": "Revoke Token",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
"copySuccess": "Copied to clipboard",
|
||||
"copyError": "Copy failed",
|
||||
"neverExpires": "Never expires",
|
||||
"expired": "Expired",
|
||||
"expiresSoon": "Expires in {0} days",
|
||||
"createdAt": "Created",
|
||||
"lastUsed": "Last used",
|
||||
"empty": "No tokens",
|
||||
"days7": "7 days",
|
||||
"days30": "30 days",
|
||||
"days60": "60 days",
|
||||
"days90": "90 days",
|
||||
"days365": "1 year"
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
{
|
||||
"title": "Application Management",
|
||||
"createApp": "Create App",
|
||||
"editApp": "Edit App",
|
||||
"search": "Search",
|
||||
"searchPlaceholder": "Search app name or code",
|
||||
"noApps": "No applications",
|
||||
"appName": "App Name",
|
||||
"appNamePlaceholder": "Please enter app name",
|
||||
"appCode": "App Code",
|
||||
"appCodePlaceholder": "Please enter app code (for URL routing)",
|
||||
"appType": "App Type",
|
||||
"appTypePlaceholder": "Please select app type",
|
||||
"appDescription": "Description",
|
||||
"appDescriptionPlaceholder": "Please enter app description",
|
||||
"appIcon": "App Icon",
|
||||
"systemMenu": "System Menu",
|
||||
"systemMenuPlaceholder": "Select system menus for dev mode (leave empty for all)",
|
||||
"selectSystemMenu": "Select System Menu",
|
||||
"save": "Save",
|
||||
"create": "Create",
|
||||
"cancel": "Cancel",
|
||||
"develop": "Develop",
|
||||
"edit": "Edit",
|
||||
"publish": "Publish",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"delete": "Delete",
|
||||
"publishApp": "Publish App",
|
||||
"enableApp": "Enable App",
|
||||
"disableApp": "Disable App",
|
||||
"confirmPublish": "Confirm Publish",
|
||||
"confirmEnable": "Confirm Enable",
|
||||
"confirmDisable": "Confirm Disable",
|
||||
"publishConfirmMsg": "Are you sure to publish app \"{name}\"?",
|
||||
"enableConfirmMsg": "Are you sure you want to re-enable app \"{name}\"?",
|
||||
"publishSuccessMsg": "After publishing, users can access this app via the following link:",
|
||||
"enableSuccessMsg": "After enabling, users can access this app via the following link:",
|
||||
"disableConfirmMsg": "Are you sure to disable app \"{name}\"?",
|
||||
"appLink": "App Link:",
|
||||
"deleteConfirm": "Delete Confirm",
|
||||
"deleteConfirmMsg": "Are you sure to delete app \"{name}\"?",
|
||||
"confirm": "Confirm",
|
||||
"loadFailed": "Failed to load applications",
|
||||
"publishSuccess": "Published successfully",
|
||||
"publishFailed": "Failed to publish",
|
||||
"enableSuccess": "Enabled successfully",
|
||||
"enableFailed": "Failed to enable",
|
||||
"disableSuccess": "Disabled successfully",
|
||||
"disableFailed": "Failed to disable",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"copySuccess": "Link copied to clipboard",
|
||||
"copyFailed": "Failed to copy",
|
||||
"appTypes": {
|
||||
"mixed": "Mixed App",
|
||||
"form": "Form App",
|
||||
"workflow": "Workflow App",
|
||||
"ai": "AI App",
|
||||
"dashboard": "Dashboard App",
|
||||
"screen": "Screen App"
|
||||
},
|
||||
"appStatus": {
|
||||
"draft": "Draft",
|
||||
"published": "Published",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"validation": {
|
||||
"nameRequired": "Please enter app name",
|
||||
"nameLength": "Length should be 2 to 100 characters",
|
||||
"codeRequired": "Please enter app code",
|
||||
"codePattern": "Code must start with a letter and contain only letters, numbers, underscores and hyphens",
|
||||
"codeLength": "Length should be 2 to 100 characters",
|
||||
"typeRequired": "Please select app type"
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
{
|
||||
"title": "Process Title",
|
||||
"type": "Process Type",
|
||||
"currentNode": "Current Node",
|
||||
"processNode": "Process Node",
|
||||
"initiator": "Initiator",
|
||||
"receiveTime": "Receive Time",
|
||||
"processTime": "Process Time",
|
||||
"copyTime": "Copy Time",
|
||||
"startTime": "Start Time",
|
||||
"actions": "Actions",
|
||||
"processResult": "Process Result",
|
||||
"status": "Status",
|
||||
"approved": "Approved",
|
||||
"rejected": "Rejected",
|
||||
"transferred": "Transferred",
|
||||
"pending": "Pending",
|
||||
"cancelled": "Cancelled",
|
||||
"unread": "Unread",
|
||||
"read": "Read",
|
||||
"tabPending": "My Pending",
|
||||
"tabHandled": "My Handled",
|
||||
"tabInitiated": "My Initiated",
|
||||
"tabCopy": "Copied to Me",
|
||||
"approveButton": "Approve",
|
||||
"detailButton": "Details",
|
||||
"urgeButton": "Urge",
|
||||
"cancelButton": "Cancel",
|
||||
"markReadButton": "Mark as Read",
|
||||
"emptyPending": "No pending tasks",
|
||||
"emptyHandled": "No handled tasks",
|
||||
"emptyInitiated": "No initiated processes",
|
||||
"emptyCopy": "No copies",
|
||||
"urgeConfirm": "Are you sure you want to urge this process?",
|
||||
"cancelConfirm": "Are you sure you want to cancel this process?",
|
||||
"urgeSuccess": "Urged successfully",
|
||||
"cancelSuccess": "Cancelled successfully",
|
||||
"markReadSuccess": "Marked as read",
|
||||
"operationFailed": "Operation failed",
|
||||
"dialogTitle": "Approval",
|
||||
"initiatorLabel": "Initiator",
|
||||
"startTimeLabel": "Start Time",
|
||||
"currentNodeLabel": "Current Node",
|
||||
"formDataTitle": "Form Data",
|
||||
"approvalLogsTitle": "Approval Logs",
|
||||
"approvalActionTitle": "Approval Action",
|
||||
"approvalResultLabel": "Approval Result",
|
||||
"approvalOpinionLabel": "Approval Opinion",
|
||||
"approvalOpinionPlaceholder": "Enter approval opinion (optional)",
|
||||
"approveAction": "Approve",
|
||||
"rejectAction": "Reject",
|
||||
"cancelButton2": "Cancel",
|
||||
"submitButton": "Submit",
|
||||
"approveSuccess": "Approved successfully",
|
||||
"rejectSuccess": "Rejected",
|
||||
"loadDataFailed": "Failed to load data",
|
||||
"startAction": "Start Process",
|
||||
"approveActionLabel": "Approve",
|
||||
"rejectActionLabel": "Reject",
|
||||
"transferAction": "Transfer",
|
||||
"cancelAction": "Cancel"
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"username": "Username",
|
||||
"usernameTip": "Please enter username",
|
||||
"password": "Password",
|
||||
"passwordTip": "Please enter password",
|
||||
"confirmPassword": "Confirm Password",
|
||||
"confirmPasswordTip": "Passwords do not match",
|
||||
"passwordStrength": "Password Strength",
|
||||
"selectAccount": "Select Account",
|
||||
"verifyRequiredTip": "Please complete the slider verification",
|
||||
"mobile": "Phone Number",
|
||||
"mobileTip": "Please enter phone number",
|
||||
"mobileErrortip": "Please enter a valid phone number",
|
||||
"code": "Verification Code",
|
||||
"codeTip": "Verification code length is {0} digits",
|
||||
"sendCode": "Send Code",
|
||||
"sendText": "Resend({0}s)",
|
||||
"email": "Email",
|
||||
"emailTip": "Please enter email",
|
||||
"emailValidErrorTip": "Please enter a valid email format",
|
||||
"agree": "I agree to",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"terms": "Terms of Service",
|
||||
"agreeTip": "Please agree to Privacy Policy and Terms of Service",
|
||||
"thirdPartyLogin": "Third-party Login",
|
||||
"getAuthUrlFailed": "Failed to get authorization link",
|
||||
"giteeLoginFailed": "Gitee login failed, please try again later",
|
||||
"githubLoginFailed": "GitHub login failed, please try again later",
|
||||
"qqLoginFailed": "QQ login failed, please try again later",
|
||||
"googleLoginFailed": "Google login failed, please try again later",
|
||||
"wechatLoginFailed": "WeChat login failed, please try again later",
|
||||
"microsoftLoginFailed": "Microsoft login failed, please try again later",
|
||||
"dingtalkLoginFailed": "DingTalk login failed, please try again later",
|
||||
"feishuLoginFailed": "Feishu login failed, please try again later",
|
||||
"wechat": "WeChat",
|
||||
"wecom": "WeCom",
|
||||
"dingtalk": "DingTalk",
|
||||
"feishu": "Feishu",
|
||||
"wecomLoginFailed": "WeCom login failed, please try again later",
|
||||
"dingtalkAutoRedirect": "DingTalk detected, redirecting to login..."
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
{
|
||||
"title": "Chat",
|
||||
"search": "Search contacts or groups",
|
||||
"noConversations": "No conversations",
|
||||
"selectHint": "Select a conversation to start chatting",
|
||||
"newChat": "New Chat",
|
||||
"newGroup": "New Group",
|
||||
"private": "Private",
|
||||
"group": "Group",
|
||||
"members": "Members",
|
||||
"memberCount": "{count} members",
|
||||
"owner": "Owner",
|
||||
"admin": "Admin",
|
||||
"member": "Member",
|
||||
"groupName": "Group Name",
|
||||
"groupNamePlaceholder": "Enter group name",
|
||||
"selectMembers": "Select Members",
|
||||
"selectMembersPlaceholder": "Select group members",
|
||||
"createGroupSuccess": "Group created successfully",
|
||||
"inputPlaceholder": "Type a message...",
|
||||
"send": "Send",
|
||||
"sendImage": "Send Image",
|
||||
"sendFile": "Send File",
|
||||
"recall": "Recall",
|
||||
"recallSuccess": "Message recalled",
|
||||
"recallFailed": "Recall failed",
|
||||
"recallTimeout": "Cannot recall after 2 minutes",
|
||||
"messageRecalled": "Message recalled",
|
||||
"typing": "Typing...",
|
||||
"yesterday": "Yesterday",
|
||||
"pin": "Pin",
|
||||
"unpin": "Unpin",
|
||||
"mute": "Mute",
|
||||
"unmute": "Unmute",
|
||||
"conversationInfo": "Conversation Info",
|
||||
"addMember": "Add Member",
|
||||
"addMemberSuccess": "Members added successfully",
|
||||
"allMembersExist": "Selected members are already in the group",
|
||||
"removeMember": "Remove Member",
|
||||
"removeMemberConfirm": "Are you sure to remove this member?",
|
||||
"dissolveGroup": "Dissolve Group",
|
||||
"dissolveGroupConfirm": "Are you sure to dissolve this group? This action cannot be undone.",
|
||||
"dissolveSuccess": "Group dissolved",
|
||||
"leaveGroup": "Leave Group",
|
||||
"noMessages": "No messages",
|
||||
"loadMore": "Load more",
|
||||
"loading": "Loading...",
|
||||
"image": "Image",
|
||||
"file": "File",
|
||||
"replyTo": "Reply",
|
||||
"groupNameRequired": "Please enter group name",
|
||||
"membersRequired": "Please select at least one member",
|
||||
"recentChats": "Chats",
|
||||
"contacts": "Contacts",
|
||||
"searchContacts": "Search contacts",
|
||||
"noContacts": "No contacts",
|
||||
"startChat": "Start Chat",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"sending": "Sending...",
|
||||
"contactDetail": "Contact Detail",
|
||||
"contactDept": "Department",
|
||||
"contactPost": "Position",
|
||||
"contactManager": "Manager",
|
||||
"contactEmail": "Email",
|
||||
"contactMobile": "Mobile",
|
||||
"contactCity": "City",
|
||||
"contactType": "User Type",
|
||||
"contactOrg": "Organization",
|
||||
"contactOrgInfo": "Organization",
|
||||
"contactInfo": "Contact",
|
||||
"selectContactHint": "Select a contact to view details",
|
||||
"copy": "Copy",
|
||||
"copySuccess": "Copied to clipboard",
|
||||
"replyingTo": "Reply to {name}",
|
||||
"markUnread": "Mark as Unread",
|
||||
"deleteConversation": "Delete",
|
||||
"deleteConversationConfirm": "Are you sure to delete this conversation?",
|
||||
"deleteSuccess": "Deleted",
|
||||
"orgStructure": "Organization",
|
||||
"emoji": "Emoji",
|
||||
"voiceMessage": "Voice Message",
|
||||
"voiceTooShort": "Recording too short",
|
||||
"voiceUploading": "Sending voice...",
|
||||
"micPermissionDenied": "Cannot access microphone, please check browser permissions",
|
||||
"voice": "Voice",
|
||||
"dropToUpload": "Drop to send file",
|
||||
"newMessage": "New Message",
|
||||
"systemNotification": "System Notification",
|
||||
"viewDetail": "View Detail"
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
{
|
||||
"all": "All",
|
||||
"mainApp": "Main App",
|
||||
"close": "Close",
|
||||
"operation": "Operation",
|
||||
"next": "Next",
|
||||
"prev": "Previous",
|
||||
"cancel": "Cancel",
|
||||
"cancelEdit": "Cancel Edit",
|
||||
"ok": "OK",
|
||||
"confirm": "Confirm",
|
||||
"confirmAndContinue": "Confirm and Continue",
|
||||
"save": "Save",
|
||||
"add": "Add",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"update": "Update",
|
||||
"reset": "Reset",
|
||||
"status": "Status",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"selected": "Selected",
|
||||
"noData": "No Data",
|
||||
"loading": "Loading",
|
||||
"loadMore": "Load More",
|
||||
"noMore": "No more data",
|
||||
"noMoreData": "No more data",
|
||||
"justNow": "Just now",
|
||||
"minutesAgo": "minutes ago",
|
||||
"hoursAgo": "hours ago",
|
||||
"yesterday": "Yesterday",
|
||||
"daysAgo": "days ago",
|
||||
"tips": "Tips",
|
||||
"warning": "Warning",
|
||||
"success": "Success",
|
||||
"info": "Info",
|
||||
"error": "Error",
|
||||
"primary": "Primary",
|
||||
"search": "Search",
|
||||
"clear": "Clear",
|
||||
"replace": "Replace",
|
||||
"copy": "Copy",
|
||||
"format": "Format",
|
||||
"compress": "Compress",
|
||||
"redo": "Redo",
|
||||
"jsonEditor": {
|
||||
"placeholder": "Enter or paste JSON",
|
||||
"valid": "✓ Valid",
|
||||
"invalid": "✗ Invalid",
|
||||
"format": "Format",
|
||||
"compress": "Compress",
|
||||
"copy": "Copy",
|
||||
"clear": "Clear",
|
||||
"copiedSuccess": "Copied to clipboard",
|
||||
"copyFailed": "Copy failed",
|
||||
"noContent": "No content to copy",
|
||||
"formatSuccess": "Format successful",
|
||||
"formatFailed": "Format failed: {0}",
|
||||
"compressSuccess": "Compress successful",
|
||||
"compressFailed": "Compress failed: {0}",
|
||||
"invalidJson": "Invalid JSON format",
|
||||
"emptyContent": "Please enter JSON content",
|
||||
"stats": "Lines: {0} | Characters: {1}"
|
||||
},
|
||||
"ui": {
|
||||
"placeholder": {
|
||||
"select": "Please select",
|
||||
"selectAll": "Select All",
|
||||
"search": "Please enter search content"
|
||||
},
|
||||
"actionTitle": {
|
||||
"create": "Create {0}",
|
||||
"add": "Add {0}",
|
||||
"edit": "Edit {0}",
|
||||
"view": "View {0}",
|
||||
"delete": "Delete {0}"
|
||||
},
|
||||
"actionMessage": {
|
||||
"createSuccess": "Created successfully",
|
||||
"createError": "Create failed",
|
||||
"updateSuccess": "Updated successfully",
|
||||
"updateError": "Update failed",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"deleteConfirm": "Are you sure you want to delete {0}?",
|
||||
"deleteError": "Delete failed",
|
||||
"loadError": "Failed to load"
|
||||
},
|
||||
"submit": "Submit",
|
||||
"formRules": {
|
||||
"required": "{0} is required",
|
||||
"minLength": "{0} must be at least {1} characters",
|
||||
"maxLength": "{0} must be at most {1} characters",
|
||||
"alreadyExists": "{0} {1} already exists",
|
||||
"startWith": "{0} must start with {1}",
|
||||
"invalidURL": "Please enter a valid URL"
|
||||
}
|
||||
},
|
||||
"male": "Male",
|
||||
"female": "Female",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"fullscreen": "Fullscreen",
|
||||
"exitFullscreen": "Exit Fullscreen",
|
||||
"setting": "Settings",
|
||||
"columnSetting": "Column Settings",
|
||||
"sort": "Sort",
|
||||
"batchDelete": "Batch Delete",
|
||||
"export": "Export",
|
||||
"import": "Import",
|
||||
"downloadTemplate": "Download Template",
|
||||
"view": "View",
|
||||
"summarySum": "Sum",
|
||||
"summaryAvg": "Average",
|
||||
"summaryCount": "Count",
|
||||
"summaryMax": "Max",
|
||||
"summaryMin": "Min",
|
||||
"startDate": "Start Date",
|
||||
"endDate": "End Date",
|
||||
"selectDate": "Select Date",
|
||||
"table": "Table",
|
||||
"action": "Action",
|
||||
"placeholder": "Please enter",
|
||||
"selectPlaceholder": "Please select",
|
||||
"back": "Back",
|
||||
"createSuccess": "Created successfully",
|
||||
"updateSuccess": "Updated successfully",
|
||||
"saveFailed": "Save failed",
|
||||
"noDescription": "No description",
|
||||
"applicationName": "Application",
|
||||
"more": "More",
|
||||
"copied": "Copied",
|
||||
"deleted": "Deleted",
|
||||
"duplicate": "Duplicate",
|
||||
"description": "Description",
|
||||
"download": "Download",
|
||||
"downloadFailed": "Download failed",
|
||||
"loadError": "Load failed",
|
||||
"loadFailed": "Load failed",
|
||||
"loadingMenu": "Loading menu",
|
||||
"moveDown": "Move Down",
|
||||
"moveUp": "Move Up",
|
||||
"none": "None",
|
||||
"operationFailed": "Operation failed",
|
||||
"operationSuccess": "Operation successful",
|
||||
"pasted": "Pasted",
|
||||
"preview": "Preview",
|
||||
"prompt": "Prompt",
|
||||
"redone": "Redone",
|
||||
"refresh": "Refresh",
|
||||
"row": "Row",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"select": "Select",
|
||||
"selectAll": "Select All",
|
||||
"unselectAll": "Unselect All",
|
||||
"submit": "Submit",
|
||||
"tip": "Tip",
|
||||
"undo": "Undo",
|
||||
"undone": "Undone",
|
||||
"video": "Video",
|
||||
"exportData": "Export Data",
|
||||
"exportSuccess": "Export successful",
|
||||
"exportFailed": "Export failed",
|
||||
"exportCompleted": "Export completed, {0} records in total",
|
||||
"exportPreparing": "Preparing export...",
|
||||
"exportReady": "Ready to export...",
|
||||
"exportQuerying": "Querying data {0} / {1} records...",
|
||||
"exportGeneratingExcel": "Generating Excel file...",
|
||||
"exportGeneratingExcelShort": "Generating Excel...",
|
||||
"querying": "Querying...",
|
||||
"retryExport": "Retry Export",
|
||||
"fileDownloadFailed": "File download failed, please retry",
|
||||
"records": "records",
|
||||
"importFailed": "Import failed",
|
||||
"importPreparing": "Preparing import...",
|
||||
"importParsing": "Parsing Excel data {0} / {1} rows...",
|
||||
"importImporting": "Importing data {0} / {1} records...",
|
||||
"importValidating": "Validating data {0} / {1} rows...",
|
||||
"validatePreparing": "Preparing validation...",
|
||||
"importValidatingData": "Checking data uniqueness...",
|
||||
"willInsert": "Will insert {0} records",
|
||||
"willUpdate": "Will update {0} records",
|
||||
"willOverwrite": "Will overwrite {0} records",
|
||||
"comma": ", ",
|
||||
"closeConfirmTitle": "Confirm Close",
|
||||
"closeConfirmMessage": "An operation is in progress. Closing will interrupt it. Are you sure?",
|
||||
"maxImportRows": "Server supports importing up to {0}0,000 rows",
|
||||
"maxExportRows": "Server supports exporting up to {0}0,000 rows"
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
{
|
||||
"contractNo": "Contract No.",
|
||||
"contractTitle": "Contract Title",
|
||||
"templateNameUsed": "Template Used",
|
||||
"statusLabel": "Status",
|
||||
"creatorName": "Creator",
|
||||
"createdTimeInstance": "Created Time",
|
||||
"completedTimeInstance": "Completed Time",
|
||||
"actions": "Actions",
|
||||
"statusDraft": "Draft",
|
||||
"statusPending": "Pending Signature",
|
||||
"statusSigning": "Signing",
|
||||
"statusCompleted": "Completed",
|
||||
"statusCanceled": "Canceled",
|
||||
"statusExpired": "Expired",
|
||||
"createContract": "Create Contract",
|
||||
"view": "View",
|
||||
"edit": "Edit",
|
||||
"copy": "Copy",
|
||||
"mobileSignButton": "Mobile Sign",
|
||||
"submit": "Submit",
|
||||
"complete": "Complete",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"submitConfirm": "Are you sure you want to submit this contract? It will enter pending signature status after submission.",
|
||||
"submitConfirmTitle": "Submit Confirmation",
|
||||
"submitSuccess": "Submitted successfully",
|
||||
"completeConfirm": "Are you sure you want to complete this contract?",
|
||||
"completeConfirmTitle": "Complete Confirmation",
|
||||
"completeSuccess": "Contract completed",
|
||||
"cancelConfirm": "Are you sure you want to cancel this contract?",
|
||||
"cancelConfirmTitle": "Cancel Confirmation",
|
||||
"cancelSuccess": "Canceled",
|
||||
"deleteConfirm": "Are you sure you want to delete this contract?",
|
||||
"deleteConfirmTitle": "Delete Confirmation",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"templateNameLabel": "Template Name",
|
||||
"templateCodeLabel": "Template Code",
|
||||
"categoryLabel2": "Category",
|
||||
"version": "Version",
|
||||
"createTimeTemplate": "Create Time",
|
||||
"categoryName": "Category",
|
||||
"categorySales": "Sales Contract",
|
||||
"categoryPurchase": "Purchase Contract",
|
||||
"categoryLabor": "Labor Contract",
|
||||
"categoryLease": "Lease Contract",
|
||||
"categoryService": "Service Contract",
|
||||
"categoryOther": "Other",
|
||||
"statusPublished": "Published",
|
||||
"statusDisabled": "Disabled",
|
||||
"addTemplate": "Add Template",
|
||||
"batchDelete": "Batch Delete",
|
||||
"batchDeleteConfirm": "Are you sure you want to delete the selected {count} templates?",
|
||||
"batchDeleteConfirmTitle": "Batch Delete Confirmation",
|
||||
"publish": "Publish",
|
||||
"publishConfirm": "Are you sure you want to publish this template? It can be used to create contracts after publishing.",
|
||||
"publishConfirmTitle": "Publish Confirmation",
|
||||
"publishSuccess": "Published successfully",
|
||||
"disable": "Disable",
|
||||
"disableConfirm": "Are you sure you want to disable this template? New contracts cannot be created after disabling.",
|
||||
"disableConfirmTitle": "Disable Confirmation",
|
||||
"disableSuccess": "Disabled successfully",
|
||||
"copyTemplate": "Copy Template",
|
||||
"copyPrompt": "Please enter the code for the new template",
|
||||
"copyConfirm": "Confirm",
|
||||
"copyCancel": "Cancel",
|
||||
"copyCodePattern": "Code must start with a letter and can only contain letters, numbers and underscores",
|
||||
"copiedSuccess": "Copied successfully",
|
||||
"pleaseSelectTemplate": "Please select a contract template",
|
||||
"pleaseInputTitle": "Please enter contract title",
|
||||
"pleaseInputContractNo": "Please enter contract number",
|
||||
"contractNoInvalid": "Invalid contract number",
|
||||
"contractNoDuplicate": "Contract number already exists",
|
||||
"basicInfo": "Basic Info",
|
||||
"variableFill": "Variable Fill",
|
||||
"createDialog": "Create Contract",
|
||||
"editDialog": "Edit Contract",
|
||||
"copyDialog": "Copy Contract",
|
||||
"basicInfoConfig": "Basic Info Configuration",
|
||||
"contractTemplate": "Contract Template",
|
||||
"selectTemplate": "Please select a contract template",
|
||||
"contractNoLabel": "Contract No.",
|
||||
"inputContractNo": "Please enter contract number",
|
||||
"regenerate": "Regenerate",
|
||||
"contractTitleLabel": "Contract Title",
|
||||
"inputContractTitle": "Please enter contract title",
|
||||
"contractPreview": "Contract Preview",
|
||||
"variableRealtime": "Variables will be replaced in real-time",
|
||||
"variableFillForm": "Variable Fill",
|
||||
"noVariables": "No variables need to be filled for this template",
|
||||
"previousStep": "Previous",
|
||||
"nextStep": "Next",
|
||||
"save": "Save",
|
||||
"close": "Close",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"createSuccess": "Created successfully",
|
||||
"loadTemplateFailed": "Failed to load template details",
|
||||
"loadContractFailed": "Failed to load contract data",
|
||||
"saveFailed": "Save failed",
|
||||
"noTemplatesAvailable": "No templates available, please publish contract templates first",
|
||||
"selectNothing": "Please select templates to delete first",
|
||||
"selectNothingWarning": "Please select templates to delete first",
|
||||
"contractDetail": "Contract Details",
|
||||
"pageNumber": "Page {page} / {total}",
|
||||
"noContent": "No Content",
|
||||
"mobileSign": "Mobile Signature",
|
||||
"contract": "Contract",
|
||||
"partyType": "Party Type:",
|
||||
"signerName": "Signer Name:",
|
||||
"signerNamePlaceholder": "Optional, enter signer name",
|
||||
"signQrCode": "Signature QR Code",
|
||||
"generatingQrCode": "Generating QR code...",
|
||||
"scanQrCodeToSign": "Please scan the QR code with your mobile phone to sign",
|
||||
"qrCodeExpiredAt": "QR code expires at {time}",
|
||||
"refreshQrCode": "Refresh QR Code",
|
||||
"invalidSignUrl": "Invalid signature link",
|
||||
"pleaseSign": "Please sign first",
|
||||
"loading": "Loading...",
|
||||
"checkSignUrl": "Please check if the signature link is correct, or contact the contract initiator",
|
||||
"signComplete": "Signature Complete",
|
||||
"signCompleteMsg": "You have successfully completed the signature, you can close this page",
|
||||
"pleaseSignArea": "Please sign in the following area",
|
||||
"signed": "Signed",
|
||||
"sign": "Sign",
|
||||
"noSignArea": "No signature area required for you",
|
||||
"contractCompleted": "Contract signature completed",
|
||||
"handwriteSign": "Handwritten Signature",
|
||||
"signInArea": "Please write your signature in the area below",
|
||||
"confirmSign": "Confirm Signature",
|
||||
"editTemplate": "Edit Contract Template",
|
||||
"viewTemplate": "View Contract Template",
|
||||
"addTemplate": "Add Contract Template",
|
||||
"templateName": "Template Name",
|
||||
"inputTemplateName": "Please enter template name",
|
||||
"templateCode": "Template Code",
|
||||
"inputTemplateCode": "Please enter template code",
|
||||
"selectCategory": "Please select category",
|
||||
"templateDescription": "Template Description",
|
||||
"inputTemplateDescription": "Please enter template description",
|
||||
"contractNumber": "Contract Number",
|
||||
"usedTemplate": "Used Template",
|
||||
"status": "Status",
|
||||
"creator": "Creator",
|
||||
"createdTime": "Created Time",
|
||||
"completedTime": "Completed Time",
|
||||
"completeSign": "Complete Signing",
|
||||
"exportPdf": "Export PDF",
|
||||
"operationLog": "Operation Log",
|
||||
"noOperationLog": "No operation logs"
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"user": {
|
||||
"name": "User",
|
||||
"title": "User Management",
|
||||
"list": "User List",
|
||||
"userName": "User Name",
|
||||
"account": "Account",
|
||||
"mobile": "Mobile",
|
||||
"mobileFormatError": "Please enter a valid mobile phone format",
|
||||
"email": "Email",
|
||||
"emailFormatError": "Please enter a valid email format",
|
||||
"gender": "Gender",
|
||||
"unknown": "Unknown",
|
||||
"male": "Male",
|
||||
"female": "Female",
|
||||
"locked": "Locked",
|
||||
"status": "Status",
|
||||
"role": "Role",
|
||||
"post": "Post",
|
||||
"dept": "Department",
|
||||
"selectRole": "Please select role",
|
||||
"selectPost": "Please select post",
|
||||
"selectDept": "Please select department",
|
||||
"createTime": "Create Time",
|
||||
"operation": "Operation",
|
||||
"batchDelete": "Batch Delete",
|
||||
"resetPassword": "Reset Password",
|
||||
"resetPasswordTitle": "Reset Password",
|
||||
"resetPasswordConfirm": "Are you sure you want to reset the password for {0}?",
|
||||
"resetPasswordSuccess": "Successfully reset the password for {0}",
|
||||
"resetPasswordError": "Failed to reset password",
|
||||
"cannotDeleteAdmin": "Cannot delete administrator account",
|
||||
"cannotResetAdminPassword": "Cannot reset administrator password",
|
||||
"selectUsersToDelete": "Please select users to delete first",
|
||||
"batchDeleteTitle": "Batch Delete Users",
|
||||
"batchDeleteConfirm": "Are you sure you want to delete the selected {0} users? {1}",
|
||||
"deleteSuccess": "Successfully deleted {0} users",
|
||||
"deleteError": "Failed to delete users",
|
||||
"avatar": "Avatar",
|
||||
"selectAvatar": "Select Avatar",
|
||||
"avatarHelp": "Recommended to upload a square image, maximum 2MB",
|
||||
"birthday": "Birthday",
|
||||
"selectBirthday": "Select Birthday",
|
||||
"city": "City",
|
||||
"address": "Address",
|
||||
"bio": "Bio",
|
||||
"bioPlaceholder": "Please enter bio",
|
||||
"manager": "Manager",
|
||||
"selectManager": "Please select manager",
|
||||
"userType": "User Type",
|
||||
"systemUser": "System User",
|
||||
"normalUser": "Normal User",
|
||||
"externalUser": "External User"
|
||||
}
|
||||
}
|
||||
@@ -1,894 +0,0 @@
|
||||
{
|
||||
"title": "Dashboard Design",
|
||||
"preview": "Preview",
|
||||
"save": "Save",
|
||||
"saveSuccess": "Save successful",
|
||||
"clear": "Clear",
|
||||
"clearConfirm": "Are you sure you want to clear the canvas? This action cannot be undone.",
|
||||
"clearSuccess": "Cleared",
|
||||
"export": "Export",
|
||||
"exportSuccess": "Export successful",
|
||||
"import": "Import",
|
||||
"importTitle": "Import Configuration",
|
||||
"importPlaceholder": "Please paste JSON configuration content...",
|
||||
"importSuccess": "Import successful",
|
||||
"importError": "Invalid configuration format",
|
||||
"importEmpty": "Please enter configuration content",
|
||||
"viewCode": "View Code",
|
||||
"jsonPreview": "JSON Preview",
|
||||
"copyCode": "Copy Code",
|
||||
"copySuccess": "Copied to clipboard",
|
||||
"copyError": "Copy failed",
|
||||
"undoSuccess": "Undone",
|
||||
"redoSuccess": "Redone",
|
||||
"copyWidgetSuccess": "Copied",
|
||||
"pasteWidgetSuccess": "Pasted",
|
||||
"deleteWidgetSuccess": "Deleted",
|
||||
"noWidgetsTip": "Please add widgets first",
|
||||
"noConfigTip": "No dashboard configuration",
|
||||
"loadingData": "Loading data...",
|
||||
"loadDataError": "Failed to load data",
|
||||
"unknownWidget": "Unknown widget type",
|
||||
"widgetCount": "{count} widgets",
|
||||
"dragTip": "Drag widgets from the left to here",
|
||||
"add": "Add",
|
||||
"copy": "Copy",
|
||||
"delete": "Delete",
|
||||
"reset": "Reset",
|
||||
"clean": "Clear",
|
||||
"canvas": {
|
||||
"title": "Design Canvas",
|
||||
"adaptive": "Adaptive",
|
||||
"actualSize": "Actual Size",
|
||||
"settings": "Canvas Settings",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
},
|
||||
"months": {
|
||||
"jan": "Jan",
|
||||
"feb": "Feb",
|
||||
"mar": "Mar",
|
||||
"apr": "Apr",
|
||||
"may": "May",
|
||||
"jun": "Jun",
|
||||
"jul": "Jul",
|
||||
"aug": "Aug",
|
||||
"sep": "Sep",
|
||||
"oct": "Oct",
|
||||
"nov": "Nov",
|
||||
"dec": "Dec"
|
||||
},
|
||||
"weekdaysShort": {
|
||||
"mon": "Mon",
|
||||
"tue": "Tue",
|
||||
"wed": "Wed",
|
||||
"thu": "Thu",
|
||||
"fri": "Fri",
|
||||
"sat": "Sat",
|
||||
"sun": "Sun"
|
||||
},
|
||||
"material": {
|
||||
"title": "Material Library",
|
||||
"search": "Search widgets...",
|
||||
"category": {
|
||||
"common": "Common",
|
||||
"chart": "Chart",
|
||||
"filter": "Filter",
|
||||
"map": "Map",
|
||||
"media": "Media",
|
||||
"other": "Other"
|
||||
},
|
||||
"widgets": {
|
||||
"statCard": "Stat Card",
|
||||
"progressCard": "Progress Card",
|
||||
"chartLine": "Line Chart",
|
||||
"chartBar": "Bar Chart",
|
||||
"chartPie": "Pie Chart",
|
||||
"chartGauge": "Gauge Chart",
|
||||
"chartArea": "Area Chart",
|
||||
"chartRadar": "Radar Chart",
|
||||
"chartFunnel": "Funnel Chart",
|
||||
"chartScatter": "Scatter Chart",
|
||||
"chartRing": "Ring Progress",
|
||||
"chartHeatmap": "Heatmap",
|
||||
"chartKline": "K-line Chart",
|
||||
"chartSankey": "Sankey Chart",
|
||||
"todoList": "Todo List",
|
||||
"noticeList": "Message Notification",
|
||||
"announcementList": "Announcement List",
|
||||
"rankingList": "Ranking List",
|
||||
"quickLinks": "Quick Links",
|
||||
"welcomeCard": "Welcome Card",
|
||||
"calendar": "Calendar",
|
||||
"countdown": "Countdown",
|
||||
"clock": "Clock",
|
||||
"weather": "Weather",
|
||||
"imageCarousel": "Image Carousel",
|
||||
"dataTable": "Data Table",
|
||||
"iframe": "iframe Embed",
|
||||
"videoPlayer": "Video Player",
|
||||
"image": "Image",
|
||||
"formRender": "Form Render",
|
||||
"approvalCenter": "Approval Center",
|
||||
"myApps": "My Apps",
|
||||
"serverMonitor": "Server Monitor",
|
||||
"filterInput": "Input Filter",
|
||||
"filterSelect": "Select Filter",
|
||||
"filterDate": "Date Filter",
|
||||
"filterDateRange": "Date Range"
|
||||
},
|
||||
"defaultProps": {
|
||||
"statTitle": "Statistic Data",
|
||||
"trendLabel": "vs Yesterday",
|
||||
"progressTitle": "Completion Progress",
|
||||
"visitTrend": "Visit Trend",
|
||||
"visits": "Visits",
|
||||
"downloads": "Downloads",
|
||||
"salesStat": "Sales Statistics",
|
||||
"trafficSource": "Traffic Source",
|
||||
"searchEngine": "Search Engine",
|
||||
"directAccess": "Direct Access",
|
||||
"emailMarketing": "Email Marketing",
|
||||
"unionAds": "Union Ads",
|
||||
"videoAds": "Video Ads",
|
||||
"sysLoad": "System Load",
|
||||
"abilityEval": "Ability Evaluation",
|
||||
"sales": "Sales",
|
||||
"mgmt": "Mgmt",
|
||||
"tech": "Tech",
|
||||
"cs": "CS",
|
||||
"rd": "R&D",
|
||||
"mkt": "Mkt",
|
||||
"budget": "Budget",
|
||||
"actual": "Actual",
|
||||
"convFunnel": "Conversion Funnel",
|
||||
"visit": "Visit",
|
||||
"consult": "Consult",
|
||||
"intent": "Intent",
|
||||
"order": "Order",
|
||||
"deal": "Deal",
|
||||
"dataDist": "Data Distribution",
|
||||
"height": "Height (cm)",
|
||||
"weight": "Weight (kg)",
|
||||
"male": "Male",
|
||||
"female": "Female",
|
||||
"kpiDone": "KPI Completion",
|
||||
"salesVolume": "Sales Volume",
|
||||
"orderVolume": "Order Volume",
|
||||
"customerCount": "Customer Count",
|
||||
"weekVisitHeat": "Weekly Visit Heat",
|
||||
"stockTrend": "Stock Trend",
|
||||
"todoTitle": "To-do Items",
|
||||
"todoItem1": "Complete project report",
|
||||
"todoItem2": "Team weekly meeting",
|
||||
"todoItem3": "Code review",
|
||||
"latestNotice": "Message Notification",
|
||||
"latestAnnouncement": "Latest Announcements",
|
||||
"salesRanking": "Sales Ranking",
|
||||
"welcomeTitle": "Welcome back",
|
||||
"welcomeSubtitle": "Today is a good day",
|
||||
"countdownTitle": "Event Countdown",
|
||||
"todayWeather": "Today's Weather",
|
||||
"dataList": "Data List",
|
||||
"externalPage": "External Page",
|
||||
"itemName": "Item",
|
||||
"imageTitle": "Image",
|
||||
"myDashboard": "My Dashboard",
|
||||
"formRender": "Form Data",
|
||||
"approvalCenter": "Approval Center",
|
||||
"myApps": "My Apps",
|
||||
"serverMonitor": "Server Monitor",
|
||||
"filterInput": "Keyword",
|
||||
"filterInputPlaceholder": "Enter keyword to filter...",
|
||||
"filterSelect": "Select Filter",
|
||||
"filterSelectPlaceholder": "Please select...",
|
||||
"filterDate": "Date",
|
||||
"filterDatePlaceholder": "Select date",
|
||||
"filterDateRange": "Date Range",
|
||||
"filterStartDate": "Start Date",
|
||||
"filterEndDate": "End Date"
|
||||
}
|
||||
},
|
||||
"attribute": {
|
||||
"title": "Attribute Panel",
|
||||
"canvasConfig": "Canvas Config",
|
||||
"widgetConfig": "Widget Config",
|
||||
"styleConfig": "Style Config",
|
||||
"dataConfig": "Data Config",
|
||||
"globalSettings": "Global Settings",
|
||||
"dashboardName": "Dashboard Name",
|
||||
"gridLayout": "Grid Layout",
|
||||
"columns": "Columns",
|
||||
"rowHeight": "Row Height (px)",
|
||||
"widgetMargin": "Widget Margin (px)",
|
||||
"horizontal": "Horizontal",
|
||||
"vertical": "Vertical",
|
||||
"background": "Background",
|
||||
"backgroundColor": "Background Color",
|
||||
"reset": "Reset",
|
||||
"display": "Display",
|
||||
"outerMargin": "Outer Margin",
|
||||
"outerMarginTip": "Show outer margin during rendering",
|
||||
"widgetTitle": "Title",
|
||||
"layout": "Layout",
|
||||
"widthGrid": "Width (Grid)",
|
||||
"heightGrid": "Height (Grid)",
|
||||
"layoutTip": "Tip: Resize widgets directly on the canvas",
|
||||
"iconConfig": "Icon Config",
|
||||
"icon": "Icon",
|
||||
"iconColor": "Icon Color",
|
||||
"status": "Status",
|
||||
"strokeWidth": "Stroke Width",
|
||||
"showText": "Show Text",
|
||||
"border": "Border",
|
||||
"borderWidth": "Border Width",
|
||||
"borderColor": "Border Color",
|
||||
"borderStyleLabel": "Border Style",
|
||||
"borderRadius": "Corner Radius",
|
||||
"shadow": "Shadow",
|
||||
"enableShadow": "Enable Shadow",
|
||||
"shadowColor": "Shadow Color",
|
||||
"shadowBlur": "Blur Radius",
|
||||
"chartLayout": "Chart Layout",
|
||||
"margin": {
|
||||
"left": "Left Margin (%)",
|
||||
"right": "Right Margin (%)",
|
||||
"top": "Top Margin (%)",
|
||||
"bottom": "Bottom Margin (%)"
|
||||
},
|
||||
"tabs": {
|
||||
"basic": "Basic",
|
||||
"data": "Data",
|
||||
"style": "Style"
|
||||
},
|
||||
"placeholder": {
|
||||
"name": "Please enter name",
|
||||
"title": "Please enter title",
|
||||
"remark": "Please enter description"
|
||||
},
|
||||
"iconOptions": {
|
||||
"trending": "Trending",
|
||||
"creditCard": "Credit Card",
|
||||
"users": "Users",
|
||||
"activity": "Activity",
|
||||
"bell": "Bell",
|
||||
"award": "Award"
|
||||
},
|
||||
"progressStatus": {
|
||||
"default": "Default",
|
||||
"success": "Success",
|
||||
"warning": "Warning",
|
||||
"exception": "Exception"
|
||||
},
|
||||
"colorTheme": {
|
||||
"label": "Color Theme",
|
||||
"default": "Default",
|
||||
"fresh": "Fresh",
|
||||
"business": "Business",
|
||||
"tech": "Tech",
|
||||
"warm": "Warm"
|
||||
},
|
||||
"chart": {
|
||||
"smooth": "Smooth Curve",
|
||||
"showArea": "Show Area",
|
||||
"showSymbol": "Show Symbol",
|
||||
"symbolSize": "Symbol Size",
|
||||
"lineWidth": "Line Width",
|
||||
"axis": "Axis",
|
||||
"xAxisName": "X-Axis Name",
|
||||
"yAxisName": "Y-Axis Name",
|
||||
"nameLocation": "Name Location",
|
||||
"legend": "Legend",
|
||||
"showLegend": "Show Legend",
|
||||
"legendPosition": "Legend Position",
|
||||
"barWidth": "Bar Width",
|
||||
"barRadius": "Bar Radius",
|
||||
"horizontal": "Horizontal",
|
||||
"stack": "Stack",
|
||||
"showBackground": "Show Background",
|
||||
"pieType": "Pie Type",
|
||||
"showLabel": "Show Label",
|
||||
"labelPosition": "Label Position",
|
||||
"minValue": "Min Value",
|
||||
"maxValue": "Max Value",
|
||||
"splitNumber": "Split Number",
|
||||
"showProgress": "Show Progress",
|
||||
"showMA5": "Show MA5",
|
||||
"showMA10": "Show MA10",
|
||||
"shape": "Shape",
|
||||
"sort": "Sort",
|
||||
"orient": "Orient",
|
||||
"pointSize": "Point Size",
|
||||
"numerical": "Numerical Config",
|
||||
"currentValue": "Current Value",
|
||||
"min": "Min",
|
||||
"max": "Max",
|
||||
"unit": "Unit",
|
||||
"maConfig": "MA Config",
|
||||
"prefix": "Prefix",
|
||||
"suffix": "Suffix",
|
||||
"trendConfig": "Trend Config",
|
||||
"trendValue": "Trend Value (%)",
|
||||
"trendTip": "Positive for increase, negative for decrease",
|
||||
"trendLabel": "Trend Description",
|
||||
"seriesNamePrefix": "Series",
|
||||
"itemNamePrefix": "Item",
|
||||
"placeholder": {
|
||||
"xAxis": "e.g. Month",
|
||||
"yAxis": "e.g. Sales",
|
||||
"unit": "e.g. %",
|
||||
"title": "Please enter title",
|
||||
"xAxis2": "Separated by commas, e.g. Jan, Feb, Mar",
|
||||
"seriesData": "Separated by commas, e.g. 100, 200, 300",
|
||||
"seriesName": "Series Name",
|
||||
"prefix": "e.g. $",
|
||||
"suffix": "e.g. items, %",
|
||||
"trendLabel": "e.g. vs Yesterday",
|
||||
"subtitle": "Please enter subtitle"
|
||||
},
|
||||
"location": {
|
||||
"start": "Start",
|
||||
"middle": "Middle",
|
||||
"end": "End",
|
||||
"top": "Top",
|
||||
"bottom": "Bottom",
|
||||
"left": "Left",
|
||||
"right": "Right"
|
||||
},
|
||||
"pieTypes": {
|
||||
"pie": "Pie",
|
||||
"ring": "Ring",
|
||||
"rose": "Rose"
|
||||
},
|
||||
"labelPositions": {
|
||||
"outside": "Outside",
|
||||
"inside": "Inside"
|
||||
},
|
||||
"barWidthOptions": {
|
||||
"auto": "Auto",
|
||||
"thin": "Thin (30%)",
|
||||
"medium": "Medium (50%)",
|
||||
"thick": "Thick (70%)"
|
||||
},
|
||||
"shapes": {
|
||||
"polygon": "Polygon",
|
||||
"circle": "Circle"
|
||||
},
|
||||
"sortOptions": {
|
||||
"descending": "Descending",
|
||||
"ascending": "Ascending",
|
||||
"none": "None"
|
||||
},
|
||||
"orientOptions": {
|
||||
"vertical": "Vertical",
|
||||
"horizontal": "Horizontal"
|
||||
},
|
||||
"dataEditMode": "Edit Mode",
|
||||
"dataEditForm": "Form",
|
||||
"dataEditJson": "JSON",
|
||||
"jsonPlaceholder": "JSON format data",
|
||||
"jsonTip": "Edit JSON directly, automatically applied on blur",
|
||||
"xAxisData": "X-Axis Data",
|
||||
"seriesDataLabel": "Series Data",
|
||||
"addSeries": "Add Series",
|
||||
"dataItem": "Data Item",
|
||||
"addItem": "Add Data Item",
|
||||
"radarTip": "Radar chart data is complex, JSON mode is recommended",
|
||||
"scatterTip": "Scatter chart data is coordinate points, JSON mode is recommended",
|
||||
"ringTip": "Ring progress data JSON mode is recommended",
|
||||
"heatmapTip": "Heatmap data JSON mode is recommended",
|
||||
"klineTip": "K-line data JSON mode is recommended",
|
||||
"sankeyTip": "Sankey data JSON mode is recommended",
|
||||
"gaugeTip": "Gauge data set 'Current Value' in Basic Config"
|
||||
},
|
||||
"dataSource": {
|
||||
"dataType": "Data Type",
|
||||
"static": "Static Data",
|
||||
"uploadImage": "Image Upload",
|
||||
"uploadVideo": "Video Upload",
|
||||
"dataSource": "Data Source",
|
||||
"api": "API Interface",
|
||||
"config": "Data Source Config",
|
||||
"select": "Select Data Source",
|
||||
"selectPlaceholder": "Please select data source",
|
||||
"mapping": "Field Mapping",
|
||||
"sourceField": "Source Field",
|
||||
"targetField": "Target",
|
||||
"addMapping": "Add Mapping",
|
||||
"mappingTip": "Tip: Data source mapping is configured in 'Data Source Management', additional mappings here override default.",
|
||||
"refresh": "Refresh Config",
|
||||
"autoRefresh": "Auto Refresh",
|
||||
"interval": "Refresh Interval",
|
||||
"intervalUnit": "Unit: seconds, min 5 seconds",
|
||||
"apiConfig": "API Config",
|
||||
"apiUrl": "Request URL",
|
||||
"apiUrlPlaceholder": "e.g. /api/dashboard/stats",
|
||||
"apiMethod": "Request Method",
|
||||
"dataPath": "Data Path",
|
||||
"dataPathPlaceholder": "e.g. data.list",
|
||||
"dataPathTip": "Path to extract data from response",
|
||||
"enableRefresh": "Enable Refresh",
|
||||
"intervalSeconds": "Refresh Interval (s)",
|
||||
"paramBinding": "Param Binding",
|
||||
"paramName": "Data Source Param",
|
||||
"globalParam": "Filter Param",
|
||||
"addBinding": "Add Binding",
|
||||
"paramBindingTip": "Bind filter component parameters to data source parameters for interactive filtering. Select data source parameter on the left, select or enter filter parameter key on the right.",
|
||||
"required": "Required"
|
||||
},
|
||||
"widget": {
|
||||
"todo": {
|
||||
"items": "Todo Items",
|
||||
"add": "Add Todo",
|
||||
"done": "Done",
|
||||
"newItem": "New Todo",
|
||||
"priority": {
|
||||
"high": "High",
|
||||
"medium": "Medium",
|
||||
"low": "Low"
|
||||
}
|
||||
},
|
||||
"notice": {
|
||||
"tip": "Notice list automatically fetched from system messages, no manual configuration needed",
|
||||
"limit": "Display Count"
|
||||
},
|
||||
"announcement": {
|
||||
"tip": "Announcement list automatically fetched from system announcements, no manual configuration needed",
|
||||
"limit": "Display Count"
|
||||
},
|
||||
"ranking": {
|
||||
"items": "Ranking Items",
|
||||
"add": "Add Ranking",
|
||||
"newItem": "New Member"
|
||||
},
|
||||
"quickLinks": {
|
||||
"title": "Quick Links",
|
||||
"tip": "Click grid to select menu, icon automatically uses menu config",
|
||||
"iconColorDefault": "Default Theme Color",
|
||||
"selectMenu": "Select Menu"
|
||||
},
|
||||
"welcome": {
|
||||
"subtitle": "Subtitle",
|
||||
"subtitlePlaceholder": "Please enter subtitle",
|
||||
"showTime": "Show Time"
|
||||
},
|
||||
"countdown": {
|
||||
"config": "Countdown Settings",
|
||||
"targetTime": "Target Time",
|
||||
"targetTimePlaceholder": "Select target time",
|
||||
"showDays": "Show Days",
|
||||
"showHours": "Show Hours",
|
||||
"showMinutes": "Show Minutes",
|
||||
"showSeconds": "Show Seconds",
|
||||
"finishedText": "Finished Text",
|
||||
"finishedTextPlaceholder": "e.g. Finished"
|
||||
},
|
||||
"clock": {
|
||||
"config": "Clock Settings",
|
||||
"showDate": "Show Date",
|
||||
"showSeconds": "Show Seconds",
|
||||
"format24": "24H Format",
|
||||
"timezone": "Timezone",
|
||||
"timezoneOptions": {
|
||||
"local": "Local Time",
|
||||
"utc": "UTC",
|
||||
"beijing": "Beijing Time",
|
||||
"tokyo": "Tokyo Time",
|
||||
"newyork": "New York Time",
|
||||
"london": "London Time"
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
"config": "Weather Config",
|
||||
"cityName": "City Name",
|
||||
"cityNamePlaceholder": "e.g. Beijing",
|
||||
"presetCity": "Preset City",
|
||||
"presetCityPlaceholder": "Quick select city",
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude",
|
||||
"autoLocate": "Auto Locate",
|
||||
"refreshInterval": "Refresh Interval (min)",
|
||||
"refreshIntervalTip": "Unit: minutes, minimum 5 minutes"
|
||||
},
|
||||
"carousel": {
|
||||
"config": "Carousel Settings",
|
||||
"autoplay": "Autoplay",
|
||||
"interval": "Interval",
|
||||
"intervalUnit": "ms",
|
||||
"showIndicator": "Show Indicator",
|
||||
"showArrow": "Show Arrow",
|
||||
"imageList": "Image List",
|
||||
"addImage": "Add Image",
|
||||
"selectFromGallery": "From Gallery",
|
||||
"selectedImages": "Selected Images",
|
||||
"selectTip": "Please select images from gallery",
|
||||
"newItem": "New Image"
|
||||
},
|
||||
"table": {
|
||||
"config": "Table Settings",
|
||||
"stripe": "Stripe",
|
||||
"border": "Border",
|
||||
"showIndex": "Show Index",
|
||||
"showHeader": "Show Header",
|
||||
"highlightCurrentRow": "Highlight Current Row",
|
||||
"alignment": "Alignment",
|
||||
"headerAlign": "Header Align",
|
||||
"cellAlign": "Cell Align",
|
||||
"heightConfig": "Height Config",
|
||||
"height": "Fixed Height",
|
||||
"heightPlaceholder": "e.g. 300px or 100%",
|
||||
"heightTip": "Supports pixels or percentage",
|
||||
"maxHeight": "Max Height",
|
||||
"maxHeightPlaceholder": "e.g. 500",
|
||||
"maxHeightTip": "Shows scrollbar when exceeded",
|
||||
"customization": "Customization",
|
||||
"emptyText": "Empty Text",
|
||||
"emptyTextPlaceholder": "No data",
|
||||
"headerBgColor": "Header Background",
|
||||
"summaryConfig": "Summary Config",
|
||||
"showSummary": "Show Footer Summary",
|
||||
"summaryType": "Summary Type",
|
||||
"summaryTypes": {
|
||||
"sum": "Sum",
|
||||
"avg": "Average",
|
||||
"count": "Count",
|
||||
"max": "Maximum",
|
||||
"min": "Minimum"
|
||||
},
|
||||
"summaryPrecision": "Decimal Places",
|
||||
"summaryColumns": "Summary Columns",
|
||||
"noColumnsConfigured": "Please configure table columns first",
|
||||
"mergeConfig": "Merge Config",
|
||||
"enableMerge": "Enable Merge",
|
||||
"mergeRules": "Merge Rules",
|
||||
"mergeTypes": {
|
||||
"row": "Row Merge",
|
||||
"column": "Column Merge"
|
||||
},
|
||||
"selectMergeField": "Select merge field",
|
||||
"rowIndex": "Row Index",
|
||||
"startCol": "Start Column",
|
||||
"colspan": "Column Span",
|
||||
"addMergeRule": "Add Merge Rule",
|
||||
"size": "Size",
|
||||
"sizeOptions": {
|
||||
"default": "Default",
|
||||
"large": "Large",
|
||||
"small": "Small"
|
||||
},
|
||||
"columnConfig": "Column Config",
|
||||
"columnConfigJson": "Column Config JSON",
|
||||
"tableData": "Table Data",
|
||||
"tableDataJson": "Table Data JSON",
|
||||
"complexDataTip": "Table data structure is complex, JSON mode is recommended"
|
||||
},
|
||||
"iframe": {
|
||||
"config": "Display Settings",
|
||||
"showBorder": "Show Border",
|
||||
"allowFullscreen": "Allow Fullscreen",
|
||||
"pageAddress": "Page Address"
|
||||
},
|
||||
"video": {
|
||||
"config": "Playback Settings",
|
||||
"autoplay": "Autoplay",
|
||||
"loop": "Loop",
|
||||
"muted": "Muted",
|
||||
"controls": "Native Controls",
|
||||
"videoAddress": "Video Address",
|
||||
"poster": "Poster",
|
||||
"posterPlaceholder": "Poster Image URL (Optional)",
|
||||
"selectFromFileLib": "From File Library"
|
||||
},
|
||||
"image": {
|
||||
"title": "Image",
|
||||
"config": "Image Settings",
|
||||
"alt": "Alt Text",
|
||||
"altPlaceholder": "Alt text when image cannot be displayed",
|
||||
"fit": "Fit Mode",
|
||||
"fitOptions": {
|
||||
"cover": "Cover",
|
||||
"contain": "Contain",
|
||||
"fill": "Fill",
|
||||
"none": "None",
|
||||
"scaleDown": "Scale Down"
|
||||
},
|
||||
"lazy": "Lazy Load",
|
||||
"previewConfig": "Preview Settings",
|
||||
"zIndex": "Z-Index",
|
||||
"closeOnClickModal": "Close on Click Modal",
|
||||
"url": "Image URL",
|
||||
"urlPlaceholder": "Please enter image URL",
|
||||
"link": "Link URL",
|
||||
"previewList": "Preview List",
|
||||
"previewListTip": "List of images to preview when clicked, current image if empty",
|
||||
"addImage": "Add Image",
|
||||
"mainImage": "Main Image"
|
||||
},
|
||||
"common": {
|
||||
"staticDataTip": "Current component uses static data, can be modified in Basic Config"
|
||||
},
|
||||
"formRender": {
|
||||
"formConfig": "Form Config",
|
||||
"formCode": "Select Form",
|
||||
"formCodePlaceholder": "Please select a form",
|
||||
"containerType": "Container Type",
|
||||
"drawer": "Drawer",
|
||||
"dialog": "Dialog",
|
||||
"displayConfig": "Display Config",
|
||||
"showToolbar": "Show Toolbar",
|
||||
"showPagination": "Show Pagination",
|
||||
"pageSize": "Page Size",
|
||||
"buttonConfig": "Button Config",
|
||||
"showAdd": "Add Button",
|
||||
"showView": "View Button",
|
||||
"showEdit": "Edit Button",
|
||||
"showDelete": "Delete Button"
|
||||
}
|
||||
},
|
||||
"fieldLabels": {
|
||||
"title": "Title",
|
||||
"value": "Value",
|
||||
"time": "Time",
|
||||
"trend": "Trend %",
|
||||
"trendLabel": "Trend Desc",
|
||||
"prefix": "Prefix",
|
||||
"suffix": "Suffix",
|
||||
"percentage": "Percentage",
|
||||
"xAxisData": "X-Axis Data",
|
||||
"seriesData": "Series Data",
|
||||
"currentValue": "Current Value",
|
||||
"min": "Min",
|
||||
"max": "Max",
|
||||
"listData": "List Data",
|
||||
"linkData": "Link Data"
|
||||
},
|
||||
"resultType": {
|
||||
"list": "List",
|
||||
"tree": "Tree",
|
||||
"object": "Object",
|
||||
"value": "Value",
|
||||
"chartAxis": "Axis Chart",
|
||||
"chartPie": "Pie Chart",
|
||||
"chartGauge": "Gauge Chart",
|
||||
"chartRadar": "Radar Chart",
|
||||
"chartScatter": "Scatter Chart",
|
||||
"chartHeatmap": "Heatmap"
|
||||
},
|
||||
"borderStyle": {
|
||||
"solid": "Solid",
|
||||
"dashed": "Dashed",
|
||||
"dotted": "Dotted",
|
||||
"none": "None"
|
||||
}
|
||||
},
|
||||
"widgets": {
|
||||
"announcement": {
|
||||
"markAllRead": "Mark All as Read",
|
||||
"top": "Top",
|
||||
"noData": "No Announcements",
|
||||
"loading": "Loading...",
|
||||
"noContent": "No detailed content",
|
||||
"publisher": "Publisher: ",
|
||||
"priority": {
|
||||
"normal": "Normal",
|
||||
"important": "Important",
|
||||
"urgent": "Urgent"
|
||||
},
|
||||
"time": {
|
||||
"justNow": "Just now",
|
||||
"minutesAgo": " minutes ago",
|
||||
"hoursAgo": " hours ago",
|
||||
"daysAgo": " days ago"
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
"city": "City",
|
||||
"humidity": "Humidity",
|
||||
"wind": "Wind",
|
||||
"loading": "Loading weather data...",
|
||||
"error": "Failed to fetch weather data",
|
||||
"codes": {
|
||||
"clear": "Clear",
|
||||
"mainlyClear": "Mainly Clear",
|
||||
"partlyCloudy": "Partly Cloudy",
|
||||
"overcast": "Overcast",
|
||||
"fog": "Fog",
|
||||
"drizzle": "Drizzle",
|
||||
"rain": "Rain",
|
||||
"snow": "Snow",
|
||||
"showers": "Showers",
|
||||
"snowShowers": "Snow Showers",
|
||||
"thunderstorm": "Thunderstorm"
|
||||
},
|
||||
"windDir": {
|
||||
"n": "N",
|
||||
"ne": "NE",
|
||||
"e": "E",
|
||||
"se": "SE",
|
||||
"s": "S",
|
||||
"sw": "SW",
|
||||
"w": "W",
|
||||
"nw": "NW"
|
||||
}
|
||||
},
|
||||
"chart": {
|
||||
"visits": "Visits"
|
||||
},
|
||||
"iframe": {
|
||||
"refresh": "Refresh",
|
||||
"openNew": "Open in New Tab",
|
||||
"placeholder": "iframe Embedding",
|
||||
"noUrl": "URL not set",
|
||||
"loading": "Loading...",
|
||||
"loadFailed": "Load failed",
|
||||
"retry": "Click to retry"
|
||||
},
|
||||
"todo": {
|
||||
"title": "To-do List",
|
||||
"noData": "No to-dos",
|
||||
"priority": {
|
||||
"high": "High",
|
||||
"medium": "Medium",
|
||||
"low": "Low"
|
||||
}
|
||||
},
|
||||
"dataTable": {
|
||||
"status": {
|
||||
"normal": "Normal",
|
||||
"warning": "Warning",
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"failed": "Failed",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"summary": {
|
||||
"sum": "Sum",
|
||||
"avg": "Avg",
|
||||
"count": "Count",
|
||||
"max": "Max",
|
||||
"min": "Min"
|
||||
}
|
||||
},
|
||||
"formRender": {
|
||||
"placeholder": "Form Render Widget",
|
||||
"noFormCode": "Please configure form code",
|
||||
"deleteConfirm": "Are you sure to delete this data?",
|
||||
"batchDeleteConfirm": "Are you sure to delete the selected {count} records?",
|
||||
"importResult": "Successfully imported {success}, failed {failed}",
|
||||
"view": "View"
|
||||
},
|
||||
"welcome": {
|
||||
"greeting": {
|
||||
"night": "Good late night",
|
||||
"morning": "Good morning",
|
||||
"morning2": "Good morning",
|
||||
"noon": "Good noon",
|
||||
"afternoon": "Good afternoon",
|
||||
"evening": "Good evening"
|
||||
}
|
||||
},
|
||||
"countdown": {
|
||||
"day": "d",
|
||||
"hour": "h",
|
||||
"minute": "m",
|
||||
"second": "s",
|
||||
"finished": "Finished"
|
||||
},
|
||||
"video": {
|
||||
"placeholder": "Video Player"
|
||||
},
|
||||
"ranking": {
|
||||
"tenThousand": "W"
|
||||
},
|
||||
"clock": {
|
||||
"weekdays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
||||
"year": "-",
|
||||
"month": "-",
|
||||
"day": ""
|
||||
},
|
||||
"image": {
|
||||
"title": "Image",
|
||||
"noData": "No images"
|
||||
},
|
||||
"approvalCenter": {
|
||||
"initiated": "My Initiated",
|
||||
"pending": "My Pending",
|
||||
"handling": "My Handling",
|
||||
"signing": "My Signing",
|
||||
"handled": "My Handled",
|
||||
"copy": "CC to Me",
|
||||
"start": "Start Process",
|
||||
"more": "More",
|
||||
"config": "Approval Center Config",
|
||||
"routePrefix": "Route Prefix"
|
||||
},
|
||||
"myApps": {
|
||||
"more": "More",
|
||||
"noData": "No apps"
|
||||
},
|
||||
"serverMonitor": {
|
||||
"config": "Server Monitor Config",
|
||||
"cpuBgColor": "CPU Background",
|
||||
"memoryBgColor": "Memory Background",
|
||||
"diskBgColor": "Disk Background",
|
||||
"networkBgColor": "Network Background",
|
||||
"core": " Cores",
|
||||
"thread": " Threads",
|
||||
"memory": "Memory",
|
||||
"disk": "Disk",
|
||||
"network": "Network",
|
||||
"read": "Read",
|
||||
"write": "Write",
|
||||
"upload": "Upload",
|
||||
"download": "Download",
|
||||
"totalRW": "Total R/W",
|
||||
"uptime": "Uptime",
|
||||
"days": "d ",
|
||||
"hours": "h ",
|
||||
"minutes": "m"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"config": "Filter Config",
|
||||
"paramKey": "Param Key",
|
||||
"paramKeyPlaceholder": "e.g. status",
|
||||
"startParamKey": "Start Date Param",
|
||||
"startParamKeyPlaceholder": "e.g. start_date",
|
||||
"endParamKey": "End Date Param",
|
||||
"endParamKeyPlaceholder": "e.g. end_date",
|
||||
"label": "Label",
|
||||
"labelPlaceholder": "Enter label",
|
||||
"defaultValue": "Default Value",
|
||||
"noParamKey": "Param key not configured",
|
||||
"optionConfig": "Option Config",
|
||||
"optionSource": "Option Source",
|
||||
"staticOptions": "Static Options",
|
||||
"optionLabel": "Label",
|
||||
"optionValue": "Value",
|
||||
"addOption": "Add Option",
|
||||
"optionDataSource": "Option Data Source",
|
||||
"labelField": "Label Field",
|
||||
"valueField": "Value Field",
|
||||
"multiple": "Multiple",
|
||||
"dateFormat": "Date Format",
|
||||
"styleConfig": "Style Config",
|
||||
"labelPosition": "Label Position",
|
||||
"labelPositionLeft": "Left",
|
||||
"labelPositionTop": "Top",
|
||||
"labelPositionHidden": "Hidden",
|
||||
"labelWidth": "Label Width",
|
||||
"labelAlign": "Label Align",
|
||||
"alignLeft": "Left",
|
||||
"alignCenter": "Center",
|
||||
"alignRight": "Right",
|
||||
"componentSize": "Size",
|
||||
"sizeLarge": "Large",
|
||||
"sizeDefault": "Default",
|
||||
"sizeSmall": "Small",
|
||||
"showBorder": "Show Border",
|
||||
"borderRadius": "Border Radius"
|
||||
},
|
||||
"gradient": {
|
||||
"solid": "Solid",
|
||||
"gradient": "Gradient",
|
||||
"startColor": "Start",
|
||||
"endColor": "End",
|
||||
"direction": "Direction",
|
||||
"noColor": "Not set",
|
||||
"presetTitle": "Preset Gradients",
|
||||
"presets": {
|
||||
"warmSunrise": "Warm Sunrise",
|
||||
"oceanBreeze": "Ocean Breeze",
|
||||
"freshMint": "Fresh Mint",
|
||||
"peachGlow": "Peach Glow",
|
||||
"lavenderDream": "Lavender Dream",
|
||||
"skyBlue": "Sky Blue",
|
||||
"roseWater": "Rose Water",
|
||||
"softGrass": "Soft Grass",
|
||||
"winterNymph": "Winter Nymph",
|
||||
"cottonCandy": "Cotton Candy",
|
||||
"sunnyMorning": "Sunny Morning",
|
||||
"crystalClear": "Crystal Clear"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
{
|
||||
"basicInfo": "Basic Info",
|
||||
"codePlaceholder": "Enter unique code, e.g. user_list",
|
||||
"dataSourceConfig": "Data Source Configuration",
|
||||
"dataSourceName": "Data Source Name",
|
||||
"inputDataSourceName": "Please enter data source name",
|
||||
"dataSourceType": "Data Source Type",
|
||||
"selectDataSourceType": "Please select data source type",
|
||||
"dataSourceDescription": "Data Source Description",
|
||||
"inputDataSourceDescription": "Please enter data source description",
|
||||
"testConnection": "Test Connection",
|
||||
"testConnectionSuccess": "Connection successful",
|
||||
"testConnectionFailed": "Connection failed",
|
||||
"testing": "Testing...",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"create": "Create",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"query": "Query",
|
||||
"sqlEditor": "SQL Editor",
|
||||
"executeSql": "Execute SQL",
|
||||
"executing": "Executing...",
|
||||
"queryResult": "Query Result",
|
||||
"noData": "No data",
|
||||
"error": "Error",
|
||||
"confirm": "Confirm",
|
||||
"close": "Close",
|
||||
"dbSchema": "Database",
|
||||
"tables": "Tables",
|
||||
"columns": "Columns",
|
||||
"dataType": "Data Type",
|
||||
"nullable": "Nullable",
|
||||
"primaryKey": "Primary Key",
|
||||
"loading": "Loading...",
|
||||
"paramType": "Parameter Type",
|
||||
"resultType": "Result Type",
|
||||
"httpMethod": "HTTP Method",
|
||||
"requestUrl": "Request URL",
|
||||
"inputRequestUrl": "Please enter request URL",
|
||||
"requestHeaders": "Request Headers",
|
||||
"requestBody": "Request Body",
|
||||
"responseMapping": "Response Mapping",
|
||||
"addParam": "Add Parameter",
|
||||
"paramName": "Parameter Name",
|
||||
"paramValue": "Parameter Value",
|
||||
"paramLabel": "Display Name",
|
||||
"deleteParam": "Delete Parameter",
|
||||
"total": "Total",
|
||||
"limited": "Limited",
|
||||
"dataSourceList": "Data Source List",
|
||||
"createDataSource": "Create Data Source",
|
||||
"editDataSource": "Edit Data Source",
|
||||
"deleteDataSourceConfirm": "Are you sure you want to delete this data source?",
|
||||
"deleteDataSourceSuccess": "Deleted successfully",
|
||||
"createDataSourceSuccess": "Created successfully",
|
||||
"updateDataSourceSuccess": "Updated successfully",
|
||||
"loadDataSourceFailed": "Failed to load data source",
|
||||
"testDataSourceFailed": "Failed to test data source",
|
||||
"previousStep": "Previous",
|
||||
"nextStep": "Next",
|
||||
"basicInfoConfig": "Basic Information Configuration",
|
||||
"status": "Status",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"selectDbSchema": "Please select database on the left",
|
||||
"tip": "Tip",
|
||||
"useParamPlaceholder": "Use",
|
||||
"onlySelectQuery": "as parameter placeholder, only SELECT queries are allowed",
|
||||
"paramDefinition": "Parameter Definition",
|
||||
"defineDataSourceParams": "Define parameters that the data source can receive",
|
||||
"required": "Required",
|
||||
"action": "Action",
|
||||
"resultProcessing": "Result Processing",
|
||||
"resultTypeListDesc": "Return array data directly, suitable for tables, dropdown selections and other components.",
|
||||
"resultTypeTreeDesc": "Convert flat list to tree structure, suitable for tree selection, cascade selection and other components.",
|
||||
"resultTypeObjectDesc": "Return the first record as an object, suitable for detail display, form backfill and other scenarios.",
|
||||
"resultTypeValueDesc": "Return the first field value of the first record, suitable for statistics, titles and other scenarios.",
|
||||
"resultTypeChartAxisDesc": "Convert to xAxisData, seriesData format, suitable for line charts, bar charts, area charts.",
|
||||
"resultTypeChartPieDesc": "Convert to seriesData: [name, value] format, suitable for pie charts, funnel charts.",
|
||||
"resultTypeChartGaugeDesc": "Convert to value, name, max format, suitable for gauges, progress charts.",
|
||||
"resultTypeChartRadarDesc": "Convert to indicator, seriesData format, suitable for radar charts, multi-dimensional comparison.",
|
||||
"resultTypeChartScatterDesc": "Convert to seriesData: [x, y] format, suitable for scatter plots, bubble charts.",
|
||||
"resultTypeChartHeatmapDesc": "Convert to xAxisData, yAxisData, seriesData format, suitable for heatmaps.",
|
||||
"treeConversionConfig": "Tree Conversion Configuration",
|
||||
"refresh": "Refresh",
|
||||
"fieldPreview": "Field Preview",
|
||||
"clickTableToView": "Click table name to view",
|
||||
"selectTable": "Please select a table",
|
||||
"noFieldInfo": "No field information",
|
||||
"clickFieldToInsert": "Click field name to insert into editor",
|
||||
"testDataSource": "Test Data Source",
|
||||
"dataSourceInfo": "Data Source Information",
|
||||
"code": "Code",
|
||||
"type": "Type",
|
||||
"name": "Name",
|
||||
"inputCode": "Please enter code",
|
||||
"all": "All",
|
||||
"staticData": "Static Data",
|
||||
"staticLabel": "Static",
|
||||
"batchDelete": "Batch Delete",
|
||||
"more": "More",
|
||||
"copy": "Copy",
|
||||
"deleteConfirmMessage": "Are you sure you want to delete data source \"{name}\"?",
|
||||
"deleteConfirmTitle": "Delete Confirmation",
|
||||
"deleteSuccess": "Data source deleted: {name}",
|
||||
"batchDeleteConfirmMessage": "Are you sure you want to delete {count} selected data sources?",
|
||||
"batchDeleteConfirmTitle": "Batch Delete Confirmation",
|
||||
"batchDeleteSuccess": "Deleted {count} data sources",
|
||||
"inputNewCode": "Please enter new data source code",
|
||||
"copyDataSource": "Copy Data Source",
|
||||
"codeFormatError": "Code must start with a letter and contain only letters, numbers and underscores",
|
||||
"copySuccess": "Copied successfully",
|
||||
"importExport": {
|
||||
"export": "Export",
|
||||
"import": "Import Config",
|
||||
"exportSuccess": "Data source config exported",
|
||||
"exportFailed": "Export failed",
|
||||
"importTitle": "Import Data Source Config",
|
||||
"dragOrClick": "Drag a JSON file here or click to upload",
|
||||
"onlyJson": "Only .json files are supported",
|
||||
"fileParseError": "Failed to parse file. Please check the format",
|
||||
"checking": "Checking...",
|
||||
"codeConflictTip": "Data source code already exists. Please change the code before importing",
|
||||
"codeAvailable": "Data source code is available",
|
||||
"newCodePlaceholder": "Enter a new data source code",
|
||||
"importSuccess": "Data source config imported successfully",
|
||||
"importFailed": "Import failed",
|
||||
"confirmImport": "Confirm Import",
|
||||
"reselect": "Reselect",
|
||||
"dataSourceInfo": "Data Source Info",
|
||||
"appTip": "Imported data source will belong to the current application",
|
||||
"dbConnectionTip": "For SQL sources, db_connection is a connection name — ensure it exists in the target environment"
|
||||
},
|
||||
"saveSuccess": "Saved successfully",
|
||||
"createTime": "Creation Time",
|
||||
"apiInterface": "API Interface",
|
||||
"sqlQuery": "SQL Query",
|
||||
"resultTypeList": "List",
|
||||
"resultTypeTree": "Tree",
|
||||
"resultTypeSingleObject": "Single Object",
|
||||
"resultTypeSingleValue": "Single Value",
|
||||
"resultTypeAxisChart": "Axis Chart",
|
||||
"resultTypePieChart": "Pie Chart Data",
|
||||
"resultTypeGauge": "Gauge",
|
||||
"resultTypeRadarChart": "Radar Chart",
|
||||
"resultTypeScatterChart": "Scatter Chart",
|
||||
"resultTypeHeatmap": "Heatmap",
|
||||
"paramTypeString": "String",
|
||||
"paramTypeInteger": "Integer",
|
||||
"paramTypeFloat": "Float",
|
||||
"paramTypeBoolean": "Boolean",
|
||||
"paramTypeDate": "Date",
|
||||
"paramTypeDatetime": "Datetime",
|
||||
"axisChartConfig": "Axis Chart Configuration",
|
||||
"xAxisField": "X Axis Field",
|
||||
"seriesField": "Series Field",
|
||||
"seriesName": "Series Name",
|
||||
"pieChartConfig": "Pie Chart Configuration",
|
||||
"nameField": "Name Field",
|
||||
"valueField": "Value Field",
|
||||
"gaugeChartConfig": "Gauge Chart Configuration",
|
||||
"maxField": "Max Field",
|
||||
"radarChartConfig": "Radar Chart Configuration",
|
||||
"indicatorNameField": "Indicator Name Field",
|
||||
"scatterChartConfig": "Scatter Chart Configuration",
|
||||
"xCoordinateField": "X Coordinate Field",
|
||||
"yCoordinateField": "Y Coordinate Field",
|
||||
"sizeField": "Size Field",
|
||||
"heatmapChartConfig": "Heatmap Configuration",
|
||||
"fieldMapping": "Field Mapping",
|
||||
"addMapping": "Add Mapping",
|
||||
"originalField": "Original Field",
|
||||
"mappedField": "Mapped Field",
|
||||
"deleteMapping": "Delete",
|
||||
"multipleFieldsComma": "Multiple fields separated by commas, e.g.: {example}",
|
||||
"multipleNamesComma": "Multiple names separated by commas, e.g.: {example}",
|
||||
"dataFieldsForChart": "Data fields used to draw charts",
|
||||
"optionalLegendNames": "Optional, the name displayed in the legend, leave blank to use field name",
|
||||
"oneFieldPerSeries": "One field per series",
|
||||
"optionalBubbleChart": "e.g.: size (optional, for bubble chart)",
|
||||
"optionalName": "e.g.: name (optional)",
|
||||
"optionalMax": "e.g.: max (optional)",
|
||||
"fieldNameMapping": "Map original field names to new field names, e.g. id -> value",
|
||||
"cacheConfig": "Cache Configuration",
|
||||
"enableCache": "Enable Cache",
|
||||
"cacheTime": "Cache Time",
|
||||
"cacheTimeUnit": "seconds (0 means no cache)",
|
||||
"test": "Test",
|
||||
"testParams": "Test Parameters",
|
||||
"executeTest": "Execute Test",
|
||||
"testResult": "Test Result",
|
||||
"success": "Success",
|
||||
"failed": "Failed",
|
||||
"returnedData": "Returned {count} records",
|
||||
"reachedLimit": "Reached limit {limit} records",
|
||||
"aiAssistant": "AI Assistant",
|
||||
"aiSqlAssistant": "AI SQL Assistant",
|
||||
"aiSqlAssistantTip": "AI will automatically generate SQL queries based on your requirements and database structure",
|
||||
"sqlWritingTip": "SQL runs against the connection default database. Use schema.table for PostgreSQL/SQL Server/Oracle; `db`.`table` or table for MySQL; bind params as :name. For external connections, pick tables in the tree or AI dialog so db_connection matches the connection code.",
|
||||
"describeYourQuery": "Describe your query requirements",
|
||||
"queryPlaceholder": "e.g.: Query the number of new users and active users per day in the last 30 days",
|
||||
"quickExamples": "Quick Examples",
|
||||
"selectDataTable": "Select Data Tables",
|
||||
"modifySelection": "Modify Selection",
|
||||
"selectedTables": "Selected {count} tables",
|
||||
"clear": "Clear",
|
||||
"pleaseSelectTableFirst": "Please select data tables first",
|
||||
"tableRelationsCount": "Configured {count} table relations",
|
||||
"includeTableRelations": "Include table relations (improves multi-table JOIN query accuracy)",
|
||||
"aiModel": "AI Model",
|
||||
"selectModel": "Select Model",
|
||||
"generating": "Generating...",
|
||||
"generateSql": "Generate SQL",
|
||||
"generationThought": "Generation Thought",
|
||||
"generatedSql": "Generated SQL",
|
||||
"paramSuggestions": "Parameter Suggestions",
|
||||
"default": "Default",
|
||||
"insertToEditor": "Insert to Editor",
|
||||
"sqlGenerateSuccess": "SQL generated successfully",
|
||||
"sqlGenerateFailed": "SQL generation failed",
|
||||
"sqlInserted": "Inserted to editor",
|
||||
"copied": "Copied to clipboard",
|
||||
"pleaseInputQuestion": "Please input query requirements",
|
||||
"pleaseSelectModel": "Please select AI model",
|
||||
"pleaseSelectTable": "Please select data tables first",
|
||||
"aiSqlInsertSuccess": "SQL inserted, parameters auto-configured",
|
||||
"manualMode": "Manual",
|
||||
"aiMode": "AI",
|
||||
"aiConfig": "AI Config",
|
||||
"aiSqlPlaceholder": "AI generated SQL will be displayed here...",
|
||||
"querying": "Querying...",
|
||||
"dataView": "Data",
|
||||
"chartView": "Chart",
|
||||
"clickTestToExecute": "Click button above to execute test",
|
||||
"rawData": "Raw Data",
|
||||
"selectChartTypeToPreview": "Please select a chart type in Result Processing to preview",
|
||||
"apiConfig": "API Configuration",
|
||||
"urlPlaceholder": "https://api.example.com/data",
|
||||
"apiTabBasic": "Basic",
|
||||
"apiTabAuth": "Auth",
|
||||
"apiTabQueryParams": "Query Params",
|
||||
"apiTabBody": "Body",
|
||||
"apiTabAdvanced": "Advanced",
|
||||
"timeout": "Timeout",
|
||||
"second": "s",
|
||||
"dataPath": "Data Path",
|
||||
"dataPathPlaceholder": "e.g. data.list",
|
||||
"description": "Description",
|
||||
"authType": "Auth Type",
|
||||
"authNone": "No Auth",
|
||||
"bearerTokenPlaceholder": "Enter Token, supports {param} placeholder",
|
||||
"username": "Username",
|
||||
"usernamePlaceholder": "Enter username",
|
||||
"password": "Password",
|
||||
"passwordPlaceholder": "Enter password",
|
||||
"keyPosition": "Key Position",
|
||||
"keyName": "Key Name",
|
||||
"keyValue": "Key Value",
|
||||
"bodyType": "Type",
|
||||
"bodyTypeNone": "None",
|
||||
"contentType": "Content-Type",
|
||||
"retryCount": "Retry Count",
|
||||
"retryInterval": "Retry Interval",
|
||||
"proxy": "Proxy",
|
||||
"followRedirects": "Follow Redirects",
|
||||
"verifySSL": "Verify SSL",
|
||||
"successCondition": "Success Condition",
|
||||
"successStatusCodes": "Status Codes",
|
||||
"successStatusCodesPlaceholder": "e.g. 200, 201 (empty checks 2xx)",
|
||||
"successFieldPath": "Field Path",
|
||||
"successFieldPathPlaceholder": "e.g. code",
|
||||
"successFieldValue": "Expected Value",
|
||||
"apiTabHeaders": "Headers",
|
||||
"viewRequest": "View Request",
|
||||
"queryParamsHint": "Parameters will auto-sync to URL",
|
||||
"headersHint": "Custom HTTP request headers",
|
||||
"addHeader": "Add Header",
|
||||
"headerName": "Name",
|
||||
"headerValue": "Value",
|
||||
"noQueryParams": "No query parameters",
|
||||
"noHeaders": "No headers"
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"title": "Database Connections",
|
||||
"createConnection": "New Connection",
|
||||
"editConnection": "Edit Connection",
|
||||
"searchPlaceholder": "Search by name or code",
|
||||
"code": "Connection Code",
|
||||
"codePlaceholder": "e.g. erp_mysql",
|
||||
"codeTip": "Must start with a letter; letters, numbers, underscore, hyphen only; 'default' is reserved",
|
||||
"name": "Connection Name",
|
||||
"namePlaceholder": "Enter connection name",
|
||||
"dbType": "Database Type",
|
||||
"host": "Host",
|
||||
"hostPlaceholder": "e.g. 192.168.1.100",
|
||||
"port": "Port",
|
||||
"user": "Username",
|
||||
"userPlaceholder": "Database username",
|
||||
"password": "Password",
|
||||
"passwordPlaceholder": "Enter password",
|
||||
"passwordKeepHint": "Leave blank to keep existing password",
|
||||
"defaultDatabase": "Default Database",
|
||||
"defaultDatabasePlaceholder": "Default database after connect (Oracle: Service Name)",
|
||||
"dbTypePostgresql": "PostgreSQL",
|
||||
"dbTypeMysql": "MySQL",
|
||||
"dbTypeSqlserver": "SQL Server",
|
||||
"dbTypeOracle": "Oracle",
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Optional description",
|
||||
"status": "Enabled",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"systemConnection": "System",
|
||||
"systemConnectionTip": "System default connection comes from DATABASE_URL and cannot be edited or deleted",
|
||||
"testConnection": "Test Connection",
|
||||
"testSuccess": "Connection successful",
|
||||
"testFailed": "Connection failed",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"deleteConfirmTitle": "Delete Connection",
|
||||
"deleteConfirmMessage": "Delete connection \"{name}\"?",
|
||||
"deleteSuccess": "Connection \"{name}\" deleted",
|
||||
"createSuccess": "Connection created",
|
||||
"updateSuccess": "Connection updated",
|
||||
"empty": "No database connections yet. Click the button above to add one.",
|
||||
"codeExists": "Connection code already exists",
|
||||
"codeAvailable": "Connection code is available",
|
||||
"filterAll": "All",
|
||||
"noDescription": "No description",
|
||||
"requiredFields": "Please fill in connection code, name and host",
|
||||
"browseDatabase": "Browse Database",
|
||||
"backToConnectionList": "Back to Connections",
|
||||
"connectionNotFoundForBrowse": "Connection not found or disabled, cannot browse"
|
||||
}
|
||||
@@ -1,394 +0,0 @@
|
||||
{
|
||||
"connectionInfo": "Connection Information",
|
||||
"testConnection": "Test Connection",
|
||||
"connectionName": "Connection Name",
|
||||
"databaseType": "Database Type",
|
||||
"connectionIdentifier": "Connection Identifier",
|
||||
"connectionStatus": "Connection Status",
|
||||
"connectionSuccessful": "✓ Connection Successful",
|
||||
"connectionFailed": "✗ Connection Failed",
|
||||
"notTested": "Not Tested",
|
||||
"testResult": "Test Result:",
|
||||
"testConnectionSuccess": "Connection test successful",
|
||||
"testConnectionFailed": "Connection test failed",
|
||||
"connectionError": "Connection failed",
|
||||
"usageInstructions": "Usage Instructions",
|
||||
"expandConnection": "· Expand Connection - View all databases under this connection",
|
||||
"selectDatabase": "· Select Database - View database details",
|
||||
"selectTable": "· Select Table - View table structure, query data, execute SQL",
|
||||
"searchFunction": "· Search Function - Quickly find databases or tables in the left tree",
|
||||
"quickActions": "Quick Actions",
|
||||
"testDatabaseConnection": "Test Database Connection",
|
||||
"tip": "Tip: Click on the left tree node to expand and view more content",
|
||||
"constraintList": "Constraint List",
|
||||
"addConstraint": "Add Constraint",
|
||||
"constraintName": "Constraint Name",
|
||||
"constraintNameTip": "A unique name to identify and manage this constraint",
|
||||
"constraintType": "Constraint Type",
|
||||
"constraintTypeTip": "Primary Key: unique row identifier; Foreign Key: links to another table; Unique: no duplicate values; Check: custom validation rule",
|
||||
"field": "Local Field",
|
||||
"constraintFieldTip": "Select the column(s) in this table used by the constraint",
|
||||
"definition": "Check Rule",
|
||||
"constraintDefinitionTip": "Required for CHECK constraints only, e.g. age > 0",
|
||||
"referencedTable": "Related Table",
|
||||
"referencedTableTip": "Select the target table to link to, usually the parent table",
|
||||
"referencedTablePlaceholder": "Enter referenced table name",
|
||||
"selectReferencedTable": "Select related table",
|
||||
"onDelete": "On Delete",
|
||||
"onDeleteTip": "What happens in this table when a related row is deleted",
|
||||
"onUpdate": "On Update",
|
||||
"onUpdateTip": "What happens in this table when the related column is updated",
|
||||
"fkActionDefault": "Default",
|
||||
"fkActionDefaultLabel": "Default rule",
|
||||
"fkActionDefaultTip": "Use the database default behavior",
|
||||
"fkActionNoActionLabel": "No action",
|
||||
"fkActionNoActionTip": "Block delete/update when related data exists",
|
||||
"fkActionRestrictLabel": "Restrict",
|
||||
"fkActionRestrictTip": "Immediately reject delete/update when related data exists",
|
||||
"fkActionCascadeLabel": "Cascade",
|
||||
"fkActionCascadeTip": "Also delete or update related rows in this table",
|
||||
"fkActionSetNullLabel": "Set null",
|
||||
"fkActionSetNullTip": "Set the foreign key column in this table to NULL",
|
||||
"fkActionSetDefaultLabel": "Set default",
|
||||
"fkActionSetDefaultTip": "Set the foreign key column in this table to its default value",
|
||||
"referencedColumns": "Related Column",
|
||||
"referencedColumnsTip": "Select the matching column in the related table, usually primary key id",
|
||||
"referencedColumnsPlaceholder": "Select related columns",
|
||||
"constraintNameRequired": "Please enter constraint name",
|
||||
"constraintColumnsRequired": "Please select constraint columns",
|
||||
"constraintCheckDefinitionRequired": "Please enter CHECK constraint definition",
|
||||
"constraintReferencedTableRequired": "Please enter referenced table for foreign key",
|
||||
"constraintReferencedColumnsRequired": "Please enter referenced columns for foreign key",
|
||||
"constraintReferencedColumnsMismatch": "Foreign key column count must match referenced column count",
|
||||
"constraintValidationFailed": "Constraint validation failed",
|
||||
"selectField": "Select Field",
|
||||
"constraintDefinition": "Constraint Definition (e.g.: age > 0)",
|
||||
"constraintDeleted": "Constraint deleted",
|
||||
"noConstraints": "No constraints, click \"Add Constraint\" to start creating",
|
||||
"foreignKey": "FOREIGN KEY",
|
||||
"foreignKeyLabel": "Foreign Key",
|
||||
"check": "CHECK",
|
||||
"checkLabel": "Check Rule",
|
||||
"database": "Database",
|
||||
"schema": "Schema",
|
||||
"tableName": "Table Name",
|
||||
"enterTableName": "Please enter table name",
|
||||
"tableComment": "Table Comment",
|
||||
"enterTableComment": "Please enter table comment (optional)",
|
||||
"fieldManagement": "Field Management",
|
||||
"indexManagement": "Index Management",
|
||||
"constraintManagement": "Constraint Management",
|
||||
"previewSQL": "Preview SQL",
|
||||
"createTable": "Create Table",
|
||||
"createTableSQLPreview": "CREATE TABLE SQL Preview",
|
||||
"executeCreate": "Execute Create",
|
||||
"fillTableNameAndFields": "Please fill in the table name and add at least one field",
|
||||
"createTableSuccess": "Table created successfully",
|
||||
"createTableFailed": "Failed to create table",
|
||||
"primaryKeyUUID": "Primary Key ID (UUID)",
|
||||
"databaseInfo": "Database Information",
|
||||
"databaseName": "Database Name",
|
||||
"owner": "Owner",
|
||||
"encoding": "Encoding",
|
||||
"collation": "Collation",
|
||||
"tableCount": "Table Count",
|
||||
"databaseSize": "Database Size",
|
||||
"description": "Description",
|
||||
"noDatabaseInfo": "No database information found",
|
||||
"viewTableList": "View Table List",
|
||||
"expandLeftTreeNode": "Expand the left tree node to view all tables in this database",
|
||||
"searchTable": "Search Table",
|
||||
"useSearchBox": "Use the search box on the left to quickly find table names",
|
||||
"switchToSqlTab": "Select any table and switch to the \"SQL Execution\" tab",
|
||||
"statistics": "Statistics",
|
||||
"loadDatabaseInfoFailed": "Failed to load database information",
|
||||
"tables": "Tables",
|
||||
"loading": "Loading...",
|
||||
"loadDatabaseConfigsFailed": "Failed to load database configurations",
|
||||
"loadDatabaseListFailed": "Failed to load database list",
|
||||
"loadSchemaListFailed": "Failed to load schema list",
|
||||
"loadTableListFailed": "Failed to load table list",
|
||||
"loadViewListFailed": "Failed to load view list",
|
||||
"cannotFindNode": "Cannot find node",
|
||||
"refreshSuccess": "Refresh successful",
|
||||
"refreshFailed": "Refresh failed",
|
||||
"expandNodeToViewLatestData": "Refresh successful, expand the node to view the latest data",
|
||||
"copiedToClipboard": "Copied to clipboard",
|
||||
"enterSchemaName": "Please enter schema name",
|
||||
"createSchema": "Create Schema",
|
||||
"schemaNamePattern": "Schema name can only contain letters, numbers and underscores, and must start with a letter or underscore",
|
||||
"schemaCreatedSuccess": "Schema \"{schemaName}\" created successfully",
|
||||
"createSchemaFailed": "Failed to create schema",
|
||||
"refreshConnection": "Refresh Connection",
|
||||
"viewInfo": "View Information",
|
||||
"copyConnectionName": "Copy Connection Name",
|
||||
"refreshDatabase": "Refresh Database",
|
||||
"copyDatabaseName": "Copy Database Name",
|
||||
"refreshSchema": "Refresh Schema",
|
||||
"copySchemaName": "Copy Schema Name",
|
||||
"refreshTableList": "Refresh Table List",
|
||||
"createNewTable": "Create New Table",
|
||||
"viewStatistics": "View Statistics",
|
||||
"refreshViewList": "Refresh View List",
|
||||
"createNewView": "Create New View",
|
||||
"refreshAllMaterializedViews": "Refresh All Materialized Views",
|
||||
"viewTableStructure": "View Table Structure",
|
||||
"queryData": "Query Data",
|
||||
"executeSql": "Execute SQL",
|
||||
"copyTableName": "Copy Table Name",
|
||||
"refresh": "Refresh",
|
||||
"viewViewStructure": "View View Structure",
|
||||
"viewDefinitionSQL": "View Definition SQL",
|
||||
"refreshView": "Refresh View",
|
||||
"copyViewName": "Copy View Name",
|
||||
"confirmTruncateTable": "Are you sure you want to clear all data in table \"{tableName}\"? This operation cannot be undone!",
|
||||
"deleteTable": "Delete Table",
|
||||
"confirmDeleteTable": "Are you sure you want to delete table \"{tableName}\"? This operation cannot be undone!",
|
||||
"deleteTableSuccess": "Table deleted successfully",
|
||||
"deleteTableFailed": "Failed to delete table",
|
||||
"warning": "Warning",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"confirmRefreshMaterializedView": "Are you sure you want to refresh the materialized view \"{viewName}\"?",
|
||||
"onlyMaterializedViewNeedsRefresh": "Only materialized views need manual refresh",
|
||||
"confirmExportAllTables": "Are you sure you want to export all tables in the current Schema/Database?",
|
||||
"confirmExportAllViews": "Are you sure you want to export all views in the current Schema/Database?",
|
||||
"confirmRefreshAllMaterializedViews": "Are you sure you want to refresh all materialized views in the current Schema/Database? This may take some time.",
|
||||
"exportFeatureDevelopment": "Export feature is under development...",
|
||||
"importFeatureDevelopment": "Import feature is under development...",
|
||||
"editFeatureDevelopment": "Edit feature is under development...",
|
||||
"deleteFeatureDevelopment": "Delete feature is under development...",
|
||||
"truncateTableFeatureDevelopment": "Truncate table feature is under development...",
|
||||
"createViewFeatureDevelopment": "Create view feature is under development...",
|
||||
"exportAllTablesFeatureDevelopment": "Export all tables feature is under development...",
|
||||
"exportAllViewsFeatureDevelopment": "Export all views feature is under development...",
|
||||
"refreshAllMaterializedViewsFeatureDevelopment": "Refresh all materialized views feature is under development...",
|
||||
"selectItemFromLeft": "Please select an item from the left",
|
||||
"tableStructure": "Table Structure",
|
||||
"dataQuery": "Data Query",
|
||||
"sqlExecution": "SQL Execution",
|
||||
"objectEditor": "Object Editor",
|
||||
"viewStructure": "View Structure",
|
||||
"fieldList": "Field List",
|
||||
"fields": "Fields",
|
||||
"indexes": "Indexes",
|
||||
"constraints": "Constraints",
|
||||
"columns": "Columns",
|
||||
"addField": "Add Field",
|
||||
"serialNumber": "Serial Number",
|
||||
"fieldName": "Field Name",
|
||||
"dataType": "Data Type",
|
||||
"lengthPrecision": "Length/Precision",
|
||||
"decimalPlaces": "Decimal Places",
|
||||
"nullable": "Nullable",
|
||||
"defaultValue": "Default Value",
|
||||
"primaryKey": "Primary Key",
|
||||
"unique": "Unique",
|
||||
"comment": "Comment",
|
||||
"operation": "Operation",
|
||||
"fieldDeleted": "Field deleted",
|
||||
"noFields": "No fields, click \"Add Field\" to start creating",
|
||||
"type": "Type",
|
||||
"fieldNamePlaceholder": "e.g. product_name",
|
||||
"defaultValuePlaceholder": "Default value",
|
||||
"fieldCommentPlaceholder": "Field comment",
|
||||
"indexList": "Index List",
|
||||
"addIndex": "Add Index",
|
||||
"indexName": "Index Name",
|
||||
"indexType": "Index Type",
|
||||
"selectFields": "Select Fields",
|
||||
"indexDeleted": "Index deleted",
|
||||
"noIndexes": "No indexes, click \"Add Index\" to start creating",
|
||||
"indexNamePlaceholder": "Index name",
|
||||
"typePlaceholder": "Type",
|
||||
"schemaInfo": "Schema Information",
|
||||
"copyName": "Copy Name",
|
||||
"schemaName": "Schema Name",
|
||||
"objectStatistics": "Object Statistics",
|
||||
"databaseObjects": "Database Objects",
|
||||
"noTablesInSchema": "No tables in this Schema",
|
||||
"noViewsInSchema": "No views in this Schema",
|
||||
"materializedView": "Materialized View",
|
||||
"updatable": "Updatable",
|
||||
"readOnly": "Read-only",
|
||||
"loadSchemaInfoFailed": "Failed to load Schema information",
|
||||
"sqlEditor": "SQL Editor",
|
||||
"loadExample": "Load Example",
|
||||
"clearSQL": "Clear",
|
||||
"execute": "Execute",
|
||||
"executionResult": "Execution Result",
|
||||
"executionTime": "Execution Time",
|
||||
"affectedRows": "Affected Rows",
|
||||
"returned": "Returned",
|
||||
"records": "records",
|
||||
"querySuccessNoData": "Query successful, but no data returned",
|
||||
"enterSQL": "Enter SQL statement above and click \"Execute\" button",
|
||||
"sqlWarning": "Please be careful when executing UPDATE, DELETE and other data modification statements",
|
||||
"enterSQLStatement": "Enter SQL statement...",
|
||||
"sqlPlaceholder": "Enter SQL statement...\\n\\nExample:\\nSELECT * FROM users WHERE id > 100;\\nUPDATE users SET status = 'active' WHERE id = 1;\\nDELETE FROM users WHERE id = 999;",
|
||||
"invalidDatabaseConnection": "Invalid database connection",
|
||||
"executeSQLFailed": "SQL execution failed",
|
||||
"pleaseEnterSQL": "Please enter SQL statement",
|
||||
"whereCondition": "WHERE condition (e.g: id > 100 AND status = 'active')",
|
||||
"orderBy": "ORDER BY (e.g: id DESC)",
|
||||
"query": "Query",
|
||||
"noData": "No data",
|
||||
"totalRecords": "{count} records in total",
|
||||
"designTable": "Design Table - {tableName}",
|
||||
"designTableAction": "Design Table",
|
||||
"databaseLabel": "Database",
|
||||
"schemaLabel": "Schema",
|
||||
"tableNameLabel": "Table Name",
|
||||
"tableNamePlaceholder": "Table name",
|
||||
"commentLabel": "Comment",
|
||||
"tableCommentPlaceholder": "Table comment",
|
||||
"unsavedChanges": "Unsaved changes",
|
||||
"noChanges": "No changes",
|
||||
"reset": "Reset",
|
||||
"saveChanges": "Save Changes",
|
||||
"sqlPreview": "SQL Preview",
|
||||
"close": "Close",
|
||||
"confirmSave": "Are you sure you want to save these changes?",
|
||||
"confirmSaveTitle": "Confirm Save",
|
||||
"confirmClose": "There are unsaved changes, are you sure you want to close?",
|
||||
"confirmCloseTitle": "Tip",
|
||||
"noChangesDetected": "No changes detected",
|
||||
"noSQLGenerated": "No SQL statement generated",
|
||||
"saveSuccess": "Save successful",
|
||||
"saveFailed": "Save failed",
|
||||
"loadTableStructureFailed": "Failed to load table structure",
|
||||
"loadTableDataFailed": "Failed to load table data",
|
||||
"tableInfo": "Table Information",
|
||||
"tableType": "Type",
|
||||
"rowCount": "Row Count",
|
||||
"tableSize": "Size",
|
||||
"ddlStatement": "DDL Statement",
|
||||
"copy": "Copy",
|
||||
"basicInfo": "Basic Information",
|
||||
"confirmReset": "Are you sure you want to reset all changes?",
|
||||
"confirmResetTitle": "Tip",
|
||||
"resetSuccess": "Reset successful",
|
||||
"missingDatabaseConfig": "Missing database configuration information",
|
||||
"loadDDLFailed": "Failed to load DDL statement",
|
||||
"loadDDLFailedMsg": "-- Failed to load DDL, please check database connection",
|
||||
"viewInfo": "View Information",
|
||||
"viewName": "View Name",
|
||||
"viewType": "View Type",
|
||||
"materializedView": "Materialized View",
|
||||
"normalView": "Normal View",
|
||||
"isUpdatable": "Is Updatable",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"checkOption": "CHECK OPTION",
|
||||
"columnInfo": "Column Information",
|
||||
"columnName": "Column Name",
|
||||
"dataType": "Data Type",
|
||||
"nullable": "Nullable",
|
||||
"position": "Position",
|
||||
"dependentTables": "Dependent Tables",
|
||||
"noDependentTables": "No Dependent Tables",
|
||||
"viewDefinition": "View Definition",
|
||||
"noDefinition": "No Definition",
|
||||
"description": "Description",
|
||||
"viewExplanation": "A View is a virtual table based on one or more tables and does not store actual data.",
|
||||
"viewBenefit1": "Views can simplify complex queries and improve data security",
|
||||
"viewBenefit2": "Updatable views allow modifying underlying table data through the view",
|
||||
"viewBenefit3": "When tables that the view depends on are modified, the view structure is automatically updated",
|
||||
"viewBenefit4": "Deleting a view does not affect the data in the underlying tables",
|
||||
"searchPlaceholder": "Search databases, tables, views...",
|
||||
"loadViewStructureFailed": "Failed to load view structure",
|
||||
"noData": "No data",
|
||||
"noColumnInfo": "No column information",
|
||||
"dataTypes": {
|
||||
"varchar": "Text",
|
||||
"char": "Fixed Text",
|
||||
"text": "Long Text",
|
||||
"integer": "Integer",
|
||||
"int": "Integer",
|
||||
"bigint": "Big Integer",
|
||||
"smallint": "Small Integer",
|
||||
"numeric": "Exact Decimal",
|
||||
"decimal": "Exact Decimal",
|
||||
"doublePrecision": "Decimal",
|
||||
"double": "Decimal",
|
||||
"float": "Decimal",
|
||||
"json": "JSON",
|
||||
"date": "Date",
|
||||
"time": "Time",
|
||||
"datetime": "DateTime",
|
||||
"timestamp": "Timestamp",
|
||||
"datetime2": "Timestamp",
|
||||
"boolean": "Boolean",
|
||||
"bit": "Boolean",
|
||||
"nvarchar": "Unicode Text",
|
||||
"nvarcharMax": "JSON"
|
||||
},
|
||||
"fieldNameRequired": "Field name is required",
|
||||
"fieldNameInvalidFormat": "Field name must start with a letter or underscore and contain only letters, numbers, and underscores",
|
||||
"fieldNameDuplicate": "Duplicate field name \"{name}\"",
|
||||
"tablesFolder": "Tables",
|
||||
"viewsFolder": "Views",
|
||||
"refreshSuccess": "Refreshed successfully",
|
||||
"refreshFailed": "Refresh failed",
|
||||
"executeShortcutHint": "Ctrl/Cmd + Enter to execute",
|
||||
"createDatabase": "Create Database",
|
||||
"dropDatabase": "Drop Database",
|
||||
"dropSchema": "Drop Schema",
|
||||
"renameSchema": "Rename Schema",
|
||||
"renameDatabase": "Rename Database",
|
||||
"editTableStructure": "Edit Table Structure",
|
||||
"enterNameToConfirm": "This action cannot be undone. Type the object name below to confirm:",
|
||||
"nameMismatch": "The name you entered does not match",
|
||||
"systemConnectionForbidden": "Write operations are not allowed on the system connection",
|
||||
"systemDatabase": "System Database",
|
||||
"systemDatabaseForbidden": "This system database cannot be modified",
|
||||
"databaseCreated": "Database \"{name}\" created successfully",
|
||||
"databaseDropped": "Database \"{name}\" dropped successfully",
|
||||
"schemaDropped": "Schema \"{name}\" dropped successfully",
|
||||
"schemaRenamed": "Schema renamed to \"{name}\"",
|
||||
"createDatabaseFailed": "Failed to create database",
|
||||
"dropDatabaseFailed": "Failed to drop database",
|
||||
"dropSchemaFailed": "Failed to drop schema",
|
||||
"renameSchemaFailed": "Failed to rename schema",
|
||||
"renameDatabaseFailed": "Failed to rename database",
|
||||
"databaseRenamed": "Database renamed to \"{name}\"",
|
||||
"enterNewDatabaseName": "Enter new database name",
|
||||
"databaseNameRequired": "Please enter a database name",
|
||||
"databaseNamePattern": "Database name must start with a letter or underscore and contain only letters, numbers, and underscores",
|
||||
"enterDatabaseName": "Enter database name",
|
||||
"databaseName": "Database Name",
|
||||
"encoding": "Encoding",
|
||||
"charset": "Charset",
|
||||
"collation": "Collation",
|
||||
"enterNewSchemaName": "Enter new schema name",
|
||||
"objectTypeDatabase": "database",
|
||||
"objectTypeSchema": "schema",
|
||||
"objectTypeTable": "table",
|
||||
"dangerousActionDefaultWarning": "Are you sure you want to drop {objectType} \"{objectName}\"? This cannot be undone.",
|
||||
"typeObjectNamePlaceholder": "Type \"{objectName}\" to confirm",
|
||||
"objectOverview": "Overview",
|
||||
"objectName": "Object Name",
|
||||
"objectTypeConnection": "Connection",
|
||||
"objectTypeView": "View",
|
||||
"databaseCount": "Database Count",
|
||||
"databasesUnderConnection": "Databases under this connection",
|
||||
"connectionHint": "Hint",
|
||||
"connectionBrowseHint": "Expand the connection in the left tree to browse databases and objects",
|
||||
"schemaList": "Schema List",
|
||||
"mysqlSchemaHint": "MySQL does not use schema layers; the database is the top-level container. Expand the left tree to view tables and views.",
|
||||
"schemaDescriptionHint": "A schema is a logical container for database objects such as tables and views.",
|
||||
"searchObjectPlaceholder": "Search object name...",
|
||||
"loadObjectListFailed": "Failed to load object list",
|
||||
"size": "Size",
|
||||
"indexTypes": {
|
||||
"btree": "B-Tree",
|
||||
"hash": "Hash",
|
||||
"gin": "GIN",
|
||||
"gist": "GiST",
|
||||
"brin": "BRIN",
|
||||
"nonclustered": "Nonclustered",
|
||||
"clustered": "Clustered",
|
||||
"normal": "Normal",
|
||||
"bitmap": "Bitmap"
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
{
|
||||
"overview": "Overview",
|
||||
"connectionInfo": "Connection Information",
|
||||
"performanceStats": "Performance Statistics",
|
||||
"tableStats": "Table Statistics",
|
||||
"loadConfigFailed": "Failed to load database configuration",
|
||||
"loading": "Loading {name}...",
|
||||
"loadSuccess": "{name} loaded successfully",
|
||||
"loadFailed": "Failed to load {name}",
|
||||
"selectDatabase": "Please select a database",
|
||||
"selectConnection": "Please select a database connection",
|
||||
"systemConnection": "System",
|
||||
"autoRefreshing": "Auto-refreshing",
|
||||
"paused": "Paused",
|
||||
"connected": "Connected",
|
||||
"disconnected": "Disconnected",
|
||||
"featureDeveloping": "Feature under development...",
|
||||
"connectionUsageRate": "Connection Usage Rate",
|
||||
"databaseSize": "Database Size",
|
||||
"cacheHitRatio": "Cache Hit Ratio",
|
||||
"activeConnections": "Active Connections",
|
||||
"currentActiveConnections": "Current active connections",
|
||||
"basicInfo": "Basic Information",
|
||||
"databaseType": "Database Type",
|
||||
"hostAddress": "Host Address",
|
||||
"databaseName": "Database Name",
|
||||
"version": "Version",
|
||||
"uptime": "Uptime",
|
||||
"timezone": "Timezone",
|
||||
"charset": "Charset",
|
||||
"totalConnections": "Total Connections",
|
||||
"maxConnections": "Max Connections",
|
||||
"idleConnections": "Idle Connections",
|
||||
"storageInfo": "Storage Information",
|
||||
"databaseSizeGb": "Database Size (GB)",
|
||||
"databaseSizeMb": "Database Size (MB)",
|
||||
"databaseSizeBytes": "Database Size (Bytes)",
|
||||
"transactionsCommit": "Transactions Commit",
|
||||
"transactionsRollback": "Transactions Rollback",
|
||||
"tuplesReturned": "Tuples Returned",
|
||||
"totalQueries": "Total Queries",
|
||||
"slowQueries": "Slow Queries",
|
||||
"bytesReceived": "Bytes Received",
|
||||
"batchRequestsPerSec": "Batch Requests/sec",
|
||||
"pageLifeExpectancy": "Page Life Expectancy",
|
||||
"bufferCacheHitRatio": "Buffer Cache Hit Ratio",
|
||||
"realtimeActiveConnections": "Real-time Active Connections",
|
||||
"currentIdleConnections": "Current Idle Connections",
|
||||
"connectionPoolStatus": "Connection Pool Status",
|
||||
"congested": "Congested",
|
||||
"busy": "Busy",
|
||||
"normal": "Normal",
|
||||
"idle": "Idle",
|
||||
"connectionDistribution": "Connection Distribution",
|
||||
"connectionPoolCapacity": "Connection Pool Capacity",
|
||||
"usedMaxConnections": "Used / Max Connections",
|
||||
"connectionDetailInfo": "Connection Detail Information",
|
||||
"usedConnections": "Used Connections",
|
||||
"availableConnections": "Available Connections",
|
||||
"connectionExplanation": "Connection Explanation",
|
||||
"totalConnectionsDesc": "Total number of connections currently established by the database",
|
||||
"maxConnectionsDesc": "Maximum allowed number of connections configured for the database",
|
||||
"activeConnectionsDesc": "Number of connections currently executing queries or transactions",
|
||||
"idleConnectionsDesc": "Number of connections established but not currently in use",
|
||||
"usageRateDesc": "Percentage of current connections relative to the maximum number of connections",
|
||||
"connectionStatus": "Connection Status",
|
||||
"connectionStatusDesc": "Idle (<50%) / Normal (50-70%) / Busy (70-90%) / Congested (≥90%)",
|
||||
"corePerformanceMetrics": "Core Performance Metrics",
|
||||
"totalCommittedTransactions": "Total Committed Transactions",
|
||||
"totalRollbackTransactions": "Total Rollback Transactions",
|
||||
"totalQueriesCount": "Total Queries Count",
|
||||
"queriesNeedOptimization": "Queries Needing Optimization",
|
||||
"batchRequestsPerSecDesc": "Batch requests per second",
|
||||
"transactionStats": "Transaction Statistics",
|
||||
"commitRate": "Commit Rate",
|
||||
"tuplesFetched": "Tuples Fetched",
|
||||
"tuplesInserted": "Tuples Inserted",
|
||||
"tuplesUpdated": "Tuples Updated",
|
||||
"tuplesDeleted": "Tuples Deleted",
|
||||
"needOptimization": "Need Optimization",
|
||||
"queryStats": "Query Statistics",
|
||||
"batchStats": "Batch Statistics",
|
||||
"cacheStats": "Cache Statistics",
|
||||
"networkTraffic": "Network Traffic",
|
||||
"bytesSent": "Bytes Sent",
|
||||
"totalTraffic": "Total Traffic",
|
||||
"performanceMetricExplanation": "Performance Metric Explanation",
|
||||
"cacheHitRatioDesc": "The proportion of data read from the cache; higher is better",
|
||||
"transactionsCommitDesc": "Total number of successfully committed transactions",
|
||||
"transactionsRollbackDesc": "Total number of rollback transactions; too high may indicate issues",
|
||||
"tupleOperations": "Tuple Operations",
|
||||
"tupleOperationsDesc": "Statistics for insert, update, delete, and select operations on data rows",
|
||||
"totalQueriesDesc": "Total number of all queries executed by the database",
|
||||
"slowQueriesDesc": "Queries with execution time exceeding the threshold, needing optimization",
|
||||
"networkTrafficDesc": "Number of bytes received and sent by the database",
|
||||
"batchRequests": "Batch Requests",
|
||||
"batchRequestsDesc": "Number of batch requests processed per second",
|
||||
"pageLifeExpectancyDesc": "Average number of seconds a page stays in the buffer pool",
|
||||
"bufferCacheHitRatioDesc": "Proportion of pages read from the buffer cache",
|
||||
"statisticalOverview": "Statistical Overview",
|
||||
"totalTables": "Total Tables",
|
||||
"totalRows": "Total Rows",
|
||||
"totalSize": "Total Size",
|
||||
"searchTable": "Search Table",
|
||||
"searchTablePlaceholder": "Enter table name to search...",
|
||||
"top10LargestTables": "Top 10 Largest Tables",
|
||||
"noTableData": "No table data",
|
||||
"rank": "Rank",
|
||||
"schema": "Schema",
|
||||
"tableName": "Table Name",
|
||||
"rows": "Rows",
|
||||
"size": "Size",
|
||||
"dataSize": "Data Size",
|
||||
"indexSize": "Index Size",
|
||||
"inserts": "Inserts",
|
||||
"updates": "Updates",
|
||||
"deletes": "Deletes",
|
||||
"deadTuples": "Dead Tuples",
|
||||
"autoIncrement": "Auto Increment",
|
||||
"usedSize": "Used Size",
|
||||
"tableStatsExplanation": "Table Statistics Explanation",
|
||||
"tableSizeDesc": "Disk space occupied by the table, including data and indexes",
|
||||
"tableRowsDesc": "Total number of data rows in the table",
|
||||
"deadTuplesDesc": "Deleted rows not yet cleaned up, requiring VACUUM",
|
||||
"insertUpdateDelete": "Insert/Update/Delete",
|
||||
"tableOpsDesc": "Statistics for insert, update, and delete operations on the table",
|
||||
"tableDataSizeDesc": "Space occupied by table data",
|
||||
"tableIndexSizeDesc": "Space occupied by table indexes",
|
||||
"autoIncrementDesc": "Current value of the auto-increment primary key",
|
||||
"tableUsedSizeDesc": "Actual space used by the table",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"realtime": "Real-time",
|
||||
"noMatchingTables": "No matching tables found",
|
||||
"allTablesList": "All Tables List",
|
||||
"tableCount": "{count} tables",
|
||||
"tableSize": "Table Size"
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"title": "Demos",
|
||||
"elementPlus": "Element Plus",
|
||||
"form": "Form",
|
||||
"vben": {
|
||||
"title": "Project",
|
||||
"about": "About",
|
||||
"document": "Document",
|
||||
"antdv": "Ant Design Vue Version",
|
||||
"naive-ui": "Naive UI Version",
|
||||
"element-plus": "Element Plus Version"
|
||||
},
|
||||
"demo": {
|
||||
"name": "Demo",
|
||||
"list": "Demo List",
|
||||
"create": "Create Demo",
|
||||
"edit": "Edit Demo",
|
||||
"delete": "Delete Demo",
|
||||
"detail": "Demo Detail",
|
||||
"title": "Title",
|
||||
"content": "Content",
|
||||
"status": "Status",
|
||||
"priority": "Priority",
|
||||
"isActive": "Is Active",
|
||||
"createTime": "Create Time",
|
||||
"updateTime": "Update Time",
|
||||
"creator": "Creator",
|
||||
"dept": "Department",
|
||||
"statusDraft": "Draft",
|
||||
"statusPublished": "Published",
|
||||
"statusArchived": "Archived",
|
||||
"priorityLow": "Low",
|
||||
"priorityMedium": "Medium",
|
||||
"priorityHigh": "High",
|
||||
"titlePlaceholder": "Please enter title",
|
||||
"contentPlaceholder": "Please enter content",
|
||||
"searchPlaceholder": "Search by title",
|
||||
"createSuccess": "Created successfully",
|
||||
"updateSuccess": "Updated successfully",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"deleteConfirm": "Are you sure to delete this Demo?",
|
||||
"exportExcel": "Export Excel",
|
||||
"importExcel": "Import Excel",
|
||||
"downloadTemplate": "Download Template",
|
||||
"importSuccess": "Imported successfully",
|
||||
"selectFile": "Select File"
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "Department",
|
||||
"title": "Department Management",
|
||||
"deptName": "Department Name",
|
||||
"parentDept": "Parent Department",
|
||||
"deptCode": "Department Code",
|
||||
"deptCodeHelp": "Optional, unique code to identify the department",
|
||||
"deptCodeFormatError": "Department code can only contain letters, numbers, underscores and hyphens",
|
||||
"deptType": "Department Type",
|
||||
"deptTypeOptions": {
|
||||
"company": "Company",
|
||||
"department": "Department",
|
||||
"team": "Team",
|
||||
"other": "Other"
|
||||
},
|
||||
"lead": "Department Leader",
|
||||
"phone": "Department Phone",
|
||||
"phoneHelp": "Optional, department contact phone number",
|
||||
"phoneFormatError": "Invalid phone number format",
|
||||
"email": "Department Email",
|
||||
"emailHelp": "Optional, department contact email",
|
||||
"emailFormatError": "Please enter a valid email address",
|
||||
"status": "Status",
|
||||
"sort": "Sort",
|
||||
"description": "Department Description",
|
||||
"descriptionPlaceholder": "Please enter department description",
|
||||
"descriptionHelp": "Optional, detailed description of the department",
|
||||
"operation": "Operation",
|
||||
"addChildDept": "Add Sub-Department",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"updateSuccess": "Department updated successfully",
|
||||
"updateFailed": "Failed to update department data",
|
||||
"searchFailed": "Failed to search departments",
|
||||
"selectDeptFirst": "Please select a department first",
|
||||
"selectUsersFirst": "Please select users first",
|
||||
"addUsersSuccess": "Added successfully",
|
||||
"removeUsersConfirm": "Are you sure you want to delete {0} selected users?",
|
||||
"removeUsersSuccess": "Deleted successfully",
|
||||
"removeUsersFailed": "Delete failed"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "Dictionary",
|
||||
"title": "Dictionary Management",
|
||||
"dictName": "Dictionary Name",
|
||||
"dictCode": "Dictionary Code",
|
||||
"remark": "Remark",
|
||||
"remarkPlaceholder": "Please enter remark",
|
||||
"status": "Status",
|
||||
"operation": "Operation",
|
||||
"edit": "Edit",
|
||||
"codeFormatError": "Dictionary code can only contain letters, numbers and underscores",
|
||||
"selectDictFirst": "Please select dictionary first",
|
||||
"noData": "No data",
|
||||
"itemName": "Dictionary Item",
|
||||
"itemLabel": "Label",
|
||||
"itemValue": "Value",
|
||||
"itemIcon": "Icon",
|
||||
"sort": "Sort",
|
||||
"isGlobal": "Global Visible",
|
||||
"globalTag": "Global",
|
||||
"mainApp": "Main App"
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
{
|
||||
"title": "DingTalk Sync Configuration",
|
||||
"corpId": "Corp ID",
|
||||
"corpIdPlaceholder": "Enter AgentId",
|
||||
"appKey": "App Key",
|
||||
"appKeyPlaceholder": "Enter AppKey",
|
||||
"appSecret": "App Secret",
|
||||
"appSecretPlaceholder": "Enter AppSecret",
|
||||
"testConnection": "Test Connection",
|
||||
"testSuccess": "Connection Successful",
|
||||
"testFail": "Connection Failed",
|
||||
"testing": "Testing...",
|
||||
"syncScope": "Sync Scope",
|
||||
"syncScopePlaceholder": "Please select",
|
||||
"syncScopeTip": "Select an organization as the top-level for data synchronization. Once synced, this organization cannot be changed.",
|
||||
"syncScopeLocked": "Initial sync completed. Sync scope is now locked. Contact admin to change.",
|
||||
"syncStats": "Sync Statistics",
|
||||
"syncType": "Sync Type",
|
||||
"totalCount": "Total",
|
||||
"successCount": "Synced",
|
||||
"failCount": "Failed",
|
||||
"notSynced": "Not Synced",
|
||||
"syncTime": "Sync Time",
|
||||
"syncStatus": "Status",
|
||||
"statusRunning": "Syncing",
|
||||
"statusSuccess": "Success",
|
||||
"statusPartial": "Partial",
|
||||
"statusFailed": "Failed",
|
||||
"operation": "Operation",
|
||||
"sync": "Sync",
|
||||
"syncing": "Syncing...",
|
||||
"syncDept": "Organization",
|
||||
"syncUser": "User",
|
||||
"syncDeptSuccess": "Organization sync completed",
|
||||
"syncUserSuccess": "User sync completed",
|
||||
"syncFail": "Sync failed",
|
||||
"triggerEvents": "Trigger Events",
|
||||
"triggerEvent": "Trigger Event",
|
||||
"description": "Description",
|
||||
"enableSyncDept": "Enable Sync Organization",
|
||||
"enableSyncDeptDesc": "Trigger organization sync on add, delete, or modify organization info",
|
||||
"enableSyncUser": "Enable Sync User",
|
||||
"enableSyncUserDesc": "Trigger user sync on add, delete, or modify user info",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"saveFail": "Save failed",
|
||||
"corpName": "Corp Name",
|
||||
"loadingDeptTree": "Loading department tree...",
|
||||
"callbackConfig": "Event Callback Configuration",
|
||||
"callbackConfigTip": "After configuring the callback URL, DingTalk contacts changes will be pushed to this system in real-time for incremental sync.",
|
||||
"callbackUrl": "Callback URL",
|
||||
"callbackUrlPlaceholder": "Enter callback URL, e.g. https://example.com/api/core/dingtalk-sync/callback",
|
||||
"callbackToken": "Callback Token",
|
||||
"callbackTokenPlaceholder": "Enter callback token",
|
||||
"callbackAesKey": "Callback AES Key",
|
||||
"callbackAesKeyPlaceholder": "Enter callback AES Key (43 characters)",
|
||||
"callbackStatus": "Callback Status",
|
||||
"callbackRegistered": "Registered",
|
||||
"callbackNotRegistered": "Not Registered",
|
||||
"registerCallback": "Register Callback",
|
||||
"deleteCallback": "Delete Callback",
|
||||
"registerSuccess": "Callback registered successfully",
|
||||
"registerFail": "Failed to register callback",
|
||||
"deleteCallbackSuccess": "Callback deleted successfully",
|
||||
"deleteCallbackFail": "Failed to delete callback",
|
||||
"registering": "Registering...",
|
||||
"subscribedEvents": "Subscribed Events",
|
||||
"generateRandom": "Generate",
|
||||
"streamConfig": "Stream Mode (Real-time Sync)",
|
||||
"streamConfigTip": "Receive DingTalk contacts change events in real-time via WebSocket connection. No public callback URL required.",
|
||||
"streamStatus": "Connection Status",
|
||||
"streamConnected": "Connected",
|
||||
"streamDisconnected": "Disconnected",
|
||||
"streamTotalEvents": "Events Received",
|
||||
"streamLastEvent": "Last Event",
|
||||
"streamLastEventTime": "Last Event Time",
|
||||
"streamEventNone": "None",
|
||||
"streamEventLog": "Incremental Sync Log",
|
||||
"refresh": "Refresh",
|
||||
"eventType": "Event Type",
|
||||
"targetType": "Target Type",
|
||||
"targetName": "Target Name",
|
||||
"eventStatus": "Status",
|
||||
"eventTime": "Time",
|
||||
"eventCreate": "Create Org",
|
||||
"eventModify": "Modify Org",
|
||||
"eventRemove": "Remove Org",
|
||||
"eventAddUser": "Add User",
|
||||
"eventModifyUser": "Modify User",
|
||||
"eventLeaveUser": "User Left",
|
||||
"eventActiveUser": "User Activated",
|
||||
"guideTitle": "DingTalk Sync Setup Guide",
|
||||
"guideStep1Title": "Create a DingTalk Internal App",
|
||||
"guideStep1Desc": "Log in to DingTalk Open Platform (open.dingtalk.com), navigate to \"App Development\" -> \"Internal Development\", create an H5 mini-app, and obtain the AppKey and AppSecret.",
|
||||
"guideStep2Title": "Configure App Permissions",
|
||||
"guideStep2Desc": "In the app management page, go to \"Permission Management\" and enable the following permissions: \"Contact Department Info Read\", \"Member Info Read\", \"Contact Department Members Read\", \"Employee Phone Number Info\", etc.",
|
||||
"guideStep3Title": "Enter Credentials",
|
||||
"guideStep3Desc": "Fill in the CorpId, AppKey, and AppSecret into the corresponding fields on this page, then click \"Test Connection\" to verify the credentials.",
|
||||
"guideStep4Title": "Set Sync Scope & Run Full Sync",
|
||||
"guideStep4Desc": "After a successful connection, select the root department in \"Sync Scope\", then click \"Sync\" in the statistics table — sync organizations first, then users.",
|
||||
"guideStep5Title": "Enable Stream Mode on DingTalk Platform",
|
||||
"guideStep5Desc": "Log in to DingTalk Open Platform, go to app -> \"Events & Callbacks\", select \"Stream Mode\" for push method, and save. No callback URL, Token, or AES Key needed.",
|
||||
"guideStep6Title": "Enable Trigger Events",
|
||||
"guideStep6Desc": "In the \"Trigger Events\" section, check the event types to auto-sync (organizations, users) and save. DingTalk contacts changes will then be pushed to the system for incremental sync automatically."
|
||||
}
|
||||
@@ -1,527 +0,0 @@
|
||||
{
|
||||
"documentGenerator": {
|
||||
"title": "Document Generator",
|
||||
"templateManagement": "Template Management",
|
||||
"sealManagement": "Seal Management",
|
||||
"documentList": "Document List",
|
||||
|
||||
"addTemplate": "Add Template",
|
||||
"editTemplate": "Edit Template",
|
||||
"copyTemplate": "Copy Template",
|
||||
"previewTemplate": "Preview Template",
|
||||
|
||||
"templateName": "Template Name",
|
||||
"templateCode": "Template Code",
|
||||
"category": "Category",
|
||||
"formCode": "Form Code",
|
||||
"workflowCode": "Workflow Code",
|
||||
"bindingType": "Binding Type",
|
||||
"bindToWorkflow": "Bind to Workflow",
|
||||
"bindToForm": "Bind to Form",
|
||||
"workflowLinkedForm": "Workflow linked form",
|
||||
|
||||
"basicInfo": "Basic Info",
|
||||
"pageSettings": "Page Settings",
|
||||
"autoGenerate": "Auto Generate",
|
||||
"watermark": "Watermark",
|
||||
"templateDesign": "Template Design",
|
||||
|
||||
"pageSize": "Page Size",
|
||||
"pageSizeCustom": "Custom",
|
||||
"customPageWidth": "Page Width",
|
||||
"customPageHeight": "Page Height",
|
||||
"pageOrientation": "Orientation",
|
||||
"pageMargin": "Page Margin",
|
||||
"top": "Top",
|
||||
"right": "Right",
|
||||
"bottom": "Bottom",
|
||||
"left": "Left",
|
||||
"showPageNumber": "Show Page Number",
|
||||
"pageNumberSettings": "Page Number",
|
||||
"pageNumberPosition": "Position",
|
||||
"pageNumberPositionFooter": "Footer",
|
||||
"pageNumberPositionHeader": "Header",
|
||||
"pageNumberAlign": "Alignment",
|
||||
"pageNumberFormat": "Format",
|
||||
"pageNumberFormatChinese": "第 1 页 / 共 10 页",
|
||||
"pageNumberFormatFraction": "1 / 10",
|
||||
"pageNumberFormatEnglish": "Page 1 of 10",
|
||||
"pageNumberFontSize": "Font Size",
|
||||
"pageNumberColor": "Color",
|
||||
|
||||
"enableAutoGenerate": "Enable Auto Generate",
|
||||
"generateTrigger": "Trigger",
|
||||
|
||||
"enableWatermark": "Enable Watermark",
|
||||
"watermarkText": "Watermark Text",
|
||||
"watermarkOpacity": "Opacity",
|
||||
"watermarkAngle": "Angle",
|
||||
|
||||
"elementLibrary": "Element Library",
|
||||
"canvas": "Canvas",
|
||||
"properties": "Properties",
|
||||
"dragElementHere": "Click elements on the left to add to canvas",
|
||||
"selectElementToEdit": "Select an element to edit properties",
|
||||
|
||||
"basicProperties": "Basic Properties",
|
||||
"elementType": "Element Type",
|
||||
"content": "Content",
|
||||
"fieldName": "Field Name",
|
||||
"fieldLabel": "Label",
|
||||
"format": "Format",
|
||||
"dataSource": "Data Source",
|
||||
"columns": "Columns",
|
||||
"sealType": "Seal Type",
|
||||
|
||||
"position": "Position",
|
||||
"width": "Width",
|
||||
"height": "Height",
|
||||
|
||||
"style": "Style",
|
||||
"fontSize": "Font Size",
|
||||
"fontWeight": "Font Weight",
|
||||
"textAlign": "Text Align",
|
||||
"alignLeft": "Left",
|
||||
"alignCenter": "Center",
|
||||
"alignRight": "Right",
|
||||
"color": "Color",
|
||||
|
||||
"publish": "Publish",
|
||||
"unpublish": "Unpublish",
|
||||
"builtin": "Built-in",
|
||||
|
||||
"confirmDelete": "Are you sure to delete this template?",
|
||||
"confirmUnpublish": "Are you sure to unpublish this template?",
|
||||
"builtinCannotDelete": "Built-in templates cannot be deleted",
|
||||
"publishSuccess": "Published successfully",
|
||||
"unpublishSuccess": "Unpublished successfully",
|
||||
|
||||
"pleaseInputName": "Please input template name",
|
||||
"pleaseInputCode": "Please input template code",
|
||||
"codeFormatError": "Code must start with a letter and contain only letters, numbers and underscores",
|
||||
"pleaseInputNameAndCode": "Please input template name and code",
|
||||
"pleaseInputFormCode": "Please input form code",
|
||||
"pleaseInputWorkflowCode": "Please input workflow code",
|
||||
"pleaseSelectWorkflow": "Please select workflow (optional)",
|
||||
"pleaseSelectForm": "Please select form",
|
||||
"pleaseInputWatermarkText": "Please input watermark text",
|
||||
|
||||
"newCode": "New Code",
|
||||
"newName": "New Name",
|
||||
|
||||
"previewFailed": "Preview failed",
|
||||
|
||||
"generateDocument": "Generate Document",
|
||||
"regenerate": "Regenerate",
|
||||
"downloadDocument": "Download Document",
|
||||
"documentName": "Document Name",
|
||||
"documentNo": "Document No.",
|
||||
"generateTime": "Generate Time",
|
||||
"generator": "Generator",
|
||||
"downloadCount": "Download Count",
|
||||
|
||||
"sealName": "Seal Name",
|
||||
"sealImage": "Seal Image",
|
||||
"uploadSeal": "Upload Seal",
|
||||
"sealOwner": "Owner",
|
||||
"sealScope": "Scope",
|
||||
|
||||
"noDocuments": "No documents",
|
||||
"selectTemplate": "Select Template",
|
||||
"pleaseSelectTemplate": "Please select a template",
|
||||
"generate": "Generate",
|
||||
"generateSuccess": "Generated successfully",
|
||||
"generateFailed": "Generate failed",
|
||||
"regenerateSuccess": "Regenerated successfully",
|
||||
"regenerateFailed": "Regenerate failed",
|
||||
"pageCount": "Pages",
|
||||
"fileSize": "File Size",
|
||||
"generateType": "Type",
|
||||
"auto": "Auto",
|
||||
"manual": "Manual",
|
||||
|
||||
"elementText": "Text",
|
||||
"elementField": "Field",
|
||||
"elementTable": "Table",
|
||||
"elementImage": "Image",
|
||||
"selectImage": "Select Image",
|
||||
"elementSignature": "Signature",
|
||||
"elementSeal": "Seal",
|
||||
"elementQrcode": "QR Code",
|
||||
"elementDivider": "Divider",
|
||||
|
||||
"noDataSource": "(No data source)",
|
||||
"signatureDefault": "Signature",
|
||||
"fieldNamePlaceholder": "e.g. applicant_name",
|
||||
"fieldLabelPlaceholder": "e.g. Applicant:",
|
||||
"dataSourcePlaceholder": "e.g. expense_items",
|
||||
"columnFieldPlaceholder": "Field",
|
||||
"columnLabelPlaceholder": "Title",
|
||||
"columnWidthPlaceholder": "Width",
|
||||
"signatureFieldPlaceholder": "Signature field name",
|
||||
|
||||
"formatNone": "None",
|
||||
"formatDate": "Date",
|
||||
"formatDatetime": "Datetime",
|
||||
"formatMoney": "Money",
|
||||
"formatNumber": "Number",
|
||||
|
||||
"sealCompany": "Company Seal",
|
||||
"sealDepartment": "Department Seal",
|
||||
"sealPersonal": "Personal Seal",
|
||||
"sealFinance": "Finance Seal",
|
||||
"sealContract": "Contract Seal",
|
||||
|
||||
"selectedSeal": "Selected Seal",
|
||||
"selectSeal": "Select Seal",
|
||||
"noSealSelected": "No seal selected",
|
||||
"noSealImage": "No image",
|
||||
"noAvailableSeals": "No available seals",
|
||||
"clearSeal": "Clear Seal",
|
||||
"sealLabel": "Seal Label",
|
||||
"sealLabelPlaceholder": "Enter seal label (e.g., Seal Here)",
|
||||
"allTypes": "All Types",
|
||||
"noSealsOfType": "No seals of this type",
|
||||
|
||||
"pleaseInputText": "Please input text",
|
||||
"fieldPrefix": "Field: ",
|
||||
"column": "Column",
|
||||
"addColumn": "Add Column",
|
||||
|
||||
"fontNormal": "Normal",
|
||||
"fontBold": "Bold",
|
||||
"alignLeft": "Left",
|
||||
"alignCenter": "Center",
|
||||
"alignRight": "Right",
|
||||
|
||||
"notSet": "Not set",
|
||||
"notSetDataSource": "Data source not set",
|
||||
|
||||
"documents": "Documents",
|
||||
|
||||
"categoryLeave": "Leave",
|
||||
"categoryExpense": "Expense",
|
||||
"categoryPurchase": "Purchase",
|
||||
"categoryContract": "Contract",
|
||||
"categoryCertificate": "Certificate",
|
||||
"categoryOther": "Other",
|
||||
|
||||
"statusPublished": "Published",
|
||||
"statusDraft": "Draft",
|
||||
|
||||
"orientationPortrait": "Portrait",
|
||||
"orientationLandscape": "Landscape",
|
||||
|
||||
"triggerOnApprove": "On Approval",
|
||||
"triggerOnSubmit": "On Submit",
|
||||
"triggerOnComplete": "On Complete",
|
||||
|
||||
"deleteConfirmTitle": "Delete Confirmation",
|
||||
"deleteSuccess": "Template {name} deleted",
|
||||
"unpublishConfirmTitle": "Unpublish Confirmation",
|
||||
|
||||
"copyTitle": "Copy Template",
|
||||
"copyCodePlaceholder": "Enter the new template code",
|
||||
"copyCodeRule": "Code can only contain letters, numbers, underscores and hyphens",
|
||||
"copy": "Copy",
|
||||
"copySuccess": "Copied successfully",
|
||||
"importExport": {
|
||||
"export": "Export Config",
|
||||
"import": "Import Config",
|
||||
"exportSuccess": "Template config exported",
|
||||
"exportFailed": "Export failed",
|
||||
"importTitle": "Import Document Template",
|
||||
"dragOrClick": "Drag JSON file here or click to upload",
|
||||
"onlyJson": "Only .json files are supported",
|
||||
"fileParseError": "Failed to parse file. Please check the format",
|
||||
"checking": "Checking...",
|
||||
"codeConflictTip": "Template code already exists. Enter a new code to import",
|
||||
"codeAvailable": "Template code is available",
|
||||
"newCodePlaceholder": "Enter a new template code",
|
||||
"importSuccess": "Template imported successfully",
|
||||
"importFailed": "Import failed",
|
||||
"confirmImport": "Confirm Import",
|
||||
"reselect": "Reselect",
|
||||
"templateInfo": "Template Info",
|
||||
"appTip": "Imported template belongs to the current app (draft status)",
|
||||
"bindingTip": "Ensure linked form/workflow codes exist in the target environment"
|
||||
},
|
||||
|
||||
"design": "Design",
|
||||
"editInfo": "Edit Info",
|
||||
"createSuccess": "Created successfully",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"autoSave": {
|
||||
"saving": "Saving...",
|
||||
"saved": "Saved",
|
||||
"unsaved": "Unsaved"
|
||||
},
|
||||
|
||||
"layoutElements": "Layout",
|
||||
"headerElements": "Header Area",
|
||||
"infoElements": "Info Area",
|
||||
"tableElements": "Table Area",
|
||||
"contentElements": "Content",
|
||||
"approvalElements": "Approval Area",
|
||||
"otherElements": "Other Elements",
|
||||
"footerElements": "Footer Area",
|
||||
|
||||
"rowContainer": "Row Container",
|
||||
"columnCount": "Column Count",
|
||||
"columnWidths": "Column Widths",
|
||||
"columnGap": "Column Gap",
|
||||
"addRowColumn": "Add Column",
|
||||
"removeRowColumn": "Remove Column",
|
||||
"dropElementHere": "Drop element here",
|
||||
|
||||
"documentHeader": "Document Header",
|
||||
"documentTitle": "Document Title",
|
||||
"documentInfo": "Document Info",
|
||||
"infoRow": "Info Row",
|
||||
"infoTable": "Info Table",
|
||||
"smartTable": "Smart Table",
|
||||
"smartText": "Smart Text",
|
||||
"smartTextPlaceholder": "Type text here...",
|
||||
"smartTextItalic": "Italic",
|
||||
"smartTextUnderline": "Underline",
|
||||
"smartTextStrikethrough": "Strikethrough",
|
||||
"smartTextUnorderedList": "Unordered List",
|
||||
"smartTextOrderedList": "Ordered List",
|
||||
"smartTextLineHeight": "Line Height",
|
||||
"smartTextHeading": "Heading",
|
||||
"smartTextParagraph": "Paragraph",
|
||||
"smartTextH1": "Heading 1",
|
||||
"smartTextH2": "Heading 2",
|
||||
"smartTextH3": "Heading 3",
|
||||
"smartTextInsertTable": "Insert Table",
|
||||
"smartTextRemoveTable": "Remove Table",
|
||||
"smartTableRows": "Rows",
|
||||
"smartTableCols": "Columns",
|
||||
"smartTableAddRow": "Add Row",
|
||||
"smartTableAddCol": "Add Column",
|
||||
"smartTableDeleteRow": "Delete Row",
|
||||
"smartTableDeleteCol": "Delete Column",
|
||||
"smartTableMergeCells": "Merge Cells",
|
||||
"smartTableSplitCell": "Split Cell",
|
||||
"smartTableCellContent": "Cell Content",
|
||||
"smartTableSelectCells": "Please select cells first",
|
||||
"smartTableInsertText": "Enter text or insert variable",
|
||||
"smartTableInsertRowAbove": "Insert Row Above",
|
||||
"smartTableInsertRowBelow": "Insert Row Below",
|
||||
"smartTableInsertColLeft": "Insert Column Left",
|
||||
"smartTableInsertColRight": "Insert Column Right",
|
||||
"smartTableClearContent": "Clear Content",
|
||||
"smartTableBold": "Bold",
|
||||
"smartTableFontSize": "Font Size",
|
||||
"smartTableFontColor": "Font Color",
|
||||
"smartTableBgColor": "Background Color",
|
||||
"smartTableToggleBorder": "Border Settings",
|
||||
"borderAll": "All Borders",
|
||||
"borderOuter": "Outer Border",
|
||||
"borderInner": "Inner Border",
|
||||
"borderHorizontal": "Horizontal Border",
|
||||
"borderVertical": "Vertical Border",
|
||||
"borderColorLabel": "Border Color",
|
||||
"customColor": "Custom",
|
||||
"smartTableColumnWidths": "Column Widths",
|
||||
"labelField": "Label Field",
|
||||
"detailTable": "Detail Table",
|
||||
"amountField": "Amount Field",
|
||||
"paragraph": "Paragraph",
|
||||
"richText": "Rich Text",
|
||||
"approvalArea": "Approval Area",
|
||||
"barcode": "Barcode",
|
||||
"spacer": "Spacer",
|
||||
"documentFooter": "Document Footer",
|
||||
|
||||
"searchElements": "Search elements",
|
||||
"elementTitle": "Title",
|
||||
"elementParagraph": "Paragraph",
|
||||
|
||||
"documentTemplate": "Document Template",
|
||||
"elements": "elements",
|
||||
|
||||
"viewJSON": "View JSON",
|
||||
"jsonPreview": "JSON Preview",
|
||||
"copyCode": "Copy Code",
|
||||
"importConfig": "Import Config",
|
||||
"pasteJsonConfig": "Paste JSON configuration",
|
||||
"importSuccess": "Imported successfully",
|
||||
"exportSuccess": "Exported successfully",
|
||||
"configFormatError": "Configuration format error",
|
||||
"copiedToClipboard": "Copied to clipboard",
|
||||
"copyFailed": "Copy failed",
|
||||
"pleaseEnterConfig": "Please enter configuration",
|
||||
"pleaseAddElements": "Please add elements first",
|
||||
"previewNotImplemented": "Preview not implemented yet",
|
||||
"pdfPreview": "PDF Preview",
|
||||
"generatingPreview": "Generating preview...",
|
||||
"noPreviewContent": "No preview content",
|
||||
"saveBeforePreview": "Please save the template before preview",
|
||||
"viewHTML": "View HTML",
|
||||
"htmlPreview": "HTML Preview",
|
||||
"htmlPreviewFailed": "HTML preview failed",
|
||||
"clearCanvasConfirm": "Are you sure to clear the canvas? This action cannot be undone.",
|
||||
"cleared": "Cleared",
|
||||
"releaseToAdd": "Release to add element",
|
||||
|
||||
"elementProperties": "Element Properties",
|
||||
"templateSettings": "Template Settings",
|
||||
|
||||
"signatureZone": "Signature Zone",
|
||||
"sealZone": "Seal Zone",
|
||||
"qrcodeContent": "QR Code Content",
|
||||
"qrcodeContentPlaceholder": "Enter QR code content or field name",
|
||||
"qrcodeType": "QR Code Type",
|
||||
"qrcodeTypeText": "Text",
|
||||
"qrcodeTypeUrl": "URL Link",
|
||||
"qrcodeTypePhone": "Phone Number",
|
||||
"qrcodeTypeEmail": "Email",
|
||||
"signatureFieldName": "Signature field name",
|
||||
"dividerLine": "Divider Line",
|
||||
"sourceCode": "Source",
|
||||
"pagePreview": "Preview",
|
||||
"fontFamily": "Font Family",
|
||||
"defaultFont": "Default Font",
|
||||
"lineHeight": "Line Height",
|
||||
"fontWeightNormal": "Normal",
|
||||
"fontWeightBold": "Bold",
|
||||
"alignJustify": "Justify",
|
||||
"titlePlaceholder": "Enter title",
|
||||
"contentPlaceholder": "Enter content, use {{variable}} to insert variables",
|
||||
"imageUrl": "Image URL",
|
||||
"imageUrlPlaceholder": "Enter image URL or field name",
|
||||
"imageLabelPlaceholder": "e.g. Signature, Approver",
|
||||
"labelPosition": "Label Position",
|
||||
"labelPositionTop": "Top",
|
||||
"labelPositionBottom": "Bottom",
|
||||
"labelPositionLeft": "Left",
|
||||
"labelPositionRight": "Right",
|
||||
"labelFontSize": "Label Font Size",
|
||||
"showUnderline": "Show Underline",
|
||||
"lineStyle": "Line Style",
|
||||
"lineSolid": "Solid",
|
||||
"lineDashed": "Dashed",
|
||||
"lineDotted": "Dotted",
|
||||
"lineWidth": "Line Width",
|
||||
"lineColor": "Line Color",
|
||||
"showPrintDate": "Show Print Date",
|
||||
"footerContentPlaceholder": "Enter footer content",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"fieldsConfig": "Fields Configuration",
|
||||
"rowsConfig": "Rows Configuration",
|
||||
"field": "Field",
|
||||
"row": "Row",
|
||||
"addField": "Add Field",
|
||||
"addRow": "Add Row",
|
||||
"newField": "New Field",
|
||||
"label": "Label",
|
||||
"value": "Value",
|
||||
"labelWidth": "Label Width",
|
||||
"labelBgColor": "Label Background",
|
||||
"labelColor": "Label Font Color",
|
||||
"fieldFontSize": "Field Font Size",
|
||||
"fieldColor": "Field Font Color",
|
||||
"valueFontSize": "Value Font Size",
|
||||
"valueColor": "Value Font Color",
|
||||
"fontSettings": "Font Settings",
|
||||
"fontColor": "Font Color",
|
||||
"labelFontWeight": "Label Bold",
|
||||
"headerBgColor": "Header Background",
|
||||
"headerFontSize": "Header Font Size",
|
||||
"headerColor": "Header Font Color",
|
||||
"headerFontWeight": "Header Bold",
|
||||
"labelSettings": "Label Settings",
|
||||
"contentFontSize": "Content Font Size",
|
||||
"contentColor": "Content Font Color",
|
||||
"columnWidth": "Width",
|
||||
"showIndex": "Show Index",
|
||||
"indexWidth": "Index Width",
|
||||
"noBackground": "No Background",
|
||||
"borderStyle": "Border Style",
|
||||
"borderNone": "No Border",
|
||||
"insertVariable": "Insert Variable",
|
||||
"searchVariable": "Search variables...",
|
||||
"noVariables": "No variables",
|
||||
"otherVariables": "Other",
|
||||
"varApplicant": "Applicant",
|
||||
"varDepartment": "Department",
|
||||
"varPosition": "Position",
|
||||
"varApplyDate": "Apply Date",
|
||||
"varDocNo": "Document No",
|
||||
"varCreateDate": "Create Date",
|
||||
"varPhone": "Phone",
|
||||
"varEmail": "Email",
|
||||
"varTotalAmount": "Total Amount",
|
||||
"varRemark": "Remark",
|
||||
|
||||
"placeholder": {
|
||||
"name": "Search template name"
|
||||
},
|
||||
"groupFormFields": "Form Fields",
|
||||
"groupCalculationFields": "Calculation Fields",
|
||||
"groupSubTablePrefix": "Sub Table: ",
|
||||
"subTableDataSourceSuffix": " (Data Source)",
|
||||
"subTableFirstRowSuffix": " (First Row)",
|
||||
"nameSuffix": " (Name)",
|
||||
"companyName": "Company Name",
|
||||
"headerType": "Header Type",
|
||||
"noImage": "No Image",
|
||||
"positionMode": "Position Mode",
|
||||
"positionInline": "Inline",
|
||||
"positionFloat": "Float"
|
||||
},
|
||||
"calculation": {
|
||||
"title": "Calculation Config",
|
||||
"enableCalculation": "Enable Calculation",
|
||||
"enableCalculationHint": "Enable to define calculation and aggregation fields in the calculation config step",
|
||||
"calculationFields": "Calculation Fields",
|
||||
"aggregationFields": "Aggregation Fields",
|
||||
"calculationField": "Calculation Field",
|
||||
"aggregationField": "Aggregation Field",
|
||||
"noCalculationFields": "No calculation fields, click add button to create",
|
||||
"noAggregationFields": "No aggregation fields, click add button to create",
|
||||
"fieldName": "Field Name",
|
||||
"fieldLabel": "Field Label",
|
||||
"fieldNamePlaceholder": "e.g. total_amount",
|
||||
"fieldLabelPlaceholder": "e.g. Total Amount",
|
||||
"formula": "Formula",
|
||||
"formulaPlaceholder": "e.g. quantity * unit_price * (1 - discount_rate)",
|
||||
"formulaHint": "Operators: +, -, *, /, %, ** | Functions: round, abs, numberToChinese | Can reference other fields and aggregation results",
|
||||
"format": "Format",
|
||||
"formatNumber": "Number",
|
||||
"formatMoney": "Money",
|
||||
"formatPercent": "Percent",
|
||||
"formatChinese": "Chinese Amount",
|
||||
"decimalPlaces": "Decimal Places",
|
||||
"dataSource": "Data Source",
|
||||
"selectSubTable": "Select sub table",
|
||||
"aggregateField": "Aggregate Field",
|
||||
"selectField": "Select field",
|
||||
"aggregateFunction": "Aggregate Function",
|
||||
"funcSum": "Sum",
|
||||
"funcAvg": "Average",
|
||||
"funcMax": "Maximum",
|
||||
"funcMin": "Minimum",
|
||||
"funcCount": "Count",
|
||||
"usageTitle": "Usage Guide",
|
||||
"usageHint1": "Aggregation fields are used to summarize sub-table data (e.g. total amount of order items)",
|
||||
"usageHint2": "Calculation fields are used for formula calculations based on form fields or aggregation results",
|
||||
"usageHint3": "Calculation results can be referenced in document templates by variable name (wrap with double curly braces)",
|
||||
"usageHint4": "Chinese format will automatically convert numbers to Chinese amount format",
|
||||
"positionMode": "Position Mode",
|
||||
"positionInline": "Inline",
|
||||
"positionFloat": "Float",
|
||||
"floatX": "X",
|
||||
"floatY": "Y",
|
||||
"floatZIndex": "Z-Index",
|
||||
"editorModeComponent": "Component",
|
||||
"editorModeWysiwyg": "Document",
|
||||
"wysiwygInsertImage": "Insert Image",
|
||||
"wysiwygInsertSeal": "Insert Seal",
|
||||
"wysiwygPageCount": "{count} pages",
|
||||
"hideAttributePanel": "Hide Properties",
|
||||
"showAttributePanel": "Show Properties"
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"title": "Electronic Seal Management",
|
||||
"create": "Create Seal",
|
||||
"createSeal": "Create Seal",
|
||||
"editSeal": "Edit Seal",
|
||||
"name": "Seal Name",
|
||||
"sealTypeLabel": "Seal Type",
|
||||
"ownerTypeLabel": "Owner Type",
|
||||
"scopeLabel": "Scope",
|
||||
"sealImage": "Seal Image",
|
||||
"description": "Description",
|
||||
"width": "Width",
|
||||
"height": "Height",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"sealType": {
|
||||
"company": "Company Seal",
|
||||
"department": "Department Seal",
|
||||
"personal": "Personal Seal",
|
||||
"contract": "Contract Seal",
|
||||
"finance": "Finance Seal"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"ownerType": {
|
||||
"all": "Everyone",
|
||||
"dept": "Specific Department",
|
||||
"role": "Specific Role",
|
||||
"user": "Specific User"
|
||||
},
|
||||
"scope": {
|
||||
"all": "All Templates",
|
||||
"specific": "Specific Templates"
|
||||
},
|
||||
"placeholder": {
|
||||
"name": "Please enter seal name",
|
||||
"sealType": "Please select seal type",
|
||||
"ownerType": "Please select owner type",
|
||||
"scope": "Please select scope",
|
||||
"sealImage": "Please upload seal image",
|
||||
"description": "Please enter description",
|
||||
"selectDept": "Please select department",
|
||||
"selectRole": "Please select role",
|
||||
"selectUser": "Please select user",
|
||||
"selectTemplate": "Please select template"
|
||||
},
|
||||
"deleteConfirm": "Are you sure you want to delete seal \"{name}\"?",
|
||||
"deleteConfirmTitle": "Delete Confirmation",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"disableConfirm": "Are you sure you want to disable this seal? It will not be available after disabled.",
|
||||
"disableConfirmTitle": "Disable Confirmation",
|
||||
"disableSuccess": "Disabled successfully",
|
||||
"enableSuccess": "Enabled successfully",
|
||||
"selectDept": "Select Department",
|
||||
"selectRole": "Select Role",
|
||||
"selectUser": "Select User",
|
||||
"selectTemplate": "Select Template"
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"title": "Feishu Sync Configuration",
|
||||
"appId": "App ID",
|
||||
"appIdPlaceholder": "Enter Feishu App ID",
|
||||
"appSecret": "App Secret",
|
||||
"appSecretPlaceholder": "Enter Feishu App Secret",
|
||||
"testConnection": "Test Connection",
|
||||
"testSuccess": "Connection Successful",
|
||||
"testFail": "Connection Failed",
|
||||
"testing": "Testing...",
|
||||
"syncScope": "Sync Scope",
|
||||
"syncScopePlaceholder": "Please select",
|
||||
"syncScopeTip": "Select an organization as the top-level for data synchronization. Once synced, this organization cannot be changed.",
|
||||
"syncScopeLocked": "Initial sync completed. Sync scope is now locked. Contact admin to change.",
|
||||
"syncStats": "Sync Statistics",
|
||||
"syncType": "Sync Type",
|
||||
"totalCount": "Total",
|
||||
"successCount": "Synced",
|
||||
"failCount": "Failed",
|
||||
"notSynced": "Not Synced",
|
||||
"syncTime": "Sync Time",
|
||||
"operation": "Operation",
|
||||
"sync": "Sync",
|
||||
"syncing": "Syncing...",
|
||||
"syncDept": "Organization",
|
||||
"syncUser": "User",
|
||||
"syncDeptSuccess": "Organization sync completed",
|
||||
"syncUserSuccess": "User sync completed",
|
||||
"syncFail": "Sync failed",
|
||||
"triggerEvents": "Trigger Events",
|
||||
"triggerEvent": "Trigger Event",
|
||||
"description": "Description",
|
||||
"enableSyncDept": "Enable Sync Organization",
|
||||
"enableSyncDeptDesc": "Trigger organization sync on add, delete, or modify organization info",
|
||||
"enableSyncUser": "Enable Sync User",
|
||||
"enableSyncUserDesc": "Trigger user sync on add, delete, or modify user info",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"saveFail": "Save failed",
|
||||
"callbackConfig": "Event Callback Configuration",
|
||||
"callbackConfigTip": "After configuring the callback URL, Feishu contacts changes will be pushed to this system in real-time for incremental sync. Feishu callbacks must be manually configured in the Feishu Open Platform console.",
|
||||
"callbackUrl": "Request URL",
|
||||
"callbackUrlPlaceholder": "Enter request URL, e.g. https://example.com/api/core/feishu-sync/callback",
|
||||
"encryptKey": "Encrypt Key",
|
||||
"encryptKeyPlaceholder": "Enter Encrypt Key",
|
||||
"verificationToken": "Verification Token",
|
||||
"verificationTokenPlaceholder": "Enter Verification Token",
|
||||
"callbackStatus": "Callback Status",
|
||||
"callbackConfigured": "Configured",
|
||||
"callbackNotConfigured": "Not Configured",
|
||||
"subscribedEvents": "Subscribed Events",
|
||||
"generateRandom": "Generate",
|
||||
"guideTitle": "Feishu Sync Setup Guide",
|
||||
"guideStep1Title": "Create a Feishu Custom App",
|
||||
"guideStep1Desc": "Log in to Feishu Open Platform (open.feishu.cn), go to \"Developer Console\", create a custom app, and obtain the App ID and App Secret.",
|
||||
"guideStep2Title": "Configure App Permissions",
|
||||
"guideStep2Desc": "In the app management page, go to \"Permission Management\" and enable contact-related permissions: \"Get Department Basic Info\", \"Get Department Organization Info\", \"Get User Basic Info\", \"Get User Phone Number\", etc.",
|
||||
"guideStep3Title": "Enter Credentials & Run Full Sync",
|
||||
"guideStep3Desc": "Fill in App ID and App Secret, click \"Test Connection\" to verify. After success, select the sync scope and sync organizations first, then users.",
|
||||
"guideStep4Title": "Configure Event Subscription (Real-time Sync)",
|
||||
"guideStep4Desc": "In the Feishu Open Platform app's \"Event Subscription\" page, set the request URL (format: https://your-domain/api/core/feishu-sync/callback), enter the Encrypt Key and Verification Token generated on this page into Feishu console, and subscribe to contact events.",
|
||||
"guideStep5Title": "Enable Trigger Events",
|
||||
"guideStep5Desc": "In the \"Trigger Events\" section, check the event types to auto-sync (organizations, users) and save. Feishu contacts changes will then be pushed to the system for incremental sync automatically."
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"myFiles": "My Files",
|
||||
"fileManagement": "File Management",
|
||||
"folderName": "Folder Name",
|
||||
"newFolder": "New Folder",
|
||||
"rename": "Rename",
|
||||
"name": "Name",
|
||||
"size": "Size",
|
||||
"modifiedTime": "Modified Time",
|
||||
"actions": "Actions",
|
||||
"search": "Search...",
|
||||
"listView": "List View",
|
||||
"gridView": "Grid View",
|
||||
"batchDelete": "Batch Delete",
|
||||
"upload": "Upload",
|
||||
"uploadFile": "Upload File",
|
||||
"uploadFolder": "Upload Folder",
|
||||
"open": "Open",
|
||||
"download": "Download",
|
||||
"delete": "Delete",
|
||||
"selectAll": "Select All",
|
||||
"noFiles": "No Files",
|
||||
"pleaseEnterName": "Please enter name",
|
||||
"pleaseEnterFolderName": "Please enter folder name",
|
||||
"renameSuccess": "Rename successful",
|
||||
"createSuccess": "Create successful",
|
||||
"deleteSuccess": "Delete successful",
|
||||
"deleteConfirm": "Are you sure you want to delete {name}?",
|
||||
"batchDeleteConfirm": "Are you sure you want to delete the selected {count} items?",
|
||||
"uploadSuccess": "Successfully uploaded {count} files",
|
||||
"uploadFailed": "{count} files failed to upload",
|
||||
"uploadError": "Upload error",
|
||||
"folderDownloadNotSupported": "Folder download is not supported currently",
|
||||
"downloadFailed": "Download failed",
|
||||
"previewFailed": "Preview failed",
|
||||
"previewNotSupported": "This file type is not supported for preview",
|
||||
"previewRenderError": "File rendering failed",
|
||||
"filePreview": "File Preview",
|
||||
"tip": "Tip",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm"
|
||||
}
|
||||
@@ -1,803 +0,0 @@
|
||||
{
|
||||
"form": "Form",
|
||||
"name": "Form Name",
|
||||
"code": "Form Code",
|
||||
"application": "Application",
|
||||
"type": "Form Type",
|
||||
"status": "Status",
|
||||
"description": "Description",
|
||||
"createTime": "Create Time",
|
||||
"updateTime": "Update Time",
|
||||
"actions": "Actions",
|
||||
"showInMobile": "Show in Mobile",
|
||||
"showInMobileTip": "When enabled, this form will be displayed in the mobile workbench",
|
||||
"icon": "Form Icon",
|
||||
"iconBgColor": "Icon Background Color",
|
||||
"iconPreview": "Preview",
|
||||
"iconBgColorPlaceholder": "Enter custom color or gradient",
|
||||
"iconPlaceholder": "Select form icon",
|
||||
"placeholder": {
|
||||
"name": "Please enter form name",
|
||||
"code": "Please enter form code",
|
||||
"type": "Please select form type",
|
||||
"status": "Please select status"
|
||||
},
|
||||
"typeMap": {
|
||||
"all": "All",
|
||||
"normal": "Normal Form",
|
||||
"workflow": "Workflow Form"
|
||||
},
|
||||
"statusMap": {
|
||||
"all": "All",
|
||||
"published": "Published",
|
||||
"draft": "Draft"
|
||||
},
|
||||
"create": "New Form",
|
||||
"batchDelete": "Batch Delete",
|
||||
"batchDeleteWithCount": "Batch Delete ({count})",
|
||||
"deleteConfirm": "Are you sure you want to delete form \"{name}\"?",
|
||||
"deleteConfirmTitle": "Delete Confirmation",
|
||||
"deleteConfirmMessage": "The following actions will be performed when deleting the form:<br/><br/>1. Delete the form menu<br/>2. Delete the form API permissions<br/>3. Delete the form field permissions<br/>4. Delete the form data permissions<br/>5. Permanently delete form metadata and design config<br/><br/>Are you sure you want to delete form \"{name}\"?",
|
||||
"deleteSuccess": "Form deleted: {name}",
|
||||
"batchDeleteConfirm": "Are you sure you want to delete {count} selected forms?",
|
||||
"batchDeleteConfirmTitle": "Batch Delete Confirmation",
|
||||
"batchDeleteSuccess": "{count} forms deleted",
|
||||
"unpublishSuccess": "Form \"{name}\" unpublished",
|
||||
"unpublishFailed": "Unpublish failed",
|
||||
"unpublishConfirmTitle": "Unpublish Confirmation",
|
||||
"unpublishConfirmMessage": "The following actions will be performed after unpublishing:<br/><br/>1. Delete the form menu<br/>2. Delete the form API permissions<br/>3. Delete the form field permissions<br/>4. Delete the form data permissions<br/><br/>Are you sure you want to unpublish?",
|
||||
"copyCodePlaceholder": "Please enter new form code",
|
||||
"copyTitle": "Copy Form",
|
||||
"copyCodeRule": "Code can only contain letters, numbers, underscores, and dashes",
|
||||
"codeFormatError": "Code must start with a letter and contain only letters, numbers and underscores",
|
||||
"copySuccess": "Copied successfully",
|
||||
"preview": "Preview",
|
||||
"publish": "Publish",
|
||||
"unpublish": "Unpublish",
|
||||
"copy": "Copy",
|
||||
"visit": "Visit Form",
|
||||
"setAsHome": "Set as Home",
|
||||
"setAsHomeTitle": "Set as Default Home",
|
||||
"setAsHomeConfirm": "Are you sure you want to set form \"{name}\" as the default home page?",
|
||||
"setAsHomePath": "Home Path",
|
||||
"setAsHomeTip": "After setting, users will be redirected to this form page by default after login.",
|
||||
"setAsHomeAppTip": "This form belongs to app \"{app}\". The home page config of that app will be updated.",
|
||||
"setHomeSuccess": "Set as default home page successfully",
|
||||
"setHomeFailed": "Failed to set as home page",
|
||||
"more": "More",
|
||||
"design": "Design",
|
||||
"editInfo": "Edit Info",
|
||||
"saveSuccess": "Form saved successfully",
|
||||
"previewDialog": {
|
||||
"title": "Form Preview",
|
||||
"loadFailed": "Failed to load form configuration",
|
||||
"verifySuccess": "Validation passed",
|
||||
"realtimeData": "Realtime Data (v-model):",
|
||||
"noConfig": "No form configuration",
|
||||
"close": "Close",
|
||||
"verifySubmit": "Verify Submit",
|
||||
"verifyFailed": "Form validation failed"
|
||||
},
|
||||
"editor": {
|
||||
"title": "Online Development",
|
||||
"create": "Create Form",
|
||||
"edit": "Edit Form",
|
||||
"steps": {
|
||||
"basic": "Basic Info",
|
||||
"database": "Database Design",
|
||||
"form": "Form Design",
|
||||
"list": "List Design",
|
||||
"publish": "Publish Form"
|
||||
},
|
||||
"placeholder": {
|
||||
"remark": "Please enter form description"
|
||||
},
|
||||
"validate": {
|
||||
"title": "Form validation failed",
|
||||
"warningTitle": "Form validation warnings",
|
||||
"repairTip": "Please fix the following issues before continuing",
|
||||
"warningTip": "The following issues won't block saving, but please review and confirm",
|
||||
"continueAnyway": "Continue anyway",
|
||||
"basic": "Validation failed: Name and Code are required",
|
||||
"database": "Validation failed: Main table must be configured",
|
||||
"incomplete": "Incomplete form configuration",
|
||||
"perfectDesign": "Please complete form design"
|
||||
},
|
||||
"loadFailed": "Failed to load data",
|
||||
"createSuccess": "Created successfully",
|
||||
"save": "Save",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"autoSave": {
|
||||
"saving": "Saving...",
|
||||
"saved": "Saved",
|
||||
"unsaved": "Unsaved"
|
||||
}
|
||||
},
|
||||
"dataSource": {
|
||||
"refresh": "Refresh",
|
||||
"refreshSuccess": "Refresh successful",
|
||||
"loading": "Loading...",
|
||||
"mainTable": "Main Table",
|
||||
"subTable": "Sub Table",
|
||||
"added": "Added",
|
||||
"fieldPreview": "Field Preview",
|
||||
"noFields": "No field information",
|
||||
"relationConfig": "Table Relation Config",
|
||||
"selectMainTip": "Please select main table first",
|
||||
"selectMainDesc": "Expand to table node in database tree, click [Main Table] button to set.",
|
||||
"addDatabase": "Add Database",
|
||||
"refreshConn": "Refresh Connection",
|
||||
"copyConnName": "Copy Connection Name",
|
||||
"addSchema": "Add Schema",
|
||||
"addTable": "Add Table",
|
||||
"editDatabase": "Edit Database",
|
||||
"refreshDatabase": "Refresh Database",
|
||||
"copyDatabaseName": "Copy Database Name",
|
||||
"editSchema": "Edit Schema",
|
||||
"refreshSchema": "Refresh Schema",
|
||||
"copySchemaName": "Copy Schema Name",
|
||||
"viewFields": "View Fields",
|
||||
"designTable": "Design Table",
|
||||
"setMainTable": "Set as Main",
|
||||
"addSubTable": "Add as Sub",
|
||||
"copyTableName": "Copy Table Name",
|
||||
"deleteTable": "Delete Table",
|
||||
"deleteTableConfirmTitle": "Delete Confirmation",
|
||||
"confirmDeleteTable": "Are you sure you want to delete table \"{tableName}\"? This operation cannot be undone!",
|
||||
"deleteTableSuccess": "Table deleted successfully",
|
||||
"deleteTableFailed": "Failed to delete table",
|
||||
"inputDatabaseName": "Please enter database name",
|
||||
"databaseNameRule": "Name can only contain letters, numbers, and underscores, starting with letter/underscore",
|
||||
"databaseCreateSuccess": "Database \"{name}\" created successfully",
|
||||
"databaseCreateFailed": "Failed to create database",
|
||||
"inputNewDatabaseName": "Please enter new database name",
|
||||
"nameNotChanged": "Name not changed",
|
||||
"renameNotSupported": "Database renaming not supported yet, please do it manually",
|
||||
"inputSchemaName": "Please enter Schema name",
|
||||
"createSchema": "Create Schema",
|
||||
"schemaNameRule": "Name can only contain letters, numbers, and underscores, starting with letter/underscore",
|
||||
"schemaCreateSuccess": "Schema \"{name}\" created successfully",
|
||||
"schemaCreateFailed": "Failed to create Schema",
|
||||
"inputNewSchemaName": "Please enter new Schema name",
|
||||
"schemaRenameSuccess": "Schema renamed to \"{name}\"",
|
||||
"schemaRenameFailed": "Failed to rename Schema",
|
||||
"sqlServerRenameSchemaNotSupported": "SQL Server does not support renaming Schema directly",
|
||||
"loadConfigFailed": "Failed to load database configuration",
|
||||
"loadDatabaseFailed": "Failed to load databases",
|
||||
"loadSchemaFailed": "Failed to load schemas",
|
||||
"loadTableFailed": "Failed to load tables",
|
||||
"loadNodeFailed": "Failed to load node data",
|
||||
"nodeNotFound": "Node not found",
|
||||
"refreshNodeSuccess": "Refresh successful, expand node to see latest data",
|
||||
"refreshNodeFailed": "Failed to refresh node",
|
||||
"refreshFailed": "Refresh failed",
|
||||
"copyToClipboard": "Copied to clipboard",
|
||||
"tableAlreadyAdded": "Table already added",
|
||||
"setMainSuccess": "{name} set as main table",
|
||||
"addSubSuccess": "Sub table {name} added",
|
||||
"tableFieldsRefreshed": "Table {name} fields updated",
|
||||
"removeMainConfirm": "Are you sure you want to remove the main table?",
|
||||
"removeSubConfirm": "Are you sure you want to remove this sub table?",
|
||||
"removeMain": "Remove Main Table",
|
||||
"subTableCount": "{count} sub tables in total",
|
||||
"tableName": "Table Name",
|
||||
"tableNamePlaceholder": "Please enter table name",
|
||||
"alias": "Alias",
|
||||
"aliasPlaceholder": "For SQL query",
|
||||
"fieldList": "Field List ({count})",
|
||||
"fieldName": "Field Name",
|
||||
"fieldNamePlaceholder": "Please enter field name",
|
||||
"fieldType": "Type",
|
||||
"fieldLength": "Length",
|
||||
"fieldScale": "Scale",
|
||||
"fieldComment": "Comment",
|
||||
"fieldCommentPlaceholder": "Please enter field comment",
|
||||
"schemaName": "Schema",
|
||||
"schemaNamePlaceholder": "Please enter schema name",
|
||||
"nullable": "Nullable",
|
||||
"isPrimaryKey": "Primary Key",
|
||||
"uniqueCheck": "Unique",
|
||||
"addField": "Add Field",
|
||||
"relationType": "Relation Type",
|
||||
"oneToMany": "One-to-Many",
|
||||
"oneToOne": "One-to-One",
|
||||
"foreignKeyField": "Foreign Key (Sub)",
|
||||
"relatedFieldMain": "Related Field (Main)",
|
||||
"selectForeignKey": "Select foreign key",
|
||||
"selectRelatedField": "Select related field",
|
||||
"viewFieldList": "View field list ({count} fields)",
|
||||
"noSubTables": "No sub tables",
|
||||
"addSubTip": "Click + button in database tree to add sub table",
|
||||
"missingSystemFieldsTitle": "Missing System Fields",
|
||||
"missingSystemFieldsMessage": "This table is missing system fields required for data permissions:<br/><br/><strong>{fields}</strong><br/><br/>Data permissions will not work properly without these fields.<br/><br/>Click 【Auto Add】 to automatically add the missing fields;<br/>Click 【Ignore and Continue】 to skip this check.",
|
||||
"autoAddFields": "Auto Add",
|
||||
"ignoreAndContinue": "Ignore and Continue",
|
||||
"addSystemFieldsSuccess": "System fields added successfully",
|
||||
"addSystemFieldsFailed": "Failed to add system fields",
|
||||
"mainTableRequired": "Please configure a main table first",
|
||||
"connectionUnavailable": "Database connection is unavailable; check connection settings",
|
||||
"mainTableNotFound": "Main table {table} was not found on the target connection",
|
||||
"subTableNotFound": "Sub table {table} was not found on the target connection",
|
||||
"currentConnection": "Connection",
|
||||
"externalConnectionTip": "Form data is stored on the external connection; ensure tables exist on the target database"
|
||||
},
|
||||
"listDesign": {
|
||||
"containerPage": "Page",
|
||||
"title": "List Design",
|
||||
"queryTab": "Query Fields",
|
||||
"listTab": "List Fields",
|
||||
"propertyTab": "List Properties",
|
||||
"querySelectionTip": "Select fields to add to query criteria",
|
||||
"listSelectionTip": "Select fields to add to list display",
|
||||
"selectAll": "Select All",
|
||||
"noAvailableFields": "No available fields, please add fields in form design first",
|
||||
"tableProperties": "Table Properties",
|
||||
"showPagination": "Show Pagination",
|
||||
"pageSize": "Page Size",
|
||||
"itemsPerPage": "{count} items/page",
|
||||
"showIndex": "Show Index Column",
|
||||
"showSelection": "Show Selection Box",
|
||||
"stripe": "Stripe",
|
||||
"border": "Border",
|
||||
"size": "Size",
|
||||
"sizeLarge": "Large",
|
||||
"sizeDefault": "Medium",
|
||||
"sizeSmall": "Small",
|
||||
"tableHeight": "Table Height",
|
||||
"adaptive": "Adaptive",
|
||||
"dialogProperties": "Dialog Properties",
|
||||
"dialogWidth": "Dialog Width",
|
||||
"widthSmall": "Small (600px)",
|
||||
"widthMedium": "Medium (800px)",
|
||||
"widthLarge": "Large (1000px)",
|
||||
"widthExtraLarge": "Extra Large (1200px)",
|
||||
"fullscreen": "Fullscreen",
|
||||
"draggable": "Draggable",
|
||||
"closeOnClickModal": "Close on Mask Click",
|
||||
"closeOnPressEscape": "Close on ESC",
|
||||
"pageProperties": "Page Properties",
|
||||
"showBackButton": "Show Back Button",
|
||||
"showBackButtonTip": "Control whether to display the back to list button in the upper left corner of the page",
|
||||
"layoutRenderMode": "Render Mode",
|
||||
"conditionRender": "Conditional Render",
|
||||
"routeRender": "Route Render",
|
||||
"conditionRenderTip": "Switch form display via v-if within the current page without creating a new route",
|
||||
"routeRenderTip": "Navigate to a separate route page to display the form without showing tabs",
|
||||
"openInNewTab": "Open in New Tab",
|
||||
"openInNewTabTip": "Whether to open the form page in a new tab when clicking add, edit, or view buttons",
|
||||
"buttonDisplay": "Button Display",
|
||||
"toolbarButtons": "Toolbar Buttons",
|
||||
"rowActionButtons": "Row Action Buttons",
|
||||
"addBtn": "Add Button",
|
||||
"editBtn": "Edit Button",
|
||||
"deleteBtn": "Delete Button",
|
||||
"viewBtn": "View Button",
|
||||
"exportBtn": "Export Button",
|
||||
"importBtn": "Import Button",
|
||||
"batchDeleteBtn": "Batch Delete",
|
||||
"startWorkflow": "Start Workflow",
|
||||
"noWorkflowBound": "No workflow bound to this form",
|
||||
"startWorkflowSuccess": "Workflow started successfully",
|
||||
"startWorkflowFailed": "Failed to start workflow",
|
||||
"workflowTitleLabel": "Workflow Title",
|
||||
"workflowTitlePlaceholder": "Please enter workflow title",
|
||||
"workflowTitleRequired": "Workflow title is required",
|
||||
"formActionSettings": "Form Action Settings",
|
||||
"showConfirmButton": "Show Confirm Button",
|
||||
"showConfirmButtonTip": "When disabled, the confirm button will not be shown when adding/editing data",
|
||||
"afterSaveAction": "After Save Action",
|
||||
"afterSaveClose": "Close and return to list",
|
||||
"afterSaveEditMode": "Switch to edit mode",
|
||||
"afterSaveContinueAdd": "Clear form and continue adding",
|
||||
"afterSaveActionTip": "Only applies when adding: close and return, stay on current record to edit, or clear form to add another",
|
||||
"enableStartWorkflowOnAdd": "Enable Start Workflow on Add",
|
||||
"enableStartWorkflowOnAddTip": "When enabled, a 'Start Workflow' button will be shown when adding new data",
|
||||
"queryConfigTitle": "Query Field Config",
|
||||
"listConfigTitle": "List Field Config",
|
||||
"sort": "Sort",
|
||||
"displayName": "Display Name",
|
||||
"fieldKey": "Field Key",
|
||||
"queryType": "Query Type",
|
||||
"componentType": "Component Type",
|
||||
"width": "Width",
|
||||
"defaultValue": "Default Value",
|
||||
"hidden": "Hidden",
|
||||
"showTime": "Time",
|
||||
"multiple": "Multiple",
|
||||
"caseSensitive": "Case Sensitive",
|
||||
"actions": "Actions",
|
||||
"matchLike": "Like",
|
||||
"matchEq": "Equals",
|
||||
"matchRange": "Range",
|
||||
"matchIn": "In",
|
||||
"matchSpaceLikeAnd": "Space Fuzzy AND",
|
||||
"matchSpaceLikeOr": "Space Fuzzy OR",
|
||||
"matchSpaceEqAnd": "Space Exact AND",
|
||||
"matchSpaceEqOr": "Space Exact OR",
|
||||
"compInput": "Input",
|
||||
"compSelect": "Select",
|
||||
"compDate": "Date",
|
||||
"compTime": "Time",
|
||||
"compUser-select": "User Selector",
|
||||
"compDept-select": "Department Selector",
|
||||
"compRole-select": "Role Selector",
|
||||
"compPost-select": "Post Selector",
|
||||
"compForm-select": "Form Selector",
|
||||
"compTable-select": "Table Selector",
|
||||
"compFile-select": "File Selector",
|
||||
"compImage-select": "Image Selector",
|
||||
"compRegion-select": "Region Selector",
|
||||
"compMoney-input": "Money Input",
|
||||
"compRich-text": "Rich Text Editor",
|
||||
"compCode-editor": "Code Editor",
|
||||
"compFormula-input": "Formula Input",
|
||||
"compCron-selector": "Cron Expression",
|
||||
"compCurrent-user": "Current User",
|
||||
"compCurrent-datetime": "Current Datetime",
|
||||
"compCode-generator": "Code Generator",
|
||||
"compAi-image-ocr": "AI Image OCR",
|
||||
"width18": "1/8 (3)",
|
||||
"width16": "1/6 (4)",
|
||||
"width14": "1/4 (6)",
|
||||
"width13": "1/3 (8)",
|
||||
"width12": "1/2 (12)",
|
||||
"widthFull": "Full (24)",
|
||||
"none": "None",
|
||||
"noQueryFields": "No query fields",
|
||||
"selectQueryTip": "Please select fields to query on the right",
|
||||
"columnName": "Column Name",
|
||||
"left": "Left",
|
||||
"center": "Center",
|
||||
"right": "Right",
|
||||
"minWidth": "Min Width",
|
||||
"min": "Min",
|
||||
"noFixed": "Not Fixed",
|
||||
"fixedLeft": "Left Side",
|
||||
"fixedRight": "Right Side",
|
||||
"sortable": "Sortable",
|
||||
"resizable": "Resizable",
|
||||
"overflowTooltip": "Overflow Tooltip",
|
||||
"ellipsis": "Ellipsis",
|
||||
"fileImageDisplayHint": "Image/signature fields display as thumbnails automatically; text options do not apply",
|
||||
"showAsTag": "Show as Tag",
|
||||
"tagType": "Tag Type:",
|
||||
"tagDefault": "Default",
|
||||
"tagSuccess": "Success",
|
||||
"tagWarning": "Warning",
|
||||
"tagInfo": "Info",
|
||||
"tagDanger": "Danger",
|
||||
"formatter": "Formatter",
|
||||
"formatDate": "Date",
|
||||
"formatDateTime": "DateTime",
|
||||
"formatMoney": "Money",
|
||||
"formatPercent": "Percent",
|
||||
"formatNumber": "Number",
|
||||
"formatPattern": "Pattern",
|
||||
"prefix": "Prefix",
|
||||
"suffix": "Suffix",
|
||||
"custom": "Custom",
|
||||
"unitYuan": "Yuan",
|
||||
"unitGe": "Unit",
|
||||
"unitCi": "Times",
|
||||
"unitDay": "Days",
|
||||
"unitHour": "Hours",
|
||||
"noListFields": "No list fields",
|
||||
"selectListTip": "Please select fields to display on the right",
|
||||
"defaultSortField": "Default Sort Field",
|
||||
"selectSortField": "Select sort field",
|
||||
"sortOrder": "Sort Order",
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending",
|
||||
"addSortField": "Add Sort Field",
|
||||
"defaultFilterConditions": "Default Filter Conditions",
|
||||
"selectFilterField": "Select field",
|
||||
"selectFilterOperator": "Operator",
|
||||
"filterValue": "Value",
|
||||
"filterValuePlaceholder": "Enter filter value",
|
||||
"addFilterCondition": "Add Filter Condition",
|
||||
"filterOperatorEq": "Equals",
|
||||
"filterOperatorNe": "Not equals",
|
||||
"filterOperatorGt": "Greater than",
|
||||
"filterOperatorGte": "Greater or equal",
|
||||
"filterOperatorLt": "Less than",
|
||||
"filterOperatorLte": "Less or equal",
|
||||
"filterOperatorLike": "Contains",
|
||||
"filterOperatorIn": "In list",
|
||||
"filterOperatorNull": "Is null",
|
||||
"filterOperatorNotNull": "Not null",
|
||||
"tableSummary": "Table Summary",
|
||||
"showSummary": "Show Summary Row",
|
||||
"summaryText": "Summary Row Label",
|
||||
"summaryTextPlaceholder": "Total",
|
||||
"summaryColumnTip": "Enable summary for numeric fields in list field config",
|
||||
"treeConfig": "Tree Table",
|
||||
"enableTree": "Enable Tree Table",
|
||||
"parentField": "Parent Field",
|
||||
"lazyLoad": "Lazy Load",
|
||||
"lazyLoadOnTip": "Load children on demand",
|
||||
"lazyLoadOffTip": "Load all data at once",
|
||||
"defaultExpandAll": "Expand All by Default",
|
||||
"indent": "Indent",
|
||||
"checkStrictly": "Check Strictly",
|
||||
"enableSummary": "Summary",
|
||||
"summaryType": "Type",
|
||||
"summarySum": "Sum",
|
||||
"summaryAvg": "Average",
|
||||
"summaryCount": "Count",
|
||||
"summaryMax": "Max",
|
||||
"summaryMin": "Min",
|
||||
"summaryPrecision": "Precision",
|
||||
"sortFrontend": "Frontend",
|
||||
"sortBackend": "Backend",
|
||||
"filterable": "Filter",
|
||||
"filterInput": "Input",
|
||||
"filterSelect": "Select",
|
||||
"filterDateRange": "Date Range",
|
||||
"filterMultiple": "Multiple",
|
||||
"dialogFilter": "Dialog Select",
|
||||
"subTableButtons": "Sub Table Buttons",
|
||||
"subTableButtonsTip": "Configure sub table form buttons, click to manage sub table data in dialog/drawer/page",
|
||||
"addSubTableButton": "Add Sub Table Button",
|
||||
"noSubTableButtons": "No sub table buttons",
|
||||
"subTableButtonText": "Button Text",
|
||||
"selectSubTable": "Select Sub Table",
|
||||
"selectSubTablePlaceholder": "Select the sub table to associate",
|
||||
"selectSubForm": "Select Sub Form",
|
||||
"selectSubFormPlaceholder": "Select the independent form for sub table",
|
||||
"foreignKeyField": "Foreign Key Field",
|
||||
"foreignKeyFieldPlaceholder": "Auto-filled from sub table config",
|
||||
"buttonStyle": "Button Style",
|
||||
"subFormContainerType": "Container Type",
|
||||
"drawerSize": "Drawer Size",
|
||||
"drawerDirection": "Open Direction",
|
||||
"customButtons": "Custom Buttons",
|
||||
"addCustomButton": "Add Custom Button",
|
||||
"noCustomButtons": "No custom buttons",
|
||||
"defaultButtonName": "Custom Button",
|
||||
"buttonName": "Button Name",
|
||||
"buttonType": "Button Type",
|
||||
"buttonPosition": "Button Position",
|
||||
"toolbar": "Toolbar",
|
||||
"tools": "Toolbar Right",
|
||||
"row": "Row Action",
|
||||
"iconAndDisplay": "Icon & Display",
|
||||
"iconOnly": "Icon Only",
|
||||
"actionType": "Action Type",
|
||||
"actionLink": "Open Link",
|
||||
"actionApi": "Call API",
|
||||
"actionEvent": "Trigger Event",
|
||||
"actionPage": "Open Page",
|
||||
"actionGenerateDocument": "Generate Document",
|
||||
"bindDocumentTemplates": "Bind Document Templates",
|
||||
"bindDocumentTemplatesPlaceholder": "Select templates to generate (multiple)",
|
||||
"bindDocumentTemplatesHint": "Leave empty to generate all published templates for this form; otherwise only selected templates",
|
||||
"pageCode": "Page Code",
|
||||
"pageCodePlaceholder": "Select page to open",
|
||||
"pageCodeRequired": "Please select a page",
|
||||
"dialogTitle": "Dialog Title",
|
||||
"dialogTitlePlaceholder": "Supports variables: {id}, {name}, etc.",
|
||||
"pageDialogWidth": "Dialog Width",
|
||||
"pageDialogFullscreen": "Fullscreen",
|
||||
"pageView": "Page View",
|
||||
"pageLoadFailed": "Failed to load page",
|
||||
"pageNoConfig": "No page configuration",
|
||||
"linkUrl": "Link URL",
|
||||
"linkUrlPlaceholder": "Supports variables: {id}, {field}, etc.",
|
||||
"apiUrl": "API URL",
|
||||
"apiUrlPlaceholder": "/api/xxx, supports variables: {id}",
|
||||
"apiMethod": "Request Method",
|
||||
"confirmMessage": "Confirm Message",
|
||||
"confirmMessagePlaceholder": "Confirmation message before execution",
|
||||
"eventName": "Event Name",
|
||||
"eventNamePlaceholder": "Custom event name",
|
||||
"buttonIcon": "Button Icon",
|
||||
"buttonIconPlaceholder": "lucide:icon-name",
|
||||
"showCondition": "Show Condition",
|
||||
"showConditionPlaceholder": "row.status === 'active'",
|
||||
"permissionCode": "Permission Code",
|
||||
"permissionCodePlaceholder": "Optional, for permission control",
|
||||
"styleOptions": "Style Options",
|
||||
"plain": "Plain",
|
||||
"round": "Round",
|
||||
"circle": "Circle",
|
||||
"textBtn": "Text",
|
||||
"linkBtn": "Link",
|
||||
"buttonSize": "Button Size",
|
||||
"stateControl": "State Control",
|
||||
"disabled": "Disabled",
|
||||
"disabledCondition": "Disabled Condition",
|
||||
"disabledConditionPlaceholder": "row.status === 'completed'",
|
||||
"tooltip": "Tooltip",
|
||||
"tooltipPlaceholder": "Tooltip text on hover",
|
||||
"badge": "Badge",
|
||||
"badgePlaceholder": "Number or text, supports variables",
|
||||
"badgeType": "Badge Type",
|
||||
"confirmDialog": "Confirm Dialog",
|
||||
"confirmTitle": "Confirm Title",
|
||||
"confirmTitlePlaceholder": "Confirm Operation",
|
||||
"messageConfig": "Message Config",
|
||||
"successMessage": "Success Message",
|
||||
"successMessagePlaceholder": "Operation successful",
|
||||
"errorMessage": "Error Message",
|
||||
"errorMessagePlaceholder": "Operation failed",
|
||||
"reloadAfterSuccess": "Reload after success",
|
||||
"actionAgent": "Agent Chat",
|
||||
"agentId": "Agent ID",
|
||||
"agentIdPlaceholder": "Unique identifier of the agent",
|
||||
"agentCode": "Agent Code",
|
||||
"agentCodePlaceholder": "Agent code (alternative)",
|
||||
"initialMessage": "Initial Message",
|
||||
"initialMessagePlaceholder": "Initial message when opening chat, supports variables like {name}",
|
||||
"includeRowData": "Include Row Data",
|
||||
"autoSend": "Auto Send Initial Message",
|
||||
"listType": "List Type",
|
||||
"cardProperties": "Card Properties",
|
||||
"cardColumns": "Columns Per Row",
|
||||
"cardColumnsOption": "{count} Columns",
|
||||
"cardGap": "Card Gap (px)",
|
||||
"cardShadow": "Card Shadow",
|
||||
"shadowAlways": "Always",
|
||||
"shadowHover": "On Hover",
|
||||
"shadowNever": "Never",
|
||||
"cardFieldsConfig": "Card Fields Config",
|
||||
"cardFieldsTip": "Drag fields to card areas",
|
||||
"cardPreview": "Card Preview",
|
||||
"cardFieldProperties": "Field Properties",
|
||||
"fieldProperties": "Field Properties",
|
||||
"showDisplayName": "Show Name",
|
||||
"showRelationField": "Show Relation Field",
|
||||
"selectDisplayField": "Select Display Field",
|
||||
"selectDisplayFieldPlaceholder": "Select field to display",
|
||||
"loadingFields": "Loading fields...",
|
||||
"showAvatar": "Show Avatar",
|
||||
"showVirtualValue": "Show Linked Value",
|
||||
"cardAreaIcon": "Icon",
|
||||
"cardAreaTitle": "Title",
|
||||
"cardAreaSubtitle": "Subtitle",
|
||||
"cardAreaDescription": "Description",
|
||||
"cardAreaTags": "Tags",
|
||||
"cardAreaFooterLeft": "Footer Left",
|
||||
"cardAreaFooterRight": "Footer Right",
|
||||
"useCursorPagination": "Cursor Pagination",
|
||||
"useCursorPaginationTip": "Suitable for large datasets. When enabled, total count is hidden and only prev/next navigation is available",
|
||||
"prevPage": "Previous",
|
||||
"nextPage": "Next",
|
||||
"noMoreData": "No more data"
|
||||
},
|
||||
"formRender": {
|
||||
"subTableData": "Sub Table Data",
|
||||
"loadSubFormFailed": "Failed to load sub form",
|
||||
"loadDataFailed": "Failed to load data",
|
||||
"validateFailed": "Please check the form input"
|
||||
},
|
||||
"publishDialog": {
|
||||
"title": "Form Publish",
|
||||
"settings": "Publish Settings",
|
||||
"allowGuest": "Allow Guest Access",
|
||||
"success": "Published successfully",
|
||||
"failed": "Publish failed",
|
||||
"menuConfig": "Menu Config",
|
||||
"menuName": "Menu Name",
|
||||
"parentMenu": "Parent Menu",
|
||||
"parentMenuPlaceholder": "Please select parent menu (top level if empty)",
|
||||
"menuIcon": "Menu Icon",
|
||||
"routeInfo": "Route Info",
|
||||
"accessPath": "Access Path",
|
||||
"confirmPublish": "Confirm Publish"
|
||||
},
|
||||
"validator": {
|
||||
"required": "is required",
|
||||
"formatInvalid": "Format is invalid",
|
||||
"tooLong": "Length exceeds limit",
|
||||
"notBound": "Not bound to any data field",
|
||||
"duplicateSubField": "Field name \"{field}\" is duplicated in sub table \"{table}\"",
|
||||
"duplicateField": "Field name \"{field}\" is duplicated",
|
||||
"invalidSubTable": "Invalid sub table \"{table}\"",
|
||||
"fieldNotInSubTable": "Field \"{field}\" does not exist in sub table \"{table}\"",
|
||||
"fieldNotInMainTable": "Field \"{field}\" does not exist in main table",
|
||||
"fileMultipleRequiresJson": "Field \"{field}\" is configured for multiple file/image selection, database type must be JSON, current type is {currentType}",
|
||||
"fileSingleRequiresVarchar": "Field \"{field}\" is configured for single file/image selection, database type must be VARCHAR, current type is {currentType}",
|
||||
"regionRequiresJson": "Field \"{field}\" uses region selector component, database type must be JSON, current type is {currentType}",
|
||||
"numericFieldRequiresNumericComponent": "Field \"{field}\" has database type {fieldType} (numeric), cannot use {componentType} component. Please use numeric components like input-number, money-input, slider, rate, or formula-input",
|
||||
"stringFieldCannotUseDateComponent": "Field \"{field}\" has database type {fieldType} (string), cannot use {componentType} component. Date/time components require corresponding date/time type fields",
|
||||
"dateTimeFieldRequiresDateComponent": "Field \"{field}\" has database type {fieldType} (date/time), cannot use {componentType} component. Please use date picker, time picker, or current-datetime components",
|
||||
"booleanFieldRequiresSwitchComponent": "Field \"{field}\" has database type {fieldType} (boolean), cannot use {componentType} component. Please use switch, radio, or select components",
|
||||
"jsonFieldRequiresJsonComponent": "Field \"{field}\" has database type {fieldType} (JSON), cannot use {componentType} component. Please use checkbox, cascader, tree-select, file-selector, or other JSON-compatible components",
|
||||
"reverseAutoFillRequiresMultiple": "Field \"{field}\" has reverse auto-fill enabled and must use multiple selection",
|
||||
"reverseAutoFillNoFormCode": "Field \"{field}\" has reverse auto-fill enabled but no linked form is configured",
|
||||
"reverseAutoFillNoSourceField": "Field \"{field}\" has reverse auto-fill enabled but no source field is configured",
|
||||
"reverseAutoFillNoTargetField": "Field \"{field}\" has reverse auto-fill enabled but no linked form filter field is configured",
|
||||
"reverseAutoFillSelfSource": "Field \"{field}\" reverse auto-fill source field cannot be itself",
|
||||
"reverseAutoFillConflictsValueLink": "Field \"{field}\" has reverse auto-fill enabled and cannot also use value link",
|
||||
"unknown": "Unknown",
|
||||
"columnNoField": "Column is not bound to any field",
|
||||
"unknownColumn": "Unknown column",
|
||||
"columnFieldNotExist": "Column bound field \"{field}\" does not exist in form or database",
|
||||
"queryFieldNoField": "Query field is not bound to any field",
|
||||
"unknownQueryField": "Unknown query field",
|
||||
"queryFieldNotExist": "Query field \"{field}\" does not exist in form or database",
|
||||
"subTableButton": "Sub-table button",
|
||||
"subTableButtonNoText": "Sub-table button has no button text",
|
||||
"subTableButtonNoFormCode": "Sub-table button has no sub-form code",
|
||||
"subTableButtonNoForeignKey": "Sub-table button has no foreign key field",
|
||||
"customButton": "Custom button",
|
||||
"customButtonNoName": "Custom button has no name",
|
||||
"customButtonLinkNoUrl": "Link type button has no URL",
|
||||
"customButtonApiNoUrl": "API type button has no API URL",
|
||||
"customButtonEventNoName": "Event type button has no event name"
|
||||
},
|
||||
"generateDocument": {
|
||||
"title": "Generate Document",
|
||||
"template": "Document Template",
|
||||
"selectTemplate": "Please select a document template",
|
||||
"selectTemplatePlaceholder": "Select the document template to generate",
|
||||
"description": "Template Description",
|
||||
"noTemplates": "No published document templates bound to this form",
|
||||
"generatedDocuments": "Generated Documents",
|
||||
"generate": "Generate",
|
||||
"generateSuccess": "Document generated successfully",
|
||||
"generateFailed": "Failed to generate document",
|
||||
"preview": "Preview",
|
||||
"download": "Download",
|
||||
"buttonLabel": "Generate Document",
|
||||
"buttonTooltip": "Generate document from this data",
|
||||
"positionHint": "Generate document button can only be placed in row actions, as it requires specific form data",
|
||||
"autoRefresh": "Auto Refresh",
|
||||
"autoRefreshHint": "When enabled, each click regenerates documents from the latest form data; when disabled, existing documents are shown first"
|
||||
},
|
||||
"apiInfo": {
|
||||
"title": "API Information",
|
||||
"description": "This form provides the following API endpoints. All endpoints require authentication.",
|
||||
"formCode": "Form Code",
|
||||
"basePrefix": "Base Prefix",
|
||||
"authRequired": "Auth Required",
|
||||
"permissions": "Permissions",
|
||||
"getPermissions": "Get Permissions",
|
||||
"getPermissionsDesc": "Get current user's operation permissions for this form (view/add/edit/delete/export/import)",
|
||||
"getFieldPermissions": "Get Field Permissions",
|
||||
"getFieldPermissionsDesc": "Get current user's field-level read/write permissions for this form",
|
||||
"dataOperations": "Data Operations",
|
||||
"getList": "List Data",
|
||||
"getListDesc": "Paginated query of form data with sorting, search, and filtering support",
|
||||
"getDetail": "Get Detail",
|
||||
"getDetailDesc": "Get single record detail including sub-table data",
|
||||
"createData": "Create Data",
|
||||
"createDataDesc": "Create a new form record with optional sub-table data",
|
||||
"updateData": "Update Data",
|
||||
"updateDataDesc": "Update a specific record with optional sub-table data",
|
||||
"deleteData": "Delete Data",
|
||||
"deleteDataDesc": "Delete a specific record and its associated sub-table data",
|
||||
"batchDelete": "Batch Delete",
|
||||
"batchDeleteDesc": "Delete multiple records and their associated sub-table data",
|
||||
"auxiliary": "Auxiliary",
|
||||
"getTreeChildren": "Get Tree Children",
|
||||
"getTreeChildrenDesc": "Get child nodes for tree table lazy loading",
|
||||
"getFieldValues": "Get Field Values",
|
||||
"getFieldValuesDesc": "Get unique values for a specific field, used for filter options",
|
||||
"checkUnique": "Check Unique",
|
||||
"checkUniqueDesc": "Check if a field value is unique",
|
||||
"importExport": "Import & Export",
|
||||
"exportExcel": "Export Excel",
|
||||
"exportExcelDesc": "Export form data to Excel file with field selection and sub-table support",
|
||||
"importTemplate": "Download Import Template",
|
||||
"importTemplateDesc": "Download Excel import template",
|
||||
"importExcel": "Import Excel",
|
||||
"importExcelDesc": "Import data from Excel file with append or overwrite mode",
|
||||
"request": "Request",
|
||||
"response": "Response",
|
||||
"pathParams": "Path Params",
|
||||
"queryParams": "Query Params",
|
||||
"requestBody": "Request Body",
|
||||
"noParams": "No params",
|
||||
"copyPath": "Copy Path",
|
||||
"copiedSuccess": "Copied to clipboard",
|
||||
"formFields": "Form Fields Reference"
|
||||
},
|
||||
"formData": {
|
||||
"import": {
|
||||
"title": "Import Data",
|
||||
"modeTitle": "Select Import Mode",
|
||||
"appendMode": "Append Mode",
|
||||
"appendModeDesc": "Keep existing data and append new data",
|
||||
"overwriteMode": "Overwrite Mode",
|
||||
"overwriteModeDesc": "Clear existing data and import all new data",
|
||||
"overwriteWarning": "Overwrite mode will delete all existing data in the table. This action cannot be undone!",
|
||||
"dataHandling": "Data Handling",
|
||||
"insertOnly": "Insert Only",
|
||||
"insertOnlyDesc": "All data from Excel will be added as new records",
|
||||
"updateOnly": "Update Only",
|
||||
"updateOnlyDesc": "Find existing records by the specified field and update them with Excel data. No new records will be added",
|
||||
"upsert": "Update & Insert",
|
||||
"upsertDesc": "Update existing records found by the specified field. When no match is found, insert as new records",
|
||||
"matchField": "Match Field",
|
||||
"matchFieldPlaceholder": "Select the field to match existing data",
|
||||
"matchFieldRequired": "A match field is required for update mode",
|
||||
"updatedCount": "updated successfully",
|
||||
"insertedCount": "inserted successfully",
|
||||
"skippedCount": "skipped",
|
||||
"selectFile": "Select File",
|
||||
"dragOrClick": "Drag file here or click to upload",
|
||||
"onlyXlsx": "Only .xlsx format is supported",
|
||||
"downloadTemplate": "Download Import Template",
|
||||
"validateAndUpload": "Validate & Upload",
|
||||
"validating": "Validating data...",
|
||||
"importing": "Importing data...",
|
||||
"passCount": "passed validation",
|
||||
"failCount": "failed validation",
|
||||
"importedCount": "imported successfully",
|
||||
"errorList": "Error Details",
|
||||
"rowNumber": "Row",
|
||||
"errorMsg": "Error Message",
|
||||
"allPassed": "All data passed validation, ready to import",
|
||||
"importSuccess": "Data import completed",
|
||||
"reselect": "Reselect",
|
||||
"confirmImport": "Confirm Import"
|
||||
}
|
||||
},
|
||||
"importExport": {
|
||||
"export": "Export Config",
|
||||
"import": "Import Config",
|
||||
"batchExport": "Batch Export",
|
||||
"exportSuccess": "Form config exported",
|
||||
"exportFailed": "Export failed",
|
||||
"importTitle": "Import Form Config",
|
||||
"selectFile": "Select File",
|
||||
"dragOrClick": "Drag a JSON file here or click to upload",
|
||||
"dragOrClickMulti": "Supports selecting multiple files for batch import",
|
||||
"onlyJson": "Only .json files are supported",
|
||||
"fileParseError": "Failed to parse file, please check the format",
|
||||
"checking": "Checking...",
|
||||
"checkResult": "Check Result",
|
||||
"codeConflict": "Form code already exists",
|
||||
"codeConflictTip": "Please change the code before importing",
|
||||
"newCode": "New Code",
|
||||
"newCodePlaceholder": "Enter a new form code",
|
||||
"tableStatus": "Database Table Status",
|
||||
"mainTable": "Main Table",
|
||||
"subTable": "Sub Table",
|
||||
"schema": "Schema",
|
||||
"tableExists": "Exists",
|
||||
"tableNotExists": "Not Exists",
|
||||
"hasDdl": "Has DDL",
|
||||
"noDdl": "No DDL",
|
||||
"autoCreateTables": "Auto create missing tables",
|
||||
"autoCreateTablesTip": "Missing tables will be created from exported DDL during import",
|
||||
"createSchemaIfNotExists": "Create schema if missing",
|
||||
"createSchemaIfNotExistsTip": "Create target schemas from config before import (PostgreSQL / SQL Server only)",
|
||||
"cannotAutoCreate": "Some tables are missing and have no DDL, cannot auto create",
|
||||
"crossDialectAutoCreate": "Export database type ({source}) differs from target connection ({target}). Auto-create is not supported; use the same engine or create tables manually.",
|
||||
"importSuccess": "Form config imported successfully",
|
||||
"importFailed": "Import failed",
|
||||
"confirmImport": "Confirm Import",
|
||||
"formInfo": "Form Info",
|
||||
"formName": "Form Name",
|
||||
"formCode": "Form Code",
|
||||
"formType": "Form Type",
|
||||
"dbConfig": "Database Config",
|
||||
"mainTableName": "Main Table",
|
||||
"fieldCount": "Fields",
|
||||
"subTableCount": "Sub Tables",
|
||||
"appTip": "Imported form will belong to the current application",
|
||||
"tableExistsRename": "Table exists, you can create with a new name and schema",
|
||||
"newTableName": "New Table Name",
|
||||
"newTableNamePlaceholder": "Enter a new table name",
|
||||
"selectSchema": "Select Schema",
|
||||
"selectSchemaPlaceholder": "Select target schema",
|
||||
"batchImportTitle": "Batch Import Form Config",
|
||||
"batchImportCount": "{count} forms total",
|
||||
"batchImportProgress": "Importing {current}/{total}...",
|
||||
"batchImportSuccess": "Batch import done, {success} succeeded, {fail} failed",
|
||||
"batchImportItem": "Item {index}",
|
||||
"batchNext": "Next",
|
||||
"batchSkip": "Skip",
|
||||
"batchSkipped": "Skipped",
|
||||
"batchConfirmAndNext": "Confirm & Next",
|
||||
"batchImportAll": "Import All",
|
||||
"batchImporting": "Importing...",
|
||||
"checkFailed": "Check failed",
|
||||
"noSelection": "Please select forms to export first",
|
||||
"batchExportProgress": "Exporting {current}/{total}...",
|
||||
"batchExportDone": "Batch export done, {success} succeeded, {fail} failed",
|
||||
"reselect": "Reselect"
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"name": "Login Log",
|
||||
"title": "Login Log",
|
||||
"username": "Username",
|
||||
"userId": "User ID",
|
||||
"status": "Login Status",
|
||||
"loginType": "Login Type",
|
||||
"passwordLogin": "Password",
|
||||
"codeLogin": "Verification Code",
|
||||
"qrcodeLogin": "QR Code",
|
||||
"wechatLogin": "WeChat",
|
||||
"microsoftLogin": "Microsoft",
|
||||
"dingtalkLogin": "DingTalk",
|
||||
"feishuLogin": "Feishu",
|
||||
"statusSuccess": "Success",
|
||||
"statusFailed": "Failed",
|
||||
"failureReason": "Failure Reason",
|
||||
"failureReasonUnknown": "Unknown Error",
|
||||
"failureReasonUserNotExist": "User Not Exist",
|
||||
"failureReasonPasswordError": "Password Error",
|
||||
"failureReasonUserDisabled": "User Disabled",
|
||||
"failureReasonUserLocked": "User Locked",
|
||||
"failureReasonUserInactive": "User Inactive",
|
||||
"failureReasonAccountAbnormal": "Account Abnormal",
|
||||
"failureReasonOther": "Other Error",
|
||||
"failureMessage": "Failure Message",
|
||||
"loginIp": "Login IP",
|
||||
"ipLocation": "IP Location",
|
||||
"userAgent": "User Agent",
|
||||
"browserType": "Browser",
|
||||
"osType": "Operating System",
|
||||
"deviceType": "Device Type",
|
||||
"deviceTypeDesktop": "Desktop",
|
||||
"deviceTypeMobile": "Mobile",
|
||||
"deviceTypeTablet": "Tablet",
|
||||
"deviceTypeOther": "Other",
|
||||
"duration": "Session Duration",
|
||||
"durationSeconds": "Session Duration (seconds)",
|
||||
"sessionId": "Session ID",
|
||||
"remark": "Remark",
|
||||
"loginTime": "Login Time",
|
||||
"createTime": "Create Time",
|
||||
"operation": "Operation",
|
||||
"detail": "Detail",
|
||||
"detailTitle": "Login Log Detail",
|
||||
"batchDelete": "Batch Delete",
|
||||
"batchDeleteTitle": "Batch Delete Login Logs",
|
||||
"batchDeleteConfirm": "Are you sure to delete {0} login logs? Users: {1}",
|
||||
"deleteConfirm": "Are you sure to delete this login log of user {0}?",
|
||||
"deleteSuccess": "Delete Success",
|
||||
"deleteError": "Delete Failed",
|
||||
"selectLogsToDelete": "Please select logs to delete",
|
||||
"getDetailError": "Failed to get login log detail",
|
||||
"noData": "No Data",
|
||||
"searchPlaceholder": "Please enter {0}",
|
||||
"selectPlaceholder": "Please select {0}",
|
||||
"selectStatus": "Please select login status",
|
||||
"selectLoginType": "Please select login type",
|
||||
"selectFailureReason": "Please select failure reason",
|
||||
"selectDeviceType": "Please select device type",
|
||||
"startTime": "Start Time",
|
||||
"endTime": "End Time",
|
||||
"selectStartTime": "Please select start time",
|
||||
"selectEndTime": "Please select end time",
|
||||
"formatHours": "{0} hours",
|
||||
"formatMinutes": "{0} minutes",
|
||||
"formatSeconds": "{0} seconds",
|
||||
"formatZeroSeconds": "0 seconds"
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"overview": "Overview",
|
||||
"analytics": "Analytics",
|
||||
"workspace": "Workspace",
|
||||
"home": "Home",
|
||||
"systemManagement": "System Management",
|
||||
"userManagement": "User Management",
|
||||
"departmentManagement": "Department Management",
|
||||
"organizationChart": "Organization Chart",
|
||||
"permissionManagement": "API Management",
|
||||
"menuManagement": "Menu Management",
|
||||
"positionManagement": "Position Management",
|
||||
"roleManagement": "Role Permission",
|
||||
"userCenter": "User Center",
|
||||
"accountSettings": "Account Settings",
|
||||
"loginLog": "Login Log",
|
||||
"databaseManagement": "Database Management",
|
||||
"dbManagement": "DB Management",
|
||||
"databaseConnection": "Database Connections",
|
||||
"redisManagement": "Redis Management",
|
||||
"dataSource": "Data Source",
|
||||
"systemTools": "System Tools",
|
||||
"fileManagement": "File Management",
|
||||
"dictionaryManagement": "Dictionary Management",
|
||||
"scheduledTasks": "Scheduled Tasks",
|
||||
"systemMonitoring": "System Monitoring",
|
||||
"redisMonitoring": "Redis Monitoring",
|
||||
"serverMonitoring": "Server Monitoring",
|
||||
"databaseMonitoring": "Database Monitoring",
|
||||
"contractManagement": "Contract Management",
|
||||
"contractList": "Contract List",
|
||||
"templateManagement": "Template Management",
|
||||
"crowdUsers": "Crowd Users",
|
||||
"onlineDevelopment": "Online Development",
|
||||
"workflowManagement": "Workflow Management",
|
||||
"workflowInstanceManagement": "Workflow Instances",
|
||||
"formManagement": "Form Management",
|
||||
"pageManagement": "Page Management",
|
||||
"reportManagement": "Report Management",
|
||||
"approvalProcess": "Approval Process",
|
||||
"myTasks": "My Tasks",
|
||||
"initiatedProcesses": "Initiate Process",
|
||||
"myPending": "My Pending",
|
||||
"myInitiated": "My Initiated",
|
||||
"ccToMe": "CC to Me",
|
||||
"messageCenter": "Message Center",
|
||||
"announcementList": "Announcement List",
|
||||
"announcementManagement": "Announcement Management",
|
||||
"messageList": "Message List",
|
||||
"dataScreen": "Data Screen",
|
||||
"screenManagement": "Screen Management",
|
||||
"aiPlatform": "AI Platform",
|
||||
"workflowOrchestration": "Workflow Orchestration",
|
||||
"workflowRunHistory": "Run History",
|
||||
"aiAgent": "AI Agent",
|
||||
"knowledgeBase": "Knowledge Base",
|
||||
"controlCenter": "Control Center",
|
||||
"applicationManagement": "App Management",
|
||||
"startChat": "Start Chat",
|
||||
"uiConfig": "UI Config"
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"selectMenu": "Please select a menu",
|
||||
"addChildMenu": "Add Sub-Menu",
|
||||
"deleteMenu": "Delete",
|
||||
"searchFailed": "Failed to search menus",
|
||||
"refreshFailed": "Failed to refresh node",
|
||||
"menuDetail": "Menu Details",
|
||||
"title": "Menu Management",
|
||||
"name": "Menu Management",
|
||||
"menuName": "Menu Name",
|
||||
"menuTitle": "Menu Title",
|
||||
"parent": "Parent Menu",
|
||||
"path": "Menu Path",
|
||||
"activePath": "Active Path",
|
||||
"activePathHelp": "The path to highlight the menu, used to solve the problem of inconsistency between routing paths and menu highlighting",
|
||||
"activePathMustExist": "The active path must be an existing menu path",
|
||||
"type": "Menu Type",
|
||||
"typeCatalog": "Catalog",
|
||||
"typeMenu": "Menu",
|
||||
"typeButton": "Button",
|
||||
"typeEmbedded": "Embedded",
|
||||
"typeLink": "Link",
|
||||
"typeOnlineForm": "Online Form",
|
||||
"typeOnlinePage": "Online Page",
|
||||
"typeOnlineReport": "Online Report",
|
||||
"reportCode": "Report Code",
|
||||
"typeAgent": "Agent",
|
||||
"formCode": "Form Code",
|
||||
"agentCode": "Agent Code",
|
||||
"pageCode": "Page Code",
|
||||
"component": "Component",
|
||||
"componentPath": "Component Path",
|
||||
"icon": "Menu Icon",
|
||||
"activeIcon": "Active Icon",
|
||||
"status": "Status",
|
||||
"authCode": "Permission Code",
|
||||
"linkSrc": "Link URL",
|
||||
"operation": "Operation",
|
||||
"advancedSettings": "Advanced Settings",
|
||||
"keepAlive": "KeepAlive Cache",
|
||||
"affixTab": "Affix Tab",
|
||||
"hideInMenu": "Hide in Menu",
|
||||
"hideChildrenInMenu": "Hide Children in Menu",
|
||||
"hideInBreadcrumb": "Hide in Breadcrumb",
|
||||
"hideInTab": "Hide in Tab",
|
||||
"noBasicLayout": "No Basic Layout (Fullscreen)",
|
||||
"badgeType": {
|
||||
"title": "Badge Type",
|
||||
"dot": "Dot",
|
||||
"normal": "Number"
|
||||
},
|
||||
"badge": "Badge Content",
|
||||
"badgeVariants": "Badge Variant",
|
||||
"order": "Order",
|
||||
"applicationId": "Application",
|
||||
"applicationIdHelp": "Select the application that the menu belongs to, leave empty for main application menu",
|
||||
"fullPathKey": "Full Path as Key",
|
||||
"fullPathKeyHelp": "When set to 'No', path parameter changes won't refresh the component (for pages with dynamic parameters)",
|
||||
"isSystem": "System Menu",
|
||||
"isSystemHelp": "System menus are visible in all applications (main and sub-applications)",
|
||||
"appMenu": "App Menu",
|
||||
"systemMenu": "System Menu"
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
{
|
||||
"title": "Message Center",
|
||||
"type": "Type",
|
||||
"msgTitle": "Title",
|
||||
"content": "Content",
|
||||
"time": "Time",
|
||||
"actions": "Actions",
|
||||
"status": "Status",
|
||||
"typeMap": {
|
||||
"all": "All",
|
||||
"system": "System Notification",
|
||||
"workflow": "Workflow",
|
||||
"todo": "To-do",
|
||||
"announcement": "Announcement"
|
||||
},
|
||||
"statusMap": {
|
||||
"all": "All",
|
||||
"unread": "Unread",
|
||||
"read": "Read"
|
||||
},
|
||||
"unreadCount": "{count} unread",
|
||||
"markAllRead": "Mark All as Read",
|
||||
"clearRead": "Clear Read",
|
||||
"markRead": "Mark as Read",
|
||||
"delete": "Delete",
|
||||
"markReadSuccess": "Marked as read",
|
||||
"markAllReadSuccess": "All marked as read",
|
||||
"markReadFailed": "Failed to mark as read",
|
||||
"markAllReadConfirm": "Are you sure you want to mark all messages as read?",
|
||||
"markAllReadConfirmTitle": "Mark All Read",
|
||||
"deleteConfirm": "Are you sure you want to delete this message?",
|
||||
"deleteConfirmTitle": "Delete Confirmation",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"clearReadConfirm": "Are you sure you want to clear all read messages?",
|
||||
"clearReadConfirmTitle": "Clear Confirmation",
|
||||
"clearReadSuccess": "Cleared successfully",
|
||||
"loadUnreadFailed": "Failed to load unread count",
|
||||
"keywordPlaceholder": "Search messages",
|
||||
"selectHint": "Select a message to view",
|
||||
"detailTitle": "Message Detail",
|
||||
"emptyList": "No messages",
|
||||
"loadingMore": "Loading...",
|
||||
"noMore": "No more",
|
||||
"noContent": "No content",
|
||||
"noData": "No data",
|
||||
"sender": "Sender: ",
|
||||
"send": {
|
||||
"button": "Send Message",
|
||||
"title": "Send Message",
|
||||
"recipient": "Recipient",
|
||||
"recipientPlaceholder": "Select recipients",
|
||||
"msgTitle": "Title",
|
||||
"msgTitlePlaceholder": "Enter message title",
|
||||
"msgType": "Type",
|
||||
"content": "Content",
|
||||
"contentPlaceholder": "Enter message content",
|
||||
"channels": "Channels",
|
||||
"channelSite": "Site Message",
|
||||
"channelEmail": "Email",
|
||||
"channelDingtalk": "DingTalk",
|
||||
"channelFeishu": "Feishu",
|
||||
"channelWechat": "WeCom",
|
||||
"channelWechatMp": "WeChat MP",
|
||||
"channelDingtalkTodo": "DingTalk Todo",
|
||||
"recipientRequired": "Please select recipients",
|
||||
"titleRequired": "Please enter message title",
|
||||
"contentRequired": "Please enter message content",
|
||||
"success": "Message sent successfully",
|
||||
"failed": "Failed to send message"
|
||||
},
|
||||
"drawer": {
|
||||
"title": "Message Center",
|
||||
"messageTab": "Messages",
|
||||
"announcementTab": "Announcements",
|
||||
"markAllRead": "Mark All Read",
|
||||
"clearRead": "Clear Read",
|
||||
"noMessages": "No messages",
|
||||
"noAnnouncements": "No announcements",
|
||||
"pinned": "Pinned",
|
||||
"urgent": "Urgent",
|
||||
"important": "Important",
|
||||
"justNow": "Just now",
|
||||
"minutesAgo": "{count} min ago",
|
||||
"hoursAgo": "{count} hr ago",
|
||||
"daysAgo": "{count} days ago",
|
||||
"chatTab": "Chat",
|
||||
"noChats": "No unread chats",
|
||||
"unreadMessages": "{count} unread"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"orgChart": {
|
||||
"title": "Organization Chart",
|
||||
"description": "Click on a node to expand or collapse subordinates",
|
||||
"empty": "No organization data available",
|
||||
"focusMode": "Switch to Focus Mode",
|
||||
"expandMode": "Switch to Expand Mode",
|
||||
"showAll": "Show all siblings"
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
{
|
||||
"applicationName": "Application",
|
||||
"name": "Page Name",
|
||||
"code": "Page Code",
|
||||
"status": "Status",
|
||||
"description": "Description",
|
||||
"createTime": "Create Time",
|
||||
"updateTime": "Update Time",
|
||||
"actions": "Actions",
|
||||
"placeholder": {
|
||||
"name": "Please enter page name",
|
||||
"remark": "Please enter form description",
|
||||
"category": "Please select category",
|
||||
"code": "Please enter page code",
|
||||
"status": "Please select status",
|
||||
"description": "Please enter page description"
|
||||
},
|
||||
"categoryMap": {
|
||||
"dashboard": "Dashboard",
|
||||
"portal": "Portal Page",
|
||||
"databoard": "Data Board",
|
||||
"other": "Other"
|
||||
},
|
||||
"statusMap": {
|
||||
"all": "All",
|
||||
"published": "Published",
|
||||
"draft": "Draft"
|
||||
},
|
||||
"create": "New Page",
|
||||
"batchDelete": "Batch Delete",
|
||||
"batchDeleteWithCount": "Batch Delete ({count})",
|
||||
"deleteConfirm": "Are you sure you want to delete page \"{name}\"?",
|
||||
"deleteConfirmTitle": "Delete Confirmation",
|
||||
"deleteSuccess": "Page deleted: {name}",
|
||||
"batchDeleteConfirm": "Are you sure you want to delete {count} selected pages?",
|
||||
"batchDeleteConfirmTitle": "Batch Delete Confirmation",
|
||||
"batchDeleteSuccess": "{count} pages deleted",
|
||||
"unpublishSuccess": "Page \"{name}\" unpublished",
|
||||
"unpublishFailed": "Unpublish failed",
|
||||
"copyCodePlaceholder": "Please enter new page code",
|
||||
"copyTitle": "Copy Page",
|
||||
"copyCodeRule": "Code can only contain letters, numbers, underscores, and dashes",
|
||||
"codeFormatError": "Code must start with a letter and contain only letters, numbers and underscores",
|
||||
"copySuccess": "Copied successfully",
|
||||
"design": "Design",
|
||||
"editInfo": "Edit Info",
|
||||
"saveSuccess": "Page saved successfully",
|
||||
"preview": "Preview",
|
||||
"publish": "Publish",
|
||||
"unpublish": "Unpublish",
|
||||
"copy": "Copy",
|
||||
"setAsHome": "Set as Home",
|
||||
"setAsHomeTitle": "Set as Default Home",
|
||||
"setAsHomeConfirm": "Are you sure you want to set page \"{name}\" as the default home page?",
|
||||
"setAsHomePath": "Home Path",
|
||||
"setAsHomeTip": "After setting, users will be redirected to this page by default after login.",
|
||||
"setAsHomeAppTip": "This page belongs to app \"{app}\". The home page config of that app will be updated.",
|
||||
"setHomeSuccess": "Set as default home page successfully",
|
||||
"setHomeFailed": "Failed to set as home page",
|
||||
"more": "More",
|
||||
"category": "Page Category",
|
||||
"editor": {
|
||||
"title": "Online Development",
|
||||
"create": "Create Page",
|
||||
"edit": "Edit Page",
|
||||
"createSuccess": "Created successfully",
|
||||
"steps": {
|
||||
"basic": "Basic Info",
|
||||
"design": "Page Design",
|
||||
"preview": "Page Preview"
|
||||
},
|
||||
"loadFailed": "Failed to load page data",
|
||||
"validate": {
|
||||
"basic": "Validation failed: Name and Code are required",
|
||||
"perfectDesign": "Please complete page design"
|
||||
},
|
||||
"autoSave": {
|
||||
"saving": "Saving...",
|
||||
"saved": "Saved",
|
||||
"unsaved": "Unsaved"
|
||||
}
|
||||
},
|
||||
"previewDialog": {
|
||||
"title": "Page Preview",
|
||||
"loadFailed": "Failed to load page configuration",
|
||||
"verifySuccess": "Validation passed",
|
||||
"realtimeData": "Realtime Data (v-model):",
|
||||
"noConfig": "No page configuration",
|
||||
"close": "Close",
|
||||
"verifySubmit": "Verify Submit",
|
||||
"verifyFailed": "Page validation failed"
|
||||
},
|
||||
"importExport": {
|
||||
"export": "Export Config",
|
||||
"import": "Import Config",
|
||||
"exportSuccess": "Page config exported",
|
||||
"exportFailed": "Export failed",
|
||||
"importTitle": "Import Page Config",
|
||||
"dragOrClick": "Drag JSON file here or click to upload",
|
||||
"onlyJson": "Only .json files are supported",
|
||||
"fileParseError": "Failed to parse file. Please check the format",
|
||||
"checking": "Checking...",
|
||||
"codeConflictTip": "Page code already exists. Enter a new code to import",
|
||||
"codeAvailable": "Page code is available",
|
||||
"newCodePlaceholder": "Enter a new page code",
|
||||
"importSuccess": "Page config imported successfully",
|
||||
"importFailed": "Import failed",
|
||||
"confirmImport": "Confirm Import",
|
||||
"reselect": "Reselect",
|
||||
"pageInfo": "Page Info",
|
||||
"appTip": "Imported page will belong to the current application"
|
||||
},
|
||||
"publishDialog": {
|
||||
"title": "Page Publish",
|
||||
"success": "Published successfully",
|
||||
"failed": "Publish failed",
|
||||
"menuConfig": "Menu Config",
|
||||
"menuName": "Menu Name",
|
||||
"parentMenu": "Parent Menu",
|
||||
"parentMenuPlaceholder": "Please select parent menu (top level if empty)",
|
||||
"menuIcon": "Menu Icon",
|
||||
"routeInfo": "Route Info",
|
||||
"accessPath": "Access Path",
|
||||
"confirmPublish": "Confirm Publish"
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"codeLogin": "Code Login",
|
||||
"qrcodeLogin": "Qr Code Login",
|
||||
"forgetPassword": "Forget Password"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"analytics": "Analytics",
|
||||
"workspace": "Workspace"
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"name": "Permission",
|
||||
"title": "Permission Management",
|
||||
"permissionName": "Permission Name",
|
||||
"permissionCode": "Permission Code",
|
||||
"permissionType": "Permission Type",
|
||||
"httpMethod": "HTTP Method",
|
||||
"apiPath": "API Path",
|
||||
"dataScope": "Data Scope",
|
||||
"dataScopes": {
|
||||
"all": "All Data",
|
||||
"self": "Self Only",
|
||||
"dept": "Department",
|
||||
"deptAndSub": "Dept & Subordinates",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"description": "Description",
|
||||
"operation": "Operation",
|
||||
"add": "Add Permission",
|
||||
"edit": "Edit Permission",
|
||||
"selectMenuFirst": "Please select a menu first",
|
||||
"searchMenu": "Search Menu",
|
||||
"loadMenuFailed": "Failed to load menu",
|
||||
"loadSubMenuFailed": "Failed to load sub-menu",
|
||||
"searchMenuFailed": "Failed to search menu",
|
||||
"deleteConfirm": "Are you sure you want to delete permission \"{0}\"?",
|
||||
"batchDelete": "Batch Delete",
|
||||
"batchDeleteConfirm": "Are you sure you want to delete {0} permissions?\n{1}",
|
||||
"batchDeleteSuccess": "Successfully deleted {0} permissions",
|
||||
"batchDeleteFailed": "Batch delete failed",
|
||||
"selectToDelete": "Please select permissions to delete",
|
||||
"selectAtLeastOneRoute": "Please select at least one route",
|
||||
"createSuccess": "Successfully created {created} permissions{skipped}",
|
||||
"skipped": ", skipped {count}",
|
||||
"createFailed": "{failed} permissions failed to create: {errors}",
|
||||
"createError": "Failed to create permissions",
|
||||
"getRoutesFailed": "Failed to get routes list",
|
||||
"autoGenerateApi": "Auto Generate API Permissions",
|
||||
"quickAddApiPermission": "Quick Add API Permission",
|
||||
"permissionTypes": {
|
||||
"button": "Button Permission",
|
||||
"api": "API Permission",
|
||||
"data": "Data Permission",
|
||||
"other": "Other Permission",
|
||||
"buttonDesc": "Used to control the display and hiding of buttons, menu items, and other elements on the page",
|
||||
"apiDesc": "Used to control API interfaces that users can access",
|
||||
"dataDesc": "Used to control the data range that users can access",
|
||||
"otherDesc": "Other types of permissions"
|
||||
},
|
||||
"typeLabels": {
|
||||
"button": "Button",
|
||||
"api": "API",
|
||||
"data": "Data",
|
||||
"other": "Other",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"helpText": {
|
||||
"code": "Permission code should follow the \"module:action\" format, using letters, numbers, underscores and colons",
|
||||
"apiPath": "Complete API path, e.g. /api/user/create or /api/user/:id/update",
|
||||
"httpMethod": "The HTTP request method corresponding to this permission"
|
||||
},
|
||||
"placeholder": {
|
||||
"name": "e.g.: Create User, View Report, etc.",
|
||||
"code": "e.g.: user:create, report:view, etc.",
|
||||
"apiPath": "/api/user/create"
|
||||
},
|
||||
"validationErrors": {
|
||||
"nameRequired": "Permission name is required",
|
||||
"nameMaxLength": "Permission name can be at most 64 characters",
|
||||
"codeRequired": "Permission code is required",
|
||||
"codeMaxLength": "Permission code can be at most 64 characters",
|
||||
"codeFormat": "Permission code can only contain letters, numbers, underscores and colons"
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "Post",
|
||||
"title": "Post Management",
|
||||
"postName": "Post Name",
|
||||
"postCode": "Post Code",
|
||||
"postType": "Post Type",
|
||||
"postLevel": "Post Level",
|
||||
"department": "Department",
|
||||
"description": "Post Description",
|
||||
"status": "Status",
|
||||
"operation": "Operation",
|
||||
"edit": "Edit",
|
||||
"codeFormatError": "Post code can only contain letters, numbers, underscores and hyphens",
|
||||
"selectDepartment": "Please select department",
|
||||
"descriptionPlaceholder": "Please enter post description/responsibilities",
|
||||
"selectUsersFirst": "Please select users first",
|
||||
"addUsersSuccess": "Added successfully",
|
||||
"removeUsersConfirm": "Are you sure you want to delete {0} selected users?",
|
||||
"removeUsersSuccess": "Deleted successfully",
|
||||
"removeUsersFailed": "Delete failed",
|
||||
"types": {
|
||||
"management": "Management",
|
||||
"technical": "Technical",
|
||||
"business": "Business",
|
||||
"functional": "Functional",
|
||||
"other": "Other"
|
||||
},
|
||||
"levels": {
|
||||
"senior": "Senior",
|
||||
"middle": "Middle",
|
||||
"basic": "Basic",
|
||||
"staff": "Staff"
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"databases": "Database List",
|
||||
"database": "Database",
|
||||
"allTypes": "All Types",
|
||||
"refresh": "Refresh",
|
||||
"expires": "Expires",
|
||||
"avgTTL": "Avg TTL",
|
||||
"keyList": "Key List",
|
||||
"keyDetail": "Key Detail",
|
||||
"keysCount": "{count} Keys",
|
||||
"searchKeyPlaceholder": "Search keys (supports * and ?)",
|
||||
"type": "Type",
|
||||
"addKey": "Add Key",
|
||||
"editKey": "Edit Key",
|
||||
"renameKey": "Rename Key",
|
||||
"deleteKey": "Delete Key",
|
||||
"keyName": "Key Name",
|
||||
"value": "Value",
|
||||
"ttl": "Expiration",
|
||||
"size": "Size",
|
||||
"encoding": "Encoding",
|
||||
"bytes": "Bytes",
|
||||
"loading": "Loading...",
|
||||
"selectKeyPrompt": "Please select a key from the left",
|
||||
"keyNotExist": "Key does not exist or has expired",
|
||||
"copy": "Copy",
|
||||
"copySuccess": "Copied to clipboard",
|
||||
"deleteConfirm": "Are you sure you want to delete key \"{key}\"?",
|
||||
"confirmDelete": "Confirm Delete",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"deleteFailed": "Delete failed",
|
||||
"renamePrompt": "Please enter a new key name",
|
||||
"renameSuccess": "Renamed successfully",
|
||||
"renameFailed": "Rename failed",
|
||||
"setExpirePrompt": "Please enter expiration time (seconds), -1 for permanent",
|
||||
"setExpire": "Set Expiration",
|
||||
"setSuccess": "Set successfully",
|
||||
"setFailed": "Set failed",
|
||||
"createSuccess": "Created successfully",
|
||||
"createFailed": "Create failed",
|
||||
"updateSuccess": "Updated successfully",
|
||||
"updateFailed": "Update failed",
|
||||
"keyRequired": "Key name is required",
|
||||
"typeRequired": "Please select a type",
|
||||
"valueRequired": "Please enter a value",
|
||||
"invalidNumber": "Please enter a valid number",
|
||||
"permanent": "Permanent",
|
||||
"expired": "Expired",
|
||||
"seconds": "s",
|
||||
"minutes": "mins",
|
||||
"hours": "hrs",
|
||||
"days": "days",
|
||||
"listItem": "List Item",
|
||||
"setMember": "Set Member",
|
||||
"zsetMember": "ZSet",
|
||||
"hashField": "Hash Field",
|
||||
"add": "Add",
|
||||
"addItem": "Add Item",
|
||||
"addMember": "Add Member",
|
||||
"addField": "Add Field",
|
||||
"fieldName": "Field Name",
|
||||
"fieldValue": "Field Value",
|
||||
"member": "Member",
|
||||
"score": "Score",
|
||||
"ttlDesc": "-1 for permanent",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"create": "Create",
|
||||
"update": "Update",
|
||||
"loadDatabasesFailed": "Failed to load database list",
|
||||
"loadKeyDetailFailed": "Failed to load key detail",
|
||||
"searchKeysFailed": "Failed to search keys"
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
{
|
||||
"overview": "Overview Info",
|
||||
"memory": "Memory Info",
|
||||
"clients": "Clients",
|
||||
"keyspace": "Keyspace",
|
||||
"stats": "Stats Info",
|
||||
"slowlog": "Slow Log",
|
||||
"loadFailed": "Failed to load monitor data",
|
||||
"loadRealtimeStatsFailed": "Failed to load realtime stats",
|
||||
"refreshSuccess": "Refresh successful",
|
||||
"autoRefreshing": "Auto-refreshing",
|
||||
"paused": "Paused",
|
||||
"featureDeveloping": "Developing...",
|
||||
"seconds": "s",
|
||||
"minutes": "mins",
|
||||
"hours": "hrs",
|
||||
"days": "days",
|
||||
"master": "Master",
|
||||
"slave": "Slave",
|
||||
"blocked": "Blocked",
|
||||
"normal": "Normal",
|
||||
"active": "Active",
|
||||
"connectedClients": "Connected Clients",
|
||||
"blockedClients": "Blocked Clients",
|
||||
"totalConnections": "Total Connections",
|
||||
"clientList": "Client List",
|
||||
"totalClientsCount": "Total {count} clients",
|
||||
"noClientConnections": "No client connections",
|
||||
"clientId": "Client ID",
|
||||
"address": "Address",
|
||||
"name": "Name",
|
||||
"database": "Database",
|
||||
"status": "Status",
|
||||
"age": "Age",
|
||||
"idle": "Idle",
|
||||
"outputBuffer": "Output Buffer",
|
||||
"lastCommand": "Last Command",
|
||||
"used": "Used",
|
||||
"queue": "Queue",
|
||||
"fieldDescription": "Field Description",
|
||||
"clientIdDesc": "Unique client identifier assigned by Redis",
|
||||
"addressDesc": "IP address and port of the client",
|
||||
"nameDesc": "Client name set via CLIENT SETNAME",
|
||||
"ageDesc": "Total duration of the client connection",
|
||||
"idleDesc": "Duration the client has been idle (no commands sent)",
|
||||
"outputBufferDesc": "Memory used by output buffer and queue length",
|
||||
"permanent": "Permanent",
|
||||
"milliseconds": "ms",
|
||||
"totalKeys": "Total Keys",
|
||||
"expiresKeys": "Expires Keys",
|
||||
"proportion": "Proportion",
|
||||
"avgTTL": "Avg TTL",
|
||||
"databaseList": "Database List",
|
||||
"totalDatabasesCount": "Total {count} databases",
|
||||
"noDatabaseInfo": "No database info",
|
||||
"keyCount": "Key Count",
|
||||
"expires": "Expires",
|
||||
"expireRate": "Expire Rate",
|
||||
"noExpireTime": "No expire time",
|
||||
"keyCountDesc": "Total number of keys stored in the database",
|
||||
"expiresDesc": "Number of keys with an expiration set",
|
||||
"avgTTLDesc": "Average time to live for all expiring keys (ms)",
|
||||
"expireRateDesc": "Percentage of keys that have an expiration set",
|
||||
"dbId": "DB ID",
|
||||
"dbIdDesc": "Redis database index (0-15)",
|
||||
"usageRateDesc": "Ratio of database keys relative to maximum capacity",
|
||||
"insufficientMemory": "Insufficient Memory",
|
||||
"moreFragmentation": "High Fragmentation",
|
||||
"memoryUsage": "Memory Usage",
|
||||
"memoryPeak": "Memory Peak",
|
||||
"memoryPeakDesc": "Historical peak memory usage",
|
||||
"fragmentationRatio": "Mem Fragmentation Ratio",
|
||||
"memoryUsageDetail": "Memory Usage Detail",
|
||||
"usedMemory": "Used Memory",
|
||||
"rssMemory": "Used Memory RSS",
|
||||
"physicalMemory": "Physical Memory",
|
||||
"totalSystemMemory": "Total System Memory",
|
||||
"system": "System",
|
||||
"datasetMemory": "Used Memory Dataset",
|
||||
"allocatorAllocated": "Allocator Allocated",
|
||||
"allocatorActive": "Allocator Active",
|
||||
"memoryPolicyConfig": "Memory Policy Config",
|
||||
"maxMemoryLimit": "Max Memory Limit",
|
||||
"noLimit": "No Limit",
|
||||
"set": "Set",
|
||||
"notLimited": "Not Limited",
|
||||
"evictionPolicy": "Eviction Policy",
|
||||
"evictionPolicyDesc": "💡 Eviction Policy Description",
|
||||
"noevictionDesc": "noeviction: return errors when memory limit is reached",
|
||||
"allkeysLruDesc": "allkeys-lru: evict any key using approximated LRU",
|
||||
"volatileLruDesc": "volatile-lru: evict keys with an expire set using approximated LRU",
|
||||
"allkeysRandomDesc": "allkeys-random: evict any key randomly",
|
||||
"volatileRandomDesc": "volatile-random: evict keys with an expire set randomly",
|
||||
"volatileTtlDesc": "volatile-ttl: evict keys with an expire set and shortest TTL",
|
||||
"memoryUsageTrend": "Memory Usage Trend",
|
||||
"recentDataPoints": "Recent {count} data points",
|
||||
"waitingForData": "Waiting for data...",
|
||||
"collectingMemoryData": "Collecting memory usage data...",
|
||||
"currentUsage": "Current Usage",
|
||||
"average": "Average",
|
||||
"highest": "Highest",
|
||||
"currentConnections": "Current Connections",
|
||||
"opsPerSec": "Ops Per Sec",
|
||||
"hitRate": "Hit Rate",
|
||||
"redisBasicInfo": "Redis Basic Info",
|
||||
"redisVersion": "Redis Version",
|
||||
"redisMode": "Redis Mode",
|
||||
"role": "Role",
|
||||
"architecture": "Architecture",
|
||||
"bits": "bits",
|
||||
"tcpPort": "TCP Port",
|
||||
"uptime": "Uptime",
|
||||
"uptimeInDays": "Uptime In Days",
|
||||
"connectionStatus": "Connection Status",
|
||||
"connected": "Connected",
|
||||
"disconnected": "Disconnected",
|
||||
"memoryInfo": "Memory Info",
|
||||
"memoryPolicy": "Memory Policy",
|
||||
"connectionStats": "Connection Stats",
|
||||
"rejectedConnections": "Rejected Connections",
|
||||
"commandStats": "Command Stats",
|
||||
"totalCommands": "Total Commands",
|
||||
"keyspaceHits": "Keyspace Hits",
|
||||
"keyspaceMisses": "Keyspace Misses",
|
||||
"keyspaceInfo": "Keyspace Info",
|
||||
"keys": "Keys",
|
||||
"networkInput": "Network Input",
|
||||
"totalNetInput": "Total Net Input Bytes",
|
||||
"inputKbps": "Instantaneous Input Rate",
|
||||
"networkOutput": "Network Output",
|
||||
"totalNetOutput": "Total Net Output Bytes",
|
||||
"outputKbps": "Instantaneous Output Rate",
|
||||
"totalSlowLogs": "Total Slow Logs",
|
||||
"avgDuration": "Avg Duration",
|
||||
"maxDuration": "Max Duration",
|
||||
"slowLogList": "Slow Log List",
|
||||
"recentLogsCount": "Recent {count} logs",
|
||||
"noSlowLogs": "No slow logs found",
|
||||
"command": "Command",
|
||||
"clientInfo": "Client Info",
|
||||
"unnamed": "Unnamed",
|
||||
"durationProportion": "Duration Proportion",
|
||||
"relativeToMax": "Relative To Max",
|
||||
"slowLogDesc": "Slow Log Description",
|
||||
"slowLogThreshold": "Slow Log Threshold",
|
||||
"slowLogThresholdDesc": "Commands exceeding the threshold are logged",
|
||||
"executionTime": "Execution Time",
|
||||
"executionTimeDesc": "Total time taken to execute the command (μs)",
|
||||
"clientInfoDesc": "IP and name of the client that executed the command",
|
||||
"performanceOptimization": "Performance Optimization",
|
||||
"performanceOptimizationDesc": "Analyzing slow logs helps optimize Redis performance",
|
||||
"timeUnit": "Time Unit",
|
||||
"colorIndicator": "Color Indicator",
|
||||
"excellent": "Excellent",
|
||||
"good": "Good",
|
||||
"lower": "Lower",
|
||||
"veryLow": "Very Low",
|
||||
"keyPerformanceIndicators": "Key Performance Indicators",
|
||||
"cacheHitRate": "Cache Hit Rate",
|
||||
"detailedStats": "Detailed Statistics",
|
||||
"totalInputTraffic": "Total Input Traffic",
|
||||
"totalOutputTraffic": "Total Output Traffic",
|
||||
"instantaneousInputRate": "Instantaneous Input Rate",
|
||||
"instantaneousOutputRate": "Instantaneous Output Rate",
|
||||
"keyOperationStats": "Key Operation Stats",
|
||||
"evictedKeys": "Evicted Keys",
|
||||
"syncStats": "Sync Stats",
|
||||
"syncFull": "Sync Full",
|
||||
"syncPartialOk": "Sync Partial OK",
|
||||
"syncPartialErr": "Sync Partial Err",
|
||||
"pubsubChannels": "Pubsub Channels",
|
||||
"pubsubPatterns": "Pubsub Patterns",
|
||||
"latestForkUsec": "Latest Fork Usec",
|
||||
"indicatorDesc": "Indicator Description",
|
||||
"opsPerSecDesc": "Commands processed per second",
|
||||
"hitRateDesc": "Keyspace hits as a percentage of total lookups",
|
||||
"evictedKeysDesc": "Keys evicted due to memory limit",
|
||||
"rejectedConnectionsDesc": "Connections rejected due to maxclients limit",
|
||||
"syncFullDesc": "Full resynchronizations with replicas",
|
||||
"latestForkUsecDesc": "Duration of the latest fork operation (μs)"
|
||||
}
|
||||
@@ -1,437 +0,0 @@
|
||||
{
|
||||
"name": "Report Name",
|
||||
"reportCode": "Report Code",
|
||||
"category": "Category",
|
||||
"status": "Status",
|
||||
"description": "Description",
|
||||
"createTime": "Created At",
|
||||
"actions": "Actions",
|
||||
"create": "New Report",
|
||||
"design": "Design",
|
||||
"editInfo": "Edit Info",
|
||||
"deleteConfirm": "Delete report \"{name}\"?",
|
||||
"deleteConfirmTitle": "Confirm Delete",
|
||||
"deleteSuccess": "Deleted: {name}",
|
||||
"saveSuccess": "Report saved",
|
||||
"copyTitle": "Copy Report",
|
||||
"copyCodePlaceholder": "Enter new report code",
|
||||
"copySuccess": "Copied",
|
||||
"statusMap": {
|
||||
"draft": "Draft",
|
||||
"published": "Published"
|
||||
},
|
||||
"placeholder": {
|
||||
"name": "Report name",
|
||||
"code": "Report code",
|
||||
"category": "Category",
|
||||
"description": "Description"
|
||||
},
|
||||
"editor": {
|
||||
"create": "New Report",
|
||||
"edit": "Edit Report",
|
||||
"createSuccess": "Report created",
|
||||
"loadFailed": "Failed to load report",
|
||||
"back": "Back",
|
||||
"save": "Save",
|
||||
"publish": "Publish",
|
||||
"publishSuccess": "Published. A new draft version was created.",
|
||||
"copyVersion": "Copy Version",
|
||||
"copyVersionSuccess": "Copied as a new design version",
|
||||
"copyVersionFailed": "Failed to copy version"
|
||||
},
|
||||
"leftPanel": {
|
||||
"dataSource": "Data Source",
|
||||
"reportProperties": "Report Settings",
|
||||
"clickToConfigure": "Click to edit",
|
||||
"configuredCount": "{count} configured",
|
||||
"queryEmpty": "No query conditions yet",
|
||||
"sortEmpty": "No sort rules yet",
|
||||
"columnEmpty": "Column layout not enabled",
|
||||
"convertEmpty": "No data transforms yet",
|
||||
"columnEnabled": "Column layout enabled"
|
||||
},
|
||||
"dataset": {
|
||||
"title": "Datasets",
|
||||
"selectSource": "Select data source",
|
||||
"empty": "No datasets. Add one above.",
|
||||
"fieldMapping": "Field mapping",
|
||||
"loadingFields": "Loading fields...",
|
||||
"loadFieldsFailed": "Failed to load fields",
|
||||
"noFields": "No fields"
|
||||
},
|
||||
"column": {
|
||||
"config": "Column layout",
|
||||
"title": "Column layout settings",
|
||||
"enable": "Enable column layout",
|
||||
"style": "Layout style",
|
||||
"styleCol": "Row-based columns",
|
||||
"styleRow": "Column-based rows",
|
||||
"type": "Layout type",
|
||||
"overRows": "After",
|
||||
"splitCols": "rows, split to columns",
|
||||
"overCols": "After",
|
||||
"splitRows": "columns, split to rows",
|
||||
"splitInto": "Split into",
|
||||
"colsUnit": "columns",
|
||||
"rowsUnit": "rows",
|
||||
"dataRange": "Data range",
|
||||
"dataRangePlaceholder": "e.g. A2:D10",
|
||||
"copyColNo": "Copy row numbers",
|
||||
"copyRowNo": "Copy column numbers",
|
||||
"rangeHint": "e.g. 1,2-3,6",
|
||||
"fillEmpty": "Fill empty rows",
|
||||
"previewNote": "Preview: row-split→N columns, or column-split→N rows. Set range (e.g. A2:D10) and count ≥ 2."
|
||||
},
|
||||
"sort": {
|
||||
"config": "Sort settings",
|
||||
"title": "Dataset sorting",
|
||||
"hint": "Sort by dataset alias.field (e.g. sales.amount)",
|
||||
"add": "Add rule",
|
||||
"field": "Sort field",
|
||||
"fieldPlaceholder": "e.g. sales.createTime",
|
||||
"order": "Order",
|
||||
"asc": "Ascending",
|
||||
"desc": "Descending",
|
||||
"datasetHint": "Configured datasets"
|
||||
},
|
||||
"query": {
|
||||
"title": "Query Parameters",
|
||||
"config": "Configure queries",
|
||||
"add": "Add field",
|
||||
"field": "Field",
|
||||
"fieldPlaceholder": "Select or enter parameter field",
|
||||
"label": "Label",
|
||||
"component": "Component",
|
||||
"defaultValue": "Default",
|
||||
"required": "This field is required",
|
||||
"requiredLabel": "Required",
|
||||
"input": "Input",
|
||||
"select": "Select",
|
||||
"date": "Date",
|
||||
"dateRange": "Date range",
|
||||
"showTime": "With time",
|
||||
"options": "Options",
|
||||
"optionsPlaceholder": "Label:value,Label2:value2",
|
||||
"search": "Search",
|
||||
"reset": "Reset",
|
||||
"startDate": "Start date",
|
||||
"endDate": "End date"
|
||||
},
|
||||
"convert": {
|
||||
"config": "Data transform",
|
||||
"title": "Transform rules",
|
||||
"hint": "Field format: datasetAlias.fieldName",
|
||||
"add": "Add rule",
|
||||
"field": "Field",
|
||||
"fieldPlaceholder": "e.g. order.status",
|
||||
"type": "Type",
|
||||
"configCol": "Settings",
|
||||
"extraTitle": "Transform settings",
|
||||
"addOption": "Add option",
|
||||
"option": "Option",
|
||||
"optionId": "Value",
|
||||
"optionLabel": "Label",
|
||||
"dateFormat": "Date format",
|
||||
"precision": "Decimal places",
|
||||
"thousands": "Thousands separator",
|
||||
"types": {
|
||||
"select": "Select",
|
||||
"date": "Date",
|
||||
"number": "Number",
|
||||
"user": "User",
|
||||
"department": "Department",
|
||||
"organize": "Organization",
|
||||
"role": "Role",
|
||||
"dictionary": "Dictionary"
|
||||
},
|
||||
"namesMap": "Name mapping",
|
||||
"namesPlaceholder": "ID:Label,ID2:Label2",
|
||||
"dictionaryType": "Dictionary type",
|
||||
"dictionaryTypePlaceholder": "Dictionary code"
|
||||
},
|
||||
"preview": {
|
||||
"title": "Preview",
|
||||
"search": "Search",
|
||||
"expressionCycle": "Circular references detected between expression cells; results may be incorrect",
|
||||
"snapshotLarge": "Preview contains a large number of cells and may be slow",
|
||||
"datasetRowWarn": "Dataset \"{alias}\" has many rows; preview may be slow",
|
||||
"datasetRowLimit": "Dataset \"{alias}\" reached the row limit; results may be truncated"
|
||||
},
|
||||
"importExport": {
|
||||
"export": "Export config",
|
||||
"import": "Import config",
|
||||
"exportSuccess": "Report config exported",
|
||||
"exportFailed": "Export failed",
|
||||
"importTitle": "Import report config",
|
||||
"dragOrClick": "Drag JSON here or click to upload",
|
||||
"onlyJson": "Only .json files",
|
||||
"fileParseError": "Failed to parse file",
|
||||
"checking": "Checking...",
|
||||
"codeConflictTip": "Report code exists, enter a new code",
|
||||
"codeAvailable": "Code is available",
|
||||
"newCodePlaceholder": "New report code",
|
||||
"importSuccess": "Report imported",
|
||||
"importFailed": "Import failed",
|
||||
"confirmImport": "Import",
|
||||
"reselect": "Reselect file",
|
||||
"reportInfo": "Report info",
|
||||
"appTip": "Report will belong to the current app; datasets match by data source code"
|
||||
},
|
||||
"publishMenu": "Publish to menu",
|
||||
"updateMenu": "Update menu",
|
||||
"unpublishMenu": "Unpublish menu",
|
||||
"unpublishSuccess": "Unpublished: {name}",
|
||||
"unpublishFailed": "Failed to unpublish",
|
||||
"publishDialog": {
|
||||
"title": "Publish report to menu",
|
||||
"success": "Published to menu",
|
||||
"failed": "Publish failed",
|
||||
"confirm": "Publish"
|
||||
},
|
||||
"print": {
|
||||
"title": "Print",
|
||||
"notAllowed": "Printing is not allowed for this report",
|
||||
"browserPrint": "Print",
|
||||
"previewTitle": "Print preview",
|
||||
"imageWarn": "This report contains {count} images. Printing may be slow. Continue?",
|
||||
"failed": "Failed to open print preview. Please try again."
|
||||
},
|
||||
"printForm": {
|
||||
"printArea": "Print area",
|
||||
"paperType": "Paper",
|
||||
"padding": "Margins",
|
||||
"direction": "Orientation",
|
||||
"scale": "Scale",
|
||||
"hAlign": "Horizontal align",
|
||||
"vAlign": "Vertical align",
|
||||
"gridlines": "Gridlines",
|
||||
"workbookTitle": "Workbook title",
|
||||
"worksheetTitle": "Sheet title",
|
||||
"printDate": "Date",
|
||||
"printTime": "Time",
|
||||
"pageNumber": "Page number",
|
||||
"yFreeze": "Repeat frozen rows",
|
||||
"xFreeze": "Repeat frozen columns",
|
||||
"options": {
|
||||
"currentSheet": "Current sheet",
|
||||
"portrait": "Portrait",
|
||||
"landscape": "Landscape",
|
||||
"a4": "A4",
|
||||
"a3": "A3",
|
||||
"a5": "A5",
|
||||
"b4": "B4",
|
||||
"b5": "B5",
|
||||
"executive": "Executive",
|
||||
"statement": "Statement",
|
||||
"letter": "Letter",
|
||||
"origin": "Original scale",
|
||||
"fitWidth": "Fit width",
|
||||
"fitHeight": "Fit height",
|
||||
"fitPage": "Fit page",
|
||||
"normal": "Normal",
|
||||
"narrow": "Narrow margin",
|
||||
"wide": "Wide margin",
|
||||
"hAlign": {
|
||||
"start": "Left",
|
||||
"middle": "Center",
|
||||
"end": "Right"
|
||||
},
|
||||
"vAlign": {
|
||||
"start": "Top",
|
||||
"middle": "Middle",
|
||||
"end": "Bottom"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"empty": "Select a floating image",
|
||||
"source": "Image source",
|
||||
"sourceLocal": "Upload",
|
||||
"sourceUrl": "URL",
|
||||
"url": "Image URL",
|
||||
"urlPlaceholder": "Must start with http:// or https://",
|
||||
"upload": "Upload image",
|
||||
"selectFile": "Choose file",
|
||||
"uploadSuccess": "Image uploaded",
|
||||
"uploadFailed": "Upload failed"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Report settings",
|
||||
"allowExport": "Allow export",
|
||||
"allowPrint": "Allow print",
|
||||
"allowWatermark": "Show watermark",
|
||||
"allowExportTip": "When enabled, Excel/PDF export is allowed in preview and runtime",
|
||||
"allowPrintTip": "When enabled, printing is allowed in preview and runtime",
|
||||
"allowWatermarkTip": "When enabled, watermark is shown in preview and runtime",
|
||||
"watermarkText": "Watermark text",
|
||||
"watermarkPlaceholder": "Watermark text",
|
||||
"watermarkShowTime": "Show time in watermark",
|
||||
"watermarkTimeFormat": "Time format",
|
||||
"loadFailed": "Failed to load report settings"
|
||||
},
|
||||
"pdfExport": {
|
||||
"title": "Export PDF",
|
||||
"success": "PDF exported",
|
||||
"failed": "PDF export failed"
|
||||
},
|
||||
"version": {
|
||||
"title": "Versions",
|
||||
"hint": "Switch, copy, or delete historical versions",
|
||||
"switch": "Switch",
|
||||
"loadFailed": "Failed to load versions",
|
||||
"deleteConfirm": "Delete version v{version}?",
|
||||
"deleteSuccess": "Version deleted",
|
||||
"deleteFailed": "Failed to delete version",
|
||||
"cannotDeleteActive": "Cannot delete the active version",
|
||||
"state": {
|
||||
"0": "Designing",
|
||||
"1": "Active",
|
||||
"2": "Archived"
|
||||
}
|
||||
},
|
||||
"fieldMapping": {
|
||||
"title": "Field mapping",
|
||||
"hint": "Map data source fields to dataset aliases",
|
||||
"add": "Add mapping",
|
||||
"sourceField": "Source field",
|
||||
"targetField": "Target field",
|
||||
"sourcePlaceholder": "Source field name",
|
||||
"targetPlaceholder": "Mapped field name"
|
||||
},
|
||||
"releaseMenu": {
|
||||
"existingTitle": "Published menu",
|
||||
"existingHint": "Current menu: {title} ({path}). Republishing will update the menu."
|
||||
},
|
||||
"export": {
|
||||
"title": "Export Excel",
|
||||
"success": "Exported",
|
||||
"failed": "Export failed",
|
||||
"notAllowed": "Export is not allowed for this report"
|
||||
},
|
||||
"chart": {
|
||||
"empty": "Select a floating chart to configure",
|
||||
"cellEmpty": "Select an in-cell chart to configure",
|
||||
"cellTitle": "In-cell chart",
|
||||
"type": "Chart type",
|
||||
"title": "Chart title",
|
||||
"datasetHint": "Dataset aliases",
|
||||
"dataSet": "Dataset",
|
||||
"classifyField": "Category field",
|
||||
"maxField": "Radar max field",
|
||||
"legendShow": "Show legend",
|
||||
"legendOrient": "Legend layout",
|
||||
"legendOrientHorizontal": "Horizontal",
|
||||
"legendOrientVertical": "Vertical",
|
||||
"legendFontSize": "Legend font size",
|
||||
"styleType": "Chart style",
|
||||
"lineArea": "Area fill",
|
||||
"pieRose": "Rose chart",
|
||||
"pieShowZero": "Hide zero values",
|
||||
"layoutSection": "Layout & colors",
|
||||
"gridTop": "Grid top",
|
||||
"gridLeft": "Grid left",
|
||||
"gridRight": "Grid right",
|
||||
"gridBottom": "Grid bottom",
|
||||
"legendLeft": "Legend left (%)",
|
||||
"legendTop": "Legend top (%)",
|
||||
"colorList": "Series colors",
|
||||
"addColor": "Add color",
|
||||
"colorListPlaceholder": "e.g. #5470c6,#91cc75,#fac858",
|
||||
"seriesCenterLeft": "Pie center X (%)",
|
||||
"seriesCenterTop": "Pie center Y (%)",
|
||||
"seriesNameField": "Series name field",
|
||||
"seriesDataField": "Series value field",
|
||||
"summaryType": "Aggregation",
|
||||
"fieldPlaceholder": "e.g. sales.month or month",
|
||||
"types": {
|
||||
"bar": "Bar",
|
||||
"line": "Line",
|
||||
"pie": "Pie",
|
||||
"radar": "Radar"
|
||||
},
|
||||
"styleTypes": {
|
||||
"barDefault": "Default bar",
|
||||
"barStack": "Stacked bar",
|
||||
"lineDefault": "Default line",
|
||||
"lineSmooth": "Smooth line",
|
||||
"lineStack": "Stacked line",
|
||||
"pieDefault": "Solid pie",
|
||||
"pieRing": "Donut pie",
|
||||
"radarPolygon": "Polygon radar",
|
||||
"radarCircle": "Circle radar"
|
||||
},
|
||||
"summary": {
|
||||
"none": "Raw",
|
||||
"sum": "Sum",
|
||||
"avg": "Average",
|
||||
"max": "Max",
|
||||
"min": "Min",
|
||||
"count": "Count"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"title": "Cell Properties",
|
||||
"empty": "Select a cell in the spreadsheet",
|
||||
"position": "Row {row}, Column {col}",
|
||||
"cellType": "Cell type",
|
||||
"typeText": "Text",
|
||||
"textParamHint": "Use #'{'paramName'}' in text cells; replaced on preview",
|
||||
"typeDataSource": "Data source",
|
||||
"typeParameter": "Parameter",
|
||||
"typeQrCode": "QR code",
|
||||
"typeBarcode": "Barcode",
|
||||
"typeExpression": "Expression",
|
||||
"expressionFormula": "Formula",
|
||||
"expressionPlaceholder": "e.g. =A1+sum(sales.amount) or =sum(A1:B2)",
|
||||
"expressionHint": "Supports A1/B2 refs, sum(A1:B2), sum(alias.field), #'{'param'}', + - * /",
|
||||
"dataset": "Dataset alias",
|
||||
"field": "Field",
|
||||
"fieldPlaceholder": "e.g. name or user.name",
|
||||
"displayType": "Display type",
|
||||
"displayDefault": "Default",
|
||||
"expand": "Expand",
|
||||
"expandNone": "None",
|
||||
"expandDown": "List down",
|
||||
"expandRight": "List right",
|
||||
"polymerizationType": "Aggregation",
|
||||
"polyList": "List",
|
||||
"polyGroup": "Group",
|
||||
"polySummary": "Summary",
|
||||
"summaryType": "Summary type",
|
||||
"groupType": "Group mode",
|
||||
"groupDefault": "Default",
|
||||
"groupAdjacent": "Adjacent",
|
||||
"mergeCell": "Merge cells",
|
||||
"fillEmptyRows": "Pad empty rows after list",
|
||||
"fillEmptyNum": "Empty row count",
|
||||
"leftParent": "Left parent",
|
||||
"topParent": "Top parent",
|
||||
"leftParentCustom": "Left parent (col+row)",
|
||||
"topParentCustom": "Top parent (col+row)",
|
||||
"leftParentHint": "When expanding or summarizing, find the nearest data-source cell to the left on the same row to scope the data group. Plain text cells do not need this.",
|
||||
"topParentHint": "When expanding or summarizing, find the nearest data-source cell above in the same column to scope the data level. Use None for grand totals; use Custom for group subtotals.",
|
||||
"parentType": {
|
||||
"none": "None",
|
||||
"default": "Default",
|
||||
"custom": "Custom"
|
||||
},
|
||||
"paramField": "Parameter field",
|
||||
"apply": "Apply to cell"
|
||||
},
|
||||
"code": {
|
||||
"content": "Code content",
|
||||
"contentPlaceholder": "Static text or #'{'paramName'}'",
|
||||
"paramHint": "Use #'{'field'}' for query params; replaced on preview",
|
||||
"qrLevel": "Error correction",
|
||||
"barcodeFormat": "Barcode format"
|
||||
},
|
||||
"render": {
|
||||
"codeRequired": "Report code is required",
|
||||
"loadFailed": "Failed to load report",
|
||||
"empty": "No preview data"
|
||||
},
|
||||
"univerPlaceholder": {
|
||||
"title": "Univer Report Designer",
|
||||
"desc": "Phase 1 skeleton is ready. Full @univerjs spreadsheet engine will be integrated next."
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
{
|
||||
"name": "Role",
|
||||
"title": "Role Management",
|
||||
"roleName": "Role Name",
|
||||
"roleCode": "Role Code",
|
||||
"roleType": "Role Type",
|
||||
"dataScope": "Data Scope",
|
||||
"priority": "Role Priority",
|
||||
"priorityHelp": "Higher value means higher priority",
|
||||
"description": "Role Description",
|
||||
"descriptionPlaceholder": "Please enter role description",
|
||||
"remark": "Remark",
|
||||
"remarkPlaceholder": "Please enter remark",
|
||||
"status": "Status",
|
||||
"operation": "Operation",
|
||||
"edit": "Edit",
|
||||
"users": "Users",
|
||||
"addUsersSuccess": "Users added successfully",
|
||||
"removeUsersConfirm": "Are you sure you want to remove the selected {0} users?",
|
||||
"removeUsersSuccess": "Users removed successfully",
|
||||
"removeUsersFailed": "Failed to remove users",
|
||||
"codeFormatError": "Role code can only contain letters, numbers and underscores",
|
||||
"types": {
|
||||
"system": "System Role",
|
||||
"custom": "Custom Role"
|
||||
},
|
||||
"dataScopes": {
|
||||
"self": "Own Data Only",
|
||||
"dept": "Department Data",
|
||||
"deptAndSub": "Department and Sub-departments Data",
|
||||
"deptAndSubShort": "Dept & Sub",
|
||||
"all": "All Data",
|
||||
"custom": "Custom Data",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"permissions": {
|
||||
"title": "Permission Assignment",
|
||||
"save": "Save",
|
||||
"selectRoleFirst": "Please select a role first",
|
||||
"noRoleSelected": "No role selected",
|
||||
"appList": "App List",
|
||||
"allApps": "All Apps",
|
||||
"loadAppsFailed": "Failed to load application list",
|
||||
"menuList": "Menu List",
|
||||
"selectAll": "Select All",
|
||||
"unselectAll": "Unselect All",
|
||||
"selectMenuPrompt": "Please select a menu from the left to view permissions",
|
||||
"noPermissionData": "No permission data",
|
||||
"loading": "Loading...",
|
||||
"noPermissions": "No permissions",
|
||||
"permissionCount": "permissions",
|
||||
"loadMenuFailed": "Failed to load menu list",
|
||||
"loadPermissionsFailed": "Failed to load permissions",
|
||||
"saveSuccess": "Menu and permissions assigned successfully",
|
||||
"saveFailed": "Failed to save menu and permission assignments",
|
||||
"getRoleDetailFailed": "Failed to get role details",
|
||||
"types": {
|
||||
"button": "Button Permission",
|
||||
"api": "API Permission",
|
||||
"data": "Data Permission",
|
||||
"other": "Other Permission"
|
||||
},
|
||||
"steps": {
|
||||
"menuApi": "Menu & API Permissions",
|
||||
"fieldData": "Field & Data Permissions"
|
||||
}
|
||||
},
|
||||
"resourceScope": {
|
||||
"title": "Data Permission",
|
||||
"helpTip": "Only effective after backend implementation, refer to zq-demo",
|
||||
"addResource": "Add Resource",
|
||||
"saveConfig": "Save Configuration",
|
||||
"deleteConfirm": "Are you sure to delete this configuration?",
|
||||
"selectRole": "Please select a role first",
|
||||
"noConfig": "No configuration, click [Add Resource] to start",
|
||||
"resourceType": "Resource Type",
|
||||
"selectResourceType": "Select Resource Type",
|
||||
"dataPermission": "Data Permission Scope",
|
||||
"selectDataPermission": "Select Data Permission",
|
||||
"customDept": "Custom Department",
|
||||
"selectDept": "Select Department",
|
||||
"operation": "Operation",
|
||||
"delete": "Delete",
|
||||
"saveSuccess": "Resource data permission configuration saved successfully",
|
||||
"saveFailed": "Failed to save resource data permission configuration",
|
||||
"loadFailed": "Failed to load resource data permission configuration",
|
||||
"loadResourceTypesFailed": "Failed to load resource types",
|
||||
"loadDeptsFailed": "Failed to load department list",
|
||||
"allTypesConfigured": "All resource types have been configured",
|
||||
"noResourceTypes": "No resource types available",
|
||||
"customDeptRequired": "Custom data permission must select departments"
|
||||
},
|
||||
"fieldPermission": {
|
||||
"title": "Field Permission",
|
||||
"helpTip": "Only effective after backend implementation, refer to zq-demo",
|
||||
"config": "Config",
|
||||
"resourceType": "Resource Type",
|
||||
"selectResourceType": "Select Resource Type",
|
||||
"selectRoleFirst": "Please select a role first",
|
||||
"selectRoleAndResource": "Please select a role and resource type first",
|
||||
"fieldName": "Field Name",
|
||||
"displayName": "Display Name",
|
||||
"sensitive": "Sensitive",
|
||||
"permissionType": "Permission Type",
|
||||
"maskRule": "Mask Rule",
|
||||
"description": "Description",
|
||||
"noConfig": "No field configuration",
|
||||
"saveConfig": "Save Configuration",
|
||||
"saveSuccess": "Field permission configuration saved successfully",
|
||||
"saveFailed": "Failed to save field permission configuration",
|
||||
"loadFailed": "Failed to load field permission configuration",
|
||||
"loadMetadataFailed": "Failed to load resource field metadata",
|
||||
"maskRuleRequired": "Mask rule is required",
|
||||
"permissionTypes": {
|
||||
"read": "Read",
|
||||
"write": "Write",
|
||||
"hidden": "Hidden",
|
||||
"masked": "Masked"
|
||||
},
|
||||
"maskRules": {
|
||||
"phone": "Phone",
|
||||
"email": "Email",
|
||||
"id_card": "ID Card",
|
||||
"name": "Name",
|
||||
"default": "Default"
|
||||
},
|
||||
"permissionDesc": {
|
||||
"read": "Can view this field",
|
||||
"write": "Can view and modify this field",
|
||||
"hidden": "Completely invisible",
|
||||
"masked": "Partially visible (masked display)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
{
|
||||
"scheduler": "Scheduler",
|
||||
"jobList": "Job List",
|
||||
"jobDetail": "Job Detail",
|
||||
"executionLogs": "Execution Logs",
|
||||
"runStatus": "Run Status",
|
||||
"running": "Running",
|
||||
"stopped": "Stopped",
|
||||
"totalJobs": "Total Jobs",
|
||||
"enabledJobs": "Enabled Jobs",
|
||||
"totalExecutions": "Total Executions",
|
||||
"successRate": "Success Rate",
|
||||
"executionCount": "Execution Count",
|
||||
"failureCount": "Failure Count",
|
||||
"successCount": "Success Count",
|
||||
"allJobs": "All Jobs",
|
||||
"runningJobs": "Running Jobs",
|
||||
"executionRecords": "Execution Records",
|
||||
"jobSuccessRate": "Job Success Rate",
|
||||
"totalRunCount": "Total Run Count",
|
||||
"executionFailed": "Failed",
|
||||
"executionSuccess": "Success",
|
||||
"fetchStatusFailed": "Failed to fetch scheduler status",
|
||||
"fetchDetailFailed": "Failed to fetch job detail",
|
||||
"cardView": "Card View",
|
||||
"listView": "List View",
|
||||
"refresh": "Refresh",
|
||||
"loading": "Loading...",
|
||||
"noLogs": "No logs found",
|
||||
"allLogsLoaded": "All logs loaded",
|
||||
"retryCount": "Retry Count",
|
||||
"retryTimes": "Retry {count} times",
|
||||
"startTime": "Start Time",
|
||||
"endTime": "End Time",
|
||||
"duration": "Duration",
|
||||
"executionDuration": "Duration (s)",
|
||||
"executionResult": "Result",
|
||||
"exceptionInfo": "Exception",
|
||||
"stackTrace": "Stack Trace",
|
||||
"jobName": "Job Name",
|
||||
"jobCode": "Job Code",
|
||||
"jobGroup": "Job Group",
|
||||
"triggerType": "Trigger Type",
|
||||
"cronExpression": "Cron Expression",
|
||||
"intervalTime": "Interval Time",
|
||||
"jobStatus": "Job Status",
|
||||
"priority": "Priority",
|
||||
"maxInstances": "Max Instances",
|
||||
"maxRetries": "Max Retries",
|
||||
"timeout": "Timeout",
|
||||
"timeoutSeconds": "Timeout (s)",
|
||||
"coalesce": "Coalesce",
|
||||
"allowConcurrent": "Allow Concurrent",
|
||||
"remark": "Remark",
|
||||
"remarkPlaceholder": "Remark information",
|
||||
"taskMode": "Task Type",
|
||||
"modeFunction": "Execute Function",
|
||||
"modeWorkflow": "Execute Workflow",
|
||||
"taskFunc": "Task Function",
|
||||
"taskFuncPlaceholder": "Enter task function path (e.g. scheduler.tasks.test_task)",
|
||||
"taskFuncs": {
|
||||
"testTask": "Test Task",
|
||||
"cleanupTask": "Cleanup Logs",
|
||||
"workflowTask": "Execute Workflow"
|
||||
},
|
||||
"workflowCode": "Target Workflow",
|
||||
"workflowCodePlaceholder": "Select workflow to execute",
|
||||
"taskArgs": "Task Args",
|
||||
"taskKwargs": "Task Kwargs",
|
||||
"cronPlaceholder": "e.g., 0 0 * * * (Every day at midnight)",
|
||||
"intervalPlaceholder": "Interval time",
|
||||
"datePlaceholder": "Run date",
|
||||
"groupPlaceholder": "Job group, default is 'default'",
|
||||
"argsPlaceholder": "JSON array, e.g., [\"param1\", \"param2\"]",
|
||||
"kwargsPlaceholder": "JSON object, e.g., {'{'}'key': 'value'{'}'}",
|
||||
"required": "{field} is required",
|
||||
"deleteJob": "Delete Job",
|
||||
"maxLength": "{field} length cannot exceed {max} characters",
|
||||
"codeInvalid": "Job code can only contain letters, numbers, and underscores",
|
||||
"createJob": "Create Job",
|
||||
"editJob": "Edit Job",
|
||||
"deleteJobConfirm": "Are you sure you want to delete job \"{name}\"?",
|
||||
"operationSuccess": "Operation successful",
|
||||
"lastRunTime": "Last Run",
|
||||
"nextRunTime": "Next Run",
|
||||
"description": "Description",
|
||||
"triggerCron": "Cron",
|
||||
"triggerInterval": "Interval",
|
||||
"triggerDate": "One-time",
|
||||
"status": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"paused": "Paused",
|
||||
"pending": "Pending",
|
||||
"running": "Running",
|
||||
"success": "Success",
|
||||
"failed": "Failed",
|
||||
"timeout": "Timeout",
|
||||
"skipped": "Skipped",
|
||||
"waiting": "Waiting",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"unit": {
|
||||
"seconds": "s",
|
||||
"minutes": "mins",
|
||||
"hours": "hrs",
|
||||
"days": "days"
|
||||
},
|
||||
"paramType": {
|
||||
"string": "String",
|
||||
"number": "Number",
|
||||
"boolean": "Boolean"
|
||||
},
|
||||
"paramKey": "Key",
|
||||
"paramValue": "Value",
|
||||
"argValue": "Value",
|
||||
"addParam": "Add Parameter",
|
||||
"addArg": "Add Argument",
|
||||
"noParams": "No parameters, click button below to add",
|
||||
"noArgs": "No arguments, click button below to add",
|
||||
"executeNow": "Execute Now",
|
||||
"executeSuccess": "Job {name} has started execution",
|
||||
"executeFailed": "Failed to execute job",
|
||||
"executionProgress": "Execution Progress",
|
||||
"streaming": "Streaming",
|
||||
"streamComplete": "Completed",
|
||||
"noStreamLogs": "No execution logs",
|
||||
"waitingForLogs": "Waiting for logs...",
|
||||
"viewLiveLogs": "View Live Logs"
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
{
|
||||
"systemInfo": "System Info",
|
||||
"cpuInfo": "CPU Info",
|
||||
"memoryInfo": "Memory Info",
|
||||
"diskInfo": "Disk Info",
|
||||
"networkInfo": "Network Info",
|
||||
"processInfo": "Process Info",
|
||||
"loadFailed": "Failed to load server monitor data",
|
||||
"refreshSuccess": "Refresh successful",
|
||||
"autoRefreshOn": "Auto-refresh enabled",
|
||||
"autoRefreshOff": "Auto-refresh disabled",
|
||||
"autoRefreshing": "Auto-refreshing",
|
||||
"paused": "Paused",
|
||||
"featureDeveloping": "Feature under development...",
|
||||
"serverMonitor": "Server Monitor",
|
||||
"uptime": "Uptime",
|
||||
"days": "days",
|
||||
"hours": "hours",
|
||||
"minutes": "minutes",
|
||||
"justStarted": "Just started",
|
||||
"cpuUsage": "CPU Usage",
|
||||
"core": "Core",
|
||||
"memoryUsage": "Memory Usage",
|
||||
"diskUsage": "Disk Usage",
|
||||
"totalDiskCapacity": "Total Disk Capacity",
|
||||
"networkTraffic3Min": "Network Traffic (Last 3 mins)",
|
||||
"upload": "Upload",
|
||||
"download": "Download",
|
||||
"peakUpload": "Peak Upload",
|
||||
"peakDownload": "Peak Download",
|
||||
"top10Processes": "Top 10 Processes",
|
||||
"topProcessesCpu": "Top Processes (by CPU usage)",
|
||||
"processName": "Process Name",
|
||||
"status": "Status",
|
||||
"createTime": "Create Time",
|
||||
"overallUsage": "Overall Usage",
|
||||
"physicalCores": "Physical Cores",
|
||||
"coreCount": "Core Count",
|
||||
"logicalProcessors": "Logical Processors",
|
||||
"threadCount": "Thread Count",
|
||||
"basicInfo": "Basic Information",
|
||||
"processorModel": "Processor Model",
|
||||
"architecture": "Architecture",
|
||||
"physicalCoreCount": "Physical Core Count",
|
||||
"logicalProcessorCount": "Logical Processor Count",
|
||||
"currentUsage": "Current Usage",
|
||||
"frequencyInfo": "Frequency Information",
|
||||
"currentFrequency": "Current Frequency",
|
||||
"maxFrequency": "Max Frequency",
|
||||
"minFrequency": "Min Frequency",
|
||||
"coreUsage": "Usage per Core",
|
||||
"cpuTimeStats": "CPU Time Statistics",
|
||||
"cpuStatsInfo": "CPU Statistics",
|
||||
"systemLoad": "System Load",
|
||||
"load1min": "1-min Average Load",
|
||||
"load5min": "5-min Average Load",
|
||||
"load15min": "15-min Average Load",
|
||||
"cpuCoreCount": "CPU Cores",
|
||||
"totalMemory": "Total Memory",
|
||||
"physicalMemoryTotal": "Total Physical Memory",
|
||||
"used": "Used",
|
||||
"diskIo": "Disk IO",
|
||||
"read": "Read",
|
||||
"write": "Write",
|
||||
"totalRead": "Total Read",
|
||||
"totalWrite": "Total Write",
|
||||
"networkIo": "Network IO",
|
||||
"totalSent": "Total Sent",
|
||||
"totalReceived": "Total Received",
|
||||
"hostname": "Hostname",
|
||||
"ipAddress": "IP Address",
|
||||
"os": "OS",
|
||||
"processor": "Processor",
|
||||
"pythonVersion": "Python Version",
|
||||
"systemStatus": "System Status",
|
||||
"startTime": "Start Time",
|
||||
"processCount": "Process Count",
|
||||
"onlineUsers": "Online Users",
|
||||
"unitUser": "",
|
||||
"updateTime": "Update Time",
|
||||
"networkUsageTrend": "Network Usage Trend",
|
||||
"recentDataPoints": "Recent {count} data points",
|
||||
"waitingForData": "Waiting for data...",
|
||||
"collectingNetworkData": "Collecting network usage data...",
|
||||
"availableMemory": "Available Memory",
|
||||
"immediatelyAvailable": "Immediately Available",
|
||||
"virtualMemoryRam": "Virtual Memory (RAM)",
|
||||
"memoryUsageStatus": "Memory Usage Status",
|
||||
"kernelMemory": "Kernel Memory",
|
||||
"usedMemory": "Used Memory",
|
||||
"swapPartition": "Swap Partition",
|
||||
"swapUsageStatus": "Swap Usage Status",
|
||||
"swapTotal": "Total Swap",
|
||||
"swapAvailable": "Available Swap",
|
||||
"swapUsed": "Used Swap",
|
||||
"memoryDistribution": "Memory Distribution",
|
||||
"cache": "Cache",
|
||||
"buffer": "Buffer",
|
||||
"activeMemory": "Active Memory",
|
||||
"inactiveMemory": "Inactive Memory",
|
||||
"freeMemory": "Free Memory",
|
||||
"realtimeMemoryDetails": "Real-time Memory Details",
|
||||
"readSpeed": "Read Speed",
|
||||
"currentReadRate": "Current Read Rate",
|
||||
"writeSpeed": "Write Speed",
|
||||
"currentWriteRate": "Current Write Rate",
|
||||
"diskPartitionList": "Disk Partition List",
|
||||
"device": "Device",
|
||||
"fileSystem": "File System",
|
||||
"totalCapacity": "Total Capacity",
|
||||
"available": "Available",
|
||||
"diskIoStats": "Disk IO Statistics",
|
||||
"readStats": "Read Statistics",
|
||||
"totalReadAmount": "Total Read Amount",
|
||||
"readCount": "Read Count",
|
||||
"readTime": "Read Time",
|
||||
"seconds": "seconds",
|
||||
"writeStats": "Write Statistics",
|
||||
"totalWriteAmount": "Total Write Amount",
|
||||
"writeCount": "Write Count",
|
||||
"writeTime": "Write Time",
|
||||
"realtimeDiskIo": "Real-time Disk IO",
|
||||
"uploadSpeed": "Upload Speed",
|
||||
"currentUploadRate": "Current Upload Rate",
|
||||
"downloadSpeed": "Download Speed",
|
||||
"currentDownloadRate": "Current Download Rate",
|
||||
"accumulatedSentData": "Accumulated Sent Data",
|
||||
"accumulatedReceivedData": "Accumulated Received Data",
|
||||
"networkInterfaceList": "Network Interface List",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"interfaceName": "Interface Name",
|
||||
"speed": "Speed",
|
||||
"mtu": "MTU",
|
||||
"mountPoint": "Mount Point",
|
||||
"usageRate": "Usage Rate",
|
||||
"availableSpace": "Available Space",
|
||||
"remainingSpace": "Remaining Space",
|
||||
"currentSpeed": "Current Speed",
|
||||
"sentBytes": "Sent Bytes",
|
||||
"receivedBytes": "Received Bytes",
|
||||
"networkTrafficStats": "Network Traffic Statistics",
|
||||
"sentStats": "Sent Statistics",
|
||||
"totalSentAmount": "Total Sent Amount",
|
||||
"receivedStats": "Received Statistics",
|
||||
"totalReceivedAmount": "Total Received Amount",
|
||||
"realtimeNetworkIo": "Real-time Network IO",
|
||||
"totalProcesses": "Total Processes",
|
||||
"totalSystemProcesses": "Total System Processes",
|
||||
"running": "Running",
|
||||
"runningProcesses": "Running Processes",
|
||||
"sleeping": "Sleeping",
|
||||
"sleepingProcesses": "Sleeping Processes",
|
||||
"otherStatus": "Other Status",
|
||||
"stoppedZombieStatus": "Stopped/Zombie etc.",
|
||||
"processDistribution": "Process Distribution",
|
||||
"total": "Total",
|
||||
"resourceUsageRanking": "Resource Usage Ranking",
|
||||
"noProcessData": "No process data"
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
{
|
||||
"title": "System Config",
|
||||
"ssoConfig": "SSO Config",
|
||||
"notifyConfig": "Notification Config",
|
||||
"modelConfig": "Model Config",
|
||||
"syncConfig": "Sync Config",
|
||||
"save": "Save",
|
||||
"reset": "Reset to Default",
|
||||
"resetConfirm": "Are you sure to reset this group to default? This will delete custom configs from database and restore to environment variable defaults.",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"saveError": "Save failed",
|
||||
"resetSuccess": "Restored to default config",
|
||||
"resetError": "Reset failed",
|
||||
"warmupSuccess": "Cache warmup completed",
|
||||
"clearCacheSuccess": "Cache cleared",
|
||||
"secretTip": "Sensitive field, displayed as masked value. Leave empty or keep masked value to preserve original.",
|
||||
"groups": {
|
||||
"oauth_gitee": "Gitee",
|
||||
"oauth_github": "GitHub",
|
||||
"oauth_qq": "QQ",
|
||||
"oauth_google": "Google",
|
||||
"oauth_wechat": "WeChat",
|
||||
"oauth_microsoft": "Microsoft",
|
||||
"oauth_dingtalk": "DingTalk",
|
||||
"oauth_feishu": "Feishu",
|
||||
"oauth_wecom": "WeCom",
|
||||
"notify_email": "Email",
|
||||
"notify_dingtalk": "DingTalk",
|
||||
"notify_feishu": "Feishu",
|
||||
"notify_wecom": "WeCom",
|
||||
"notify_sms": "SMS",
|
||||
"notify_wechat_mp": "WeChat MP",
|
||||
"sync_dingtalk": "DingTalk",
|
||||
"sync_wecom": "WeCom",
|
||||
"sync_feishu": "Feishu"
|
||||
},
|
||||
"fields": {
|
||||
"client_id": "Client ID",
|
||||
"client_secret": "Client Secret",
|
||||
"redirect_uri": "Web Redirect URI",
|
||||
"h5_redirect_uri": "H5 Redirect URI",
|
||||
"app_id": "App ID",
|
||||
"app_key": "App Key",
|
||||
"app_secret": "App Secret",
|
||||
"corp_id": "Corp ID",
|
||||
"agent_id": "Agent ID",
|
||||
"smtp_host": "SMTP Host",
|
||||
"smtp_port": "SMTP Port",
|
||||
"smtp_user": "SMTP User",
|
||||
"smtp_password": "SMTP Password",
|
||||
"smtp_use_tls": "Use TLS",
|
||||
"smtp_from_name": "From Name",
|
||||
"smtp_from_email": "From Email",
|
||||
"webhook_url": "Webhook URL",
|
||||
"webhook_secret": "Webhook Secret",
|
||||
"template_id": "Template ID",
|
||||
"url": "Redirect URL",
|
||||
"mini_appid": "Mini Program AppID",
|
||||
"mini_page": "Mini Program Page",
|
||||
"provider": "Provider",
|
||||
"providerAliyun": "Alibaba Cloud",
|
||||
"providerTencent": "Tencent Cloud",
|
||||
"aliyun_access_key_id": "Aliyun AccessKey ID",
|
||||
"aliyun_access_key_secret": "Aliyun AccessKey Secret",
|
||||
"aliyun_sign_name": "Aliyun SMS Sign Name",
|
||||
"aliyun_template_code": "Aliyun SMS Template Code",
|
||||
"tencent_secret_id": "Tencent SecretId",
|
||||
"tencent_secret_key": "Tencent SecretKey",
|
||||
"tencent_sdk_app_id": "Tencent SDKAppID",
|
||||
"tencent_sign_name": "Tencent SMS Sign Name",
|
||||
"tencent_template_id": "Tencent SMS Template ID",
|
||||
"todo_pc_url": "Todo PC Redirect URL",
|
||||
"todo_app_url": "Todo Mobile Redirect URL"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"title": "System Management",
|
||||
"tool": "System Tool",
|
||||
"user": {
|
||||
"selectPost": "Select Post",
|
||||
"selectDept": "Select Department",
|
||||
"selectRole": "Select Role",
|
||||
"allDept": "All Departments"
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
{
|
||||
"title": "UI Config Management",
|
||||
"name": "UI Config",
|
||||
"styleConfig": "Style Config",
|
||||
"configKey": "Config Key",
|
||||
"configKeyPlaceholder": "Please enter config key",
|
||||
"configValue": "Config Value",
|
||||
"configValuePlaceholder": "Please enter config value (JSON format)",
|
||||
"configType": "Config Type",
|
||||
"configTypePlaceholder": "Please select config type",
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Please enter description",
|
||||
"status": "Status",
|
||||
"sort": "Sort",
|
||||
"operation": "Operation",
|
||||
"edit": "Edit",
|
||||
"keyFormatError": "Config key can only contain letters, numbers, underscores and hyphens",
|
||||
"appConfig": "App Config",
|
||||
"themeConfig": "Theme Config",
|
||||
"logoConfig": "Logo Config",
|
||||
"copyrightConfig": "Copyright Config",
|
||||
"otherConfig": "Other Config",
|
||||
"configList": "Config List",
|
||||
"configTypes": {
|
||||
"preferences": "Preferences",
|
||||
"theme": "Theme",
|
||||
"logo": "Logo",
|
||||
"copyright": "Copyright",
|
||||
"other": "Other"
|
||||
},
|
||||
"jsonFormatError": "JSON format error",
|
||||
"save": "Save",
|
||||
"saveSuccess": "Save success",
|
||||
"saveError": "Save failed",
|
||||
"loadSuccess": "Load success",
|
||||
"loadError": "Load failed",
|
||||
"previewConfig": "Preview Config",
|
||||
"applyConfig": "Apply Config",
|
||||
"app": {
|
||||
"title": "App Config",
|
||||
"name": "App Name",
|
||||
"namePlaceholder": "Please enter app name",
|
||||
"defaultHomePath": "Default Home Path",
|
||||
"defaultHomePathPlaceholder": "Please enter default home path",
|
||||
"locale": "Default Language",
|
||||
"dynamicTitle": "Dynamic Title",
|
||||
"dynamicTitleTip": "When enabled, page title will change dynamically based on current route",
|
||||
"watermark": "Watermark",
|
||||
"watermarkTip": "When enabled, watermark will be displayed on the page",
|
||||
"watermarkContent": "Watermark Content",
|
||||
"watermarkContentPlaceholder": "Please enter watermark content",
|
||||
"enablePreferences": "Enable Preferences",
|
||||
"enablePreferencesTip": "When enabled, users can modify preferences in the interface",
|
||||
"layout": "Layout Mode",
|
||||
"layoutOptions": {
|
||||
"sidebar-nav": "Sidebar Navigation",
|
||||
"header-nav": "Header Navigation",
|
||||
"mixed-nav": "Mixed Navigation",
|
||||
"header-sidebar-nav": "Header + Sidebar Navigation"
|
||||
}
|
||||
},
|
||||
"theme": {
|
||||
"mode": "Theme Mode",
|
||||
"modeOptions": {
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"auto": "Follow System"
|
||||
},
|
||||
"colorPrimary": "Primary Color",
|
||||
"radius": "Border Radius",
|
||||
"builtinType": "Built-in Theme",
|
||||
"semiDarkSidebar": "Semi Dark Sidebar",
|
||||
"semiDarkHeader": "Semi Dark Header"
|
||||
},
|
||||
"logo": {
|
||||
"enable": "Enable Logo",
|
||||
"source": "Logo Image",
|
||||
"sourcePlaceholder": "Please enter logo image URL or upload",
|
||||
"fit": "Fit Mode",
|
||||
"fitOptions": {
|
||||
"contain": "Contain",
|
||||
"cover": "Cover",
|
||||
"fill": "Fill",
|
||||
"none": "None",
|
||||
"scale-down": "Scale Down"
|
||||
}
|
||||
},
|
||||
"copyright": {
|
||||
"enable": "Enable Copyright",
|
||||
"companyName": "Company Name",
|
||||
"companyNamePlaceholder": "Please enter company name",
|
||||
"companySiteLink": "Company Website",
|
||||
"companySiteLinkPlaceholder": "Please enter company website link",
|
||||
"date": "Copyright Year",
|
||||
"datePlaceholder": "Please enter copyright year",
|
||||
"icp": "ICP License",
|
||||
"icpPlaceholder": "Please enter ICP license number",
|
||||
"icpLink": "ICP Link",
|
||||
"icpLinkPlaceholder": "Please enter ICP link",
|
||||
"policeIcp": "Police ICP Number",
|
||||
"policeIcpPlaceholder": "Please enter police ICP number",
|
||||
"policeIcpLink": "Police ICP Link",
|
||||
"policeIcpLinkPlaceholder": "Please enter police ICP link",
|
||||
"loginOnly": "Login Page Only",
|
||||
"loginOnlyTip": "When enabled, copyright info will only be displayed on the login page"
|
||||
},
|
||||
"loginConfig": {
|
||||
"title": "Login Config",
|
||||
"enableThirdPartyLogin": "Enable Third-party Login",
|
||||
"enableThirdPartyLoginTip": "When enabled, the login page will display third-party login options",
|
||||
"enabledProviders": "Enabled Login Methods",
|
||||
"enabledProvidersTip": "Select which third-party login methods to display on the login page",
|
||||
"providers": {
|
||||
"gitee": "Gitee",
|
||||
"github": "GitHub",
|
||||
"google": "Google",
|
||||
"microsoft": "Microsoft",
|
||||
"qq": "QQ",
|
||||
"wechat": "WeChat",
|
||||
"wecom": "WeCom",
|
||||
"dingtalk": "DingTalk",
|
||||
"feishu": "Feishu"
|
||||
}
|
||||
},
|
||||
"modelConfig": "Model Config",
|
||||
"model": {
|
||||
"title": "Model Config",
|
||||
"providerManagement": "Provider Management",
|
||||
"modelManagement": "Model Management",
|
||||
"addProvider": "Add Provider",
|
||||
"editProvider": "Edit Provider",
|
||||
"addModel": "Add Model",
|
||||
"editModel": "Edit Model",
|
||||
"addDefaultModels": "Add Default Models",
|
||||
"providerName": "Name",
|
||||
"providerType": "Type",
|
||||
"providerDescription": "Description",
|
||||
"apiKey": "API Key",
|
||||
"apiBase": "API Base URL",
|
||||
"ollamaHost": "Ollama Host",
|
||||
"modelName": "Model Name",
|
||||
"displayName": "Display Name",
|
||||
"modelType": "Model Type",
|
||||
"maxTokens": "Max Tokens",
|
||||
"supportsVision": "Supports Vision",
|
||||
"supportsFunction": "Supports Function",
|
||||
"enabled": "Enabled",
|
||||
"status": "Status",
|
||||
"operation": "Operation",
|
||||
"test": "Test",
|
||||
"selectProvider": "Select Provider",
|
||||
"chatModel": "Chat Model",
|
||||
"embeddingModel": "Embedding Model",
|
||||
"completionModel": "Completion Model",
|
||||
"rerankModel": "Rerank Model",
|
||||
"chat": "Chat",
|
||||
"embedding": "Embedding",
|
||||
"rerank": "Rerank",
|
||||
"addSelected": "Add Selected",
|
||||
"noDefaultModels": "No default models",
|
||||
"addedModels": "Added Models",
|
||||
"availableModels": "Available Models",
|
||||
"addAll": "Add All",
|
||||
"added": "Added",
|
||||
"refreshModels": "Refresh",
|
||||
"sourceApi": "Live",
|
||||
"sourceDefault": "Default",
|
||||
"fetchFromApiSuccess": "Fetched latest models from provider API",
|
||||
"fetchFromApiFallback": "Online fetch failed, using default model list",
|
||||
"searchModel": "Search models"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"layout": {
|
||||
"poweredBy": "AI Agent Admin"
|
||||
},
|
||||
"actionMessage": {
|
||||
"createSuccess": "Created successfully",
|
||||
"createError": "Failed to create",
|
||||
"updateSuccess": "Updated successfully",
|
||||
"updateError": "Failed to update",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"deleteConfirm": "Are you sure to delete {0}?",
|
||||
"deleteError": "Failed to delete",
|
||||
"loadError": "Failed to load"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"profile": {
|
||||
"overview": "Overview",
|
||||
"organization": "Organization",
|
||||
"email": "Email",
|
||||
"mobile": "Mobile",
|
||||
"department": "Department",
|
||||
"position": "Position",
|
||||
"manager": "Manager",
|
||||
"city": "City",
|
||||
"type": "Type"
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
{
|
||||
"name": "User",
|
||||
"title": "User Management",
|
||||
"userName": "User Name",
|
||||
"account": "Account",
|
||||
"avatar": "Avatar",
|
||||
"avatarHelp": "Support jpg, png format, size no more than 2MB",
|
||||
"selectAvatar": "Select Avatar",
|
||||
"email": "Email",
|
||||
"emailFormatError": "Please enter a valid email address",
|
||||
"mobile": "Mobile",
|
||||
"mobileFormatError": "Please enter a valid mobile number",
|
||||
"gender": "Gender",
|
||||
"unknown": "Unknown",
|
||||
"male": "Male",
|
||||
"female": "Female",
|
||||
"birthday": "Birthday",
|
||||
"selectBirthday": "Select Birthday",
|
||||
"city": "City",
|
||||
"address": "Address",
|
||||
"bio": "Bio",
|
||||
"bioPlaceholder": "Please enter bio",
|
||||
"dept": "Department",
|
||||
"selectDept": "Please select department",
|
||||
"manager": "Manager",
|
||||
"selectManager": "Please select manager",
|
||||
"post": "Post",
|
||||
"selectPost": "Please select post",
|
||||
"role": "Role",
|
||||
"selectRole": "Please select role",
|
||||
"selectUser": "Select User",
|
||||
"userList": "User List",
|
||||
"userType": "User Type",
|
||||
"systemUser": "System User",
|
||||
"normalUser": "Normal User",
|
||||
"externalUser": "External User",
|
||||
"status": "Status",
|
||||
"locked": "Locked",
|
||||
"createTime": "Create Time",
|
||||
"operation": "Operation",
|
||||
"resetPassword": "Reset Password",
|
||||
"cannotDeleteAdmin": "Cannot delete administrator account",
|
||||
"selectUsersToDelete": "Please select users to delete",
|
||||
"batchDeleteTitle": "Batch Delete Users",
|
||||
"batchDeleteConfirm": "Are you sure to delete {0} users?\n{1}",
|
||||
"batchDelete": "Batch Delete",
|
||||
"deleteSuccess": "Successfully deleted {0} users",
|
||||
"deleteError": "Failed to delete user",
|
||||
"cannotResetAdminPassword": "Cannot reset administrator password",
|
||||
"resetPasswordTitle": "Reset Password",
|
||||
"resetPasswordConfirm": "Are you sure to reset password for user \"{0}\"?\nThe password will be reset to: admin123",
|
||||
"resetPasswordSuccess": "Password reset successfully for user \"{0}\"",
|
||||
"resetPasswordError": "Failed to reset password",
|
||||
"basicInfo": "Basic Information",
|
||||
"changePassword": "Change Password",
|
||||
"oldPassword": "Current Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmPassword": "Confirm Password",
|
||||
"oldPasswordPlaceholder": "Please enter current password",
|
||||
"newPasswordPlaceholder": "Please enter new password",
|
||||
"confirmPasswordPlaceholder": "Please enter new password again",
|
||||
"passwordNotMatch": "Passwords do not match",
|
||||
"changePasswordSuccess": "Password changed successfully",
|
||||
"changePasswordError": "Failed to change password",
|
||||
"updateProfileSuccess": "Profile updated successfully",
|
||||
"updateProfileError": "Failed to update profile",
|
||||
"loadProfileError": "Failed to load profile"
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"title": "WeCom Sync Configuration",
|
||||
"corpId": "Corp ID",
|
||||
"corpIdPlaceholder": "Enter Corp ID",
|
||||
"corpSecret": "Contacts Secret",
|
||||
"corpSecretPlaceholder": "Enter Contacts Management Secret",
|
||||
"testConnection": "Test Connection",
|
||||
"testSuccess": "Connection Successful",
|
||||
"testFail": "Connection Failed",
|
||||
"testing": "Testing...",
|
||||
"syncScope": "Sync Scope",
|
||||
"syncScopePlaceholder": "Please select",
|
||||
"syncScopeTip": "Select an organization as the top-level for data synchronization. Once synced, this organization cannot be changed.",
|
||||
"syncScopeLocked": "Initial sync completed. Sync scope is now locked. Contact admin to change.",
|
||||
"syncStats": "Sync Statistics",
|
||||
"syncType": "Sync Type",
|
||||
"totalCount": "Total",
|
||||
"successCount": "Synced",
|
||||
"failCount": "Failed",
|
||||
"notSynced": "Not Synced",
|
||||
"syncTime": "Sync Time",
|
||||
"operation": "Operation",
|
||||
"sync": "Sync",
|
||||
"syncing": "Syncing...",
|
||||
"syncDept": "Organization",
|
||||
"syncUser": "User",
|
||||
"syncDeptSuccess": "Organization sync completed",
|
||||
"syncUserSuccess": "User sync completed",
|
||||
"syncFail": "Sync failed",
|
||||
"triggerEvents": "Trigger Events",
|
||||
"triggerEvent": "Trigger Event",
|
||||
"description": "Description",
|
||||
"enableSyncDept": "Enable Sync Organization",
|
||||
"enableSyncDeptDesc": "Trigger organization sync on add, delete, or modify organization info",
|
||||
"enableSyncUser": "Enable Sync User",
|
||||
"enableSyncUserDesc": "Trigger user sync on add, delete, or modify user info",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"saveFail": "Save failed",
|
||||
"corpName": "Corp Name",
|
||||
"loadingDeptTree": "Loading department tree...",
|
||||
"callbackConfig": "Event Callback Configuration",
|
||||
"callbackConfigTip": "After configuring the callback URL in WeCom admin console, contacts changes will be pushed to this system in real-time for incremental sync.",
|
||||
"callbackUrl": "Callback URL",
|
||||
"callbackUrlPlaceholder": "Enter callback URL, e.g. https://example.com/api/core/wecom-sync/callback",
|
||||
"callbackToken": "Callback Token",
|
||||
"callbackTokenPlaceholder": "Enter callback token",
|
||||
"callbackAesKey": "EncodingAESKey",
|
||||
"callbackAesKeyPlaceholder": "Enter EncodingAESKey (43 characters)",
|
||||
"callbackStatus": "Callback Status",
|
||||
"callbackRegistered": "Configured",
|
||||
"callbackNotRegistered": "Not Configured",
|
||||
"subscribedEvents": "Subscribed Events",
|
||||
"generateRandom": "Generate",
|
||||
"guideTitle": "WeCom Sync Setup Guide",
|
||||
"guideStep1Title": "Get WeCom Contacts Secret",
|
||||
"guideStep1Desc": "Log in to WeCom admin console (work.weixin.qq.com), go to \"Management Tools\" -> \"Contacts Sync\", enable API sync, and get the Contacts Secret. Also get the Corp ID from the \"My Enterprise\" page.",
|
||||
"guideStep2Title": "Enter Credentials",
|
||||
"guideStep2Desc": "Fill in the Corp ID and Contacts Secret into the corresponding fields on this page, then click \"Test Connection\" to verify.",
|
||||
"guideStep3Title": "Set Sync Scope & Run Full Sync",
|
||||
"guideStep3Desc": "After a successful connection, select the root department in \"Sync Scope\", then click \"Sync\" in the statistics table — sync organizations first, then users.",
|
||||
"guideStep4Title": "Configure Event Callback (Real-time Sync)",
|
||||
"guideStep4Desc": "In WeCom admin console under \"Management Tools\" -> \"Contacts Sync\", set up the event receiver server. Enter the Token and EncodingAESKey generated on this page into WeCom admin. The callback URL format is: https://your-domain/api/core/wecom-sync/callback. Enter the same values on this page and save.",
|
||||
"guideStep5Title": "Enable Trigger Events",
|
||||
"guideStep5Desc": "In the \"Trigger Events\" section, check the event types to auto-sync (organizations, users) and save. WeCom contacts changes will then be pushed to the system for incremental sync automatically."
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
{
|
||||
"workflow": "Workflow",
|
||||
"title": "Process Title",
|
||||
"type": "Process Type",
|
||||
"status": "Status",
|
||||
"currentNode": "Current Node",
|
||||
"startTime": "Start Time",
|
||||
"endTime": "End Time",
|
||||
"actions": "Actions",
|
||||
"details": "Details",
|
||||
"recall": "Recall",
|
||||
"recallConfirm": "Recall Confirmation",
|
||||
"recallConfirmMsg": "Are you sure you want to recall this process? This action cannot be undone.",
|
||||
"recallSuccess": "Recall successful",
|
||||
"recallFailed": "Recall failed",
|
||||
"statusMap": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"rejected": "Rejected",
|
||||
"canceled": "Canceled",
|
||||
"transferred": "Transferred"
|
||||
},
|
||||
"nodeName": "Node Name",
|
||||
"result": "Result",
|
||||
"initiator": "Initiator",
|
||||
"handleTime": "Handle Time",
|
||||
"workflowType": {
|
||||
"label": "Workflow Type",
|
||||
"approval": "Approval",
|
||||
"application": "Application",
|
||||
"business": "Business",
|
||||
"hr": "HR",
|
||||
"finance": "Finance",
|
||||
"admin": "Admin",
|
||||
"other": "Other"
|
||||
},
|
||||
"manager": {
|
||||
"title": "Workflow Management",
|
||||
"add": "Add Workflow",
|
||||
"edit": "Edit Workflow",
|
||||
"view": "View Workflow",
|
||||
"basicInfo": "Basic Information",
|
||||
"design": "Workflow Design",
|
||||
"editInfo": "Edit Info",
|
||||
"name": "Process Name",
|
||||
"code": "Process Code",
|
||||
"workflowType": "Workflow Type",
|
||||
"icon": "Process Icon",
|
||||
"iconBgColor": "Icon Background Color",
|
||||
"preview": "Preview",
|
||||
"form": "Related Form",
|
||||
"category": "Category",
|
||||
"version": "Version",
|
||||
"updatedAt": "Updated At",
|
||||
"remark": "Process Description",
|
||||
"sort": "Sort Order",
|
||||
"addProcess": "Add Process",
|
||||
"batchDelete": "Batch Delete",
|
||||
"batchDeleteWithCount": "Batch Delete ({count})",
|
||||
"publish": "Publish",
|
||||
"disable": "Disable",
|
||||
"copy": "Copy",
|
||||
"status": {
|
||||
"draft": "Draft",
|
||||
"published": "Published",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"placeholder": {
|
||||
"title": "Please enter process title",
|
||||
"workflowType": "Please select workflow type",
|
||||
"icon": "Please select process icon",
|
||||
"iconBgColor": "Enter custom color or gradient",
|
||||
"status": "Please select status",
|
||||
"name": "Please enter process name",
|
||||
"code": "Please enter process code",
|
||||
"form": "Please select related form",
|
||||
"remark": "Please enter process description",
|
||||
"sort": "Smaller values appear first, default is 0",
|
||||
"copyCode": "Please enter new process code"
|
||||
},
|
||||
"validate": {
|
||||
"name": "Please enter process name",
|
||||
"code": "Please enter process code",
|
||||
"codeFormat": "Code must start with a letter and contain only letters, numbers, and underscores",
|
||||
"form": "Please select related form",
|
||||
"design": "Workflow design is incomplete"
|
||||
},
|
||||
"message": {
|
||||
"deleteConfirm": "Are you sure you want to delete this process?",
|
||||
"deleteConfirmTitle": "Delete Confirmation",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"selectToDelete": "Please select processes to delete first",
|
||||
"batchDeleteConfirm": "Are you sure you want to delete {count} selected processes?",
|
||||
"batchDeleteConfirmTitle": "Batch Delete Confirmation",
|
||||
"publishConfirm": "Are you sure you want to publish this process? It can be used after publishing.",
|
||||
"publishConfirmTitle": "Publish Confirmation",
|
||||
"publishSuccess": "Published successfully",
|
||||
"disableConfirm": "Are you sure you want to disable this process? New processes cannot be started after disabling.",
|
||||
"disableConfirmTitle": "Disable Confirmation",
|
||||
"disableSuccess": "Disabled successfully",
|
||||
"copyConfirmTitle": "Copy Process",
|
||||
"copySuccess": "Copied successfully",
|
||||
"copySuffix": "Copy",
|
||||
"loadFailed": "Failed to load data",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"createSuccess": "Created successfully"
|
||||
},
|
||||
"autoSave": {
|
||||
"saving": "Saving...",
|
||||
"saved": "Saved",
|
||||
"unsaved": "Unsaved"
|
||||
},
|
||||
"steps": {
|
||||
"basic": "Basic Information",
|
||||
"design": "Workflow Design"
|
||||
},
|
||||
"importExport": {
|
||||
"export": "Export Config",
|
||||
"import": "Import Config",
|
||||
"exportSuccess": "Workflow config exported",
|
||||
"exportFailed": "Export failed",
|
||||
"importTitle": "Import Workflow Config",
|
||||
"dragOrClick": "Drag JSON file here or click to upload",
|
||||
"onlyJson": "Only .json files are supported",
|
||||
"fileParseError": "Invalid file. Required fields: name, code, form_code",
|
||||
"checking": "Checking...",
|
||||
"codeConflictTip": "Workflow code already exists. Enter a new code to import",
|
||||
"codeAvailable": "Workflow code is available",
|
||||
"newCodePlaceholder": "Enter a new workflow code",
|
||||
"importSuccess": "Workflow imported successfully",
|
||||
"importFailed": "Import failed",
|
||||
"confirmImport": "Confirm Import",
|
||||
"reselect": "Reselect",
|
||||
"workflowInfo": "Workflow Info",
|
||||
"appTip": "Imported workflow belongs to the current app (draft status)",
|
||||
"bindingTip": "Ensure linked form and document template codes exist and are published in the target environment"
|
||||
}
|
||||
},
|
||||
"pending": {
|
||||
"title": "Pending Approval",
|
||||
"approval": "Approval",
|
||||
"approve": "Approve",
|
||||
"reject": "Reject",
|
||||
"submit": "Submit",
|
||||
"comment": "Comment",
|
||||
"commentPlaceholder": "Please enter approval comment",
|
||||
"result": "Approval Result",
|
||||
"success": "Process successful",
|
||||
"taskType": "Task Type",
|
||||
"timeoutStatus": "Timeout Status",
|
||||
"arriveTime": "Arrive Time",
|
||||
"isTimeout": "Timed Out",
|
||||
"aboutToTimeout": "About to Timeout",
|
||||
"deadline": "Deadline",
|
||||
"daysAfter": "{count} days later",
|
||||
"hoursAfter": "{count} hours later",
|
||||
"minutesAfter": "{count} mins later",
|
||||
"view": "View",
|
||||
"handle": "Handle",
|
||||
"modify": "Modify",
|
||||
"cc": "CC",
|
||||
"copyDetail": "CC Details",
|
||||
"reviseSubmit": "Modify and Resubmit",
|
||||
"copyAction": "CC Action",
|
||||
"handleAction": "Handle Action",
|
||||
"reviseAction": "Modify Action",
|
||||
"approvalAction": "Approval Action",
|
||||
"signComment": "Sign Comment",
|
||||
"delegateComment": "Delegate Comment",
|
||||
"rejectReason": "Reject Reason",
|
||||
"returnReason": "Return Reason",
|
||||
"transferComment": "Transfer Comment",
|
||||
"signPlaceholder": "Optional, enter sign description",
|
||||
"approvalPlaceholder": "Optional, enter approval comment",
|
||||
"delegatePlaceholder": "Optional, enter delegate description",
|
||||
"rejectPlaceholder": "Please enter reject reason",
|
||||
"returnPlaceholder": "Please enter return reason",
|
||||
"transferPlaceholder": "Optional, enter transfer description",
|
||||
"fetchDetailFailed": "Failed to load task details",
|
||||
"markReadSuccess": "Marked as read",
|
||||
"handleSuccess": "Handle completed",
|
||||
"actionFailed": "Action failed",
|
||||
"selectSignUser": "Please select sign user",
|
||||
"signSuccess": "Sign successful",
|
||||
"approveSuccess": "Approval passed",
|
||||
"signature": "Signature",
|
||||
"signaturePlaceholder": "Please sign here",
|
||||
"signatureRequired": "This approval node requires a signature, please sign first",
|
||||
"selectDelegateUser": "Please select delegate user",
|
||||
"delegateSuccess": "Delegated",
|
||||
"rejectCommentRequired": "Approval comment is required when rejecting",
|
||||
"rejectSuccess": "Rejected",
|
||||
"returnCommentRequired": "Approval comment is required when returning",
|
||||
"returnSuccess": "Returned",
|
||||
"selectTransferUser": "Please select transfer user",
|
||||
"transferSuccess": "Transferred",
|
||||
"unsupportedAction": "Unsupported action",
|
||||
"continueHandle": "Continue Handling",
|
||||
"actionSuccess": "Action successful",
|
||||
"viewHandled": "View Handled",
|
||||
"reviseSuccess": "Resubmitted",
|
||||
"markRead": "Read",
|
||||
"formContent": "Form Content",
|
||||
"emptyForm": "No form content",
|
||||
"processInfo": "Process Info",
|
||||
"returnTo": "Return To",
|
||||
"initiator": "Initiator",
|
||||
"previousNode": "Previous Node",
|
||||
"transferTo": "Transfer To",
|
||||
"delegateTo": "Delegate To",
|
||||
"signType": "Sign Type",
|
||||
"beforeSign": "Before Sign",
|
||||
"afterSign": "After Sign",
|
||||
"parallelSign": "Parallel Sign",
|
||||
"beforeSignTip": "Signer approves first, then returns to you",
|
||||
"afterSignTip": "You approve first, then signer approves",
|
||||
"parallelSignTip": "You and signer approve simultaneously",
|
||||
"signUser": "Signer",
|
||||
"selectSignUserPlaceholder": "Please select signer (Multiple choice)",
|
||||
"handleTip": "Please view the form content and click \"Submit\" to complete handling",
|
||||
"handleComment": "Handle Comment",
|
||||
"handleCommentPlaceholder": "Optional, enter handle comment",
|
||||
"copyTip": "This is a CC task for reference only. Click \"Read\" to confirm.",
|
||||
"reviseTip": "Please modify the form content and click \"Submit\" to resubmit for approval.",
|
||||
"modifyComment": "Modify Description",
|
||||
"modifyCommentPlaceholder": "Optional, enter modify description",
|
||||
"selectTask": "Please select a task from the left",
|
||||
"approvalInfo": "Approval Info"
|
||||
},
|
||||
"handled": {
|
||||
"title": "Handled Tasks"
|
||||
},
|
||||
"initiated": {
|
||||
"title": "Initiated by Me",
|
||||
"selectInstance": "Please select a process from the left"
|
||||
},
|
||||
"copy": {
|
||||
"title": "CC to Me",
|
||||
"ccNode": "CC Node",
|
||||
"ccTime": "CC Time"
|
||||
},
|
||||
"start": {
|
||||
"title": "Start Process",
|
||||
"select": "Select Process",
|
||||
"form": "Process Form",
|
||||
"searchPlaceholder": "Search by name, code or category",
|
||||
"fetchFailed": "Failed to load process list",
|
||||
"startSuccess": "Process started successfully, check progress in Approval Center",
|
||||
"start": "Start",
|
||||
"submit": "Submit",
|
||||
"allWorkflows": "All Processes",
|
||||
"noWorkflows": "No processes available to start",
|
||||
"workflowTitle": "Process Title",
|
||||
"titlePlaceholder": "Please enter process title, e.g., John's Leave Application",
|
||||
"titleHint": "Recommended format: Applicant + Process Type, for easier identification",
|
||||
"formInfo": "Form Information",
|
||||
"loadConfigFailed": "Failed to load form configuration",
|
||||
"titleRequired": "Please enter process title",
|
||||
"formInvalid": "Please check if the form is filled correctly",
|
||||
"startFailed": "Failed to start process",
|
||||
"submitSuccess": "Process Started Successfully",
|
||||
"submitSuccessHint": "Your application has been submitted, please wait for approval",
|
||||
"workflowName": "Process Name",
|
||||
"instanceNo": "Process No.",
|
||||
"viewDetail": "View Details",
|
||||
"continueStart": "Start Another",
|
||||
"backToList": "Back to List",
|
||||
"approvalPath": "Approval Path",
|
||||
"showFlow": "Show Flow",
|
||||
"hideFlow": "Hide Flow",
|
||||
"loadingUsers": "Loading..."
|
||||
},
|
||||
"validation": {
|
||||
"title": "Workflow Validation",
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"warning": "Warning",
|
||||
"errorCount": "{count} errors",
|
||||
"warningCount": "{count} warnings",
|
||||
"validating": "Validating workflow configuration...",
|
||||
"failedMsg": "Validation failed, please fix and retry",
|
||||
"passedMsg": "Validation passed, you can save the workflow",
|
||||
"ready": "Ready to validate",
|
||||
"checkCount": "{count} checks in total",
|
||||
"confirmSave": "Confirm Save"
|
||||
},
|
||||
"documents": "Documents",
|
||||
"viewDocuments": "View Documents",
|
||||
"noDocuments": "No documents",
|
||||
"pages": "pages",
|
||||
"documentTemplates": "Document Templates",
|
||||
"selectDocumentTemplates": "Select Document Templates",
|
||||
"generateDocuments": "Generate Documents",
|
||||
"generateDocumentsSuccess": "Documents generated successfully",
|
||||
"generateDocumentsFailed": "Failed to generate documents",
|
||||
"regenerateDocuments": "Regenerate",
|
||||
"regenerateDocumentsSuccess": "Documents regenerated successfully",
|
||||
"regenerateDocumentsFailed": "Failed to regenerate documents",
|
||||
"instanceManager": {
|
||||
"title": "Workflow Instances",
|
||||
"detailTitle": "Instance Details",
|
||||
"instanceNo": "Instance No.",
|
||||
"application": "Application",
|
||||
"searchTitle": "Search by title",
|
||||
"searchInstanceNo": "Search by instance no.",
|
||||
"searchWorkflowName": "Search by workflow name",
|
||||
"searchInitiator": "Search by initiator",
|
||||
"searchStatus": "Select status"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"passwordLengthHint": "密码长度需为 6-20 位"
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
{
|
||||
"title": "应用管理",
|
||||
"createApp": "创建应用",
|
||||
"editApp": "编辑应用",
|
||||
"search": "搜索",
|
||||
"searchPlaceholder": "搜索应用名称或编码",
|
||||
"noApps": "暂无应用",
|
||||
"appName": "应用名称",
|
||||
"appNamePlaceholder": "请输入应用名称",
|
||||
"appCode": "应用编码",
|
||||
"appCodePlaceholder": "请输入应用编码(用于URL路由)",
|
||||
"appType": "应用类型",
|
||||
"appTypePlaceholder": "请选择应用类型",
|
||||
"appDescription": "应用描述",
|
||||
"appDescriptionPlaceholder": "请输入应用描述",
|
||||
"appIcon": "应用图标",
|
||||
"systemMenu": "系统菜单",
|
||||
"systemMenuPlaceholder": "选择开发模式下显示的系统菜单(留空则显示全部)",
|
||||
"selectSystemMenu": "选择系统菜单",
|
||||
"save": "保存",
|
||||
"create": "创建",
|
||||
"cancel": "取消",
|
||||
"develop": "设置开发",
|
||||
"edit": "编辑",
|
||||
"publish": "发布",
|
||||
"enable": "启用",
|
||||
"disable": "停用",
|
||||
"delete": "删除",
|
||||
"publishApp": "发布应用",
|
||||
"enableApp": "启用应用",
|
||||
"disableApp": "停用应用",
|
||||
"confirmPublish": "确认发布",
|
||||
"confirmEnable": "确认启用",
|
||||
"confirmDisable": "确认停用",
|
||||
"publishConfirmMsg": "确定要发布应用「{name}」吗?",
|
||||
"enableConfirmMsg": "确定要重新启用应用「{name}」吗?",
|
||||
"publishSuccessMsg": "发布后,用户可以通过以下链接访问该应用:",
|
||||
"enableSuccessMsg": "启用后,用户可以通过以下链接访问该应用:",
|
||||
"disableConfirmMsg": "确定要停用应用「{name}」吗?",
|
||||
"appLink": "应用链接:",
|
||||
"deleteConfirm": "删除确认",
|
||||
"deleteConfirmMsg": "确定要删除应用「{name}」吗?",
|
||||
"confirm": "确定",
|
||||
"loadFailed": "加载应用列表失败",
|
||||
"publishSuccess": "发布成功",
|
||||
"publishFailed": "发布失败",
|
||||
"enableSuccess": "启用成功",
|
||||
"enableFailed": "启用失败",
|
||||
"disableSuccess": "停用成功",
|
||||
"disableFailed": "停用失败",
|
||||
"deleteSuccess": "删除成功",
|
||||
"copySuccess": "链接已复制到剪贴板",
|
||||
"copyFailed": "复制失败",
|
||||
"appTypes": {
|
||||
"mixed": "混合应用",
|
||||
"form": "表单应用",
|
||||
"workflow": "流程应用",
|
||||
"ai": "AI应用",
|
||||
"dashboard": "数据应用",
|
||||
"screen": "大屏应用"
|
||||
},
|
||||
"appStatus": {
|
||||
"draft": "开发中",
|
||||
"published": "已发布",
|
||||
"disabled": "已停用"
|
||||
},
|
||||
"validation": {
|
||||
"nameRequired": "请输入应用名称",
|
||||
"nameLength": "长度在 2 到 100 个字符",
|
||||
"codeRequired": "请输入应用编码",
|
||||
"codePattern": "编码必须以字母开头,只能包含字母、数字、下划线和短横线",
|
||||
"codeLength": "长度在 2 到 100 个字符",
|
||||
"typeRequired": "请选择应用类型"
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
{
|
||||
"title": "聊天",
|
||||
"search": "搜索联系人或群聊",
|
||||
"noConversations": "暂无会话",
|
||||
"selectHint": "选择一个会话开始聊天",
|
||||
"newChat": "发起聊天",
|
||||
"newGroup": "创建群聊",
|
||||
"private": "单聊",
|
||||
"group": "群聊",
|
||||
"members": "成员",
|
||||
"memberCount": "{count} 人",
|
||||
"owner": "群主",
|
||||
"admin": "管理员",
|
||||
"member": "成员",
|
||||
"groupName": "群聊名称",
|
||||
"groupNamePlaceholder": "请输入群聊名称",
|
||||
"selectMembers": "选择成员",
|
||||
"selectMembersPlaceholder": "请选择群成员",
|
||||
"createGroupSuccess": "群聊创建成功",
|
||||
"inputPlaceholder": "输入消息...",
|
||||
"send": "发送",
|
||||
"sendImage": "发送图片",
|
||||
"sendFile": "发送文件",
|
||||
"recall": "撤回",
|
||||
"recallSuccess": "消息已撤回",
|
||||
"recallFailed": "撤回失败",
|
||||
"recallTimeout": "超过2分钟无法撤回",
|
||||
"messageRecalled": "消息已撤回",
|
||||
"typing": "正在输入...",
|
||||
"yesterday": "昨天",
|
||||
"pin": "置顶",
|
||||
"unpin": "取消置顶",
|
||||
"mute": "免打扰",
|
||||
"unmute": "取消免打扰",
|
||||
"conversationInfo": "会话信息",
|
||||
"addMember": "添加成员",
|
||||
"addMemberSuccess": "成员添加成功",
|
||||
"allMembersExist": "所选成员已在群聊中",
|
||||
"removeMember": "移除成员",
|
||||
"removeMemberConfirm": "确定移除该成员吗?",
|
||||
"dissolveGroup": "解散群聊",
|
||||
"dissolveGroupConfirm": "确定解散该群聊吗?解散后不可恢复。",
|
||||
"dissolveSuccess": "群聊已解散",
|
||||
"leaveGroup": "退出群聊",
|
||||
"noMessages": "暂无消息",
|
||||
"loadMore": "加载更多",
|
||||
"loading": "加载中...",
|
||||
"image": "图片",
|
||||
"file": "文件",
|
||||
"replyTo": "回复",
|
||||
"groupNameRequired": "请输入群聊名称",
|
||||
"membersRequired": "请至少选择一个成员",
|
||||
"recentChats": "最近聊天",
|
||||
"contacts": "联系人",
|
||||
"searchContacts": "搜索联系人",
|
||||
"noContacts": "暂无联系人",
|
||||
"startChat": "发起聊天",
|
||||
"online": "在线",
|
||||
"offline": "离线",
|
||||
"sending": "发送中...",
|
||||
"contactDetail": "联系人详情",
|
||||
"contactDept": "部门",
|
||||
"contactPost": "岗位",
|
||||
"contactManager": "直属上级",
|
||||
"contactEmail": "邮箱",
|
||||
"contactMobile": "手机",
|
||||
"contactCity": "城市",
|
||||
"contactType": "用户类型",
|
||||
"contactOrg": "组织架构",
|
||||
"contactOrgInfo": "组织信息",
|
||||
"contactInfo": "联系方式",
|
||||
"selectContactHint": "选择一个联系人查看详情",
|
||||
"copy": "复制",
|
||||
"copySuccess": "已复制到剪贴板",
|
||||
"replyingTo": "回复 {name}",
|
||||
"markUnread": "标记未读",
|
||||
"deleteConversation": "删除记录",
|
||||
"deleteConversationConfirm": "确定删除该会话记录吗?",
|
||||
"deleteSuccess": "已删除",
|
||||
"orgStructure": "组织架构",
|
||||
"emoji": "表情",
|
||||
"voiceMessage": "语音消息",
|
||||
"voiceTooShort": "录音时间太短",
|
||||
"voiceUploading": "语音发送中...",
|
||||
"micPermissionDenied": "无法访问麦克风,请检查浏览器权限",
|
||||
"voice": "语音",
|
||||
"dropToUpload": "松开发送文件",
|
||||
"newMessage": "新消息",
|
||||
"systemNotification": "系统通知",
|
||||
"viewDetail": "查看详情"
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
{
|
||||
"basicInfo": "基础信息",
|
||||
"codePlaceholder": "请输入唯一编码,如 user_list",
|
||||
"dataSourceConfig": "数据源配置",
|
||||
"dataSourceName": "数据源名称",
|
||||
"inputDataSourceName": "请输入数据源名称",
|
||||
"dataSourceType": "数据源类型",
|
||||
"selectDataSourceType": "请选择数据源类型",
|
||||
"dataSourceDescription": "数据源描述",
|
||||
"inputDataSourceDescription": "请输入数据源描述",
|
||||
"testConnection": "测试连接",
|
||||
"testConnectionSuccess": "连接成功",
|
||||
"testConnectionFailed": "连接失败",
|
||||
"testing": "测试中...",
|
||||
"save": "保存",
|
||||
"create": "创建",
|
||||
"query": "查询",
|
||||
"sqlEditor": "SQL编辑器",
|
||||
"executeSql": "执行SQL",
|
||||
"executing": "执行中...",
|
||||
"queryResult": "查询结果",
|
||||
"noData": "暂无数据",
|
||||
"error": "错误",
|
||||
"confirm": "确定",
|
||||
"close": "关闭",
|
||||
"dbSchema": "数据库",
|
||||
"tables": "表",
|
||||
"columns": "列",
|
||||
"dataType": "数据类型",
|
||||
"nullable": "可空",
|
||||
"primaryKey": "主键",
|
||||
"loading": "加载中...",
|
||||
"paramType": "参数类型",
|
||||
"resultType": "结果类型",
|
||||
"httpMethod": "HTTP方法",
|
||||
"requestUrl": "请求URL",
|
||||
"inputRequestUrl": "请输入请求URL",
|
||||
"requestHeaders": "请求头",
|
||||
"requestBody": "请求体",
|
||||
"responseMapping": "响应映射",
|
||||
"addParam": "添加参数",
|
||||
"paramName": "参数名",
|
||||
"paramValue": "参数值",
|
||||
"paramLabel": "显示名",
|
||||
"deleteParam": "删除参数",
|
||||
"total": "总数",
|
||||
"limited": "限制",
|
||||
"dataSourceList": "数据源列表",
|
||||
"createDataSource": "创建数据源",
|
||||
"editDataSource": "编辑数据源",
|
||||
"deleteDataSourceConfirm": "确定要删除此数据源吗?",
|
||||
"deleteDataSourceSuccess": "删除成功",
|
||||
"createDataSourceSuccess": "创建成功",
|
||||
"updateDataSourceSuccess": "更新成功",
|
||||
"loadDataSourceFailed": "加载数据源失败",
|
||||
"testDataSourceFailed": "测试数据源失败",
|
||||
"previousStep": "上一步",
|
||||
"nextStep": "下一步",
|
||||
"basicInfoConfig": "基础信息配置",
|
||||
"status": "状态",
|
||||
"enable": "启用",
|
||||
"disable": "禁用",
|
||||
"selectDbSchema": "请在左侧选择数据库",
|
||||
"tip": "提示",
|
||||
"useParamPlaceholder": "使用",
|
||||
"onlySelectQuery": "作为参数占位符,只允许 SELECT 查询",
|
||||
"paramDefinition": "参数定义",
|
||||
"defineDataSourceParams": "定义数据源可接收的参数",
|
||||
"required": "必填",
|
||||
"action": "操作",
|
||||
"resultProcessing": "结果处理",
|
||||
"resultTypeListDesc": "直接返回数组数据,适用于表格、下拉选择等组件。",
|
||||
"resultTypeTreeDesc": "将平铺列表转换为树形结构,适用于树形选择、级联选择等组件。",
|
||||
"resultTypeObjectDesc": "返回第一条记录作为对象,适用于详情展示、表单回填等场景。",
|
||||
"resultTypeValueDesc": "返回第一条记录的第一个字段值,适用于统计数字、标题等场景。",
|
||||
"resultTypeChartAxisDesc": "转换为 xAxisData, seriesData 格式,适用于折线图、柱状图、面积图。",
|
||||
"resultTypeChartPieDesc": "转换为 seriesData: [name, value] 格式,适用于饼图、漏斗图。",
|
||||
"resultTypeChartGaugeDesc": "转换为 value, name, max 格式,适用于仪表盘、进度图。",
|
||||
"resultTypeChartRadarDesc": "转换为 indicator, seriesData 格式,适用于雷达图、多维对比。",
|
||||
"resultTypeChartScatterDesc": "转换为 seriesData: [x, y] 格式,适用于散点图、气泡图。",
|
||||
"resultTypeChartHeatmapDesc": "转换为 xAxisData, yAxisData, seriesData 格式,适用于热力图。",
|
||||
"treeConversionConfig": "树形转换配置",
|
||||
"refresh": "刷新",
|
||||
"fieldPreview": "字段预览",
|
||||
"clickTableToView": "点击表名查看",
|
||||
"selectTable": "请选择一个表",
|
||||
"noFieldInfo": "暂无字段信息",
|
||||
"clickFieldToInsert": "点击字段名插入到编辑器",
|
||||
"testDataSource": "测试数据源",
|
||||
"dataSourceInfo": "数据源信息",
|
||||
"code": "编码",
|
||||
"type": "类型",
|
||||
"name": "名称",
|
||||
"inputCode": "请输入编码",
|
||||
"all": "全部",
|
||||
"staticData": "静态数据",
|
||||
"staticLabel": "静态",
|
||||
"batchDelete": "批量删除",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"more": "更多",
|
||||
"copy": "复制",
|
||||
"deleteConfirmMessage": "确定要删除数据源 \"{name}\" 吗?",
|
||||
"deleteConfirmTitle": "删除确认",
|
||||
"deleteSuccess": "已删除数据源: {name}",
|
||||
"batchDeleteConfirmMessage": "确定要删除选中的 {count} 个数据源吗?",
|
||||
"batchDeleteConfirmTitle": "批量删除确认",
|
||||
"batchDeleteSuccess": "已删除 {count} 个数据源",
|
||||
"inputNewCode": "请输入新数据源编码",
|
||||
"copyDataSource": "复制数据源",
|
||||
"codeFormatError": "编码必须以字母开头,只能包含字母、数字和下划线",
|
||||
"copySuccess": "复制成功",
|
||||
"importExport": {
|
||||
"export": "导出",
|
||||
"import": "导入配置",
|
||||
"exportSuccess": "数据源配置已导出",
|
||||
"exportFailed": "导出失败",
|
||||
"importTitle": "导入数据源配置",
|
||||
"dragOrClick": "拖拽 JSON 文件到此处或点击上传",
|
||||
"onlyJson": "仅支持 .json 格式文件",
|
||||
"fileParseError": "文件解析失败,请确认文件格式正确",
|
||||
"checking": "正在检查...",
|
||||
"codeConflictTip": "数据源编码已存在,请修改编码后再导入",
|
||||
"codeAvailable": "数据源编码可用,可以导入",
|
||||
"newCodePlaceholder": "请输入新的数据源编码",
|
||||
"importSuccess": "数据源配置导入成功",
|
||||
"importFailed": "导入失败",
|
||||
"confirmImport": "确认导入",
|
||||
"reselect": "重新选择",
|
||||
"dataSourceInfo": "数据源信息",
|
||||
"appTip": "导入的数据源将归属到当前应用",
|
||||
"dbConnectionTip": "SQL 类型数据源的 db_connection 为连接名称,请确认目标环境已配置同名数据库连接"
|
||||
},
|
||||
"saveSuccess": "保存成功",
|
||||
"createTime": "创建时间",
|
||||
"cancel": "取消",
|
||||
"apiInterface": "API接口",
|
||||
"sqlQuery": "SQL查询",
|
||||
"resultTypeList": "列表",
|
||||
"resultTypeTree": "树形",
|
||||
"resultTypeSingleObject": "单对象",
|
||||
"resultTypeSingleValue": "单值",
|
||||
"resultTypeAxisChart": "轴向图表",
|
||||
"resultTypePieChart": "饼图数据",
|
||||
"resultTypeGauge": "仪表盘",
|
||||
"resultTypeRadarChart": "雷达图",
|
||||
"resultTypeScatterChart": "散点图",
|
||||
"resultTypeHeatmap": "热力图",
|
||||
"paramTypeString": "字符串",
|
||||
"paramTypeInteger": "整数",
|
||||
"paramTypeFloat": "浮点数",
|
||||
"paramTypeBoolean": "布尔值",
|
||||
"paramTypeDate": "日期",
|
||||
"paramTypeDatetime": "日期时间",
|
||||
"axisChartConfig": "轴向图表配置",
|
||||
"xAxisField": "X轴字段",
|
||||
"seriesField": "系列字段",
|
||||
"seriesName": "系列名称",
|
||||
"pieChartConfig": "饼图配置",
|
||||
"nameField": "名称字段",
|
||||
"valueField": "数值字段",
|
||||
"gaugeChartConfig": "仪表盘配置",
|
||||
"maxField": "最大值字段",
|
||||
"radarChartConfig": "雷达图配置",
|
||||
"indicatorNameField": "指标名称字段",
|
||||
"scatterChartConfig": "散点图配置",
|
||||
"xCoordinateField": "X坐标字段",
|
||||
"yCoordinateField": "Y坐标字段",
|
||||
"sizeField": "大小字段",
|
||||
"heatmapChartConfig": "热力图配置",
|
||||
"fieldMapping": "字段映射",
|
||||
"addMapping": "添加映射",
|
||||
"originalField": "原字段",
|
||||
"mappedField": "映射字段",
|
||||
"deleteMapping": "删除",
|
||||
"multipleFieldsComma": "多个字段用逗号分隔,如:{example}",
|
||||
"multipleNamesComma": "多个名称用逗号分隔,如:{example}",
|
||||
"dataFieldsForChart": "数据中用于绘制图表的数值字段",
|
||||
"optionalLegendNames": "可选,图例显示的名称,留空则使用字段名",
|
||||
"oneFieldPerSeries": "每个字段对应一个系列",
|
||||
"optionalBubbleChart": "如:size(可选,气泡图)",
|
||||
"optionalName": "如:name(可选)",
|
||||
"optionalMax": "如:max(可选)",
|
||||
"fieldNameMapping": "将原字段名映射为新字段名,如 id -> value",
|
||||
"cacheConfig": "缓存配置",
|
||||
"enableCache": "启用缓存",
|
||||
"cacheTime": "缓存时间",
|
||||
"cacheTimeUnit": "秒(0表示不缓存)",
|
||||
"test": "测试",
|
||||
"testParams": "测试参数",
|
||||
"executeTest": "执行测试",
|
||||
"testResult": "测试结果",
|
||||
"success": "成功",
|
||||
"failed": "失败",
|
||||
"returnedData": "返回 {count} 条数据",
|
||||
"reachedLimit": "已达上限 {limit} 条",
|
||||
"aiAssistant": "AI 助手",
|
||||
"aiSqlAssistant": "AI SQL 助手",
|
||||
"aiSqlAssistantTip": "AI 将根据你的需求和数据库结构自动生成 SQL 查询语句",
|
||||
"sqlWritingTip": "SQL 在「数据库连接」配置的默认库上执行。PostgreSQL/SQL Server/Oracle 请写 schema.table;MySQL 写 `库名`.`表名` 或表名;参数用 :name。第三方连接请在左侧树或 AI 选表后确认 db_connection 为连接 code。",
|
||||
"describeYourQuery": "描述你的查询需求",
|
||||
"queryPlaceholder": "例如:查询最近30天内,每天的新增用户数量和活跃用户数量",
|
||||
"quickExamples": "快速示例",
|
||||
"selectDataTable": "选择数据表",
|
||||
"modifySelection": "修改选择",
|
||||
"selectedTables": "已选 {count} 张表",
|
||||
"clear": "清空",
|
||||
"pleaseSelectTableFirst": "请先选择数据表",
|
||||
"tableRelationsCount": "已配置 {count} 个表关系",
|
||||
"includeTableRelations": "包含表关系信息(提高多表 JOIN 查询准确度)",
|
||||
"aiModel": "AI 模型",
|
||||
"selectModel": "选择模型",
|
||||
"generating": "生成中...",
|
||||
"generateSql": "生成 SQL",
|
||||
"generationThought": "生成思路",
|
||||
"generatedSql": "生成的 SQL",
|
||||
"paramSuggestions": "参数建议",
|
||||
"default": "默认",
|
||||
"insertToEditor": "插入到编辑器",
|
||||
"sqlGenerateSuccess": "SQL 生成成功",
|
||||
"sqlGenerateFailed": "SQL 生成失败",
|
||||
"sqlInserted": "已插入到编辑器",
|
||||
"copied": "已复制到剪贴板",
|
||||
"pleaseInputQuestion": "请输入查询需求",
|
||||
"pleaseSelectModel": "请选择 AI 模型",
|
||||
"pleaseSelectTable": "请先选择数据表",
|
||||
"aiSqlInsertSuccess": "SQL 已插入,参数已自动配置",
|
||||
"manualMode": "手动",
|
||||
"aiMode": "AI",
|
||||
"aiConfig": "AI 配置",
|
||||
"aiSqlPlaceholder": "AI 生成的 SQL 将显示在此处...",
|
||||
"querying": "正在查询...",
|
||||
"dataView": "数据",
|
||||
"chartView": "图表",
|
||||
"clickTestToExecute": "点击上方按钮执行测试",
|
||||
"rawData": "原始数据",
|
||||
"selectChartTypeToPreview": "请在结果处理中选择图表类型以预览",
|
||||
"apiConfig": "API 配置",
|
||||
"urlPlaceholder": "https://api.example.com/data",
|
||||
"apiTabBasic": "基本",
|
||||
"apiTabAuth": "认证",
|
||||
"apiTabQueryParams": "Query参数",
|
||||
"apiTabBody": "请求体",
|
||||
"apiTabAdvanced": "高级",
|
||||
"timeout": "超时时间",
|
||||
"second": "秒",
|
||||
"dataPath": "数据路径",
|
||||
"dataPathPlaceholder": "如 data.list",
|
||||
"description": "描述",
|
||||
"authType": "认证类型",
|
||||
"authNone": "无认证",
|
||||
"bearerTokenPlaceholder": "输入 Token,支持 {param} 占位符",
|
||||
"username": "用户名",
|
||||
"usernamePlaceholder": "请输入用户名",
|
||||
"password": "密码",
|
||||
"passwordPlaceholder": "请输入密码",
|
||||
"keyPosition": "Key 位置",
|
||||
"keyName": "Key 名称",
|
||||
"keyValue": "Key 值",
|
||||
"bodyType": "类型",
|
||||
"bodyTypeNone": "无",
|
||||
"contentType": "Content-Type",
|
||||
"retryCount": "重试次数",
|
||||
"retryInterval": "重试间隔",
|
||||
"proxy": "代理地址",
|
||||
"followRedirects": "跟随重定向",
|
||||
"verifySSL": "验证SSL",
|
||||
"successCondition": "成功条件",
|
||||
"successStatusCodes": "状态码",
|
||||
"successStatusCodesPlaceholder": "如 200, 201(留空则检查 2xx)",
|
||||
"successFieldPath": "字段路径",
|
||||
"successFieldPathPlaceholder": "如 code",
|
||||
"successFieldValue": "期望值",
|
||||
"apiTabHeaders": "请求头",
|
||||
"viewRequest": "查看请求",
|
||||
"queryParamsHint": "参数会自动同步到 URL",
|
||||
"headersHint": "自定义 HTTP 请求头",
|
||||
"addHeader": "添加请求头",
|
||||
"headerName": "名称",
|
||||
"headerValue": "值",
|
||||
"noQueryParams": "暂无 Query 参数",
|
||||
"noHeaders": "暂无请求头"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user