fix(operator): fix operator edit issue
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m35s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m35s
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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'
|
||||
Reference in New Issue
Block a user