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 () => {
|
||||
try {
|
||||
// 调用退出登录接口
|
||||
@@ -117,19 +134,7 @@ export const useUserStore = defineStore(
|
||||
console.error('退出登录接口调用失败:', error)
|
||||
} finally {
|
||||
// 无论接口成功与否,都清理本地状态
|
||||
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)
|
||||
clearLocalState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +171,7 @@ export const useUserStore = defineStore(
|
||||
setLockStatus,
|
||||
setLockPassword,
|
||||
setToken,
|
||||
clearLocalState,
|
||||
logOut
|
||||
}
|
||||
},
|
||||
|
||||
@@ -93,9 +93,9 @@ axiosInstance.interceptors.response.use(
|
||||
const userStore = useUserStore()
|
||||
const originalRequest = response.config as any
|
||||
|
||||
// 如果没有 refreshToken,直接退出登录
|
||||
// 如果没有 refreshToken,直接清理本地状态(不调用退出接口)
|
||||
if (!userStore.refreshToken) {
|
||||
logOut()
|
||||
clearLocalStateAndRedirect()
|
||||
return Promise.reject(response)
|
||||
}
|
||||
|
||||
@@ -140,16 +140,16 @@ axiosInstance.interceptors.response.use(
|
||||
// 重试原请求
|
||||
resolve(axiosInstance.request(originalRequest))
|
||||
} else {
|
||||
// 刷新失败
|
||||
// 刷新失败 - 清理本地状态(不调用退出接口)
|
||||
processQueue(new Error('Token refresh failed'), null)
|
||||
logOut()
|
||||
clearLocalStateAndRedirect()
|
||||
reject(res)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
// 刷新失败
|
||||
// 刷新失败 - 清理本地状态(不调用退出接口)
|
||||
processQueue(err, null)
|
||||
logOut()
|
||||
clearLocalStateAndRedirect()
|
||||
reject(err)
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -162,7 +162,16 @@ axiosInstance.interceptors.response.use(
|
||||
(error) => {
|
||||
if (axios.isCancel(error)) {
|
||||
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处理
|
||||
return Promise.reject(error)
|
||||
}
|
||||
@@ -270,7 +279,7 @@ const api = {
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
// 退出登录(调用接口)
|
||||
const logOut = () => {
|
||||
ElMessage.error('登录已过期,请重新登录')
|
||||
setTimeout(() => {
|
||||
@@ -278,4 +287,12 @@ const logOut = () => {
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 仅清理本地状态(不调用接口)- 用于401等情况
|
||||
const clearLocalStateAndRedirect = () => {
|
||||
ElMessage.error('登录已过期,请重新登录')
|
||||
setTimeout(() => {
|
||||
useUserStore().clearLocalState()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
export default api
|
||||
|
||||
@@ -65,19 +65,16 @@
|
||||
<span style="font-weight: bold; color: #409eff">{{ selectedDevices.length }}</span> 台
|
||||
</ElFormItem>
|
||||
<ElFormItem label="目标店铺" prop="target_shop_id">
|
||||
<ElSelect
|
||||
<ElTreeSelect
|
||||
v-model="allocateForm.target_shop_id"
|
||||
:data="shopTreeData"
|
||||
:props="{ label: 'shop_name', value: 'id', children: 'children' }"
|
||||
placeholder="请选择目标店铺"
|
||||
clearable
|
||||
filterable
|
||||
check-strictly
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="shop in shopList"
|
||||
:key="shop.id"
|
||||
:label="shop.name"
|
||||
:value="shop.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="备注">
|
||||
<ElInput
|
||||
@@ -204,9 +201,14 @@
|
||||
<ElFormItem label="套餐系列分配" prop="series_allocation_id">
|
||||
<ElSelect
|
||||
v-model="seriesBindingForm.series_allocation_id"
|
||||
placeholder="请选择套餐系列分配(选择清除关联将解除绑定)"
|
||||
placeholder="请选择或搜索套餐系列分配(支持系列名称搜索)"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="searchSeriesAllocations"
|
||||
:loading="seriesLoading"
|
||||
clearable
|
||||
>
|
||||
<ElOption label="清除关联" :value="0" />
|
||||
<ElOption
|
||||
@@ -264,49 +266,133 @@
|
||||
</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">
|
||||
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
|
||||
</div>
|
||||
<ElDescriptions v-else-if="currentDeviceDetail" :column="3" border>
|
||||
<ElDescriptionsItem label="设备ID">{{ currentDeviceDetail.id }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备号" :span="2">{{
|
||||
currentDeviceDetail.device_no
|
||||
}}</ElDescriptionsItem>
|
||||
<div v-else-if="currentDeviceDetail">
|
||||
<!-- 设备基本信息 -->
|
||||
<ElDescriptions :column="3" border style="margin-bottom: 20px">
|
||||
<ElDescriptionsItem label="设备ID">{{ currentDeviceDetail.id }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备号" :span="2">{{
|
||||
currentDeviceDetail.device_no
|
||||
}}</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="设备名称">{{
|
||||
currentDeviceDetail.device_name || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备型号">{{
|
||||
currentDeviceDetail.device_model || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备类型">{{
|
||||
currentDeviceDetail.device_type || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备名称">{{
|
||||
currentDeviceDetail.device_name || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备型号">{{
|
||||
currentDeviceDetail.device_model || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备类型">{{
|
||||
currentDeviceDetail.device_type || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="制造商">{{
|
||||
currentDeviceDetail.manufacturer || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="最大插槽数">{{
|
||||
currentDeviceDetail.max_sim_slots
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="已绑定卡数量">{{
|
||||
currentDeviceDetail.bound_card_count
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="制造商">{{
|
||||
currentDeviceDetail.manufacturer || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="最大插槽数">{{
|
||||
currentDeviceDetail.max_sim_slots
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="已绑定卡数量">{{
|
||||
currentDeviceDetail.bound_card_count
|
||||
}}</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="状态">
|
||||
<ElTag :type="getDeviceStatusTagType(currentDeviceDetail.status)">
|
||||
{{ currentDeviceDetail.status_name }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="批次号" :span="2">{{
|
||||
currentDeviceDetail.batch_no || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="状态">
|
||||
<ElTag :type="getDeviceStatusTagType(currentDeviceDetail.status)">
|
||||
{{ currentDeviceDetail.status_name }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="批次号" :span="2">{{
|
||||
currentDeviceDetail.batch_no || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="创建时间" :span="3">{{
|
||||
currentDeviceDetail.created_at || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
<ElDescriptionsItem label="创建时间" :span="3">{{
|
||||
formatDateTime(currentDeviceDetail.created_at) || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
</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>
|
||||
</ElCard>
|
||||
</div>
|
||||
@@ -316,9 +402,9 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
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 { 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 type { FormInstance, FormRules } from 'element-plus'
|
||||
import type {
|
||||
@@ -347,7 +433,7 @@
|
||||
const allocateDialogVisible = ref(false)
|
||||
const recallDialogVisible = ref(false)
|
||||
const selectedDevices = ref<Device[]>([])
|
||||
const shopList = ref<any[]>([])
|
||||
const shopTreeData = ref<any[]>([])
|
||||
const allocateResult = ref<AllocateDevicesResponse | null>(null)
|
||||
const recallResult = ref<RecallDevicesResponse | null>(null)
|
||||
|
||||
@@ -369,6 +455,24 @@
|
||||
const deviceDetailDialogVisible = ref(false)
|
||||
const deviceDetailLoading = ref(false)
|
||||
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 = {
|
||||
@@ -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 typeMap: Record<number, any> = {
|
||||
@@ -610,10 +881,14 @@
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 100,
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
formatter: (row: Device) => {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
text: '查看卡片',
|
||||
onClick: () => handleViewCards(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => deleteDevice(row)
|
||||
@@ -625,21 +900,57 @@
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
loadShopList()
|
||||
loadShopTree()
|
||||
})
|
||||
|
||||
// 加载店铺列表
|
||||
const loadShopList = async () => {
|
||||
// 当页面被 keep-alive 激活时自动刷新数据
|
||||
onActivated(() => {
|
||||
getTableData()
|
||||
})
|
||||
|
||||
// 加载店铺树形数据
|
||||
const loadShopTree = async () => {
|
||||
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) {
|
||||
shopList.value = res.data.items || []
|
||||
shopTreeData.value = buildShopTree(res.data.items || [])
|
||||
}
|
||||
} catch (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 () => {
|
||||
loading.value = true
|
||||
@@ -724,7 +1035,7 @@
|
||||
}
|
||||
|
||||
// 批量分配
|
||||
const handleBatchAllocate = () => {
|
||||
const handleBatchAllocate = async () => {
|
||||
if (selectedDevices.value.length === 0) {
|
||||
ElMessage.warning('请先选择要分配的设备')
|
||||
return
|
||||
@@ -839,16 +1150,21 @@
|
||||
seriesBindingDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 加载套餐系列分配列表
|
||||
const loadSeriesAllocationList = async () => {
|
||||
// 加载套餐系列分配列表(默认加载20条)
|
||||
const loadSeriesAllocationList = async (seriesName?: string) => {
|
||||
seriesLoading.value = true
|
||||
try {
|
||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocations({
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 1000
|
||||
})
|
||||
if (res.code === 0 && res.data.list) {
|
||||
seriesAllocationList.value = res.data.list
|
||||
page_size: 20,
|
||||
status: 1 // 只获取启用的
|
||||
}
|
||||
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) {
|
||||
console.error('获取套餐系列分配列表失败:', error)
|
||||
@@ -858,6 +1174,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索套餐系列分配(根据系列名称)
|
||||
const searchSeriesAllocations = async (query: string) => {
|
||||
await loadSeriesAllocationList(query || undefined)
|
||||
}
|
||||
|
||||
// 确认设置套餐系列绑定
|
||||
const handleConfirmSeriesBinding = async () => {
|
||||
if (!seriesBindingFormRef.value) return
|
||||
|
||||
@@ -232,7 +232,7 @@
|
||||
{
|
||||
prop: 'perm_code',
|
||||
label: '权限标识',
|
||||
width: 200
|
||||
width: 240
|
||||
},
|
||||
{
|
||||
prop: 'perm_type',
|
||||
|
||||
Reference in New Issue
Block a user