feat: 7月迭代
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m21s

This commit is contained in:
luo
2026-07-28 14:00:22 +08:00
parent f0a2b84e53
commit 2706c52a47
63 changed files with 4113 additions and 3945 deletions

View File

@@ -10,7 +10,11 @@
</div>
<div class="card-header-right">
<ElButton
v-if="cardInfo?.asset_type === 'card' && hasAuth(JULY_PERMISSIONS.speedTier.view)"
v-if="
cardInfo?.asset_type === 'card' &&
hasAuth(JULY_PERMISSIONS.speedTier.view) &&
hasAuth(JULY_PERMISSIONS.speedTier.update)
"
type="primary"
link
@click="emit('showSpeedLimit')"

View File

@@ -270,6 +270,7 @@
v-model="createRefundDialogVisible"
:initial-order-id="currentRefundOrderId"
:initial-order-no="currentRefundOrderNo"
:initial-package-usage-id="currentRefundPackageUsageId"
@success="handleRefundSuccess"
/>
@@ -279,12 +280,7 @@
width="30%"
@closed="resetUsedDataForm"
>
<ElForm
ref="usedDataFormRef"
:model="usedDataForm"
:rules="usedDataRules"
label-width="90px"
>
<ElForm ref="usedDataFormRef" :model="usedDataForm" :rules="usedDataRules" label-width="90px">
<ElFormItem label="套餐名称">
<span>{{ selectedPackage?.package_name || '-' }}</span>
</ElFormItem>
@@ -416,6 +412,7 @@
const createRefundDialogVisible = ref(false)
const currentRefundOrderId = ref<number | undefined>(undefined)
const currentRefundOrderNo = ref<string | undefined>(undefined)
const currentRefundPackageUsageId = ref<number | undefined>(undefined)
const selectedPackage = ref<PackageInfo>()
const usedDataDialogVisible = ref(false)
const expiresAtDialogVisible = ref(false)
@@ -474,10 +471,12 @@
}
currentRefundOrderId.value = row.order_id
currentRefundOrderNo.value = row.order_no
currentRefundPackageUsageId.value = row.package_usage_id ?? row.id
createRefundDialogVisible.value = true
}
const handleRefundSuccess = () => {
currentRefundPackageUsageId.value = undefined
emit('refresh')
}

View File

