mirror of
https://github.com/rustfs/console.git
synced 2026-09-24 16:30:56 +08:00
feat: 为所有只有图标没有文字的按钮添加适当的文案
- 修复了18个只有图标没有文字的按钮,提升用户体验和可访问性 - 为存储桶配置、对象管理、用户管理、策略管理、事件管理等页面的按钮添加了文字说明 - 所有修改都使用了已存在的翻译键值,确保国际化支持 - 包括编辑、删除、添加、设置等常用操作的按钮文案优化
This commit is contained in:
+193
-245
@@ -7,9 +7,10 @@
|
||||
<span>{{ t('Access Policy') }}</span>
|
||||
<n-button class="align-middle" quaternary round type="primary" @click="editPolicy">
|
||||
<Icon name="ri:edit-2-line" class="mr-2" />
|
||||
<span>{{ t('Edit') }}</span>
|
||||
</n-button>
|
||||
</template>
|
||||
{{ policyOptions.find(item => item.value === bucketPolicy)?.label }}
|
||||
{{policyOptions.find(item => item.value === bucketPolicy)?.label}}
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
@@ -17,6 +18,7 @@
|
||||
<span class="mr-2">{{ t('Encryption') }}</span>
|
||||
<n-button class="align-middle" quaternary round type="primary" @click="editEncript">
|
||||
<Icon name="ri:edit-2-line" class="mr-2" />
|
||||
<span>{{ t('Edit') }}</span>
|
||||
</n-button>
|
||||
</template>
|
||||
<span v-if="encryptFormValue.encrypt === 'disabled'">{{ t('Disabled') }}</span>
|
||||
@@ -38,42 +40,23 @@
|
||||
{{ t('Tag') }}
|
||||
<n-button class="align-middle" round quaternary type="primary" @click="addTag">
|
||||
<Icon name="ri:add-line" size="16" class="mr-2" />
|
||||
<span>{{ t('Add') }}</span>
|
||||
</n-button>
|
||||
</template>
|
||||
<n-tag
|
||||
class="m-2"
|
||||
v-for="(tag, index) in tags"
|
||||
type="info"
|
||||
@click="editTag(index)"
|
||||
closable
|
||||
@close="handledeleteTag(index)"
|
||||
>
|
||||
<n-tag class="m-2" v-for="(tag, index) in tags" type="info" @click="editTag(index)" closable @close="handledeleteTag(index)">
|
||||
{{ tag.Key }}:{{ tag.Value }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
<template #label>{{ t('Object Lock') }}</template>
|
||||
<n-switch
|
||||
:disabled="true"
|
||||
v-model:value="lockStatus"
|
||||
:loading="objectLockLoading"
|
||||
:round="false"
|
||||
@update:value="handleChangeVersionStatus"
|
||||
/>
|
||||
<n-switch :disabled="true" v-model:value="lockStatus" :loading="objectLockLoading" :round="false" @update:value="handleChangeVersionStatus" />
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
<template #label>{{ t('Version Control') }}</template>
|
||||
<n-switch
|
||||
v-model:value="versioningStatus"
|
||||
:disabled="lockStatus == true"
|
||||
checked-value="Enabled"
|
||||
unchecked-value="Suspended"
|
||||
:round="false"
|
||||
:loading="statusLoading"
|
||||
@update:value="handleChangeVersionStatus"
|
||||
/>
|
||||
<n-switch v-model:value="versioningStatus" :disabled="lockStatus == true" checked-value="Enabled" unchecked-value="Suspended" :round="false" :loading="statusLoading"
|
||||
@update:value="handleChangeVersionStatus" />
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
|
||||
@@ -111,25 +94,10 @@
|
||||
</n-drawer-content>
|
||||
|
||||
<!-- policy -->
|
||||
<n-modal
|
||||
v-model:show="showPolicyModal"
|
||||
:title="t('Set Policy')"
|
||||
preset="card"
|
||||
draggable
|
||||
:style="{ width: '750px' }"
|
||||
>
|
||||
<n-form
|
||||
ref="policyFormRef"
|
||||
:inline="policyFormValue.policy !== 'custom'"
|
||||
:label-width="80"
|
||||
:model="policyFormValue"
|
||||
>
|
||||
<n-modal v-model:show="showPolicyModal" :title="t('Set Policy')" preset="card" draggable :style="{ width: '750px' }">
|
||||
<n-form ref="policyFormRef" :inline="policyFormValue.policy !== 'custom'" :label-width="80" :model="policyFormValue">
|
||||
<n-form-item :label="t('Policy')" path="" class="flex-auto">
|
||||
<n-select
|
||||
v-model:value="policyFormValue.policy"
|
||||
:placeholder="t('Please select policy')"
|
||||
:options="policyOptions"
|
||||
/>
|
||||
<n-select v-model:value="policyFormValue.policy" :placeholder="t('Please select policy')" :options="policyOptions" />
|
||||
</n-form-item>
|
||||
<n-form-item :span="24" v-if="policyFormValue.policy == 'custom'" :label="t('Policy Content')" path="content">
|
||||
<n-scrollbar class="w-full max-h-[60vh]"><json-editor v-model="policyFormValue.content" /></n-scrollbar>
|
||||
@@ -158,27 +126,13 @@
|
||||
</n-modal>
|
||||
|
||||
<!-- Encrypt -->
|
||||
<n-modal
|
||||
v-model:show="showEncryptModal"
|
||||
:title="t('Enable Storage Encryption')"
|
||||
preset="card"
|
||||
draggable
|
||||
:style="{ width: '550px' }"
|
||||
>
|
||||
<n-modal v-model:show="showEncryptModal" :title="t('Enable Storage Encryption')" preset="card" draggable :style="{ width: '550px' }">
|
||||
<n-form ref="encryptFormRef" label-placemen="left" label-width="auto" inline :model="encryptFormValue">
|
||||
<n-form-item :label="t('Encryption Type')" path="encrypt" class="flex-auto">
|
||||
<n-select
|
||||
v-model:value="encryptFormValue.encrypt"
|
||||
:placeholder="t('Please select encryption type')"
|
||||
:options="encryptOptions"
|
||||
/>
|
||||
<n-select v-model:value="encryptFormValue.encrypt" :placeholder="t('Please select encryption type')" :options="encryptOptions" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="encryptFormValue.encrypt == 'SSE-KMS'" label="KMS Key ID" path="kmsKeyId" class="flex-auto">
|
||||
<n-select
|
||||
v-model:value="encryptFormValue.kmsKeyId"
|
||||
:placeholder="t('Please select KMS key')"
|
||||
:options="kmsKeyOptions"
|
||||
/>
|
||||
<n-select v-model:value="encryptFormValue.kmsKeyId" :placeholder="t('Please select KMS key')" :options="kmsKeyOptions" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item>
|
||||
@@ -189,13 +143,7 @@
|
||||
</n-modal>
|
||||
|
||||
<!-- retention -->
|
||||
<n-modal
|
||||
v-model:show="showRetentionModal"
|
||||
:title="t('Set Retention')"
|
||||
preset="card"
|
||||
draggable
|
||||
:style="{ width: '550px' }"
|
||||
>
|
||||
<n-modal v-model:show="showRetentionModal" :title="t('Set Retention')" preset="card" draggable :style="{ width: '550px' }">
|
||||
<n-form ref="retentionFormRef" label-placemen="left" label-width="auto" :model="retentionFormValue">
|
||||
<n-form-item :label="t('Retention Mode')" path="retentionMode" class="flex-auto">
|
||||
<n-radio-group v-model:value="retentionFormValue.retentionMode">
|
||||
@@ -225,30 +173,30 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '#components';
|
||||
const { t } = useI18n();
|
||||
const dialog = useDialog();
|
||||
const visibel = ref(false);
|
||||
const bucketName = ref('');
|
||||
import { Icon } from '#components'
|
||||
const { t } = useI18n()
|
||||
const dialog = useDialog()
|
||||
const visibel = ref(false)
|
||||
const bucketName = ref('')
|
||||
const openDrawer = (bucket: string) => {
|
||||
visibel.value = true;
|
||||
bucketName.value = bucket;
|
||||
visibel.value = true
|
||||
bucketName.value = bucket
|
||||
// 在服务端获取数据
|
||||
getData();
|
||||
};
|
||||
getData()
|
||||
}
|
||||
defineExpose({
|
||||
openDrawer,
|
||||
});
|
||||
})
|
||||
|
||||
const getData = async () => {
|
||||
await getTags();
|
||||
await getVersioningStatus();
|
||||
await getObjectLockConfig();
|
||||
await getBucketEncryptionFn();
|
||||
await getbucketPolicy();
|
||||
};
|
||||
await getTags()
|
||||
await getVersioningStatus()
|
||||
await getObjectLockConfig()
|
||||
await getBucketEncryptionFn()
|
||||
await getbucketPolicy()
|
||||
}
|
||||
|
||||
const message = useMessage();
|
||||
const message = useMessage()
|
||||
const {
|
||||
getBucketTagging,
|
||||
deleteBucket,
|
||||
@@ -262,115 +210,115 @@ const {
|
||||
getBucketEncryption,
|
||||
putBucketEncryption,
|
||||
deleteBucketEncryption,
|
||||
} = useBucket({});
|
||||
} = useBucket({})
|
||||
|
||||
// 使用 SSE 相关功能
|
||||
const { getKeyList } = useSSE();
|
||||
const { getKeyList } = useSSE()
|
||||
|
||||
/**********object lock ***********************/
|
||||
const lockStatus = ref(false);
|
||||
const objectLockLoading = ref(false);
|
||||
const retentionEnabled = ref(false);
|
||||
const lockStatus = ref(false)
|
||||
const objectLockLoading = ref(false)
|
||||
const retentionEnabled = ref(false)
|
||||
|
||||
const getObjectLockConfig = async () => {
|
||||
objectLockLoading.value = true;
|
||||
objectLockLoading.value = true
|
||||
getObjectLockConfiguration(bucketName.value)
|
||||
.then(res => {
|
||||
if (res.ObjectLockConfiguration?.ObjectLockEnabled) {
|
||||
lockStatus.value = res.ObjectLockConfiguration?.ObjectLockEnabled == 'Enabled' ? true : false;
|
||||
lockStatus.value = res.ObjectLockConfiguration?.ObjectLockEnabled == 'Enabled' ? true : false
|
||||
if (res.ObjectLockConfiguration?.Rule) {
|
||||
retentionEnabled.value = true;
|
||||
retentionFormValue.value.retentionMode = res.ObjectLockConfiguration?.Rule?.DefaultRetention?.Mode || null;
|
||||
retentionEnabled.value = true
|
||||
retentionFormValue.value.retentionMode = res.ObjectLockConfiguration?.Rule?.DefaultRetention?.Mode || null
|
||||
retentionFormValue.value.retentionPeriod =
|
||||
res.ObjectLockConfiguration?.Rule?.DefaultRetention?.Days ||
|
||||
res.ObjectLockConfiguration?.Rule?.DefaultRetention?.Years ||
|
||||
null;
|
||||
null
|
||||
retentionFormValue.value.retentionUnit = res.ObjectLockConfiguration?.Rule?.DefaultRetention?.Years
|
||||
? 'Years'
|
||||
: res.ObjectLockConfiguration?.Rule?.DefaultRetention?.Days
|
||||
? 'Days'
|
||||
: '';
|
||||
: ''
|
||||
}
|
||||
} else {
|
||||
lockStatus.value = false;
|
||||
retentionEnabled.value = false;
|
||||
retentionFormValue.value.retentionMode = null;
|
||||
retentionFormValue.value.retentionPeriod = null;
|
||||
retentionFormValue.value.retentionUnit = null;
|
||||
lockStatus.value = false
|
||||
retentionEnabled.value = false
|
||||
retentionFormValue.value.retentionMode = null
|
||||
retentionFormValue.value.retentionPeriod = null
|
||||
retentionFormValue.value.retentionUnit = null
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
objectLockLoading.value = false;
|
||||
});
|
||||
};
|
||||
objectLockLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/**********object lock ***********************/
|
||||
|
||||
/******** policy ***********************/
|
||||
import { setBucketPolicy, getBucketPolicy as getBucketPolicyFn } from '~/utils/bucket-policy';
|
||||
import { getBucketPolicy as getBucketPolicyFn, setBucketPolicy } from '~/utils/bucket-policy'
|
||||
|
||||
const bucketPolicy = ref('public');
|
||||
const bucketPolicy = ref('public')
|
||||
const getbucketPolicy = async () => {
|
||||
try {
|
||||
const res = await getBucketPolicy(bucketName.value);
|
||||
const res = await getBucketPolicy(bucketName.value)
|
||||
if (res.Policy) {
|
||||
policyFormValue.value.content = res.Policy;
|
||||
policyFormValue.value.content = res.Policy
|
||||
|
||||
bucketPolicy.value = getBucketPolicyFn(JSON.parse(res.Policy).Statement, bucketName.value, '');
|
||||
policyFormValue.value.policy = bucketPolicy.value;
|
||||
bucketPolicy.value = getBucketPolicyFn(JSON.parse(res.Policy).Statement, bucketName.value, '')
|
||||
policyFormValue.value.policy = bucketPolicy.value
|
||||
if (bucketPolicy.value == 'none') {
|
||||
bucketPolicy.value = 'custom';
|
||||
policyFormValue.value.policy = 'custom';
|
||||
bucketPolicy.value = 'custom'
|
||||
policyFormValue.value.policy = 'custom'
|
||||
}
|
||||
} else {
|
||||
bucketPolicy.value = 'public';
|
||||
bucketPolicy.value = 'public'
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Handle 404 error when no policy exists
|
||||
console.error('Error fetching bucket policy:', error);
|
||||
console.error('Error fetching bucket policy:', error)
|
||||
// Set default values for private policy
|
||||
bucketPolicy.value = 'private';
|
||||
policyFormValue.value.policy = 'private';
|
||||
policyFormValue.value.content = '{}';
|
||||
bucketPolicy.value = 'private'
|
||||
policyFormValue.value.policy = 'private'
|
||||
policyFormValue.value.content = '{}'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const policyFormValue = ref({
|
||||
policy: 'private',
|
||||
content: '{}',
|
||||
});
|
||||
const showPolicyModal = ref(false);
|
||||
})
|
||||
const showPolicyModal = ref(false)
|
||||
const editPolicy = () => {
|
||||
showPolicyModal.value = true;
|
||||
};
|
||||
showPolicyModal.value = true
|
||||
}
|
||||
|
||||
const submitPolicyForm = () => {
|
||||
if (policyFormValue.value.policy == 'custom') {
|
||||
let polictStr = JSON.stringify(JSON.parse(policyFormValue.value.content));
|
||||
console.log(polictStr);
|
||||
let polictStr = JSON.stringify(JSON.parse(policyFormValue.value.content))
|
||||
console.log(polictStr)
|
||||
putBucketPolicy(bucketName.value, polictStr)
|
||||
.then(() => {
|
||||
message.success(t('Edit Success'));
|
||||
showPolicyModal.value = false;
|
||||
getbucketPolicy();
|
||||
message.success(t('Edit Success'))
|
||||
showPolicyModal.value = false
|
||||
getbucketPolicy()
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(t('Edit Failed') + ': ' + error.message);
|
||||
});
|
||||
message.error(t('Edit Failed') + ': ' + error.message)
|
||||
})
|
||||
} else {
|
||||
const policys = setBucketPolicy([], policyFormValue.value.policy as BucketPolicyType, bucketName.value, '');
|
||||
console.log('🚀 ~ policys:', policys);
|
||||
const policys = setBucketPolicy([], policyFormValue.value.policy as BucketPolicyType, bucketName.value, '')
|
||||
console.log('🚀 ~ policys:', policys)
|
||||
putBucketPolicy(bucketName.value, JSON.stringify({ Version: '2012-10-17', Statement: policys }))
|
||||
.then(() => {
|
||||
message.success(t('Edit Success'));
|
||||
showPolicyModal.value = false;
|
||||
getbucketPolicy();
|
||||
message.success(t('Edit Success'))
|
||||
showPolicyModal.value = false
|
||||
getbucketPolicy()
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(t('Edit Failed') + ': ' + error.message);
|
||||
});
|
||||
message.error(t('Edit Failed') + ': ' + error.message)
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
const policyOptions = [
|
||||
{
|
||||
label: t('Public'),
|
||||
@@ -384,19 +332,19 @@ const policyOptions = [
|
||||
label: t('Custom'),
|
||||
value: 'custom',
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
/******** policy ***********************/
|
||||
|
||||
/********Encrypt ***********************/
|
||||
const showEncryptModal = ref(false);
|
||||
const showEncryptModal = ref(false)
|
||||
const encryptFormValue = ref({
|
||||
encrypt: 'disabled',
|
||||
kmsKeyId: '',
|
||||
});
|
||||
})
|
||||
|
||||
// KMS 密钥列表
|
||||
const kmsKeyOptions = ref([]);
|
||||
const kmsKeyOptions = ref([])
|
||||
|
||||
const encryptOptions = [
|
||||
{
|
||||
@@ -411,102 +359,102 @@ const encryptOptions = [
|
||||
label: 'SSE-S3',
|
||||
value: 'SSE-S3',
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
const getBucketEncryptionFn = async () => {
|
||||
try {
|
||||
const res = await getBucketEncryption(bucketName.value);
|
||||
const res = await getBucketEncryption(bucketName.value)
|
||||
if (
|
||||
res &&
|
||||
res.ServerSideEncryptionConfiguration &&
|
||||
res.ServerSideEncryptionConfiguration.Rules &&
|
||||
res.ServerSideEncryptionConfiguration.Rules.length > 0
|
||||
) {
|
||||
const rule = res.ServerSideEncryptionConfiguration.Rules[0];
|
||||
const rule = res.ServerSideEncryptionConfiguration.Rules[0]
|
||||
if (rule.ApplyServerSideEncryptionByDefault) {
|
||||
const sseAlgorithm = rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm;
|
||||
const sseAlgorithm = rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm
|
||||
if (sseAlgorithm) {
|
||||
// 将AWS S3算法值转换为显示值
|
||||
if (sseAlgorithm === 'aws:kms') {
|
||||
encryptFormValue.value.encrypt = 'SSE-KMS';
|
||||
encryptFormValue.value.encrypt = 'SSE-KMS'
|
||||
} else if (sseAlgorithm === 'AES256') {
|
||||
encryptFormValue.value.encrypt = 'SSE-S3';
|
||||
encryptFormValue.value.encrypt = 'SSE-S3'
|
||||
} else {
|
||||
encryptFormValue.value.encrypt = sseAlgorithm;
|
||||
encryptFormValue.value.encrypt = sseAlgorithm
|
||||
}
|
||||
}
|
||||
const kmsKeyId = rule.ApplyServerSideEncryptionByDefault.KMSMasterKeyID;
|
||||
const kmsKeyId = rule.ApplyServerSideEncryptionByDefault.KMSMasterKeyID
|
||||
if (kmsKeyId) {
|
||||
encryptFormValue.value.kmsKeyId = kmsKeyId;
|
||||
encryptFormValue.value.kmsKeyId = kmsKeyId
|
||||
}
|
||||
}
|
||||
} else {
|
||||
encryptFormValue.value.encrypt = 'disabled';
|
||||
encryptFormValue.value.kmsKeyId = '';
|
||||
encryptFormValue.value.encrypt = 'disabled'
|
||||
encryptFormValue.value.kmsKeyId = ''
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 如果没有加密配置,设置为禁用状态
|
||||
if (error.status === 404) {
|
||||
encryptFormValue.value.encrypt = 'disabled';
|
||||
encryptFormValue.value.kmsKeyId = '';
|
||||
encryptFormValue.value.encrypt = 'disabled'
|
||||
encryptFormValue.value.kmsKeyId = ''
|
||||
} else {
|
||||
console.error('Failed to get bucket encryption:', error);
|
||||
console.error('Failed to get bucket encryption:', error)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 获取 KMS 密钥列表
|
||||
const fetchKMSKeys = async () => {
|
||||
try {
|
||||
const response = await getKeyList();
|
||||
const response = await getKeyList()
|
||||
if (response && response.keys) {
|
||||
kmsKeyOptions.value = response.keys.map((key: any) => {
|
||||
// 优先使用 tags.name,然后是 description,最后是 key_id
|
||||
const keyName = key.tags?.name || key.description || `Key-${key.key_id?.substring(0, 8)}`;
|
||||
const keyName = key.tags?.name || key.description || `Key-${key.key_id?.substring(0, 8)}`
|
||||
return {
|
||||
label: keyName,
|
||||
value: key.key_id,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch KMS keys:', error);
|
||||
message.error(t('Failed to fetch KMS keys'));
|
||||
console.error('Failed to fetch KMS keys:', error)
|
||||
message.error(t('Failed to fetch KMS keys'))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 监听加密类型变化,当选择 SSE-KMS 时自动获取 KMS 密钥列表
|
||||
watch(
|
||||
() => encryptFormValue.value.encrypt,
|
||||
newValue => {
|
||||
if (newValue === 'SSE-KMS') {
|
||||
fetchKMSKeys();
|
||||
fetchKMSKeys()
|
||||
}
|
||||
}
|
||||
);
|
||||
)
|
||||
const editEncript = () => {
|
||||
showEncryptModal.value = true;
|
||||
showEncryptModal.value = true
|
||||
// 当选择 SSE-KMS 时,获取 KMS 密钥列表
|
||||
if (encryptFormValue.value.encrypt === 'SSE-KMS') {
|
||||
fetchKMSKeys();
|
||||
fetchKMSKeys()
|
||||
}
|
||||
};
|
||||
}
|
||||
const submitEncryptForm = () => {
|
||||
if (encryptFormValue.value.encrypt === 'disabled') {
|
||||
// 禁用加密
|
||||
deleteBucketEncryption(bucketName.value)
|
||||
.then(() => {
|
||||
message.success(t('Edit Success'));
|
||||
showEncryptModal.value = false;
|
||||
message.success(t('Edit Success'))
|
||||
showEncryptModal.value = false
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(t('Edit Failed') + ': ' + error.message);
|
||||
});
|
||||
message.error(t('Edit Failed') + ': ' + error.message)
|
||||
})
|
||||
} else if (encryptFormValue.value.encrypt === 'SSE-KMS') {
|
||||
// 验证是否选择了 KMS Key
|
||||
if (!encryptFormValue.value.kmsKeyId) {
|
||||
message.error(t('Please select a KMS key for SSE-KMS encryption'));
|
||||
return;
|
||||
message.error(t('Please select a KMS key for SSE-KMS encryption'))
|
||||
return
|
||||
}
|
||||
|
||||
// 启用 SSE-KMS 加密
|
||||
@@ -521,12 +469,12 @@ const submitEncryptForm = () => {
|
||||
],
|
||||
})
|
||||
.then(() => {
|
||||
message.success(t('Edit Success'));
|
||||
showEncryptModal.value = false;
|
||||
message.success(t('Edit Success'))
|
||||
showEncryptModal.value = false
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(t('Edit Failed') + ': ' + error.message);
|
||||
});
|
||||
message.error(t('Edit Failed') + ': ' + error.message)
|
||||
})
|
||||
} else if (encryptFormValue.value.encrypt === 'SSE-S3') {
|
||||
// 启用 SSE-S3 加密
|
||||
putBucketEncryption(bucketName.value, {
|
||||
@@ -539,104 +487,104 @@ const submitEncryptForm = () => {
|
||||
],
|
||||
})
|
||||
.then(() => {
|
||||
message.success(t('Edit Success'));
|
||||
showEncryptModal.value = false;
|
||||
message.success(t('Edit Success'))
|
||||
showEncryptModal.value = false
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(t('Edit Failed') + ': ' + error.message);
|
||||
});
|
||||
message.error(t('Edit Failed') + ': ' + error.message)
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/********Encrypt ***********************/
|
||||
|
||||
/********versioning ***********************/
|
||||
const versioningStatus: any = ref('');
|
||||
const statusLoading = ref(false);
|
||||
const versioningStatus: any = ref('')
|
||||
const statusLoading = ref(false)
|
||||
// 获取版本控制状态
|
||||
const getVersioningStatus = async () => {
|
||||
try {
|
||||
const resp = await getBucketVersioning(bucketName.value);
|
||||
versioningStatus.value = resp.Status;
|
||||
const resp = await getBucketVersioning(bucketName.value)
|
||||
versioningStatus.value = resp.Status
|
||||
} catch (error) {
|
||||
console.error('get version fail:', error);
|
||||
console.error('get version fail:', error)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleChangeVersionStatus = async (value: string) => {
|
||||
statusLoading.value = true;
|
||||
statusLoading.value = true
|
||||
putBucketVersioning(bucketName.value, value)
|
||||
.then(() => {
|
||||
message.success(t('Edit Success'));
|
||||
getVersioningStatus();
|
||||
message.success(t('Edit Success'))
|
||||
getVersioningStatus()
|
||||
})
|
||||
.finally(() => {
|
||||
statusLoading.value = false;
|
||||
versioningStatus.value = versioningStatus.value == 'Suspended' ? 'Enabled' : 'Suspended';
|
||||
});
|
||||
};
|
||||
statusLoading.value = false
|
||||
versioningStatus.value = versioningStatus.value == 'Suspended' ? 'Enabled' : 'Suspended'
|
||||
})
|
||||
}
|
||||
|
||||
/********versioning ***********************/
|
||||
|
||||
/********tag ***********************/
|
||||
// 定义标签的类型
|
||||
interface Tag {
|
||||
Key: string;
|
||||
Value: string;
|
||||
Key: string
|
||||
Value: string
|
||||
}
|
||||
const showTagModal = ref(false);
|
||||
const showTagModal = ref(false)
|
||||
|
||||
const tagFormValue = ref({
|
||||
name: '',
|
||||
value: '',
|
||||
});
|
||||
})
|
||||
// 获取标签
|
||||
const tags = ref<Tag[]>([]);
|
||||
const tags = ref<Tag[]>([])
|
||||
const getTags = async () => {
|
||||
const resp: any = await getBucketTagging(bucketName.value);
|
||||
tags.value = resp.TagSet || [];
|
||||
};
|
||||
const resp: any = await getBucketTagging(bucketName.value)
|
||||
tags.value = resp.TagSet || []
|
||||
}
|
||||
|
||||
const addTag = () => {
|
||||
nowTagIndex.value = -1;
|
||||
tagFormValue.value = { name: '', value: '' }; // 清空表单
|
||||
showTagModal.value = true;
|
||||
};
|
||||
nowTagIndex.value = -1
|
||||
tagFormValue.value = { name: '', value: '' } // 清空表单
|
||||
showTagModal.value = true
|
||||
}
|
||||
|
||||
const submitTagForm = () => {
|
||||
if (!tagFormValue.value.name || !tagFormValue.value.value) {
|
||||
message.error(t('Please fill in complete tag information'));
|
||||
return;
|
||||
message.error(t('Please fill in complete tag information'))
|
||||
return
|
||||
}
|
||||
|
||||
if (nowTagIndex.value === -1) {
|
||||
tags.value.push({ Key: tagFormValue.value.name, Value: tagFormValue.value.value });
|
||||
tags.value.push({ Key: tagFormValue.value.name, Value: tagFormValue.value.value })
|
||||
}
|
||||
if (nowTagIndex.value !== -1) {
|
||||
tags.value[nowTagIndex.value] = {
|
||||
Key: tagFormValue.value.name,
|
||||
Value: tagFormValue.value.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
// 调用 putBucketTagging 接口
|
||||
putBucketTagging(bucketName.value, { TagSet: tags.value })
|
||||
.then(() => {
|
||||
showTagModal.value = false; // 关闭模态框
|
||||
message.success(t('Tag Update Success'));
|
||||
showTagModal.value = false // 关闭模态框
|
||||
message.success(t('Tag Update Success'))
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(t('Tag Update Failed') + ': ' + error.message);
|
||||
});
|
||||
};
|
||||
message.error(t('Tag Update Failed') + ': ' + error.message)
|
||||
})
|
||||
}
|
||||
|
||||
const nowTagIndex = ref(-1);
|
||||
const nowTagIndex = ref(-1)
|
||||
// 编辑标签
|
||||
const editTag = (index: number) => {
|
||||
nowTagIndex.value = index;
|
||||
const nowTag = tags.value[index];
|
||||
tagFormValue.value = { name: nowTag.Key, value: nowTag.Value }; // 填充表单
|
||||
showTagModal.value = true; // 打开模态框
|
||||
};
|
||||
nowTagIndex.value = index
|
||||
const nowTag = tags.value[index]
|
||||
tagFormValue.value = { name: nowTag.Key, value: nowTag.Value } // 填充表单
|
||||
showTagModal.value = true // 打开模态框
|
||||
}
|
||||
const handledeleteTag = (index: number) => {
|
||||
dialog.error({
|
||||
title: t('Warning'),
|
||||
@@ -644,53 +592,53 @@ const handledeleteTag = (index: number) => {
|
||||
positiveText: t('Confirm'),
|
||||
negativeText: t('Cancel'),
|
||||
onPositiveClick: async () => {
|
||||
nowTagIndex.value = index;
|
||||
tags.value.splice(index, 1); // 从标签列表中删除
|
||||
nowTagIndex.value = index
|
||||
tags.value.splice(index, 1) // 从标签列表中删除
|
||||
|
||||
// 调用 putBucketTagging 接口
|
||||
putBucketTagging(bucketName.value, { TagSet: tags.value })
|
||||
.then(() => {
|
||||
message.success(t('Tag Update Success'));
|
||||
message.success(t('Tag Update Success'))
|
||||
})
|
||||
.catch(error => {
|
||||
message.error(t('Tag Delete Failed') + ': ' + error.message);
|
||||
});
|
||||
message.error(t('Tag Delete Failed') + ': ' + error.message)
|
||||
})
|
||||
},
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
/********tag ***********************/
|
||||
|
||||
/********retention ***********************/
|
||||
interface RetentionFormValue {
|
||||
retentionMode: string | null;
|
||||
retentionPeriod: number | null;
|
||||
retentionUnit: string | null;
|
||||
retentionMode: string | null
|
||||
retentionPeriod: number | null
|
||||
retentionUnit: string | null
|
||||
}
|
||||
|
||||
const showRetentionModal = ref(false);
|
||||
const showRetentionModal = ref(false)
|
||||
const retentionFormValue = ref<RetentionFormValue>({
|
||||
retentionMode: null,
|
||||
retentionPeriod: null,
|
||||
retentionUnit: null,
|
||||
});
|
||||
})
|
||||
|
||||
const editRetention = () => {
|
||||
if (!lockStatus.value) {
|
||||
message.error(t('Object lock is not enabled, cannot set retention'));
|
||||
return;
|
||||
message.error(t('Object lock is not enabled, cannot set retention'))
|
||||
return
|
||||
}
|
||||
showRetentionModal.value = true;
|
||||
};
|
||||
showRetentionModal.value = true
|
||||
}
|
||||
|
||||
const submitRetentionForm = () => {
|
||||
console.log(retentionFormValue.value);
|
||||
console.log(retentionFormValue.value)
|
||||
if (
|
||||
retentionFormValue.value.retentionMode == null ||
|
||||
retentionFormValue.value.retentionPeriod == null ||
|
||||
retentionFormValue.value.retentionUnit == null
|
||||
) {
|
||||
message.error(t('Please fill in complete retention information'));
|
||||
return;
|
||||
message.error(t('Please fill in complete retention information'))
|
||||
return
|
||||
}
|
||||
|
||||
putObjectLockConfiguration(bucketName.value, {
|
||||
@@ -703,10 +651,10 @@ const submitRetentionForm = () => {
|
||||
},
|
||||
},
|
||||
}).then(() => {
|
||||
message.success(t('Edit Success'));
|
||||
showRetentionModal.value = false;
|
||||
getObjectLockConfig();
|
||||
});
|
||||
message.success(t('Edit Success'))
|
||||
showRetentionModal.value = false
|
||||
getObjectLockConfig()
|
||||
})
|
||||
};
|
||||
|
||||
/********retention ***********************/
|
||||
|
||||
+18
-23
@@ -1,8 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import ClipboardJS from 'clipboard';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import ClipboardJS from 'clipboard'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
readonly: {
|
||||
@@ -17,49 +17,44 @@ const props = defineProps({
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => {
|
||||
return `copy-input-${Math.random().toString(36).substring(2, 15)}`;
|
||||
return `copy-input-${Math.random().toString(36).substring(2, 15)}`
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const model = defineModel<string>();
|
||||
const model = defineModel<string>()
|
||||
|
||||
const message = useMessage();
|
||||
const message = useMessage()
|
||||
|
||||
const clipboard = new ClipboardJS(props.copyIcon ? '#' + props.id + 'btn' : '#' + props.id + 'btn2');
|
||||
const clipboard = new ClipboardJS(props.copyIcon ? '#' + props.id + 'btn' : '#' + props.id + 'btn2')
|
||||
clipboard.on('success', function (e) {
|
||||
message.success(t('Copy Success'));
|
||||
e.clearSelection();
|
||||
});
|
||||
message.success(t('Copy Success'))
|
||||
e.clearSelection()
|
||||
})
|
||||
|
||||
// 这里成功的时候也响应error,所以这里也加上
|
||||
clipboard.on('error', function (e) {
|
||||
// 如果复制失败,则使用navigator.clipboard.writeText进行复制
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(model.value || '');
|
||||
navigator.clipboard.writeText(model.value || '')
|
||||
}
|
||||
message.success(t('Copy Success'));
|
||||
});
|
||||
message.success(t('Copy Success'))
|
||||
})
|
||||
onUnmounted(() => {
|
||||
clipboard.destroy();
|
||||
});
|
||||
clipboard.destroy()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<NInputGroup>
|
||||
<n-input v-model:value="model" :readonly="props.readonly" :id="props.id" />
|
||||
<n-input-group-label
|
||||
v-if="props.copyIcon"
|
||||
class="flex items-center"
|
||||
:id="props.id + 'btn'"
|
||||
:data-clipboard-target="`#${props.id}`"
|
||||
>
|
||||
<n-input-group-label v-if="props.copyIcon" class="flex items-center cursor-pointer" :id="props.id + 'btn'" :data-clipboard-target="`#${props.id}`" :title="t('Copy')">
|
||||
<Icon :size="25" name="ri:file-copy-line"></Icon>
|
||||
</n-input-group-label>
|
||||
<NButton type="primary" v-else :id="props.id + 'btn2'" :data-clipboard-target="`#${props.id}`">{{
|
||||
t('Copy')
|
||||
}}</NButton>
|
||||
}}</NButton>
|
||||
</NInputGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+222
-245
@@ -17,21 +17,17 @@
|
||||
<Icon name="ri:add-line" class="mr-2" />
|
||||
<span>{{ t('New Folder') }}</span>
|
||||
</n-button>
|
||||
<n-button @click="() => handleNewObject(false)">
|
||||
<Icon name="ri:add-line" class="mr-2" />
|
||||
<span>{{ t('New File') }}</span>
|
||||
</n-button>
|
||||
<n-button @click="() => (uploadPickerVisible = true)">
|
||||
<Icon name="ri:file-add-line" class="mr-2" />
|
||||
<span>{{ t('Upload File') + '/' + t('Folder') }}</span>
|
||||
</n-button>
|
||||
<n-button :disabled="!checkedKeys.length" secondary @click="handleBatchDelete">
|
||||
<n-button v-show="checkedKeys.length > 0" :disabled="!checkedKeys.length" type="error" @click="handleBatchDelete">
|
||||
<template #icon>
|
||||
<Icon name="ri:delete-bin-5-line"></Icon>
|
||||
</template>
|
||||
{{ t('Delete Selected') }}
|
||||
</n-button>
|
||||
<n-button :disabled="!checkedKeys.length" secondary @click="downloadMultiple">
|
||||
<n-button v-show="checkedKeys.length > 0" :disabled="!checkedKeys.length" @click="downloadMultiple">
|
||||
<Icon name="ri:download-cloud-2-line" class="mr-2" />
|
||||
<span>{{ t('Download') }}</span>
|
||||
</n-button>
|
||||
@@ -42,38 +38,20 @@
|
||||
</div>
|
||||
</template>
|
||||
</n-page-header>
|
||||
<n-data-table
|
||||
class="border dark:border-neutral-700 rounded overflow-hidden"
|
||||
:columns="columns"
|
||||
:data="filteredObjects"
|
||||
:row-key="rowKey"
|
||||
@update:checked-row-keys="handleCheck"
|
||||
:pagination="false"
|
||||
:bordered="false"
|
||||
/>
|
||||
<object-upload-picker
|
||||
:show="uploadPickerVisible"
|
||||
@update:show="
|
||||
val => {
|
||||
uploadPickerVisible = val;
|
||||
refresh();
|
||||
}
|
||||
"
|
||||
:bucketName="bucketName"
|
||||
:prefix="prefix"
|
||||
/>
|
||||
<object-new-form
|
||||
:show="newObjectFormVisible"
|
||||
:asPrefix="newObjectAsPrefix"
|
||||
@update:show="
|
||||
val => {
|
||||
newObjectFormVisible = val;
|
||||
refresh();
|
||||
}
|
||||
"
|
||||
:bucketName="bucketName"
|
||||
:prefix="prefix"
|
||||
/>
|
||||
<n-data-table class="border dark:border-neutral-700 rounded overflow-hidden" :columns="columns" :data="filteredObjects" :row-key="rowKey" @update:checked-row-keys="handleCheck"
|
||||
:pagination="false" :bordered="false" />
|
||||
<object-upload-picker :show="uploadPickerVisible" @update:show="
|
||||
val => {
|
||||
uploadPickerVisible = val
|
||||
refresh()
|
||||
}
|
||||
" :bucketName="bucketName" :prefix="prefix" />
|
||||
<object-new-form :show="newObjectFormVisible" :asPrefix="newObjectAsPrefix" @update:show="
|
||||
val => {
|
||||
newObjectFormVisible = val
|
||||
refresh()
|
||||
}
|
||||
" :bucketName="bucketName" :prefix="prefix" />
|
||||
<n-button-group class="ml-auto">
|
||||
<n-button @click="goToPreviousPage" :disabled="!continuationToken">
|
||||
<Icon name="ri:arrow-left-s-line" class="mr-2" />
|
||||
@@ -88,104 +66,103 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { $s3Client } = useNuxtApp();
|
||||
const { t } = useI18n();
|
||||
import { useAsyncData, useRoute, useRouter } from '#app';
|
||||
import { NuxtLink } from '#components';
|
||||
import { ListObjectsV2Command, type _Object, type CommonPrefix } from '@aws-sdk/client-s3';
|
||||
import dayjs from 'dayjs';
|
||||
import { NButton, NSpace, NPopconfirm, type DataTableColumns, type DataTableRowKey } from 'naive-ui';
|
||||
import { Icon } from '#components';
|
||||
import { joinRelativeURL } from 'ufo';
|
||||
import { computed, ref, watch, type VNode } from 'vue';
|
||||
import { useDeleteTaskManagerStore } from '~/store/delete-tasks';
|
||||
import { useUploadTaskManagerStore } from '~/store/upload-tasks';
|
||||
import { useBucket } from '~/composables/useBucket';
|
||||
import JSZip from 'jszip';
|
||||
import { saveAs } from 'file-saver';
|
||||
const { $s3Client } = useNuxtApp()
|
||||
const { t } = useI18n()
|
||||
import { useAsyncData, useRoute, useRouter } from '#app'
|
||||
import { Icon, NuxtLink } from '#components'
|
||||
import { ListObjectsV2Command, type _Object, type CommonPrefix } from '@aws-sdk/client-s3'
|
||||
import dayjs from 'dayjs'
|
||||
import { saveAs } from 'file-saver'
|
||||
import JSZip from 'jszip'
|
||||
import { NButton, NPopconfirm, NSpace, type DataTableColumns, type DataTableRowKey } from 'naive-ui'
|
||||
import { joinRelativeURL } from 'ufo'
|
||||
import { computed, ref, watch, type VNode } from 'vue'
|
||||
import { useBucket } from '~/composables/useBucket'
|
||||
import { useDeleteTaskManagerStore } from '~/store/delete-tasks'
|
||||
import { useUploadTaskManagerStore } from '~/store/upload-tasks'
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const props = defineProps<{ bucket: string; path: string }>();
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const dialog = useDialog()
|
||||
const message = useMessage()
|
||||
const props = defineProps<{ bucket: string; path: string }>()
|
||||
|
||||
const uploadPickerVisible = ref(false);
|
||||
const newObjectFormVisible = ref(false);
|
||||
const newObjectAsPrefix = ref(false);
|
||||
const searchTerm = ref('');
|
||||
const uploadPickerVisible = ref(false)
|
||||
const newObjectFormVisible = ref(false)
|
||||
const newObjectAsPrefix = ref(false)
|
||||
const searchTerm = ref('')
|
||||
|
||||
// Add debounce function for search
|
||||
const debounce = (fn: Function, delay: number) => {
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
let timer: NodeJS.Timeout | null = null
|
||||
return (...args: any[]) => {
|
||||
if (timer) clearTimeout(timer);
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
fn(...args);
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
fn(...args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = debounce(() => {
|
||||
// Reset pagination when searching
|
||||
if (continuationToken.value) {
|
||||
continuationToken.value = undefined;
|
||||
tokenHistory.value = [];
|
||||
continuationToken.value = undefined
|
||||
tokenHistory.value = []
|
||||
}
|
||||
}, 300);
|
||||
}, 300)
|
||||
|
||||
// 上传任务
|
||||
const uploadTaskStore = useUploadTaskManagerStore();
|
||||
const uploadTasks = computed(() => uploadTaskStore.tasks);
|
||||
const uploadTaskStore = useUploadTaskManagerStore()
|
||||
const uploadTasks = computed(() => uploadTaskStore.tasks)
|
||||
|
||||
// 删除任务
|
||||
const deleteTaskStore = useDeleteTaskManagerStore();
|
||||
const deleteTasks = computed(() => deleteTaskStore.tasks);
|
||||
const deleteTaskStore = useDeleteTaskManagerStore()
|
||||
const deleteTasks = computed(() => deleteTaskStore.tasks)
|
||||
|
||||
// 当任务变化时,刷新数据
|
||||
watch(
|
||||
() => uploadTasks,
|
||||
() => setTimeout(refresh, 500),
|
||||
{ deep: true }
|
||||
);
|
||||
)
|
||||
watch(
|
||||
() => deleteTasks,
|
||||
() => setTimeout(refresh, 500),
|
||||
{ deep: true }
|
||||
);
|
||||
)
|
||||
|
||||
// bucketName
|
||||
const bucketName = computed(() => props.bucket as string);
|
||||
const bucketName = computed(() => props.bucket as string)
|
||||
// 当前路径的前缀, example: '/folder1/folder2/'
|
||||
const prefix = computed(() => decodeURIComponent(props.path as string));
|
||||
const prefix = computed(() => decodeURIComponent(props.path as string))
|
||||
|
||||
// query 参数
|
||||
const pageSize = computed(() => parseInt(route.query.pageSize as string, 10));
|
||||
const pageSize = computed(() => parseInt(route.query.pageSize as string, 10))
|
||||
// 将 continuationToken 改为 ref
|
||||
const continuationToken = ref<string | undefined>(undefined);
|
||||
const continuationToken = ref<string | undefined>(undefined)
|
||||
|
||||
// 新建文件夹
|
||||
const handleNewObject = (asPrefix: boolean) => {
|
||||
newObjectFormVisible.value = true;
|
||||
newObjectAsPrefix.value = asPrefix;
|
||||
};
|
||||
newObjectFormVisible.value = true
|
||||
newObjectAsPrefix.value = asPrefix
|
||||
}
|
||||
|
||||
const bucketPath = (path?: string | Array<string>) => {
|
||||
if (Array.isArray(path)) {
|
||||
path = path.join('/');
|
||||
path = path.join('/')
|
||||
}
|
||||
|
||||
return joinRelativeURL('/browser', encodeURIComponent(bucketName.value), path ? encodeURIComponent(path) : '');
|
||||
};
|
||||
|
||||
interface RowData {
|
||||
Key: string;
|
||||
type: 'prefix' | 'object';
|
||||
Size: number;
|
||||
LastModified: string;
|
||||
return joinRelativeURL('/browser', encodeURIComponent(bucketName.value), path ? encodeURIComponent(path) : '')
|
||||
}
|
||||
|
||||
const infoRef = ref();
|
||||
interface RowData {
|
||||
Key: string
|
||||
type: 'prefix' | 'object'
|
||||
Size: number
|
||||
LastModified: string
|
||||
}
|
||||
|
||||
const infoRef = ref()
|
||||
const columns: DataTableColumns<RowData> = [
|
||||
{
|
||||
type: 'selection',
|
||||
@@ -194,27 +171,27 @@ const columns: DataTableColumns<RowData> = [
|
||||
key: 'Key',
|
||||
title: t('Object'),
|
||||
render: (row: { Key: string; type: 'prefix' | 'object' }) => {
|
||||
const displayKey = prefix.value ? row.Key.substring(prefix.value.length) : row.Key;
|
||||
let label: string | VNode = displayKey || '/';
|
||||
const displayKey = prefix.value ? row.Key.substring(prefix.value.length) : row.Key
|
||||
let label: string | VNode = displayKey || '/'
|
||||
|
||||
if (row.type === 'prefix') {
|
||||
label = h('span', { class: 'inline-flex items-center gap-2' }, [icon('ri:folder-line'), label]);
|
||||
label = h('span', { class: 'inline-flex items-center gap-2' }, [icon('ri:folder-line'), label])
|
||||
}
|
||||
|
||||
const keyInUri = row.Key;
|
||||
const keyInUri = row.Key
|
||||
return h(
|
||||
NuxtLink,
|
||||
{
|
||||
href: row.type === 'prefix' ? bucketPath(keyInUri) : '',
|
||||
class: 'block text-cyan-400 cursor-pointer',
|
||||
onClick: (e: MouseEvent) => {
|
||||
if (row.type === 'prefix') return;
|
||||
infoRef.value.openDrawer(bucketName.value, row.Key);
|
||||
return;
|
||||
if (row.type === 'prefix') return
|
||||
infoRef.value.openDrawer(bucketName.value, row.Key)
|
||||
return
|
||||
},
|
||||
},
|
||||
() => label
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -226,14 +203,14 @@ const columns: DataTableColumns<RowData> = [
|
||||
key: 'LastModified',
|
||||
title: t('Update Time'),
|
||||
render: (row: { LastModified: string }) => {
|
||||
return row.LastModified ? dayjs(row.LastModified).format('YYYY-MM-DD HH:mm:ss') : '';
|
||||
return row.LastModified ? dayjs(row.LastModified).format('YYYY-MM-DD HH:mm:ss') : ''
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('Actions'),
|
||||
key: 'actions',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
width: 250,
|
||||
render: (row: RowData) => {
|
||||
return h(
|
||||
NSpace,
|
||||
@@ -252,7 +229,7 @@ const columns: DataTableColumns<RowData> = [
|
||||
NButton,
|
||||
{ size: 'small', secondary: true },
|
||||
{
|
||||
default: () => '',
|
||||
default: () => t('Download'),
|
||||
icon: () => h(Icon, { name: 'ri:download-2-line' }),
|
||||
}
|
||||
),
|
||||
@@ -283,7 +260,7 @@ const columns: DataTableColumns<RowData> = [
|
||||
NButton,
|
||||
{ size: 'small', type: 'error', secondary: true },
|
||||
{
|
||||
default: () => '',
|
||||
default: () => t('Delete'),
|
||||
icon: () => h(Icon, { name: 'ri:delete-bin-5-line' }),
|
||||
}
|
||||
),
|
||||
@@ -291,26 +268,26 @@ const columns: DataTableColumns<RowData> = [
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
interface ListObjectsResponse {
|
||||
contents: _Object[];
|
||||
commonPrefixes: CommonPrefix[];
|
||||
nextContinuationToken: string | null;
|
||||
isTruncated: boolean;
|
||||
contents: _Object[]
|
||||
commonPrefixes: CommonPrefix[]
|
||||
nextContinuationToken: string | null
|
||||
isTruncated: boolean
|
||||
}
|
||||
const randomString = () => {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
let result = ''
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const salt = ref(randomString());
|
||||
return result
|
||||
}
|
||||
const salt = ref(randomString())
|
||||
// 在服务端获取数据
|
||||
const { data, refresh } = await useAsyncData<ListObjectsResponse>(
|
||||
`objectsData-${salt.value}&${prefix.value}&${pageSize.value}&${continuationToken.value}`,
|
||||
@@ -321,30 +298,30 @@ const { data, refresh } = await useAsyncData<ListObjectsResponse>(
|
||||
Delimiter: '/',
|
||||
Prefix: prefix.value || undefined,
|
||||
ContinuationToken: continuationToken.value,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await $s3Client.send(new ListObjectsV2Command(params));
|
||||
const result = await $s3Client.send(new ListObjectsV2Command(params))
|
||||
|
||||
return {
|
||||
contents: result.Contents || [],
|
||||
commonPrefixes: result.CommonPrefixes || [],
|
||||
nextContinuationToken: result.NextContinuationToken || null,
|
||||
isTruncated: result.IsTruncated ?? false,
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
watch(
|
||||
continuationToken,
|
||||
() => {
|
||||
refresh();
|
||||
refresh()
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
)
|
||||
|
||||
const contents = computed(() => data.value?.contents || []);
|
||||
const commonPrefixes = computed(() => data.value?.commonPrefixes || []);
|
||||
const nextToken = computed(() => data.value?.nextContinuationToken || null);
|
||||
const contents = computed(() => data.value?.contents || [])
|
||||
const commonPrefixes = computed(() => data.value?.commonPrefixes || [])
|
||||
const nextToken = computed(() => data.value?.nextContinuationToken || null)
|
||||
|
||||
const objects = computed(() => {
|
||||
return commonPrefixes.value
|
||||
@@ -353,7 +330,7 @@ const objects = computed(() => {
|
||||
Key: prefix.Prefix,
|
||||
type: 'prefix',
|
||||
Size: 0,
|
||||
};
|
||||
}
|
||||
})
|
||||
.concat(
|
||||
contents.value.map(object => {
|
||||
@@ -362,31 +339,31 @@ const objects = computed(() => {
|
||||
type: 'object',
|
||||
Size: object.Size ?? 0,
|
||||
LastModified: object.LastModified ?? new Date(0),
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
// New computed property to filter objects based on search term
|
||||
const filteredObjects = computed(() => {
|
||||
if (!searchTerm.value.trim()) {
|
||||
return objects.value;
|
||||
return objects.value
|
||||
}
|
||||
|
||||
const term = searchTerm.value.toLowerCase();
|
||||
const term = searchTerm.value.toLowerCase()
|
||||
return objects.value.filter(obj => {
|
||||
const displayKey = prefix.value ? obj.Key?.substring(prefix.value.length) : obj.Key;
|
||||
return displayKey?.toLowerCase().includes(term);
|
||||
});
|
||||
});
|
||||
const displayKey = prefix.value ? obj.Key?.substring(prefix.value.length) : obj.Key
|
||||
return displayKey?.toLowerCase().includes(term)
|
||||
})
|
||||
})
|
||||
|
||||
/** ************************************处理对象删除********************************* */
|
||||
// 处理对象被删除(从版本组件触发)
|
||||
const handleObjectDeleted = () => {
|
||||
// 刷新文件列表
|
||||
salt.value = randomString();
|
||||
refresh();
|
||||
};
|
||||
salt.value = randomString()
|
||||
refresh()
|
||||
}
|
||||
|
||||
/** ************************************删除所有版本********************************* */
|
||||
// 删除文件的所有版本或文件夹及其内容
|
||||
@@ -394,50 +371,50 @@ const handleDeleteAllVersions = async (row: RowData) => {
|
||||
try {
|
||||
if (row.type === 'object') {
|
||||
// 删除文件的所有版本
|
||||
const { deleteAllVersions } = useObject({ bucket: bucketName.value });
|
||||
await deleteAllVersions(row.Key);
|
||||
message.success(t('Delete All Versions Success'));
|
||||
const { deleteAllVersions } = useObject({ bucket: bucketName.value })
|
||||
await deleteAllVersions(row.Key)
|
||||
message.success(t('Delete All Versions Success'))
|
||||
} else if (row.type === 'prefix') {
|
||||
// 删除文件夹及其所有内容
|
||||
const { mapAllFiles } = useObject({ bucket: bucketName.value });
|
||||
const files: string[] = [];
|
||||
const { mapAllFiles } = useObject({ bucket: bucketName.value })
|
||||
const files: string[] = []
|
||||
|
||||
// 递归收集文件夹下的所有文件
|
||||
await mapAllFiles(bucketName.value, row.Key, (fileKey: string) => {
|
||||
files.push(fileKey);
|
||||
});
|
||||
files.push(fileKey)
|
||||
})
|
||||
|
||||
if (files.length > 0) {
|
||||
// 使用删除任务管理器删除所有文件
|
||||
deleteTaskStore.addKeys(files, bucketName.value);
|
||||
message.success(t('Delete task created'));
|
||||
deleteTaskStore.addKeys(files, bucketName.value)
|
||||
message.success(t('Delete task created'))
|
||||
} else {
|
||||
message.success(t('Delete Success'));
|
||||
message.success(t('Delete Success'))
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
salt.value = randomString();
|
||||
refresh();
|
||||
salt.value = randomString()
|
||||
refresh()
|
||||
} catch (error: any) {
|
||||
console.error('Delete failed:', error);
|
||||
message.error(t('Delete Failed'));
|
||||
console.error('Delete failed:', error)
|
||||
message.error(t('Delete Failed'))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** ************************************批量删除********************************* */
|
||||
function rowKey(row: any): string {
|
||||
return row.Key || '';
|
||||
return row.Key || ''
|
||||
}
|
||||
|
||||
const checkedKeys = ref<DataTableRowKey[]>([]);
|
||||
const checkedKeys = ref<DataTableRowKey[]>([])
|
||||
function handleCheck(keys: DataTableRowKey[]) {
|
||||
// 过滤掉 undefined
|
||||
checkedKeys.value = keys.filter((k): k is string => typeof k === 'string');
|
||||
return checkedKeys;
|
||||
checkedKeys.value = keys.filter((k): k is string => typeof k === 'string')
|
||||
return checkedKeys
|
||||
}
|
||||
const objectApi = useObject({ bucket: bucketName.value });
|
||||
const bucketApi = useBucket({});
|
||||
const objectApi = useObject({ bucket: bucketName.value })
|
||||
const bucketApi = useBucket({})
|
||||
|
||||
// 批量删除
|
||||
function handleBatchDelete() {
|
||||
@@ -448,33 +425,33 @@ function handleBatchDelete() {
|
||||
negativeText: t('Cancel'),
|
||||
onPositiveClick: async () => {
|
||||
if (!checkedKeys.value.length) {
|
||||
message.error(t('Please select at least one item'));
|
||||
return;
|
||||
message.error(t('Please select at least one item'))
|
||||
return
|
||||
}
|
||||
try {
|
||||
await Promise.all(
|
||||
checkedKeys.value.map(async item => {
|
||||
const findOne = objects.value.find(obj => obj.Key === item);
|
||||
const findOne = objects.value.find(obj => obj.Key === item)
|
||||
// 目录删除
|
||||
// 递归查询目录下的所有文件,然后删除
|
||||
if (findOne?.type === 'prefix' && findOne?.Key) {
|
||||
return await objectApi.mapAllFiles(bucketName.value, findOne.Key, (fileKey: string) => {
|
||||
deleteTaskStore.addKeys([fileKey], bucketName.value);
|
||||
});
|
||||
deleteTaskStore.addKeys([fileKey], bucketName.value)
|
||||
})
|
||||
}
|
||||
|
||||
return deleteTaskStore.addKeys([String(item)], bucketName.value);
|
||||
return deleteTaskStore.addKeys([String(item)], bucketName.value)
|
||||
})
|
||||
);
|
||||
)
|
||||
|
||||
message.success(t('Delete task created'));
|
||||
salt.value = randomString();
|
||||
refresh();
|
||||
message.success(t('Delete task created'))
|
||||
salt.value = randomString()
|
||||
refresh()
|
||||
} catch (error) {
|
||||
message.error(t('Delete Failed'));
|
||||
message.error(t('Delete Failed'))
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
/** ************************************批量删除********************************* */
|
||||
|
||||
@@ -483,106 +460,106 @@ function handleBatchDelete() {
|
||||
const handledownload = async (item: any) => {
|
||||
if (item.type === 'object') {
|
||||
// 单文件下载
|
||||
const msg = message.loading(t('Getting URL'));
|
||||
const url = await objectApi.getSignedUrl(item.Key);
|
||||
msg.destroy();
|
||||
window.open(url, '_blank');
|
||||
const msg = message.loading(t('Getting URL'))
|
||||
const url = await objectApi.getSignedUrl(item.Key)
|
||||
msg.destroy()
|
||||
window.open(url, '_blank')
|
||||
} else if (item.type === 'prefix') {
|
||||
// 文件夹下载
|
||||
const msg = message.loading(t('Preparing folder...'));
|
||||
const msg = message.loading(t('Preparing folder...'))
|
||||
// 递归获取所有文件
|
||||
const files: string[] = [];
|
||||
const files: string[] = []
|
||||
await objectApi.mapAllFiles(bucketName.value, item.Key, (fileKey: string) => {
|
||||
files.push(fileKey);
|
||||
});
|
||||
files.push(fileKey)
|
||||
})
|
||||
|
||||
if (files.length === 0) {
|
||||
msg.destroy();
|
||||
message.warning(t('Folder is empty'));
|
||||
return;
|
||||
msg.destroy()
|
||||
message.warning(t('Folder is empty'))
|
||||
return
|
||||
}
|
||||
|
||||
msg.destroy();
|
||||
const zip = new JSZip();
|
||||
msg.destroy()
|
||||
const zip = new JSZip()
|
||||
|
||||
// 批量获取文件内容并添加到 zip
|
||||
const downloadMsg = message.loading(t('Downloading files...'));
|
||||
const downloadMsg = message.loading(t('Downloading files...'))
|
||||
await Promise.all(
|
||||
files.map(async fileKey => {
|
||||
const url = await objectApi.getSignedUrl(fileKey);
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
const url = await objectApi.getSignedUrl(fileKey)
|
||||
const response = await fetch(url)
|
||||
const blob = await response.blob()
|
||||
// 保持相对路径
|
||||
zip.file(fileKey.substring(item.Key.length), blob);
|
||||
zip.file(fileKey.substring(item.Key.length), blob)
|
||||
})
|
||||
);
|
||||
downloadMsg.destroy();
|
||||
)
|
||||
downloadMsg.destroy()
|
||||
|
||||
// 生成 zip 并下载
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
saveAs(zipBlob, `${item.Key.replace(/\/$/, '') || 'folder'}.zip`);
|
||||
message.success(t('Download ready'));
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' })
|
||||
saveAs(zipBlob, `${item.Key.replace(/\/$/, '') || 'folder'}.zip`)
|
||||
message.success(t('Download ready'))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 批量下载
|
||||
const downloadMultiple = async () => {
|
||||
if (!checkedKeys.value.length) {
|
||||
message.warning(t('Please select at least one item'));
|
||||
return;
|
||||
message.warning(t('Please select at least one item'))
|
||||
return
|
||||
}
|
||||
// 收集文件 loading
|
||||
let collectMsg = message.loading(t('Collecting files'));
|
||||
let collectMsg = message.loading(t('Collecting files'))
|
||||
// 1. 找到所有选中的对象
|
||||
const selectedItems = objects.value.filter(obj => checkedKeys.value.includes(obj.Key as string));
|
||||
const selectedItems = objects.value.filter(obj => checkedKeys.value.includes(obj.Key as string))
|
||||
// 2. 递归收集所有文件
|
||||
let allFiles: { key: string; relative: string }[] = [];
|
||||
let allFiles: { key: string; relative: string }[] = []
|
||||
for (const item of selectedItems) {
|
||||
if (item.type === 'object') {
|
||||
const key = item.Key || '';
|
||||
const rel = prefix.value ? key.substring(prefix.value.length) : key;
|
||||
allFiles.push({ key, relative: rel });
|
||||
const key = item.Key || ''
|
||||
const rel = prefix.value ? key.substring(prefix.value.length) : key
|
||||
allFiles.push({ key, relative: rel })
|
||||
} else if (item.type === 'prefix') {
|
||||
const key = item.Key || '';
|
||||
const key = item.Key || ''
|
||||
await objectApi.mapAllFiles(bucketName.value, key, fileKey => {
|
||||
const rel = prefix.value ? fileKey.substring(prefix.value.length) : fileKey;
|
||||
allFiles.push({ key: fileKey, relative: rel });
|
||||
});
|
||||
const rel = prefix.value ? fileKey.substring(prefix.value.length) : fileKey
|
||||
allFiles.push({ key: fileKey, relative: rel })
|
||||
})
|
||||
}
|
||||
}
|
||||
collectMsg.destroy();
|
||||
collectMsg.destroy()
|
||||
if (allFiles.length === 0) {
|
||||
message.warning(t('No files to download'));
|
||||
return;
|
||||
message.warning(t('No files to download'))
|
||||
return
|
||||
}
|
||||
// 下载文件 loading,带进度
|
||||
let percent = ref(0);
|
||||
const total = allFiles.length;
|
||||
let finished = 0;
|
||||
let downloadMsg: any = null;
|
||||
let percent = ref(0)
|
||||
const total = allFiles.length
|
||||
let finished = 0
|
||||
let downloadMsg: any = null
|
||||
const updateDownloadMsg = () => {
|
||||
if (downloadMsg) downloadMsg.destroy();
|
||||
if (downloadMsg) downloadMsg.destroy()
|
||||
downloadMsg = message.loading(`${t('Downloading files')} ${Math.round((finished / total) * 100)}%`, {
|
||||
duration: 0,
|
||||
});
|
||||
};
|
||||
updateDownloadMsg();
|
||||
const zip = new JSZip();
|
||||
})
|
||||
}
|
||||
updateDownloadMsg()
|
||||
const zip = new JSZip()
|
||||
await Promise.all(
|
||||
allFiles.map(async ({ key, relative }) => {
|
||||
const url = await objectApi.getSignedUrl(key);
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
zip.file(relative, blob);
|
||||
finished++;
|
||||
updateDownloadMsg();
|
||||
const url = await objectApi.getSignedUrl(key)
|
||||
const response = await fetch(url)
|
||||
const blob = await response.blob()
|
||||
zip.file(relative, blob)
|
||||
finished++
|
||||
updateDownloadMsg()
|
||||
})
|
||||
);
|
||||
if (downloadMsg) downloadMsg.destroy();
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
saveAs(zipBlob, `download.zip`);
|
||||
message.success(t('Download ready'));
|
||||
};
|
||||
)
|
||||
if (downloadMsg) downloadMsg.destroy()
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' })
|
||||
saveAs(zipBlob, `download.zip`)
|
||||
message.success(t('Download ready'))
|
||||
}
|
||||
/** ************************************下载********************************* */
|
||||
|
||||
// 为了实现 "Previous" 功能,需要记录访问过的 token 列表。
|
||||
@@ -592,40 +569,40 @@ const downloadMultiple = async () => {
|
||||
// 因此"上一页"可以通过浏览器后退实现,也可以在数据中保存 token 来人工实现。
|
||||
// 这里演示一个简化版本——在客户端保存 tokenHistory。
|
||||
// 请注意:刷新后 tokenHistory 会丢失,因为它是前端状态。
|
||||
const tokenHistory = ref<string[]>([]);
|
||||
const tokenHistory = ref<string[]>([])
|
||||
|
||||
// 当页面加载后,如果 continuationToken 有值,就表示不是第一页
|
||||
// 将当前 continuationToken 添加到历史中
|
||||
if (continuationToken.value && !tokenHistory.value.includes(continuationToken.value)) {
|
||||
tokenHistory.value.push(continuationToken.value);
|
||||
tokenHistory.value.push(continuationToken.value)
|
||||
}
|
||||
|
||||
// previousToken 根据 tokenHistory 来确定
|
||||
// tokenHistory 中最后一个是当前页的 token,上一个则是 previousToken
|
||||
const previousToken = computed(() => {
|
||||
if (tokenHistory.value.length < 2) return null;
|
||||
if (tokenHistory.value.length < 2) return null
|
||||
// 倒数第二个是上一页的 token
|
||||
return tokenHistory.value[tokenHistory.value.length - 2];
|
||||
});
|
||||
return tokenHistory.value[tokenHistory.value.length - 2]
|
||||
})
|
||||
|
||||
// 修改分页方法
|
||||
function goToNextPage() {
|
||||
if (nextToken.value) {
|
||||
continuationToken.value = nextToken.value;
|
||||
tokenHistory.value.push(nextToken.value);
|
||||
continuationToken.value = nextToken.value
|
||||
tokenHistory.value.push(nextToken.value)
|
||||
}
|
||||
}
|
||||
|
||||
function goToPreviousPage() {
|
||||
if (previousToken.value) {
|
||||
continuationToken.value = previousToken.value;
|
||||
continuationToken.value = previousToken.value
|
||||
// 将上一页 token 之后的记录删除
|
||||
const prevIndex = tokenHistory.value.indexOf(previousToken.value);
|
||||
tokenHistory.value.splice(prevIndex + 1);
|
||||
const prevIndex = tokenHistory.value.indexOf(previousToken.value)
|
||||
tokenHistory.value.splice(prevIndex + 1)
|
||||
} else {
|
||||
// 回到第一页
|
||||
continuationToken.value = undefined;
|
||||
tokenHistory.value = [];
|
||||
continuationToken.value = undefined
|
||||
tokenHistory.value = []
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<div
|
||||
class="p-4 sticky top-0 z-10 px-6 h-16 border-b dark:border-neutral-800 flex justify-between bg-gray-100 dark:bg-neutral-900"
|
||||
>
|
||||
<div class="p-4 sticky top-0 z-10 px-6 h-16 border-b bg-background flex justify-between ">
|
||||
<slot name="title"></slot>
|
||||
<slot name="actions"></slot>
|
||||
</div>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<template #icon>
|
||||
<Icon name="ri:delete-bin-5-line"></Icon>
|
||||
</template>
|
||||
删除选中项
|
||||
</NButton> -->
|
||||
删除选中项
|
||||
</NButton> -->
|
||||
<NButton :disabled="!checkedKeys.length" secondary @click="allocationPolicy">
|
||||
<template #icon>
|
||||
<Icon name="ri:group-2-fill"></Icon>
|
||||
@@ -29,27 +29,16 @@
|
||||
</n-space>
|
||||
</n-flex>
|
||||
</n-form>
|
||||
<n-data-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:data="listData"
|
||||
:pagination="false"
|
||||
:bordered="true"
|
||||
max-height="calc(100vh - 320px)"
|
||||
:row-key="rowKey"
|
||||
@update:checked-row-keys="handleCheck"
|
||||
/>
|
||||
<n-data-table ref="tableRef" :columns="columns" :data="listData" :pagination="false" :bordered="true" max-height="calc(100vh - 320px)" :row-key="rowKey"
|
||||
@update:checked-row-keys="handleCheck" />
|
||||
<users-group-edit ref="editItemRef"></users-group-edit>
|
||||
<users-group-new v-model:visible="newItemVisible" @search="getDataList" ref="newItemRef"></users-group-new>
|
||||
<users-group-set-policies-mutiple
|
||||
:checkedKeys="checkedKeys"
|
||||
@changePoliciesSuccess="changePoliciesSuccess"
|
||||
ref="policiesRef"
|
||||
></users-group-set-policies-mutiple>
|
||||
<users-group-set-policies-mutiple :checkedKeys="checkedKeys" @changePoliciesSuccess="changePoliciesSuccess" ref="policiesRef"></users-group-set-policies-mutiple>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '#components'
|
||||
import {
|
||||
type DataTableColumns,
|
||||
type DataTableInst,
|
||||
@@ -57,24 +46,23 @@ import {
|
||||
NButton,
|
||||
NPopconfirm,
|
||||
NSpace,
|
||||
} from 'naive-ui';
|
||||
import { Icon } from '#components';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
} from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
// import { groupEdit, newGroup, setPoliciesMutiple } from '../components';
|
||||
|
||||
const messge = useMessage();
|
||||
const { t } = useI18n();
|
||||
const messge = useMessage()
|
||||
const { t } = useI18n()
|
||||
|
||||
const { $api } = useNuxtApp();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const group = useGroups();
|
||||
const { $api } = useNuxtApp()
|
||||
const dialog = useDialog()
|
||||
const message = useMessage()
|
||||
const group = useGroups()
|
||||
|
||||
const searchForm = reactive({
|
||||
name: '',
|
||||
});
|
||||
})
|
||||
interface RowData {
|
||||
name: string;
|
||||
name: string
|
||||
}
|
||||
|
||||
const columns: DataTableColumns<RowData> = [
|
||||
@@ -86,7 +74,7 @@ const columns: DataTableColumns<RowData> = [
|
||||
align: 'left',
|
||||
key: 'name',
|
||||
filter(value, row) {
|
||||
return !!row.name.includes(value.toString());
|
||||
return !!row.name.includes(value.toString())
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -110,7 +98,7 @@ const columns: DataTableColumns<RowData> = [
|
||||
onClick: () => openEditItem(row),
|
||||
},
|
||||
{
|
||||
default: () => '',
|
||||
default: () => t('Edit'),
|
||||
icon: () => h(Icon, { name: 'ri:edit-2-line' }),
|
||||
}
|
||||
),
|
||||
@@ -124,7 +112,7 @@ const columns: DataTableColumns<RowData> = [
|
||||
NButton,
|
||||
{ size: 'small', secondary: true },
|
||||
{
|
||||
default: () => '',
|
||||
default: () => t('Delete'),
|
||||
icon: () => h(Icon, { name: 'ri:delete-bin-5-line' }),
|
||||
}
|
||||
),
|
||||
@@ -132,71 +120,71 @@ const columns: DataTableColumns<RowData> = [
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
// 搜索过滤
|
||||
const tableRef = ref<DataTableInst>();
|
||||
const tableRef = ref<DataTableInst>()
|
||||
function filterName(value: string) {
|
||||
tableRef.value &&
|
||||
tableRef.value.filter({
|
||||
name: [value],
|
||||
});
|
||||
})
|
||||
}
|
||||
const listData = ref<any[]>([]);
|
||||
const listData = ref<any[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
getDataList()
|
||||
})
|
||||
// 获取数据
|
||||
const getDataList = async () => {
|
||||
try {
|
||||
const res = await group.listGroup();
|
||||
const res = await group.listGroup()
|
||||
listData.value =
|
||||
res?.map((item: string) => {
|
||||
return {
|
||||
name: item,
|
||||
};
|
||||
}) || [];
|
||||
checkedKeys.value = [];
|
||||
}
|
||||
}) || []
|
||||
checkedKeys.value = []
|
||||
} catch (error) {
|
||||
message.error(t('Failed to get data'));
|
||||
message.error(t('Failed to get data'))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** **********************************添加 */
|
||||
const newItemRef = ref();
|
||||
const newItemVisible = ref(false);
|
||||
const newItemRef = ref()
|
||||
const newItemVisible = ref(false)
|
||||
|
||||
function addUserGroup() {
|
||||
newItemVisible.value = true;
|
||||
newItemVisible.value = true
|
||||
}
|
||||
|
||||
/** **********************************分配策略 */
|
||||
const policiesRef = ref();
|
||||
const policiesRef = ref()
|
||||
const allocationPolicy = () => {
|
||||
policiesRef.value.openDialog();
|
||||
};
|
||||
policiesRef.value.openDialog()
|
||||
}
|
||||
|
||||
const changePoliciesSuccess = () => {
|
||||
checkedKeys.value = [];
|
||||
};
|
||||
checkedKeys.value = []
|
||||
}
|
||||
/** **********************************修改 */
|
||||
const editItemRef = ref();
|
||||
const editItemRef = ref()
|
||||
function openEditItem(row: any) {
|
||||
editItemRef.value.openDialog(row);
|
||||
editItemRef.value.openDialog(row)
|
||||
}
|
||||
|
||||
/** ***********************************删除 */
|
||||
async function deleteItem(row: any) {
|
||||
try {
|
||||
// 获取组的成员
|
||||
const info = await group.getGroup(row.name);
|
||||
const info = await group.getGroup(row.name)
|
||||
if (info.members.length) {
|
||||
messge.error('请先清空组成员');
|
||||
return;
|
||||
messge.error('请先清空组成员')
|
||||
return
|
||||
}
|
||||
// 清空组的成员
|
||||
await group.updateGroupMembers({
|
||||
@@ -204,23 +192,23 @@ async function deleteItem(row: any) {
|
||||
members: info.members,
|
||||
isRemove: true,
|
||||
groupStatus: 'enabled',
|
||||
});
|
||||
message.success(t('Delete Success'));
|
||||
getDataList();
|
||||
})
|
||||
message.success(t('Delete Success'))
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
message.error(t('Delete Failed'));
|
||||
message.error(t('Delete Failed'))
|
||||
}
|
||||
}
|
||||
|
||||
/** ************************************批量删除 */
|
||||
function rowKey(row: any): string {
|
||||
return row.name;
|
||||
return row.name
|
||||
}
|
||||
|
||||
const checkedKeys = ref<DataTableRowKey[]>([]);
|
||||
const checkedKeys = ref<DataTableRowKey[]>([])
|
||||
function handleCheck(keys: DataTableRowKey[]) {
|
||||
checkedKeys.value = keys;
|
||||
return checkedKeys;
|
||||
checkedKeys.value = keys
|
||||
return checkedKeys
|
||||
}
|
||||
function deleteByList() {
|
||||
dialog.error({
|
||||
@@ -230,16 +218,16 @@ function deleteByList() {
|
||||
negativeText: t('Cancel'),
|
||||
onPositiveClick: () => {
|
||||
if (!checkedKeys.value.length) {
|
||||
message.error(t('Please select at least one item'));
|
||||
return;
|
||||
message.error(t('Please select at least one item'))
|
||||
return
|
||||
}
|
||||
checkedKeys.value.forEach(async (element: any) => {
|
||||
const res = await group.removeGroup(element);
|
||||
});
|
||||
const res = await group.removeGroup(element)
|
||||
})
|
||||
|
||||
getDataList();
|
||||
getDataList()
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -29,21 +29,14 @@
|
||||
</n-space>
|
||||
</n-flex>
|
||||
</n-form>
|
||||
<n-data-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:data="listData"
|
||||
:pagination="false"
|
||||
:bordered="true"
|
||||
:row-key="rowKey"
|
||||
@update:checked-row-keys="handleCheck"
|
||||
/>
|
||||
<n-data-table ref="tableRef" :columns="columns" :data="listData" :pagination="false" :bordered="true" :row-key="rowKey" @update:checked-row-keys="handleCheck" />
|
||||
<users-user-new @search="getDataList" ref="newItemRef"></users-user-new>
|
||||
<users-user-edit @search="getDataList" :checkedKeys="checkedKeys" ref="editItemRef"></users-user-edit>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '#components'
|
||||
import {
|
||||
type DataTableColumns,
|
||||
type DataTableInst,
|
||||
@@ -51,22 +44,21 @@ import {
|
||||
NButton,
|
||||
NPopconfirm,
|
||||
NSpace,
|
||||
} from 'naive-ui';
|
||||
import { Icon } from '#components';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
} from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n()
|
||||
|
||||
const { $api } = useNuxtApp();
|
||||
const { listUsers, deleteUser } = useUsers();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const { $api } = useNuxtApp()
|
||||
const { listUsers, deleteUser } = useUsers()
|
||||
const dialog = useDialog()
|
||||
const message = useMessage()
|
||||
|
||||
const searchForm = reactive({
|
||||
accessKey: '',
|
||||
});
|
||||
})
|
||||
interface RowData {
|
||||
accessKey: string;
|
||||
accessKey: string
|
||||
}
|
||||
|
||||
const columns: DataTableColumns<RowData> = [
|
||||
@@ -78,7 +70,7 @@ const columns: DataTableColumns<RowData> = [
|
||||
align: 'left',
|
||||
key: 'accessKey',
|
||||
filter(value, row) {
|
||||
return !!row.accessKey.includes(value.toString());
|
||||
return !!row.accessKey.includes(value.toString())
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -102,7 +94,7 @@ const columns: DataTableColumns<RowData> = [
|
||||
onClick: () => openEditItem(row),
|
||||
},
|
||||
{
|
||||
default: () => '',
|
||||
default: () => t('Edit'),
|
||||
icon: () => h(Icon, { name: 'ri:edit-2-line' }),
|
||||
}
|
||||
),
|
||||
@@ -116,7 +108,7 @@ const columns: DataTableColumns<RowData> = [
|
||||
NButton,
|
||||
{ size: 'small', secondary: true },
|
||||
{
|
||||
default: () => '',
|
||||
default: () => t('Delete'),
|
||||
icon: () => h(Icon, { name: 'ri:delete-bin-5-line' }),
|
||||
}
|
||||
),
|
||||
@@ -124,74 +116,74 @@ const columns: DataTableColumns<RowData> = [
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
)
|
||||
},
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
// 搜索过滤
|
||||
const tableRef = ref<DataTableInst>();
|
||||
const tableRef = ref<DataTableInst>()
|
||||
function filterName(value: string) {
|
||||
tableRef.value &&
|
||||
tableRef.value.filter({
|
||||
accessKey: [value],
|
||||
});
|
||||
})
|
||||
}
|
||||
const listData = ref<any[]>([]);
|
||||
const listData = ref<any[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
getDataList()
|
||||
})
|
||||
|
||||
// 获取数据
|
||||
const getDataList = async () => {
|
||||
try {
|
||||
const res = await listUsers();
|
||||
const res = await listUsers()
|
||||
|
||||
listData.value = Object.entries(res).map(([username, info]) => ({
|
||||
accessKey: username, // 添加用户名
|
||||
...(typeof info === 'object' ? info : {}), // 展开用户信息
|
||||
}));
|
||||
}))
|
||||
} catch (error) {
|
||||
message.error(t('Failed to get data'));
|
||||
message.error(t('Failed to get data'))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** **********************************添加 */
|
||||
const newItemRef = ref();
|
||||
const newItemRef = ref()
|
||||
|
||||
function addUserItem() {
|
||||
newItemRef.value.openDialog();
|
||||
newItemRef.value.openDialog()
|
||||
}
|
||||
|
||||
/** **********************************添加到的分组 */
|
||||
const addToGroup = () => {};
|
||||
const addToGroup = () => { }
|
||||
|
||||
/** **********************************修改 */
|
||||
const editItemRef = ref();
|
||||
const editItemRef = ref()
|
||||
function openEditItem(row: any) {
|
||||
editItemRef.value.openDialog(row);
|
||||
editItemRef.value.openDialog(row)
|
||||
}
|
||||
/** ***********************************删除 */
|
||||
async function deleteItem(row: any) {
|
||||
try {
|
||||
const res = await deleteUser(row.accessKey);
|
||||
message.success(t('Delete Success'));
|
||||
getDataList();
|
||||
const res = await deleteUser(row.accessKey)
|
||||
message.success(t('Delete Success'))
|
||||
getDataList()
|
||||
} catch (error) {
|
||||
message.error(t('Delete Failed'));
|
||||
message.error(t('Delete Failed'))
|
||||
}
|
||||
}
|
||||
|
||||
/** ************************************批量删除 */
|
||||
function rowKey(row: any): string {
|
||||
return row.accessKey;
|
||||
return row.accessKey
|
||||
}
|
||||
|
||||
const checkedKeys = ref<DataTableRowKey[]>([]);
|
||||
const checkedKeys = ref<DataTableRowKey[]>([])
|
||||
function handleCheck(keys: DataTableRowKey[]) {
|
||||
checkedKeys.value = keys;
|
||||
return checkedKeys;
|
||||
checkedKeys.value = keys
|
||||
return checkedKeys
|
||||
}
|
||||
function deleteByList() {
|
||||
dialog.error({
|
||||
@@ -201,22 +193,22 @@ function deleteByList() {
|
||||
negativeText: t('Cancel'),
|
||||
onPositiveClick: async () => {
|
||||
if (!checkedKeys.value.length) {
|
||||
message.error(t('Please select at least one item'));
|
||||
return;
|
||||
message.error(t('Please select at least one item'))
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 循环遍历删除
|
||||
await Promise.all(checkedKeys.value.map(item => deleteUser(item as string)));
|
||||
checkedKeys.value = [];
|
||||
message.success(t('Delete Success'));
|
||||
await Promise.all(checkedKeys.value.map(item => deleteUser(item as string)))
|
||||
checkedKeys.value = []
|
||||
message.success(t('Delete Success'))
|
||||
nextTick(() => {
|
||||
getDataList();
|
||||
});
|
||||
getDataList()
|
||||
})
|
||||
} catch (error) {
|
||||
message.error(t('Delete Failed'));
|
||||
message.error(t('Delete Failed'))
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user