fix(operator): fix operator edit issue
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m35s

This commit is contained in:
sexygoat
2026-04-10 09:50:00 +08:00
parent c2c1644799
commit 9a6f085cde
20 changed files with 4082 additions and 2782 deletions

View File

@@ -0,0 +1,205 @@
<template>
<ElDialog v-model="visible" title="套餐流量详单" width="900px" destroy-on-close>
<div v-if="data">
<!-- 套餐信息摘要 -->
<ElDescriptions :column="2" border style="margin-bottom: 20px">
<ElDescriptionsItem label="套餐名称">
{{ data.package_name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="套餐使用记录ID">
{{ data.package_usage_id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="总使用流量" :span="2">
{{ formatDataSize(data.total_usage_mb) }}
</ElDescriptionsItem>
</ElDescriptions>
<!-- 筛选器 -->
<div style="display: flex; gap: 12px; align-items: center; margin-bottom: 16px">
<span style="font-weight: 500">筛选方式</span>
<ElRadioGroup v-model="filterType" @change="handleFilterTypeChange">
<ElRadio value="all">全部</ElRadio>
<ElRadio value="day">按日</ElRadio>
<ElRadio value="month">按月</ElRadio>
</ElRadioGroup>
<ElDatePicker
v-if="filterType === 'day'"
v-model="selectedDate"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
clearable
@change="filterRecordsByDate"
style="width: 200px"
/>
<ElDatePicker
v-if="filterType === 'month'"
v-model="selectedMonth"
type="month"
placeholder="选择月份"
format="YYYY-MM"
value-format="YYYY-MM"
clearable
@change="filterRecordsByMonth"
style="width: 200px"
/>
</div>
<!-- 每日流量记录表格 -->
<ElTable :data="filteredRecords" border stripe max-height="400" v-loading="loading">
<ElTableColumn prop="date" label="日期" width="120" align="center" />
<ElTableColumn label="当日使用流量" width="150" align="center">
<template #default="{ row }">
{{ formatDataSize(row.daily_usage_mb) }}
</template>
</ElTableColumn>
<ElTableColumn label="累计流量" width="150" align="center">
<template #default="{ row }">
{{ formatDataSize(row.cumulative_usage_mb) }}
</template>
</ElTableColumn>
<ElTableColumn label="使用进度" min-width="200">
<template #default="{ row }">
<ElProgress
:percentage="getUsageProgress(row.cumulative_usage_mb, data.total_usage_mb)"
:color="
getProgressColor(getUsageProgress(row.cumulative_usage_mb, data.total_usage_mb))
"
/>
</template>
</ElTableColumn>
</ElTable>
</div>
<div v-else-if="!loading" style="padding: 40px 0; text-align: center">
<ElEmpty description="暂无流量详单数据" />
</div>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import {
ElDialog,
ElDescriptions,
ElDescriptionsItem,
ElRadioGroup,
ElRadio,
ElDatePicker,
ElTable,
ElTableColumn,
ElProgress,
ElEmpty,
ElMessage
} from 'element-plus'
import { CardService } from '@/api/modules'
interface Props {
modelValue: boolean
packageUsageId?: number
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const loading = ref(false)
const data = ref<any>(null)
const filterType = ref<'all' | 'day' | 'month'>('all')
const selectedDate = ref<string>('')
const selectedMonth = ref<string>('')
const filteredRecords = ref<any[]>([])
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 监听对话框打开,加载数据
watch(visible, async (newVal) => {
if (newVal && props.packageUsageId) {
await loadDailyRecords()
} else if (!newVal) {
// 关闭时重置数据
data.value = null
filterType.value = 'all'
selectedDate.value = ''
selectedMonth.value = ''
filteredRecords.value = []
}
})
const loadDailyRecords = async () => {
if (!props.packageUsageId) return
try {
loading.value = true
const res = await CardService.getPackageDailyRecords(props.packageUsageId)
if (res.code === 0 && res.data) {
data.value = res.data
filteredRecords.value = res.data.records || []
}
} catch (error) {
console.error('加载流量详单失败:', error)
ElMessage.error('加载流量详单失败')
} finally {
loading.value = false
}
}
const formatDataSize = (mb: number) => {
if (mb >= 1024) {
return `${(mb / 1024).toFixed(2)}GB`
}
return `${mb.toFixed(2)}MB`
}
const handleFilterTypeChange = () => {
selectedDate.value = ''
selectedMonth.value = ''
filteredRecords.value = data.value?.records || []
}
const filterRecordsByDate = () => {
if (!data.value?.records) return
if (!selectedDate.value) {
filteredRecords.value = data.value.records
return
}
filteredRecords.value = data.value.records.filter((record: any) => {
return record.date === selectedDate.value
})
}
const filterRecordsByMonth = () => {
if (!data.value?.records) return
if (!selectedMonth.value) {
filteredRecords.value = data.value.records
return
}
filteredRecords.value = data.value.records.filter((record: any) => {
return record.date.startsWith(selectedMonth.value)
})
}
const getUsageProgress = (cumulative: number, total: number): number => {
if (!total || total <= 0) return 0
const percentage = (cumulative / total) * 100
return Math.min(Math.round(percentage * 100) / 100, 100)
}
const getProgressColor = (percentage: number): string => {
if (percentage < 50) return '#67c23a'
if (percentage < 80) return '#e6a23c'
return '#f56c6c'
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,232 @@
<template>
<ElDialog v-model="visible" title="往期订单" width="60%" destroy-on-close>
<div v-if="loading" style="padding: 40px 0; text-align: center">
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
</div>
<div v-else-if="data">
<!-- 当前代订单 -->
<div class="generation-section">
<h3 style="margin-bottom: 16px"> 当前代 ({{ data.current_generation.identifier }}) </h3>
<ElTable :data="data.current_generation.items" border>
<ElTableColumn prop="order_no" label="订单号" width="200" />
<ElTableColumn prop="order_type" label="订单类型" width="120" />
<ElTableColumn prop="payment_status_text" label="支付状态" width="100">
<template #default="{ row }">
<ElTag :type="getPaymentStatusType(row.payment_status)">
{{ row.payment_status_text }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn prop="total_amount" label="总金额" width="120">
<template #default="{ row }"> ¥{{ (row.total_amount / 100).toFixed(2) }} </template>
</ElTableColumn>
<ElTableColumn prop="payment_method" label="支付方式" width="120" />
<ElTableColumn prop="paid_at" label="支付时间" width="180">
<template #default="{ row }">
{{ row.paid_at ? formatDateTime(row.paid_at) : '-' }}
</template>
</ElTableColumn>
<ElTableColumn label="订单项" min-width="200">
<template #default="{ row }">
<div v-for="(item, idx) in row.items" :key="idx" style="margin-bottom: 4px">
{{ item.package_name }} × {{ item.quantity }}
</div>
</template>
</ElTableColumn>
<ElTableColumn prop="created_at" label="创建时间" width="180">
<template #default="{ row }">
{{ formatDateTime(row.created_at) }}
</template>
</ElTableColumn>
</ElTable>
<!-- 分页 -->
<div style="margin-top: 16px; text-align: right">
<ElPagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.page_size"
:total="data.current_generation.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
/>
</div>
</div>
<!-- 前代订单 -->
<div
v-if="data.previous_generations && data.previous_generations.length > 0"
style="margin-top: 32px"
>
<div
v-for="gen in data.previous_generations"
:key="gen.generation"
class="generation-section"
style="margin-bottom: 24px"
>
<h3 style="margin-bottom: 16px">
{{ gen.generation }} ({{ gen.identifier }})
<span v-if="gen.exchange_no" style="margin-left: 12px; font-size: 14px; color: #909399">
换货单号: {{ gen.exchange_no }}
</span>
<span
v-if="gen.exchanged_at"
style="margin-left: 12px; font-size: 14px; color: #909399"
>
换货时间: {{ formatDateTime(gen.exchanged_at) }}
</span>
</h3>
<ElTable :data="gen.items" border>
<ElTableColumn prop="order_no" label="订单号" width="200" />
<ElTableColumn prop="order_type" label="订单类型" width="120" />
<ElTableColumn prop="payment_status_text" label="支付状态" width="100">
<template #default="{ row }">
<ElTag :type="getPaymentStatusType(row.payment_status)">
{{ row.payment_status_text }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn prop="total_amount" label="总金额" width="120">
<template #default="{ row }"> ¥{{ (row.total_amount / 100).toFixed(2) }} </template>
</ElTableColumn>
<ElTableColumn prop="payment_method" label="支付方式" width="120" />
<ElTableColumn prop="paid_at" label="支付时间" width="180">
<template #default="{ row }">
{{ row.paid_at ? formatDateTime(row.paid_at) : '-' }}
</template>
</ElTableColumn>
<ElTableColumn label="订单项" min-width="200">
<template #default="{ row }">
<div v-for="(item, idx) in row.items" :key="idx" style="margin-bottom: 4px">
{{ item.package_name }} × {{ item.quantity }}
</div>
</template>
</ElTableColumn>
<ElTableColumn prop="created_at" label="创建时间" width="180">
<template #default="{ row }">
{{ formatDateTime(row.created_at) }}
</template>
</ElTableColumn>
</ElTable>
</div>
<ElAlert v-if="data.truncated" type="warning" :closable="false" style="margin-top: 16px">
换货链超过10代仅显示最近10代的订单记录
</ElAlert>
</div>
</div>
<div v-else style="padding: 40px 0; text-align: center">
<ElEmpty description="暂无订单数据" />
</div>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import {
ElDialog,
ElTable,
ElTableColumn,
ElTag,
ElPagination,
ElAlert,
ElEmpty,
ElIcon,
ElMessage
} from 'element-plus'
import { Loading } from '@element-plus/icons-vue'
import { AssetService } from '@/api/modules'
import { formatDateTime } from '@/utils/business/format'
interface Props {
modelValue: boolean
assetIdentifier?: string
assetType?: 'card' | 'device'
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const loading = ref(false)
const data = ref<any>(null)
const pagination = reactive({
page: 1,
page_size: 20
})
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 监听对话框打开,加载数据
watch(visible, async (newVal) => {
if (newVal && props.assetIdentifier) {
pagination.page = 1
pagination.page_size = 20
await loadOrderHistory()
} else if (!newVal) {
// 关闭时重置数据
data.value = null
}
})
const loadOrderHistory = async () => {
if (!props.assetIdentifier) return
try {
loading.value = true
const response = await AssetService.getAssetOrders(props.assetIdentifier, {
page: pagination.page,
page_size: pagination.page_size,
include_previous: true // 包含换货前代的订单
})
if (response.code === 0 && response.data) {
data.value = response.data
}
} catch (error: any) {
console.error('加载往期订单失败:', error)
ElMessage.error(error?.message || '加载往期订单失败')
} finally {
loading.value = false
}
}
const handlePageSizeChange = (size: number) => {
pagination.page_size = size
loadOrderHistory()
}
const handlePageChange = (page: number) => {
pagination.page = page
loadOrderHistory()
}
const getPaymentStatusType = (status: number): 'success' | 'warning' | 'danger' | 'info' => {
const statusMap: Record<number, 'success' | 'warning' | 'danger' | 'info'> = {
1: 'warning', // 待支付
2: 'success', // 已支付
3: 'danger', // 已取消
4: 'info' // 已关闭
}
return statusMap[status] || 'info'
}
</script>
<style lang="scss" scoped>
.generation-section {
h3 {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
</style>

View File

@@ -0,0 +1,237 @@
<template>
<ElDialog v-model="visible" title="套餐充值" width="600px" @closed="handleDialogClosed">
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
<ElFormItem label="资产信息">
<span style="font-weight: bold; color: #409eff">
{{ assetIdentifier }}
</span>
</ElFormItem>
<ElFormItem label="选择套餐" prop="package_id">
<ElSelect
v-model="form.package_id"
placeholder="请选择套餐"
filterable
remote
reserve-keyword
:remote-method="handlePackageSearch"
:loading="packageSearchLoading"
clearable
style="width: 100%"
>
<ElOption
v-for="pkg in packageOptions"
:key="pkg.id"
:label="`${pkg.package_name} (¥${((pkg.cost_price || 0) / 100).toFixed(2)})`"
:value="pkg.id"
>
<div style="display: flex; justify-content: space-between">
<span>{{ pkg.package_name }}</span>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
¥{{ ((pkg.cost_price || 0) / 100).toFixed(2) }}
</span>
</div>
</ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="支付方式" prop="payment_method">
<ElSelect v-model="form.payment_method" placeholder="请选择支付方式" style="width: 100%">
<ElOption label="钱包支付" value="wallet" />
<ElOption v-if="showOfflinePayment" label="线下支付" value="offline" />
</ElSelect>
<div style="margin-top: 8px; font-size: 12px; color: var(--el-text-color-secondary)">
<template v-if="form.payment_method === 'wallet'">
提示: 使用钱包支付时,订单将直接完成
</template>
<template v-else-if="form.payment_method === 'offline'">
提示: 线下支付订单需要手动确认支付
</template>
</div>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="handleCancel">取消</ElButton>
<ElButton type="primary" @click="handleConfirm" :loading="loading"> 确认充值 </ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import {
ElDialog,
ElForm,
ElFormItem,
ElSelect,
ElOption,
ElButton,
ElMessage
} from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { OrderService, PackageManageService } from '@/api/modules'
import type { CreateOrderRequest, PackageResponse } from '@/types/api'
import { useUserStore } from '@/store/modules/user'
interface Props {
modelValue: boolean
assetIdentifier: string
assetType: 'card' | 'device'
seriesId?: number
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'confirm'): void
(e: 'success'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const userStore = useUserStore()
const formRef = ref<FormInstance>()
const loading = ref(false)
const packageSearchLoading = ref(false)
const packageOptions = ref<PackageResponse[]>([])
const form = reactive({
package_id: undefined as number | undefined,
payment_method: 'wallet' as 'wallet' | 'offline'
})
const rules: FormRules = {
package_id: [{ required: true, message: '请选择套餐', trigger: 'change' }],
payment_method: [{ required: true, message: '请选择支付方式', trigger: 'change' }]
}
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 只有平台用户(user_type 1:超级管理员, 2:平台用户)才显示线下支付选项
const showOfflinePayment = computed(() => {
return userStore.info.user_type === 1 || userStore.info.user_type === 2
})
// 监听对话框打开,加载套餐列表
watch(visible, async (newVal) => {
if (newVal) {
await loadPackages()
}
})
const loadPackages = async () => {
if (!props.seriesId) return
try {
packageSearchLoading.value = true
const response = await PackageManageService.getPackages({
series_id: props.seriesId,
page: 1,
page_size: 20,
status: 1, // 只获取启用的套餐
shelf_status: 1 // 只获取已上架的套餐
})
if (response.code === 0 && response.data) {
packageOptions.value = response.data.items || []
}
} catch (error: any) {
console.error('加载套餐列表失败:', error)
ElMessage.error(error?.message || '加载套餐列表失败')
} finally {
packageSearchLoading.value = false
}
}
const handlePackageSearch = async (query: string) => {
if (!props.seriesId) return
try {
packageSearchLoading.value = true
const response = await PackageManageService.getPackages({
package_name: query || undefined,
series_id: props.seriesId,
page: 1,
page_size: 20,
status: 1,
shelf_status: 1
})
if (response.code === 0 && response.data) {
packageOptions.value = response.data.items || []
}
} catch (error: any) {
console.error('搜索套餐失败:', error)
} finally {
packageSearchLoading.value = false
}
}
const handleCancel = () => {
visible.value = false
}
const handleConfirm = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
if (!form.package_id) {
ElMessage.error('请选择套餐')
return
}
loading.value = true
// 创建订单(后端接收数组,前端单选转为单元素数组)
const data: CreateOrderRequest = {
identifier: props.assetIdentifier,
package_ids: [form.package_id],
payment_method: form.payment_method
}
await OrderService.createOrder(data)
// 根据支付方式显示不同的成功消息
if (form.payment_method === 'wallet') {
ElMessage.success('订单创建成功,已自动完成支付')
} else {
ElMessage.success('订单创建成功')
}
visible.value = false
emit('confirm')
emit('success')
} catch (error: any) {
// 用户取消确认对话框
if (error === 'cancel') {
return
}
console.error('创建订单失败:', error)
ElMessage.error(error?.message || '创建订单失败')
} finally {
loading.value = false
}
}
const handleDialogClosed = () => {
formRef.value?.resetFields()
form.package_id = undefined
form.payment_method = 'wallet'
packageOptions.value = []
}
</script>
<style lang="scss" scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<ElDialog v-model="visible" title="设置限速" width="500px">
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
<ElFormItem label="下行速率" prop="download_speed">
<ElInputNumber
v-model="form.download_speed"
:min="1"
:step="128"
controls-position="right"
style="width: 100%"
/>
<div style="margin-top: 4px; font-size: 12px; color: #909399">单位: KB/s</div>
</ElFormItem>
<ElFormItem label="上行速率" prop="upload_speed">
<ElInputNumber
v-model="form.upload_speed"
:min="1"
:step="128"
controls-position="right"
style="width: 100%"
/>
<div style="margin-top: 4px; font-size: 12px; color: #909399">单位: KB/s</div>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="handleCancel">取消</ElButton>
<ElButton type="primary" @click="handleConfirm" :loading="loading"> 确认设置 </ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { ElDialog, ElForm, ElFormItem, ElInputNumber, ElButton } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
interface Props {
modelValue: boolean
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'confirm', data: { download_speed: number; upload_speed: number }): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const formRef = ref<FormInstance>()
const loading = ref(false)
const form = reactive({
download_speed: 1024,
upload_speed: 512
})
const rules: FormRules = {
download_speed: [{ required: true, message: '请输入下行速率', trigger: 'blur' }],
upload_speed: [{ required: true, message: '请输入上行速率', trigger: 'blur' }]
}
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 监听对话框打开,重置表单
watch(visible, (newVal) => {
if (newVal) {
form.download_speed = 1024
form.upload_speed = 512
}
})
const handleCancel = () => {
visible.value = false
}
const handleConfirm = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
emit('confirm', {
download_speed: form.download_speed,
upload_speed: form.upload_speed
})
} catch (error) {
console.error('表单验证失败:', error)
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,165 @@
<template>
<ElDialog v-model="visible" title="切换SIM卡" width="600px">
<div v-if="loading" style="padding: 40px; text-align: center">
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
<div style="margin-top: 16px">加载设备绑定的卡列表中...</div>
</div>
<template v-else>
<ElAlert
v-if="bindingCards.length === 0"
title="该设备暂无绑定的SIM卡"
type="warning"
:closable="false"
style="margin-bottom: 20px"
>
<template #default>
<div>当前设备没有绑定任何SIM卡,无法进行切换操作</div>
<div>请先为设备绑定SIM卡后再进行切换</div>
</template>
</ElAlert>
<ElForm
v-if="bindingCards.length > 0"
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
>
<ElFormItem label="目标ICCID" prop="target_iccid">
<ElSelect
v-model="form.target_iccid"
placeholder="请选择要切换到的目标ICCID"
style="width: 100%"
clearable
>
<ElOption
v-for="card in bindingCards"
:key="card.iccid"
:label="`${card.iccid} - 插槽${card.slot_position} - ${card.carrier_name}`"
:value="card.iccid"
>
<div style="display: flex; align-items: center; justify-content: space-between">
<span>{{ card.iccid }}</span>
<ElTag size="small" style="margin-left: 10px">插槽{{ card.slot_position }}</ElTag>
</div>
</ElOption>
</ElSelect>
<div style="margin-top: 8px; font-size: 12px; color: #909399">
当前设备共绑定 {{ bindingCards.length }} 张SIM卡
</div>
</ElFormItem>
</ElForm>
</template>
<template #footer>
<ElButton @click="handleCancel">取消</ElButton>
<ElButton
v-if="bindingCards.length > 0"
type="primary"
@click="handleConfirm"
:loading="confirmLoading"
>
确认切换
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import {
ElDialog,
ElForm,
ElFormItem,
ElSelect,
ElOption,
ElButton,
ElAlert,
ElIcon,
ElTag,
ElMessage
} from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { Loading } from '@element-plus/icons-vue'
import { DeviceService } from '@/api/modules'
interface Props {
modelValue: boolean
deviceId: number
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'confirm', data: { target_iccid: string }): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const formRef = ref<FormInstance>()
const loading = ref(false)
const confirmLoading = ref(false)
const bindingCards = ref<any[]>([])
const form = reactive({
target_iccid: ''
})
const rules: FormRules = {
target_iccid: [{ required: true, message: '请选择目标ICCID', trigger: 'change' }]
}
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 监听对话框打开,加载绑定卡列表
watch(visible, async (newVal) => {
if (newVal) {
form.target_iccid = ''
bindingCards.value = []
await loadDeviceCards()
}
})
const loadDeviceCards = async () => {
if (!props.deviceId) return
try {
loading.value = true
const res = await DeviceService.getDeviceCards(props.deviceId)
if (res.code === 0 && res.data) {
bindingCards.value = res.data.bindings || []
if (bindingCards.value.length === 0) {
ElMessage.warning('该设备暂无绑定的SIM卡')
}
}
} catch (error) {
console.error('加载设备绑定卡列表失败:', error)
ElMessage.error('加载卡列表失败')
} finally {
loading.value = false
}
}
const handleCancel = () => {
visible.value = false
}
const handleConfirm = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
emit('confirm', {
target_iccid: form.target_iccid
})
} catch (error) {
console.error('表单验证失败:', error)
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,117 @@
<template>
<ElDialog v-model="visible" title="设置WiFi" width="500px">
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
<ElFormItem label="WiFi状态" prop="enabled">
<ElRadioGroup v-model="form.enabled">
<ElRadio :value="1">启用</ElRadio>
<ElRadio :value="0">禁用</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem label="WiFi名称" prop="ssid">
<ElInput
v-model="form.ssid"
placeholder="请输入WiFi名称1-32个字符"
maxlength="32"
show-word-limit
clearable
/>
</ElFormItem>
<ElFormItem label="WiFi密码" prop="password">
<ElInput
v-model="form.password"
type="password"
placeholder="请输入WiFi密码8-63个字符"
maxlength="63"
show-word-limit
show-password
clearable
/>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="handleCancel">取消</ElButton>
<ElButton type="primary" @click="handleConfirm" :loading="loading"> 确认设置 </ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import {
ElDialog,
ElForm,
ElFormItem,
ElInput,
ElRadioGroup,
ElRadio,
ElButton
} from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
interface Props {
modelValue: boolean
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'confirm', data: { enabled: number; ssid: string; password: string }): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const formRef = ref<FormInstance>()
const loading = ref(false)
const form = reactive({
enabled: 1,
ssid: '',
password: ''
})
const rules: FormRules = {
ssid: [
{ required: true, message: '请输入WiFi名称', trigger: 'blur' },
{ min: 1, max: 32, message: 'WiFi名称长度为1-32个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入WiFi密码', trigger: 'blur' },
{ min: 8, max: 63, message: 'WiFi密码长度为8-63个字符', trigger: 'blur' }
]
}
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 监听对话框打开,重置表单
watch(visible, (newVal) => {
if (newVal) {
form.enabled = 1
form.ssid = ''
form.password = ''
}
})
const handleCancel = () => {
visible.value = false
}
const handleConfirm = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
emit('confirm', {
enabled: form.enabled,
ssid: form.ssid,
password: form.password
})
} catch (error) {
console.error('表单验证失败:', error)
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,6 @@
export { default as SpeedLimitDialog } from './SpeedLimitDialog.vue'
export { default as SwitchCardDialog } from './SwitchCardDialog.vue'
export { default as WiFiConfigDialog } from './WiFiConfigDialog.vue'
export { default as PackageRechargeDialog } from './PackageRechargeDialog.vue'
export { default as DailyRecordsDialog } from './DailyRecordsDialog.vue'
export { default as OrderHistoryDialog } from './OrderHistoryDialog.vue'