feat: i18n

This commit is contained in:
meilin.huang
2024-11-20 22:43:53 +08:00
parent 74ae031853
commit 99a746085b
308 changed files with 8177 additions and 3880 deletions

View File

@@ -11,7 +11,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@vueuse/core": "^11.1.0",
"@vueuse/core": "^11.2.0",
"asciinema-player": "^3.8.1",
"axios": "^1.6.2",
"clipboard": "^2.0.11",
@@ -19,7 +19,7 @@
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"echarts": "^5.5.1",
"element-plus": "^2.8.6",
"element-plus": "^2.8.8",
"js-base64": "^3.7.7",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
@@ -28,7 +28,7 @@
"monaco-sql-languages": "^0.12.2",
"monaco-themes": "^0.4.4",
"nprogress": "^0.2.0",
"pinia": "^2.2.5",
"pinia": "^2.2.6",
"qrcode.vue": "^3.5.1",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.3",
@@ -36,7 +36,8 @@
"sql-formatter": "^15.4.5",
"trzsz": "^1.1.5",
"uuid": "^9.0.1",
"vue": "^3.5.12",
"vue": "^3.5.13",
"vue-i18n": "^10.0.4",
"vue-router": "^4.4.5",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",
@@ -51,16 +52,16 @@
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"@vitejs/plugin-vue": "^5.1.4",
"@vue/compiler-sfc": "^3.5.12",
"@vitejs/plugin-vue": "^5.2.0",
"@vue/compiler-sfc": "^3.5.13",
"code-inspector-plugin": "^0.4.5",
"dotenv": "^16.3.1",
"eslint": "^8.35.0",
"eslint-plugin-vue": "^9.28.0",
"prettier": "^3.2.5",
"sass": "^1.80.5",
"sass": "^1.80.6",
"typescript": "^5.6.3",
"vite": "^5.4.10",
"vite": "^5.4.11",
"vue-eslint-parser": "^9.4.3"
},
"browserslist": [
@@ -68,4 +69,4 @@
"last 2 versions",
"not dead"
]
}
}

View File

@@ -1,24 +1,26 @@
<template>
<div class="h100">
<el-watermark
:zIndex="10000000"
:width="210"
v-if="themeConfig.isWatermark"
:font="{ color: 'rgba(180, 180, 180, 0.3)' }"
:content="themeConfig.watermarkText"
class="h100"
>
<router-view v-show="themeConfig.lockScreenTime !== 0" />
</el-watermark>
<router-view v-if="!themeConfig.isWatermark" v-show="themeConfig.lockScreenTime !== 0" />
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<div class="h100">
<el-watermark
:zIndex="10000000"
:width="210"
v-if="themeConfig.isWatermark"
:font="{ color: 'rgba(180, 180, 180, 0.3)' }"
:content="themeConfig.watermarkText"
class="h100"
>
<router-view v-show="themeConfig.lockScreenTime !== 0" />
</el-watermark>
<router-view v-if="!themeConfig.isWatermark" v-show="themeConfig.lockScreenTime !== 0" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
</div>
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
</div>
</el-config-provider>
</template>
<script setup lang="ts" name="app">
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
@@ -26,6 +28,10 @@ import LockScreen from '@/layout/lockScreen/index.vue';
import Setings from '@/layout/navBars/breadcrumb/setings.vue';
import mittBus from '@/common/utils/mitt';
import { useIntervalFn } from '@vueuse/core';
import { useI18n } from 'vue-i18n';
import EnumValue from './common/Enum';
import { I18nEnum } from './common/commonEnum';
import { saveThemeConfig } from './common/utils/storage';
const setingsRef = ref();
const route = useRoute();
@@ -33,6 +39,9 @@ const route = useRoute();
const themeConfigStores = useThemeConfig();
const { themeConfig } = storeToRefs(themeConfigStores);
// 定义变量内容
const { locale, t } = useI18n();
// 布局配置弹窗打开
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
@@ -67,6 +76,31 @@ watch(
}
);
watch(
() => themeConfig.value.globalI18n,
(val) => {
locale.value = val;
}
);
watch(
themeConfig,
(val) => {
saveThemeConfig(val);
},
{ deep: true }
);
// 获取全局组件大小
const getGlobalComponentSize = computed(() => {
return themeConfig.value.globalComponentSize;
});
// 获取全局 i18n
const getGlobalI18n = computed(() => {
return EnumValue.getEnumByValue(I18nEnum, locale.value)?.extra.el;
});
// 刷新水印时间
const { pause, resume } = useIntervalFn(() => {
if (!themeConfig.value.isWatermark) {
@@ -96,7 +130,7 @@ watch(
() => route.path,
() => {
nextTick(() => {
document.title = `${route.meta.title} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
document.title = `${t((route.meta.title as string) || '')} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
});
}
);

File diff suppressed because one or more lines are too long

View File

@@ -1,107 +1,121 @@
{
"id": "3953964",
"name": "mayfly-go",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "23957582",
"name": "MongoDB",
"font_class": "mongo",
"unicode": "e646",
"unicode_decimal": 58950
},
{
"icon_id": "4969649",
"name": "Redis",
"font_class": "op-redis",
"unicode": "e728",
"unicode_decimal": 59176
},
{
"icon_id": "22442993",
"name": "PostgreSQL",
"font_class": "op-postgres",
"unicode": "e8b7",
"unicode_decimal": 59575
},
{
"icon_id": "12295203",
"name": "达梦数据库",
"font_class": "db-dm",
"unicode": "e6f0",
"unicode_decimal": 59120
},
{
"icon_id": "10055634",
"name": "云数据库MongoDB",
"font_class": "op-mongo",
"unicode": "e7d7",
"unicode_decimal": 59351
},
{
"icon_id": "10055642",
"name": "数据库 RDS MySQL",
"font_class": "op-mysql",
"unicode": "e7d8",
"unicode_decimal": 59352
},
{
"icon_id": "3876165",
"name": "redis",
"font_class": "redis",
"unicode": "e619",
"unicode_decimal": 58905
},
{
"icon_id": "25271976",
"name": "oracle",
"font_class": "oracle",
"unicode": "e507",
"unicode_decimal": 58631
},
{
"icon_id": "8105644",
"name": "mariadb",
"font_class": "mariadb",
"unicode": "e513",
"unicode_decimal": 58643
},
{
"icon_id": "13601813",
"name": "sqlite",
"font_class": "sqlite",
"unicode": "e546",
"unicode_decimal": 58694
},
{
"icon_id": "29340317",
"name": "temp-mssql",
"font_class": "MSSQLNATIVE",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "7699332",
"name": "gaussdb",
"font_class": "gauss",
"unicode": "e683",
"unicode_decimal": 59011
},
{
"icon_id": "34836637",
"name": "kingbase",
"font_class": "kingbase",
"unicode": "e882",
"unicode_decimal": 59522
},
{
"icon_id": "33047500",
"name": "vastbase",
"font_class": "vastbase",
"unicode": "e62b",
"unicode_decimal": 58923
}
]
}
"id": "3953964",
"name": "mayfly-go",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "2967035",
"name": "符号-英文",
"font_class": "fuhao-yingwen",
"unicode": "e712",
"unicode_decimal": 59154
},
{
"icon_id": "26283783",
"name": "符号-中文",
"font_class": "fuhao-zhongwen",
"unicode": "e603",
"unicode_decimal": 58883
},
{
"icon_id": "23957582",
"name": "MongoDB",
"font_class": "mongo",
"unicode": "e646",
"unicode_decimal": 58950
},
{
"icon_id": "4969649",
"name": "Redis",
"font_class": "op-redis",
"unicode": "e728",
"unicode_decimal": 59176
},
{
"icon_id": "22442993",
"name": "PostgreSQL",
"font_class": "op-postgres",
"unicode": "e8b7",
"unicode_decimal": 59575
},
{
"icon_id": "12295203",
"name": "达梦数据库",
"font_class": "db-dm",
"unicode": "e6f0",
"unicode_decimal": 59120
},
{
"icon_id": "10055634",
"name": "云数据库MongoDB",
"font_class": "op-mongo",
"unicode": "e7d7",
"unicode_decimal": 59351
},
{
"icon_id": "10055642",
"name": "云数据库 RDS MySQL",
"font_class": "op-mysql",
"unicode": "e7d8",
"unicode_decimal": 59352
},
{
"icon_id": "3876165",
"name": "redis",
"font_class": "redis",
"unicode": "e619",
"unicode_decimal": 58905
},
{
"icon_id": "25271976",
"name": "oracle",
"font_class": "oracle",
"unicode": "e507",
"unicode_decimal": 58631
},
{
"icon_id": "8105644",
"name": "mariadb",
"font_class": "mariadb",
"unicode": "e513",
"unicode_decimal": 58643
},
{
"icon_id": "13601813",
"name": "sqlite",
"font_class": "sqlite",
"unicode": "e546",
"unicode_decimal": 58694
},
{
"icon_id": "29340317",
"name": "temp-mssql",
"font_class": "MSSQLNATIVE",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "7699332",
"name": "gaussdb",
"font_class": "gauss",
"unicode": "e683",
"unicode_decimal": 59011
},
{
"icon_id": "34836637",
"name": "kingbase",
"font_class": "kingbase",
"unicode": "e882",
"unicode_decimal": 59522
},
{
"icon_id": "33047500",
"name": "vastbase",
"font_class": "vastbase",
"unicode": "e62b",
"unicode_decimal": 58923
}
]
}

View File

