feat:带宽限速界面(已联调)
This commit is contained in:
366
src/views/billing/ratelimit/index.vue
Normal file
366
src/views/billing/ratelimit/index.vue
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
<template>
|
||||||
|
<SimpleScrollbar>
|
||||||
|
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
|
||||||
|
<ACard
|
||||||
|
title="限速管理"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ flex: 1, overflow: 'hidden' }"
|
||||||
|
class="flex-col-stretch sm:flex-1-hidden card-wrapper"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<AButton type="primary" @click="handleAdd">
|
||||||
|
<template #icon>
|
||||||
|
<PlusOutlined />
|
||||||
|
</template>
|
||||||
|
新增
|
||||||
|
</AButton>
|
||||||
|
<TableHeaderOperation
|
||||||
|
v-model:columns="columnChecks"
|
||||||
|
:loading="loading"
|
||||||
|
:show-delete="false"
|
||||||
|
:show-add="false"
|
||||||
|
@refresh="getData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<ATable
|
||||||
|
ref="wrapperEl"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="data"
|
||||||
|
:loading="loading"
|
||||||
|
row-key="rateLimitName"
|
||||||
|
size="small"
|
||||||
|
:pagination="mobilePagination"
|
||||||
|
:scroll="scrollConfig"
|
||||||
|
class="h-full"
|
||||||
|
/>
|
||||||
|
</ACard>
|
||||||
|
|
||||||
|
<!-- 新增限速配置弹窗 -->
|
||||||
|
<AModal
|
||||||
|
v-model:visible="showModal"
|
||||||
|
:title="isEdit ? '修改限速配置' : '新增限速配置'"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<AForm
|
||||||
|
ref="formRef"
|
||||||
|
:model="formState"
|
||||||
|
:rules="rules"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
>
|
||||||
|
<AFormItem label="限速名称" name="rateLimitName">
|
||||||
|
<AInput v-model:value="formState.rateLimitName" placeholder="请输入限速名称" />
|
||||||
|
</AFormItem>
|
||||||
|
|
||||||
|
<AFormItem label="上行限速" name="upLimitEnable">
|
||||||
|
<ACheckbox v-model:checked="formState.upLimitEnable">启用上行限速</ACheckbox>
|
||||||
|
</AFormItem>
|
||||||
|
|
||||||
|
<AFormItem
|
||||||
|
v-if="formState.upLimitEnable"
|
||||||
|
label="上行带宽"
|
||||||
|
name="upLimit"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<AFormItem name="upLimit" class="mb-0 flex-1">
|
||||||
|
<AInputNumber
|
||||||
|
v-model:value="formState.upLimit"
|
||||||
|
placeholder="请输入上行带宽"
|
||||||
|
:min="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</AFormItem>
|
||||||
|
<AFormItem name="upLimitUnit" class="mb-0">
|
||||||
|
<ASelect
|
||||||
|
v-model:value="formState.upLimitUnit"
|
||||||
|
:options="bandwidthUnits.map(unit => ({ label: unit, value: unit }))"
|
||||||
|
style="width: 100px"
|
||||||
|
/>
|
||||||
|
</AFormItem>
|
||||||
|
</div>
|
||||||
|
</AFormItem>
|
||||||
|
|
||||||
|
<AFormItem label="下行限速" name="downLimitEnable">
|
||||||
|
<ACheckbox v-model:checked="formState.downLimitEnable">启用下行限速</ACheckbox>
|
||||||
|
</AFormItem>
|
||||||
|
|
||||||
|
<AFormItem
|
||||||
|
v-if="formState.downLimitEnable"
|
||||||
|
label="下行带宽"
|
||||||
|
name="downLimit"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<AFormItem name="downLimit" class="mb-0 flex-1">
|
||||||
|
<AInputNumber
|
||||||
|
v-model:value="formState.downLimit"
|
||||||
|
placeholder="请输入下行带宽"
|
||||||
|
:min="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</AFormItem>
|
||||||
|
<AFormItem name="downLimitUnit" class="mb-0">
|
||||||
|
<ASelect
|
||||||
|
v-model:value="formState.downLimitUnit"
|
||||||
|
:options="bandwidthUnits.map(unit => ({ label: unit, value: unit }))"
|
||||||
|
style="width: 100px"
|
||||||
|
/>
|
||||||
|
</AFormItem>
|
||||||
|
</div>
|
||||||
|
</AFormItem>
|
||||||
|
</AForm>
|
||||||
|
</AModal>
|
||||||
|
</div>
|
||||||
|
</SimpleScrollbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="tsx">
|
||||||
|
import { useTable } from '@/hooks/common/table';
|
||||||
|
import { SimpleScrollbar } from '~/packages/materials/src';
|
||||||
|
import { computed, shallowRef, ref } from 'vue';
|
||||||
|
import { useElementSize } from '@vueuse/core';
|
||||||
|
import { fetchRateLimitList, addRateLimit, editRateLimit, removeRateLimit } from '@/service/api/auth';
|
||||||
|
import { message, Modal, Button } from 'ant-design-vue';
|
||||||
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
|
import { PlusOutlined, ExclamationCircleOutlined,DeleteOutlined,FormOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { bandwidthUnits, convertBandwidth, formatBandwidth, type BandwidthUnit } from '@/utils/units';
|
||||||
|
|
||||||
|
interface RateLimitForm extends Omit<Api.Auth.RateLimitAdd, 'upLimit' | 'downLimit'> {
|
||||||
|
upLimit: number;
|
||||||
|
downLimit: number;
|
||||||
|
upLimitUnit: BandwidthUnit;
|
||||||
|
downLimitUnit: BandwidthUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapperEl = shallowRef<HTMLElement | null>(null);
|
||||||
|
const { height: wrapperElHeight } = useElementSize(wrapperEl);
|
||||||
|
|
||||||
|
const scrollConfig = computed(() => {
|
||||||
|
return {
|
||||||
|
y: wrapperElHeight.value - 72,
|
||||||
|
x: 1000
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const showModal = ref(false);
|
||||||
|
const formRef = ref();
|
||||||
|
const formState = ref<RateLimitForm>({
|
||||||
|
rateLimitName: '',
|
||||||
|
upLimitEnable: false,
|
||||||
|
downLimitEnable: false,
|
||||||
|
upLimit: 0,
|
||||||
|
downLimit: 0,
|
||||||
|
upLimitUnit: 'Mbps',
|
||||||
|
downLimitUnit: 'Mbps'
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules: Record<string, Rule[]> = {
|
||||||
|
rateLimitName: [{ required: true, message: '请输入限速名称', trigger: 'blur' }],
|
||||||
|
upLimit: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入上行限速带宽',
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: (_rule: Rule, value: number) => {
|
||||||
|
if (!formState.value.upLimitEnable) return Promise.resolve();
|
||||||
|
if (!value || value <= 0) return Promise.reject('请输入大于0的带宽值');
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
downLimit: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入下行限速带宽',
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: (_rule: Rule, value: number) => {
|
||||||
|
if (!formState.value.downLimitEnable) return Promise.resolve();
|
||||||
|
if (!value || value <= 0) return Promise.reject('请输入大于0的带宽值');
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const editId = ref<number>();
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
isEdit.value = false;
|
||||||
|
editId.value = undefined;
|
||||||
|
formState.value = {
|
||||||
|
rateLimitName: '',
|
||||||
|
upLimitEnable: false,
|
||||||
|
downLimitEnable: false,
|
||||||
|
upLimit: 0,
|
||||||
|
downLimit: 0,
|
||||||
|
upLimitUnit: 'Mbps',
|
||||||
|
downLimitUnit: 'Mbps'
|
||||||
|
};
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (record: Api.Auth.RateLimit) => {
|
||||||
|
isEdit.value = true;
|
||||||
|
editId.value = record.id;
|
||||||
|
|
||||||
|
formState.value = {
|
||||||
|
rateLimitName: record.rateLimitName,
|
||||||
|
upLimitEnable: record.upLimitEnable,
|
||||||
|
downLimitEnable: record.downLimitEnable,
|
||||||
|
upLimit: convertBandwidth(record.upLimit, 'Kbps', 'Mbps'),
|
||||||
|
downLimit: convertBandwidth(record.downLimit, 'Kbps', 'Mbps'),
|
||||||
|
upLimitUnit: 'Mbps',
|
||||||
|
downLimitUnit: 'Mbps'
|
||||||
|
};
|
||||||
|
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (record: Api.Auth.RateLimit) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
icon: () => <ExclamationCircleOutlined />,
|
||||||
|
content: `确定要删除限速配置 "${record.rateLimitName}" 吗?`,
|
||||||
|
async onOk() {
|
||||||
|
try {
|
||||||
|
await removeRateLimit(record.id);
|
||||||
|
message.success('删除成功');
|
||||||
|
getData();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
showModal.value = false;
|
||||||
|
formState.value = {
|
||||||
|
rateLimitName: '',
|
||||||
|
upLimitEnable: false,
|
||||||
|
downLimitEnable: false,
|
||||||
|
upLimit: 0,
|
||||||
|
downLimit: 0,
|
||||||
|
upLimitUnit: 'Mbps',
|
||||||
|
downLimitUnit: 'Mbps'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
const submitData = {
|
||||||
|
...formState.value,
|
||||||
|
upLimit: formState.value.upLimitEnable
|
||||||
|
? Math.round(convertBandwidth(formState.value.upLimit, formState.value.upLimitUnit, 'Kbps'))
|
||||||
|
: 0,
|
||||||
|
downLimit: formState.value.downLimitEnable
|
||||||
|
? Math.round(convertBandwidth(formState.value.downLimit, formState.value.downLimitUnit, 'Kbps'))
|
||||||
|
: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
if (!editId.value) {
|
||||||
|
message.error('编辑ID不能为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await editRateLimit({
|
||||||
|
id: editId.value,
|
||||||
|
rateLimitName: submitData.rateLimitName,
|
||||||
|
upLimitEnable: submitData.upLimitEnable,
|
||||||
|
downLimitEnable: submitData.downLimitEnable,
|
||||||
|
upLimit: submitData.upLimit,
|
||||||
|
downLimit: submitData.downLimit
|
||||||
|
});
|
||||||
|
message.success('修改成功');
|
||||||
|
} else {
|
||||||
|
await addRateLimit(submitData);
|
||||||
|
message.success('添加成功');
|
||||||
|
}
|
||||||
|
handleCancel();
|
||||||
|
getData();
|
||||||
|
} catch (error) {
|
||||||
|
message.error(isEdit.value ? '修改失败' : '添加失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
columns,
|
||||||
|
columnChecks,
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
getData,
|
||||||
|
mobilePagination
|
||||||
|
} = useTable({
|
||||||
|
apiFn: async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetchRateLimitList();
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
rows: response.data || [],
|
||||||
|
total: response.data?.length || 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
rows: [],
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: true,
|
||||||
|
rowKey: 'id',
|
||||||
|
columns: (): AntDesign.TableColumn<Api.Auth.RateLimit>[] => [
|
||||||
|
{
|
||||||
|
key: 'rateLimitName',
|
||||||
|
dataIndex: 'rateLimitName',
|
||||||
|
title: '限速名称',
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'upLimit',
|
||||||
|
dataIndex: 'upLimit',
|
||||||
|
title: '上行限速',
|
||||||
|
align: 'center',
|
||||||
|
customRender: ({ text }) => {
|
||||||
|
const { value, unit } = formatBandwidth(text);
|
||||||
|
return `${value} ${unit}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'downLimit',
|
||||||
|
dataIndex: 'downLimit',
|
||||||
|
title: '下行限速',
|
||||||
|
align: 'center',
|
||||||
|
customRender: ({ text }) => {
|
||||||
|
const { value, unit } = formatBandwidth(text);
|
||||||
|
return `${value} ${unit}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'operate',
|
||||||
|
align: 'center',
|
||||||
|
width: 200,
|
||||||
|
fixed: 'right',
|
||||||
|
customRender: ({ record }) => (
|
||||||
|
<div class="flex justify-center gap-2">
|
||||||
|
<Button type="link" onClick={() => handleEdit(record)}>
|
||||||
|
<FormOutlined />
|
||||||
|
</Button>
|
||||||
|
<Button type="link" danger onClick={() => handleDelete(record)}>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
Reference in New Issue
Block a user