fix: Modify the style of the file exclusion control (#10504)

This commit is contained in:
ssongliu 2025-09-26 18:08:11 +08:00 committed by GitHub
parent 3f141346fa
commit e87bf0b3fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 143 additions and 137 deletions

View file

@ -1,121 +0,0 @@
<template>
<div>
<el-form ref="formRef" :model="form" :rules="rules" v-loading="loading" class="mt-2">
<el-form-item prop="tmpRule">
<div class="w-full">
<el-input
v-model="form.tmpRule"
:rows="5"
style="width: calc(100% - 50px)"
type="textarea"
:placeholder="$t('setting.ignoreHelper1')"
/>
<el-button icon="Folder" @click="fileRef.acceptParams({ path: baseDir, isAll: true })" />
</div>
<span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span>
</el-form-item>
</el-form>
<el-button :disabled="form.tmpRule === ''" @click="handleAdd(formRef)">
{{ $t('xpack.tamper.addRule') }}
</el-button>
<el-table :data="tableList">
<el-table-column prop="value" />
<el-table-column min-width="18">
<template #default="scope">
<el-button link type="primary" @click="handleDelete(scope.$index)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<FileList ref="fileRef" @choose="loadDir" />
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import FileList from '@/components/file-list/index.vue';
import { FormInstance } from 'element-plus';
import { loadBaseDir } from '@/api/modules/setting';
const loading = ref();
const fileRef = ref();
const baseDir = ref();
const tableList = ref();
const em = defineEmits(['update:files']);
const props = defineProps({
files: {
type: Array<String>,
default: [],
},
});
const form = reactive({
tmpRule: '',
});
const formRef = ref<FormInstance>();
const rules = reactive({
tmpRule: [{ validator: checkData, trigger: 'blur' }],
});
function checkData(rule: any, value: any, callback: any) {
if (form.tmpRule !== '') {
const reg = /^[^\\\"'|<>?]{1,128}$/;
let items = value.split('\n');
for (const item of items) {
if (item.indexOf(' ') !== -1) {
callback(new Error(i18n.global.t('setting.noSpace')));
}
if (!reg.test(item) && value !== '') {
callback(new Error(i18n.global.t('commons.rule.linuxName', ['\\:?\'"<>|'])));
} else {
callback();
}
}
}
callback();
}
const loadPath = async () => {
const pathRes = await loadBaseDir();
baseDir.value = pathRes.data;
};
const loadDir = async (path: string) => {
form.tmpRule += path + '\n';
};
const handleAdd = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let itemData = form.tmpRule.split('\n');
for (const item of itemData) {
if (item) {
tableList.value.push({ value: item });
}
}
em(
'update:files',
tableList.value.map((item) => item.value),
);
form.tmpRule = '';
});
};
const handleDelete = (index: number) => {
tableList.value.splice(index, 1);
em(
'update:files',
tableList.value.map((item) => item.value),
);
};
onMounted(() => {
loadPath();
tableList.value = props.files.map((item) => {
return { value: item };
});
});
</script>

View file

@ -0,0 +1,85 @@
<template>
<div>
<el-input-tag
v-model="tmpTags"
trigger="Enter"
@paste="handlePaste"
@change="handleUpdate"
@add-tag="handleAdd"
>
<template #suffix>
<el-tooltip v-if="withFile" :content="$t('website.select')">
<el-button
class="-mr-3"
link
icon="Folder"
@click="fileRef.acceptParams({ path: baseDir, isAll: true })"
/>
</el-tooltip>
<el-tooltip :content="$t('commons.button.copy')">
<el-button class="-mr-3" link icon="CopyDocument" @click="copyText(tmpTags.join('\n'))" />
</el-tooltip>
<el-tooltip :content="$t('commons.button.clean')">
<el-button link icon="Close" @click="handleClean" />
</el-tooltip>
</template>
</el-input-tag>
<span v-if="props.egHelp" class="input-help">{{ props.egHelp }}</span>
<FileList ref="fileRef" @choose="loadFile" />
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { copyText } from '@/utils/util';
import FileList from '@/components/file-list/index.vue';
const em = defineEmits(['update:tags']);
const tmpTags = ref([]);
const fileRef = ref();
const props = defineProps({
egHelp: { type: String, default: 'key=val' },
tags: { type: Array<string>, default: [] },
withFile: { type: Boolean, default: false },
baseDir: { type: String, default: '/' },
});
watch(
() => props.tags,
(newVal) => {
tmpTags.value = newVal || [];
},
);
const loadFile = async (path: string) => {
handleAdd(path);
};
const handlePaste = (event: any) => {
event.preventDefault();
const pasteData = event.clipboardData.getData('text');
const tags = pasteData.split('\n');
for (const item of tags) {
if (item) {
handleAdd(item);
}
}
};
const handleAdd = (val: string) => {
tmpTags.value = tmpTags.value?.filter((item) => item !== val);
tmpTags.value.push(val);
handleUpdate();
};
const handleUpdate = () => {
em('update:tags', tmpTags.value);
};
const handleClean = () => {
tmpTags.value = [];
handleUpdate();
};
onMounted(() => {
tmpTags.value = props.tags || [];
});
</script>

