fetch(modify):完善设备管理
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 2m47s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 2m47s
This commit is contained in:
@@ -109,6 +109,23 @@ export const useUserStore = defineStore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清理本地状态(不调用退出接口)
|
||||||
|
const clearLocalState = () => {
|
||||||
|
info.value = {}
|
||||||
|
isLogin.value = false
|
||||||
|
isLock.value = false
|
||||||
|
lockPassword.value = ''
|
||||||
|
accessToken.value = ''
|
||||||
|
refreshToken.value = ''
|
||||||
|
permissions.value = []
|
||||||
|
menus.value = []
|
||||||
|
buttons.value = []
|
||||||
|
useWorktabStore().opened = []
|
||||||
|
sessionStorage.removeItem('iframeRoutes')
|
||||||
|
resetRouterState(router)
|
||||||
|
router.push(RoutesAlias.Login)
|
||||||
|
}
|
||||||
|
|
||||||
const logOut = async () => {
|
const logOut = async () => {
|
||||||
try {
|
try {
|
||||||
// 调用退出登录接口
|
// 调用退出登录接口
|
||||||
@@ -117,19 +134,7 @@ export const useUserStore = defineStore(
|
|||||||
console.error('退出登录接口调用失败:', error)
|
console.error('退出登录接口调用失败:', error)
|
||||||
} finally {
|
} finally {
|
||||||
// 无论接口成功与否,都清理本地状态
|
// 无论接口成功与否,都清理本地状态
|
||||||
info.value = {}
|
clearLocalState()
|
||||||
isLogin.value = false
|
|
||||||
isLock.value = false
|
|
||||||
lockPassword.value = ''
|
|
||||||
accessToken.value = ''
|
|
||||||
refreshToken.value = ''
|
|
||||||
permissions.value = []
|
|
||||||
menus.value = []
|
|
||||||
buttons.value = []
|
|
||||||
useWorktabStore().opened = []
|
|
||||||
sessionStorage.removeItem('iframeRoutes')
|
|
||||||
resetRouterState(router)
|
|
||||||
router.push(RoutesAlias.Login)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +171,7 @@ export const useUserStore = defineStore(
|
|||||||
setLockStatus,
|
setLockStatus,
|
||||||
setLockPassword,
|
setLockPassword,
|
||||||
setToken,
|
setToken,
|
||||||
|
clearLocalState,
|
||||||
logOut
|
logOut
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ axiosInstance.interceptors.response.use(
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const originalRequest = response.config as any
|
const originalRequest = response.config as any
|
||||||
|
|
||||||
// 如果没有 refreshToken,直接退出登录
|
// 如果没有 refreshToken,直接清理本地状态(不调用退出接口)
|
||||||
if (!userStore.refreshToken) {
|
if (!userStore.refreshToken) {
|
||||||
logOut()
|
clearLocalStateAndRedirect()
|
||||||
return Promise.reject(response)
|
return Promise.reject(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,16 +140,16 @@ axiosInstance.interceptors.response.use(
|
|||||||
// 重试原请求
|
// 重试原请求
|
||||||
resolve(axiosInstance.request(originalRequest))
|
resolve(axiosInstance.request(originalRequest))
|
||||||
} else {
|
} else {
|
||||||
// 刷新失败
|
// 刷新失败 - 清理本地状态(不调用退出接口)
|
||||||
processQueue(new Error('Token refresh failed'), null)
|
processQueue(new Error('Token refresh failed'), null)
|
||||||
logOut()
|
clearLocalStateAndRedirect()
|
||||||
reject(res)
|
reject(res)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// 刷新失败
|
// 刷新失败 - 清理本地状态(不调用退出接口)
|
||||||
processQueue(err, null)
|
processQueue(err, null)
|
||||||
logOut()
|
clearLocalStateAndRedirect()
|
||||||
reject(err)
|
reject(err)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -162,7 +162,16 @@ axiosInstance.interceptors.response.use(
|
|||||||
(error) => {
|
(error) => {
|
||||||
if (axios.isCancel(error)) {
|
if (axios.isCancel(error)) {
|
||||||
console.log('repeated request: ' + error.message)
|
console.log('repeated request: ' + error.message)
|
||||||
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 HTTP 401 状态码 - 直接清理本地状态(不调用退出接口)
|
||||||
|
if (error.response && error.response.status === 401) {
|
||||||
|
console.warn('HTTP 401 Unauthorized - 强制退出登录')
|
||||||
|
clearLocalStateAndRedirect()
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
// 注意:错误处理现在在request函数中根据requestOptions处理
|
// 注意:错误处理现在在request函数中根据requestOptions处理
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
@@ -270,7 +279,7 @@ const api = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 退出登录
|
// 退出登录(调用接口)
|
||||||
const logOut = () => {
|
const logOut = () => {
|
||||||
ElMessage.error('登录已过期,请重新登录')
|
ElMessage.error('登录已过期,请重新登录')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -278,4 +287,12 @@ const logOut = () => {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 仅清理本地状态(不调用接口)- 用于401等情况
|
||||||
|
const clearLocalStateAndRedirect = () => {
|
||||||
|
ElMessage.error('登录已过期,请重新登录')
|
||||||
|
setTimeout(() => {
|
||||||
|
useUserStore().clearLocalState()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
export default api
|
export default api
|
||||||
|
|||||||
@@ -65,19 +65,16 @@
|
|||||||
<span style="font-weight: bold; color: #409eff">{{ selectedDevices.length }}</span> 台
|
<span style="font-weight: bold; color: #409eff">{{ selectedDevices.length }}</span> 台
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="目标店铺" prop="target_shop_id">
|
<ElFormItem label="目标店铺" prop="target_shop_id">
|
||||||
<ElSelect
|
<ElTreeSelect
|
||||||
v-model="allocateForm.target_shop_id"
|
v-model="allocateForm.target_shop_id"
|
||||||
|
:data="shopTreeData"
|
||||||
|
:props="{ label: 'shop_name', value: 'id', children: 'children' }"
|
||||||
placeholder="请选择目标店铺"
|
placeholder="请选择目标店铺"
|
||||||
|
clearable
|
||||||
filterable
|
filterable
|
||||||
|
check-strictly
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
/>
|
||||||
<ElOption
|
|
||||||
v-for="shop in shopList"
|
|
||||||
:key="shop.id"
|
|
||||||
:label="shop.name"
|
|
||||||
:value="shop.id"
|
|
||||||
/>
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="备注">
|
<ElFormItem label="备注">
|
||||||
<ElInput
|
<ElInput
|
||||||
@@ -204,9 +201,14 @@
|
|||||||
<ElFormItem label="套餐系列分配" prop="series_allocation_id">
|
<ElFormItem label="套餐系列分配" prop="series_allocation_id">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="seriesBindingForm.series_allocation_id"
|
v-model="seriesBindingForm.series_allocation_id"
|
||||||
placeholder="请选择套餐系列分配(选择清除关联将解除绑定)"
|
placeholder="请选择或搜索套餐系列分配(支持系列名称搜索)"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
reserve-keyword
|
||||||
|
:remote-method="searchSeriesAllocations"
|
||||||
:loading="seriesLoading"
|
:loading="seriesLoading"
|
||||||
|
clearable
|
||||||
>
|
>
|
||||||
<ElOption label="清除关联" :value="0" />
|
<ElOption label="清除关联" :value="0" />
|
||||||
<ElOption
|
<ElOption
|
||||||
@@ -264,49 +266,133 @@
|
|||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 设备详情弹窗 -->
|
<!-- 设备详情弹窗 -->
|
||||||
<ElDialog v-model="deviceDetailDialogVisible" title="设备详情" width="900px">
|
<ElDialog v-model="deviceDetailDialogVisible" title="设备详情" width="1000px">
|
||||||
<div v-if="deviceDetailLoading" style="text-align: center; padding: 40px 0">
|
<div v-if="deviceDetailLoading" style="text-align: center; padding: 40px 0">
|
||||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
</div>
|
</div>
|
||||||
<ElDescriptions v-else-if="currentDeviceDetail" :column="3" border>
|
<div v-else-if="currentDeviceDetail">
|
||||||
<ElDescriptionsItem label="设备ID">{{ currentDeviceDetail.id }}</ElDescriptionsItem>
|
<!-- 设备基本信息 -->
|
||||||
<ElDescriptionsItem label="设备号" :span="2">{{
|
<ElDescriptions :column="3" border style="margin-bottom: 20px">
|
||||||
currentDeviceDetail.device_no
|
<ElDescriptionsItem label="设备ID">{{ currentDeviceDetail.id }}</ElDescriptionsItem>
|
||||||
}}</ElDescriptionsItem>
|
<ElDescriptionsItem label="设备号" :span="2">{{
|
||||||
|
currentDeviceDetail.device_no
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="设备名称">{{
|
<ElDescriptionsItem label="设备名称">{{
|
||||||
currentDeviceDetail.device_name || '--'
|
currentDeviceDetail.device_name || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="设备型号">{{
|
<ElDescriptionsItem label="设备型号">{{
|
||||||
currentDeviceDetail.device_model || '--'
|
currentDeviceDetail.device_model || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="设备类型">{{
|
<ElDescriptionsItem label="设备类型">{{
|
||||||
currentDeviceDetail.device_type || '--'
|
currentDeviceDetail.device_type || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="制造商">{{
|
<ElDescriptionsItem label="制造商">{{
|
||||||
currentDeviceDetail.manufacturer || '--'
|
currentDeviceDetail.manufacturer || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="最大插槽数">{{
|
<ElDescriptionsItem label="最大插槽数">{{
|
||||||
currentDeviceDetail.max_sim_slots
|
currentDeviceDetail.max_sim_slots
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="已绑定卡数量">{{
|
<ElDescriptionsItem label="已绑定卡数量">{{
|
||||||
currentDeviceDetail.bound_card_count
|
currentDeviceDetail.bound_card_count
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="状态">
|
<ElDescriptionsItem label="状态">
|
||||||
<ElTag :type="getDeviceStatusTagType(currentDeviceDetail.status)">
|
<ElTag :type="getDeviceStatusTagType(currentDeviceDetail.status)">
|
||||||
{{ currentDeviceDetail.status_name }}
|
{{ currentDeviceDetail.status_name }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="批次号" :span="2">{{
|
<ElDescriptionsItem label="批次号" :span="2">{{
|
||||||
currentDeviceDetail.batch_no || '--'
|
currentDeviceDetail.batch_no || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
|
|
||||||
<ElDescriptionsItem label="创建时间" :span="3">{{
|
<ElDescriptionsItem label="创建时间" :span="3">{{
|
||||||
currentDeviceDetail.created_at || '--'
|
formatDateTime(currentDeviceDetail.created_at) || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
|
</div>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 绑定卡片列表弹窗 -->
|
||||||
|
<ElDialog v-model="deviceCardsDialogVisible" title="绑定的卡片" width="900px">
|
||||||
|
<div style="margin-bottom: 10px; text-align: right">
|
||||||
|
<ElButton type="primary" size="small" @click="handleBindCard">绑定新卡</ElButton>
|
||||||
|
</div>
|
||||||
|
<div v-if="deviceCardsLoading" style="text-align: center; padding: 40px 0">
|
||||||
|
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<ElTable :data="deviceCards" border style="width: 100%">
|
||||||
|
<ElTableColumn prop="slot_position" label="插槽位置" width="100" align="center" />
|
||||||
|
<ElTableColumn prop="iccid" label="ICCID" min-width="180" />
|
||||||
|
<ElTableColumn prop="msisdn" label="接入号" width="140" />
|
||||||
|
<ElTableColumn prop="status" label="状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag :type="getCardStatusTagType(row.status)">
|
||||||
|
{{ getCardStatusText(row.status) }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn prop="bind_time" label="绑定时间" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDateTime(row.bind_time) || '--' }}
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="操作" width="100" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElButton type="danger" size="small" link @click="handleUnbindCard(row)">
|
||||||
|
解绑
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
<div v-if="deviceCards.length === 0" style="text-align: center; padding: 20px; color: #909399">
|
||||||
|
暂无绑定的卡片
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 绑定卡弹窗 -->
|
||||||
|
<ElDialog v-model="bindCardDialogVisible" title="绑定卡到设备" width="500px">
|
||||||
|
<ElForm ref="bindCardFormRef" :model="bindCardForm" :rules="bindCardRules" label-width="100px">
|
||||||
|
<ElFormItem label="IoT卡" prop="iot_card_id">
|
||||||
|
<ElSelect
|
||||||
|
v-model="bindCardForm.iot_card_id"
|
||||||
|
placeholder="请选择或搜索IoT卡(支持ICCID搜索)"
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
reserve-keyword
|
||||||
|
:remote-method="searchIotCards"
|
||||||
|
:loading="iotCardSearchLoading"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="card in iotCardList"
|
||||||
|
:key="card.id"
|
||||||
|
:label="`${card.iccid} ${card.msisdn ? '(' + card.msisdn + ')' : ''}`"
|
||||||
|
:value="card.id"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="插槽位置" prop="slot_position">
|
||||||
|
<ElSelect v-model="bindCardForm.slot_position" placeholder="请选择插槽位置" style="width: 100%">
|
||||||
|
<ElOption
|
||||||
|
v-for="i in currentDeviceDetail?.max_sim_slots || 4"
|
||||||
|
:key="i"
|
||||||
|
:label="`插槽 ${i}`"
|
||||||
|
:value="i"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="bindCardDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleConfirmBindCard" :loading="bindCardLoading">
|
||||||
|
确认绑定
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
@@ -316,9 +402,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { DeviceService, ShopService } from '@/api/modules'
|
import { DeviceService, ShopService, CardService } from '@/api/modules'
|
||||||
import { ShopSeriesAllocationService } from '@/api/modules/shopSeriesAllocation'
|
import { ShopSeriesAllocationService } from '@/api/modules/shopSeriesAllocation'
|
||||||
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElIcon } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElIcon, ElTreeSelect } from 'element-plus'
|
||||||
import { Loading } from '@element-plus/icons-vue'
|
import { Loading } from '@element-plus/icons-vue'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import type {
|
import type {
|
||||||
@@ -347,7 +433,7 @@
|
|||||||
const allocateDialogVisible = ref(false)
|
const allocateDialogVisible = ref(false)
|
||||||
const recallDialogVisible = ref(false)
|
const recallDialogVisible = ref(false)
|
||||||
const selectedDevices = ref<Device[]>([])
|
const selectedDevices = ref<Device[]>([])
|
||||||
const shopList = ref<any[]>([])
|
const shopTreeData = ref<any[]>([])
|
||||||
const allocateResult = ref<AllocateDevicesResponse | null>(null)
|
const allocateResult = ref<AllocateDevicesResponse | null>(null)
|
||||||
const recallResult = ref<RecallDevicesResponse | null>(null)
|
const recallResult = ref<RecallDevicesResponse | null>(null)
|
||||||
|
|
||||||
@@ -369,6 +455,24 @@
|
|||||||
const deviceDetailDialogVisible = ref(false)
|
const deviceDetailDialogVisible = ref(false)
|
||||||
const deviceDetailLoading = ref(false)
|
const deviceDetailLoading = ref(false)
|
||||||
const currentDeviceDetail = ref<any>(null)
|
const currentDeviceDetail = ref<any>(null)
|
||||||
|
const deviceCards = ref<any[]>([])
|
||||||
|
const deviceCardsLoading = ref(false)
|
||||||
|
const deviceCardsDialogVisible = ref(false)
|
||||||
|
|
||||||
|
// 绑定卡相关
|
||||||
|
const bindCardDialogVisible = ref(false)
|
||||||
|
const bindCardLoading = ref(false)
|
||||||
|
const bindCardFormRef = ref<FormInstance>()
|
||||||
|
const iotCardList = ref<any[]>([])
|
||||||
|
const iotCardSearchLoading = ref(false)
|
||||||
|
const bindCardForm = reactive({
|
||||||
|
iot_card_id: undefined as number | undefined,
|
||||||
|
slot_position: 1
|
||||||
|
})
|
||||||
|
const bindCardRules = reactive<FormRules>({
|
||||||
|
iot_card_id: [{ required: true, message: '请选择IoT卡', trigger: 'change' }],
|
||||||
|
slot_position: [{ required: true, message: '请选择插槽位置', trigger: 'change' }]
|
||||||
|
})
|
||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
@@ -511,6 +615,173 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查看设备绑定的卡片
|
||||||
|
const handleViewCards = async (device: Device) => {
|
||||||
|
currentDeviceDetail.value = device
|
||||||
|
deviceCards.value = []
|
||||||
|
deviceCardsDialogVisible.value = true
|
||||||
|
await loadDeviceCards(device.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载设备绑定的卡列表
|
||||||
|
const loadDeviceCards = async (deviceId: number) => {
|
||||||
|
deviceCardsLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await DeviceService.getDeviceCards(deviceId)
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
deviceCards.value = res.data.bindings || []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取设备绑定的卡列表失败:', error)
|
||||||
|
} finally {
|
||||||
|
deviceCardsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开绑定卡弹窗
|
||||||
|
const handleBindCard = async () => {
|
||||||
|
bindCardForm.iot_card_id = undefined
|
||||||
|
bindCardForm.slot_position = 1
|
||||||
|
bindCardDialogVisible.value = true
|
||||||
|
// 加载默认的IoT卡列表
|
||||||
|
await loadDefaultIotCards()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载默认的IoT卡列表
|
||||||
|
const loadDefaultIotCards = async () => {
|
||||||
|
iotCardSearchLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await CardService.getStandaloneIotCards({
|
||||||
|
page: 1,
|
||||||
|
page_size: 20
|
||||||
|
})
|
||||||
|
if (res.code === 0 && res.data?.items) {
|
||||||
|
iotCardList.value = res.data.items
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取IoT卡列表失败:', error)
|
||||||
|
} finally {
|
||||||
|
iotCardSearchLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索IoT卡(根据ICCID)
|
||||||
|
const searchIotCards = async (query: string) => {
|
||||||
|
if (!query) {
|
||||||
|
await loadDefaultIotCards()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iotCardSearchLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await CardService.getStandaloneIotCards({
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
iccid: query
|
||||||
|
})
|
||||||
|
if (res.code === 0 && res.data?.items) {
|
||||||
|
iotCardList.value = res.data.items
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('搜索IoT卡失败:', error)
|
||||||
|
} finally {
|
||||||
|
iotCardSearchLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认绑定卡
|
||||||
|
const handleConfirmBindCard = async () => {
|
||||||
|
if (!bindCardFormRef.value || !currentDeviceDetail.value) return
|
||||||
|
|
||||||
|
await bindCardFormRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
bindCardLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await DeviceService.bindCard(currentDeviceDetail.value.id, {
|
||||||
|
iot_card_id: bindCardForm.iot_card_id!,
|
||||||
|
slot_position: bindCardForm.slot_position
|
||||||
|
})
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('绑定成功')
|
||||||
|
bindCardDialogVisible.value = false
|
||||||
|
// 重新加载卡列表
|
||||||
|
await loadDeviceCards(currentDeviceDetail.value.id)
|
||||||
|
// 刷新设备详情以更新绑定卡数量
|
||||||
|
const detailRes = await DeviceService.getDeviceByImei(currentDeviceDetail.value.device_no)
|
||||||
|
if (detailRes.code === 0 && detailRes.data) {
|
||||||
|
currentDeviceDetail.value = detailRes.data
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '绑定失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('绑定卡失败:', error)
|
||||||
|
ElMessage.error(error?.message || '绑定失败')
|
||||||
|
} finally {
|
||||||
|
bindCardLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解绑卡
|
||||||
|
const handleUnbindCard = (row: any) => {
|
||||||
|
ElMessageBox.confirm(`确定要解绑 ICCID: ${row.iccid} 吗?`, '解绑确认', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await DeviceService.unbindCard(
|
||||||
|
currentDeviceDetail.value.id,
|
||||||
|
row.iot_card_id
|
||||||
|
)
|
||||||
|
if (res.code === 0) {
|
||||||
|
ElMessage.success('解绑成功')
|
||||||
|
// 重新加载卡列表
|
||||||
|
await loadDeviceCards(currentDeviceDetail.value.id)
|
||||||
|
// 刷新设备详情以更新绑定卡数量
|
||||||
|
const detailRes = await DeviceService.getDeviceByImei(
|
||||||
|
currentDeviceDetail.value.device_no
|
||||||
|
)
|
||||||
|
if (detailRes.code === 0 && detailRes.data) {
|
||||||
|
currentDeviceDetail.value = detailRes.data
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message || '解绑失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('解绑卡失败:', error)
|
||||||
|
ElMessage.error(error?.message || '解绑失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 用户取消
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取卡状态标签类型
|
||||||
|
const getCardStatusTagType = (status: number) => {
|
||||||
|
const typeMap: Record<number, any> = {
|
||||||
|
1: 'info', // 在库
|
||||||
|
2: 'warning', // 已分销
|
||||||
|
3: 'success', // 已激活
|
||||||
|
4: 'danger' // 已停用
|
||||||
|
}
|
||||||
|
return typeMap[status] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取卡状态文本
|
||||||
|
const getCardStatusText = (status: number) => {
|
||||||
|
const textMap: Record<number, string> = {
|
||||||
|
1: '在库',
|
||||||
|
2: '已分销',
|
||||||
|
3: '已激活',
|
||||||
|
4: '已停用'
|
||||||
|
}
|
||||||
|
return textMap[status] || '未知'
|
||||||
|
}
|
||||||
|
|
||||||
// 获取设备状态标签类型
|
// 获取设备状态标签类型
|
||||||
const getDeviceStatusTagType = (status: number) => {
|
const getDeviceStatusTagType = (status: number) => {
|
||||||
const typeMap: Record<number, any> = {
|
const typeMap: Record<number, any> = {
|
||||||
@@ -610,10 +881,14 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 100,
|
width: 180,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: Device) => {
|
formatter: (row: Device) => {
|
||||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||||
|
h(ArtButtonTable, {
|
||||||
|
text: '查看卡片',
|
||||||
|
onClick: () => handleViewCards(row)
|
||||||
|
}),
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'delete',
|
type: 'delete',
|
||||||
onClick: () => deleteDevice(row)
|
onClick: () => deleteDevice(row)
|
||||||
@@ -625,21 +900,57 @@
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getTableData()
|
getTableData()
|
||||||
loadShopList()
|
loadShopTree()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载店铺列表
|
// 当页面被 keep-alive 激活时自动刷新数据
|
||||||
const loadShopList = async () => {
|
onActivated(() => {
|
||||||
|
getTableData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载店铺树形数据
|
||||||
|
const loadShopTree = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await ShopService.getShops({ page: 1, pageSize: 1000 })
|
const res = await ShopService.getShops({
|
||||||
|
page: 1,
|
||||||
|
page_size: 9999, // 获取所有数据用于构建树形结构
|
||||||
|
status: 1 // 只获取启用的店铺
|
||||||
|
})
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
shopList.value = res.data.items || []
|
shopTreeData.value = buildShopTree(res.data.items || [])
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取店铺列表失败:', error)
|
console.error('获取店铺列表失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建店铺树形结构
|
||||||
|
const buildShopTree = (shops: any[]) => {
|
||||||
|
const map = new Map<number, any>()
|
||||||
|
const tree: any[] = []
|
||||||
|
|
||||||
|
// 先将所有项放入 map
|
||||||
|
shops.forEach((shop) => {
|
||||||
|
map.set(shop.id, { ...shop, children: [] })
|
||||||
|
})
|
||||||
|
|
||||||
|
// 构建树形结构
|
||||||
|
shops.forEach((shop) => {
|
||||||
|
const node = map.get(shop.id)!
|
||||||
|
if (shop.parent_id && map.has(shop.parent_id)) {
|
||||||
|
// 有父节点,添加到父节点的 children 中
|
||||||
|
const parent = map.get(shop.parent_id)!
|
||||||
|
if (!parent.children) parent.children = []
|
||||||
|
parent.children.push(node)
|
||||||
|
} else {
|
||||||
|
// 没有父节点或父节点不存在,作为根节点
|
||||||
|
tree.push(node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
// 获取设备列表
|
// 获取设备列表
|
||||||
const getTableData = async () => {
|
const getTableData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -724,7 +1035,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 批量分配
|
// 批量分配
|
||||||
const handleBatchAllocate = () => {
|
const handleBatchAllocate = async () => {
|
||||||
if (selectedDevices.value.length === 0) {
|
if (selectedDevices.value.length === 0) {
|
||||||
ElMessage.warning('请先选择要分配的设备')
|
ElMessage.warning('请先选择要分配的设备')
|
||||||
return
|
return
|
||||||
@@ -839,16 +1150,21 @@
|
|||||||
seriesBindingDialogVisible.value = true
|
seriesBindingDialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载套餐系列分配列表
|
// 加载套餐系列分配列表(默认加载20条)
|
||||||
const loadSeriesAllocationList = async () => {
|
const loadSeriesAllocationList = async (seriesName?: string) => {
|
||||||
seriesLoading.value = true
|
seriesLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocations({
|
const params: any = {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 1000
|
page_size: 20,
|
||||||
})
|
status: 1 // 只获取启用的
|
||||||
if (res.code === 0 && res.data.list) {
|
}
|
||||||
seriesAllocationList.value = res.data.list
|
if (seriesName) {
|
||||||
|
params.series_name = seriesName
|
||||||
|
}
|
||||||
|
const res = await ShopSeriesAllocationService.getShopSeriesAllocations(params)
|
||||||
|
if (res.code === 0 && res.data.items) {
|
||||||
|
seriesAllocationList.value = res.data.items
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取套餐系列分配列表失败:', error)
|
console.error('获取套餐系列分配列表失败:', error)
|
||||||
@@ -858,6 +1174,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索套餐系列分配(根据系列名称)
|
||||||
|
const searchSeriesAllocations = async (query: string) => {
|
||||||
|
await loadSeriesAllocationList(query || undefined)
|
||||||
|
}
|
||||||
|
|
||||||
// 确认设置套餐系列绑定
|
// 确认设置套餐系列绑定
|
||||||
const handleConfirmSeriesBinding = async () => {
|
const handleConfirmSeriesBinding = async () => {
|
||||||
if (!seriesBindingFormRef.value) return
|
if (!seriesBindingFormRef.value) return
|
||||||
|
|||||||
@@ -232,7 +232,7 @@
|
|||||||
{
|
{
|
||||||
prop: 'perm_code',
|
prop: 'perm_code',
|
||||||
label: '权限标识',
|
label: '权限标识',
|
||||||
width: 200
|
width: 240
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'perm_type',
|
prop: 'perm_type',
|
||||||
|
|||||||
Reference in New Issue
Block a user