@@ -1,39 +1,66 @@
<template>
<ElDialog v-model="visible" title="设置限速" width="500px">
<ElDialog
v-model="visible"
title="设置限速"
width="500px"
:lock-scroll="true"
:z-index="4000"
modal-class="speed-limit-modal"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
<ElFormItem label="固定档位" prop="code">
<ElSelect v-model="form.code" style="width: 100%">
<ElOption v-for="tier in speedTiers" :key="tier.code" :label="tier.label" :value="tier.code" />
<ElSelect
v-model="form.code"
style="width: 100%"
:teleported="true"
append-to="body"
popper-class="speed-limit-select-popper"
:popper-style="{ zIndex: 4001 }"
>
<ElOption
v-for="tier in speedTiers"
:key="tier.code"
:label="tier.label"
:value="tier.code"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="handleCancel">取消</ElButton>
<ElButton type="primary" @click="handleConfirm" :loading="loading"> 确认设置 </ElButton>
<ElButton
type="primary"
@click="handleConfirm"
:loading="confirmLoading"
:disabled="confirmLoading"
>
确认设置
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { ref, reactive, computed, onUnmounted, watch } from 'vue'
import { ElDialog, ElForm, ElFormItem, ElSelect, ElOption, ElButton } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
interface Props {
modelValue: boolean
confirmLoading?: boolean
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'confirm', data: { code: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 }): void
(e: 'confirm', data: { code: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 }): void
}
const props = defineProps<Props>()
const props = withDefaults(defineProps<Props>(), {
confirmLoading: false
})
const emit = defineEmits<Emits>()
const formRef = ref<FormInstance>()
const loading = ref(false)
const speedTiers = [
{ code: -1, label: '不限速' },
{ code: 0, label: '0kbps' },
@@ -59,10 +86,25 @@
})
// 监听对话框打开,重置表单
watch(visible, (newVal) => {
if (newVal) {
form.code = 3
}
const setLayoutScrollLock = (locked: boolean) => {
document
.querySelector<HTMLElement>('.layouts')
?.classList.toggle('speed-limit-scroll-locked', locked)
}
watch(
visible,
(newVal) => {
setLayoutScrollLock(newVal)
if (newVal) {
form.code = 3
}
},
{ immediate: true }
)
onUnmounted(() => {
setLayoutScrollLock(false)
})
const handleCancel = () => {
@@ -70,6 +112,7 @@
}
const handleConfirm = async () => {
if (props.confirmLoading) return
if (!formRef.value) return
try {
@@ -83,4 +126,28 @@
}
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.speed-limit-modal .el-dialog__body {
overflow: visible;
}
.speed-limit-select-popper {
z-index: 4001 !important;
overscroll-behavior: contain;
}
.speed-limit-select-popper .el-select-dropdown__wrap,
.speed-limit-select-popper .el-scrollbar__wrap {
max-height: none !important;
overflow-y: visible !important;
}
.speed-limit-modal {
overflow: hidden;
overscroll-behavior: contain;
}
.layouts.speed-limit-scroll-locked {
overflow: hidden !important;
}
</style>

View File

@@ -36,8 +36,8 @@
@show-switch-card="showSwitchCardDialog"
@show-switch-mode="showSwitchModeDialog"
@show-set-wifi="showSetWiFiDialog"
@show-realname-policy="showRealnamePolicyDialog"
@show-speed-limit="speedLimitDialogVisible = true"
@show-realname-policy="showRealnamePolicyDialog"
@show-speed-limit="speedLimitDialogVisible = true"
@show-update-realname-status="showUpdateRealnameStatusDialog"
@enable-binding-card="handleEnableBindingCard"
@disable-binding-card="handleDisableBindingCard"
@@ -174,7 +174,11 @@
:current-realname-status="bindingCardRealnameStatusValue"
@success="handleBindingCardRealnameStatusSuccess"
/>
<SpeedLimitDialog v-model="speedLimitDialogVisible" @confirm="handleSpeedTierConfirm" />
<SpeedLimitDialog
v-model="speedLimitDialogVisible"
:confirm-loading="speedTierSubmitting"
@confirm="handleSpeedTierConfirm"
/>
</div>
</template>
@@ -183,7 +187,10 @@
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElCard, ElEmpty } from 'element-plus'
import { RoutesAlias } from '@/router/routesAlias'
import { CardService, DeviceService } from '@/api/modules'
import { CardService, DeviceService } from '@/api/modules'
import { JULY_PERMISSIONS } from '@/config/constants'
import { useAuth } from '@/composables/useAuth'
import { normalizeApiError } from '@/utils/business/apiError'
import {
formatRemainingTime,
FrontendRateLimitError,
@@ -204,9 +211,9 @@
SwitchModeDialog,
WiFiConfigDialog,
PackageRechargeDialog,
DailyRecordsDialog,
OrderHistoryDialog,
SpeedLimitDialog
DailyRecordsDialog,
OrderHistoryDialog,
SpeedLimitDialog
} from './components/dialogs'
import SwitchCardDialog from '@/components/device/SwitchCardDialog.vue'
import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue'
@@ -235,6 +242,7 @@
const route = useRoute()
const router = useRouter()
const { hasAuth } = useAuth()
// ========== 组件引用 ==========
const assetSearchCardRef = ref<InstanceType<typeof AssetSearchCard>>()
@@ -294,6 +302,7 @@
// 绑定卡实名状态更新
const bindingCardRealnameStatusDialogVisible = ref(false)
const speedLimitDialogVisible = ref(false)
const speedTierSubmitting = ref(false)
const bindingCardRealnameStatusIccid = ref('')
const bindingCardRealnameStatusValue = ref<number>(0)
@@ -806,12 +815,34 @@
}
}
const handleSpeedTierConfirm = async (data: { code: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 }) => {
if (!cardInfo.value?.iccid) return
const response = await CardService.setSpeedTier(cardInfo.value.iccid, data.code)
if (response.code === 0) {
if (!hasAuth(JULY_PERMISSIONS.speedTier.update)) {
ElMessage.error('暂无设置限速权限')
return
}
const iccid = cardInfo.value?.iccid
if (!iccid) {
ElMessage.error('当前卡缺少 ICCID无法设置限速')
return
}
if (speedTierSubmitting.value) return
speedTierSubmitting.value = true
try {
const response = await CardService.setSpeedTier(iccid, data.code)
if (response.code !== 0) {
ElMessage.error(response.msg || '限速设置失败')
return
}
ElMessage.success(`限速设置成功:${response.data.speed_tier_name}`)
speedLimitDialogVisible.value = false
await handleRefresh()
} catch (error) {
console.error('设置限速失败:', error)
ElMessage.error(normalizeApiError(error).message)
} finally {
speedTierSubmitting.value = false
}
}
</script>