View file

@ -1070,7 +1070,8 @@ const message = {
snapshot: 'System snapshot',
allOptionHelper: `The current task plan is to back up all [{0}]. Direct download isn't supported at the moment. You can check the backup list of [{0}] menu.`,
exclusionRules: 'Exclusive rule',
exclusionRulesHelper: 'The exclusion rules will apply to all compression operations of this backup.',
exclusionRulesHelper:
'Select or enter exclusion rules, press Enter after each set to continue. Exclusion rules will apply to all compression operations in this backup',
default_download_path: 'Default download link',
saveLocal: 'Retain local backups (the same as the number of cloud storage copies)',
url: 'URL Address',

View file

@ -1069,7 +1069,7 @@ const message = {
allOptionHelper: `El plan actual es respaldar todos los [{0}]. La descarga directa no está soportada por ahora. Puede consultar la lista de respaldos en el menú [{0}].`,
exclusionRules: 'Reglas de exclusión',
exclusionRulesHelper:
'Las reglas de exclusión se aplicarán a todas las operaciones de compresión de este respaldo.',
'Seleccione o ingrese reglas de exclusión, presione Enter después de cada conjunto para continuar. Las reglas de exclusión se aplicarán a todas las operaciones de compresión en esta copia de seguridad',
default_download_path: 'Enlace de descarga predeterminado',
saveLocal: 'Retener respaldos locales (igual al número de copias en la nube)',
url: 'Dirección URL',

View file

@ -1040,7 +1040,8 @@ const message = {
allOptionHelper:
'現在のタスク計画はすべての[{0}]をバックアップすることです直接ダウンロードは現時点ではサポートされていません[{{0}]メニューのバックアップリストを確認できます',
exclusionRules: '排他的ルール',
exclusionRulesHelper: '除外ルールはこのバックアップのすべての圧縮操作に適用されます',
exclusionRulesHelper:
'除外ルールを選択または入力し各セット入力後にEnterキーを押して続行します除外ルールはこのバックアップのすべての圧縮操作に適用されます',
default_download_path: 'デフォルトのダウンロードリンク',
saveLocal: 'ローカルバックアップを保持しますクラウドストレージコピーの数と同じ',
url: 'URLアドレス',

View file

@ -1028,7 +1028,8 @@ const message = {
snapshot: '시스템 스냅샷',
allOptionHelper: `현재 작업 계획은 모든 [{0}]을 백업하는 것입니다. 현재 직접 다운로드는 지원되지 않습니다. [{0}] 메뉴에서 백업 목록을 확인하실 수 있습니다.`,
exclusionRules: '배제 규칙',
exclusionRulesHelper: '배제 규칙은 백업의 모든 압축 작업에 적용됩니다.',
exclusionRulesHelper:
'제외 규칙을 선택하거나 입력하고, 세트 입력 Enter 키를 눌러 계속합니다. 제외 규칙은 백업의 모든 압축 작업에 적용됩니다',
default_download_path: '기본 다운로드 링크',
saveLocal: '로컬 백업 보관 (클라우드 저장소 복사본 수와 동일)',
url: 'URL 주소',

View file

@ -1062,7 +1062,8 @@ const message = {
allOptionHelper:
'Pelan tugas semasa adalah untuk menyandarkan semua [{0}]. Muat turun terus tidak disokong buat masa ini. Anda boleh menyemak senarai sandaran dalam menu [{0}].',
exclusionRules: 'Peraturan pengecualian',
exclusionRulesHelper: 'Peraturan pengecualian akan terpakai pada semua operasi mampatan bagi sandaran ini.',
exclusionRulesHelper:
'Pilih atau masukkan peraturan pengecualian, tekan Enter selepas setiap set untuk teruskan. Peraturan pengecualian akan digunakan untuk semua operasi mampatan dalam sandaran ini',
default_download_path: 'Pautan muat turun lalai',
saveLocal: 'Simpan sandaran tempatan (sama seperti bilangan salinan storan awan)',
url: 'Alamat URL',

View file

@ -1059,7 +1059,8 @@ const message = {
allOptionHelper:
'O plano de tarefa atual é fazer backup de todos os [{0}]. O download direto não é suportado no momento. Você pode verificar a lista de backups no menu [{0}].',
exclusionRules: 'Regras de exclusão',
exclusionRulesHelper: 'As regras de exclusão se aplicam a todas as operações de compressão deste backup.',
exclusionRulesHelper:
'Selecione ou insira regras de exclusão, pressione Enter após cada conjunto para continuar. As regras de exclusão se aplicarão a todas as operações de compactação neste backup',
default_download_path: 'Link de download padrão',
saveLocal: 'Manter backups locais (o mesmo número de cópias na nuvem)',
url: 'Endereço URL',

View file

@ -1056,7 +1056,8 @@ const message = {
allOptionHelper:
'Текущий план задачи - резервное копирование всех [{0}]. Прямое скачивание сейчас не поддерживается. Вы можете проверить список резервных копий в меню [{0}].',
exclusionRules: 'Правило исключения',
exclusionRulesHelper: 'Правила исключения будут применяться ко всем операциям сжатия этой резервной копии.',
exclusionRulesHelper:
'Выберите или введите правила исключения, нажмите Enter после каждого набора для продолжения. Правила исключения будут применяться ко всем операциям сжатия в этой резервной копии',
default_download_path: 'Ссылка для скачивания по умолчанию',
saveLocal: 'Сохранять локальные резервные копии (столько же, сколько копий в облачном хранилище)',
url: 'URL-адрес',

View file

@ -1083,7 +1083,8 @@ const message = {
allOptionHelper:
'Mevcut görev planı tüm [{0}] öğelerini yedeklemektir. Doğrudan indirme şu anda desteklenmiyor. [{0}] menüsünün yedekleme listesini kontrol edebilirsiniz.',
exclusionRules: 'Hariç tutma kuralı',
exclusionRulesHelper: 'Hariç tutma kuralları bu yedeğin tüm sıkıştırma işlemlerine uygulanacaktır.',
exclusionRulesHelper:
'Hariç tutma kurallarını seçin veya girin, her setten sonra Enter basarak devam edin. Hariç tutma kuralları bu yedeklemedeki tüm sıkıştırma işlemlerine uygulanacaktır',
default_download_path: 'Varsayılan indirme bağlantısı',
saveLocal: 'Yerel yedeklemeleri sakla (bulut depolama kopyalarının sayısı ile aynı)',
url: 'URL Adresi',

View file

@ -1016,7 +1016,7 @@ const message = {
snapshot: '系統快照',
allOptionHelper: '目前計劃任務為備份所有{0}暫不支援直接下載可在{0}備份列表中查看',
exclusionRules: '排除規則',
exclusionRulesHelper: '排除規則將對此次備份的所有壓縮操作生效',
exclusionRulesHelper: '選擇或輸入排除規則輸入完一組後回車繼續排除規則將對此次備份的所有壓縮操作生效',
default_download_path: '預設下載網址',
saveLocal: '同時保留本機備份和雲端儲存保留份數一致',
url: 'URL 地址',

View file

@ -1015,7 +1015,7 @@ const message = {
snapshot: '系统快照',
allOptionHelper: '当前计划任务为备份所有{0}暂不支持直接下载可在{0}备份列表中查看',
exclusionRules: '排除规则',
exclusionRulesHelper: '排除规则将对此次备份的所有压缩操作生效',
exclusionRulesHelper: '选择或输入排除规则输入完一组后回车继续排除规则将对此次备份的所有压缩操作生效',
default_download_path: '默认下载地址',
saveLocal: '同时保留本地备份和云存储保留份数一致',
url: 'URL 地址',

View file

@ -624,8 +624,13 @@
<el-row :gutter="20">
<LayoutCol :span="20" v-if="hasExclusionRules()">
<el-form-item :label="$t('cronjob.exclusionRules')" prop="exclusionRules">
<IgnoreFile class="w-full" v-model:files="form.ignoreFiles"></IgnoreFile>
<span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span>
<InputTag
class="w-full"
v-model:tags="form.ignoreFiles"
:withFile="true"
:baseDir="loadItemDir()"
:egHelp="$t('cronjob.exclusionRulesHelper')"
/>
</el-form-item>
</LayoutCol>
</el-row>
@ -757,7 +762,7 @@ import { ElForm } from 'element-plus';
import { Cronjob } from '@/api/interface/cronjob';
import { addCronjob, editCronjob, loadCronjobInfo, loadNextHandle, loadScriptOptions } from '@/api/modules/cronjob';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import IgnoreFile from '@/components/file-batch/index.vue';
import InputTag from '@/components/input-tag/index.vue';
import LayoutCol from '@/components/layout-col/form.vue';
import { listDbItems } from '@/api/modules/database';
import { getWebsiteOptions } from '@/api/modules/website';
@ -782,6 +787,7 @@ import LicenseImport from '@/components/license-import/index.vue';
import { splitTimeFromSecond, transferTimeToSecond } from '@/utils/util';
import { getGroupList } from '@/api/modules/group';
import { routerToName, routerToPath } from '@/utils/router';
import { loadBaseDir } from '@/api/modules/setting';
const router = useRouter();
const globalStore = GlobalStore();
@ -793,6 +799,8 @@ const { isProductPro } = storeToRefs(globalStore);
const loading = ref();
const nextTimes = ref([]);
const baseDir = ref();
const isCreate = ref();
const defaultGroupID = ref();
const form = reactive<Cronjob.CronjobInfo>({
@ -1162,6 +1170,18 @@ const loadScriptDir = async (path: string) => {
form.script = path;
};
const loadItemDir = () => {
if (form.type === 'directory' && form.isDir) {
return form.sourceDir;
}
return baseDir.value;
};
const loadInstallDir = async () => {
const pathRes = await loadBaseDir();
baseDir.value = pathRes.data;
};
const loadGroups = async () => {
const res = await getGroupList('cronjob');
groupOptions.value = res.data || [];
@ -1468,6 +1488,7 @@ onMounted(() => {
}
loadGroups();
search();
loadInstallDir();
});
</script>
<style scoped lang="scss">

View file

@ -158,7 +158,13 @@
</fu-step>
<template #footer></template>
<fu-step id="ignoreFiles" :title="$t('cronjob.exclusionRules')">
<IgnoreFile v-model:files="form.ignoreFiles"></IgnoreFile>
<InputTag
class="w-full"
v-model:tags="form.ignoreFiles"
:withFile="true"
:baseDir="baseDir"
:egHelp="$t('cronjob.exclusionRulesHelper')"
/>
</fu-step>
</fu-steps>
<template #footer>
@ -174,11 +180,11 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { loadSnapshotInfo, snapshotCreate } from '@/api/modules/setting';
import { loadBaseDir, loadSnapshotInfo, snapshotCreate } from '@/api/modules/setting';
import { computeSize, newUUID, transferTimeToSecond } from '@/utils/util';
import i18n from '@/lang';
import TaskLog from '@/components/log/task/index.vue';
import IgnoreFile from '@/components/file-batch/index.vue';
import InputTag from '@/components/input-tag/index.vue';
import { listBackupOptions } from '@/api/modules/backup';
import { Rules } from '@/global/form-rules';
import { ElForm } from 'element-plus';
@ -194,6 +200,8 @@ const panelRef = ref();
const backupRef = ref();
const taskLogRef = ref();
const baseDir = ref();
const backupOptions = ref();
const accountOptions = ref();
@ -249,6 +257,7 @@ const acceptParams = (): void => {
nowIndex.value = 0;
search();
loadBackups();
loadInstallDir();
drawerVisible.value = true;
};
@ -466,6 +475,11 @@ const selectAllImage = () => {
}
};
const loadInstallDir = async () => {
const pathRes = await loadBaseDir();
baseDir.value = pathRes.data;
};
const search = async () => {
loading.value = true;
await loadSnapshotInfo()