@@ -1,4 +1,13 @@
import EnumValue from './Enum';
// element plus 自带国际化
import zhcnLocale from 'element-plus/es/locale/lang/zh-cn';
import enLocale from 'element-plus/es/locale/lang/en';
// i18n
export const I18nEnum = {
ZhCn: EnumValue.of('zh-cn', '简体中文').setExtra({ icon: 'iconfont icon-fuhao-zhongwen', el: zhcnLocale }),
En: EnumValue.of('en', 'English').setExtra({ icon: 'iconfont icon-fuhao-yingwen', el: enLocale }),
};
// 资源类型
export const ResourceTypeEnum = {

View File

@@ -6,6 +6,7 @@ export default {
otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
getPublicKey: () => request.get('/common/public-key'),
getConfigValue: (params: any) => request.get('/sys/configs/value', params),
getServerConf: () => request.get('/sys/configs/server'),
oauth2LoginConfig: () => request.get('/auth/oauth2-config'),
changePwd: (param: any) => request.post('/sys/accounts/change-pwd', param),
captcha: () => request.get('/sys/captcha'),

View File

@@ -1,9 +1,11 @@
import { i18n } from '@/i18n';
export const AccountUsernamePattern = {
pattern: /^[a-zA-Z0-9_]{5,20}$/g,
message: '只允许输入5-20位大小写字母、数字、_-.:',
pattern: /^[a-zA-Z0-9_]{5,16}$/g,
message: i18n.global.t('system.account.usernamePatternErrMsg'),
};
export const ResourceCodePattern = {
pattern: /^[a-zA-Z0-9_\-.:]{1,32}$/g,
message: '只允许输入1-32位大小写字母、数字、_-.:',
message: i18n.global.t('system.menu.resourceCodePatternErrMsg'),
};

View File

@@ -73,6 +73,15 @@ export async function getMachineConfig(): Promise<any> {
}
}
/**
* 获取系统服务启动配置
*
* @returns 配置信息
*/
export async function getServerConf(): Promise<any> {
return openApi.getServerConf();
}
/**
* 获取系统配置值
*

View File

@@ -1,4 +0,0 @@
import { getLocal } from '@/common/utils/storage';
// 全局组件大小
export const globalComponentSize = getLocal('themeConfig')?.globalComponentSize;

View File

@@ -69,7 +69,7 @@ export function convertToBytes(sizeStr: string) {
* @returns
*/
export function formatTime(time: number, unit: string = 's') {
const units = {
const units: any = {
y: 31536000,
M: 2592000,
d: 86400,

View File

@@ -177,3 +177,40 @@ export function isPrefixSubsequence(prefix: string, targetTemplate: string) {
// 如果prefix的所有字符都被找到返回true
return i === prefix.length;
}
/**
* 生成随机密码
* @param length 密码长度
*/
export function randomPassword(length = 10) {
const lowerCase = 'abcdefghijklmnopqrstuvwxyz';
const upperCase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const numbers = '0123456789';
const specialChars = '!@#$%^&*()-_=+[]{}|;:,.<>?';
// 确保每个类别至少包含一个字符
let password = [getRandomChar(lowerCase), getRandomChar(upperCase), getRandomChar(numbers), getRandomChar(specialChars)];
// 剩余字符从所有字符集中随机选择
const allChars = lowerCase + upperCase + numbers + specialChars;
for (let i = 4; i < length; i++) {
password.push(getRandomChar(allChars));
}
// 打乱数组顺序以增加随机性
shuffleArray(password);
return password.join('');
}
function getRandomChar(charSet: string) {
const randomIndex = Math.floor(Math.random() * charSet.length);
return charSet[randomIndex];
}
function shuffleArray(array: string[]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}

View File

@@ -16,7 +16,7 @@
:is="`el-option`"
v-for="(col, index) in item.options"
:key="index"
:label="col[fieldNames.label]"
:label="$t(col[fieldNames.label])"
:value="col[fieldNames.value]"
></component>
</template>
@@ -28,6 +28,9 @@
<script setup lang="ts" name="SearchFormItem">
import { computed } from 'vue';
import { SearchItem } from '../index';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
interface SearchFormItemProps {
item: SearchItem;
@@ -70,7 +73,7 @@ const handleEvents = computed(() => {
// 处理默认 placeholder
const placeholder = computed(() => {
const search = props.item;
const label = search.label;
const label = t(search.label);
if (['datetimerange', 'daterange', 'monthrange'].includes(search?.props?.type) || search?.props?.isRange) {
return {
rangeSeparator: search?.props?.rangeSeparator ?? '至',
@@ -78,7 +81,9 @@ const placeholder = computed(() => {
endPlaceholder: search?.props?.endPlaceholder ?? '结束时间',
};
}
const placeholder = search?.props?.placeholder ?? (search?.type?.includes('input') ? `请输入${label}` : `请选择${label}`);
return { placeholder };
const placeholder =
search?.props?.placeholder ?? (search?.type?.includes('input') ? t('common.pleaseInput', { label }) : t('common.pleaseSelect', { label }));
return { placeholder: t(placeholder) };
});
</script>

View File

@@ -22,7 +22,7 @@
@click="onCurrentContextmenuClick(v)"
>
<SvgIcon :name="v.icon" />
<span>{{ v.txt }}</span>
<span>{{ $t(v.txt) }}</span>
</li>
</template>
</ul>

View File

@@ -1,43 +1,43 @@
<template>
<div class="crontab">
<el-tabs v-model="state.activeName" @tab-change="changeTab(state.activeName)" type="border-card">
<el-tab-pane label="" name="second" v-if="shouldHide('second')">
<el-tab-pane :label="$t('components.crontab.second')" name="second" v-if="shouldHide('second')">
<CrontabSecond :cron="crontabValueObj" ref="secondRef" />
</el-tab-pane>
<el-tab-pane label="分钟" name="min" v-if="shouldHide('min')">
<el-tab-pane :label="$t('components.crontab.minute')" name="min" v-if="shouldHide('min')">
<CrontabMin :cron="crontabValueObj" ref="minRef" />
</el-tab-pane>
<el-tab-pane label="小时" name="hour" v-if="shouldHide('hour')">
<el-tab-pane :label="$t('components.crontab.hour')" name="hour" v-if="shouldHide('hour')">
<CrontabHour :cron="crontabValueObj" ref="hourRef" />
</el-tab-pane>
<el-tab-pane label="" name="day" v-if="shouldHide('day')">
<el-tab-pane :label="$t('components.crontab.day')" name="day" v-if="shouldHide('day')">
<CrontabDay :cron="crontabValueObj" ref="dayRef" />
</el-tab-pane>
<el-tab-pane label="" name="mouth" v-if="shouldHide('mouth')">
<el-tab-pane :label="$t('components.crontab.month')" name="mouth" v-if="shouldHide('mouth')">
<CrontabMouth :cron="crontabValueObj" ref="mouthRef" />
</el-tab-pane>
<el-tab-pane label="" name="week" v-if="shouldHide('week')">
<el-tab-pane :label="$t('components.crontab.week')" name="week" v-if="shouldHide('week')">
<CrontabWeek :cron="crontabValueObj" ref="weekRef" />
</el-tab-pane>
<el-tab-pane label="" name="year" v-if="shouldHide('year')">
<el-tab-pane :label="$t('components.crontab.year')" name="year" v-if="shouldHide('year')">
<CrontabYear :cron="crontabValueObj" ref="yearRef" />
</el-tab-pane>
</el-tabs>
<div class="popup-main">
<div class="popup-result">
<p class="title">时间表达式</p>
<p class="title">{{ $t('components.crontab.timeExpression') }}</p>
<table>
<thead>
<tr>
<th v-for="item of tabTitles" width="40" :key="item">{{ item }}</th>
<th>crontab完整表达式</th>
<th>{{ $t('components.crontab.crontabCompleteExpression') }}</th>
</tr>
</thead>
<tbody>
@@ -73,9 +73,9 @@
<CrontabResult :ex="crontabValueString"></CrontabResult>
<div class="pop_btn">
<el-button size="small" @click="hidePopup">取消</el-button>
<el-button size="small" type="warning" @click="clearCron">重置</el-button>
<el-button size="small" type="primary" @click="submitFill">确定</el-button>
<el-button size="small" @click="hidePopup">{{ $t('common.cancel') }}</el-button>
<el-button size="small" type="warning" @click="clearCron">{{ $t('common.reset') }}</el-button>
<el-button size="small" type="primary" @click="submitFill">{{ $t('common.confirm') }}</el-button>
</div>
</div>
</div>

View File

@@ -1,42 +1,45 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 允许的通配符[, - * / L M] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.day') }}{{ $t('components.crontab.dayCrontype1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2"> 不指定 </el-radio>
<el-radio v-model="radioValue" :label="2"> {{ $t('components.crontab.crontype2') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
周期从
<el-input-number v-model="cycle01" :min="0" :max="31" /> - <el-input-number v-model="cycle02" :min="0" :max="31" />
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="0" :max="31" /> - <el-input-number v-model="cycle02" :min="0" :max="31" />
{{ $t('components.crontab.day') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="4">
<el-input-number v-model="average01" :min="0" :max="31" /> 号开始,每 <el-input-number v-model="average02" :min="0" :max="31" /> 日执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="0" :max="31" /> {{ $t('components.crontab.crontypeStartDay') }}
{{ $t('components.crontab.crontypeEvery') }} <el-input-number v-model="average02" :min="0" :max="31" />
{{ $t('components.crontab.crontypeExecDay') }}
</el-radio>
</el-form-item>
<el-form-item>
<!-- <el-form-item>
<el-radio v-model="radioValue" :label="5">
每月
<el-input-number v-model="workday" :min="0" :max="31" /> 号最近的那个工作日
</el-radio>
</el-form-item>
</el-form-item> -->
<el-form-item>
<el-radio v-model="radioValue" :label="6"> 本月最后一天 </el-radio>
<el-radio v-model="radioValue" :label="6"> {{ $t('components.crontab.monthLastDay') }} </el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="7" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 7" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="7" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 7" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 31" :key="item" :value="`${item}`">{{ item }}</el-option>
</el-select>
</div>

View File

@@ -1,27 +1,30 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 小时允许的通配符[, - * /] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.hour') }}{{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
周期从
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" /> 小时
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" />
{{ $t('components.crontab.hour') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
<el-input-number v-model="average01" :min="0" :max="60" /> 小时开始,每 <el-input-number v-model="average02" :min="0" :max="60" /> 小时执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="0" :max="60" /> {{ $t('components.crontab.crontypeStartHour') }}
{{ $t('components.crontab.crontypeEvery') }} <el-input-number v-model="average02" :min="0" :max="60" />
{{ $t('components.crontab.crontypeExecHour') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="4" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="4" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 60" :key="item" :value="`${item - 1}`">{{ item - 1 }}</el-option>
</el-select>
</div>

View File

@@ -1,10 +1,10 @@
<template>
<el-input v-model="cron" placeholder="可点击左边按钮配置">
<el-input v-model="cron" :placeholder="$t('components.crontab.crontabInputPlaceholder')">
<template #prepend>
<el-button @click="showCron = true" icon="Pointer"></el-button>
</template>
</el-input>
<el-dialog title="生成 cron" v-model="showCron" width="600px">
<el-dialog :title="$t('components.crontab.crontabTitle')" v-model="showCron" width="660px">
<Crontab :expression="cron" @hide="showCron = false" @fill="(ex: any) => (cron = ex)" />
</el-dialog>
</template>

View File

@@ -1,27 +1,30 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 分钟允许的通配符[, - * /] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.minute') }}{{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
周期从
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" /> 分钟
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" />
{{ $t('components.crontab.minute') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
<el-input-number v-model="average01" :min="0" :max="60" /> 分钟开始,每 <el-input-number v-model="average02" :min="0" :max="60" /> 分钟执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="0" :max="60" /> {{ $t('components.crontab.crontypeStartMin') }}
{{ $t('components.crontab.crontypeEvery') }} <el-input-number v-model="average02" :min="0" :max="60" />
{{ $t('components.crontab.crontypeExecMin') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="4" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="4" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 60" :key="item" :value="`${item - 1}`">{{ item - 1 }}</el-option>
</el-select>
</div>

View File

@@ -1,27 +1,30 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 允许的通配符[, - * /] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.month') }}{{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
周期从
<el-input-number v-model="cycle01" :min="1" :max="12" /> - <el-input-number v-model="cycle02" :min="1" :max="12" />
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="1" :max="12" /> - <el-input-number v-model="cycle02" :min="1" :max="12" />
{{ $t('components.crontab.month') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
<el-input-number v-model="average01" :min="1" :max="12" /> 月开始,每 <el-input-number v-model="average02" :min="1" :max="12" /> 月月执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="1" :max="12" /> {{ $t('components.crontab.crontypeStartMonth') }}
{{ $t('components.crontab.crontypeEvery') }}
<el-input-number v-model="average02" :min="1" :max="12" /> {{ $t('components.crontab.crontypeExecMonth') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="4" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="4" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 12" :key="item" :value="`${item}`">{{ item }}</el-option>
</el-select>
</div>

View File

@@ -1,11 +1,11 @@
<template>
<div class="popup-result">
<p class="title">最近5次运行时间</p>
<p class="title">{{ $t('components.crontab.last5runTimes') }}</p>
<ul class="popup-result-scroll">
<template v-if="isShow">
<li v-for="item in resultList" :key="item">{{ item }}</li>
</template>
<li v-else>计算结果中...</li>
<li v-else>{{ $t('components.crontab.calculationing') }}...</li>
</ul>
</div>
</template>

View File

@@ -1,27 +1,30 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 允许的通配符[, - * /] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.second') }}{{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2">
周期从
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" />
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="0" :max="60" /> - <el-input-number v-model="cycle02" :min="0" :max="60" />
{{ $t('components.crontab.second') }}
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
<el-input-number v-model="average01" :min="0" :max="60" /> 秒开始,每 <el-input-number v-model="average02" :min="0" :max="60" /> 秒执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="0" :max="60" /> {{ $t('components.crontab.crontypeStartSecond') }}
{{ $t('components.crontab.crontypeEvery') }}<el-input-number v-model="average02" :min="0" :max="60" />
{{ $t('components.crontab.crontypeExecSecond') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="4" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="4" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 4" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 60" :key="item" :value="`${item - 1}`">{{ item - 1 }}</el-option>
</el-select>
</div>

View File

@@ -1,22 +1,22 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model="radioValue" :label="1"> 允许的通配符[, - * / L #] </el-radio>
<el-radio v-model="radioValue" :label="1"> {{ $t('components.crontab.weekCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="2"> 不指定 </el-radio>
<el-radio v-model="radioValue" :label="2"> {{ $t('components.crontab.crontype2') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model="radioValue" :label="3">
周期从星期
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="1" :max="7" /> -
<el-input-number v-model="cycle02" :min="1" :max="7" />
</el-radio>
</el-form-item>
<el-form-item>
<!-- <el-form-item>
<el-radio v-model="radioValue" :label="4">
<el-input-number v-model="average01" :min="1" :max="4" /> 周的星期
@@ -29,13 +29,13 @@
本月最后一个星期
<el-input-number v-model="weekday" :min="1" :max="7" />
</el-radio>
</el-form-item>
</el-form-item> -->
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="6" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 6" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-option v-for="(item, index) of weekList" :label="item" :key="index" :value="`${index + 1}`">{{ item }}</el-option>
<el-radio v-model="radioValue" :label="6" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 6" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="(item, index) of weekList" :label="item" :key="index" :value="`${index + 1}`">{{ $t(item) }}</el-option>
</el-select>
</div>
</el-form-item>
@@ -56,7 +56,15 @@ const state = reactive({
average01: 1,
average02: 1,
checkboxList: [] as any,
weekList: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
weekList: [
'components.crontab.monday',
'components.crontab.tuesday',
'components.crontab.wednesday',
'components.crontab.thursday',
'components.crontab.friday',
'components.crontab.saturday',
'components.crontab.sunday',
],
});
const { radioValue, cycle01, cycle02, average01, average02, checkboxList, weekday, weekList } = toRefs(state);

View File

@@ -1,16 +1,16 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio :label="1" v-model="radioValue"> 不填允许的通配符[, - * /] </el-radio>
<el-radio :label="1" v-model="radioValue"> {{ $t('components.crontab.hourCronType1') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="2" v-model="radioValue"> 每年 </el-radio>
<el-radio :label="2" v-model="radioValue"> {{ $t('components.crontab.yearly') }} </el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="3" v-model="radioValue">
周期从
{{ $t('components.crontab.crontype3') }}
<el-input-number v-model="cycle01" :min="fullYear" /> -
<el-input-number v-model="cycle02" :min="fullYear" />
</el-radio>
@@ -18,15 +18,17 @@
<el-form-item>
<el-radio :label="4" v-model="radioValue">
<el-input-number v-model="average01" :min="fullYear" /> 年开始,每 <el-input-number v-model="average02" :min="fullYear" /> 年执行一次
{{ $t('components.crontab.crontypeFrom') }}
<el-input-number v-model="average01" :min="fullYear" /> {{ $t('components.crontab.crontypeStartYear') }}
{{ $t('components.crontab.crontypeEvery') }}
<el-input-number v-model="average02" :min="fullYear" /> {{ $t('components.crontab.crontypeExecYear') }}
</el-radio>
</el-form-item>
<el-form-item>
<div class="flex-align-center w100">
<el-radio v-model="radioValue" :label="5" class="mr5"> 指定 </el-radio>
<el-select @click="radioValue = 5" class="w100" clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-radio v-model="radioValue" :label="5" class="mr5"> {{ $t('components.crontab.appoint') }} </el-radio>
<el-select @click="radioValue = 5" class="w100" clearable v-model="checkboxList" multiple>
<el-option v-for="item in 9" :key="item" :value="`${item - 1 + fullYear}`" :label="item - 1 + fullYear" />
</el-select>
</div>

View File

@@ -1,10 +1,18 @@
<template>
<div class="dynamic-form">
<el-form v-bind="$attrs" ref="formRef" :model="modelValue" label-width="auto">
<el-form-item v-for="item in props.formItems as any" :key="item.name" :prop="item.model" :label="item.name" :required="item.required ?? true">
<el-input v-if="!item.options" v-model="modelValue[item.model]" :placeholder="item.placeholder" autocomplete="off" clearable></el-input>
<el-form-item v-for="item in props.formItems as any" :key="item.name" :prop="item.model" :label="$t(item.name)" :required="item.required ?? true">
<el-input v-if="!item.options" v-model="modelValue[item.model]" :placeholder="$t(item.placeholder)" autocomplete="off" clearable></el-input>
<el-select v-else v-model="modelValue[item.model]" :placeholder="item.placeholder" filterable autocomplete="off" clearable style="width: 100%">
<el-select
v-else
v-model="modelValue[item.model]"
:placeholder="$t(item.placeholder)"
filterable
autocomplete="off"
clearable
style="width: 100%"
>
<el-option v-for="option in item.options.split(',')" :key="option" :label="option" :value="option" />
</el-select>
</el-form-item>

View File

@@ -6,8 +6,8 @@
<template #footer>
<span>
<slot name="btns">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="confirm"> </el-button>
<el-button @click="dialogVisible = false">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="confirm">{{ $t('common.confirm') }}</el-button>
</slot>
</span>
</template>

View File

@@ -1,41 +1,41 @@
<template>
<div class="dynamic-form-edit w100">
<el-table :data="formItems" stripe class="w100" empty-text="暂无表单项">
<el-table :data="formItems" stripe class="w100">
<el-table-column prop="name" label="model" min-width="100px">
<template #header>
<el-button class="ml0" type="primary" circle size="small" icon="Plus" @click="addItem()"> </el-button>
<span class="ml10">model</span>
<span class="ml10">model field</span>
</template>
<template #default="scope">
<el-input v-model="scope.row['model']" placeholder="字段model" clearable> </el-input>
<el-input v-model="scope.row['model']" :placeholder="$t('components.df.fieldModelPlaceholder')" clearable> </el-input>
</template>
</el-table-column>
<el-table-column prop="name" label="label" min-width="100px">
<el-table-column prop="name" :label="$t('components.df.fieldLabel')" min-width="100px">
<template #default="scope">
<el-input v-model="scope.row['name']" placeholder="字段title" clearable> </el-input>
<el-input v-model="scope.row['name']" clearable> </el-input>
</template>
</el-table-column>
<el-table-column prop="placeholder" label="字段说明" min-width="140px">
<el-table-column prop="placeholder" :label="$t('components.df.fieldPlaceholder')" min-width="140px">
<template #default="scope">
<el-input v-model="scope.row['placeholder']" placeholder="字段说明" clearable> </el-input>
<el-input v-model="scope.row['placeholder']" clearable> </el-input>
</template>
</el-table-column>
<el-table-column prop="options" label="可选值" min-width="140px">
<el-table-column prop="options" :label="$t('components.df.optionalValues')" min-width="140px">
<template #default="scope">
<el-input v-model="scope.row['options']" placeholder="可选值 ,分割" clearable> </el-input>
<el-input v-model="scope.row['options']" :placeholder="$t('components.df.optionalValuesPlaceholder')" clearable> </el-input>
</template>
</el-table-column>
<el-table-column prop="required" label="必填" min-width="40px">
<el-table-column prop="required" :label="$t('components.df.required')" min-width="65px">
<template #default="scope">
<el-checkbox v-model="scope.row['required']" />
</template>
</el-table-column>
<el-table-column label="操作" wdith="20px">
<el-table-column :label="$t('common.operation')" wdith="20px">
<template #default="scope">
<el-button type="danger" @click="deleteItem(scope.$index)" icon="delete" plain></el-button>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<el-select v-bind="$attrs" v-model="modelValue">
<el-option v-for="item in props.enums" :key="item.value" :label="item.label" :value="item.value"> </el-option>
<el-option v-for="item in props.enums" :key="item.value" :label="$t(item.label)" :value="item.value"> </el-option>
</el-select>
</template>

View File

@@ -1,5 +1,5 @@
<template>
<el-tag :disable-transitions="true" v-bind="$attrs" :type="type" :color="color" effect="plain">{{ enumLabel }}</el-tag>
<el-tag :disable-transitions="true" v-bind="$attrs" :type="type" :color="color" effect="plain">{{ $t(enumLabel) }}</el-tag>
</template>
<script lang="ts" setup>

View File

@@ -2,7 +2,8 @@
<div>
<transition name="el-zoom-in-top">
<!-- 查询表单 -->
<SearchForm v-if="isShowSearch" :items="tableSearchItems" v-model="queryForm" :search="search" :reset="reset" :search-col="searchCol">
<SearchForm v-if="isShowSearch" :items="tableSearchItems" v-model="queryForm" :search="search"
:reset="reset" :search-col="searchCol">
<!-- 遍历父组件传入的 solts 透传给子组件 -->
<template v-for="(_, key) in useSlots()" v-slot:[key]>
<slot :name="key"></slot>
@@ -27,54 +28,45 @@
<SvgIcon :size="16" name="CaretBottom" class="mr4 mt6 simple-search-form-btn" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="searchItem in searchItems"
:key="searchItem.prop"
@click="changeSimpleFormItem(searchItem)"
>
{{ searchItem.label }}
<el-dropdown-item v-for="searchItem in searchItems"
:key="searchItem.prop" @click="changeSimpleFormItem(searchItem)">
{{ $t(searchItem.label) }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="simple-search-form-label mt5">
<el-text truncated tag="b">{{ `${nowSearchItem?.label} : ` }}</el-text>
<el-text truncated tag="b">{{ `${$t(nowSearchItem?.label)} : ` }}</el-text>
</div>
<el-form-item style="width: 200px" :key="nowSearchItem.prop">
<SearchFormItem
@keyup.enter.native="searchFormItemKeyUpEnter"
v-if="!nowSearchItem.slot"
:item="nowSearchItem"
v-model="queryForm[nowSearchItem.prop]"
/>
<SearchFormItem @keyup.enter.native="searchFormItemKeyUpEnter"
v-if="!nowSearchItem.slot" :item="nowSearchItem"
v-model="queryForm[nowSearchItem.prop]" />
<slot @keyup.enter.native="searchFormItemKeyUpEnter" v-else :name="nowSearchItem.slot"></slot>
<slot @keyup.enter.native="searchFormItemKeyUpEnter" v-else
:name="nowSearchItem.slot">
</slot>
</el-form-item>
</div>
<div>
<el-button v-if="showToolButton('search') && searchItems?.length" icon="Search" circle @click="search" />
<el-button v-if="showToolButton('search') && searchItems?.length" icon="Search"
circle @click="search" />
<!-- <el-button v-if="showToolButton('refresh')" icon="Refresh" circle @click="execQuery()" /> -->
<el-button
v-if="showToolButton('search') && searchItems?.length > 1"
:icon="isShowSearch ? 'ArrowDown' : 'ArrowUp'"
circle
@click="isShowSearch = !isShowSearch"
/>
<el-button v-if="showToolButton('search') && searchItems?.length > 1"
:icon="isShowSearch ? 'ArrowDown' : 'ArrowUp'" circle
@click="isShowSearch = !isShowSearch" />
<el-popover
placement="bottom"
title="表格配置"
popper-style="max-height: 550px; overflow: auto; max-width: 450px"
width="auto"
trigger="click"
>
<el-popover placement="bottom" title="表格配置"
popper-style="max-height: 550px; overflow: auto; max-width: 450px" width="auto"
trigger="click">
<div v-for="(item, index) in tableColumns" :key="index">
<el-checkbox v-model="item.show" :label="item.label" :true-value="true" :false-value="false" />
<el-checkbox v-model="item.show" :label="item.label" :true-value="true"
:false-value="false" />
</div>
<template #reference>
<el-button icon="Operation" circle :size="props.size"></el-button>
@@ -86,33 +78,16 @@
</div>
</div>
<el-table
ref="tableRef"
v-bind="$attrs"
:max-height="tableMaxHeight"
@selection-change="handleSelectionChange"
:data="tableData"
highlight-current-row
v-loading="loading"
:size="props.size as any"
:border="border"
>
<el-table ref="tableRef" v-bind="$attrs" :max-height="tableMaxHeight"
@selection-change="handleSelectionChange" :data="tableData" highlight-current-row
v-loading="loading" :size="props.size as any" :border="border">
<el-table-column v-if="props.showSelection" :selectable="selectable" type="selection" width="40" />
<template v-for="(item, index) in tableColumns">
<el-table-column
:key="index"
v-if="item.show"
:prop="item.prop"
:label="item.label"
:fixed="item.fixed"
:align="item.align"
:show-overflow-tooltip="item.showOverflowTooltip"
:min-width="item.minWidth"
:sortable="item.sortable || false"
:type="item.type"
:width="item.width"
>
<el-table-column :key="index" v-if="item.show" :prop="item.prop" :label="$t(item.label)"
:fixed="item.fixed" :align="item.align" :show-overflow-tooltip="item.showOverflowTooltip"
:min-width="item.minWidth" :sortable="item.sortable || false" :type="item.type"
:width="item.width">
<!-- 插槽预留功能 -->
<template #default="scope" v-if="item.slot">
<slot :name="item.slotName ? item.slotName : item.prop" :data="scope.row"></slot>
@@ -120,29 +95,21 @@
<!-- 枚举类型使用tab展示 -->
<template #default="scope" v-else-if="item.type == 'tag'">
<enum-tag :size="props.size" :enums="item.typeParam" :value="item.getValueByData(scope.row)"></enum-tag>
<enum-tag :size="props.size" :enums="item.typeParam"
:value="item.getValueByData(scope.row)"></enum-tag>
</template>
<template #default="scope" v-else>
<!-- 配置了美化文本按钮以及文本内容大于指定长度则显示美化按钮 -->
<el-popover
v-if="item.isBeautify && item.getValueByData(scope.row)?.length > 35"
effect="light"
trigger="click"
placement="top"
width="600px"
>
<el-popover v-if="item.isBeautify && item.getValueByData(scope.row)?.length > 35"
effect="light" trigger="click" placement="top" width="600px">
<template #default>
<el-input :autosize="{ minRows: 3, maxRows: 15 }" disabled v-model="formatVal" type="textarea" />
<el-input :autosize="{ minRows: 3, maxRows: 15 }" disabled v-model="formatVal"
type="textarea" />
</template>
<template #reference>
<el-link
@click="formatText(item.getValueByData(scope.row))"
:underline="false"
type="success"
icon="MagicStick"
class="mr5"
></el-link>
<el-link @click="formatText(item.getValueByData(scope.row))" :underline="false"
type="success" icon="MagicStick" class="mr5"></el-link>
</template>
</el-popover>
@@ -154,17 +121,10 @@
</div>
<el-row v-if="props.pageable" class="mt20" type="flex" justify="end">
<el-pagination
:small="props.size == 'small'"
@current-change="pageNumChange"
@size-change="pageSizeChange"
style="text-align: right"
layout="prev, pager, next, total, sizes"
:total="total"
v-model:current-page="queryForm.pageNum"
v-model:page-size="queryForm.pageSize"
:page-sizes="pageSizes"
/>
<el-pagination :small="props.size == 'small'" @current-change="pageNumChange"
@size-change="pageSizeChange" style="text-align: right" layout="prev, pager, next, total, sizes"
:total="total" v-model:current-page="queryForm.pageNum" v-model:page-size="queryForm.pageSize"
:page-sizes="pageSizes" />
</el-row>
</div>
</div>
@@ -185,6 +145,7 @@ import SvgIcon from '@/components/svgIcon/index.vue';
import { usePageTable } from '@/hooks/usePageTable';
import { ElTable } from 'element-plus';
const emit = defineEmits(['update:selectionData', 'pageSizeChange', 'pageNumChange']);
export interface PageTableProps {
@@ -377,6 +338,7 @@ defineExpose({
// 表格 header 样式
.table-header {
width: 100%;
.header-button-lf {
float: left;
}
@@ -445,7 +407,7 @@ defineExpose({
// }
// 设置 el-table 中 header 文字不换行,并省略
.el-table__header .el-table__cell > .cell {
.el-table__header .el-table__cell>.cell {
// white-space: nowrap;
white-space: wrap;
}

View File

@@ -2,6 +2,7 @@ import EnumValue from '@/common/Enum';
import { formatDate } from '@/common/utils/format';
import { getValueByPath } from '@/common/utils/object';
import { getTextWidth } from '@/common/utils/string';
import { i18n } from '@/i18n';
export class TableColumn {
/**
@@ -183,7 +184,7 @@ export class TableColumn {
*/
isEnum(enums: any): TableColumn {
this.setFormatFunc((data: any, prop: string) => {
return EnumValue.getLabelByValue(enums, getValueByPath(data, prop));
return i18n.global.t(EnumValue.getLabelByValue(enums, getValueByPath(data, prop)));
});
return this;
}
@@ -243,7 +244,7 @@ export class TableColumn {
// 需要加上表格的内间距等,视情况加
const contentWidth: number = getTextWidth(maxWidthText) + 30;
// 获取label的宽度取较大的宽度
const columnWidth: number = getTextWidth(label) + 60;
const columnWidth: number = getTextWidth(i18n.global.t(label)) + 60;
const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
// 设置上限与累加需要额外增加的宽度
this.minWidth = (flexWidth > 400 ? 400 : flexWidth) + this.addWidth;

View File

@@ -11,7 +11,7 @@
<el-input v-model="state.modelValue" type="textarea" :rows="20" />
<template #footer>
<el-button type="primary" @click="onsubmit"> </el-button>
<el-button type="primary" @click="onsubmit">{{ $t('common.confirm') }}</el-button>
</template>
</el-dialog>
</div>

View File

@@ -22,6 +22,9 @@ import { TerminalStatus } from './common';
import { useEventListener } from '@vueuse/core';
import themes from './themes';
import { TrzszFilter } from 'trzsz';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
// mounted时是否执行init方法
@@ -166,7 +169,7 @@ function initSocket() {
// 监听socket错误信息
socket.onerror = (e: Event) => {
term.writeln('\r\n\x1b[31m提示: 连接错误...');
term.writeln(`\r\n\x1b[31m${t('components.terminal.connErrMsg')}`);
state.status = TerminalStatus.Error;
console.log('连接错误', e);
};

View File

@@ -2,7 +2,7 @@
<div>
<div class="terminal-dialog-container" v-for="openTerminal of terminals" :key="openTerminal.terminalId">
<el-dialog
title="SSH终端"
title="SSH Terminal"
v-model="openTerminal.visible"
top="32px"
class="terminal-dialog"
@@ -26,11 +26,13 @@
<!-- 右侧 -->
<div class="title-right-fixed">
<el-popconfirm @confirm="reConnect(openTerminal.terminalId)" title="确认重新连接?">
<el-popconfirm @confirm="reConnect(openTerminal.terminalId)" :title="$t('components.terminal.connConfirm')">
<template #reference>
<div class="mr15 pointer">
<el-tag v-if="openTerminal.status == TerminalStatus.Connected" type="success" effect="light" round> 已连接 </el-tag>
<el-tag v-else type="danger" effect="light" round> 未连接 </el-tag>
<el-tag v-if="openTerminal.status == TerminalStatus.Connected" type="success" effect="light" round>
{{ $t('components.terminal.connected') }}
</el-tag>
<el-tag v-else type="danger" effect="light" round> {{ $t('components.terminal.notConn') }} </el-tag>
</div>
</template>
</el-popconfirm>
@@ -39,8 +41,8 @@
<template #reference>
<SvgIcon name="QuestionFilled" :size="20" class="pointer-icon mr10" />
</template>
<div>ctrl | command + f (搜索)</div>
<div class="mt5">点击连接状态可重连</div>
<div>ctrl | command + f ({{ $t('components.terminal.search') }})</div>
<div class="mt5">{{ $t('components.terminal.reConnTips') }}</div>
</el-popover>
<SvgIcon
@@ -49,12 +51,24 @@
@click="minimize(openTerminal.terminalId)"
:size="20"
class="pointer-icon mr10"
title="最小化"
:title="$t('components.terminal.minimize')"
/>
<SvgIcon name="FullScreen" @click="handlerFullScreen(openTerminal)" :size="20" class="pointer-icon mr10" title="全屏|退出全屏" />
<SvgIcon
name="FullScreen"
@click="handlerFullScreen(openTerminal)"
:size="20"
class="pointer-icon mr10"
:title="$t('components.terminal.fullScreenTitle')"
/>
<SvgIcon name="Close" class="pointer-icon" @click="close(openTerminal.terminalId)" title="关闭" :size="20" />
<SvgIcon
name="Close"
class="pointer-icon"
@click="close(openTerminal.terminalId)"
:title="$t('components.terminal.close')"
:size="20"
/>
</div>
</div>
</template>

View File

@@ -30,7 +30,7 @@ import EnumTag from '@/components/enumtag/EnumTag.vue';
const props = defineProps({
title: {
type: String,
default: '日志',
default: 'Log',
},
});

View File

@@ -1,11 +1,11 @@
<template>
<div id="search-card" v-show="search.visible" @keydown.esc="closeSearch">
<el-card title="搜索" size="small">
<el-card :title="$t('components.terminal.search')" size="small">
<!-- 搜索框 -->
<el-input
class="search-input"
ref="searchInputRef"
placeholder="请输入查找内容,回车搜索"
:placeholder="$t('components.terminal.serachPlaceholder')"
v-model="search.value"
@keyup.enter.native="searchKeywords(true)"
clearable
@@ -15,24 +15,30 @@
<div class="search-options">
<el-row>
<el-col :span="12">
<el-checkbox class="usn" v-model="search.regex"> 正则匹配 </el-checkbox>
<el-checkbox class="usn" v-model="search.regex"> {{ $t('components.terminal.regexMatch') }} </el-checkbox>
</el-col>
<el-col :span="12">
<el-checkbox class="usn" v-model="search.words"> 单词全匹配 </el-checkbox>
<el-checkbox class="usn" v-model="search.words"> {{ $t('components.terminal.fullWordMatching') }} </el-checkbox>
</el-col>
<el-col :span="12">
<el-checkbox class="usn" v-model="search.matchCase"> 区分大小写 </el-checkbox>
<el-checkbox class="usn" v-model="search.matchCase"> {{ $t('components.terminal.caseSensitive') }} </el-checkbox>
</el-col>
<el-col :span="12">
<el-checkbox class="usn" v-model="search.incremental"> 增量查找 </el-checkbox>
<el-checkbox class="usn" v-model="search.incremental"> {{ $t('components.terminal.incrementalSearch') }} </el-checkbox>
</el-col>
</el-row>
</div>
<!-- 按钮 -->
<div class="search-buttons">
<el-button class="terminal-search-button search-button-prev" type="primary" size="small" @click="searchKeywords(false)"> 上一个 </el-button>
<el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="searchKeywords(true)"> 下一个 </el-button>
<el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="closeSearch"> 关闭 </el-button>
<el-button class="terminal-search-button search-button-prev" type="primary" size="small" @click="searchKeywords(false)">
{{ $t('components.terminal.previous') }}}}
</el-button>
<el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="searchKeywords(true)">
{{ $t('components.terminal.next') }}}}
</el-button>
<el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="closeSearch">
{{ $t('components.terminal.close') }}
</el-button>
</div>
</el-card>
</div>
@@ -41,6 +47,9 @@
import { ref, toRefs, nextTick, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
searchAddon: {
@@ -101,7 +110,7 @@ function searchKeywords(direction: any) {
res = props.searchAddon?.findPrevious(state.search.value, getSearchOptions(option));
}
if (!res) {
ElMessage.info('未查询到匹配项');
ElMessage.info(t('components.terminal.noMatchMsg'));
}
}

View File

@@ -8,8 +8,8 @@ export enum TerminalStatus {
}
export const TerminalStatusEnum = {
Error: EnumValue.of(TerminalStatus.Error, '连接出错').setExtra({ iconColor: 'var(--el-color-error)' }),
NoConnected: EnumValue.of(TerminalStatus.NoConnected, '未连接').setExtra({ iconColor: 'var(--el-color-primary)' }),
Connected: EnumValue.of(TerminalStatus.Connected, '连接成功').setExtra({ iconColor: 'var(--el-color-success)' }),
Disconnected: EnumValue.of(TerminalStatus.Disconnected, '连接失败').setExtra({ iconColor: 'var(--el-color-error)' }),
Error: EnumValue.of(TerminalStatus.Error, 'components.terminal.connError').setExtra({ iconColor: 'var(--el-color-error)' }),
NoConnected: EnumValue.of(TerminalStatus.NoConnected, 'components.terminal.notConn').setExtra({ iconColor: 'var(--el-color-primary)' }),
Connected: EnumValue.of(TerminalStatus.Connected, 'components.terminal.connSuccess').setExtra({ iconColor: 'var(--el-color-success)' }),
Disconnected: EnumValue.of(TerminalStatus.Disconnected, 'components.terminal.connFail').setExtra({ iconColor: 'var(--el-color-error)' }),
};

View File

@@ -0,0 +1,94 @@
import { i18n } from '@/i18n';
import { ElMessage, ElMessageBox } from 'element-plus';
/**
* rule message 提示输入字段名
* @param label 字段名称key
* @returns
*/
export function useI18nPleaseInput(labelI18nKey: string) {
const t = i18n.global.t;
return t('common.pleaseInput', { label: t(labelI18nKey) });
}
/**
* rule message 提示选择字段名
* @param label 字段名称key
* @returns
*/
export function useI18nPleaseSelect(labelI18nKey: string) {
const t = i18n.global.t;
return t('common.pleaseSelect', { label: t(labelI18nKey) });
}
/**
* 提示确认删除
* @param name 删除对象名称
* @returns
*/
export async function useI18nDeleteConfirm(name: string = '') {
return useI18nConfirm('common.deleteConfirm', { name });
}
/**
* 提示确认信息
* @param i18nKey i18n msg key
* @param value i18n msg value
* @returns
*/
export async function useI18nConfirm(i18nKey: string = '', value = {}) {
const t = i18n.global.t;
return ElMessageBox.confirm(t(i18nKey, value), t('common.hint'), {
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
type: 'warning',
});
}
/**
* 表单校验
* @param formRef 表单ref
* @param callback 校验通过回调
* @returns
*/
export async function useI18nFormValidate(formRef: any) {
const t = i18n.global.t;
try {
await formRef.value.validate();
return true;
} catch (e: any) {
ElMessage.error(t('common.formValidationError'));
throw e;
}
}
export function useI18nCreateTitle(i18nKey: string) {
const t = i18n.global.t;
return t('common.createTitle', { name: t(i18nKey) });
}
export function useI18nEditTitle(i18nKey: string) {
const t = i18n.global.t;
return t('common.editTitle', { name: t(i18nKey) });
}
export function useI18nDetailTitle(i18nKey: string) {
const t = i18n.global.t;
return t('common.detailTitle', { name: t(i18nKey) });
}
export function useI18nOperateSuccessMsg() {
const t = i18n.global.t;
ElMessage.success(t('common.operateSuccess'));
}
export function useI18nSaveSuccessMsg() {
const t = i18n.global.t;
ElMessage.success(t('common.saveSuccess'));
}
export function useI18nDeleteSuccessMsg() {
const t = i18n.global.t;
ElMessage.success(t('common.deleteSuccess'));
}

View File

@@ -9,6 +9,7 @@ import config from '@/common/config';
import { unref } from 'vue';
import { URL_401 } from '@/router/staticRouter';
import openApi from '@/common/openApi';
import { useThemeConfig } from '@/store/themeConfig';
const baseUrl: string = config.baseApiUrl;
@@ -27,7 +28,11 @@ const useCustomFetch = createFetch({
headers.set('Authorization', token);
headers.set('ClientId', getClientId());
}
const themeConfig = useThemeConfig().themeConfig;
headers.set('Content-Type', 'application/json');
headers.set('Accept-Language', themeConfig?.globalI18n);
options.headers = headers;
return { options };
@@ -89,7 +94,7 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
return {
execute: async function () {
return execUaf(uaf);
return execCustomFetch(uaf);
},
isFetching: uaf.isFetching,
data: uaf.data,
@@ -100,7 +105,7 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
let refreshingToken = false;
let queue: any[] = [];
async function execUaf(uaf: any) {
async function execCustomFetch(uaf: any) {
try {
await uaf.execute(true);
} catch (e: any) {
@@ -146,7 +151,7 @@ async function execUaf(uaf: any) {
// 请求加入队列等待, 防止并发多次请求refreshToken
return new Promise((resolve) => {
queue.push(() => {
resolve(execUaf(uaf));
resolve(execCustomFetch(uaf));
});
});
}
@@ -170,7 +175,7 @@ async function execUaf(uaf: any) {
queue = [];
}
await execUaf(uaf);
await execCustomFetch(uaf);
return;
}

View File

@@ -0,0 +1,445 @@
// 定义内容
export default {
common: {
create: 'Create',
edit: 'Edit',
delete: 'Delete',
detail: 'Details',
add: 'Add',
save: 'Save',
remove: 'Remove',
confirm: 'Confirm',
cancel: 'Cancel',
submit: 'Submit',
operation: 'Operations',
name: 'Name',
code: 'Code',
remark: 'Remark',
status: 'Status',
username: 'Username',
role: 'Role',
msg: 'Message',
type: 'Type',
time: 'Time',
account: 'Account',
password: 'Password',
createTime: 'Create Time',
creator: 'Creator',
updateTime: 'Update Time',
modifier: 'Modifier',
keyword: 'Keyword',
path: 'Path',
tag: 'Tag',
more: 'More',
enable: 'Enable',
disable: 'Disable',
hint: 'Hint',
yes: 'Yes',
no: 'No',
refresh: 'Refresh',
basic: 'Basic',
other: 'Other',
reset: 'Reset',
success: 'Success',
fail: 'Fail',
previousStep: 'Previous Step',
nextStep: 'Next Step',
copy: 'Copy',
pleaseInput: 'Please enter {label}',
pleaseSelect: 'Please select {label}',
formValidationError: 'Please fill in the form information correctly',
createTitle: 'Create {name}',
editTitle: 'Edit {name}',
detailTitle: '{name} Details',
deleteConfirm: 'This operation will delete [{name}]. Do you want to continue?',
saveSuccess: 'save successfully',
deleteSuccess: 'delete successfully',
operateSuccess: 'operate successfully',
},
layout: {
user: {
title0: 'Component size',
langSwitch: 'Language switching',
menuSearch: 'Menu search',
layoutConf: 'Layout configuration',
news: 'news',
fullScreenOn: 'Full screen on',
fullScreenOff: 'Full screen off',
dropdownLarge: 'large',
dropdownDefault: 'default',
dropdownSmall: 'small',
index: 'Home Page',
personalCenter: 'Personal Center',
logout: 'Log out',
dropdown6: 'Code warehouse',
searchPlaceholder: 'Menu search',
newTitle: 'Notice',
newBtn: 'All read',
newGo: 'Go to the notification center',
newDesc: 'No notice',
logOutTitle: 'Tips',
logOutMessage: 'This operation will log out. Do you want to continue?',
logOutExit: 'Exiting',
logoutSuccess: 'Safe exit successful!',
},
tagsView: {
refresh: 'Refresh',
close: 'Close',
closeOther: 'Close Other',
closeAll: 'Close All',
fullscreen: 'Fullscreen',
closeFullscreen: 'Close Fullscreen',
},
notFound: {
title: 'Wrong address input, please re-enter the address~',
msg: 'You can check the web address first, and then re-enter or give us feedback.',
backHomepage: 'Back to home page',
},
noAccess: {
title: 'You are not authorized to operate~',
loginAgain: 'Login again',
},
config: {
configTitle: 'Layout configuration',
terminalTheme: 'Terminal Themes',
theme: 'Theme',
custom: 'Custom',
fontColor: 'Font Color',
backgroundColor: 'Background Color',
cursorColor: 'Cursor Color',
fontSize: 'Font Size',
fontWeight: 'Font Weight',
editorSetting: 'Editor Settings',
globalSetting: 'Global Settings',
pagesize: 'Page Size',
globalTheme: 'Global Themes',
twoTopTitle: 'top bar set up',
menuSetting: 'Menu set up',
menuBar: 'Menu background',
menuBarFontColor: 'Menu default font color',
menuBarActiveColor: 'Menu Highlight Color',
isMenuBarColorGradual: 'Menu gradient',
twoColumnsTitle: 'Columns set up',
twoTopBar: 'Top bar background',
twoTopBarColor: 'Top bar default font color',
twoIsTopBarColorGradual: 'Top bar gradient',
twoColumnsMenuBar: 'Column menu background',
twoColumnsMenuBarColor: 'Default font color bar menu',
twoIsColumnsMenuBarColorGradual: 'Column gradient',
twoIsColumnsMenuHoverPreload: 'Column Menu Hover Preload',
interfaceSetting: 'Interface settings',
isCollapse: 'Menu horizontal collapse',
isUniqueOpened: 'Menu accordion',
isFixedHeader: 'Fixed header',
isClassicSplitMenu: 'Classic layout split menu',
isLockScreen: 'Open the lock screen',
lockScreenTime: 'screen locking(s/s)',
interfaceDisplay: 'Interface display',
isShowLogo: 'Sidebar logo',
isBreadcrumb: 'Open breadcrumb',
isBreadcrumbIcon: 'Open breadcrumb icon',
isTagsview: 'Open tagsview',
isTagsviewIcon: 'Open tagsview Icon',
isCacheTagsView: 'Enable tagsview cache',
isSortableTagsView: 'Enable tagsview drag',
isShareTagsView: 'Enable tagsview sharing',
isFooter: 'Open footer',
isGrayscale: 'Grey model',
isInvert: 'Color weak mode',
isDark: 'Dark Mode',
isWartermark: 'Turn on watermark',
wartermarkText: 'Watermark copy',
otherSetting: 'Other settings',
tagsStyle: 'Tagsview style',
animation: 'Page animation',
columnsAsideStyle: 'Column style',
columnsAsideLayout: 'Column layout',
layoutSwitch: 'Layout switch',
defaults: 'One',
classic: 'Two',
transverse: 'Three',
columns: 'Four',
},
},
staticRoutes: {
signIn: 'signIn',
notFound: 'notFound',
noPower: 'noPower',
},
upgrade: {
title: 'New version',
msg: 'The new version is available, please update it now! Dont worry, the update is fast!',
desc: 'Prompt: Update will restore the default configuration',
btnOne: 'Cruel refusal',
btnTwo: 'Update now',
btnTwoLoading: 'Updating',
},
menu: {
index: 'Home Page',
personalCenter: 'Personal Center',
tag: 'Tag',
tagTree: 'Tag Tree',
tagSave: 'Save Tag',
tagDelete: 'Delete Tag',
authorization: 'Authorization',
authorizationBase: 'Base Permission',
authorizationSave: 'Save Authorization',
authorizationDelete: 'Delete Authorization',
team: 'Team',
teamSave: 'Save Team',
teamDelete: 'Delete Team',
teamMemberAdd: 'Add Member',
teamMemberDelete: 'Delete Member',
teamTagSave: 'Save Team Tag',
machine: 'Machine',
machineOp: 'Machine Operation',
machineOpBase: 'Base Permission',
machineList: 'Machine List',
machineBase: 'Base Permission',
machineCreate: 'Create Machine',
machineEdit: 'Edit Machine',
machineDelete: 'Delete Machine',
machineTerminal: 'Machine Terminal',
machineFileConf: 'File',
machineFileConfCreate: 'File-Add Config',
machineFileConfDelete: 'File-Delete Config',
machineFileCreate: 'File-Create',
machineFileDelete: 'File-Delete',
machineFileWrite: 'File-Write',
machineFileUpload: 'File-Upload',
machineScript: 'Script',
machineScriptSave: 'Script-Save',
machineScriptDelete: 'Script-Delete',
machineScriptRun: 'Script-Run',
machineKillprocess: 'Kill Process',
machineCronJob: 'Cron Job',
machineCronJobSvae: 'Cron Job-Save',
machineCronJobDelete: 'Cron Job-Delete',
machineSecurityConfig: 'Security Config',
machineSecurityCmdSvae: 'Cmd Config-Save',
machineSecurityCmdDelete: 'Cmd Config-Delete',
dbms: 'DBMS',
dbDataOp: 'Data Operation',
dbDataOpBase: 'Base Permission',
dbDataOpSqlScriptRun: 'SQL Script Run',
dbInstance: 'DB Instance',
dbInstanceBase: 'Base Permission',
dbInstanceSave: 'Save Instance',
dbInstanceDelete: 'Delete Instance',
dbBase: 'Db Base Permission',
dbSave: 'Save Db',
dbDelete: 'Delete Db',
dbDataSync: 'Data Sync',
dbDataSyncBase: 'Base Permission',
dbDataSyncSave: 'Save Sync Task',
dbDataSyncDelete: 'Delete Sync Task',
dbDataSyncChangeStatus: 'Enable/Disable Sync Task',
dbDataSyncLog: 'Sync Log',
dbTransfer: 'DB Transfer',
dbTransferBase: 'Base Permission',
dbTransferSave: 'Save Transfer Task',
dbTransferDelete: 'Delete Transfer Task',
dbTransferChangeStatus: 'Enable/Disable Transfer Task',
dbTransferRun: 'Run Transfer Task',
dbTransferRunLog: 'Transfer Log',
dbTransferFileShow: 'ransfer File-Show',
dbTransferFileDelete: 'Transfer File-Delete',
dbTransferFileDownload: 'Transfer File-Download',
dbTransferFileRun: 'Transfer File-Run',
redis: 'Redis',
redisDataOp: 'Data Operation',
redisDataOpBase: 'Base Permission',
redisDataOpSave: 'Save Data',
redisDataOpDelete: 'Delete Data',
redisManage: 'Redis Manage',
redisManageBase: 'Base Permission',
mongo: 'Mongo',
mongoDataOp: 'Data Operation',
mongoDataOpBase: 'Base Permission',
mongoDataOpSave: 'Save Data',
mongoDataOpDelete: 'Delete Data',
mongoManage: 'Mongo Manage',
mongoManageBase: 'Base Permission',
flow: 'Flow',
myTask: 'My Task',
myFlow: 'My Flow',
flowProcDef: 'Process Define',
flowProcDefSave: 'Save Process Define',
flowProcDefDelete: 'Delete Process Define',
system: 'System',
menuPermission: 'Menu & Permission',
menuPermissionBase: 'Base Permission',
menuPermissionAdd: 'Add Menu Permission',
menuPermissionEdit: 'Edit Menu Permission',
menuPermissionDelete: 'Delete Menu Permission',
menuPermissionEnableDisable: 'Enable/Disable Menu Permission',
account: 'Account',
accountBase: 'Base Permission',
accountAdd: 'Add Account',
accountEdit: 'Edit Account',
accountDelete: 'Delete Account',
accountEnableDisable: 'Enable/Disable Account',
accountRoleAllocation: 'Role Allocation',
role: 'Role',
roleBase: 'Base Permission',
roleAdd: 'Add Role',
roleEdit: 'Edit Role',
roleDelete: 'Delete Role',
roleMenuPermissionAllocation: 'Menu & Permission Allocation',
sysConf: 'System Config',
sysConfBase: 'Base Permission',
sysConfSave: 'Save System Config',
opLog: 'Operation Log',
opLogBase: 'Base Permission',
noPagePermission: 'No Page Permission',
authcertShowciphertext: 'Show Ciphertext',
},
home: {
personalInfo: 'Personal Information',
welcomeMsg: `Hello, {name}, no matter how bad life gets, it doesn't prevent me from getting better!`,
lastLoginIp: 'Last Login Ip',
lastLoginTime: 'Last Login Time',
msgNotify: 'Message Notification',
noOpRecord: 'No operation record',
msgTypeLogin: 'Login',
msgTypeNotify: 'Notify',
},
personal: {
updateInfo: 'Update Information',
basicInfo: 'Basic Information',
inputNewPasswordPlaceholder: 'Enter a new password',
updatePersonalInfo: 'Updating Personal Information',
accountInfo: 'Account Information',
currentStatus: 'Current Status',
boundUp: 'Bound up',
notBound: 'Not bound',
immediateBinding: 'Immediate binding',
unbundle: 'Unbundle',
updateSuccess: 'Update successfully',
bindingSuccess: 'Binding successfully',
unbundleSuccess: 'Unbundle successfully',
},
login: {
accountPasswordLogin: 'Account Password Login',
thirdPartyLogin: 'Third Party login',
ldapLogin: 'LDAP Login',
inputUsernamePlaceholder: 'Please enter your username',
inputPasswordPlaceholder: 'Please enter your password',
inputCaptchaPlaceholder: 'Please enter the verification code',
login: 'Login',
loginFailTip: 'Tip: If you fail to log in more than {loginFailCount} times, you will not be allowed to log in again for {loginFailMin} minutes',
loginSuccessTip: 'welcome back!',
changePassword: 'Change Password',
oldPassword: 'Old Password',
newPassword: 'New Password',
inputNewPasswordPlaceholder: 'Please enter a new password',
passwordRuleTip: 'Must be at least 8 characters long and contain upper/lower case + number + special symbol',
passwordChangeSuccessTip: 'The password has been changed successfully, and the new password has been filled in the login password box',
otpValidation: 'OTP validation',
qrCode: 'QR code',
enterOtpCodeTip: 'Enter the authorization code shown in the Token APP',
inputOtpCodePlaceholder: 'Please enter the OTP authorization code',
updateBasicInfo: 'Modifying basic information',
name: 'Name',
inputNamePlaceholder: 'Please enter your name',
},
components: {
df: {
fieldModelPlaceholder: 'The field associated with model',
fieldLabel: 'label',
fieldPlaceholder: 'placeholder',
optionalValues: 'Optional values',
optionalValuesPlaceholder: 'Optional values, use, split',
required: 'Required',
},
terminal: {
connError: 'Connection error',
notConn: 'Not connected',
connSuccess: 'be connected successfully',
connFail: 'connection fail',
connErrMsg: 'Tip: Connection error...',
connConfirm: 'Are you sure to reconnect?',
connected: 'Connected',
search: 'Search',
reConnTips: 'Click the connection status to reconnect',
minimize: 'Minimize',
fullScreenTitle: 'Full screen | Exits from full screen',
close: 'Close',
serachPlaceholder: 'Please enter the search content, press enter search',
regexMatch: 'Regex Match',
fullWordMatching: 'Full Word Matching',
caseSensitive: 'Case Sensitive',
incrementalSearch: 'Incremental Search',
previous: 'Previous',
next: 'Next',
noMatchMsg: 'No matching item is found',
},
crontab: {
crontabInputPlaceholder: 'Click the left button to configure',
crontabTitle: 'Generate cron',
second: 'Second',
minute: 'Minute',
hour: 'Hour',
day: 'Day',
month: 'Month',
week: 'Week',
year: 'Year',
timeExpression: 'Time Expression',
crontabCompleteExpression: 'Crontab Complete Expression',
dayCrontype1: 'Allowed wildcard [, - * / L M]',
crontype2: 'Not specify',
crontype3: 'Cycle from',
crontypeFrom: 'Starting on the',
crontypeEvery: 'It is executed every',
appoint: 'Appoint',
crontypeStartDay: 'th',
crontypeExecDay: 'days',
monthLastDay: 'Last day of the month',
hourCronType1: 'Allowed wildcard [, - * /]',
crontypeStartHour: "o 'clock",
crontypeExecHour: 'hours',
crontypeStartMin: 'minutes',
crontypeExecMin: 'minutes',
crontypeStartSecond: 'seconds',
crontypeExecSecond: 'seconds',
crontypeStartMonth: 'months',
crontypeExecMonth: 'months',
yearly: 'Yearly',
crontypeStartYear: 'years',
crontypeExecYear: 'years',
weekCronType1: 'Allowed wildcard [, - * / L #]',
monday: 'Monday',
tuesday: 'Tuesday',
wednesday: 'Wednesday',
thursday: 'Thursday',
friday: 'Friday',
saturday: 'Saturday',
sunday: 'Sunday',
last5runTimes: 'Last 5 running times',
calculationing: 'In the calculation result',
},
},
};

207
frontend/src/i18n/en/db.ts Normal file
View File

@@ -0,0 +1,207 @@
export default {
db: {
// db instance
dbManage: 'DB Management',
port: 'Port',
connParam: 'Connection Params',
keywordPlaceholder: 'host / name / code',
acName: 'Credential',
dbInst: 'DB Instance',
manageDbTitle: 'Manage the [{instName}] database',
sqlitePathPlaceholder: 'Please enter the absolute address of the sqlite file on the server',
connParamPlaceholder: 'Other connection parameters of the form key1=value1&key2=value2',
connSuccess: 'be connected successfully',
showDb: 'View DB',
db: 'Database',
dbFilterPlaceholder: 'DB name: Input filterable',
sqlRecord: 'SQL records',
dump: 'Export',
dumpContent: 'Export Content',
structure: 'Structure',
data: 'Data',
extName: 'Ext Name',
dbFilterPlacehoder: 'Filter by database name',
allDb: 'All DB',
dumpDb: 'Export DB',
getDbMode: 'Get DB Mode',
noDumpDbMsg: 'Add the database you want to export',
allSelect: 'check all',
selectDbPlacehoder: '',
// db
dbInstInfo: 'DB Instance Info',
newQuery: 'New Query',
locationTagTree: 'Navigate to the specified position in the left tree',
dbShowSetting: 'DB View Setting',
showFieldComments: 'Show column comment',
autoLocationTagTree: 'Automatically locate the tree nodes',
cacheTableInfo: 'Cache table information -[If not enabled, get table information in real time]',
dbName: 'DB Name',
table: 'Table',
createTable: 'Create Table',
tableOp: 'Table Operation',
copyTable: 'Copy Table',
renameTable: 'Rename',
editTable: 'Edit',
delTable: 'Delete Table',
close: 'Close',
closeOther: 'Close Other',
noDbInstMsg: 'Select the database instance and the corresponding schema',
query: 'Query',
nQuery: 'NewQuery',
renamePrompt: 'Rename table 【{db}.{tableName}】',
noChange: 'No change',
isCopyTableData: 'Do you copy data?',
execSuccess: 'Successful execution',
execFail: 'Execution failure',
sqlScriptRun: 'Run SQL Script',
saveSql: 'Save SQL',
execInfo: 'Execution info',
result: 'Result',
times: 'times',
resultSet: 'Result Set',
tableDataEmptyTextTips:
'tips: Single table query at the beginning of select * or click the default query data of the table name, double-click the data online modification',
noSelctRunSqlMsg: 'Select the sql you want to execute',
enterExecRemarkTips: 'Please enter remark',
execRemarkPlaceholder: 'Enter the remark to execute the sql',
currentSqlTabIsRunning: 'The current result set tab is being executed, please use the new TAB to execute',
sqlCannotEmpty: 'sql content cannot be empty',
enterSqlScriptNameTips: 'Please enter the SQL script name',
scriptFileUploadRunning: `'{filename}' is being uploaded for execution, please pay attention to the result notification`,
runSql: 'Run SQL',
newTabRunSql: 'NewTab Run SQL',
formatSql: 'Format SQL',
execTime: 'execution time',
oneClickCopy: 'One click copy',
asc: 'Asc',
desc: 'Desc',
fixed: 'Fixed',
cancelFiexd: 'Cancel Fixed',
formView: 'Form View',
genJson: 'Generating JSON',
exportCsv: 'Export CSV',
exportSql: 'Export SQL',
onlySelectOneData: 'Only one row can be selected',
editField: 'Edit field',
valueTypeNoMatch: 'The input does not match the type',
tableFieldConf: 'Table field Configuration',
columnFilterPlaceholder: 'Enter column name or remark filter',
selectAll: 'Select All',
submitUpdate: 'Submit changes',
cancelUpdate: 'Cancel changes',
autoCompleteColumnPlaceholder:
'Select a column or enter a SQL conditional expression and press Enter or click the query icon to filter the results. The input can be prompted by the field name',
selectColumn: 'Select Column',
columnName: 'Column Name',
homePage: 'Home Page',
previousPage: 'Previous Page',
rowsPage: 'rows/page',
rows: 'rows',
conditionInputDialogTitle: 'Enter the value of [{columnName}]',
addDataDialogTitle: 'Add `{tableName}` table data',
exportContent: 'Export Content',
selectExportTable: 'Select the table you want to export first',
tableNamePlaceholder: 'Table name: Input filterable',
comment: 'Comment',
commentPlaceholder: 'Comment: Input filterable',
dataSize: 'Data Size',
indexSize: 'Index Size',
column: 'Column',
index: 'Index',
nullable: 'Nullable',
seqInIndex: 'Sequence number',
// DbSqlExecLog
selectDbPlaceholder: 'Please select database',
restoreSql: 'Restore SQL',
stmtType: 'Statement type',
execUser: 'Executor',
execRes: 'Result',
oldValue: 'Old Value',
// db transfer
pleaseSetting: 'Please set',
log: 'Logs',
stop: 'Stop',
run: 'Run',
file: 'File',
taskName: 'Task Name',
srcDb: 'Source DB',
runState: 'Run State',
createDbTransferDialogTitle: 'Added DB transfer task (transfer does not change the source DB)',
editDbTransferDialogTitle: 'Modify the DB transfer task (transfer does not change the source DB)',
stopConfirm: 'Sure to stop?',
runConfirm: 'Sure to run?',
transferFileManage: 'Transfer file management',
dbFileType: 'DB dialect file',
targetDb: 'Target DB',
fileDbType: 'SQL Dialect',
transferFileRunDialogTitle: 'Specify the database to execute the sql file',
targetDbTypeSelectError: 'Please select [{dbType}] database',
cronAble: 'Timed transfer',
transferMode: 'Transfer Mode',
transfer2Db: 'Transfer to DB',
transfer2File: 'Transfer to File',
fileSaveDays: 'File retention days',
transferStrategy: 'Transfer Strategy',
day: 'Day',
transferFull: 'Full',
transferIncrement: 'Incrementnot yet available',
nameCase: 'Convert',
none: 'None',
lower: 'Lower',
upper: 'Upper',
dbObj: 'DB',
allTable: 'All Table',
custom: 'Custom',
noTransferTableMsg: 'Select the table you want to transfer',
// dbSync
recentState: 'Recent task status',
dbSync: 'Data Synchronism',
realTime: 'Real Time',
noRealTime: 'Non-real time',
srcDataSql: 'Source Data SQL',
targetDbTable: 'Target Db table',
pageSize: 'Page Size',
pageSizePlaceholder: 'Size of data per page queried when synchronizing data',
updateField: 'Update Field',
updateFieldTips: 'The current maximum value of this field will be included when querying the data source, with aliases such as t.reate_time',
updateFiledPlaceholder: 'The current maximum value of this field will be included when querying the data source',
updateFieldValue: 'Update Field Value',
updateFieldValueTips:
'The record updates the current value of the field, such as: current time, current date, etc., and the next time the data is queried, the value condition will be added',
updateFieldValuePlaceholder: 'Update the current maximum value of the field',
fieldValueSrc: 'Source of values',
fieldValueSrcTips:
'The field name of the updated value is taken from the query result. The default is the same as the updated field. If the query result specifies a field alias and is inconsistent with the original updated field, the field value is the current updated value',
fieldValueSrcPlaceholder: 'Update the value source',
fieldMap: 'Field Mapping',
srcField: 'Source Field',
targetField: 'Target Field',
sqlPreview: 'SQL Preview',
selectSql: 'Select SQL',
insertSql: 'Insert SQL',
keyDuplicateStrategy: 'Key Duplicate Strategy',
fieldMapError: 'There are duplicate target fields in the field map, please check',
noDataSqlMsg: 'Please enter data sql',
notSelectSql: 'sql statement error, please enter the select statement',
notOneSql: 'sql statement error, please enter a single query statement',
notColumnSql: 'No field found. Check your sql',
// enums
getDbNamesModeAuto: 'Real-time get db',
getDbNamesModeAssign: 'Specifying the db name',
ignore: 'Ignore',
replate: 'Replate',
running: 'Running',
waitRun: 'Wait Run',
},
};

View File

@@ -0,0 +1,90 @@
export default {
flow: {
// procdef
approvalNode: 'Approval Node',
procdef: 'Process Definition',
triggeringCondition: 'Condition',
triggeringConditionTips: 'go template syntax. If the output is 1, the approval process is triggered',
conditionPlaceholder: 'Trigger condition, return value =1, means to trigger the approval process',
conditionDefault: `{{/* DBMS- Run Sql rules The param parameter is described as follows */}}
{{/* stmtType: select / read / insert / update / delete / ddl ; */}}
{{ if eq .bizType "db_sql_exec_flow"}}
{{/* Enable process approval when select and read statements are not available */}}
{{ if and (ne .param.stmtType "select") (ne .param.stmtType "read") }}
1
{{ end }}
{{ end }}
{{/* Redis-Run Cmd rules; param: parameter is described as follows */}}
{{/* cmdType: read(Read cmd) / write(Write cmd); */}}
{{/* cmd: get/set/hset... */}}
{{ if eq .bizType "redis_run_cmd_flow"}}
{{ if eq .param.cmdType "write" }}
1
{{ end }}
{{ end }}`,
nodeName: 'Node Name',
nodeNameTips: 'Click the specified node to drag and drop sort',
auditor: 'Auditor',
tasksNotEmpty: 'Please improve the task of the approval node',
tasksNoComplete: 'Please complete the task information of the {index} th approval node',
// procdef status enum
enable: 'Enable',
disable: 'Disable',
// procinst
startProcess: 'Start Process',
cancelProcessConfirm: 'Confirm canceling the process?',
bizType: 'Business Type',
bizKey: 'Business Key',
initiator: 'Initiator',
procdefName: 'Process Name',
bizStatus: 'Business Status',
startingTime: 'Starting Time',
endTime: 'End Time',
duration: 'Duration',
proc: 'Process',
bizInfo: 'Business Information',
approvalNodeNotExist: 'There is no approval node',
resourceNotExistFlow: 'This resource does not require an approval operation',
procinstFormError: 'Please fill in the information correctly',
procinstStartSuccess: 'Process initiated successfully',
// db run sql flow biz
runSql: 'Run SQL',
selectDbPlaceholder: 'Please select database',
// redis run cmd flow biz
runCmd: 'Rum Cmd',
selectRedisPlaceholder: 'Select the Redis instance and db',
cmdPlaceholder: `For example: SET 'key' 'value'; Multiple commands; segmentation`,
// ProcinstStatusEnum
active: 'Active',
completed: 'Completed',
suspended: 'Suspended',
terminated: 'Terminated',
cancelled: 'Cancelled',
handleResult: 'Result of handling',
runResult: 'Result of execution',
// ProcinstBizStatus
waitHandle: 'Pending',
handleSuccess: 'Success',
handleFail: 'Fail',
noHandle: 'No processing',
// ProcinstTaskStatus
waitProcess: 'Waiting',
pass: 'Pass',
reject: 'Reject',
back: 'Back',
canceled: 'Canceled',
// FlowBizType
dbSqlExec: 'DBMS-Run SQL',
redisRunCmd: 'Redis-Run Cmd',
// task
audit: 'Audit',
procinstStatus: 'Process status',
taskStatus: 'Task status',
taskName: 'Task Name',
taskBeginTime: 'Begin Time',
flowAudit: 'Approval Process',
},
};

View File

@@ -0,0 +1,134 @@
export default {
machine: {
keywordPlaceholder: 'ip / Name / Code',
acName: 'Credential',
runningStat: 'Running Status',
fs: 'Disk (mount point => available/total)',
memberInfo: 'Memory (available/total)',
cpuInfo: 'CPU (free)',
file: 'File',
directory: 'Directory',
folder: 'Folder',
script: 'Script',
process: 'Process',
terminalPlayback: 'Terminal Playback',
createMachine: 'Create Machine',
editMachine: 'Edit Machine',
reConnTips: 'Are you sure to reconnect?',
clickReConn: 'Click to reconnect',
port: 'Port',
sshTunnel: 'SSH Tunnel',
newOpenTabTerminalTips: 'Hold down ctrl to open a new TAB',
newTab: 'New TAB',
openTerminal: 'Open Terminal',
newTabOpenTerminal: 'Open Terminal(New TAB)',
fileManage: 'File Manage',
scriptManage: 'Script Manage',
machineState: 'Machine State',
remoteFileDesktopManage: 'Remote desktop file management', // Remote desktop file management
remoteDesktop: 'Remote Desktop',
protocol: 'Protocol',
ipAndPort: 'ip and port',
connSuccess: 'be connected successfully',
noAcErrMsg: 'Please complete the voucher account information',
// MachineRec
playback: 'Playback',
cmd: 'Command',
execCmdRecord: 'Executive command record',
execTime: 'Execution time',
operator: 'Operator',
beginTime: 'Begin Time',
endTime: 'End Time',
// MachineStats
basicInfo: 'basic information',
hostname: 'Hostname',
runTime: 'Run Time',
totalTask: 'Total Task',
runningTask: 'Running Task',
load: 'Load',
disk: 'Disk',
mountPoint: 'Mount Point',
available: 'Available',
used: 'Used',
networkCard: 'Network Card',
receive: 'Receive',
send: 'Send',
memory: 'Memory',
cpuUsageRate: 'Cpu Usage Rate',
// process
processName: 'Process Name',
selectSortType: 'Please select a sort type',
selectProcessNum: 'Please select the number of processes',
cpuDesc: 'CUP descending',
memDesc: 'Memory descending',
virtualMemory: 'Virtual Memory',
fixedMemory: 'Fixed Memory',
procState: 'process state',
startTime: 'Start Time',
procCpuRunTime: 'The actual CPU time used by the process',
killProcConfirm: 'Are you sure to terminate the process?',
kill: 'Kill',
// script
execute: 'Execute',
scriptParam: 'Script Param',
execResult: 'result of execution',
execCompleted: 'execution is completed',
scriptParamTips1: '1. You can use {{.model}} as a placeholder in the script content',
scriptParamTips2: '2. When executing the script, you can enter the corresponding form content to replace the placeholder',
scriptResultEnumResult: 'Have result',
scriptResultEnumNoResult: 'No result',
scriptResultEnumRealTime: 'Real-time',
scriptTypeEnumPrivate: 'Private',
scriptTypeEnumPublic: 'Public',
// security
cmdConfig: 'Command Config',
filterCmds: 'Filter command',
relateMachine: 'Associated machine',
newCmd: 'New Command',
cmdPlaceholder: 'Enter the command regular expression',
// cronjob
cronjob: 'Cronjob',
machineCode: 'Machine Code',
cronjobRunning: 'Running',
cronjobNoRun: 'Not Run',
cronjobRun: 'Execute',
cronJobExecStatusEnumSuccess: 'Success',
cronJobExecStatusEnumFail: 'Fail',
cronjobExecResult: 'Execute Result',
cronjobExecTime: 'Execute Time',
cronjobExecRecord: 'Record of execution',
runSuccess: 'Executed successfully',
cronjobRunState: 'Run State',
execResRecordType: 'Result record type',
cronExpression: 'Cron Expression',
// file
upload: 'Upload',
download: 'Download',
copy: 'Copy',
move: 'Move',
paste: 'Paste',
fileNameFilterPlaceholder: 'Name: Input filterable',
calculate: 'Calculate',
modificationTime: 'Modify Time',
attribute: 'Attribute',
user: 'User',
group: 'Group',
renameTips: 'rename: Double-click the file name cell and then press Ente',
fileDetail: 'File Details',
createFile: 'Create File',
pasteSuccess: 'Paste successfully',
sameDirNoPaste: 'Can not paste in the same directory',
renameSuccess: 'Rename successfully',
newFileNameNotEmpty: 'he new name cannot be empty',
fileTooLargeTips: 'The file is too large, please download and use it',
uploadSuccess: 'Upload successfully',
fileExceedsSysConf: 'The uploaded file exceeds the system configuration [{uploadMaxFileSize}]',
},
};

View File

@@ -0,0 +1,44 @@
export default {
mongo: {
mongo: 'MongoDB',
db: 'DB',
keywordPlaceholder: 'host / name / code',
connUrl: 'Connection URL',
dbList: 'DB List',
isEmpty: 'Is Empty',
deleteDbConfirm: 'Make sure to delete the db?',
coll: 'Collection',
deleteCollConfirm: 'Are you sure to delete the collection??',
collState: 'Collection State',
createDbAndColl: 'Crate DB & Collection',
dbName: 'DB Name',
collName: 'Collection Name',
createColl: 'Create Collection',
collTitle: '`{dbName}` Collection',
template: 'Template',
cmdTemplatePlaceholder: 'Select command template',
moreCmdTips: 'Select more commands to view ',
usersInfoDesc: 'Get user information',
createUserDesc: 'Create a new user',
grantRolesToUserDesc: 'Grants additional roles to the user',
dropUserDesc: 'Drop User',
roleInfoDesc: 'Get Role Information',
createRoleDesc: 'Create Role',
runSuccess: 'Run successfully',
queryParam: 'query parameter',
queryParamPlaceholder: 'Click to enter the corresponding search conditions',
deleteDocConfirm: 'Are you sure to delete the document?',
doc: 'Document',
findParamErrMsg: 'The filter or sort field json string value is incorrect. Note: The json key requires double quotes',
docErrMsg: 'The document content is incorrect and cannot be parsed into a json object',
insertSuccess: 'Insert successfully',
insertFail: 'Insert failure',
idNotExist: 'The _id attribute for the document does not exist',
modifyFail: 'fail to modify',
deleteFail: 'fail to delete',
docParse2jsonFail: 'Parsing the document content into a json object failed',
},
};

View File

@@ -0,0 +1,66 @@
export default {
redis: {
standaloneInfo: 'Standalone',
clusterInfo: 'Cluster',
node: 'Node',
clusterIpTips:
'p:port1@port2 :port1 indicates the port through which the redis server communicates with clients, and port2 indicates the port through which nodes in the cluster communicate',
masterSlaveRelationTips: `If the node is slave and the master node is known, it is the ID of the master node. Otherwise, the symbol '-'`,
configEpochTips:
'The epoch value of a node (or the epoch value of its primary node if the node is a slave node). Each time a node fails to switch, a new, unique, increasing epoch is created.',
keywordPlaceholder: 'host / name / code',
hostTips: `Enter host:port; The sentinel mode is mastername=sentinelhost:port. If the cluster or sentinels need multiple nodes, they can be separated by ','`,
nodePassword: 'Node Password',
sentinelHostErr: 'Sentinel model host for: mastername = sentinelhost: sentinelport mode',
delimiter: 'Delimiter',
keyMatchTips: 'match supports * obfuscated key, enter search',
loadMore: 'Load More',
addKey: 'Adding key',
newTabOpen: 'New tab opens',
redisSelectErr: 'Select redis first',
flushDbTips: 'Make sure to clear all keys of the [{db}] library?',
keyNotEmpty: 'The Key cannot be empty',
// info
redisInfoTitle: 'Redis server information',
version: 'Version',
port: 'Port',
mode: 'Mode',
os: 'os',
uptimeDays: 'Uptime in days',
execPath: 'Executable',
confFile: 'Config file',
clusterEnable: 'Cluster enabled',
nodeCount: 'Node count',
clientConn: 'Client Connection',
connectedNum: 'Connected clients',
blockedClientNum: 'Blocked clients',
sysCpu: 'used_cpu_sys',
userCpu: 'used_cpu_user',
sysChildCpu: 'used_cpu_sys_children',
userChildCpu: 'used_cpu_user_children',
keyCount: 'Key Count',
countInfo: 'Count',
totalCmdProcess: 'total_commands_processed',
curQps: 'instantaneous_ops_per_sec',
expiredKeys: 'expired_keys',
netInputBytes: 'total_net_input_bytes',
netOutputBytes: 'total_net_output_bytes',
persistence: 'Persistence',
aofEnable: 'aof_enabled',
loadingPersistence: 'loading',
availableMemory: 'Available',
usedMemory: 'Used',
renameTips: 'Click to rename',
ttlPlaceholder: 'Unit (seconds); negative values are permanent',
ttlTips: 'Click Change the expiration time',
settingSuccess: 'successfully set',
permanent: 'Permanent',
persistenceConfirm: 'Decide to persist the key?',
addNewLine: 'Adding new rows',
filterPlaceholder: 'Keyword Enter search',
deleteConfirm: 'Sure to delete?',
},
};

View File

@@ -0,0 +1,212 @@
export default {
system: {
menu: {
filterPlaceholder: 'Input keyword filter (right click operation)',
opTips: 'Red and orange fonts indicate disabled status (right click on the resource to operate)',
info: 'Information',
menu: 'Menu',
permission: 'Permission',
icon: 'Icon',
routerName: 'Router Name',
componentPath: 'Component Path',
isCache: 'Cache or not',
isHide: 'Whether to hide',
tagIsDelete: 'Tag cannot be deleted',
externalLink: 'External Link',
yes: 'Yes',
no: 'No',
addSubResource: 'Adding subresources',
enable: 'Enable',
disable: 'Disable',
tips: 'Tips',
addTopMenu: 'Add a top-level menu',
addChildrenMenuTitle: 'Adds `{parentName}` subresources',
updateMenu: 'Change `{name}`',
success: 'Success',
menuCodeTips: `The menu type is the access path (if the menu path does not begin with '/', the access address will automatically concatenate the parent menu path), otherwise it is the unique code of the resource`,
menuCodePlaceholder: `A menu that does not begin with '/' will automatically concatenate the parent menu path`,
routerNameTips: 'For component caching to work, match the vue component name, such as ResourceLis',
componentPathTips: 'Access path components, such as: ` system/resource/ResourceList `, default in ` views ` directory',
isCacheTips: `If yes is selected, it will be 'keepalive' cached (reentering the page without refreshing the page and requesting data again), and needs the route name to match the vue component name`,
isHideTips:
'Select Hide and the route will not appear in the menu bar, but it will still be accessible. Disabled will not be able to access and operate',
externalLinkTips: 'Inline: displayed as an iframe, external link: opened in a new TAB',
inline: 'Embedded',
linkAddress: 'Link Address',
linkPlaceholder: 'External/embedded links (http://xxx.com)',
menuNameRuleMsg: 'Please enter a resource name',
routeNameNotEmpty: 'Route names cannot be empty',
resourceCodePatternErrMsg: 'Only 1-32 uppercase letters, numbers, and -.: characters are allowed',
assignedRole: 'Assigned Role',
},
account: {
roleAllocation: 'Role Allocation',
resetOtp: 'Reset OTP',
roleName: 'Role Name',
assigner: 'Assigner',
allocateTime: 'Allocate Time',
name: 'Name',
lastLoginTime: 'Last Login Time',
usernamePlacholder: '5-16 uppercase letters, numbers, -.: characters',
random: 'Random',
usernamePatternErrMsg: 'Only 5-16 uppercase letters, numbers, and -.: characters are allowed',
accountSearchPlaceholder: 'Enter account fuzzy search and select',
accountInfo: 'Account Information',
allocateRoleTitle: 'Allocate the `{name}` role',
allocated: 'Allocated',
undistributed: 'Undistributed',
menuAndPermission: 'Menu & Permission',
remove: 'Remove',
allocation: 'Allocation',
roleCode: 'Role Code',
roleStatus: 'Role Status',
userMenuTitle: '`{name}` menus and permissions',
statusEnable: 'Enable',
statusDisable: 'Disable',
},
role: {
permissionDetail: 'Permission',
permissionAllocate: 'Permission Alloctate',
userManage: 'User',
roleName: 'Role Name',
roleCode: 'Role Code',
rolePermissionTitle: '`{name}` menus and permissions',
roleCodePlaceholder: 'COMMON begins with the role shared by all accounts',
statusEnable: 'Enable',
statusDisable: 'Disable',
allocateMenuTitle: 'Allocate permissions to `{roleName}`',
allocateAccountTitle: 'Account associated with `{roleName}`',
addAccount: 'Adding an account',
userStatus: 'User Status',
assigner: 'Assigner',
allocateTime: 'Allocate Time',
permissionInfo: 'Permission assignment information',
},
sysconf: {
confItem: 'Config Item',
confKey: ' Config Key',
permission: 'Permission',
permissionPlaceholder: 'Please enter account fuzzy search and select',
conf: 'Config',
confItemSetting: 'Config Item Setting',
confValue: 'Config Value',
fileConf: 'File Config',
fileConfRemark: 'System file config',
basePath: 'Base path',
baesPathPlaceholder: 'The default is ./file in the directory corresponding to the executable file',
dbmsConf: 'DBMS Config',
dbmsConfRemark: 'DBMS Config',
recordQuerySql: 'Record Query Sql',
recordQuerySqlPlaceholder: 'Whether to record queries sql',
maxResultSet: 'Max Result Set',
maxResultSetPlaceholder: 'Maximum number of result sets allowed for sql queries. Note: 0= no limit',
sqlExecLimt: 'sql execution time limit',
sqlExecLimtPlaceholder: 'After that time (in seconds), the execution is aborted',
machineConf: 'Machine Config',
machineConfRemark: 'Machine related configuration, such as the number of days terminal operation records are kept',
uploadMaxFileSize: 'Upload file size limit',
uploadMaxFileSizePlaceholder: 'Maximum file size allowed to upload (1MB, 2GB, etc.)',
termOpSaveDays: 'Terminal records the retention time',
termOpSaveDaysPlaceholder: 'Unit day, after which the terminal operation record will be deleted',
guacdHost: 'guacd server ip',
guacdHostPlaceholder: 'guacd server ip, default 127.0.0.1',
guacdPort: 'guacd server port',
guacdPortPlaceholder: 'guacd server port, default 4822',
guacdFilePath: 'guacd server file path',
guacdFilePathPlaceholder: 'guacd serves the file storage location for mounting the RDP folder',
systemConf: 'System-wide styling',
systemConfRemark: 'Configuration of system icon, title, watermark information, etc',
logoIcon: 'Logo Icon',
logoIconPlaceholder: 'System logo icon (base64 encoded, svg format recommended, no more than 10k)',
title: 'Menu bar title',
titlePlaceholder: 'System menu bar title display',
viceTitle: 'Login page title',
viceTitlePlaceholder: 'The login page title is displayed',
useWatermark: 'Watermarking',
useWatermarkPlaceholder: 'Whether system watermarking is enabled',
watermarkContent: 'Watermark information',
watermarkContentPlaceholder: 'Watermark supplementary information, such as company name, etc',
ldapLoginConf: 'LDAP Login Config',
ldapLoginConfRemark: 'LDAP Login Config',
ldapEnable: 'Enable',
dapEnablePlaceholder: 'Whether ldap login is enabled',
host: 'host',
port: 'port',
bindDN: 'bindDN',
bindDnPlaceholder: 'admin account for the LDAP service, for example: "cn=admin,dc=example,dc=com"',
bindPwd: 'bindPwd',
bindPwdPlaceholder: 'The administrator password for the LDAP service',
baseDN: 'baseDN',
baseDnPlaceholder: 'The user\'s base DN, for example: "ou=users,dc=example,dc=com"',
userFilter: 'userFilter',
userFilerPlaceholder: 'How to filter users, such as "(uid=%s), (&(objectClass=organizationalPerson)(uid=%s))"',
uidMap: 'uidMap',
uidMapPlaceholder: 'Mapping between user id and LDAP field name, such as: cn',
udnMap: 'udnMap',
udnMapPlaceholder: 'mapping between user name (dispalyName) and LDAP field name, such as displayName',
emailMap: 'emailMap',
emailMapPlaceholder: 'Mapping between user email and LDAP field name',
skipTlsVerfify: 'skipTlsVerfify',
skipTlsVerfifyPlaceholder: 'Whether the client skips TLS certificate validation',
securityProtocol: 'security protocol',
securityProtocolPlaceholder: 'Security protocol (Null does not use security protocol), such as StartTLS, LDAPS',
oauth2LoginConf: 'OAuth2 Login Config',
oauth2LoginConfRemark: 'OAuth2 Login Config',
oauth2Enable: 'Enable',
oauth2EnablePlaceholder: 'Whether oauth2 login is enabled or not',
name: 'Name',
namePlaceholder: 'oauth2 name',
clientId: 'Client ID',
clientIdPlaceholder: 'oauth2 Client ID',
clientSecret: 'Client Secret',
clientSecretPlaceholder: 'oauth2 Client Secret',
authorizationUrl: 'Authorization URL',
authorizationUrlPlaceholder: 'oauth2 Authorization url',
accessTokenUrl: 'Access Token URL',
accessTokenUrlPlaceholder: 'oauth2 gets the token url',
redirectUrl: 'Redirect URL',
redirectUrlPlaceholder: 'This system url',
scope: 'Scopes',
scopePlaceholder: 'oauth2 Scopes',
resourceUrl: 'Resource URL',
resourceUrlPlaceholder: 'Get the user information resource url',
userId: 'User ID',
userIdPlaceholder: 'User unique identification field The format is type:fieldPath(string:username)',
autoRegister: 'Automatic registration',
accountLoginConf: 'Login Security Settings',
accountLoginConfRemark: 'Account Login Security Settings',
useCaptcha: 'Login verification code',
useCaptchaPlaceholder: 'Whether to enable the login CAPTCHA',
useOtp: 'OTP',
useOtpPlaceholder: 'Whether two-factor (OTP) validation is enabled',
otpIssuer: 'OTP Issuer',
loginFailCount: 'Number of login failures',
loginFailCountPlaceholder: 'Disable login after n failed login attempts',
loginFainMin: 'Prohibited login time',
loginFailMinPlaceholder: 'After a specified number of login failures, re-login is prohibited within m minutes',
},
syslog: {
operator: 'Operator',
operatorPlaceholder: 'Please enter and select an account number',
operatingResult: 'Operating Result',
description: 'Description',
operatingTime: 'Operating Time',
operatingInfo: 'Operating Information',
result: 'Result',
response: 'Response',
resultSuccess: 'Success',
resultFail: 'Fail',
resultRunning: 'Running',
},
oauth: {
authSuccess: 'Authorization succeeded',
},
},
};

View File

@@ -0,0 +1,55 @@
export default {
tag: {
relateTag: 'Relate Tag',
keywordFilterPlaceholder: 'keyword filter',
nameFilterPlaceholder: 'Keyword filtering (right-click node operation)',
tagFilterPlaceholder: 'Enter the keyword -> Search for expanded node information',
tagTips1: '1. Used to group assets',
tagTips2: '2. Can be allocated in team management for resource isolation',
tagTips3: '3. Team members who own a parent tag have access to resources that manipulate their own or child tag associations',
machine: 'Machine',
db: 'Db',
code: 'Code',
createSubTag: 'Creating child tags',
createSubTagTitle: 'Creates a child tag for {codePath}',
rootTag: 'Root Tag',
selectTagPlaceholder: 'Select the associated tag',
},
team: {
team: 'Team',
member: 'Member',
addMember: 'Adding members',
teamMember: '[{teamName}] Member information',
selectAccountTips: 'Please select your account first',
joinTime: 'Join Date',
accountName: 'Account Name',
assigner: 'Assigner',
allocateTag: 'Assigned tags',
validity: 'Validity Date',
effectiveStartTime: 'Effective Start Time',
effectiveEndTime: 'Effective End Time',
},
// authcert
ac: {
namePlaceholder: 'Please enter a credential name (globally unique)',
privateKeyPlaceholder: 'Please copy the contents of the private key file here',
privateKeyPwd: 'Secret key passphrase',
resourceCode: 'Resource Code',
credentialName: 'Credential Name',
resourceType: 'Resource Type',
credentialType: 'Credential Type',
ciphertextType: 'Ciphertext Type',
privateKey: 'Private Key',
ac: 'Credential',
testConn: 'Test Connection',
usernameExist: 'The username already exists in the account list',
publicAc: 'Public credentials',
acTypeEnumPublic: 'Public credentials',
acTypeEnumPrivate: 'Ordinary certificate',
acTypeEnumPrivileged: 'Privilege credentials',
acTypeEnumPrivateDefault: 'Default credentials',
ciphertextTypeEnumPassword: 'Password',
ciphertextTypeEnumPrivateKey: 'Private key',
ciphertextTypeEnumPublic: 'Public credentials',
},
};

View File

@@ -0,0 +1,56 @@
// 定义语言国际化内容
/**
* 说明:
* 注意国际化定义的字段,不要与原有的定义字段相同。
* /src/i18n/(zh-cn、en...)/module.ts 下的 ts 为各模块国际化内容。
*/
import { createI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import pinia from '@/store';
import { I18nEnum } from '@/common/commonEnum';
const modules: Record<string, any> = import.meta.glob('./**/*.ts', { eager: true });
// 读取 pinia 默认语言
const { themeConfig } = storeToRefs(useThemeConfig(pinia));
function initI18n() {
// 定义变量内容
const messages: any = {};
const itemizeMap = new Map<string, any[]>();
// 对自动引入的 modules 进行分类 en-us、zh-cn
// https://vitejs.cn/vite3-cn/guide/features.html#glob-import
for (const path in modules) {
const parts = path.split('/');
const i18n = parts[1];
const msgs = modules[path].default;
if (itemizeMap.get(i18n)) {
itemizeMap.get(i18n)?.push(modules[path].default);
} else {
itemizeMap.set(i18n, [msgs]);
}
}
// 处理最终格式
itemizeMap.forEach((value, key) => {
messages[key] = Object.assign({}, ...value);
});
// https://vue-i18n.intlify.dev/guide/essentials/fallback.html#explicit-fallback-with-one-locale
return createI18n({
legacy: false,
silentTranslationWarn: true,
missingWarn: false,
silentFallbackWarn: true,
fallbackWarn: false,
locale: themeConfig.value.globalI18n,
fallbackLocale: I18nEnum.ZhCn.value,
messages,
});
}
// 导出语言国际化
export const i18n = initI18n();

View File

@@ -0,0 +1,453 @@
// 定义内容
export default {
common: {
create: '创建',
edit: '编辑',
delete: '删除',
detail: '详情',
add: '添加',
save: '保存',
remove: '移除',
confirm: '确定',
cancel: '取消',
submit: '提交',
operation: '操作',
name: '名称',
code: '编号',
remark: '备注',
status: '状态',
username: '用户名',
role: '角色',
msg: '消息',
type: '类型',
time: '时间',
account: '账号',
password: '密码',
createTime: '创建时间',
creator: '创建者',
updateTime: '更新时间',
modifier: '修改者',
keyword: '关键字',
path: '路径',
tag: '标签',
more: '更多',
enable: '启用',
disable: '禁用',
hint: '提示',
yes: '是',
no: '否',
refresh: '刷新',
basic: '基本',
other: '其他',
reset: '重置',
success: '成功',
fail: '失败',
previousStep: '上一步',
nextStep: '下一步',
copy: '复制',
pleaseInput: '请输入{label}',
pleaseSelect: '请选择{label}',
formValidationError: '请正确填写表单信息',
createTitle: '创建{name}',
editTitle: '编辑{name}',
detailTitle: '{name}详情',
deleteConfirm: '此操作将删除【{name}】, 是否继续?',
saveSuccess: '保存成功',
deleteSuccess: '删除成功',
operateSuccess: '操作成功',
},
layout: {
user: {
title0: '组件大小',
langSwitch: '语言切换',
menuSearch: '菜单搜索',
layoutConf: '布局配置',
news: '消息',
fullScreenOn: '开全屏',
fullScreenOff: '关全屏',
dropdownLarge: '大型',
dropdownDefault: '默认',
dropdownSmall: '小型',
index: '首页',
personalCenter: '个人中心',
logout: '退出登录',
searchPlaceholder: '菜单搜索',
newTitle: '通知',
newBtn: '全部已读',
newGo: '前往通知中心',
newDesc: '暂无通知',
logOutTitle: '提示',
logOutMessage: '此操作将退出登录, 是否继续?',
logOutExit: '退出中',
logoutSuccess: '安全退出成功!',
},
tagsView: {
refresh: '刷新',
close: '关闭',
closeOther: '关闭其它',
closeAll: '全部关闭',
fullscreen: '当前页全屏',
closeFullscreen: '关闭全屏',
},
notFound: {
title: '地址输入错误,请重新输入地址~',
msg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
backHomepage: '返回首页',
},
noAccess: {
title: '您未被授权,没有操作权限~',
loginAgain: '重新登录',
},
config: {
configTitle: '布局配置',
terminalTheme: '终端主题',
theme: '主题',
custom: '自定义',
fontColor: '字体颜色',
backgroundColor: '背景颜色',
cursorColor: '光标颜色',
fontSize: '字体大小',
fontWeight: '字体粗细',
editorSetting: 'Editor 设置',
globalSetting: '全局设置',
pagesize: '分页size',
globalTheme: '全局主题',
// 菜单设置
menuSetting: '菜单设置',
menuBar: '菜单背景',
menuBarFontColor: '菜单默认字体颜色',
menuBarActiveColor: '菜单高亮背景色',
isMenuBarColorGradual: '菜单背景渐变',
// 顶栏设置
topTitle: '顶栏设置',
twoColumnsTitle: '分栏设置',
twoTopBar: '顶栏背景',
twoTopBarColor: '顶栏默认字体颜色',
twoIsTopBarColorGradual: '顶栏背景渐变',
twoColumnsMenuBar: '分栏菜单背景',
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
twoIsColumnsMenuHoverPreload: '分栏菜单鼠标悬停预加载',
// 界面设置
interfaceSetting: '界面设置',
isCollapse: '菜单水平折叠',
isUniqueOpened: '菜单手风琴',
isFixedHeader: '固定 Header',
isClassicSplitMenu: '经典布局分割菜单',
isLockScreen: '开启锁屏',
lockScreenTime: '自动锁屏(s/秒)',
interfaceDisplay: '界面显示',
isShowLogo: '侧边栏 Logo',
isBreadcrumb: '开启 Breadcrumb',
isBreadcrumbIcon: '开启 Breadcrumb 图标',
isTagsview: '开启 Tagsview',
isTagsviewIcon: '开启 Tagsview 图标',
isCacheTagsView: '开启 TagsView 缓存',
isSortableTagsView: '开启 TagsView 拖拽',
// IsShareTagsView: '开启 TagsView 共用',
isFooter: '开启 Footer',
isGrayscale: '灰色模式',
isInvert: '色弱模式',
isDark: '深色模式',
// IsWartermark: '开启水印',
// WartermarkText: '水印文案',
// 其他设置
otherSetting: '其它设置',
tagsStyle: 'Tagsview 风格',
animation: '主页面切换动画',
columnsAsideStyle: '分栏高亮风格',
// fiveColumnsAsideLayout: '分栏布局风格',
layoutSwitch: '布局切换',
defaults: '默认',
classic: '经典',
transverse: '横向',
columns: '分栏',
// tipText: '点击下方按钮,复制布局配置去 `src/stores/themeConfig.ts` 中修改。',
// copyText: '一键复制配置',
// resetText: '一键恢复默认',
// copyTextSuccess: '复制成功!',
// copyTextError: '复制失败!',
},
},
staticRoutes: {
signIn: '登录',
notFound: '找不到此页面',
noPower: '没有权限',
},
upgrade: {
title: '新版本升级',
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
desc: '提示:更新会还原默认配置',
btnOne: '残忍拒绝',
btnTwo: '马上更新',
btnTwoLoading: '更新中',
},
menu: {
index: '首页',
personalCenter: '个人中心',
tag: '标签管理',
tagTree: '标签树',
tagSave: '保存标签',
tagDelete: '删除标签',
authorization: '授权凭证',
authorizationBase: '基础权限',
authorizationSave: '保存权限',
authorizationDelete: '删除权限',
team: '团队管理',
teamSave: '保存团队',
teamDelete: '删除团队',
teamMemberAdd: '添加成员',
teamMemberDelete: '删除成员',
teamTagSave: '保存团队标签',
machine: '机器管理',
machineOp: '机器操作',
machineOpBase: '基本权限',
machineList: '机器列表',
machineBase: '基本权限',
machineCreate: '创建机器',
machineEdit: '编辑机器',
machineDelete: '删除机器',
machineTerminal: '机器终端',
machineFileConf: '文件管理',
machineFileConfCreate: '文件-添加配置',
machineFileConfDelete: '文件-删除配置',
machineFileCreate: '文件-创建',
machineFileDelete: '文件-删除',
machineFileWrite: '文件-写入',
machineFileUpload: '文件-上传',
machineScript: '脚本管理',
machineScriptSave: '脚本-保存',
machineScriptDelete: '脚本-删除',
machineScriptRun: '脚本-执行',
machineKillprocess: '终止进程',
machineCronJob: '计划任务',
machineCronJobSvae: '计划任务-保存',
machineCronJobDelete: '计划任务-删除',
machineSecurityConfig: '安全配置',
machineSecurityCmdSvae: '命令配置-保存',
machineSecurityCmdDelete: '命令配置-删除',
dbms: 'DBMS',
dbDataOp: '数据操作',
dbDataOpBase: '基本权限',
dbDataOpSqlScriptRun: 'SQL脚本执行',
dbInstance: '数据库实例',
dbInstanceBase: '基本权限',
dbInstanceSave: '保存实例',
dbInstanceDelete: '删除实例',
dbBase: '数据库基本权限',
dbSave: '保存数据库',
dbDelete: '删除数据库',
dbDataSync: '数据同步',
dbDataSyncBase: '基本权限',
dbDataSyncSave: '保存同步',
dbDataSyncDelete: '删除同步',
dbDataSyncChangeStatus: '启用停用',
dbDataSyncLog: '同步日志',
dbTransfer: '数据库迁移',
dbTransferBase: '基本权限',
dbTransferSave: '保存迁移任务',
dbTransferDelete: '删除迁移任务',
dbTransferChangeStatus: '启用停用',
dbTransferRun: '执行迁移任务',
dbTransferRunLog: '迁移日志查看',
dbTransferFileShow: '迁移文件-查看',
dbTransferFileDelete: '迁移文件-删除',
dbTransferFileDownload: '迁移文件-下载',
dbTransferFileRun: '迁移文件-执行',
redis: 'Redis',
redisDataOp: '数据操作',
redisDataOpBase: '基本权限',
redisDataOpSave: '数据保存',
redisDataOpDelete: '数据删除',
redisManage: 'Redis管理',
redisManageBase: '基本权限',
mongo: 'Mongo',
mongoDataOp: '数据操作',
mongoDataOpBase: '基本权限',
mongoDataOpSave: '数据保存',
mongoDataOpDelete: '数据删除',
mongoManage: 'Mongo管理',
mongoManageBase: '基本权限',
flow: '工单流程',
myTask: '我的任务',
myFlow: '我的流程',
flowProcDef: '流程定义',
flowProcDefSave: '保存流程定义',
flowProcDefDelete: '删除流程定义',
system: '系统管理',
menuPermission: '菜单权限',
menuPermissionBase: '基本权限',
menuPermissionAdd: '添加菜单权限',
menuPermissionEdit: '编辑菜单权限',
menuPermissionDelete: '删除菜单权限',
menuPermissionEnableDisable: '启用/禁用菜单权限',
account: '账号管理',
accountBase: '基本权限',
accountAdd: '添加账号',
accountEdit: '编辑账号',
accountDelete: '删除账号',
accountEnableDisable: '启用/禁用账号',
accountRoleAllocation: '角色分配',
role: '角色管理',
roleBase: '基本权限',
roleAdd: '添加角色',
roleEdit: '编辑角色',
roleDelete: '删除角色',
roleMenuPermissionAllocation: '菜单权限分配',
sysConf: '系统配置',
sysConfBase: '基本权限',
sysConfSave: '保存配置',
opLog: '操作日志',
opLogBase: '基本权限',
noPagePermission: '无页面权限',
authcertShowciphertext: '授权凭证密文查看',
},
home: {
personalInfo: '个人信息',
welcomeMsg: '您好, {name},生活变的再糟糕,也不妨碍我变得更好!',
lastLoginIp: '上次登录IP',
lastLoginTime: '上次登录时间',
msgNotify: '消息通知',
noOpRecord: '暂无操作记录',
msgTypeLogin: '登录',
msgTypeNotify: '通知',
},
personal: {
updateInfo: '更新信息',
basicInfo: '基本信息',
inputNewPasswordPlaceholder: '请输入新密码',
updatePersonalInfo: '更新个人信息',
accountInfo: '账号信息',
currentStatus: '当前状态',
boundUp: '已绑定',
notBound: '未绑定',
immediateBinding: '立即绑定',
unbundle: '解绑',
updateSuccess: '更新成功',
bindingSuccess: '绑定成功',
unbundleSuccess: '解绑成功',
},
login: {
accountPasswordLogin: '账号密码登录',
thirdPartyLogin: '第三方登录',
ldapLogin: 'LDAP 登录',
inputUsernamePlaceholder: '请输入用户名',
inputPasswordPlaceholder: '请输入密码',
inputCaptchaPlaceholder: '请输入验证码',
login: '登 录',
loginFailTip: '提示:登录失败超过{loginFailCount}次后将被限制{loginFailMin}分钟内不可再次登录',
loginSuccessTip: '欢迎回来!',
changePassword: '修改密码',
oldPassword: '旧密码',
newPassword: '新密码',
passwordRuleTip: '须为8位以上且包含字⺟⼤⼩写+数字+特殊符号',
passwordChangeSuccessTip: '密码修改成功, 新密码已填充至登录密码框',
otpValidation: 'OTP校验',
qrCode: '二维码',
enterOtpCodeTip: '请输入令牌APP中显示的授权码',
updateBasicInfo: '修改基本信息',
name: '姓名',
inputNamePlaceholder: '请输入姓名',
},
components: {
df: {
fieldModelPlaceholder: 'model关联的字段',
fieldLabel: 'label',
fieldPlaceholder: 'placeholder',
optionalValues: '可选值',
optionalValuesPlaceholder: '可选值 ,分割',
required: '必填',
},
terminal: {
connError: '连接出错',
notConn: '未连接',
connSuccess: '连接成功',
connFail: '连接失败',
connErrMsg: '提示: 连接错误...',
connConfirm: '确认重新连接?',
connected: '已连接',
search: '搜索',
reConnTips: '点击连接状态可重连',
minimize: '最小化',
fullScreenTitle: '全屏|退出全屏',
close: '关闭',
serachPlaceholder: '请输入查找内容,回车搜索',
regexMatch: '正则匹配',
fullWordMatching: '单词全匹配',
caseSensitive: '区分大小写',
incrementalSearch: '增量查找',
previous: '上一个',
next: '下一个',
noMatchMsg: '未查询到匹配项',
},
crontab: {
crontabInputPlaceholder: '可点击左边按钮配置',
crontabTitle: '生成 cron',
second: '秒',
minute: '分钟',
hour: '小时',
day: '日',
month: '月',
week: '周',
year: '年',
timeExpression: '时间表达式',
crontabCompleteExpression: 'crontab完整表达式',
dayCrontype1: '允许的通配符[, - * / L M]',
crontype2: '不指定',
crontype3: '周期从',
crontypeFrom: '从',
crontypeEvery: '每',
appoint: '指定',
crontypeStartDay: '号开始',
crontypeExecDay: '日执行一次',
monthLastDay: '本月最后一天',
hourCronType1: '允许的通配符[, - * /]',
crontypeStartHour: '小时开始',
crontypeExecHour: '小时执行一次',
crontypeStartMin: '分钟开始',
crontypeExecMin: '分钟执行一次',
crontypeStartSecond: '秒开始',
crontypeExecSecond: '秒执行一次',
crontypeStartMonth: '月开始',
crontypeExecMonth: '月执行一次',
yearly: '每年',
crontypeStartYear: '年开始',
crontypeExecYear: '年执行一次',
weekCronType1: '允许的通配符[, - * / L #]',
monday: '周一',
tuesday: '周二',
wednesday: '周三',
thursday: '周四',
friday: '周五',
saturday: '周六',
sunday: '周日',
last5runTimes: '最近5次运行时间',
calculationing: '计算结果中',
},
},
};

View File

@@ -0,0 +1,203 @@
export default {
db: {
// db instance
dbManage: '库管理',
port: '端口',
connParam: '连接参数',
keywordPlaceholder: 'host / 名称 / 编号',
acName: '授权凭证',
dbInst: '数据库实例',
manageDbTitle: '管理【{instName}】数据库',
sqlitePathPlaceholder: '请输入sqlite文件在服务器的绝对地址',
connParamPlaceholder: '其他连接参数,形如: key1=value1&key2=value2',
connSuccess: '连接成功',
showDb: '查看库',
db: '数据库',
dbFilterPlaceholder: '库名: 输入可过滤',
sqlRecord: 'SQL记录',
dump: '导出',
dumpContent: '导出内容',
structure: '结构',
data: '数据',
extName: '扩展名',
dbFilterPlacehoder: '按数据库名称筛选',
allDb: '全部数据库',
dumpDb: '导出数据库',
getDbMode: '获库方式',
noDumpDbMsg: '请添加要导出的数据库',
allSelect: '全选',
selectDbPlacehoder: '获库方式为‘指定库名’时,可选择',
// db
dbInstInfo: '数据库实例信息',
newQuery: '新建查询',
locationTagTree: '定位至左侧树的指定位置',
dbShowSetting: '数据库展示配置',
showFieldComments: '显示字段备注',
autoLocationTagTree: '自动定位树节点',
cacheTableInfo: '缓存表信息-[不开启则实时获取表信息]',
dbName: '库名',
table: '表',
createTable: '创建表',
tableOp: '表操作',
copyTable: '复制表',
renameTable: '重命名',
editTable: '编辑表',
delTable: '删除表',
close: '关闭',
closeOther: '关闭其他',
noDbInstMsg: '请选择数据库实例及对应的schema',
query: '查询',
nQuery: '新查询',
renamePrompt: '重命名表【{db}.{tableName}】',
noChange: '无更改',
isCopyTableData: '是否复制数据?',
execSuccess: '执行成功',
execFail: '执行失败',
sqlScriptRun: 'SQL脚本执行',
saveSql: '保存SQL',
execInfo: '执行信息',
result: '结果',
times: '耗时',
resultSet: '结果集',
tableDataEmptyTextTips: 'tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改',
noSelctRunSqlMsg: '请选中需要执行的sql',
enterExecRemarkTips: '请输入备注',
execRemarkPlaceholder: '输入执行该sql的备注信息',
currentSqlTabIsRunning: '当前结果集tab正在执行, 请使用新标签执行',
sqlCannotEmpty: 'sql内容不能为空',
enterSqlScriptNameTips: '请输入SQL脚本名',
scriptFileUploadRunning: `'{filename}' 正在上传执行, 请关注结果通知`,
runSql: '执行SQL',
newTabRunSql: '新标签执行SQL',
formatSql: '格式化SQL',
execTime: '执行时间',
oneClickCopy: '一键复制',
asc: '升序',
desc: '降序',
fixed: '固定',
cancelFiexd: '取消固定',
formView: '表单视图',
genJson: '生成JSON',
exportCsv: '导出CSV',
exportSql: '导出SQL',
onlySelectOneData: '只能选择一行数据',
editField: '编辑字段',
valueTypeNoMatch: '输入内容与类型不匹配',
tableFieldConf: '表格字段配置',
columnFilterPlaceholder: '输入列名或备注过滤',
selectAll: '选择所有',
submitUpdate: '提交修改',
cancelUpdate: '取消修改',
autoCompleteColumnPlaceholder: '选择列 或 输入SQL条件表达式后回车或点击查询图标过滤结果, 输入时可根据字段名提示',
selectColumn: '选择列',
columnName: '列名',
homePage: '首页',
previousPage: '上一页',
rowsPage: '条/页',
rows: '条',
conditionInputDialogTitle: '请输入 [{columnName}] 的值',
addDataDialogTitle: '添加`{tableName}`表数据',
exportContent: '导出内容',
selectExportTable: '请先选择要导出的表',
tableNamePlaceholder: '表名: 输入可过滤',
comment: '备注',
commentPlaceholder: '备注: 输入可过滤',
dataSize: '数据大小',
indexSize: '索引大小',
column: '列',
index: '索引',
nullable: '是否可为空',
seqInIndex: '列序列号',
// DbSqlExecLog
selectDbPlaceholder: '请选择数据库',
restoreSql: '还原SQL',
stmtType: '操作类型',
execUser: '执行人',
execRes: '执行结果',
oldValue: '原值',
// db transfer
pleaseSetting: '请设置',
log: '日志',
stop: '停止',
run: '运行',
file: '文件',
taskName: '任务名',
srcDb: '源库',
runState: '运行状态',
createDbTransferDialogTitle: '新增数据库迁移任务(迁移不会对源库造成修改)',
editDbTransferDialogTitle: '修改数据库迁移任务(迁移不会对源库造成修改)',
stopConfirm: '确定停止?',
runConfirm: '确定运行?',
transferFileManage: '迁移文件管理',
dbFileType: '文件数据库类型',
targetDb: '目标数据库',
fileDbType: 'sql语言',
transferFileRunDialogTitle: '指定数据库执行sql文件',
targetDbTypeSelectError: '请选择[{dbType}]数据库',
cronAble: '定时迁移',
transferMode: '迁移方式',
transfer2Db: '迁移到数据库',
transfer2File: '迁移到文件',
fileSaveDays: '文件保留天数',
transferStrategy: '迁移策略',
day: '天',
transferFull: '全量',
transferIncrement: '增量(暂不可用)',
nameCase: '转换表、字段名',
none: '无',
lower: '小写',
upper: '大写',
dbObj: '数据库对象',
allTable: '全部表',
custom: '自定义',
noTransferTableMsg: '请选择需要迁移的表',
// dbSync
recentState: '最近任务状态',
dbSync: '数据同步',
realTime: '实时',
noRealTime: '非实时',
srcDataSql: '源数据sql',
targetDbTable: '目标库表',
pageSize: '分页大小',
pageSizePlaceholder: '同步数据时查询的每页数据大小',
updateField: '更新字段',
updateFieldTips: '查询数据源的时候会带上这个字段当前最大值支持带别名t.create_time',
updateFiledPlaceholder: '查询数据源的时候会带上这个字段当前最大值',
updateFieldValue: '更新值',
updateFieldValueTips: '记录更新字段当前值,如:当前时间,当前日期等,下次查询数据时会带上该值条件',
updateFieldValuePlaceholder: '更新字段当前最大值',
fieldValueSrc: '值来源',
fieldValueSrcTips: '从查询结果中取更新值的字段名,默认同更新字段,如果查询结果指定了字段别名且与原更新字段不一致,则取这个字段值为当前更新值',
fieldValueSrcPlaceholder: '更新值来源',
fieldMap: '字段映射',
srcField: '源字段',
targetField: '目标字段',
sqlPreview: 'sql预览',
selectSql: '查询sql',
insertSql: '插入sql',
keyDuplicateStrategy: '键冲突策略',
fieldMapError: '字段映射中存在重复的目标字段,请检查',
noDataSqlMsg: '请输入数据sql',
notSelectSql: 'sql语句错误请输入select语句',
notOneSql: 'sql语句错误请输入单条查询语句',
notColumnSql: '没有查询到字段请检查sql',
// enums
getDbNamesModeAuto: '实时获取',
getDbNamesModeAssign: '指定库名',
ignore: '忽略',
replate: '替换',
running: '运行中',
waitRun: '待运行',
},
};

View File

@@ -0,0 +1,90 @@
export default {
flow: {
// procdef
approvalNode: '审批节点',
procdef: '流程定义',
triggeringCondition: '触发条件',
triggeringConditionTips: 'go template语法。若输出结果为1则表示触发该审批流程',
conditionPlaceholder: '触发条件, 返回值=1, 则表示触发该审批流程',
conditionDefault: `{{/* DBMS-执行sql规则; param参数描述如下 */}}
{{/* stmtType: select / read / insert / update / delete / ddl ; */}}
{{ if eq .bizType "db_sql_exec_flow"}}
{{/* 不是select和read语句时开启流程审批 */}}
{{ if and (ne .param.stmtType "select") (ne .param.stmtType "read") }}
1
{{ end }}
{{ end }}
{{/* Redis-执行命令规则; param参数描述如下 */}}
{{/* cmdType: read(读命令) / write(写命令); */}}
{{/* cmd: get/set/hset...等 */}}
{{ if eq .bizType "redis_run_cmd_flow"}}
{{ if eq .param.cmdType "write" }}
1
{{ end }}
{{ end }}`,
nodeName: '节点名称',
nodeNameTips: '点击指定节点可进行拖拽排序',
auditor: '审核人员',
tasksNotEmpty: '请完善审批节点任务',
tasksNoComplete: '请完善第{index}个审批节点任务信息',
// procdef status enum
enable: '启用',
disable: '禁用',
// procinst
startProcess: '发起流程',
cancelProcessConfirm: '确认取消该流程?',
bizType: '业务类型',
bizKey: '业务Key',
initiator: '发起人',
procdefName: '流程名',
bizStatus: '业务状态',
startingTime: '发起时间',
endTime: '结束时间',
duration: '持续时间',
proc: '流程',
bizInfo: '业务信息',
approvalNodeNotExist: '不存在审批节点',
resourceNotExistFlow: '该资源无需审批操作',
procinstFormError: '请正确填写信息',
procinstStartSuccess: '流程发起成功',
// db run sql flow biz
runSql: '执行SQL',
selectDbPlaceholder: '请选择数据库',
// redis run cmd flow biz
runCmd: '执行Cmd',
selectRedisPlaceholder: '请选择Redis实例与库',
cmdPlaceholder: `如: SET 'key' 'value'; 多条命令;分割`,
// ProcinstStatusEnum
active: '执行中',
completed: '完成',
suspended: '挂起',
terminated: '终止',
cancelled: '取消',
handleResult: '处理结果',
runResult: '执行结果',
// ProcinstBizStatus
waitHandle: '待处理',
handleSuccess: '处理成功',
handleFail: '处理失败',
noHandle: '不处理',
// ProcinstTaskStatus
waitProcess: '待处理',
pass: '通过',
reject: '拒绝',
back: '回退',
canceled: '取消',
// FlowBizType
dbSqlExec: 'DBMS-执行SQL',
redisRunCmd: 'Redis-执行命令',
// task
audit: '审核',
procinstStatus: '流程状态',
taskStatus: '任务状态',
taskName: '当前节点',
taskBeginTime: '开始时间',
flowAudit: '流程审批',
},
};

View File

@@ -0,0 +1,135 @@
export default {
machine: {
keywordPlaceholder: 'ip / 名称 / 编号',
acName: '授权凭证',
runningStat: '运行状态',
fs: '磁盘(挂载点=>可用/总)',
remark: '备注',
memberInfo: '内存(可用/总)',
cpuInfo: 'CPU(空闲)',
file: '文件',
directory: '目录',
folder: '文件夹',
script: '脚本',
process: '进程',
terminalPlayback: '终端回放',
createMachine: '添加机器',
editMachine: '编辑机器',
reConnTips: '确认重新连接?',
clickReConn: '点击重连',
port: '端口',
sshTunnel: 'SSH隧道',
newOpenTabTerminalTips: '按住ctrl则为新标签打开',
newTab: '新窗口',
openTerminal: '打开终端',
newTabOpenTerminal: '打开终端(新窗口)',
fileManage: '文件管理',
scriptManage: '脚本管理',
machineState: '机器状态',
remoteFileDesktopManage: '远程桌面文件管理', // Remote desktop file management
remoteDesktop: '远程桌面',
protocol: '协议',
ipAndPort: 'ip和port',
connSuccess: '连接成功',
noAcErrMsg: '请完善授权凭证账号信息',
// MachineRec
playback: '回放',
cmd: '命令',
execCmdRecord: '执行命令记录', //Executive command record
execTime: '执行时间', // execution time
operator: '操作人',
beginTime: '开始时间',
endTime: '结束时间',
// MachineStats
basicInfo: '基本信息', // basic information
hostname: '主机名',
runTime: '运行时间',
totalTask: '总任务',
runningTask: '运行中任务',
load: '负载',
disk: '磁盘',
mountPoint: '挂载点',
available: '可使用',
used: '已使用',
networkCard: '网卡',
receive: '接收',
send: '发送',
memory: '内存',
cpuUsageRate: 'CPU使用率',
// process
processName: '进程名',
selectSortType: '请选择排序类型',
selectProcessNum: '请选择进程个数',
cpuDesc: 'CUP降序',
memDesc: '内存降序',
virtualMemory: '虚拟内存',
fixedMemory: '固定内存',
procState: '进程状态',
startTime: '启动时间',
procCpuRunTime: '该进程实际使用CPU运作的时间',
killProcConfirm: '确定终止该进程?',
kill: '终止',
// script
execute: '执行',
scriptParam: '脚本参数',
execResult: '执行结果',
execCompleted: '执行完成', // execution is completed
scriptParamTips1: '1. 脚本内容中可使用{{.model}}作为占位符',
scriptParamTips2: '2. 执行脚本时可输入对应表单内容对占位符进行替换后执行',
scriptResultEnumResult: '有结果',
scriptResultEnumNoResult: '无结果',
scriptResultEnumRealTime: '实时交互',
scriptTypeEnumPrivate: '私有',
scriptTypeEnumPublic: '公共',
// security
cmdConfig: '命令配置',
filterCmds: '过滤命令',
relateMachine: '关联机器',
newCmd: '新建命令',
cmdPlaceholder: '请输入命令正则表达式',
// cronjob
cronjob: '计划任务',
machineCode: '机器编号',
cronjobRunning: '运行中',
cronjobNoRun: '未运行',
cronjobRun: '执行',
cronJobExecStatusEnumSuccess: '成功',
cronJobExecStatusEnumFail: '失败',
cronjobExecResult: '执行结果',
cronjobExecTime: '执行时间',
cronjobExecRecord: '执行记录',
runSuccess: '执行成功',
cronjobRunState: '运行状态',
execResRecordType: '结果记录类型',
cronExpression: 'cron表达式',
// file
upload: '上传',
download: '下载',
copy: '复制',
move: '移动',
paste: '粘贴',
fileNameFilterPlaceholder: '名称: 输入可过滤',
calculate: '计算',
modificationTime: '修改时间',
attribute: '属性',
user: '用户',
group: '组',
renameTips: 'rename: 双击文件名单元格修改后回车',
fileDetail: '文件详情',
createFile: '新建文件',
pasteSuccess: '粘贴成功',
sameDirNoPaste: '同目录下不能粘贴',
renameSuccess: '重命名成功',
newFileNameNotEmpty: '新名称不能为空',
fileTooLargeTips: '文件太大, 请下载使用',
uploadSuccess: '上传成功',
fileExceedsSysConf: '上传的文件超过系统配置的【{uploadMaxFileSize}】',
},
};

View File

@@ -0,0 +1,44 @@
export default {
mongo: {
mongo: 'MongoDB',
db: '数据库',
keywordPlaceholder: 'host / 名称 / 编号',
connUrl: '连接url',
dbList: '数据库列表',
isEmpty: '是否为空',
deleteDbConfirm: '确定删除该库?',
coll: '集合',
deleteCollConfirm: '确定删除该集合?',
collState: '集合状态',
createDbAndColl: '新建库&集合',
dbName: '库名',
collName: '集合名',
createColl: '新建集合',
collTitle: '`{dbName}` 集合',
template: '模板',
cmdTemplatePlaceholder: '选择命令模板',
moreCmdTips: '更多命令查看',
usersInfoDesc: '获取用户信息',
createUserDesc: '创建新用户',
grantRolesToUserDesc: '授予对用户的额外角色',
dropUserDesc: '删除用户',
roleInfoDesc: '获取角色信息',
createRoleDesc: '创建角色',
runSuccess: '执行成功',
queryParam: '查询参数',
queryParamPlaceholder: '点击输入相应查询条件',
deleteDocConfirm: '确定删除该文档?',
doc: '文档',
findParamErrMsg: 'filter或sort字段json字符串值错误。注意: json key需双引号',
docErrMsg: '文档内容错误,无法解析为json对象',
insertSuccess: '新增成功',
insertFail: '新增失败',
idNotExist: '文档的_id属性不存在',
modifyFail: '修改失败',
deleteFail: '删除失败',
docParse2jsonFail: '文档内容解析为json对象失败',
},
};

View File

@@ -0,0 +1,64 @@
export default {
redis: {
standaloneInfo: '单机信息',
clusterInfo: '集群信息',
node: '节点',
clusterIpTips: 'ip:port1@port2port1指redis服务器与客户端通信的端口port2则是集群内部节点间通信的端口',
masterSlaveRelationTips: `如果节点是slave并且已知master节点则为master节点ID否则为符号'-'`,
configEpochTips: '节点的epoch值如果该节点是从节点则为其主节点的epoch值。每当节点发生失败切换时都会创建一个新的独特的递增的epoch。',
keywordPlaceholder: 'host / 名称 / 编号',
hostTips: `请输入host:portsentinel模式为: mastername=sentinelhost:port若集群或哨兵需设多个节点可使用','分割`,
nodePassword: '节点密码',
sentinelHostErr: 'sentinel模式host需为: mastername=sentinelhost:sentinelport模式',
delimiter: '分隔符',
keyMatchTips: 'match 支持*模糊key, 回车搜索',
loadMore: '加载更多',
addKey: '新增key',
newTabOpen: '新tab打开',
redisSelectErr: '请先选择redis',
flushDbTips: '确定清空[{db}]库的所有key?',
keyNotEmpty: 'Key不能为空',
// info
redisInfoTitle: 'Redis服务器信息',
version: '版本',
port: '端口',
mode: '模式',
os: '操作系统',
uptimeDays: '运行天数',
execPath: '可执行文件路径',
confFile: '配置文件路径',
clusterEnable: '是否启用集群模式',
nodeCount: '节点总数',
clientConn: '客户端连接',
connectedNum: '已连接客户端数',
blockedClientNum: '正在等待阻塞命令客户端数',
sysCpu: '系统CPU',
userCpu: '用户CPU',
sysChildCpu: '后台系统CPU',
userChildCpu: '后台用户CPU',
keyCount: '键值统计',
countInfo: '统计信息',
totalCmdProcess: '总处理命令数',
curQps: '当前qps',
expiredKeys: '过期key的总数量',
netInputBytes: '网络入口流量字节数',
netOutputBytes: '网络出口流量字节数',
persistence: '持久化',
aofEnable: '是否启用aof',
loadingPersistence: '是否正在载入持久化文件',
availableMemory: '可用内存',
usedMemory: '已用内存',
renameTips: '点击重命名',
ttlPlaceholder: '单位(秒),负数永久',
ttlTips: '点击修改过期时间',
settingSuccess: '设置成功',
permanent: '永久',
persistenceConfirm: '确定持久化该key?',
addNewLine: '添加新行',
filterPlaceholder: '关键词回车搜索',
deleteConfirm: '确定删除?',
},
};

View File

@@ -0,0 +1,210 @@
export default {
system: {
menu: {
filterPlaceholder: '输入关键字过滤(右击操作)',
opTips: '红色、橙色字体表示禁用状态 (右击资源进行操作)',
info: '资源信息',
menu: '菜单',
permission: '权限',
icon: '图标',
routerName: '路由名',
componentPath: '组件路径',
isCache: '是否缓存',
isHide: '是否隐藏',
tagIsDelete: 'tag不可删除',
externalLink: '外链',
yes: '是',
no: '否',
addSubResource: '添加子资源',
enable: '启用',
disable: '禁用',
tips: '提示',
addTopMenu: '添加顶级菜单',
addChildrenMenuTitle: '添加`{parentName}`的子资源',
updateMenu: '修改`{name}`',
success: '成功',
menuCodeTips: `菜单类型则为访问路径(若菜单路径不以'/'开头则访问地址会自动拼接父菜单路径)、否则为资源唯一编码`,
menuCodePlaceholder: `菜单不以'/'开头则自动拼接父菜单路径`,
routerNameTips: '与vue的组件名一致才可使组件缓存生效如ResourceLis',
componentPathTips: '访问的组件路径,如:`system/resource/ResourceList`,默认在`views`目录下',
isCacheTips: '选择是则会被`keep-alive`缓存(重新进入页面不会刷新页面及重新请求数据)需要路由名与vue的组件名一致',
isHideTips: '选择隐藏则路由将不会出现在菜单栏中,但仍然可以访问。禁用则不可访问与操作',
externalLinkTips: '内嵌: 以iframe展示、外链: 新标签打开',
inline: '内嵌',
linkAddress: '链接地址',
linkPlaceholder: '外链/内嵌的链接地址http://xxx.com',
menuNameRuleMsg: '请输入资源名称',
routeNameNotEmpty: '路由名不能为空',
resourceCodePatternErrMsg: '只允许输入1-32位大小写字母、数字、_-.:',
assignedRole: '已分配角色',
},
account: {
roleAllocation: '角色分配',
resetOtp: '重置OTP',
assigner: '分配者',
allocateTime: '分配时间',
name: '姓名',
lastLoginTime: '最后登录时间',
deleteAccountConfirm: '确定删除【{name}】的账号?',
usernamePlacholder: '5-16位大小写字母、数字、_-.:',
random: '随机',
usernamePatternErrMsg: '只允许输入5-16位大小写字母、数字、_-.:',
accountSearchPlaceholder: '输入账号模糊搜索并选择',
accountInfo: '账号信息',
allocateRoleTitle: '分配 `{name}` 的角色',
allocated: '已分配',
undistributed: '未分配',
menuAndPermission: '菜单&权限',
remove: '移除',
allocation: '分配',
roleStatus: '角色状态',
userMenuTitle: '`{name}` 的菜单&权限',
statusEnable: '启用',
statusDisable: '禁用',
},
role: {
permissionDetail: '权限详情',
permissionAllocate: '权限分配',
userManage: '用户管理',
roleName: '角色名',
roleCode: '角色编码',
rolePermissionTitle: '`{name} 的菜单&权限`',
roleCodePlaceholder: 'COMMON开头则为所有账号共有角色',
statusEnable: '启用',
statusDisable: '禁用',
allocateMenuTitle: '分配 `{roleName}` 的菜单&权限',
allocateAccountTitle: '`{roleName}` 关联的账号',
addAccount: '添加账号',
userStatus: '用户状态',
assigner: '分配者',
allocateTime: '分配时间',
permissionInfo: '权限分配信息',
},
sysconf: {
confItem: '配置项',
confKey: '配置key',
permission: '权限',
permissionPlaceholder: '请输入账号模糊搜索并选择',
conf: '配置',
confItemSetting: '配置项设置',
confValue: '配置值',
fileConf: '文件配置',
fileConfRemark: '系统文件配置',
basePath: '基础路径',
baesPathPlaceholder: '默认为可执行文件对应目录下./file',
dbmsConf: 'DBMS配置',
dbmsConfRemark: '数据库相关配置',
recordQuerySql: '记录查询sql',
recordQuerySqlPlaceholder: '是否记录查询类sql',
maxResultSet: '最大结果集',
maxResultSetPlaceholder: '允许sql查询的最大结果集数。注: 0=不限制',
sqlExecLimt: 'sql执行时间限制',
sqlExecLimtPlaceholder: '超过该时间(单位:秒),执行将被取消',
machineConf: '机器相关配置',
machineConfRemark: '机器相关配置,如终端操作记录保存天数等',
uploadMaxFileSize: '上传文件大小限制',
uploadMaxFileSizePlaceholder: '允许上传的最大文件大小(1MB、2GB等)',
termOpSaveDays: '终端记录保存时间',
termOpSaveDaysPlaceholder: '单位天,超过该时间,将删除终端操作记录',
guacdHost: 'guacd服务ip',
guacdHostPlaceholder: 'guacd服务ip默认 127.0.0.1',
guacdPort: 'guacd服务端口',
guacdPortPlaceholder: 'guacd服务端口默认 4822',
guacdFilePath: 'guacd服务文件存储位置',
guacdFilePathPlaceholder: 'guacd服务文件存储位置用于挂载RDP文件夹',
systemConf: '系统全局样式设置',
systemConfRemark: '系统icon、标题、水印信息等配置',
logoIcon: 'logo图标',
logoIconPlaceholder: '系统logo图标base64编码, 建议svg格式不超过10k',
title: '菜单栏标题',
titlePlaceholder: '系统菜单栏标题展示',
viceTitle: '登录页标题',
viceTitlePlaceholder: '登录页标题展示',
useWatermark: '是否启用水印',
useWatermarkTips: '是否启用系统水印',
watermarkContent: '水印补充信息',
watermarkContentPlaceholder: '水印补充信息,如公司名称等',
ldapLoginConf: 'LDAP登录配置',
ldapLoginConfRemark: 'ldap登录相关配置',
ldapEnable: '是否启用',
dapEnablePlaceholder: '是否启用ldap登录',
host: 'host',
port: 'port',
bindDN: 'bindDN',
bindDnPlaceholder: 'LDAP服务的管理员账号如: "cn=admin,dc=example,dc=com"',
bindPwd: 'bindPwd',
bindPwdPlaceholder: 'LDAP服务的管理员密码',
baseDN: 'baseDN',
baseDnPlaceholder: '用户所在的 base DN, 如: "ou=users,dc=example,dc=com"',
userFilter: 'userFilter',
userFilerPlaceholder: '过滤用户的方式, 如: "(uid=%s)、(&(objectClass=organizationalPerson)(uid=%s))"',
uidMap: 'uidMap',
uidMapPlaceholder: '用户id和 LDAP 字段名之间的映射关系,如: cn',
udnMap: 'udnMap',
udnMapPlaceholder: '用户姓名(dispalyName)和 LDAP 字段名之间的映射关系,如: displayName',
emailMap: 'emailMap',
emailMapPlaceholder: '用户email和 LDAP 字段名之间的映射关系',
skipTlsVerfify: 'skipTlsVerfify',
skipTlsVerfifyPlaceholder: '客户端是否跳过 TLS 证书验证',
securityProtocol: '安全协议',
securityProtocolPlaceholder: '安全协议为Null不使用安全协议如: StartTLS, LDAPS',
oauth2LoginConf: 'OAuth2登录配置',
oauth2LoginConfRemark: 'oauth2登录相关配置信息',
oauth2Enable: '是否启用',
oauth2EnablePlaceholder: '是否启用oauth2登录',
name: '名称',
namePlaceholder: 'oauth2名称',
clientId: 'Client ID',
clientIdPlaceholder: 'oauth2 Client ID',
clientSecret: 'Client Secret',
clientSecretPlaceholder: 'oauth2 Client Secret',
authorizationUrl: 'Authorization URL',
authorizationUrlPlaceholder: 'oauth2 授权地址',
accessTokenUrl: 'Access Token URL',
accessTokenUrlPlaceholder: 'oauth2 获取token地址',
redirectUrl: 'Redirect URL',
redirectUrlPlaceholder: '本系统地址',
scope: 'Scopes',
scopePlaceholder: 'oauth2 Scopes',
resourceUrl: 'Resource URL',
resourceUrlPlaceholder: '获取用户信息资源地址',
userId: 'User ID',
userIdPlaceholder: '用户唯一标识字段;格式为type:fieldPath(string:username)',
autoRegister: '是否自动注册',
accountLoginConf: '账号登录安全设置',
accountLoginConfRemark: '系统账号登录相关安全设置',
useCaptcha: '登录验证码',
useCaptchaPlaceholder: '是否启用登录验证码',
useOtp: '是否启用OTP',
useOtpPlaceholder: '是否启用双因素(OTP)校验',
otpIssuer: 'OTP签发人',
loginFailCount: '允许登录失败次数',
loginFailCountPlaceholder: '登录失败n次后禁止登录',
loginFainMin: '登录失败禁止登录时间',
loginFailMinPlaceholder: '登录失败指定次数后禁止m分钟内再次登录',
},
syslog: {
operator: '操作人',
operatorPlaceholder: '请输入并选择账号',
operatingResult: '操作结果',
description: '描述',
operatingTime: '操作时间',
operatingInfo: '操作信息',
result: '结果',
response: '响应信息',
resultSuccess: '成功',
resultFail: '失败',
resultRunning: '执行中',
},
oauth: {
authSuccess: '授权认证成功',
},
},
};

View File

@@ -0,0 +1,56 @@
export default {
tag: {
relateTag: '关联标签',
keywordFilterPlaceholder: '关键字过滤',
nameFilterPlaceholder: '关键字过滤(右击节点操作)',
tagFilterPlaceholder: '输入关键字->搜索已展开节点信息',
tagTips1: '1. 用于将资产进行归类',
tagTips2: '2. 可在团队管理中进行分配,用于资源隔离',
tagTips3: '3. 拥有父标签的团队成员可访问操作其自身或子标签关联的资源',
machine: '机器',
db: '数据库',
code: '编号',
createSubTag: '创建子标签',
createSubTagTitle: '创建【{codePath}】的子标签',
rootTag: '根标签',
selectTagPlaceholder: '请选择关联标签',
},
team: {
team: '团队',
member: '成员',
addMember: '添加成员',
teamMember: '【{teamName}】成员信息',
selectAccountTips: '请先选择账号',
joinTime: '加入时间',
accountName: '姓名',
assigner: '分配人',
allocateTag: '分配标签',
validity: '有效期',
effectiveStartTime: '生效开始时间',
effectiveEndTime: '生效结束时间',
},
// authcert
ac: {
namePlaceholder: '请输入凭证名 (全局唯一)',
privateKeyPlaceholder: '请将私钥文件内容拷贝至此',
privateKeyPwd: '秘钥密码',
resourceCode: '资源编号',
credentialName: '凭证名称',
resourceType: '资源类型',
credentialType: '凭证类型',
ciphertextType: '密文类型',
privateKey: '秘钥',
ac: '授权凭证',
testConn: '测试连接',
connSuccess: '连接成功',
usernameExist: '该用户名已存在于该账号列表中',
publicAc: '公共凭证',
acTypeEnumPublic: '公共凭证',
acTypeEnumPrivate: '普通凭证',
acTypeEnumPrivileged: '特权凭证',
acTypeEnumPrivateDefault: '默认凭证',
ciphertextTypeEnumPassword: '密码',
ciphertextTypeEnumPrivateKey: '秘钥',
ciphertextTypeEnumPublic: '公共凭证',
},
};

View File

@@ -2,29 +2,25 @@
<div class="layout-columns-aside">
<el-scrollbar>
<ul>
<li
v-for="(v, k) in state.columnsAsideList"
:key="k"
@click="onColumnsAsideMenuClick(v, k)"
:ref="
(el) => {
if (el) columnsAsideOffsetTopRefs[k] = el;
}
"
:class="{ 'layout-columns-active': state.liIndex === k }"
:title="v.meta.title"
>
<div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.linkType == 1)">
<li v-for="(v, k) in state.columnsAsideList" :key="k" @click="onColumnsAsideMenuClick(v, k)" :ref="(el) => {
if (el) columnsAsideOffsetTopRefs[k] = el;
}
" :class="{ 'layout-columns-active': state.liIndex === k }" :title="$t(v.meta.title)">
<div class="layout-columns-aside-li-box"
v-if="!v.meta.link || (v.meta.link && v.meta.linkType == 1)">
<i :class="v.meta.icon"></i>
<div class="layout-columns-aside-li-box-title font12">
{{ v.meta.title && v.meta.title.length >= 4 ? v.meta.title.substr(0, 4) : v.meta.title }}
{{ $t(v.meta.title) && $t(v.meta.title).length >= 4 ? $t(v.meta.title).substr(0, 4) :
$t(v.meta.title) }}
</div>
</div>
<div class="layout-columns-aside-li-box" v-else>
<a :href="v.meta.link" target="_blank">
<i :class="v.meta.icon"></i>
<div class="layout-columns-aside-li-box-title font12">
{{ v.meta.title && v.meta.title.length >= 4 ? v.meta.title.substr(0, 4) : v.meta.title }}
{{ $t(v.meta.title) && $t(v.meta.title).length >= 4 ? $t(v.meta.title).substr(0, 4) :
$t(v.meta.title)
}}
</div>
</a>
</div>

View File

@@ -6,11 +6,11 @@
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
<span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
{{ v.meta.title }}
{{ $t(v.meta.title) }}
</span>
<a v-else @click.prevent="onBreadcrumbClick(v)">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
{{ v.meta.title }}
{{ $t(v.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>

View File

@@ -4,7 +4,7 @@
<el-autocomplete
v-model="state.menuQuery"
:fetch-suggestions="menuSearch"
placeholder="菜单搜索"
:placeholder="$t('layout.user.searchPlaceholder')"
prefix-icon="el-icon-search"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
@@ -16,7 +16,7 @@
</el-icon>
</template>
<template #default="{ item }">
<div><SvgIcon :name="item.meta.icon" class="mr5" />{{ item.meta.title }}</div>
<div><SvgIcon :name="item.meta.icon" class="mr5" />{{ $t(item.meta.title) }}</div>
</template>
</el-autocomplete>
</el-dialog>
@@ -27,6 +27,9 @@
import { reactive, ref, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import { useRoutesList } from '@/store/routesList';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const layoutMenuAutocompleteRef: any = ref(null);
const router = useRouter();
@@ -50,16 +53,18 @@ const openSearch = () => {
const closeSearch = () => {
state.isShowSearch = false;
};
// 菜单搜索数据过滤
const menuSearch = (queryString: any, cb: any) => {
let results = queryString ? state.tagsViewList.filter(createFilter(queryString)) : state.tagsViewList;
let results = queryString ? state.tagsViewList.filter(createFilter(t(queryString))) : state.tagsViewList;
cb(results);
};
// 菜单搜索过滤
const createFilter = (queryString: any) => {
return (restaurant: any) => {
return (
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 || restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1
t(restaurant.path).toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
t(restaurant.meta.title).toLowerCase().indexOf(queryString.toLowerCase()) > -1
);
};
};

View File

@@ -1,35 +1,35 @@
<template>
<div class="layout-breadcrumb-seting">
<el-drawer title="布局设置" v-model="themeConfig.isDrawer" direction="rtl" destroy-on-close size="240px" @close="onDrawerClose">
<el-drawer :title="$t('layout.config.configTitle')" v-model="themeConfig.isDrawer" direction="rtl" destroy-on-close size="240px" @close="onDrawerClose">
<el-scrollbar class="layout-breadcrumb-seting-bar">
<!-- ssh终端主题 -->
<el-divider content-position="left">终端主题</el-divider>
<el-divider content-position="left">{{ $t('layout.config.terminalTheme') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.theme') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalTheme" size="small" style="width: 140px">
<el-select v-model="themeConfig.terminalTheme" size="small" style="width: 140px">
<el-option v-for="(_, k) in themes" :key="k" :label="k" :value="k"> </el-option>
<el-option label="自定义" value="custom"> </el-option>
<el-option :label="$t('layout.config.custom')" value="custom"> </el-option>
</el-select>
</div>
</div>
<template v-if="themeConfig.terminalTheme == 'custom'">
<div class="layout-breadcrumb-seting-bar-flex mt10">
<div class="layout-breadcrumb-seting-bar-flex-label">字体颜色</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.fontColor') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.terminalForeground" size="small" @change="onColorPickerChange('terminalForeground')">
</el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">背景颜色</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.backgroundColor') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.terminalBackground" size="small" @change="onColorPickerChange('terminalBackground')">
</el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">cursor颜色</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.cursorColor') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.terminalCursor" size="small" @change="onColorPickerChange('terminalCursor')">
</el-color-picker>
@@ -38,35 +38,27 @@
</template>
<div class="layout-breadcrumb-seting-bar-flex mt10">
<div class="layout-breadcrumb-seting-bar-flex-label">字体大小</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.fontSize') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-input-number
v-model="themeConfig.terminalFontSize"
controls-position="right"
:min="12"
:max="24"
@change="setLocalThemeConfig"
size="small"
style="width: 90px"
>
<el-input-number v-model="themeConfig.terminalFontSize" controls-position="right" :min="12" :max="24" size="small" style="width: 90px">
</el-input-number>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt10">
<div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.fontWeight') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalFontWeight" size="small" style="width: 90px">
<el-select v-model="themeConfig.terminalFontWeight" size="small" style="width: 90px">
<el-option label="normal" value="normal"> </el-option>
<el-option label="bold" value="bold"> </el-option>
</el-select>
</div>
</div>
<el-divider content-position="left">editor 设置</el-divider>
<el-divider content-position="left">{{ $t('layout.config.editorSetting') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.theme') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select @change="setLocalThemeConfig" v-model="themeConfig.editorTheme" size="small" style="width: 130px">
<el-select v-model="themeConfig.editorTheme" size="small" style="width: 130px">
<el-option label="vs" value="vs"> </el-option>
<el-option label="vs-dark" value="vs-dark"> </el-option>
<el-option label="SolarizedLight" value="SolarizedLight"> </el-option>
@@ -75,16 +67,15 @@
</div>
<!-- 全局设置 -->
<el-divider content-position="left">全局设置</el-divider>
<el-divider content-position="left">{{ $t('layout.config.globalSetting') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">分页size</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.pagesize') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-input-number
v-model="themeConfig.defaultListPageSize"
controls-position="right"
:min="10"
:max="50"
@change="setLocalThemeConfig"
size="small"
style="width: 90px"
>
@@ -93,7 +84,7 @@
</div>
<!-- 全局主题 -->
<el-divider content-position="left">全局主题</el-divider>
<el-divider content-position="left">{{ $t('layout.config.globalTheme') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
@@ -125,129 +116,156 @@
</div>
</div>
<!-- 菜单 / 顶栏 -->
<el-divider content-position="left">菜单 / 顶栏</el-divider>
<!-- 菜单 -->
<el-divider content-position="left">{{ $t('layout.config.menuSetting') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.topBar" size="small" @change="onBgColorPickerChange('topBar')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">菜单背景</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.menuBar') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.menuBar" size="small" @change="onBgColorPickerChange('menuBar')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.menuBarFontColor') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.menuBarColor" size="small" @change="onBgColorPickerChange('menuBarColor')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.menuBarActiveColor') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isMenuBarColorHighlight" @change="onMenuBarHighlightChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isMenuBarColorGradual') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isMenuBarColorGradual" @change="onMenuBarGradualChange"></el-switch>
</div>
</div>
<!-- <div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.topBar" size="small"
@change="onBgColorPickerChange('topBar')">
</el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单背景</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.columnsMenuBar" size="small" @change="onBgColorPickerChange('columnsMenuBar')"> </el-color-picker>
<el-color-picker v-model="themeConfig.columnsMenuBar" size="small"
@change="onBgColorPickerChange('columnsMenuBar')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏默认字体颜色</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.topBarColor" size="small" @change="onBgColorPickerChange('topBarColor')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">菜单默认字体颜色</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.menuBarColor" size="small" @change="onBgColorPickerChange('menuBarColor')"> </el-color-picker>
<el-color-picker v-model="themeConfig.topBarColor" size="small"
@change="onBgColorPickerChange('topBarColor')">
</el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单默认字体颜色</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="themeConfig.columnsMenuBarColor" size="small" @change="onBgColorPickerChange('columnsMenuBarColor')">
<el-color-picker v-model="themeConfig.columnsMenuBarColor" size="small"
@change="onBgColorPickerChange('columnsMenuBarColor')">
</el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt10">
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景渐变</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isTopBarColorGradual" @change="onTopBarGradualChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">菜单背景渐变</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isMenuBarColorGradual" @change="onMenuBarGradualChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单背景渐变</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isColumnsMenuBarColorGradual" @change="onColumnsMenuBarGradualChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">菜单字体背景高亮</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isMenuBarColorHighlight" @change="onMenuBarHighlightChange"></el-switch>
<el-switch v-model="themeConfig.isTopBarColorGradual"
@change="onTopBarGradualChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14">
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单背景渐变</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isColumnsMenuBarColorGradual"
@change="onColumnsMenuBarGradualChange"></el-switch>
</div>
</div> -->
<!-- 界面设置 -->
<el-divider content-position="left">界面设置</el-divider>
<el-divider content-position="left">{{ $t('layout.config.interfaceSetting') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">菜单水平折叠</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isCollapse') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isCollapse" @change="onThemeConfigChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">菜单手风琴</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isUniqueOpened') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isUniqueOpened" @change="setLocalThemeConfig"></el-switch>
<el-switch v-model="themeConfig.isUniqueOpened"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">固定 Header</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isFixedHeader') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isFixedHeader" @change="onIsFixedHeaderChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: themeConfig.layout !== 'classic' ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex-label">经典布局分割菜单</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isClassicSplitMenu') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isClassicSplitMenu" :disabled="themeConfig.layout !== 'classic'" @change="onClassicSplitMenuChange">
</el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">开启锁屏</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isLockScreen') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isLockScreen" @change="setLocalThemeConfig"></el-switch>
<el-switch v-model="themeConfig.isLockScreen"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt11">
<div class="layout-breadcrumb-seting-bar-flex-label">自动锁屏(s/)</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.lockScreenTime') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-input-number
v-model="themeConfig.lockScreenTime"
controls-position="right"
:min="0"
:max="9999"
@change="setLocalThemeConfig"
size="small"
style="width: 90px"
>
<el-input-number v-model="themeConfig.lockScreenTime" controls-position="right" :min="0" :max="9999" size="small" style="width: 90px">
</el-input-number>
</div>
</div>
<!-- 界面显示 -->
<el-divider content-position="left">界面显示</el-divider>
<el-divider content-position="left">{{ $t('layout.config.interfaceDisplay') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">侧边栏 Logo</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.isShowLogo') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isShowLogo" @change="onIsShowLogoChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: themeConfig.layout === 'transverse' ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex-label">开启Breadcrumb</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isBreadcrumb') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch
v-model="themeConfig.isBreadcrumb"
@@ -257,60 +275,68 @@
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">开启Breadcrumb图标</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isBreadcrumbIcon') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isBreadcrumbIcon" @change="setLocalThemeConfig"></el-switch>
<el-switch v-model="themeConfig.isBreadcrumbIcon"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">开启 Tagsview</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.isTagsview') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isTagsview" @change="setLocalThemeConfig"></el-switch>
<el-switch v-model="themeConfig.isTagsview"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">开启 Tagsview图标</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isTagsviewIcon') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isTagsviewIcon" @change="setLocalThemeConfig"></el-switch>
<el-switch v-model="themeConfig.isTagsviewIcon"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">开启 TagsView缓存</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isCacheTagsView') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isCacheTagsView" @change="setLocalThemeConfig"></el-switch>
<el-switch v-model="themeConfig.isCacheTagsView"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">开启 TagsView拖拽</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.isSortableTagsView') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isSortableTagsView" @change="onSortableTagsViewChange"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">开启 Footer</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.isFooter') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isFooter" @change="setLocalThemeConfig"></el-switch>
<el-switch v-model="themeConfig.isFooter"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">灰色模式</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.isGrayscale') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isGrayscale" @change="onAddFilterChange('grayscale')"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">色弱模式</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.isInvert') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="themeConfig.isInvert" @change="onAddFilterChange('invert')"></el-switch>
</div>
</div>
<!-- 其它设置 -->
<el-divider content-position="left">其他设置</el-divider>
<el-divider content-position="left">{{ $t('layout.config.otherSetting') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">Tagsview 风格</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.tagsStyle') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="themeConfig.tagsStyle" placeholder="请选择" size="small" style="width: 90px" @change="setLocalThemeConfig">
<el-select v-model="themeConfig.tagsStyle" placeholder="请选择" size="small" style="width: 90px">
<el-option label="风格1" value="tags-style-one"></el-option>
<el-option label="风格2" value="tags-style-two"></el-option>
<el-option label="风格3" value="tags-style-three"></el-option>
@@ -318,9 +344,9 @@
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">主页面切换动画</div>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('layout.config.animation') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="themeConfig.animation" placeholder="请选择" size="small" style="width: 90px" @change="setLocalThemeConfig">
<el-select v-model="themeConfig.animation" size="small" style="width: 90px">
<el-option label="slide-right" value="slide-right"></el-option>
<el-option label="slide-left" value="slide-left"></el-option>
<el-option label="opacitys" value="opacitys"></el-option>
@@ -328,9 +354,11 @@
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
<div class="layout-breadcrumb-seting-bar-flex-label">分栏高亮风格</div>
<div class="layout-breadcrumb-seting-bar-flex-label">
{{ $t('layout.config.columnsAsideStyle') }}
</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="themeConfig.columnsAsideStyle" placeholder="请选择" size="small" style="width: 90px" @change="setLocalThemeConfig">
<el-select v-model="themeConfig.columnsAsideStyle" placeholder="请选择" size="small" style="width: 90px">
<el-option label="圆角" value="columns-round"></el-option>
<el-option label="卡片" value="columns-card"></el-option>
</el-select>
@@ -338,7 +366,7 @@
</div>
<!-- 布局切换 -->
<el-divider content-position="left">布局切换</el-divider>
<el-divider content-position="left">{{ $t('layout.config.layoutSwitch') }}</el-divider>
<div class="layout-drawer-content-flex">
<!-- defaults 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('defaults')">
@@ -351,7 +379,7 @@
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'defaults' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">默认</p>
<p class="layout-tips-txt">{{ $t('layout.config.defaults') }}</p>
</div>
</div>
</div>
@@ -368,7 +396,7 @@
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'classic' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">经典</p>
<p class="layout-tips-txt">{{ $t('layout.config.classic') }}</p>
</div>
</div>
</div>
@@ -384,7 +412,7 @@
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'transverse' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">横向</p>
<p class="layout-tips-txt">{{ $t('layout.config.transverse') }}</p>
</div>
</div>
</div>
@@ -400,7 +428,7 @@
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'columns' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">分栏</p>
<p class="layout-tips-txt">{{ $t('layout.config.columns') }}</p>
</div>
</div>
</div>
@@ -429,12 +457,12 @@ import ClipboardJS from 'clipboard';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import { getLightColor } from '@/common/utils/theme';
import { setLocal, getLocal, removeLocal } from '@/common/utils/storage';
import { setLocal, getLocal } from '@/common/utils/storage';
import mittBus from '@/common/utils/mitt';
import themes from '@/components/terminal/themes';
const copyConfigBtnRef = ref();
const { themeConfig } = storeToRefs(useThemeConfig());
const { themeConfig } = storeToRefs(useThemeConfig()) as any;
// 1、全局主题
const onColorPickerChange = (color: string) => {
@@ -475,7 +503,7 @@ const setGraduaFun = (el: string, bool: boolean, color: string) => {
if (!els) return false;
if (bool) els.setAttribute('style', `background-image:linear-gradient(to bottom left , ${color}, ${getLightColor(color, 0.6)})`);
else els.setAttribute('style', `background-image:${color}`);
setLocalThemeConfig();
const elNavbars: any = document.querySelector('.layout-navbars-breadcrumb-index');
const elAside: any = document.querySelector('.layout-container .el-aside');
const elColumns: any = document.querySelector('.layout-container .layout-columns-aside');
@@ -498,7 +526,6 @@ const onMenuBarHighlightChange = () => {
} else {
elActive.setAttribute('id', ``);
}
setLocalThemeConfig();
}, 0);
});
};
@@ -510,30 +537,26 @@ const onThemeConfigChange = () => {
// 3、界面设置 --> 固定 Header
const onIsFixedHeaderChange = () => {
themeConfig.value.isFixedHeaderChange = themeConfig.value.isFixedHeader ? false : true;
setLocalThemeConfig();
};
// 3、界面设置 --> 经典布局分割菜单
const onClassicSplitMenuChange = () => {
themeConfig.value.isBreadcrumb = false;
setLocalThemeConfig();
mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
};
// 4、界面显示 --> 侧边栏 Logo
const onIsShowLogoChange = () => {
themeConfig.value.isShowLogoChange = themeConfig.value.isShowLogo ? false : true;
setLocalThemeConfig();
};
// 4、界面显示 --> 面包屑 Breadcrumb
const onIsBreadcrumbChange = () => {
if (themeConfig.value.layout === 'classic') {
themeConfig.value.isClassicSplitMenu = false;
}
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 拖拽
const onSortableTagsViewChange = () => {
mittBus.emit('openOrCloseSortable');
setLocalThemeConfig();
};
// 4、界面显示 --> 暗模式/灰色模式/色弱模式
const onAddFilterChange = (attr: string) => {
@@ -545,7 +568,7 @@ const onAddFilterChange = (attr: string) => {
const cssAttr = attr === 'grayscale' ? `grayscale(${themeConfig.value.isGrayscale ? 1 : 0})` : `invert(${themeConfig.value.isInvert ? '80%' : '0%'})`;
const appEle: any = document.querySelector('#app');
appEle.setAttribute('style', `filter: ${cssAttr}`);
setLocalThemeConfig();
setLocal('appFilterStyle', appEle.style.cssText);
};
// 5、布局切换
@@ -603,7 +626,6 @@ const onDrawerClose = () => {
themeConfig.value.isFixedHeaderChange = false;
themeConfig.value.isShowLogoChange = false;
themeConfig.value.isDrawer = false;
setLocalThemeConfig();
};
// 布局配置弹窗打开
const openDrawer = () => {
@@ -613,16 +635,12 @@ const openDrawer = () => {
onCopyConfigClick(copyConfigBtnRef.value?.$el);
});
};
// 触发 store 布局配置更新
const setDispatchThemeConfig = () => {
setLocalThemeConfig();
setLocalThemeConfigStyle();
};
// 存储布局配置
const setLocalThemeConfig = () => {
removeLocal('themeConfig');
setLocal('themeConfig', themeConfig.value);
};
// 存储布局配置全局主题样式html根标签
const setLocalThemeConfigStyle = () => {
setLocal('themeConfigStyle', document.documentElement.style.cssText);

View File

@@ -23,24 +23,32 @@
<el-dropdown-item command="small" :disabled="state.disabledSize === 'small'">小型</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown> -->
</el-dropdown> -->
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
<div class="layout-navbars-breadcrumb-user-icon">
<SvgIcon :size="16" :name="EnumValue.getEnumByValue(I18nEnum, themeConfig.globalI18n)?.extra.icon" :title="$t('layout.user.langSwitch')" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in I18nEnum" :key="item.value" :command="item.value" :disabled="themeConfig.globalI18n === item.value">
{{ item.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
<el-icon title="菜单搜索">
<search />
</el-icon>
<SvgIcon name="search" :title="$t('layout.user.menuSearch')" />
</div>
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
<el-icon title="布局设置">
<setting />
</el-icon>
<SvgIcon name="setting" :title="$t('layout.user.layoutConf')" />
</div>
<div class="layout-navbars-breadcrumb-user-icon">
<el-popover placement="bottom" trigger="click" :visible="state.isShowUserNewsPopover" :width="300" popper-class="el-popover-pupop-user-news">
<template #reference>
<el-badge :is-dot="false" @click="state.isShowUserNewsPopover = !state.isShowUserNewsPopover">
<el-icon title="消息">
<bell />
</el-icon>
<SvgIcon name="bell" :title="$t('layout.user.news')" />
</el-badge>
</template>
<transition name="el-zoom-in-top">
@@ -49,12 +57,8 @@
</el-popover>
</div>
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<el-icon v-if="!state.isScreenfull" title="关全屏">
<full-screen />
</el-icon>
<el-icon v-else title="开全屏">
<crop />
</el-icon>
<SvgIcon v-if="!state.isScreenfull" name="full-screen" :title="$t('layout.user.fullScreenOff')" />
<SvgIcon v-else name="crop" />
</div>
<el-dropdown trigger="click" :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<span class="layout-navbars-breadcrumb-user-link" style="cursor: pointer">
@@ -64,9 +68,9 @@
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="/home">首页</el-dropdown-item>
<el-dropdown-item command="/personal">个人中心</el-dropdown-item>
<el-dropdown-item divided command="logOut">退出登录</el-dropdown-item>
<el-dropdown-item command="/home">{{ $t('layout.user.index') }}</el-dropdown-item>
<el-dropdown-item command="/personal">{{ $t('layout.user.personalCenter') }}</el-dropdown-item>
<el-dropdown-item divided command="logOut">{{ $t('layout.user.logout') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -88,20 +92,23 @@ import UserNews from '@/layout/navBars/breadcrumb/userNews.vue';
import SearchMenu from '@/layout/navBars/breadcrumb/search.vue';
import mittBus from '@/common/utils/mitt';
import openApi from '@/common/openApi';
import { saveThemeConfig, getThemeConfig } from '@/common/utils/storage';
import { getThemeConfig } from '@/common/utils/storage';
import { useDark, usePreferredDark } from '@vueuse/core';
import { useI18n } from 'vue-i18n';
import { I18nEnum } from '@/common/commonEnum';
import EnumValue from '@/common/Enum';
const router = useRouter();
const searchRef = ref();
const state = reactive({
isScreenfull: false,
isShowUserNewsPopover: false,
disabledI18n: 'zh-cn',
disabledSize: '',
});
const { userInfo } = storeToRefs(useUserInfo());
const themeConfigStore = useThemeConfig();
const { themeConfig } = storeToRefs(themeConfigStore);
const { t } = useI18n();
// 设置分割样式
const layoutUserFlexNum = computed(() => {
@@ -111,6 +118,16 @@ const layoutUserFlexNum = computed(() => {
else num = '';
return num;
});
// 页面加载时
onMounted(() => {
const themeConfig = getThemeConfig();
if (themeConfig) {
initComponentSize();
isDark.value = themeConfig.isDark;
}
});
// 全屏点击时
const onScreenfullClick = () => {
if (!screenfull.isEnabled) {
@@ -130,16 +147,16 @@ const onHandleCommandClick = (path: string) => {
ElMessageBox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: '提示',
message: '此操作将退出登录, 是否继续?',
title: t('layout.user.logOutTitle'),
message: t('layout.user.logOutMessage'),
showCancelButton: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
beforeClose: async (action, instance, done) => {
if (action === 'confirm') {
await openApi.logout();
instance.confirmButtonLoading = true;
instance.confirmButtonText = '退出中';
instance.confirmButtonText = t('layout.user.logOutExit');
setTimeout(() => {
done();
setTimeout(() => {
@@ -156,7 +173,7 @@ const onHandleCommandClick = (path: string) => {
resetRoute(); // 删除/重置路由
router.push('/login');
setTimeout(() => {
ElMessage.success('安全退出成功!');
ElMessage.success(t('layout.user.logoutSuccess'));
}, 300);
})
.catch(() => {});
@@ -175,7 +192,6 @@ watch(preDark, (newValue) => {
const switchDark = () => {
themeConfigStore.switchDark(isDark.value);
saveThemeConfig(themeConfig.value);
};
// // 菜单搜索点击
@@ -211,14 +227,10 @@ const initComponentSize = () => {
}
};
// 页面加载时
onMounted(() => {
const themeConfig = getThemeConfig();
if (themeConfig) {
initComponentSize();
isDark.value = themeConfig.isDark;
}
});
// 语言切换
const onLanguageChange = (lang: string) => {
themeConfig.value.globalI18n = lang;
};
</script>
<style scoped lang="scss">

View File

@@ -1,8 +1,8 @@
<template>
<div class="layout-navbars-breadcrumb-user-news">
<div class="head-box">
<div class="head-box-title">通知</div>
<div class="head-box-btn" v-if="newsList.length > 0" @click="onAllReadClick">全部已读</div>
<div class="head-box-title">{{ $t('layout.user.newTitle') }}</div>
<div class="head-box-btn" v-if="newsList.length > 0" @click="onAllReadClick">{{ $t('layout.user.newBtn') }}</div>
</div>
<div class="content-box">
<template v-if="newsList.length > 0">
@@ -14,9 +14,9 @@
<div class="content-box-time">{{ v.time }}</div>
</div>
</template>
<el-empty description="暂无通知" v-else></el-empty>
<el-empty :description="$t('layout.user.newDesc')" v-else></el-empty>
</div>
<div class="foot-box" @click="toMsgCenter" v-if="newsList.length > 0">前往通知中心</div>
<div class="foot-box" @click="toMsgCenter" v-if="newsList.length > 0">{{ $t('layout.user.newGo') }}</div>
</div>
</template>
@@ -59,33 +59,41 @@ export default {
justify-content: space-between;
height: 35px;
align-items: center;
.head-box-btn {
color: var(--el-color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
.content-box {
font-size: 13px;
.content-box-item {
padding-top: 12px;
&:last-of-type {
padding-bottom: 12px;
}
.content-box-msg {
color: #999999;
margin-top: 5px;
margin-bottom: 5px;
}
.content-box-time {
color: #999999;
}
}
}
.foot-box {
height: 35px;
color: var(--el-color-primary);
@@ -96,10 +104,12 @@ export default {
align-items: center;
justify-content: center;
border-top: 1px solid #ebeef5;
&:hover {
opacity: 1;
}
}
::v-deep(.el-empty__description p) {
font-size: 13px;
}

View File

@@ -18,7 +18,7 @@
>
<SvgIcon name="iconfont icon-tag-view-active" class="layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)" />
<SvgIcon :name="v.icon" class="layout-navbars-tagsview-ul-li-iconfont" v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
<span>{{ v.title }}</span>
<span>{{ $t(v.title) }}</span>
<template v-if="isActive(v)">
<SvgIcon
name="RefreshRight"
@@ -74,7 +74,7 @@ const route = useRoute();
const router = useRouter();
const contextmenuItems = [
new ContextmenuItem(0, '刷新').withIcon('RefreshRight').withOnClick((data: any) => {
new ContextmenuItem(0, 'layout.tagsView.refresh').withIcon('RefreshRight').withOnClick((data: any) => {
// path为fullPath
let { path } = data;
let currentTag = tagsViews.value.find((v: any) => v.path === path);
@@ -82,18 +82,18 @@ const contextmenuItems = [
router.push({ path, query: currentTag?.query });
}),
new ContextmenuItem(1, '关闭').withIcon('Close').withOnClick((data: any) => closeCurrentTagsView(data.path)),
new ContextmenuItem(1, 'layout.tagsView.close').withIcon('Close').withOnClick((data: any) => closeCurrentTagsView(data.path)),
new ContextmenuItem(2, '关闭其他').withIcon('CircleClose').withOnClick((data: any) => {
new ContextmenuItem(2, 'layout.tagsView.closeOther').withIcon('CircleClose').withOnClick((data: any) => {
let { path } = data;
let currentTag = tagsViews.value.find((v: any) => v.path === path);
router.push({ path, query: currentTag?.query });
closeOtherTagsView(path);
}),
new ContextmenuItem(3, '关闭所有').withIcon('FolderDelete').withOnClick((data: any) => closeAllTagsView(data.path)),
new ContextmenuItem(3, 'layout.tagsView.closeAll').withIcon('FolderDelete').withOnClick((data: any) => closeAllTagsView(data.path)),
new ContextmenuItem(4, '当前页全屏').withIcon('full-screen').withOnClick((data: any) => openCurrenFullscreen(data.path)),
new ContextmenuItem(4, 'layout.tagsView.fullscreen').withIcon('full-screen').withOnClick((data: any) => openCurrenFullscreen(data.path)),
];
const state = reactive({

View File

@@ -6,19 +6,19 @@
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="val.meta.icon" />
<span>{{ val.meta.title }}</span>
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-sub-menu>
<el-menu-item :index="val.path" :key="val?.path" v-else>
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
<SvgIcon :name="val.meta.icon" />
{{ val.meta.title }}
{{ $t(val.meta.title) }}
</template>
<template #title v-else>
<a :href="val.meta.link" target="_blank">
<SvgIcon :name="val.meta.icon" />
{{ val.meta.title }}
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>
@@ -57,8 +57,7 @@ const menuLists = computed(() => {
// 设置横向滚动条可以鼠标滚轮滚动
const onElMenuHorizontalScroll = (e: any) => {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft =
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft + eventDelta / 4;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft = proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft + eventDelta / 4;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
const initElMenuOffsetLeft = () => {

View File

@@ -3,19 +3,19 @@
<el-sub-menu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
<template #title>
<SvgIcon :name="val.meta.icon" />
<span>{{ val.meta.title }}</span>
<span>{{ $t(val.meta.title) }}</span>
</template>
<sub-item :chil="val.children" />
</el-sub-menu>
<el-menu-item :index="val.path" :key="val?.path" v-else>
<template v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
<SvgIcon :name="val.meta.icon" />
<span>{{ val.meta.title }}</span>
<span>{{ $t(val.meta.title) }}</span>
</template>
<template v-else>
<a :href="val.meta.link" target="_blank">
<SvgIcon :name="val.meta.icon" />
{{ val.meta.title }}
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>

View File

@@ -11,17 +11,17 @@
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="val.meta.icon" />
<span>{{ val.meta.title }}</span>
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-sub-menu>
<el-menu-item :index="val.path" :key="val?.path" v-else>
<SvgIcon :name="val.meta.icon" />
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
<span>{{ val.meta.title }}</span>
<span>{{ $t(val.meta.title) }}</span>
</template>
<template #title v-else>
<a :href="val.meta.link" target="_blank">{{ val.meta.title }}</a></template
<a :href="val.meta.link" target="_blank">{{ $t(val.meta.title) }}</a></template
>
</el-menu-item>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div class="layout-view-bg-white flex layout-view-link">
<a :href="currentRouteMeta.link" target="_blank" class="flex-margin">{{ currentRouteMeta.title }}{{ currentRouteMeta.link }}</a>
<a :href="currentRouteMeta.link" target="_blank" class="flex-margin"> {{ $t(currentRouteMeta.title) }}{{ currentRouteMeta.link }} </a>
</div>
</div>
</template>

View File

@@ -4,27 +4,27 @@ import App from '@/App.vue';
import router from './router';
import pinia from '@/store/index';
import { directive } from '@/directive/index';
import { globalComponentSize } from '@/common/utils/componentSize';
import { registElSvgIcon } from '@/common/utils/svgIcons';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import 'element-plus/theme-chalk/dark/css-vars.css';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import { ElMessage } from 'element-plus';
import { i18n } from '@/i18n/index';
import 'splitpanes/dist/splitpanes.css';
import '@/theme/index.scss';
import '@/assets/font/font.css';
import '@/assets/iconfont/iconfont.js';
import { getThemeConfig } from './common/utils/storage';
const app = createApp(App);
registElSvgIcon(app);
directive(app);
app.use(pinia).use(router).use(ElementPlus, { size: globalComponentSize, locale: zhCn }).mount('#app');
app.use(pinia).use(router).use(i18n).use(ElementPlus, { size: getThemeConfig()?.globalComponentSize }).mount('#app');
// 屏蔽警告信息
app.config.warnHandler = () => null;

View File

@@ -25,7 +25,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录',
title: 'staticRoutes.signIn',
},
},
{
@@ -40,7 +40,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
name: 'oauth2Callback',
component: () => import('@/views/oauth/Oauth2Callback.vue'),
meta: {
title: 'oauth2回调',
title: 'oauth2 callback',
},
},
{
@@ -49,7 +49,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
component: () => import('@/views/ops/machine/SshTerminalPage.vue'),
meta: {
// 将路径 'xxx?name=名字' 里的name字段值替换到title里
title: '终端 | {name}',
title: 'terminal | {name}',
// 是否根据query对标题名进行参数替换即最终显示为终端_机器名
titleRename: true,
},
@@ -60,7 +60,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
component: () => import('@/views/ops/machine/RdpTerminalPage.vue'),
meta: {
// 将路径 'xxx?name=名字' 里的name字段值替换到title里
title: '终端 | {name}',
title: 'terminal | {name}',
// 是否根据query对标题名进行参数替换即最终显示为终端_机器名
titleRename: true,
},
@@ -74,7 +74,7 @@ export const errorRoutes: Array<RouteRecordRaw> = [
name: 'notFound',
component: () => import('@/views/error/404.vue'),
meta: {
title: '找不到此页面',
title: 'staticRoutes.notFound',
},
},
{
@@ -82,7 +82,7 @@ export const errorRoutes: Array<RouteRecordRaw> = [
name: 'noPower',
component: () => import('@/views/error/401.vue'),
meta: {
title: '没有权限',
title: 'staticRoutes.noPower',
},
},
// Resolve refresh page, route warnings

View File

@@ -1,7 +1,7 @@
import { defineStore } from 'pinia';
import { formatDate } from '@/common/utils/format';
import { useUserInfo } from '@/store/userInfo';
import { getSysStyleConfig } from '@/common/sysconfig';
import { getServerConf, getSysStyleConfig } from '@/common/sysconfig';
import { getLocal, getThemeConfig } from '@/common/utils/storage';
// 系统默认logo图标对应于@/assets/image/logo.svg
@@ -154,9 +154,14 @@ export const useThemeConfig = defineStore('themeConfig', {
initThemeConfig() {
// 获取缓存中的布局配置
const tc = getThemeConfig();
if (tc) {
this.themeConfig = tc;
document.documentElement.style.cssText = getLocal('themeConfigStyle');
} else {
getServerConf().then((res) => {
this.themeConfig.globalI18n = res.i18n;
})
}
// 根据后台系统配置初始化

View File

@@ -4,10 +4,12 @@
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">401</div>
<div class="left-item-animation left-item-title">您未被授权或登录超时没有操作权限</div>
<div class="left-item-animation left-item-title">{{ $t('layout.noAccess.title') }}</div>
<div class="left-item-animation left-item-msg"></div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onSetAuth">重新登录</el-button>
<el-button type="primary" round @click="onSetAuth">
{{ $t('layout.noAccess.loginAgain') }}
</el-button>
</div>
</div>
</div>
@@ -46,16 +48,19 @@ export default {
height: 100%;
background-color: var(--bg-main-color);
display: flex;
.error-flex {
margin: auto;
display: flex;
height: 350px;
width: 900px;
.left {
flex: 1;
height: 100%;
align-items: center;
display: flex;
.left-item {
.left-item-animation {
opacity: 0;
@@ -63,33 +68,39 @@ export default {
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
.left-item-num {
color: #d6e0f6;
font-size: 55px;
}
.left-item-title {
font-size: 20px;
// color: #333333;
margin: 15px 0 5px 0;
animation-delay: 0.1s;
}
.left-item-msg {
color: #c0bebe;
font-size: 12px;
margin-bottom: 30px;
animation-delay: 0.2s;
}
.left-item-btn {
animation-delay: 0.2s;
}
}
}
.right {
flex: 1;
opacity: 0;
animation-name: error-img;
animation-duration: 2s;
animation-fill-mode: forwards;
img {
width: 100%;
height: 100%;

View File

@@ -4,10 +4,12 @@
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">404</div>
<div class="left-item-animation left-item-title">地址输入有误请重新输入地址~</div>
<div class="left-item-animation left-item-msg">您可以先检查网址然后重新输入</div>
<div class="left-item-animation left-item-title">{{ $t('layout.notFound.title') }}</div>
<div class="left-item-animation left-item-msg">{{ $t('layout.notFound.msg') }}</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onGoHome">返回首页</el-button>
<el-button type="primary" round @click="onGoHome">
{{ $t('layout.notFound.backHomepage') }}
</el-button>
</div>
</div>
</div>
@@ -39,16 +41,19 @@ export default {
height: 100%;
background-color: var(--bg-main-color);
display: flex;
.error-flex {
margin: auto;
display: flex;
height: 350px;
width: 900px;
.left {
flex: 1;
height: 100%;
align-items: center;
display: flex;
.left-item {
.left-item-animation {
opacity: 0;
@@ -56,33 +61,39 @@ export default {
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
.left-item-num {
color: #d6e0f6;
font-size: 55px;
}
.left-item-title {
font-size: 20px;
// color: #333333;
margin: 15px 0 5px 0;
animation-delay: 0.1s;
}
.left-item-msg {
color: #c0bebe;
font-size: 12px;
margin-bottom: 30px;
animation-delay: 0.2s;
}
.left-item-btn {
animation-delay: 0.2s;
}
}
}
.right {
flex: 1;
opacity: 0;
animation-name: error-img;
animation-duration: 2s;
animation-fill-mode: forwards;
img {
width: 100%;
height: 100%;

View File

@@ -6,15 +6,15 @@
</template>
<el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
<el-form-item prop="bizType" label="业务类型">
<EnumSelect v-model="form.bizType" :enums="FlowBizType" placeholder="请选择业务类型" />
<el-form-item prop="bizType" :label="$t('flow.bizType')">
<EnumSelect v-model="form.bizType" :enums="FlowBizType" />
</el-form-item>
<el-form-item prop="remark" label="备注">
<el-input v-model.trim="form.remark" type="textarea" placeholder="备注" auto-complete="off" clearable></el-input>
<el-form-item prop="remark" :label="$t('common.remark')">
<el-input v-model.trim="form.remark" type="textarea" auto-complete="off" clearable></el-input>
</el-form-item>
<el-divider content-position="left">业务信息</el-divider>
<el-divider content-position="left">{{ $t('flow.bizInfo') }}</el-divider>
<component
ref="bizFormRef"
v-if="form.bizType"
@@ -26,17 +26,18 @@
</el-form>
<span v-if="flowProcdef || !state.form.procdefId">
<el-divider content-position="left">审批节点</el-divider>
<el-divider content-position="left">{{ $t('flow.approvalNode') }}</el-divider>
<ProcdefTasks v-if="flowProcdef" :procdef="flowProcdef" />
<el-result v-if="!state.form.procdefId" icon="error" title="不存在审批节点" sub-title="该资源无需审批操作"> </el-result>
<el-result v-if="!state.form.procdefId" icon="error" :title="$t('flow.approvalNodeNotExist')" :sub-title="$t('flow.resourceNotExistFlow')">
</el-result>
</span>
<template #footer>
<div>
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk" :disabled="!state.form.procdefId"> </el-button>
<el-button @click="cancel()">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk" :disabled="!state.form.procdefId">{{ $t('common.confirm') }}</el-button>
</div>
</template>
</el-drawer>
@@ -52,9 +53,13 @@ import { FlowBizType } from './enums';
import EnumSelect from '@/components/enumselect/EnumSelect.vue';
import ProcdefTasks from './components/ProcdefTasks.vue';
import RedisRunCmdFlowBizForm from './flowbiz/redis/RedisRunCmdFlowBizForm.vue';
import { useI18nPleaseInput, useI18nPleaseSelect } from '@/hooks/useI18n';
import { useI18n } from 'vue-i18n';
const DbSqlExecFlowBizForm = defineAsyncComponent(() => import('./flowbiz/dbms/DbSqlExecFlowBizForm.vue'));
const { t } = useI18n();
const props = defineProps({
title: {
type: String,
@@ -79,14 +84,14 @@ const rules = {
bizType: [
{
required: true,
message: '请选择流程业务类型',
message: useI18nPleaseSelect('flow.bizType'),
trigger: ['change', 'blur'],
},
],
remark: [
{
required: true,
message: '请输入申请备注',
message: useI18nPleaseInput('common.remark'),
trigger: ['change', 'blur'],
},
],
@@ -125,12 +130,12 @@ const btnOk = async () => {
await formRef.value.validate();
await bizFormRef.value.validateBizForm();
} catch (e: any) {
ElMessage.error('请正确填写信息');
ElMessage.error(t('flow.procinstFormError'));
return false;
}
await procinstStart();
ElMessage.success('流程发起成功');
ElMessage.success(t('flow.procinstStartSuccess'));
emit('val-change', state.form);
//重置表单域
cancel();

View File

@@ -6,24 +6,20 @@
</template>
<el-form :model="form" ref="formRef" :rules="rules" label-width="auto">
<el-form-item prop="name" label="名称">
<el-input v-model.trim="form.name" placeholder="请输入流程名称" auto-complete="off" clearable></el-input>
<el-form-item prop="name" :label="$t('common.name')">
<el-input v-model.trim="form.name" auto-complete="off" clearable></el-input>
</el-form-item>
<el-form-item prop="defKey" label="key">
<el-input :disabled="form.id" v-model.trim="form.defKey" placeholder="请输入流程key" auto-complete="off" clearable></el-input>
<el-form-item prop="defKey" label="Key">
<el-input :disabled="form.id" v-model.trim="form.defKey" auto-complete="off" clearable></el-input>
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option v-for="item in ProcdefStatus" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
<el-form-item prop="status" :label="$t('common.status')">
<EnumSelect :enums="ProcdefStatus" v-model="form.status" />
</el-form-item>
<el-form-item prop="condition" label="触发条件">
<el-form-item prop="condition" :label="$t('flow.triggeringCondition')">
<template #label>
触发条件
<el-tooltip content="go template语法。若输出结果为1则表示触发该审批流程" placement="top">
<el-icon>
<question-filled />
</el-icon>
{{ $t('flow.triggeringCondition') }}
<el-tooltip :content="$t('flow.triggeringConditionTips')" placement="top">
<SvgIcon name="question-filled" />
</el-tooltip>
</template>
@@ -31,42 +27,46 @@
v-model="form.condition"
:rows="10"
type="textarea"
placeholder="触发条件, 返回值=1, 则表示触发该审批流程"
:placeholder="$t('flow.conditionPlaceholder')"
auto-complete="off"
clearable
></el-input>
</el-form-item>
<el-form-item prop="remark" label="备注">
<el-input v-model.trim="form.remark" placeholder="备注" auto-complete="off" clearable></el-input>
<el-form-item prop="remark" :label="$t('common.remark')">
<el-input v-model.trim="form.remark" auto-complete="off" clearable></el-input>
</el-form-item>
<el-form-item ref="tagSelectRef" prop="codePaths" label="关联资源">
<el-form-item ref="tagSelectRef" prop="codePaths" :label="$t('tag.relateTag')">
<tag-tree-check height="300px" v-model="form.codePaths" :tag-type="[TagResourceTypeEnum.DbName.value, TagResourceTypeEnum.Redis.value]" />
</el-form-item>
<el-divider content-position="left">审批节点</el-divider>
<el-divider content-position="left">{{ $t('flow.approvalNode') }}</el-divider>
<el-table ref="taskTableRef" :data="tasks" row-key="taskKey" stripe style="width: 100%">
<el-table-column prop="name" label="名称" min-width="100px">
<el-table-column prop="name" min-width="100px">
<template #header>
<el-button class="ml0" type="primary" circle size="small" icon="Plus" @click="addTask()"> </el-button>
<span class="ml10">节点名称</span>
<el-tooltip content="点击指定节点可进行拖拽排序" placement="top">
<el-icon class="ml5">
<question-filled />
</el-icon>
<span class="ml10">{{ $t('flow.nodeName') }}<span class="ml5" style="color: red">*</span></span>
<el-tooltip :content="$t('flow.nodeNameTips')" placement="top">
<SvgIcon class="ml5" name="question-filled" />
</el-tooltip>
</template>
<template #default="scope">
<el-input v-model="scope.row.name"> </el-input>
</template>
</el-table-column>
<el-table-column prop="userId" label="审核人员" min-width="150px" show-overflow-tooltip>
<el-table-column prop="userId" min-width="150px" show-overflow-tooltip>
<template #header>
<span class="ml10">{{ $t('flow.auditor') }}<span class="ml5" style="color: red">*</span></span>
</template>
<template #default="scope">
<AccountSelectFormItem v-model="scope.row.userId" label="" />
</template>
</el-table-column>
<el-table-column label="操作" width="60px">
<el-table-column :label="$t('common.operation')" width="110px">
<template #default="scope">
<el-link @click="deleteTask(scope.$index)" class="ml5" type="danger" icon="delete" plain></el-link>
</template>
@@ -76,8 +76,8 @@
<template #footer>
<div>
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
<el-button @click="cancel()">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">{{ $t('common.confirm') }}</el-button>
</div>
</template>
</el-drawer>
@@ -95,6 +95,11 @@ import { randomUuid } from '../../common/utils/string';
import { ProcdefStatus } from './enums';
import TagTreeCheck from '../ops/component/TagTreeCheck.vue';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import EnumSelect from '@/components/enumselect/EnumSelect.vue';
import { useI18nFormValidate, useI18nPleaseInput, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
data: {
@@ -117,14 +122,14 @@ const rules = {
name: [
{
required: true,
message: '请输入流程名称',
message: useI18nPleaseInput('common.name'),
trigger: ['change', 'blur'],
},
],
defKey: [
{
required: true,
message: '请输入流程key',
message: useI18nPleaseInput('Key'),
trigger: ['change', 'blur'],
},
],
@@ -161,23 +166,7 @@ watch(props, (newValue: any) => {
state.tasks = tasks;
} else {
state.form = { status: ProcdefStatus.Enable.value } as any;
state.form.condition = `{{/* DBMS-执行sql规则; param参数描述如下 */}}
{{/* stmtType: select / read / insert / update / delete / ddl ; */}}
{{ if eq .bizType "db_sql_exec_flow"}}
{{/* 不是select和read语句时开启流程审批 */}}
{{ if and (ne .param.stmtType "select") (ne .param.stmtType "read") }}
1
{{ end }}
{{ end }}
{{/* Redis-执行命令规则; param参数描述如下 */}}
{{/* cmdType: read(读命令) / write(写命令); */}}
{{/* cmd: get/set/hset...等 */}}
{{ if eq .bizType "redis_run_cmd_flow"}}
{{ if eq .param.cmdType "write" }}
1
{{ end }}
{{ end }}`;
state.form.condition = t('flow.conditionDefault');
state.tasks = [];
}
});
@@ -205,13 +194,7 @@ const deleteTask = (idx: any) => {
};
const btnOk = async () => {
try {
await formRef.value.validate();
} catch (e: any) {
ElMessage.error('请正确填写信息');
return false;
}
await useI18nFormValidate(formRef);
const checkRes = checkTasks();
if (checkRes.err) {
ElMessage.error(checkRes.err);
@@ -220,7 +203,7 @@ const btnOk = async () => {
state.form.tasks = JSON.stringify(checkRes.tasks);
await saveFlowDefExec();
ElMessage.success('操作成功');
useI18nSaveSuccessMsg();
emit('val-change', state.form);
//重置表单域
formRef.value.resetFields();
@@ -229,14 +212,14 @@ const btnOk = async () => {
const checkTasks = () => {
if (state.tasks?.length == 0) {
return { err: '请完善审批节点任务' };
return { err: t('flow.tasksNotEmpty') };
}
const tasks = [];
for (let i = 0; i < state.tasks.length; i++) {
const task = { ...state.tasks[i] };
if (!task.name || !task.userId) {
return { err: `请完善第${i + 1}个审批节点任务信息` };
return { err: t('flow.tasksNoComplete', { index: i + 1 }) };
}
// 转为字符串(方便后续万一需要调整啥的)
task.userId = `${task.userId}`;

View File

@@ -10,8 +10,10 @@
:columns="columns"
>
<template #tableHeader>
<el-button v-auth="perms.save" type="primary" icon="plus" @click="editFlowDef(false)">添加</el-button>
<el-button v-auth="perms.del" :disabled="state.selectionData.length < 1" @click="deleteProcdef()" type="danger" icon="delete">删除</el-button>
<el-button v-auth="perms.save" type="primary" icon="plus" @click="editFlowDef(false)">{{ $t('common.create') }}</el-button>
<el-button v-auth="perms.del" :disabled="state.selectionData.length < 1" @click="deleteProcdef()" type="danger" icon="delete">
{{ $t('common.delete') }}
</el-button>
</template>
<template #tasks="{ data }">
@@ -23,7 +25,7 @@
</template>
<template #action="{ data }">
<el-button link v-if="actionBtns[perms.save]" @click="editFlowDef(data)" type="primary">编辑</el-button>
<el-button link v-if="actionBtns[perms.save]" @click="editFlowDef(data)" type="primary">{{ $t('common.edit') }}</el-button>
</template>
</page-table>
@@ -38,7 +40,6 @@
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import { procdefApi } from './api';
import { ElMessage, ElMessageBox } from 'element-plus';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
@@ -47,27 +48,31 @@ import ProcdefEdit from './ProcdefEdit.vue';
import ProcdefTasks from './components/ProcdefTasks.vue';
import { ProcdefStatus } from './enums';
import TagCodePath from '../ops/component/TagCodePath.vue';
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle } from '@/hooks/useI18n';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const perms = {
save: 'flow:procdef:save',
del: 'flow:procdef:del',
};
const searchItems = [SearchItem.input('name', '名称'), SearchItem.input('defKey', 'key')];
const searchItems = [SearchItem.input('name', 'common.name'), SearchItem.input('defKey', 'key')];
const columns = [
TableColumn.new('name', '名称'),
TableColumn.new('defKey', 'key'),
TableColumn.new('status', '状态').typeTag(ProcdefStatus),
TableColumn.new('remark', '备注'),
TableColumn.new('tasks', '审批节点').isSlot().alignCenter().setMinWidth(60),
TableColumn.new('codePaths', '关联资源').isSlot().setMinWidth('250px'),
TableColumn.new('creator', '创建账号'),
TableColumn.new('createTime', '创建时间').isTime(),
TableColumn.new('name', 'common.name'),
TableColumn.new('defKey', 'Key'),
TableColumn.new('status', 'common.status').typeTag(ProcdefStatus),
TableColumn.new('remark', 'common.remark'),
TableColumn.new('tasks', 'flow.approvalNode').isSlot().alignCenter().setMinWidth(60),
TableColumn.new('codePaths', 'tag.relateTag').isSlot().setMinWidth('250px'),
TableColumn.new('creator', 'common.creator'),
TableColumn.new('createTime', 'common.createTime').isTime(),
];
// 该用户拥有的的操作列按钮权限
const actionBtns: any = hasPerms([perms.save, perms.del]);
const actionColumn = TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter();
const actionColumn = TableColumn.new('action', 'common.operation').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter();
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
@@ -84,7 +89,7 @@ const state = reactive({
pageSize: 0,
},
flowDefEditor: {
title: '新建流程定义',
title: '',
visible: false,
data: null as any,
},
@@ -109,17 +114,17 @@ const search = async () => {
const showProcdefTasks = (procdef: any) => {
state.flowTasksDialog.tasks = procdef.tasks;
state.flowTasksDialog.title = procdef.name + '-审批节点';
state.flowTasksDialog.title = procdef.name + ' - ' + t('flow.approvalNode');
state.flowTasksDialog.visible = true;
};
const editFlowDef = (data: any) => {
if (!data) {
state.flowDefEditor.data = null;
state.flowDefEditor.title = '新建流程定义';
state.flowDefEditor.title = useI18nCreateTitle('flow.procdef');
} else {
state.flowDefEditor.data = data;
state.flowDefEditor.title = '编辑流程定义';
state.flowDefEditor.title = useI18nEditTitle('flow.procdef');
}
state.flowDefEditor.visible = true;
};
@@ -131,13 +136,9 @@ const valChange = () => {
const deleteProcdef = async () => {
try {
await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】的流程定义?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await useI18nDeleteConfirm(state.selectionData.map((x: any) => x.name).join(', '));
await procdefApi.del.request({ id: state.selectionData.map((x: any) => x.id).join(',') });
ElMessage.success('删除成功');
useI18nDeleteSuccessMsg();
search();
} catch (err) {
//

View File

@@ -8,29 +8,28 @@
<div>
<el-divider content-position="left">流程信息</el-divider>
<el-descriptions :column="3" border>
<el-descriptions-item label="流程名">{{ procinst.procdefName }}</el-descriptions-item>
<el-descriptions-item label="业务">
<el-descriptions-item :span="1" label="流程名">{{ procinst.procdefName }}</el-descriptions-item>
<el-descriptions-item :span="1" label="业务">
<enum-tag :enums="FlowBizType" :value="procinst.bizType"></enum-tag>
</el-descriptions-item>
<el-descriptions-item label="发起人">
<el-descriptions-item :span="1" label="发起人">
<AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
</el-descriptions-item>
<el-descriptions-item label="流程状态">
<el-descriptions-item :span="1" label="流程状态">
<enum-tag :enums="ProcinstStatus" :value="procinst.status"></enum-tag>
</el-descriptions-item>
<el-descriptions-item label="业务状态">
<el-descriptions-item :span="1" label="业务状态">
<enum-tag :enums="ProcinstBizStatus" :value="procinst.bizStatus"></enum-tag>
</el-descriptions-item>
<el-descriptions-item label="发起时间">{{ formatDate(procinst.createTime) }}</el-descriptions-item>
<el-descriptions-item :span="1" label="发起时间">{{ formatDate(procinst.createTime) }}</el-descriptions-item>
<div v-if="procinst.duration">
<el-descriptions-item label="结束时间">{{ formatDate(procinst.endTime) }}</el-descriptions-item>
<el-descriptions-item label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
<el-descriptions-item :span="1.5" label="结束时间">{{ formatDate(procinst.endTime) }}</el-descriptions-item>
<el-descriptions-item :span="1.5" label="持续时间">{{ formatTime(procinst.duration) }}</el-descriptions-item>
</div>
<el-descriptions-item label="备注">
<el-descriptions-item :span="3" label="备注">
{{ procinst.remark }}
</el-descriptions-item>
</el-descriptions>

View File

@@ -9,20 +9,20 @@
:columns="columns"
>
<template #tableHeader>
<el-button type="primary" icon="plus" @click="startProcInst()">发起流程</el-button>
<el-button type="primary" icon="plus" @click="startProcInst()">{{ $t('flow.startProcess') }}</el-button>
</template>
<template #action="{ data }">
<el-button link @click="showProcinst(data)" type="primary">查看</el-button>
<el-button link @click="showProcinst(data)" type="primary">{{ $t('common.detail') }}</el-button>
<el-popconfirm
v-if="data.status == ProcinstStatus.Active.value || data.status == ProcinstStatus.Suspended.value"
title="确认取消该流程?"
:title="$t('flow.cancelProcessConfirm')"
width="160"
@confirm="procinstCancel(data)"
>
<template #reference>
<el-button link type="warning">取消</el-button>
<el-button link type="warning">{{ $t('common.cancel') }}</el-button>
</template>
</el-popconfirm>
</template>
@@ -49,35 +49,37 @@ import { TableColumn } from '@/components/pagetable';
import { SearchItem } from '@/components/SearchForm';
import ProcinstDetail from './ProcinstDetail.vue';
import { FlowBizType, ProcinstBizStatus, ProcinstStatus } from './enums';
import { ElMessage } from 'element-plus';
import { formatTime } from '@/common/utils/format';
import ProcInstEdit from './ProcInstEdit.vue';
import { useI18nDetailTitle, useI18nOperateSuccessMsg } from '@/hooks/useI18n';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const searchItems = [
SearchItem.select('status', '流程状态').withEnum(ProcinstStatus),
SearchItem.select('bizType', '业务类型').withEnum(FlowBizType),
SearchItem.input('bizKey', '业务key'),
SearchItem.select('status', 'common.status').withEnum(ProcinstStatus),
SearchItem.select('bizType', 'flow.bizType').withEnum(FlowBizType),
SearchItem.input('bizKey', 'flow.bizKey'),
];
const columns = [
TableColumn.new('bizType', '业务').typeTag(FlowBizType),
TableColumn.new('remark', '备注'),
TableColumn.new('creator', '发起人'),
TableColumn.new('bizKey', '业务key'),
TableColumn.new('procdefName', '流程名'),
TableColumn.new('status', '流程状态').typeTag(ProcinstStatus),
TableColumn.new('bizStatus', '业务状态').typeTag(ProcinstBizStatus),
TableColumn.new('createTime', '发起时间').isTime(),
TableColumn.new('endTime', '结束时间').isTime(),
TableColumn.new('duration', '持续时间').setFormatFunc((data: any, prop: string) => {
TableColumn.new('bizType', 'flow.bizType').typeTag(FlowBizType),
TableColumn.new('remark', 'common.remark'),
TableColumn.new('creator', 'flow.initiator'),
TableColumn.new('bizKey', 'flow.bizKey'),
TableColumn.new('procdefName', 'flow.procdefName'),
TableColumn.new('status', 'common.status').setAddWidth(8).typeTag(ProcinstStatus),
TableColumn.new('bizStatus', 'flow.bizStatus').typeTag(ProcinstBizStatus),
TableColumn.new('createTime', 'flow.startingTime').isTime(),
TableColumn.new('endTime', 'flow.endTime').isTime(),
TableColumn.new('duration', 'flow.duration').setFormatFunc((data: any, prop: string) => {
const duration = data[prop];
if (!duration) {
return '';
}
return formatTime(duration);
}),
// TableColumn.new('bizHandleRes', '业务处理结果'),
TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
TableColumn.new('action', 'common.operation').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
];
const pageTableRef: Ref<any> = ref(null);
@@ -96,13 +98,13 @@ const state = reactive({
pageSize: 0,
},
procinstDetail: {
title: '查看流程',
title: '',
visible: false,
procinstId: 0,
instTaskId: 0,
},
procinstEdit: {
title: '发起流程',
title: '',
visible: false,
},
});
@@ -115,17 +117,18 @@ const search = async () => {
const procinstCancel = async (data: any) => {
await procinstApi.cancel.request({ id: data.id });
ElMessage.success('操作成功');
useI18nOperateSuccessMsg();
search();
};
const showProcinst = (data: any) => {
state.procinstDetail.procinstId = data.id;
state.procinstDetail.title = '流程查看';
state.procinstDetail.title = useI18nDetailTitle('flow.proc');
state.procinstDetail.visible = true;
};
const startProcInst = () => {
state.procinstEdit.title = t('flow.startProcess');
state.procinstEdit.visible = true;
};

View File

@@ -13,8 +13,10 @@
</template>
<template #action="{ data }">
<el-button link @click="showProcinst(data, false)" type="primary">查看</el-button>
<el-button v-if="data.status == ProcinstTaskStatus.Process.value" link @click="showProcinst(data, true)" type="primary">审核</el-button>
<el-button link @click="showProcinst(data, false)" type="primary">{{ $t('common.detail') }}</el-button>
<el-button v-if="data.status == ProcinstTaskStatus.Process.value" link @click="showProcinst(data, true)" type="primary">
{{ $t('flow.audit') }}
</el-button>
</template>
</page-table>
@@ -38,28 +40,35 @@ import { SearchItem } from '@/components/SearchForm';
import ProcinstDetail from './ProcinstDetail.vue';
import { FlowBizType, ProcinstStatus, ProcinstTaskStatus } from './enums';
import { formatTime } from '@/common/utils/format';
import { useI18nDetailTitle } from '@/hooks/useI18n';
import { useI18n } from 'vue-i18n';
const searchItems = [SearchItem.select('status', '任务状态').withEnum(ProcinstTaskStatus), SearchItem.select('bizType', '业务类型').withEnum(FlowBizType)];
const { t } = useI18n();
const searchItems = [
SearchItem.select('status', 'common.status').withEnum(ProcinstTaskStatus),
SearchItem.select('bizType', 'flow.bizType').withEnum(FlowBizType),
];
const columns = [
TableColumn.new('procinst.bizType', '业务').typeTag(FlowBizType),
TableColumn.new('procinst.remark', '备注'),
TableColumn.new('procinst.creator', '发起人'),
TableColumn.new('procinst.status', '流程状态').typeTag(ProcinstStatus),
TableColumn.new('status', '任务状态').typeTag(ProcinstTaskStatus),
TableColumn.new('procinst.bizKey', '业务key'),
TableColumn.new('procinst.procdefName', '流程名'),
TableColumn.new('taskName', '当前节点'),
TableColumn.new('procinst.createTime', '发起时间').isTime(),
TableColumn.new('createTime', '开始时间').isTime(),
TableColumn.new('endTime', '结束时间').isTime(),
TableColumn.new('duration', '持续时间').setFormatFunc((data: any, prop: string) => {
TableColumn.new('procinst.bizType', 'flow.bizType').typeTag(FlowBizType),
TableColumn.new('procinst.remark', 'common.remark'),
TableColumn.new('procinst.creator', 'flow.initiator'),
TableColumn.new('procinst.status', 'flow.procinstStatus').typeTag(ProcinstStatus),
TableColumn.new('status', 'flow.taskStatus').typeTag(ProcinstTaskStatus),
TableColumn.new('procinst.bizKey', 'flow.bizKey'),
TableColumn.new('procinst.procdefName', 'flow.procdefName'),
TableColumn.new('taskName', 'flow.taskName'),
TableColumn.new('procinst.createTime', 'flow.startingTime').isTime(),
TableColumn.new('createTime', 'flow.taskBeginTime').isTime(),
TableColumn.new('endTime', 'flow.endTime').isTime(),
TableColumn.new('duration', 'flow.duration').setFormatFunc((data: any, prop: string) => {
const duration = data[prop];
if (!duration) {
return '';
}
return formatTime(duration);
}),
TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
TableColumn.new('action', 'common.operation').isSlot().fixedRight().setMinWidth(160).noShowOverflowTooltip().alignCenter(),
];
const pageTableRef: Ref<any> = ref(null);
@@ -78,7 +87,7 @@ const state = reactive({
pageSize: 0,
},
procinstDetail: {
title: '查看流程',
title: '',
visible: false,
procinstId: 0,
instTaskId: 0,
@@ -95,10 +104,10 @@ const showProcinst = (data: any, audit: boolean) => {
state.procinstDetail.procinstId = data.procinstId;
if (!audit) {
state.procinstDetail.instTaskId = 0;
state.procinstDetail.title = '流程查看';
state.procinstDetail.title = useI18nDetailTitle('flow.proc');
} else {
state.procinstDetail.instTaskId = data.id;
state.procinstDetail.title = '流程审批';
state.procinstDetail.title = t('flow.flowAudit');
}
state.procinstDetail.visible = true;
};

View File

@@ -1,34 +1,34 @@
import { EnumValue } from '@/common/Enum';
export const ProcdefStatus = {
Enable: EnumValue.of(1, '启用').setTagType('success'),
Disable: EnumValue.of(-1, '禁用').setTagType('warning'),
Enable: EnumValue.of(1, 'flow.enable').setTagType('success'),
Disable: EnumValue.of(-1, 'flow.disable').setTagType('warning'),
};
export const ProcinstStatus = {
Active: EnumValue.of(1, '执行中').setTagType('primary'),
Completed: EnumValue.of(2, '完成').setTagType('success'),
Suspended: EnumValue.of(-1, '挂起').setTagType('warning'),
Terminated: EnumValue.of(-2, '终止').setTagType('danger'),
Cancelled: EnumValue.of(-3, '取消').setTagType('warning'),
Active: EnumValue.of(1, 'flow.active').setTagType('primary'),
Completed: EnumValue.of(2, 'flow.completed').setTagType('success'),
Suspended: EnumValue.of(-1, 'flow.suspended').setTagType('warning'),
Terminated: EnumValue.of(-2, 'flow.terminated').setTagType('danger'),
Cancelled: EnumValue.of(-3, 'flow.cancelled').setTagType('warning'),
};
export const ProcinstBizStatus = {
Wait: EnumValue.of(1, '待处理').setTagType('primary'),
Success: EnumValue.of(2, '处理成功').setTagType('success'),
Fail: EnumValue.of(-2, '处理失败').setTagType('danger'),
No: EnumValue.of(-1, '不处理').setTagType('warning'),
Wait: EnumValue.of(1, 'flow.waitHandle').setTagType('primary'),
Success: EnumValue.of(2, 'flow.handleSuccess').setTagType('success'),
Fail: EnumValue.of(-2, 'flow.handleFail').setTagType('danger'),
No: EnumValue.of(-1, 'flow.noHandle').setTagType('warning'),
};
export const ProcinstTaskStatus = {
Process: EnumValue.of(1, '待处理').setTagType('primary'),
Pass: EnumValue.of(2, '通过').setTagType('success'),
Reject: EnumValue.of(-1, '拒绝').setTagType('danger'),
Back: EnumValue.of(-2, '驳回').setTagType('warning'),
Canceled: EnumValue.of(-3, '取消').setTagType('warning'),
Process: EnumValue.of(1, 'flow.waitProcess').setTagType('primary'),
Pass: EnumValue.of(2, 'flow.pass').setTagType('success'),
Reject: EnumValue.of(-1, 'flow.reject').setTagType('danger'),
Back: EnumValue.of(-2, 'flow.back').setTagType('warning'),
Canceled: EnumValue.of(-3, 'flow.canceled').setTagType('warning'),
};
export const FlowBizType = {
DbSqlExec: EnumValue.of('db_sql_exec_flow', 'DBMS-执行SQL').setTagType('warning'),
RedisRunWriteCmd: EnumValue.of('redis_run_cmd_flow', 'Redis-执行命令').setTagType('danger'),
DbSqlExec: EnumValue.of('db_sql_exec_flow', 'flow.dbSqlExec').setTagType('warning'),
RedisRunWriteCmd: EnumValue.of('redis_run_cmd_flow', 'flow.redisRunCmd').setTagType('danger'),
};

View File

@@ -1,26 +1,26 @@
<template>
<div>
<el-descriptions :column="3" border>
<el-descriptions-item :span="3" label="标签"><TagCodePath :path="db.codePaths" /></el-descriptions-item>
<el-descriptions-item :span="3" :label="$t('common.tag')"><TagCodePath :path="db.codePaths" /></el-descriptions-item>
<el-descriptions-item :span="1" label="名称">{{ db?.name }}</el-descriptions-item>
<el-descriptions-item :span="1" label="主机">
<el-descriptions-item :span="1" :label="$t('common.name')">{{ db?.name }}</el-descriptions-item>
<el-descriptions-item :span="1" label="Host">
<SvgIcon :name="getDbDialect(db?.type).getInfo().icon" :size="20" />
{{ `${db?.host}:${db?.port}` }}
</el-descriptions-item>
<el-descriptions-item :span="1" label="数据库">{{ dbName }}</el-descriptions-item>
<el-descriptions-item :span="1" :label="$t('tag.db')">{{ dbName }}</el-descriptions-item>
<el-descriptions-item label="执行SQL">
<el-descriptions-item :label="$t('flow.runSql')">
<monaco-editor height="300px" language="sql" v-model="sql" :options="{ readOnly: true }" />
</el-descriptions-item>
</el-descriptions>
<div v-if="runRes && runRes.length > 0">
<el-divider content-position="left">处理结果</el-divider>
<el-divider content-position="left">{{ $t('flow.handleResult') }}</el-divider>
<el-table :data="runRes" :max-height="400">
<el-table-column prop="sql" label="SQL" show-overflow-tooltip />
<el-table-column prop="res" label="执行结果" :min-width="30" show-overflow-tooltip>
<el-table-column prop="res" :label="$t('flow.runResult')" :min-width="30" show-overflow-tooltip>
<template #default="scope">
<el-popover placement="top" :width="400" trigger="hover">
<template #reference>

View File

@@ -1,8 +1,8 @@
<template>
<el-form :model="bizForm" ref="formRef" :rules="rules" label-width="auto">
<el-form-item prop="dbId" label="数据库" required>
<el-form-item prop="dbId" :label="$t('tag.db')" required>
<db-select-tree
placeholder="请选择数据库"
:placeholder="$t('flow.selectDbPlaceholder')"
v-model:db-id="bizForm.dbId"
v-model:db-name="bizForm.dbName"
v-model:db-type="dbType"
@@ -24,19 +24,23 @@ import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
import { registerDbCompletionItemProvider } from '@/views/ops/db/db';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { useI18n } from 'vue-i18n';
import { useI18nPleaseInput } from '@/hooks/useI18n';
const { t } = useI18n();
const rules = {
dbId: [
{
required: true,
message: '请选择数据库',
message: t('flow.selectDbPlaceholder'),
trigger: ['change', 'blur'],
},
],
sql: [
{
required: true,
message: '请输入执行SQL',
message: useI18nPleaseInput('flow.runSql'),
trigger: ['change', 'blur'],
},
],

View File

@@ -1,27 +1,27 @@
<template>
<div>
<el-descriptions :column="3" border>
<el-descriptions-item :span="3" label="标签"><TagCodePath :path="redis.codePaths" /></el-descriptions-item>
<el-descriptions-item :span="3" :label="$t('common.tag')"><TagCodePath :path="redis.codePaths" /></el-descriptions-item>
<el-descriptions-item :span="2" label="编号">{{ redis?.code }}</el-descriptions-item>
<el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
<el-descriptions-item :span="2" :label="$t('common.code')">{{ redis?.code }}</el-descriptions-item>
<el-descriptions-item :span="1" :label="$t('common.name')">{{ redis?.name }}</el-descriptions-item>
<el-descriptions-item :span="1" label="主机">{{ `${redis?.host}` }}</el-descriptions-item>
<el-descriptions-item :span="1" label="">{{ state.db }}</el-descriptions-item>
<el-descriptions-item :span="1" label="Host">{{ `${redis?.host}` }}</el-descriptions-item>
<el-descriptions-item :span="1" label="DB">{{ state.db }}</el-descriptions-item>
<el-descriptions-item :span="1" label="mode">
{{ redis.mode }}
</el-descriptions-item>
<el-descriptions-item :span="3" label="执行Cmd">
<el-descriptions-item :span="3" :label="$t('flow.runCmd')">
<el-input type="textarea" disabled v-model="cmd" rows="5" />
</el-descriptions-item>
</el-descriptions>
<div v-if="runRes && runRes.length > 0">
<el-divider content-position="left">处理结果</el-divider>
<el-divider content-position="left">{{ $t('flow.handleResult') }}</el-divider>
<el-table :data="runRes" :max-height="400">
<el-table-column prop="cmd" label="命令" show-overflow-tooltip />
<el-table-column prop="res" label="执行结果" :min-width="50" show-overflow-tooltip> </el-table-column>
<el-table-column prop="cmd" label="Cmd" show-overflow-tooltip />
<el-table-column prop="res" :label="$t('flow.runResult')" :min-width="50" show-overflow-tooltip> </el-table-column>
</el-table>
</div>
</div>

View File

@@ -1,19 +1,19 @@
<template>
<el-form :model="bizForm" ref="formRef" :rules="rules" label-width="auto">
<el-form-item prop="id" label="" required>
<el-form-item prop="id" label="DB" required>
<TagTreeResourceSelect
v-bind="$attrs"
v-model="selectRedis"
@change="changeRedis"
:resource-type="TagResourceTypeEnum.Redis.value"
:tag-path-node-type="NodeTypeTagPath"
placeholder="请选择Redis实例与库"
:placeholder="$t('flow.selectRedisPlaceholder')"
>
</TagTreeResourceSelect>
</el-form-item>
<el-form-item prop="cmd" label="CMD" required>
<el-input type="textarea" v-model="bizForm.cmd" placeholder="如: SET 'key' 'value'; 多条命令;分割" :rows="5" />
<el-input type="textarea" v-model="bizForm.cmd" :placeholder="$t('flow.cmdPlaceholder')" :rows="5" />
</el-form-item>
</el-form>
</template>
@@ -25,19 +25,23 @@ import TagTreeResourceSelect from '@/views/ops/component/TagTreeResourceSelect.v
import { NodeType, TagTreeNode } from '@/views/ops/component/tag';
import { redisApi } from '@/views/ops/redis/api';
import { sleep } from '@/common/utils/loading';
import { useI18n } from 'vue-i18n';
import { useI18nPleaseInput } from '@/hooks/useI18n';
const { t } = useI18n();
const rules = {
id: [
{
required: true,
message: '请选择Redis实例',
message: t('flow.selectRedisPlaceholder'),
trigger: ['change', 'blur'],
},
],
cmd: [
{
required: true,
message: '请输入执行CMD',
message: useI18nPleaseInput('flow.runCmd'),
trigger: ['change', 'blur'],
},
],

View File

@@ -3,7 +3,7 @@
<el-row :gutter="15">
<!-- 个人信息 -->
<el-col :xs="24" :sm="16">
<el-card shadow="hover" header="个人信息">
<el-card shadow="hover" :header="$t('home.personalInfo')">
<div class="personal-user">
<div class="personal-user-left">
<el-upload
@@ -20,17 +20,17 @@
</div>
<div class="personal-user-right">
<el-row>
<el-col :span="24" class="personal-title mb18"
>{{ currentTime }}{{ userInfo.name }}生活变的再糟糕也不妨碍我变得更好
<el-col :span="24" class="personal-title mb18">
{{ $t('home.welcomeMsg', { name: userInfo.name }) }}
</el-col>
<el-col :span="24">
<el-row>
<el-col :xs="24" :sm="12" class="personal-item mb6">
<div class="personal-item-label">用户名</div>
<div class="personal-item-label">{{ $t('common.username') }}</div>
<div class="personal-item-value">{{ userInfo.username }}</div>
</el-col>
<el-col :xs="24" :sm="12" class="personal-item mb6">
<div class="personal-item-label">角色</div>
<div class="personal-item-label">{{ $t('common.role') }}</div>
<div class="personal-item-value">{{ roleInfo }}</div>
</el-col>
</el-row>
@@ -38,11 +38,11 @@
<el-col :span="24">
<el-row>
<el-col :xs="24" :sm="12" class="personal-item mb6">
<div class="personal-item-label">上次登录IP</div>
<div class="personal-item-label">{{ $t('home.lastLoginIp') }}</div>
<div class="personal-item-value">{{ userInfo.lastLoginIp }}</div>
</el-col>
<el-col :xs="24" :sm="12" class="personal-item mb6">
<div class="personal-item-label">上次登录时间</div>
<div class="personal-item-label">{{ $t('home.lastLoginTime') }}</div>
<div class="personal-item-value">{{ formatDate(userInfo.lastLoginTime) }}</div>
</el-col>
</el-row>
@@ -57,13 +57,13 @@
<el-col :xs="24" :sm="8" class="pl15 personal-info">
<el-card shadow="hover">
<template #header>
<span>消息通知</span>
<span @click="showMsgs" class="personal-info-more">更多</span>
<span>{{ $t('home.msgNotify') }}</span>
<span @click="showMsgs" class="personal-info-more">{{ $t('common.more') }}</span>
</template>
<div class="personal-info-box">
<ul class="personal-info-ul">
<li v-for="(v, k) in state.msgs as any" :key="k" class="personal-info-li">
<a class="personal-info-li-title">{{ `[${getMsgTypeDesc(v.type)}] ${v.msg}` }}</a>
<a class="personal-info-li-title">{{ `[${$t(EnumValue.getLabelByValue(MsgTypeEnum, v.type))}] ${v.msg}` }}</a>
</li>
</ul>
</div>
@@ -89,7 +89,13 @@
</template>
<el-row>
<el-col :sm="24">
<el-table :data="state.machine.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
<el-table
:data="state.machine.opLogs"
:height="state.resourceOpTableHeight"
stripe
size="small"
:empty-text="$t('home.noOpRecord')"
>
<el-table-column prop="createTime" show-overflow-tooltip width="135">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
@@ -123,7 +129,7 @@
</template>
<el-row>
<el-col :sm="24">
<el-table :data="state.db.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
<el-table :data="state.db.opLogs" :height="state.resourceOpTableHeight" stripe size="small" :empty-text="$t('home.noOpRecord')">
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
@@ -164,7 +170,7 @@
</template>
<el-row>
<el-col :sm="24">
<el-table :data="state.redis.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
<el-table :data="state.redis.opLogs" :height="state.resourceOpTableHeight" stripe size="small" :empty-text="$t('home.noOpRecord')">
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
@@ -203,7 +209,7 @@
</template>
<el-row>
<el-col :sm="24">
<el-table :data="state.mongo.opLogs" :height="state.resourceOpTableHeight" stripe size="small" empty-text="暂无操作记录">
<el-table :data="state.mongo.opLogs" :height="state.resourceOpTableHeight" stripe size="small" :empty-text="$t('home.noOpRecord')">
<el-table-column prop="createTime" show-overflow-tooltip min-width="135">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
@@ -226,15 +232,15 @@
</el-col>
</el-row>
<el-dialog width="900px" title="消息" v-model="msgDialog.visible">
<el-dialog width="900px" :title="$t('common.msg')" v-model="msgDialog.visible">
<el-table border :data="msgDialog.msgs.list" size="small">
<el-table-column property="type" label="类型" width="60">
<el-table-column property="type" :label="$t('common.type')" width="60">
<template #default="scope">
{{ getMsgTypeDesc(scope.row.type) }}
{{ $t(EnumValue.getLabelByValue(MsgTypeEnum, scope.row.type)) }}
</template>
</el-table-column>
<el-table-column property="msg" label="消息"></el-table-column>
<el-table-column property="createTime" label="时间" width="150">
<el-table-column property="msg" :label="$t('common.msg')"></el-table-column>
<el-table-column property="createTime" :label="$t('common.time')" width="150">
<template #default="scope">
{{ formatDate(scope.row.createTime) }}
</template>
@@ -274,6 +280,8 @@ import { getAllTagInfoByCodePaths } from '../ops/component/tag';
import { ElMessage } from 'element-plus';
import { getFileUrl, getUploadFileUrl } from '@/common/request';
import { saveUser } from '@/common/utils/storage';
import EnumValue from '../../common/Enum';
import { MsgTypeEnum } from './enums';
const router = useRouter();
const { userInfo } = storeToRefs(useUserInfo());
@@ -352,15 +360,6 @@ const searchMsg = async () => {
state.msgDialog.msgs = await getMsgs();
};
const getMsgTypeDesc = (type: number) => {
if (type == 1) {
return '登录';
}
if (type == 2) {
return '通知';
}
};
const getAccountInfo = async () => {
state.accountInfo = await personApi.accountInfo.request();
};

View File

@@ -0,0 +1,6 @@
import { EnumValue } from '@/common/Enum';
export const MsgTypeEnum = {
Login: EnumValue.of(1, 'home.msgTypeLogin'),
Notify: EnumValue.of(2, 'home.msgTypeNotify'),
};

View File

@@ -2,12 +2,20 @@
<div>
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large">
<el-form-item prop="username">
<el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable autocomplete="off"> </el-input>
<el-input
type="text"
:placeholder="$t('login.inputUsernamePlaceholder')"
prefix-icon="user"
v-model="loginForm.username"
clearable
autocomplete="off"
>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="请输入密码"
:placeholder="$t('login.inputPasswordPlaceholder')"
prefix-icon="lock"
v-model="loginForm.password"
autocomplete="off"
@@ -22,7 +30,7 @@
<el-input
type="text"
maxlength="6"
placeholder="请输入验证码"
:placeholder="$t('login.inputCaptchaPlaceholder')"
prefix-icon="position"
v-model="loginForm.captcha"
clearable
@@ -38,30 +46,35 @@
</el-row>
</el-form-item>
<el-form-item v-if="ldapEnabled" prop="ldapLogin">
<el-checkbox v-model="loginForm.ldapLogin" label="LDAP 登录" size="small" />
<el-checkbox v-model="loginForm.ldapLogin" :label="$t('login.ldapLogin')" size="small" />
</el-form-item>
<span v-if="showLoginFailTips" style="color: #f56c6c; font-size: 12px">
提示登录失败超过{{ accountLoginSecurity.loginFailCount }}次后将被限制{{ accountLoginSecurity.loginFailMin }}分钟内不可再次登录
{{
$t('login.loginFailTip', {
loginFailCount: accountLoginSecurity.loginFailCount,
loginFailMin: accountLoginSecurity.loginFailMin,
})
}}
</span>
<el-form-item>
<el-button type="primary" class="login-content-submit" round @click="login" :loading="loading.signIn">
<span> </span>
<span>{{ $t('login.login') }}</span>
</el-button>
</el-form-item>
</el-form>
<el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
<el-dialog :title="$t('login.changePassword')" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef" label-width="auto">
<el-form-item prop="username" label="用户名" required>
<el-form-item prop="username" :label="$t('common.username')" required>
<el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
</el-form-item>
<el-form-item prop="oldPassword" label="旧密码" required>
<el-form-item prop="oldPassword" :label="$t('login.oldPassword')" required>
<el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password" type="password"></el-input>
</el-form-item>
<el-form-item prop="newPassword" label="新密码" required>
<el-form-item prop="newPassword" :label="$t('login.newPassword')" required>
<el-input
v-model.trim="changePwdDialog.form.newPassword"
placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
:placeholder="$t('login.passwordRuleTip')"
type="password"
autocomplete="new-password"
></el-input>
@@ -70,14 +83,16 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelChangePwd"> </el-button>
<el-button @click="changePwd" type="primary" :loading="loading.changePwd"> </el-button>
<el-button @click="cancelChangePwd">{{ $t('common.cancel') }}</el-button>
<el-button @click="changePwd" type="primary" :loading="loading.changePwd">
{{ $t('common.confirm') }}
</el-button>
</div>
</template>
</el-dialog>
<el-dialog
title="OTP校验"
:title="$t('login.otpValidation')"
v-model="otpDialog.visible"
@close="loading.signIn = false"
:close-on-click-modal="false"
@@ -85,7 +100,7 @@
:destroy-on-close="true"
>
<el-form ref="otpFormRef" :model="otpDialog.form" :rules="otpDialog.rules" @submit.native.prevent label-width="auto">
<el-form-item v-if="otpDialog.otpUrl" label="二维码">
<el-form-item v-if="otpDialog.otpUrl" :label="$t('login.qrCode')">
<qrcode-vue :value="otpDialog.otpUrl" :size="200" level="H" />
</el-form-item>
@@ -96,31 +111,35 @@
v-model.trim="otpDialog.form.code"
clearable
@keyup.enter="otpVerify"
placeholder="请输入令牌APP中显示的授权码"
:placeholder="$t('login.enterOtpCodeTip')"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="otpVerify" type="primary" :loading="loading.otpConfirm"> </el-button>
<el-button @click="otpVerify" type="primary" :loading="loading.otpConfirm">
{{ $t('common.confirm') }}
</el-button>
</div>
</template>
</el-dialog>
<el-dialog title="修改基本信息" v-model="baseInfoDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
<el-dialog :title="$t('updateBasicInfo')" v-model="baseInfoDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
<el-form :model="baseInfoDialog.form" :rules="baseInfoDialog.rules" ref="baseInfoFormRef" label-width="auto">
<el-form-item prop="username" label="用户名" required>
<el-form-item prop="username" :label="$t('common.username')" required>
<el-input v-model.trim="baseInfoDialog.form.username"></el-input>
</el-form-item>
<el-form-item prop="name" label="姓名" required>
<el-form-item prop="name" :label="$t('login.name')" required>
<el-input v-model.trim="baseInfoDialog.form.name"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="updateUserInfo()" type="primary" :loading="loading.updateUserConfirm"> </el-button>
<el-button @click="updateUserInfo()" type="primary" :loading="loading.updateUserConfirm">
{{ $t('common.confirm') }}
</el-button>
</div>
</template>
</el-dialog>
@@ -145,11 +164,14 @@ import { AccountUsernamePattern } from '@/common/pattern';
import { getToken } from '@/common/utils/storage';
import { useThemeConfig } from '@/store/themeConfig';
import { getFileUrl } from '@/common/request';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
username: [{ required: true, message: t('login.inputUsernamePlaceholder'), trigger: 'blur' }],
password: [{ required: true, message: t('login.inputPasswordPlaceholder'), trigger: 'blur' }],
captcha: [{ required: true, message: t('login.inputCaptchaPlaceholder'), trigger: 'blur' }],
};
// 定义变量内容
@@ -189,10 +211,10 @@ const state = reactive({
},
rules: {
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ required: true, message: t('login.inputNewPasswordPlaceholder'), trigger: 'blur' },
{
pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[`~!@#$%^&*()_+<>?:"{},.\/\\;'[\]])[A-Za-z\d`~!@#$%^&*()_+<>?:"{},.\/\\;'[\]]{8,}$/,
message: '须为8位以上且包含字⺟⼤⼩写+数字+特殊符号',
message: t('login.passwordRuleTip'),
trigger: 'blur',
},
],
@@ -206,7 +228,7 @@ const state = reactive({
otpToken: '',
},
rules: {
code: [{ required: true, message: '请输入OTP授权码', trigger: 'blur' }],
code: [{ required: true, message: t('login.inputOtpCodePlaceholder'), trigger: 'blur' }],
},
},
baseInfoDialog: {
@@ -217,14 +239,14 @@ const state = reactive({
},
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ required: true, message: t('login.inputUsernamePlaceholder'), trigger: 'blur' },
{
pattern: AccountUsernamePattern.pattern,
message: AccountUsernamePattern.message,
trigger: ['blur'],
},
],
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
name: [{ required: true, message: t('login.inputNamePlaceholder'), trigger: 'blur' }],
},
},
loading: {
@@ -424,7 +446,7 @@ const toIndex = async () => {
setTimeout(async () => {
// 关闭 loading
state.loading.signIn = true;
ElMessage.success(`${currentTimeInfo},欢迎回来!`);
ElMessage.success(t('login.loginSuccessTip'));
// 水印设置用户信息
storesThemeConfig.setWatermarkUser();
}, 300);
@@ -444,7 +466,7 @@ const changePwd = async () => {
changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
await openApi.changePwd(changePwdReq);
ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
ElMessage.success(t('login.passwordChangeSuccessTip'));
state.loginForm.password = state.changePwdDialog.form.newPassword;
state.changePwdDialog.visible = false;
getCaptcha();

View File

@@ -22,13 +22,13 @@
<div class="login-right-warp-main-form">
<div v-if="!state.isScan">
<el-tabs v-model="state.tabsActiveName">
<el-tab-pane label="账号密码登录" name="account">
<el-tab-pane :label="$t('login.accountPasswordLogin')" name="account">
<Account ref="loginForm" />
</el-tab-pane>
</el-tabs>
</div>
<div class="mt20" v-show="state.oauth2LoginConfig.enable">
<el-button link size="small">第三方登录: </el-button>
<el-button link size="small">{{ $t('login.thirdPartyLogin') }}: </el-button>
<el-tooltip :content="state.oauth2LoginConfig.name" placement="top-start">
<el-button link size="small" type="primary" @click="oauth2Login">
<el-icon :size="18">
@@ -52,6 +52,7 @@ import loginBgSplitImg from '@/assets/image/login-bg-split.svg';
import openApi from '@/common/openApi';
import config from '@/common/config';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
// 引入组件
const Account = defineAsyncComponent(() => import('./component/AccountLogin.vue'));
@@ -61,18 +62,20 @@ const loginForm = ref<{ loginResDeal: (data: any) => void } | null>(null);
// 定义变量内容
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { locale } = useI18n();
const state = reactive({
tabsActiveName: 'account',
isScan: false,
oauth2LoginConfig: {
name: 'OAuth2登录',
name: 'OAuth2 Login',
enable: false,
},
});
onMounted(async () => {
storesThemeConfig.setWatermarkUser(true);
locale.value = themeConfig.value.globalI18n;
state.oauth2LoginConfig = await openApi.oauth2LoginConfig();
});
@@ -104,11 +107,13 @@ const oauth2Login = () => {
.login-container {
height: 100%;
background: var(--bg-main-color);
.login-left {
flex: 1;
position: relative;
background-color: rgba(211, 239, 255, 1);
margin-right: 100px;
.login-left-logo {
display: flex;
align-items: center;
@@ -117,24 +122,29 @@ const oauth2Login = () => {
left: 80px;
z-index: 1;
animation: logoAnimation 0.3s ease;
img {
width: 52px;
height: 52px;
}
.login-left-logo-text {
display: flex;
flex-direction: column;
span {
margin-left: 10px;
font-size: 28px;
color: #26a59a;
}
.login-left-logo-text-msg {
font-size: 12px;
color: #32a99e;
}
}
}
.login-left-img {
position: absolute;
top: 50%;
@@ -142,20 +152,24 @@ const oauth2Login = () => {
transform: translate(-50%, -50%);
width: 100%;
height: 52%;
img {
width: 100%;
height: 100%;
animation: error-num 0.6s ease;
}
}
.login-left-waves {
position: absolute;
top: 0;
right: -100px;
}
}
.login-right {
width: 700px;
.login-right-warp {
border: 1px solid var(--el-color-primary-light-3);
border-radius: 3px;
@@ -164,12 +178,14 @@ const oauth2Login = () => {
position: relative;
overflow: hidden;
background-color: var(--bg-main-color);
.login-right-warp-one,
.login-right-warp-two {
position: absolute;
display: block;
width: inherit;
height: inherit;
&::before,
&::after {
content: '';
@@ -177,6 +193,7 @@ const oauth2Login = () => {
z-index: 1;
}
}
.login-right-warp-one {
&::before {
filter: hue-rotate(0deg);
@@ -187,6 +204,7 @@ const oauth2Login = () => {
background: linear-gradient(90deg, transparent, var(--el-color-primary));
animation: loginLeft 3s linear infinite;
}
&::after {
filter: hue-rotate(60deg);
top: -100%;
@@ -198,6 +216,7 @@ const oauth2Login = () => {
animation-delay: 0.7s;
}
}
.login-right-warp-two {
&::before {
filter: hue-rotate(120deg);
@@ -209,6 +228,7 @@ const oauth2Login = () => {
animation: loginRight 3s linear infinite;
animation-delay: 1.4s;
}
&::after {
filter: hue-rotate(300deg);
bottom: -100%;
@@ -220,10 +240,12 @@ const oauth2Login = () => {
animation-delay: 2.1s;
}
}
.login-right-warp-mian {
display: flex;
flex-direction: column;
height: 100%;
.login-right-warp-main-title {
height: 110px;
line-height: 110px;
@@ -234,9 +256,11 @@ const oauth2Login = () => {
animation-delay: 0.3s;
color: var(--el-text-color-primary);
}
.login-right-warp-main-form {
flex: 1;
padding: 0 50px 50px;
.login-content-main-sacn {
position: absolute;
top: 0;
@@ -247,6 +271,7 @@ const oauth2Login = () => {
cursor: pointer;
transition: all ease 0.3s;
color: var(--el-color-primary);
&-delta {
position: absolute;
width: 35px;
@@ -257,11 +282,13 @@ const oauth2Login = () => {
background: var(--el-color-white);
transform: rotate(-45deg);
}
&:hover {
opacity: 1;
transition: all ease 0.3s;
color: var(--el-color-primary) !important;
}
i {
width: 47px;
height: 50px;

View File

@@ -7,9 +7,12 @@ import { onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessage } from 'element-plus';
import openApi from '@/common/openApi';
import { useI18n } from 'vue-i18n';
const route = useRoute();
const { t } = useI18n();
onMounted(async () => {
try {
const queryParam = route.query;
@@ -26,7 +29,7 @@ onMounted(async () => {
}
const res: any = await openApi.oauth2Callback(queryParam);
ElMessage.success('授权认证成功');
ElMessage.success(t('system.oauth.authSuccess'));
top?.opener.postMessage(res);
window.close();
} catch (e: any) {

View File

@@ -1,13 +1,13 @@
<template>
<div class="auth-cert-edit">
<el-dialog :title="props.title" v-model="dialogVisible" :show-close="false" width="500px" :destroy-on-close="true" :close-on-click-modal="false">
<el-dialog :title="props.title" v-model="dialogVisible" :show-close="false" width="600px" :destroy-on-close="true" :close-on-click-modal="false">
<el-form ref="acForm" :model="state.form" label-width="auto" :rules="rules">
<el-form-item prop="type" label="凭证类型" required>
<el-select @change="changeType" v-model="form.type" placeholder="请选择凭证类型">
<el-form-item prop="type" :label="$t('ac.credentialType')" required>
<el-select @change="changeType" v-model="form.type">
<el-option
v-for="item in AuthCertTypeEnum"
:key="item.value"
:label="item.label"
:label="$t(item.label)"
:value="item.value"
v-show="!props.disableType?.includes(item.value)"
>
@@ -15,12 +15,12 @@
</el-select>
</el-form-item>
<el-form-item prop="ciphertextType" label="密文类型" required>
<el-select v-model="form.ciphertextType" placeholder="请选择密文类型" @change="changeCiphertextType">
<el-form-item prop="ciphertextType" :label="$t('ac.ciphertextType')" required>
<el-select v-model="form.ciphertextType" @change="changeCiphertextType">
<el-option
v-for="item in AuthCertCiphertextTypeEnum"
:key="item.value"
:label="item.label"
:label="$t(item.label)"
:value="item.value"
v-show="!props.disableCiphertextType?.includes(item.value)"
:disabled="item.value == AuthCertCiphertextTypeEnum.Public.value && form.type == AuthCertTypeEnum.Public.value"
@@ -30,45 +30,45 @@
</el-form-item>
<template v-if="showResourceEdit">
<el-form-item prop="type" label="资源类型" required>
<el-select :disabled="form.id" v-model="form.resourceType" placeholder="请选择资源类型">
<el-form-item prop="type" :label="$t('ac.resourceType')" required>
<el-select :disabled="form.id" v-model="form.resourceType">
<el-option
:key="TagResourceTypeEnum.Machine.value"
:label="TagResourceTypeEnum.Machine.label"
:label="$t(TagResourceTypeEnum.Machine.label)"
:value="TagResourceTypeEnum.Machine.value"
/>
<el-option :key="TagResourceTypeEnum.Db.value" :label="TagResourceTypeEnum.Db.label" :value="TagResourceTypeEnum.Db.value" />
<el-option
:key="TagResourceTypeEnum.Redis.value"
:label="TagResourceTypeEnum.Redis.label"
:label="$t(TagResourceTypeEnum.Redis.label)"
:value="TagResourceTypeEnum.Redis.value"
/>
</el-select>
</el-form-item>
<el-form-item prop="resourceCode" label="资源编号" required>
<el-input :disabled="form.id" v-model="form.resourceCode" placeholder="请输入资源编号"></el-input>
<el-form-item prop="resourceCode" :label="$t('ac.resourceCode')" required>
<el-input :disabled="form.id" v-model="form.resourceCode"></el-input>
</el-form-item>
</template>
<el-form-item v-if="form.type == AuthCertTypeEnum.Public.value" prop="name" label="名称" required>
<el-input :disabled="form.id" v-model="form.name" placeholder="请输入凭证名 (全局唯一)"></el-input>
<el-form-item v-if="form.type == AuthCertTypeEnum.Public.value" prop="name" :label="$t('common.name')" required>
<el-input :disabled="form.id" v-model="form.name" :placeholder="$t('ac.namePlaceholder')"></el-input>
</el-form-item>
<template v-if="form.ciphertextType != AuthCertCiphertextTypeEnum.Public.value">
<el-form-item prop="username" label="用户名">
<el-form-item prop="username" :label="$t('common.username')">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.Password.value" prop="ciphertext" label="密码">
<el-input type="password" show-password clearable v-model.trim="form.ciphertext" placeholder="请输入密码" autocomplete="new-password">
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.Password.value" prop="ciphertext" :label="$t('common.password')">
<el-input type="password" show-password clearable v-model.trim="form.ciphertext" autocomplete="new-password">
<template #suffix>
<SvgIcon v-if="form.id" v-auth="'authcert:showciphertext'" @click="getCiphertext" name="search" />
</template>
</el-input>
</el-form-item>
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.PrivateKey.value" prop="ciphertext" label="秘钥">
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.PrivateKey.value" prop="ciphertext" :label="$t('ac.privateKey')">
<div class="w100" style="position: relative">
<SvgIcon
v-if="form.id"
@@ -77,17 +77,17 @@
name="search"
style="position: absolute; top: 5px; right: 5px; cursor: pointer; z-index: 1"
/>
<el-input type="textarea" :rows="5" v-model="form.ciphertext" placeholder="请将私钥文件内容拷贝至此"> </el-input>
<el-input type="textarea" :rows="5" v-model="form.ciphertext" :placeholder="$t('ac.privateKeyPlaceholder')"> </el-input>
</div>
</el-form-item>
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.PrivateKey.value" prop="passphrase" label="秘钥密码">
<el-form-item v-if="form.ciphertextType == AuthCertCiphertextTypeEnum.PrivateKey.value" prop="passphrase" :label="$t('ac.privateKeyPwd')">
<el-input type="password" show-password v-model="form.extra.passphrase"> </el-input>
</el-form-item>
</template>
<template v-else>
<el-form-item label="公共凭证">
<el-form-item :label="$t('ac.publicAc')">
<el-select default-first-option filterable v-model="form.ciphertext" @change="changePublicAuthCert">
<el-option v-for="item in state.publicAuthCerts" :key="item.name" :label="item.name" :value="item.name">
{{ item.name }}
@@ -102,14 +102,14 @@
</el-form-item>
</template>
<el-form-item label="备注">
<el-form-item :label="$t('common.remark')">
<el-input v-model="form.remark" type="textarea" :rows="2"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelEdit"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button @click="cancelEdit">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk">{{ $t('common.confirm') }}</el-button>
</div>
</template>
</el-dialog>
@@ -123,11 +123,14 @@ import EnumTag from '@/components/enumtag/EnumTag.vue';
import { resourceAuthCertApi } from '../tag/api';
import { ResourceCodePattern } from '@/common/pattern';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
title: {
type: String,
default: '凭证保存',
default: '',
},
authCert: {
type: Object,
@@ -162,7 +165,7 @@ const rules = {
name: [
{
required: true,
message: '请输入凭证名',
message: t('common.pleaseInput', { label: t('common.name') }),
trigger: ['change', 'blur'],
},
{
@@ -174,7 +177,7 @@ const rules = {
resourceCode: [
{
required: true,
message: '请输入资源编号',
message: t('common.pleaseInput', { label: t('ac.resourceCode') }),
trigger: ['change', 'blur'],
},
],

View File

@@ -1,6 +1,6 @@
<template>
<div class="auth-cert-manage">
<el-table :data="authCerts" max-height="180" stripe size="small">
<el-table :data="authCerts" :max-height="180" stripe size="small">
<el-table-column min-wdith="120px">
<template #header>
<el-button v-auth="'authcert:save'" class="ml0" type="primary" circle size="small" icon="Plus" @click="edit(null)"> </el-button>
@@ -10,7 +10,7 @@
<el-button class="ml1" v-auth="'authcert:del'" type="danger" @click="deleteRow(scope.$index)" icon="delete" link></el-button>
<el-button
title="测试连接"
:title="$t('ac.testConn')"
:loading="props.testConnBtnLoading && scope.$index == state.idx"
:disabled="props.testConnBtnLoading"
class="ml1"
@@ -22,19 +22,18 @@
</template>
</el-table-column>
<!-- <el-table-column prop="name" label="名称" show-overflow-tooltip min-width="100px"> </el-table-column> -->
<el-table-column prop="username" label="用户名" min-width="120px" show-overflow-tooltip> </el-table-column>
<el-table-column prop="ciphertextType" label="密文类型" width="100px">
<el-table-column prop="username" :label="$t('common.username')" min-width="120px" show-overflow-tooltip> </el-table-column>
<el-table-column prop="ciphertextType" :label="$t('ac.ciphertextType')" width="100px">
<template #default="scope">
<EnumTag :value="scope.row.ciphertextType" :enums="AuthCertCiphertextTypeEnum" />
</template>
</el-table-column>
<el-table-column prop="type" label="凭证类型" width="100px">
<el-table-column prop="type" :label="$t('ac.credentialType')" width="100px">
<template #default="scope">
<EnumTag :value="scope.row.type" :enums="AuthCertTypeEnum" />
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" show-overflow-tooltip width="120px"> </el-table-column>
<el-table-column prop="remark" :label="$t('common.remark')" show-overflow-tooltip width="120px"> </el-table-column>
</el-table>
<ResourceAuthCertEdit
@@ -55,6 +54,9 @@ import { resourceAuthCertApi } from '../tag/api';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import ResourceAuthCertEdit from './ResourceAuthCertEdit.vue';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
resourceType: { type: Number },
@@ -117,17 +119,6 @@ const cancelEdit = () => {
const btnOk = async (authCert: any) => {
const isEdit = authCert.id;
// if (!isEdit) {
// const res = await resourceAuthCertApi.listByQuery.request({
// name: authCert.name,
// pageNum: 1,
// pageSize: 100,
// });
// if (res.total) {
// ElMessage.error('该授权凭证名称已存在');
// return;
// }
// }
if (isEdit || state.idx >= 0) {
authCerts.value[state.idx] = authCert;
@@ -136,7 +127,7 @@ const btnOk = async (authCert: any) => {
}
if (authCerts.value?.filter((x: any) => x.username == authCert.username).length > 0) {
ElMessage.error('该用户名已存在于该账号列表中');
ElMessage.error(t('ac.usernameExist'));
return;
}

View File

@@ -6,7 +6,7 @@
style="width: 100%"
v-model="sshTunnelMachineId"
@clear="clear"
placeholder="请选择SSH隧道机器"
placeholder="SSH tunnel machine"
clearable
>
<el-option v-for="item in sshTunnelMachineList" :key="item.id" :label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id"> </el-option>

View File

@@ -1,6 +1,6 @@
<template>
<div style="display: inline-flex; justify-content: center; align-items: center; cursor: pointer; vertical-align: middle">
<el-popover :show-after="500" @show="showTagInfo" placement="top-start" title="标签信息" :width="300" trigger="hover">
<el-popover :show-after="500" @show="showTagInfo" placement="top-start" :title="$t('common.tag')" :width="300" trigger="hover">
<template #reference>
<el-icon>
<InfoFilled />

View File

@@ -1,6 +1,6 @@
<template>
<div class="card pd5">
<el-input v-model="filterText" placeholder="输入关键字->搜索已展开节点信息" clearable size="small" class="mb5 w100" />
<el-input v-model="filterText" :placeholder="$t('tag.tagFilterPlaceholder')" clearable size="small" class="mb5 w100" />
<el-scrollbar class="tag-tree">
<el-tree
ref="treeRef"
@@ -26,11 +26,11 @@
<slot v-else :node="node" :data="data" name="prefix"></slot>
<span class="ml3" :title="data.labelRemark">
<slot name="label" :data="data" v-if="!data.disabled"> {{ data.label }}</slot>
<slot name="label" :data="data" v-if="!data.disabled"> {{ $t(data.label) }}</slot>
<!-- 禁用状态 -->
<slot name="disabledLabel" :data="data" v-else>
<el-link type="danger" disabled :underline="false">
{{ `${data.label}` }}
{{ `${$t(data.label)}` }}
</el-link>
</slot>
</span>

View File

@@ -1,6 +1,6 @@
<template>
<div class="w100 tag-tree-check">
<el-input v-model="filterTag" @input="onFilterValChanged" clearable placeholder="输入关键字过滤" size="small" />
<el-input v-model="filterTag" @input="onFilterValChanged" clearable :placeholder="$t('tag.keywordFilterPlaceholder')" size="small" />
<div class="mt3" style="border: 1px solid var(--el-border-color)">
<el-scrollbar :style="{ height: props.height }">
<el-tree

View File

@@ -5,7 +5,7 @@
v-model="state.selectTags"
@change="changeTag"
:data="tags"
placeholder="请选择关联标签"
:placeholder="$t('tag.selectTagPlaceholder')"
:default-expanded-keys="defaultExpandedKeys"
show-checkbox
node-key="codePath"

View File

@@ -161,7 +161,7 @@ export class NodeType {
* @returns
*/
export function getTagPathSearchItem(resourceType: number) {
return SearchItem.select('tagPath', '标签').withOptionsApi(
return SearchItem.select('tagPath', 'common.tag').withOptionsApi(
OptionsApi.new(tagApi.getResourceTagPaths, {resourceType}).withConvertFn((res: any) => {
return res.map((x: any) => {
return {

View File

@@ -10,12 +10,12 @@
width="38%"
>
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
<el-form-item prop="name" label="名称" required>
<el-input v-model.trim="form.name" placeholder="请输入数据库别名" auto-complete="off"></el-input>
<el-form-item prop="name" :label="$t('common.name')" required>
<el-input v-model.trim="form.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="authCertName" label="授权凭证" required>
<el-select v-model="form.authCertName" placeholder="请选择授权凭证" filterable>
<el-form-item prop="authCertName" :label="$t('db.acName')" required>
<el-select v-model="form.authCertName" filterable>
<el-option v-for="item in state.authCerts" :key="item.id" :label="`${item.name}`" :value="item.name">
{{ item.name }}
@@ -31,13 +31,11 @@
</el-select>
</el-form-item>
<el-form-item prop="getDatabaseMode" label="获库方式" required>
<el-select v-model="form.getDatabaseMode" @change="onChangeGetDatabaseMode" placeholder="请选择库名获取方式">
<el-option v-for="item in DbGetDbNamesMode" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
<el-form-item prop="getDatabaseMode" :label="$t('db.getDbMode')" required>
<EnumSelect :enums="DbGetDbNamesMode" v-model="form.getDatabaseMode" @change="onChangeGetDatabaseMode" />
</el-form-item>
<el-form-item prop="database" label="数据库名">
<el-form-item prop="database" label="DB">
<el-select
:disabled="form.getDatabaseMode == DbGetDbNamesMode.Auto.value || !form.authCertName"
v-model="dbNamesSelected"
@@ -48,26 +46,28 @@
filterable
:filter-method="filterDbNames"
allow-create
placeholder="获库方式为‘指定库名’时,可选择"
:placeholder="$t('db.selectDbPlacehoder')"
@focus="getAllDatabase(form.authCertName)"
:loading="state.loadingDbNames"
>
<template #header>
<el-checkbox v-model="checkAllDbNames" :indeterminate="indeterminateDbNames" @change="handleCheckAll"> 全选 </el-checkbox>
<el-checkbox v-model="checkAllDbNames" :indeterminate="indeterminateDbNames" @change="handleCheckAll">
{{ $t('db.allSelect') }}
</el-checkbox>
</template>
<el-option v-for="db in state.dbNamesFiltered" :key="db" :label="db" :value="db" />
</el-select>
</el-form-item>
<el-form-item prop="remark" label="备注">
<el-form-item prop="remark" :label="$t('common.remark')">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel()"> </el-button>
<el-button type="primary" @click="btnOk"> </el-button>
<el-button @click="cancel()">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="btnOk">{{ $t('common.confirm') }}</el-button>
</div>
</template>
</el-dialog>
@@ -77,7 +77,6 @@
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { dbApi } from './api';
import { ElMessage } from 'element-plus';
import type { CheckboxValueType } from 'element-plus';
import { DbType } from '@/views/ops/db/dialect';
@@ -86,6 +85,8 @@ import { AuthCertCiphertextTypeEnum } from '../tag/enums';
import { resourceAuthCertApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { DbGetDbNamesMode } from './enums';
import EnumSelect from '@/components/enumselect/EnumSelect.vue';
import { useI18nFormValidate, useI18nPleaseInput, useI18nPleaseSelect } from '@/hooks/useI18n';
const props = defineProps({
visible: {
@@ -106,39 +107,31 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'confirm']);
const rules = {
tagId: [
{
required: true,
message: '请选择标签',
trigger: ['change', 'blur'],
},
],
instanceId: [
{
required: true,
message: '请选择数据库实例',
message: useI18nPleaseSelect('db.dbInst'),
trigger: ['change', 'blur'],
},
],
name: [
{
required: true,
message: '请输入别名',
message: useI18nPleaseInput('common.name'),
trigger: ['change', 'blur'],
},
],
authCertName: [
{
required: true,
message: '请选择授权凭证',
message: useI18nPleaseSelect('db.acName'),
trigger: ['change', 'blur'],
},
],
getDatabaseMode: [
{
required: true,
message: '请选择库名获取方式',
message: useI18nPleaseSelect('db.getDbMode'),
trigger: ['change', 'blur'],
},
],
@@ -237,13 +230,7 @@ const open = async () => {
};
const btnOk = async () => {
try {
await dbForm.value.validate();
} catch (e: any) {
ElMessage.error('请正确填写信息');
return false;
}
await useI18nFormValidate(dbForm);
emit('confirm', state.form);
};

View File

@@ -46,12 +46,12 @@
<template #database="{ data }">
<el-popover placement="bottom" :width="200" trigger="click">
<template #reference>
<el-button @click="getDbNames(data)" type="primary" link>查看库</el-button>
<el-button @click="getDbNames(data)" type="primary" link>{{ $t('db.showDb') }}</el-button>
</template>
<el-table :data="filterDbs" v-loading="state.loadingDbNames" size="small">
<el-table-column prop="dbName" label="数据库">
<el-table-column prop="dbName" :label="$t('db.db')">
<template #header>
<el-input v-model="state.dbNameSearch" size="small" placeholder="库名: 输入可过滤" clearable />
<el-input v-model="state.dbNameSearch" size="small" :placeholder="$t('db.dbFilterPlaceholder')" clearable />
</template>
</el-table-column>
</el-table>
@@ -63,24 +63,24 @@
</template>
<template #action="{ data }">
<el-button v-auth="perms.saveDb" @click="editDb(data)" type="primary" link>编辑</el-button>
<el-button v-auth="perms.saveDb" @click="editDb(data)" type="primary" link>{{ $t('common.edit') }}</el-button>
<el-divider direction="vertical" border-style="dashed" />
<el-button type="primary" @click="onShowSqlExec(data)" link>SQL记录</el-button>
<el-button type="primary" @click="onShowSqlExec(data)" link>{{ $t('db.sqlRecord') }}</el-button>
<el-divider direction="vertical" border-style="dashed" />
<el-dropdown @command="handleMoreActionCommand">
<span class="el-dropdown-link-more">
更多
{{ $t('common.more') }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="{ type: 'dumpDb', data }"> 导出 </el-dropdown-item>
<el-dropdown-item :command="{ type: 'dumpDb', data }"> {{ $t('db.dump') }} </el-dropdown-item>
<!-- <el-dropdown-item
:command="{ type: 'backupDb', data }"
v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)"
@@ -106,18 +106,18 @@
</page-table>
</el-drawer>
<el-dialog width="750px" :title="`${exportDialog.db} 数据库导出`" v-model="exportDialog.visible">
<el-dialog width="750px" :title="`${exportDialog.db} DB Dump`" v-model="exportDialog.visible">
<el-row justify="space-between">
<el-col :span="9">
<el-form-item label="导出内容: ">
<el-form-item :label="$t('db.dumpContent')">
<el-checkbox-group v-model="exportDialog.contents" :min="1">
<el-checkbox label="结构" value="结构" />
<el-checkbox label="数据" value="数据" />
<el-checkbox :label="$t('db.structure')" value="结构" />
<el-checkbox :label="$t('db.data')" value="数据" />
</el-checkbox-group>
</el-form-item>
</el-col>
<el-col :span="9">
<el-form-item label="扩展名: ">
<el-form-item :label="$t('db.extName')">
<el-radio-group v-model="exportDialog.extName">
<el-radio label="sql" value="sql" />
<el-radio label="gzip" value="gzip" />
@@ -130,8 +130,8 @@
<el-transfer
v-model="exportDialog.value"
filterable
filter-placeholder="按数据库名称筛选"
:titles="['全部数据库', '导出数据库']"
:filter-placeholder="$t('db.dbFilterPlacehoder')"
:titles="[$t('db.allDb'), $t('db.dumpDb')]"
:data="exportDialog.data"
max-height="300"
size="small"
@@ -140,15 +140,15 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="exportDialog.visible = false">取消</el-button>
<el-button @click="dumpDbs()" type="primary">确定</el-button>
<el-button @click="exportDialog.visible = false">{{ $t('common.cancel') }}</el-button>
<el-button @click="dumpDbs()" type="primary">{{ $t('common.confirm') }}</el-button>
</div>
</template>
</el-dialog>
<el-dialog
width="90%"
:title="`${sqlExecLogDialog.title} - SQL执行记录`"
:title="`${sqlExecLogDialog.title} - SQL`"
:before-close="onBeforeCloseSqlExecDialog"
:close-on-click-modal="false"
v-model="sqlExecLogDialog.visible"
@@ -157,7 +157,7 @@
<db-sql-exec-log :db-id="sqlExecLogDialog.dbId" :dbs="sqlExecLogDialog.dbs" />
</el-dialog>
<el-dialog
<!-- <el-dialog
width="80%"
:title="`${dbBackupDialog.title} - 数据库备份`"
:close-on-click-modal="false"
@@ -185,7 +185,7 @@
v-model="dbRestoreDialog.visible"
>
<db-restore-list :dbId="dbRestoreDialog.dbId" :dbNames="dbRestoreDialog.dbs" />
</el-dialog>
</el-dialog> -->
<db-edit
@confirm="confirmEditDb"
@@ -210,18 +210,19 @@ import { hasPerms } from '@/components/auth/auth';
import DbSqlExecLog from './DbSqlExecLog.vue';
import { DbType } from './dialect';
import { getDbDialect } from './dialect/index';
import DbBackupList from './DbBackupList.vue';
import DbBackupHistoryList from './DbBackupHistoryList.vue';
import DbRestoreList from './DbRestoreList.vue';
import ResourceTags from '../component/ResourceTags.vue';
import { sleep } from '@/common/utils/loading';
import { DbGetDbNamesMode } from './enums';
import { DbInst } from './db';
import { ElMessage, ElMessageBox } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
import { useI18n } from 'vue-i18n';
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
const { t } = useI18n();
const props = defineProps({
instance: {
type: [Object],
@@ -237,13 +238,13 @@ const dialogVisible = defineModel<boolean>('visible');
const emit = defineEmits(['cancel']);
const columns = ref([
TableColumn.new('name', '名称'),
TableColumn.new('authCertName', '授权凭证'),
TableColumn.new('getDatabaseMode', '获库方式').typeTag(DbGetDbNamesMode),
TableColumn.new('database', '').isSlot().setMinWidth(80),
TableColumn.new('remark', '备注'),
TableColumn.new('code', '编号'),
TableColumn.new('action', '操作').isSlot().setMinWidth(210).fixedRight().alignCenter(),
TableColumn.new('name', 'common.name'),
TableColumn.new('authCertName', 'db.acName'),
TableColumn.new('getDatabaseMode', 'db.getDbMode').typeTag(DbGetDbNamesMode),
TableColumn.new('database', 'DB').isSlot().setMinWidth(80),
TableColumn.new('remark', 'common.remark'),
TableColumn.new('code', 'common.code'),
TableColumn.new('action', 'common.operation').isSlot().setMinWidth(210).fixedRight().alignCenter(),
]);
const perms = {
@@ -319,7 +320,7 @@ const state = reactive({
dbEditDialog: {
visible: false,
data: null as any,
title: '新增数据库',
title: '',
},
filterDb: {
param: '',
@@ -367,14 +368,14 @@ const editDb = (data: any) => {
instanceId: props.instance.id,
};
}
state.dbEditDialog.title = data ? '编辑数据库' : '新增数据库';
state.dbEditDialog.title = data ? useI18nEditTitle('db.db') : useI18nCreateTitle('db.db');
state.dbEditDialog.visible = true;
};
const confirmEditDb = async (db: any) => {
db.instanceId = props.instance.id;
await dbApi.saveDb.request(db);
ElMessage.success('保存成功');
useI18nSaveSuccessMsg();
search();
cancelEditDb();
};
@@ -386,15 +387,11 @@ const cancelEditDb = () => {
const deleteDb = async () => {
try {
await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】库?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
await useI18nDeleteConfirm(state.selectionData.map((x: any) => x.name).join(''));
for (let db of state.selectionData) {
await dbApi.deleteDb.request({ id: db.id });
}
ElMessage.success('删除成功');
useI18nDeleteSuccessMsg();
} catch (err) {
//
} finally {
@@ -480,7 +477,7 @@ const onDumpDbs = async (row: any) => {
state.exportDialog.value = [];
state.exportDialog.data = data;
state.exportDialog.dbId = row.id;
state.exportDialog.contents = ['结构', '数据'];
state.exportDialog.contents = [t('db.structure'), t('db.data')];
state.exportDialog.extName = 'sql';
state.exportDialog.visible = true;
};
@@ -489,7 +486,7 @@ const onDumpDbs = async (row: any) => {
* 数据库信息导出
*/
const dumpDbs = async () => {
isTrue(state.exportDialog.value.length > 0, '请添加要导出的数据库');
isTrue(state.exportDialog.value.length > 0, t('db.noDumpDbMsg'));
let type = 0;
for (let c of state.exportDialog.contents) {
if (c == '结构') {

View File

@@ -10,7 +10,7 @@
:columns="columns"
>
<template #dbSelect>
<el-select v-model="query.db" placeholder="请选择数据库" filterable clearable>
<el-select v-model="query.db" :placeholder="$t('db.selectDbPlaceholder')" filterable clearable>
<el-option v-for="item in dbs" :key="item" :label="`${item}`" :value="item"> </el-option>
</el-select>
</template>
@@ -18,6 +18,7 @@
<template #action="{ data }">
<el-link
v-if="
data.oldValue != '' &&
data.status == DbSqlExecStatusEnum.Success.value &&
(data.type == DbSqlExecTypeEnum.Update.value || data.type == DbSqlExecTypeEnum.Delete.value)
"
@@ -27,12 +28,12 @@
:underline="false"
@click="onShowRollbackSql(data)"
>
还原SQL</el-link
{{ $t('db.restoreSql') }}</el-link
>
</template>
</page-table>
<el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible">
<el-dialog width="55%" :title="$t('db.restoreSql')" v-model="rollbackSqlDialog.visible">
<el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql" size="small"> </el-input>
</el-dialog>
</div>
@@ -58,23 +59,23 @@ const props = defineProps({
});
const searchItems = [
SearchItem.slot('db', '数据库', 'dbSelect'),
SearchItem.input('table', '表名'),
SearchItem.select('type', '操作类型').withEnum(DbSqlExecTypeEnum),
SearchItem.slot('db', 'db.db', 'dbSelect'),
SearchItem.input('table', 'db.table'),
SearchItem.select('type', 'db.stmtType').withEnum(DbSqlExecTypeEnum),
];
const columns = ref([
TableColumn.new('db', '数据库'),
TableColumn.new('table', ''),
TableColumn.new('type', '类型').typeTag(DbSqlExecTypeEnum).setAddWidth(10),
TableColumn.new('creator', '执行人'),
TableColumn.new('db', 'db.db'),
TableColumn.new('table', 'db.type'),
TableColumn.new('type', 'db.stmtType').typeTag(DbSqlExecTypeEnum).setAddWidth(10),
TableColumn.new('creator', 'db.execUser'),
TableColumn.new('sql', 'SQL').canBeautify(),
TableColumn.new('remark', '备注'),
TableColumn.new('status', '执行状态').typeTag(DbSqlExecStatusEnum),
TableColumn.new('res', '执行结果'),
TableColumn.new('createTime', '执行时间').isTime(),
TableColumn.new('oldValue', '原值').canBeautify(),
TableColumn.new('action', '操作').isSlot().setMinWidth(90).fixedRight().alignCenter(),
TableColumn.new('remark', 'common.remark'),
TableColumn.new('status', 'common.status').typeTag(DbSqlExecStatusEnum),
TableColumn.new('res', 'db.execRes'),
TableColumn.new('createTime', 'db.execTime').isTime(),
TableColumn.new('oldValue', 'db.oldValue').canBeautify(),
TableColumn.new('action', 'common.operation').isSlot().setMinWidth(90).fixedRight().alignCenter(),
]);
const pageTableRef: Ref<any> = ref(null);

View File

@@ -1,30 +1,37 @@
<template>
<div class="db-transfer-edit">
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="40%">
<el-drawer :title="title" v-model="dialogVisible" :before-close="cancel" :destroy-on-close="true" :close-on-click-modal="false" size="45%">
<template #header>
<DrawerHeader :header="title" :back="cancel" />
</template>
<el-form :model="form" ref="dbForm" :rules="rules" label-width="auto">
<el-divider content-position="left">基本信息</el-divider>
<el-divider content-position="left">{{ $t('common.basic') }}</el-divider>
<el-form-item prop="taskName" label="任务名" required>
<el-input v-model.trim="form.taskName" placeholder="请输入任务名" auto-complete="off" />
<el-form-item prop="taskName" :label="$t('db.taskName')" required>
<el-input v-model.trim="form.taskName" auto-complete="off" />
</el-form-item>
<el-form-item>
<el-row class="w100">
<el-col :span="12">
<el-form-item prop="status" label="启用状态">
<el-switch v-model="form.status" inline-prompt active-text="启用" inactive-text="禁用" :active-value="1" :inactive-value="-1" />
<el-form-item prop="status" :label="$t('common.status')">
<el-switch
v-model="form.status"
inline-prompt
:active-text="$t('common.enable')"
:inactive-text="$t('common.disable')"
:active-value="1"
:inactive-value="-1"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="cronAble" label="定时迁移" required>
<el-form-item prop="cronAble" :label="$t('db.cronAble')" required>
<el-radio-group v-model="form.cronAble">
<el-radio label="" :value="1" />
<el-radio label="" :value="-1" />
<el-radio :label="$t('common.yes')" :value="1" />
<el-radio :label="$t('common.no')" :value="-1" />
</el-radio-group>
</el-form-item>
</el-col>
@@ -35,9 +42,8 @@
<CrontabInput v-model="form.cron" />
</el-form-item>
<el-form-item prop="srcDbId" label="源数据库" class="w100" required>
<el-form-item prop="srcDbId" :label="$t('db.srcDb')" class="w100" required>
<db-select-tree
placeholder="请选择源数据库"
v-model:db-id="form.srcDbId"
v-model:inst-name="form.srcInstName"
v-model:db-name="form.srcDbName"
@@ -47,18 +53,18 @@
/>
</el-form-item>
<el-form-item prop="mode" label="迁移方式" required>
<el-form-item prop="mode" :label="$t('db.transferMode')" required>
<el-radio-group v-model="form.mode">
<el-radio label="迁移到数据库" :value="1" />
<el-radio label="迁移到文件(自动命名)" :value="2" />
<el-radio :label="$t('db.transfer2Db')" :value="1" />
<el-radio :label="$t('db.transfer2File')" :value="2" />
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.mode === 2">
<el-row class="w100">
<el-col :span="12">
<el-form-item prop="targetFileDbType" label="文件数据库类型" :required="form.mode === 2">
<el-select v-model="form.targetFileDbType" placeholder="数据库类型" clearable filterable>
<el-form-item prop="targetFileDbType" :label="$t('db.dbFileType')" :required="form.mode === 2">
<el-select v-model="form.targetFileDbType" clearable filterable>
<el-option
v-for="(dbTypeAndDialect, key) in getDbDialectMap()"
:key="key"
@@ -76,10 +82,10 @@
</el-col>
<el-col :span="12">
<el-form-item label="文件保留天数">
<el-form-item :label="$t('db.fileSaveDays')">
<el-input-number v-model="form.fileSaveDays" :min="-1" :max="1000">
<template #suffix>
<span></span>
<span>{{ $t('db.day') }}</span>
</template>
</el-input-number>
</el-form-item>
@@ -87,16 +93,15 @@
</el-row>
</el-form-item>
<el-form-item prop="strategy" label="迁移策略" required>
<el-form-item prop="strategy" :label="$t('db.transferStrategy')" required>
<el-radio-group v-model="form.strategy">
<el-radio label="全量" :value="1" />
<el-radio label="增量(暂不可用)" :value="2" disabled />
<el-radio :label="$t('db.transferFull')" :value="1" />
<el-radio :label="$t('db.transferIncrement')" :value="2" disabled />
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.mode == 1" prop="targetDbId" label="目标数据库" class="w100" :required="form.mode === 1">
<el-form-item v-if="form.mode == 1" prop="targetDbId" :label="$t('db.targetDb')" class="w100" :required="form.mode === 1">
<db-select-tree
placeholder="请选择目标数据库"
v-model:db-id="form.targetDbId"
v-model:inst-name="form.targetInstName"
v-model:db-name="form.targetDbName"
@@ -106,17 +111,17 @@
/>
</el-form-item>
<el-form-item prop="nameCase" label="转换表、字段名" required>
<el-form-item prop="nameCase" :label="$t('db.nameCase')" required>
<el-radio-group v-model="form.nameCase">
<el-radio label="" :value="1" />
<el-radio label="大写" :value="2" />
<el-radio label="小写" :value="3" />
<el-radio :label="$t('db.none')" :value="1" />
<el-radio :label="$t('db.upper')" :value="2" />
<el-radio :label="$t('db.lower')" :value="3" />
</el-radio-group>
</el-form-item>
<el-divider content-position="left">数据库对象</el-divider>
<el-divider content-position="left">{{ $t('db.dbObj') }}</el-divider>
<el-form-item>
<el-input v-model="state.filterSrcTableText" placeholder="过滤表" size="small" />
<el-input v-model="state.filterSrcTableText" placeholder="filter table" size="small" />
</el-form-item>
<el-form-item class="w100">
<el-tree
@@ -136,8 +141,8 @@
<template #footer>
<div>
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
<el-button @click="cancel()">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">{{ $t('common.confirm') }}</el-button>
</div>
</template>
</el-drawer>
@@ -154,6 +159,10 @@ import { getDbDialect, getDbDialectMap } from '@/views/ops/db/dialect';
import SvgIcon from '@/components/svgIcon/index.vue';
import _ from 'lodash';
import DrawerHeader from '@/components/drawer-header/DrawerHeader.vue';
import { useI18nFormValidate, useI18nPleaseInput, useI18nPleaseSelect, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
data: {
@@ -173,35 +182,35 @@ const rules = {
taskName: [
{
required: true,
message: '请输入任务名',
message: useI18nPleaseInput('db.taskName'),
trigger: ['change', 'blur'],
},
],
srcDbId: [
{
required: true,
message: '请选择源库',
message: useI18nPleaseSelect('db.srcDb'),
trigger: ['change', 'blur'],
},
],
targetDbId: [
{
required: true,
message: '请选择目标库',
message: useI18nPleaseSelect('db.targetDb'),
trigger: ['change', 'blur'],
},
],
targetFileDbType: [
{
required: true,
message: '请选择目标文件语言类型',
message: useI18nPleaseSelect('db.dbFileType'),
trigger: ['change', 'blur'],
},
],
cron: [
{
required: true,
message: '请选择cron表达式',
message: useI18nPleaseSelect('cron'),
trigger: ['change', 'blur'],
},
],
@@ -262,12 +271,12 @@ const state = reactive({
srcTableTree: [
{
id: 'tab-check',
label: '表',
label: t('db.table'),
children: [
{ id: 'all', label: '全部表(*' },
{ id: 'all', label: `${t('db.allTable')}*` },
{
id: 'table-list',
label: '自定义',
label: t('db.custom'),
disabled: srcTableListDisabled,
children: [] as any[],
},
@@ -394,29 +403,23 @@ const getCheckedKeys = () => {
};
const btnOk = async () => {
dbForm.value.validate(async (valid: boolean) => {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
await useI18nFormValidate(dbForm);
state.submitForm = await getReqForm();
state.submitForm = await getReqForm();
let checkedKeys = getCheckedKeys();
if (checkedKeys.length > 0) {
state.submitForm.checkedKeys = checkedKeys.join(',');
}
let checkedKeys = getCheckedKeys();
if (checkedKeys.length > 0) {
state.submitForm.checkedKeys = checkedKeys.join(',');
}
if (!state.submitForm.checkedKeys) {
ElMessage.error(t('db.noTransferTableMsg'));
return false;
}
if (!state.submitForm.checkedKeys) {
ElMessage.error('请选择需要迁移的表');
return false;
}
await saveExec();
ElMessage.success('保存成功');
emit('val-change', state.form);
cancel();
});
await saveExec();
useI18nSaveSuccessMsg();
emit('val-change', state.form);
cancel();
};
const cancel = () => {

Some files were not shown because too many files have changed in this diff Show More