This commit is contained in:
@@ -422,7 +422,7 @@
|
||||
"agent": "代理商管理",
|
||||
"customerAccount": "客户账号",
|
||||
"enterpriseCustomer": "企业客户",
|
||||
"enterpriseCustomerAccounts": "客户账号列表",
|
||||
"enterpriseCustomerAccounts": "关联账号列表",
|
||||
"enterpriseCards": "企业卡管理",
|
||||
"customerCommission": "客户账号佣金"
|
||||
},
|
||||
|
||||
@@ -296,6 +296,15 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
roles: ['R_SUPER']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'carrier-management',
|
||||
name: 'CarrierManagement',
|
||||
component: RoutesAlias.CarrierManagement,
|
||||
meta: {
|
||||
title: 'menus.account.carrierManagement',
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'user-center',
|
||||
name: 'UserCenter',
|
||||
@@ -611,15 +620,15 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
// }
|
||||
// },
|
||||
// 物联网卡管理系统模块
|
||||
{
|
||||
path: '/card-management',
|
||||
name: 'CardManagement',
|
||||
component: RoutesAlias.Home,
|
||||
meta: {
|
||||
title: 'menus.cardManagement.title',
|
||||
icon: ''
|
||||
},
|
||||
children: [
|
||||
// {
|
||||
// path: '/card-management',
|
||||
// name: 'CardManagement',
|
||||
// component: RoutesAlias.Home,
|
||||
// meta: {
|
||||
// title: 'menus.cardManagement.title',
|
||||
// icon: ''
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'card-detail',
|
||||
// name: 'CardDetail',
|
||||
@@ -629,15 +638,15 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
// keepAlive: true
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: 'single-card',
|
||||
name: 'SingleCard',
|
||||
component: RoutesAlias.SingleCard,
|
||||
meta: {
|
||||
title: 'menus.cardManagement.singleCard',
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
// {
|
||||
// path: 'single-card',
|
||||
// name: 'SingleCard',
|
||||
// component: RoutesAlias.SingleCard,
|
||||
// meta: {
|
||||
// title: 'menus.cardManagement.singleCard',
|
||||
// keepAlive: true
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'card-assign',
|
||||
// name: 'CardAssign',
|
||||
@@ -701,8 +710,8 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
// keepAlive: true
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
path: '/package-management',
|
||||
name: 'PackageManagement',
|
||||
@@ -969,6 +978,15 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
icon: ''
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'single-card',
|
||||
name: 'SingleCard',
|
||||
component: RoutesAlias.SingleCard,
|
||||
meta: {
|
||||
title: 'menus.cardManagement.singleCard',
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'iot-card-management',
|
||||
name: 'StandaloneCardList',
|
||||
@@ -1072,15 +1090,6 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
// keepAlive: true
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: 'carrier-management',
|
||||
name: 'CarrierManagement',
|
||||
component: RoutesAlias.CarrierManagement,
|
||||
meta: {
|
||||
title: 'menus.account.carrierManagement',
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'orders',
|
||||
name: 'OrderManagement',
|
||||
@@ -1089,7 +1098,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
title: 'menus.account.orders',
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
}
|
||||
// {
|
||||
// path: 'my-account',
|
||||
// name: 'MyAccount',
|
||||
@@ -1099,10 +1108,12 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
// keepAlive: true
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'commission',
|
||||
path: '/commission',
|
||||
name: 'CommissionManagement',
|
||||
component: '',
|
||||
component: RoutesAlias.Home,
|
||||
meta: {
|
||||
title: 'menus.commission.menu.management',
|
||||
icon: ''
|
||||
@@ -1150,8 +1161,6 @@ export const asyncRoutes: AppRouteRecord[] = [
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
// {
|
||||
// path: '/settings',
|
||||
// name: 'Settings',
|
||||
|
||||
@@ -68,44 +68,6 @@
|
||||
>
|
||||
<ElOption label="超级管理员" :value="1" />
|
||||
<ElOption label="平台用户" :value="2" />
|
||||
<ElOption label="代理账号" :value="3" />
|
||||
<ElOption label="企业账号" :value="4" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="dialogType === 'add' && formData.user_type === 3" label="关联店铺" prop="shop_id">
|
||||
<ElSelect
|
||||
v-model="formData.shop_id"
|
||||
placeholder="请输入店铺名称搜索"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleShopSearch"
|
||||
clearable
|
||||
>
|
||||
<ElOption
|
||||
v-for="shop in shopList"
|
||||
:key="shop.id"
|
||||
:label="shop.shop_name"
|
||||
:value="shop.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="dialogType === 'add' && formData.user_type === 4" label="关联企业" prop="enterprise_id">
|
||||
<ElSelect
|
||||
v-model="formData.enterprise_id"
|
||||
placeholder="请输入企业名称搜索"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="handleEnterpriseSearch"
|
||||
clearable
|
||||
>
|
||||
<ElOption
|
||||
v-for="enterprise in enterpriseList"
|
||||
:key="enterprise.id"
|
||||
:label="enterprise.enterprise_name"
|
||||
:value="enterprise.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
@@ -124,6 +86,9 @@
|
||||
<span class="dialog-title">分配角色</span>
|
||||
<div class="account-info">
|
||||
<span class="account-name">{{ currentAccountName }}</span>
|
||||
<ElTag v-if="currentAccountType === 3" type="warning" size="small" style="margin-left: 8px">
|
||||
代理账号只能分配一个客户角色
|
||||
</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -142,18 +107,11 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ElCheckboxGroup v-model="rolesToAdd" class="role-list">
|
||||
<div
|
||||
v-for="role in filteredAvailableRoles"
|
||||
:key="role.ID"
|
||||
class="role-item"
|
||||
>
|
||||
<div v-for="role in filteredAvailableRoles" :key="role.ID" class="role-item">
|
||||
<ElCheckbox :label="role.ID" :disabled="selectedRoles.includes(role.ID)">
|
||||
<span class="role-info">
|
||||
<span>{{ role.role_name }}</span>
|
||||
<ElTag
|
||||
:type="role.role_type === 1 ? 'primary' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||
</ElTag>
|
||||
</span>
|
||||
@@ -196,19 +154,11 @@
|
||||
>
|
||||
<span class="role-info">
|
||||
<span>{{ role.role_name }}</span>
|
||||
<ElTag
|
||||
:type="role.role_type === 1 ? 'primary' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||
</ElTag>
|
||||
</span>
|
||||
<ElButton
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
@click="removeSingleRole(role.ID)"
|
||||
>
|
||||
<ElButton type="danger" size="small" link @click="removeSingleRole(role.ID)">
|
||||
移除
|
||||
</ElButton>
|
||||
</div>
|
||||
@@ -256,6 +206,7 @@
|
||||
const roleSubmitLoading = ref(false)
|
||||
const currentAccountId = ref<number>(0)
|
||||
const currentAccountName = ref<string>('')
|
||||
const currentAccountType = ref<number>(0)
|
||||
const selectedRoles = ref<number[]>([])
|
||||
const allRoles = ref<PlatformRole[]>([])
|
||||
const rolesToAdd = ref<number[]>([])
|
||||
@@ -517,7 +468,7 @@
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 200,
|
||||
width: 240,
|
||||
fixed: 'right',
|
||||
formatter: (row: any) => {
|
||||
const buttons = []
|
||||
@@ -525,7 +476,7 @@
|
||||
if (hasAuth('account:patch_role')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
icon: '',
|
||||
text: '分配角色',
|
||||
onClick: () => showRoleDialog(row)
|
||||
})
|
||||
)
|
||||
@@ -534,7 +485,7 @@
|
||||
if (hasAuth('account:edit')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
text: '编辑',
|
||||
onClick: () => showDialog('edit', row)
|
||||
})
|
||||
)
|
||||
@@ -543,7 +494,7 @@
|
||||
if (hasAuth('account:delete')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
text: '删除',
|
||||
onClick: () => deleteAccount(row)
|
||||
})
|
||||
)
|
||||
@@ -593,11 +544,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 计算属性:过滤后的可分配角色
|
||||
// 计算属性:过滤后的可分配角色(根据账号类型过滤)
|
||||
const filteredAvailableRoles = computed(() => {
|
||||
if (!leftRoleFilter.value) return allRoles.value
|
||||
let roles = allRoles.value
|
||||
|
||||
// 根据账号类型过滤角色
|
||||
if (currentAccountType.value === 3) {
|
||||
// 代理账号:只显示客户角色
|
||||
roles = roles.filter((role) => role.role_type === 2)
|
||||
} else if (currentAccountType.value === 2) {
|
||||
// 平台用户:只显示平台角色
|
||||
roles = roles.filter((role) => role.role_type === 1)
|
||||
}
|
||||
|
||||
// 根据搜索关键词过滤
|
||||
if (!leftRoleFilter.value) return roles
|
||||
const keyword = leftRoleFilter.value.toLowerCase()
|
||||
return allRoles.value.filter((role) => role.role_name.toLowerCase().includes(keyword))
|
||||
return roles.filter((role) => role.role_name.toLowerCase().includes(keyword))
|
||||
})
|
||||
|
||||
// 计算属性:过滤后的已分配角色
|
||||
@@ -612,6 +575,7 @@
|
||||
const showRoleDialog = async (row: any) => {
|
||||
currentAccountId.value = row.id
|
||||
currentAccountName.value = row.username
|
||||
currentAccountType.value = row.user_type
|
||||
selectedRoles.value = []
|
||||
rolesToAdd.value = []
|
||||
leftRoleFilter.value = ''
|
||||
@@ -641,18 +605,31 @@
|
||||
if (rolesToAdd.value.length === 0) return
|
||||
|
||||
try {
|
||||
// 将选中的角色添加到已分配列表
|
||||
const newRoles = [...new Set([...selectedRoles.value, ...rolesToAdd.value])]
|
||||
let newRoles: number[]
|
||||
|
||||
// 代理账号只能分配一个角色,会覆盖之前的角色
|
||||
if (currentAccountType.value === 3) {
|
||||
if (rolesToAdd.value.length > 1) {
|
||||
ElMessage.warning('代理账号只能分配一个角色')
|
||||
return
|
||||
}
|
||||
// 只保留新选择的一个角色
|
||||
newRoles = rolesToAdd.value
|
||||
} else {
|
||||
// 其他账号类型可以分配多个角色
|
||||
newRoles = [...new Set([...selectedRoles.value, ...rolesToAdd.value])]
|
||||
}
|
||||
|
||||
await AccountService.assignRolesToAccount(currentAccountId.value, newRoles)
|
||||
|
||||
selectedRoles.value = newRoles
|
||||
rolesToAdd.value = []
|
||||
|
||||
ElMessage.success('角色添加成功')
|
||||
ElMessage.success('角色分配成功')
|
||||
// 刷新列表以更新角色显示
|
||||
await getAccountList()
|
||||
} catch (error) {
|
||||
console.error('添加角色失败:', error)
|
||||
console.error('分配角色失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,25 +39,39 @@
|
||||
<!-- 授权详情对话框 -->
|
||||
<ElDialog v-model="detailDialogVisible" title="授权详情" width="700px">
|
||||
<ElDescriptions v-if="currentRecord" :column="2" border>
|
||||
<ElDescriptionsItem label="企业ID">{{ currentRecord.enterprise_id }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="企业名称">{{ currentRecord.enterprise_name }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="企业ID">{{
|
||||
currentRecord.enterprise_id
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="企业名称">{{
|
||||
currentRecord.enterprise_name
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="卡ID">{{ currentRecord.card_id }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="ICCID">{{ currentRecord.iccid }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="手机号">{{ currentRecord.msisdn || '--' }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="授权人ID">{{ currentRecord.authorized_by }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="授权人">{{ currentRecord.authorizer_name }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="手机号">{{
|
||||
currentRecord.msisdn || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="授权人ID">{{
|
||||
currentRecord.authorized_by
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="授权人">{{
|
||||
currentRecord.authorizer_name
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="授权人类型">
|
||||
<ElTag :type="getAuthorizerTypeTag(currentRecord.authorizer_type)">
|
||||
{{ getAuthorizerTypeText(currentRecord.authorizer_type) }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="授权时间" :span="2">{{ formatDateTime(currentRecord.authorized_at) }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="授权时间" :span="2">{{
|
||||
formatDateTime(currentRecord.authorized_at)
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="状态" :span="2">
|
||||
<ElTag :type="getStatusTag(currentRecord.status)">
|
||||
{{ getStatusText(currentRecord.status) }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="备注" :span="2">{{ currentRecord.remark || '--' }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="备注" :span="2">{{
|
||||
currentRecord.remark || '--'
|
||||
}}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
@@ -311,7 +325,7 @@
|
||||
if (hasAuth('authorization_records:update_remark')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
text: '编辑',
|
||||
onClick: () => showRemarkDialog(row)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -188,26 +188,6 @@
|
||||
@close="handleRecallDialogClose"
|
||||
>
|
||||
<ElForm ref="recallFormRef" :model="recallForm" :rules="recallRules" label-width="120px">
|
||||
<ElFormItem label="来源店铺" prop="from_shop_id">
|
||||
<ElSelect
|
||||
v-model="recallForm.from_shop_id"
|
||||
placeholder="请选择或搜索来源店铺"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="searchFromShops"
|
||||
:loading="fromShopLoading"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="shop in fromShopList"
|
||||
:key="shop.id"
|
||||
:label="shop.shop_name"
|
||||
:value="shop.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="选卡方式" prop="selection_type">
|
||||
<ElRadioGroup v-model="recallForm.selection_type">
|
||||
<ElRadio label="list">ICCID列表</ElRadio>
|
||||
@@ -755,7 +735,6 @@
|
||||
// 批量回收表单
|
||||
const recallForm = reactive<Partial<RecallStandaloneCardsRequest>>({
|
||||
selection_type: 'list',
|
||||
from_shop_id: undefined,
|
||||
iccids: [],
|
||||
iccid_start: '',
|
||||
iccid_end: '',
|
||||
@@ -766,7 +745,6 @@
|
||||
|
||||
// 批量回收表单验证规则
|
||||
const recallRules = reactive<FormRules>({
|
||||
from_shop_id: [{ required: true, message: '请选择来源店铺', trigger: 'change' }],
|
||||
selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }],
|
||||
iccid_start: [
|
||||
{
|
||||
@@ -1285,7 +1263,6 @@
|
||||
recallDialogVisible.value = true
|
||||
Object.assign(recallForm, {
|
||||
selection_type: 'list',
|
||||
from_shop_id: undefined,
|
||||
iccids: selectedCards.value.map((card) => card.iccid),
|
||||
iccid_start: '',
|
||||
iccid_end: '',
|
||||
@@ -1293,8 +1270,6 @@
|
||||
batch_no: '',
|
||||
remark: ''
|
||||
})
|
||||
// 加载默认店铺列表
|
||||
loadFromShops()
|
||||
if (recallFormRef.value) {
|
||||
recallFormRef.value.resetFields()
|
||||
}
|
||||
@@ -1378,7 +1353,6 @@
|
||||
// 根据选卡方式构建请求参数
|
||||
const params: Partial<RecallStandaloneCardsRequest> = {
|
||||
selection_type: recallForm.selection_type!,
|
||||
from_shop_id: recallForm.from_shop_id!,
|
||||
remark: recallForm.remark
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +1,89 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="single-card-page" id="table-full-screen">
|
||||
<!-- ICCID查询区域 -->
|
||||
<ElCard shadow="never" class="search-card" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<span>ICCID查询</span>
|
||||
</template>
|
||||
<div class="iccid-search">
|
||||
<ElInput
|
||||
v-model="searchIccid"
|
||||
placeholder="请输入ICCID"
|
||||
clearable
|
||||
style="width: 400px; margin-right: 16px"
|
||||
@keyup.enter="handleSearchCard"
|
||||
>
|
||||
<template #prepend>ICCID</template>
|
||||
</ElInput>
|
||||
<ElButton type="primary" @click="handleSearchCard" :loading="loading">查询</ElButton>
|
||||
</div>
|
||||
<!-- 格式化显示的ICCID -->
|
||||
<div v-if="formattedIccid" class="formatted-iccid">
|
||||
{{ formattedIccid }}
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<!-- 网卡信息卡片 -->
|
||||
<ElCard shadow="never" class="card-info-card" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<span>网卡信息</span>
|
||||
</template>
|
||||
<ElForm :model="cardInfo" label-width="120px" :inline="false">
|
||||
<div v-if="!hasSearched" class="empty-state">
|
||||
<p style="text-align: center; color: var(--el-text-color-secondary); padding: 40px">
|
||||
请在上方输入ICCID进行查询
|
||||
</p>
|
||||
</div>
|
||||
<ElForm v-else :model="cardInfo" label-width="120px" :inline="false">
|
||||
<ElRow :gutter="24">
|
||||
<ElCol :span="8">
|
||||
<ElFormItem label="ICCID:">
|
||||
<span>{{ cardInfo.iccid }}</span>
|
||||
<span>{{ cardInfo.iccid || '-' }}</span>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
<ElFormItem label="IMSI:">
|
||||
<span>{{ cardInfo.imsi }}</span>
|
||||
<span>{{ cardInfo.imsi || '-' }}</span>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
<ElFormItem label="手机号码:">
|
||||
<span>{{ cardInfo.msisdn }}</span>
|
||||
<span>{{ cardInfo.msisdn || '-' }}</span>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="24">
|
||||
<ElCol :span="8">
|
||||
<ElFormItem label="运营商:">
|
||||
<ElTag :type="getOperatorTagType(cardInfo.operator)">{{
|
||||
cardInfo.operatorName
|
||||
}}</ElTag>
|
||||
<ElTag v-if="cardInfo.operatorName" :type="getOperatorTagType(cardInfo.operator)">
|
||||
{{ cardInfo.operatorName }}
|
||||
</ElTag>
|
||||
<span v-else>-</span>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
<ElFormItem label="网络类型:">
|
||||
<span>{{ cardInfo.networkType }}</span>
|
||||
<span>{{ cardInfo.networkType || '-' }}</span>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
<ElFormItem label="状态:">
|
||||
<ElTag :type="getStatusTagType(cardInfo.status)">{{ cardInfo.statusName }}</ElTag>
|
||||
<ElTag v-if="cardInfo.statusName" :type="getStatusTagType(cardInfo.status)">
|
||||
{{ cardInfo.statusName }}
|
||||
</ElTag>
|
||||
<span v-else>-</span>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="24">
|
||||
<ElCol :span="8">
|
||||
<ElFormItem label="激活时间:">
|
||||
<span>{{ cardInfo.activatedDate || '-' }}</span>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="8">
|
||||
<ElFormItem label="过期时间:">
|
||||
<span>{{ cardInfo.expiryDate || '-' }}</span>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
@@ -47,7 +91,7 @@
|
||||
</ElCard>
|
||||
|
||||
<!-- 操作区域 -->
|
||||
<ElCard shadow="never" class="operation-card" style="margin-bottom: 16px">
|
||||
<ElCard v-if="hasSearched" shadow="never" class="operation-card" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<span>操作区域</span>
|
||||
</template>
|
||||
@@ -66,7 +110,7 @@
|
||||
</ElCard>
|
||||
|
||||
<!-- 流量信息 -->
|
||||
<ElCard shadow="never" class="traffic-card" style="margin-bottom: 16px">
|
||||
<ElCard v-if="hasSearched" shadow="never" class="traffic-card" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<span>流量信息</span>
|
||||
</template>
|
||||
@@ -100,7 +144,7 @@
|
||||
</ElCard>
|
||||
|
||||
<!-- 使用记录 -->
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<ElCard v-if="hasSearched" shadow="never" class="art-table-card">
|
||||
<template #header>
|
||||
<span>使用记录</span>
|
||||
<ElButton style="float: right" @click="refreshUsageRecords">刷新</ElButton>
|
||||
@@ -158,6 +202,7 @@
|
||||
import { ElTag, ElMessageBox, ElMessage, FormInstance } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormRules } from 'element-plus'
|
||||
import { CardService } from '@/api/modules'
|
||||
|
||||
defineOptions({ name: 'SingleCard' })
|
||||
|
||||
@@ -165,26 +210,36 @@
|
||||
const rechargeDialogVisible = ref(false)
|
||||
const route = useRoute()
|
||||
|
||||
// 网卡信息
|
||||
const cardInfo = reactive({
|
||||
iccid: '89860123456789012345',
|
||||
imsi: '460012345678901',
|
||||
msisdn: '13800138001',
|
||||
operator: 'mobile',
|
||||
operatorName: '中国移动',
|
||||
networkType: '4G',
|
||||
status: '1',
|
||||
statusName: '激活',
|
||||
activatedDate: '2024-01-15',
|
||||
expiryDate: '2025-01-15'
|
||||
// ICCID搜索相关
|
||||
const searchIccid = ref('')
|
||||
const hasSearched = ref(false)
|
||||
|
||||
// 格式化显示的ICCID(4位一组,用横杠分隔)
|
||||
const formattedIccid = computed(() => {
|
||||
if (!cardInfo.iccid) return ''
|
||||
return cardInfo.iccid.replace(/(\d{4})(?=\d)/g, '$1-')
|
||||
})
|
||||
|
||||
// 流量信息
|
||||
// 网卡信息 - 默认为空
|
||||
const cardInfo = reactive({
|
||||
iccid: '',
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
operator: '',
|
||||
operatorName: '',
|
||||
networkType: '',
|
||||
status: '',
|
||||
statusName: '',
|
||||
activatedDate: '',
|
||||
expiryDate: ''
|
||||
})
|
||||
|
||||
// 流量信息 - 默认为空
|
||||
const trafficInfo = reactive({
|
||||
totalTraffic: '10GB',
|
||||
usedTraffic: '2.5GB',
|
||||
remainingTraffic: '7.5GB',
|
||||
usagePercentage: '25'
|
||||
totalTraffic: '0MB',
|
||||
usedTraffic: '0MB',
|
||||
remainingTraffic: '0MB',
|
||||
usagePercentage: '0'
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
@@ -193,25 +248,8 @@
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 使用记录
|
||||
const usageRecords = ref([
|
||||
{
|
||||
id: 1,
|
||||
date: '2024-11-07',
|
||||
time: '14:30:25',
|
||||
dataUsage: '125.6MB',
|
||||
fee: '0.12',
|
||||
location: '北京市朝阳区'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
date: '2024-11-07',
|
||||
time: '13:45:12',
|
||||
dataUsage: '256.8MB',
|
||||
fee: '0.26',
|
||||
location: '北京市朝阳区'
|
||||
}
|
||||
])
|
||||
// 使用记录 - 默认为空
|
||||
const usageRecords = ref([])
|
||||
|
||||
// 充值表单
|
||||
const rechargeForm = reactive({
|
||||
@@ -394,40 +432,107 @@
|
||||
pagination.total = usageRecords.value.length
|
||||
})
|
||||
|
||||
// 处理ICCID搜索
|
||||
const handleSearchCard = async () => {
|
||||
if (!searchIccid.value.trim()) {
|
||||
ElMessage.warning('请输入ICCID')
|
||||
return
|
||||
}
|
||||
await loadCardInfoByIccid(searchIccid.value.trim())
|
||||
}
|
||||
|
||||
// 根据ICCID加载卡片信息
|
||||
const loadCardInfoByIccid = async (iccid: string) => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 这里应该调用API根据ICCID获取卡片详细信息
|
||||
// 模拟API调用
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
const response = await CardService.getIotCardDetailByIccid(iccid)
|
||||
|
||||
// 模拟更新卡片信息(实际应该从API获取)
|
||||
if (response.code === 200 && response.data) {
|
||||
const data = response.data
|
||||
hasSearched.value = true
|
||||
|
||||
// 更新网卡基本信息
|
||||
Object.assign(cardInfo, {
|
||||
iccid: iccid,
|
||||
imsi: '460012345678901',
|
||||
msisdn: '13800138001',
|
||||
operator: 'mobile',
|
||||
operatorName: '中国移动',
|
||||
networkType: '4G',
|
||||
status: '1',
|
||||
statusName: '激活',
|
||||
activatedDate: '2024-01-15',
|
||||
expiryDate: '2025-01-15'
|
||||
iccid: data.iccid || '',
|
||||
imsi: data.imsi || '',
|
||||
msisdn: data.msisdn || '',
|
||||
operator: data.carrier_type || '',
|
||||
operatorName: data.carrier_name || '',
|
||||
networkType: data.network_type || '',
|
||||
status: String(data.status || ''),
|
||||
statusName: getStatusName(data.status),
|
||||
activatedDate: data.activated_at || '',
|
||||
expiryDate: data.expire_at || ''
|
||||
})
|
||||
|
||||
ElMessage.success(`已加载ICCID ${iccid} 的详细信息`)
|
||||
} catch (error) {
|
||||
// 更新流量信息
|
||||
const totalMB = data.data_usage_mb || 0
|
||||
const usedMB = data.current_month_usage_mb || 0
|
||||
const remainingMB = totalMB - usedMB
|
||||
const percentage = totalMB > 0 ? Math.round((usedMB / totalMB) * 100) : 0
|
||||
|
||||
Object.assign(trafficInfo, {
|
||||
totalTraffic: formatDataSize(totalMB),
|
||||
usedTraffic: formatDataSize(usedMB),
|
||||
remainingTraffic: formatDataSize(remainingMB),
|
||||
usagePercentage: String(percentage)
|
||||
})
|
||||
|
||||
ElMessage.success('查询成功')
|
||||
} else {
|
||||
ElMessage.error(response.message || '查询失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取卡片信息失败:', error)
|
||||
ElMessage.error('获取卡片信息失败')
|
||||
ElMessage.error(error?.message || '获取卡片信息失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态名称
|
||||
const getStatusName = (status: number) => {
|
||||
const statusMap: Record<number, string> = {
|
||||
0: '未激活',
|
||||
1: '激活',
|
||||
2: '停用',
|
||||
3: '已过期',
|
||||
4: '已注销'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 格式化数据大小
|
||||
const formatDataSize = (mb: number) => {
|
||||
if (mb >= 1024) {
|
||||
return `${(mb / 1024).toFixed(2)}GB`
|
||||
}
|
||||
return `${mb.toFixed(2)}MB`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.single-card-page {
|
||||
.search-card {
|
||||
.iccid-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.formatted-iccid {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
background-color: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
text-align: center;
|
||||
color: var(--el-color-primary);
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<ElCard shadow="never">
|
||||
<div class="detail-header">
|
||||
<ElButton @click="handleBack">
|
||||
<template #icon><ElIcon><ArrowLeft /></ElIcon></template>
|
||||
<template #icon
|
||||
><ElIcon><ArrowLeft /></ElIcon
|
||||
></template>
|
||||
返回
|
||||
</ElButton>
|
||||
<h2 class="detail-title">{{ pageTitle }}</h2>
|
||||
@@ -19,6 +21,13 @@
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div style="margin: 10px 0">
|
||||
<ElButton @click="showAddAccountDialog">
|
||||
{{ isShopType ? '新增店铺账号' : '新增企业账号' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
@@ -28,28 +37,176 @@
|
||||
:currentPage="pagination.page"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"v
|
||||
:marginTop="10"
|
||||
v
|
||||
height="60vh"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || (col as any).type" v-bind="col" />
|
||||
<ElTableColumn
|
||||
v-for="col in columns"
|
||||
:key="col.prop || (col as any).type"
|
||||
v-bind="col"
|
||||
/>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 新增账号对话框 -->
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="isShopType ? '新增店铺账号' : '新增企业账号'"
|
||||
width="500px"
|
||||
>
|
||||
<ElForm ref="formRef" :model="accountForm" :rules="accountRules" label-width="100px">
|
||||
<ElFormItem label="用户名" prop="username">
|
||||
<ElInput v-model="accountForm.username" placeholder="请输入用户名" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="密码" prop="password">
|
||||
<ElInput
|
||||
v-model="accountForm.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="手机号" prop="phone">
|
||||
<ElInput v-model="accountForm.phone" placeholder="请输入手机号" maxlength="11" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="账号类型" prop="user_type">
|
||||
<ElInput :value="isShopType ? '代理账号' : '企业账号'" disabled />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit" :loading="submitLoading">
|
||||
提交
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 分配角色对话框 -->
|
||||
<ElDialog v-model="roleDialogVisible" width="900px">
|
||||
<template #header>
|
||||
<div class="dialog-header">
|
||||
<span class="dialog-title">分配角色</span>
|
||||
<div class="account-info">
|
||||
<span class="account-name">{{ currentAccountName }}</span>
|
||||
<ElTag v-if="currentAccountType === 3" type="warning" size="small" style="margin-left: 8px">
|
||||
代理账号只能分配一个客户角色
|
||||
</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="role-transfer-container">
|
||||
<!-- 左侧:可分配角色列表 -->
|
||||
<div class="transfer-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">可分配角色</span>
|
||||
<ElInput
|
||||
v-model="leftRoleFilter"
|
||||
placeholder="搜索角色"
|
||||
clearable
|
||||
size="small"
|
||||
style="width: 180px"
|
||||
/>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ElCheckboxGroup v-model="rolesToAdd" class="role-list">
|
||||
<div v-for="role in filteredAvailableRoles" :key="role.ID" class="role-item">
|
||||
<ElCheckbox :label="role.ID" :disabled="selectedRoles.includes(role.ID)">
|
||||
<span class="role-info">
|
||||
<span>{{ role.role_name }}</span>
|
||||
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||
</ElTag>
|
||||
</span>
|
||||
</ElCheckbox>
|
||||
</div>
|
||||
</ElCheckboxGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间:操作按钮 -->
|
||||
<div class="transfer-buttons">
|
||||
<ElButton
|
||||
type="primary"
|
||||
:icon="'ArrowRight'"
|
||||
@click="addRoles"
|
||||
:disabled="rolesToAdd.length === 0"
|
||||
>
|
||||
添加
|
||||
</ElButton>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:已分配角色列表 -->
|
||||
<div class="transfer-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">已分配角色</span>
|
||||
<ElInput
|
||||
v-model="rightRoleFilter"
|
||||
placeholder="搜索角色"
|
||||
clearable
|
||||
size="small"
|
||||
style="width: 180px"
|
||||
/>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="role-list">
|
||||
<div
|
||||
v-for="role in filteredAssignedRoles"
|
||||
:key="role.ID"
|
||||
class="role-item assigned-role-item"
|
||||
>
|
||||
<span class="role-info">
|
||||
<span>{{ role.role_name }}</span>
|
||||
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||
</ElTag>
|
||||
</span>
|
||||
<ElButton type="danger" size="small" link @click="removeSingleRole(role.ID)">
|
||||
移除
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="roleDialogVisible = false">关闭</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h, computed, ref, reactive, onMounted } from 'vue'
|
||||
import { h, computed, ref, reactive, onMounted, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElTag, ElIcon, ElButton, ElCard } from 'element-plus'
|
||||
import {
|
||||
ElMessage,
|
||||
ElTag,
|
||||
ElIcon,
|
||||
ElButton,
|
||||
ElCard,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElCheckbox,
|
||||
ElCheckboxGroup
|
||||
} from 'element-plus'
|
||||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||||
import { AccountService } from '@/api/modules'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { AccountService, RoleService } from '@/api/modules'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import type { PlatformAccount } from '@/types/api'
|
||||
import type { PlatformAccount, PlatformRole } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
|
||||
defineOptions({ name: 'CommonAccountList' })
|
||||
@@ -86,6 +243,17 @@
|
||||
const tableRef = ref()
|
||||
const accountList = ref<PlatformAccount[]>([])
|
||||
|
||||
// 分配角色相关变量
|
||||
const roleDialogVisible = ref(false)
|
||||
const currentAccountId = ref<number>(0)
|
||||
const currentAccountName = ref<string>('')
|
||||
const currentAccountType = ref<number>(0)
|
||||
const selectedRoles = ref<number[]>([])
|
||||
const allRoles = ref<PlatformRole[]>([])
|
||||
const rolesToAdd = ref<number[]>([])
|
||||
const leftRoleFilter = ref('')
|
||||
const rightRoleFilter = ref('')
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
username: '',
|
||||
@@ -124,19 +292,6 @@
|
||||
placeholder: '请输入手机号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '用户类型',
|
||||
prop: 'user_type',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '代理账号', value: 3 },
|
||||
{ label: '企业账号', value: 4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
@@ -163,52 +318,74 @@
|
||||
}
|
||||
|
||||
// 列配置
|
||||
const columns = computed(() => [
|
||||
const columns = computed(() => {
|
||||
const baseColumns = [
|
||||
{
|
||||
prop: 'username',
|
||||
label: '用户名',
|
||||
minWidth: 150
|
||||
label: '用户名'
|
||||
},
|
||||
{
|
||||
prop: 'phone',
|
||||
label: '手机号',
|
||||
width: 130
|
||||
label: '手机号'
|
||||
},
|
||||
{
|
||||
prop: 'user_type',
|
||||
label: '用户类型',
|
||||
width: 110,
|
||||
formatter: (row: PlatformAccount) => {
|
||||
return h(ElTag, { type: getUserTypeTag(row.user_type) }, () => getUserTypeName(row.user_type))
|
||||
return h(ElTag, { type: getUserTypeTag(row.user_type) }, () =>
|
||||
getUserTypeName(row.user_type)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'shop_id',
|
||||
label: '店铺ID',
|
||||
width: 100,
|
||||
formatter: (row: PlatformAccount) => row.shop_id || '-'
|
||||
},
|
||||
{
|
||||
prop: 'enterprise_id',
|
||||
label: '企业ID',
|
||||
width: 100,
|
||||
formatter: (row: PlatformAccount) => row.enterprise_id || '-'
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
// 根据页面类型添加不同的列
|
||||
if (isShopType.value) {
|
||||
// 店铺类型:显示店铺名称
|
||||
baseColumns.push({
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
formatter: (row: PlatformAccount) => (row as any).shop_name || '-'
|
||||
})
|
||||
} else {
|
||||
// 企业类型:显示企业名称
|
||||
baseColumns.push({
|
||||
prop: 'enterprise_name',
|
||||
label: '企业名称',
|
||||
formatter: (row: PlatformAccount) => (row as any).enterprise_name || '-'
|
||||
})
|
||||
}
|
||||
|
||||
// 添加状态和创建时间
|
||||
baseColumns.push(
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row: PlatformAccount) => {
|
||||
return h(ElTag, { type: getStatusTag(row.status) }, () => getStatusName(row.status))
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'CreatedAt',
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: PlatformAccount) => formatDateTime(row.CreatedAt)
|
||||
formatter: (row: PlatformAccount) => formatDateTime((row as any).created_at)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
formatter: (row: PlatformAccount) => {
|
||||
return h(ArtButtonTable, {
|
||||
text: '分配角色',
|
||||
onClick: () => showRoleDialog(row)
|
||||
})
|
||||
}
|
||||
])
|
||||
}
|
||||
)
|
||||
|
||||
return baseColumns
|
||||
})
|
||||
|
||||
// 获取账号列表
|
||||
const getTableData = async () => {
|
||||
@@ -220,7 +397,7 @@
|
||||
pageSize: pagination.pageSize,
|
||||
username: searchForm.username || undefined,
|
||||
phone: searchForm.phone || undefined,
|
||||
user_type: searchForm.user_type,
|
||||
user_type: isShopType.value ? 3 : 4, // 根据页面类型自动设置: 3:代理账号, 4:企业账号
|
||||
status: searchForm.status,
|
||||
[filterParamKey.value]: entityId // 动态设置 shop_id 或 enterprise_id
|
||||
}
|
||||
@@ -274,8 +451,214 @@
|
||||
router.back()
|
||||
}
|
||||
|
||||
// 对话框相关
|
||||
const dialogVisible = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 账号表单数据
|
||||
const accountForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
phone: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const accountRules = reactive<FormRules>({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '密码长度为 6-20 个字符', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ len: 11, message: '手机号必须为 11 位', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
// 显示新增账号对话框
|
||||
const showAddAccountDialog = () => {
|
||||
// 重置表单
|
||||
accountForm.username = ''
|
||||
accountForm.password = ''
|
||||
accountForm.phone = ''
|
||||
|
||||
// 重置表单验证状态
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate()
|
||||
})
|
||||
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const entityId = Number(route.params.id)
|
||||
const data: any = {
|
||||
username: accountForm.username,
|
||||
password: accountForm.password,
|
||||
phone: accountForm.phone,
|
||||
user_type: isShopType.value ? 3 : 4 // 3:代理账号, 4:企业账号
|
||||
}
|
||||
|
||||
// 根据类型添加关联ID
|
||||
if (isShopType.value) {
|
||||
data.shop_id = entityId
|
||||
} else {
|
||||
data.enterprise_id = entityId
|
||||
}
|
||||
|
||||
await AccountService.createAccount(data)
|
||||
ElMessage.success('添加账号成功')
|
||||
dialogVisible.value = false
|
||||
|
||||
// 刷新列表
|
||||
getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
ElMessage.error('添加账号失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 加载所有角色列表
|
||||
const loadAllRoles = async () => {
|
||||
try {
|
||||
const res = await RoleService.getRoles({ page: 1, pageSize: 100 })
|
||||
if (res.code === 0) {
|
||||
allRoles.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取角色列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算属性:过滤后的可分配角色(根据账号类型过滤)
|
||||
const filteredAvailableRoles = computed(() => {
|
||||
let roles = allRoles.value
|
||||
|
||||
// 根据账号类型过滤角色
|
||||
if (currentAccountType.value === 3) {
|
||||
// 代理账号:只显示客户角色
|
||||
roles = roles.filter((role) => role.role_type === 2)
|
||||
} else if (currentAccountType.value === 2) {
|
||||
// 平台用户:只显示平台角色
|
||||
roles = roles.filter((role) => role.role_type === 1)
|
||||
} else if (currentAccountType.value === 4) {
|
||||
// 企业账号:只显示客户角色
|
||||
roles = roles.filter((role) => role.role_type === 2)
|
||||
}
|
||||
|
||||
// 根据搜索关键词过滤
|
||||
if (!leftRoleFilter.value) return roles
|
||||
const keyword = leftRoleFilter.value.toLowerCase()
|
||||
return roles.filter((role) => role.role_name.toLowerCase().includes(keyword))
|
||||
})
|
||||
|
||||
// 计算属性:过滤后的已分配角色
|
||||
const filteredAssignedRoles = computed(() => {
|
||||
const assignedRolesList = allRoles.value.filter((role) => selectedRoles.value.includes(role.ID))
|
||||
if (!rightRoleFilter.value) return assignedRolesList
|
||||
const keyword = rightRoleFilter.value.toLowerCase()
|
||||
return assignedRolesList.filter((role) => role.role_name.toLowerCase().includes(keyword))
|
||||
})
|
||||
|
||||
// 显示分配角色对话框
|
||||
const showRoleDialog = async (row: PlatformAccount) => {
|
||||
currentAccountId.value = (row as any).id
|
||||
currentAccountName.value = row.username
|
||||
currentAccountType.value = row.user_type
|
||||
selectedRoles.value = []
|
||||
rolesToAdd.value = []
|
||||
leftRoleFilter.value = ''
|
||||
rightRoleFilter.value = ''
|
||||
|
||||
try {
|
||||
// 每次打开对话框时重新加载最新的角色列表
|
||||
await loadAllRoles()
|
||||
|
||||
// 先加载当前账号的角色,再打开对话框
|
||||
const res = await AccountService.getAccountRoles((row as any).id)
|
||||
if (res.code === 0) {
|
||||
// 提取角色ID数组
|
||||
const roles = res.data || []
|
||||
// 兼容 ID 和 id 两种字段名
|
||||
selectedRoles.value = roles.map((role: any) => role.ID || role.id)
|
||||
// 数据加载完成后再打开对话框
|
||||
roleDialogVisible.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取账号角色失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 批量添加角色
|
||||
const addRoles = async () => {
|
||||
if (rolesToAdd.value.length === 0) return
|
||||
|
||||
try {
|
||||
let newRoles: number[]
|
||||
|
||||
// 代理账号只能分配一个角色,会覆盖之前的角色
|
||||
if (currentAccountType.value === 3) {
|
||||
if (rolesToAdd.value.length > 1) {
|
||||
ElMessage.warning('代理账号只能分配一个角色')
|
||||
return
|
||||
}
|
||||
// 只保留新选择的一个角色
|
||||
newRoles = rolesToAdd.value
|
||||
} else {
|
||||
// 其他账号类型可以分配多个角色
|
||||
newRoles = [...new Set([...selectedRoles.value, ...rolesToAdd.value])]
|
||||
}
|
||||
|
||||
await AccountService.assignRolesToAccount(currentAccountId.value, newRoles)
|
||||
|
||||
selectedRoles.value = newRoles
|
||||
rolesToAdd.value = []
|
||||
|
||||
ElMessage.success('角色分配成功')
|
||||
// 刷新列表以更新角色显示
|
||||
await getTableData()
|
||||
} catch (error) {
|
||||
console.error('分配角色失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 移除单个角色
|
||||
const removeSingleRole = async (roleId: number) => {
|
||||
try {
|
||||
// 从已分配列表中移除该角色
|
||||
const newRoles = selectedRoles.value.filter((id) => id !== roleId)
|
||||
await AccountService.assignRolesToAccount(currentAccountId.value, newRoles)
|
||||
|
||||
selectedRoles.value = newRoles
|
||||
|
||||
ElMessage.success('角色移除成功')
|
||||
// 刷新列表以更新角色显示
|
||||
await getTableData()
|
||||
} catch (error) {
|
||||
console.error('移除角色失败:', error)
|
||||
ElMessage.error('角色移除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
loadAllRoles()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -285,9 +668,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
|
||||
.detail-title {
|
||||
margin: 0;
|
||||
@@ -297,4 +678,128 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.dialog-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.account-name {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.role-transfer-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
min-height: 500px;
|
||||
|
||||
.transfer-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
max-width: 380px;
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
|
||||
.panel-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
|
||||
.role-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.role-item {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-light);
|
||||
border-color: var(--el-border-color);
|
||||
}
|
||||
|
||||
.role-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox) {
|
||||
width: 100%;
|
||||
|
||||
.el-checkbox__label {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.assigned-role-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.role-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.transfer-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0 10px;
|
||||
|
||||
.el-button {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -387,7 +387,7 @@
|
||||
if (hasAuth('agent_commission:detail')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
icon: '',
|
||||
text: '详情',
|
||||
onClick: () => showDetail(row)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
<template>
|
||||
<div class="single-card-page">
|
||||
<!-- ICCID查询区域 -->
|
||||
<ElCard shadow="never" class="search-card" style="margin-bottom: 24px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>ICCID查询</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="iccid-search">
|
||||
<div class="iccid-input-wrapper">
|
||||
<ElInput
|
||||
v-model="searchIccid"
|
||||
placeholder="请输入ICCID"
|
||||
clearable
|
||||
style="width: 400px"
|
||||
@focus="iccidInputFocused = true"
|
||||
@blur="iccidInputFocused = false"
|
||||
@keyup.enter="handleSearchCard"
|
||||
/>
|
||||
<!-- 放大显示区域 -->
|
||||
<Transition name="zoom-fade">
|
||||
<div v-if="iccidInputFocused && searchIccid" class="iccid-magnifier">
|
||||
<div class="magnifier-arrow"></div>
|
||||
<div class="magnifier-content">
|
||||
{{ formatIccidWithDashes(searchIccid) }}
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleSearchCard"
|
||||
:loading="loading"
|
||||
style="margin-left: 16px"
|
||||
>查询</ElButton
|
||||
>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<!-- 卡片内容区域 -->
|
||||
<div v-if="cardInfo" class="card-content-area slide-in">
|
||||
<!-- 主要内容区域 -->
|
||||
@@ -150,12 +188,12 @@
|
||||
min-width="200"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<ElTableColumn prop="packageType" label="类型" width="100" />
|
||||
<ElTableColumn prop="totalFlow" label="总流量" width="100" />
|
||||
<ElTableColumn prop="usedFlow" label="已用" width="100" />
|
||||
<ElTableColumn prop="remainFlow" label="剩余" width="100" />
|
||||
<ElTableColumn prop="expireTime" label="到期时间" width="120" />
|
||||
<ElTableColumn prop="status" label="状态" width="100">
|
||||
<ElTableColumn prop="packageType" label="类型" />
|
||||
<ElTableColumn prop="totalFlow" label="总流量" />
|
||||
<ElTableColumn prop="usedFlow" label="已用" />
|
||||
<ElTableColumn prop="remainFlow" label="剩余" />
|
||||
<ElTableColumn prop="expireTime" label="到期时间" />
|
||||
<ElTableColumn prop="status" label="状态">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getPackageStatusType(scope.row.status)" size="small">
|
||||
{{ scope.row.status }}
|
||||
@@ -287,15 +325,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<!--<div v-else-if="loading" class="loading-state">-->
|
||||
<!-- <ElSkeleton :rows="10" animated />-->
|
||||
<!--</div>-->
|
||||
|
||||
<!-- 空状态 -->
|
||||
<!--<div v-else class="empty-state">-->
|
||||
<!-- <ElEmpty description="暂无卡片数据" />-->
|
||||
<!--</div>-->
|
||||
<div v-else class="empty-state">
|
||||
<ElEmpty description="请在上方输入ICCID进行查询" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -314,13 +347,30 @@
|
||||
} from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { EnterpriseService } from '@/api/modules/enterprise'
|
||||
import { CardService } from '@/api/modules'
|
||||
|
||||
defineOptions({ name: 'SingleCard' })
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(true)
|
||||
const loading = ref(false)
|
||||
const operationLoading = ref(false)
|
||||
|
||||
// ICCID搜索相关
|
||||
const searchIccid = ref('')
|
||||
const iccidInputFocused = ref(false)
|
||||
|
||||
// 格式化ICCID为4位一组,用横杠分隔
|
||||
const formatIccidWithDashes = (iccid: string) => {
|
||||
if (!iccid) return ''
|
||||
return iccid.replace(/(\d{4})(?=\d)/g, '$1-')
|
||||
}
|
||||
|
||||
// 格式化显示的ICCID(用于查询结果显示)
|
||||
const formattedIccid = computed(() => {
|
||||
if (!cardInfo.value?.iccid) return ''
|
||||
return formatIccidWithDashes(cardInfo.value.iccid)
|
||||
})
|
||||
|
||||
// 从 URL 获取参数
|
||||
const enterpriseId = computed(() => {
|
||||
const id = route.query.enterpriseId || route.query.enterprise_id
|
||||
@@ -336,10 +386,139 @@
|
||||
return cardInfo.value?.id ? Number(cardInfo.value.id) : null
|
||||
})
|
||||
|
||||
// 卡片信息
|
||||
// 卡片信息 - 默认为null,等待查询
|
||||
const cardInfo = ref<any>(null)
|
||||
|
||||
// 模拟卡片数据
|
||||
// 处理ICCID搜索
|
||||
const handleSearchCard = async () => {
|
||||
if (!searchIccid.value.trim()) {
|
||||
ElMessage.warning('请输入ICCID')
|
||||
return
|
||||
}
|
||||
await fetchCardDetailByIccid(searchIccid.value.trim())
|
||||
}
|
||||
|
||||
// 根据ICCID获取卡片详情
|
||||
const fetchCardDetailByIccid = async (iccid: string) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await CardService.getIotCardDetailByIccid(iccid)
|
||||
|
||||
if (response.code === 0 && response.data) {
|
||||
const data = response.data
|
||||
|
||||
// 映射API数据到页面显示格式
|
||||
cardInfo.value = {
|
||||
id: data.id,
|
||||
iccid: data.iccid || '',
|
||||
accessNumber: data.msisdn || '--',
|
||||
imei: data.imei || '--',
|
||||
expireTime: data.expire_at || '--',
|
||||
operator: getCarrierTypeName(data.carrier_type),
|
||||
cardStatus: getCardStatusName(data.status),
|
||||
cardType: getCardCategoryName(data.card_category),
|
||||
supplier: data.supplier_name || '--',
|
||||
importTime: data.created_at || '--',
|
||||
phoneBind: data.phone_bind || '--',
|
||||
trafficPool: data.traffic_pool || '--',
|
||||
agent: data.shop_name || data.carrier_name || '--',
|
||||
operatorStatus: getActivationStatusName(data.activation_status),
|
||||
operatorRealName: getRealNameStatusName(data.real_name_status),
|
||||
walletBalance: data.wallet_balance ? `${data.wallet_balance}元` : '0.00元',
|
||||
walletPasswordStatus: data.wallet_password_status || '--',
|
||||
realNameAuth: data.real_name_status === 1,
|
||||
virtualNumber: data.virtual_number || '--',
|
||||
// 流量信息
|
||||
packageSeries: data.series_name || '--',
|
||||
packageTotalFlow: formatDataSize(data.data_usage_mb || 0),
|
||||
usedFlow: formatDataSize(data.current_month_usage_mb || 0),
|
||||
remainFlow: formatDataSize(
|
||||
(data.data_usage_mb || 0) - (data.current_month_usage_mb || 0)
|
||||
),
|
||||
usedFlowPercentage:
|
||||
data.data_usage_mb > 0
|
||||
? `${((data.current_month_usage_mb / data.data_usage_mb) * 100).toFixed(2)}%`
|
||||
: '未设置',
|
||||
realUsedFlow: formatDataSize(data.current_month_usage_mb || 0),
|
||||
actualFlow: formatDataSize(data.current_month_usage_mb || 0),
|
||||
packageList: data.packages || []
|
||||
}
|
||||
|
||||
ElMessage.success('查询成功')
|
||||
} else {
|
||||
ElMessage.error(response.msg || '查询失败')
|
||||
cardInfo.value = null
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取卡片信息失败:', error)
|
||||
ElMessage.error(error?.message || '获取卡片信息失败')
|
||||
cardInfo.value = null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化数据大小
|
||||
const formatDataSize = (mb: number) => {
|
||||
if (mb >= 1024) {
|
||||
return `${(mb / 1024).toFixed(2)}GB`
|
||||
}
|
||||
return `${mb.toFixed(2)}MB`
|
||||
}
|
||||
|
||||
// 获取运营商类型名称
|
||||
const getCarrierTypeName = (type: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
CMCC: '中国移动',
|
||||
CUCC: '中国联通',
|
||||
CTCC: '中国电信'
|
||||
}
|
||||
return typeMap[type] || type || '未知'
|
||||
}
|
||||
|
||||
// 获取卡片类别名称
|
||||
const getCardCategoryName = (category: string) => {
|
||||
const categoryMap: Record<string, string> = {
|
||||
normal: '普通卡',
|
||||
premium: '高级卡',
|
||||
enterprise: '企业卡'
|
||||
}
|
||||
return categoryMap[category] || category || '未知'
|
||||
}
|
||||
|
||||
// 获取卡片状态名称
|
||||
const getCardStatusName = (status: number) => {
|
||||
const statusMap: Record<number, string> = {
|
||||
0: '未激活',
|
||||
1: '正常',
|
||||
2: '停用',
|
||||
3: '已过期',
|
||||
4: '已注销'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 获取激活状态名称
|
||||
const getActivationStatusName = (status: number) => {
|
||||
const statusMap: Record<number, string> = {
|
||||
0: '未激活',
|
||||
1: '已激活',
|
||||
2: '已停用'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 获取实名状态名称
|
||||
const getRealNameStatusName = (status: number) => {
|
||||
const statusMap: Record<number, string> = {
|
||||
0: '未实名',
|
||||
1: '已实名',
|
||||
2: '实名失败'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
|
||||
// 模拟卡片数据(保留作为参考)
|
||||
const mockCardData = {
|
||||
id: 1, // 卡片ID
|
||||
iccid: '8986062357007989203',
|
||||
@@ -381,24 +560,9 @@
|
||||
]
|
||||
}
|
||||
|
||||
// 获取卡片详情
|
||||
const fetchCardDetail = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
cardInfo.value = { ...mockCardData }
|
||||
loading.value = false
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
ElMessage.error('获取卡片详情失败')
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 页面初始化时加载数据
|
||||
// 页面初始化 - 不自动加载数据,等待用户输入ICCID查询
|
||||
onMounted(() => {
|
||||
fetchCardDetail()
|
||||
// 不再自动加载模拟数据,等待用户查询
|
||||
})
|
||||
|
||||
// 获取状态标签类型
|
||||
@@ -550,6 +714,89 @@
|
||||
<style lang="scss" scoped>
|
||||
.single-card-page {
|
||||
padding: 20px 0;
|
||||
|
||||
// ICCID搜索卡片
|
||||
.search-card {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.iccid-search {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
overflow: visible;
|
||||
|
||||
.iccid-input-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
// 放大显示区域
|
||||
.iccid-magnifier {
|
||||
position: absolute;
|
||||
top: calc(100% + 12px);
|
||||
left: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
// 三角箭头 - 上边框样式
|
||||
.magnifier-arrow {
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
left: 80px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: var(--el-bg-color, #fff);
|
||||
border-top: 2px solid var(--el-color-primary);
|
||||
border-left: 2px solid var(--el-color-primary);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
// 内容区域
|
||||
.magnifier-content {
|
||||
display: inline-block;
|
||||
padding: 16px 24px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
color: var(--el-color-primary);
|
||||
text-align: center;
|
||||
letter-spacing: 2px;
|
||||
white-space: nowrap;
|
||||
background-color: var(--el-bg-color, #fff);
|
||||
border: 2px solid var(--el-color-primary);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 放大效果的过渡动画
|
||||
.zoom-fade-enter-active,
|
||||
.zoom-fade-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.zoom-fade-enter-from {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) translateY(-10px);
|
||||
}
|
||||
|
||||
.zoom-fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) translateY(-10px);
|
||||
}
|
||||
|
||||
.zoom-fade-enter-to,
|
||||
.zoom-fade-leave-from {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
|
||||
// 卡片内容区域
|
||||
.card-content-area {
|
||||
&.slide-in {
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton @click="showCreateDialog" v-permission="'orders:add'">{{ t('orderManagement.createOrder') }}</ElButton>
|
||||
<ElButton @click="showCreateDialog" v-permission="'orders:add'">{{
|
||||
t('orderManagement.createOrder')
|
||||
}}</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
@@ -130,7 +132,11 @@
|
||||
reserve-keyword
|
||||
:remote-method="handlePackageSearch"
|
||||
:loading="packageSearchLoading"
|
||||
:disabled="(!createForm.iot_card_id && !createForm.device_id) || (createForm.order_type === 'single_card' && !createForm.iot_card_id) || (createForm.order_type === 'device' && !createForm.device_id)"
|
||||
:disabled="
|
||||
(!createForm.iot_card_id && !createForm.device_id) ||
|
||||
(createForm.order_type === 'single_card' && !createForm.iot_card_id) ||
|
||||
(createForm.order_type === 'device' && !createForm.device_id)
|
||||
"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
@@ -627,14 +633,14 @@
|
||||
formatter: (row: Order) => {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
icon: '',
|
||||
text: '详情',
|
||||
tooltip: t('orderManagement.actions.viewDetail'),
|
||||
onClick: () => showOrderDetail(row)
|
||||
}),
|
||||
// 只有待支付和已支付的订单可以取消
|
||||
row.payment_status === 1 || row.payment_status === 2
|
||||
? h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
text: '删除',
|
||||
tooltip: t('orderManagement.actions.cancel'),
|
||||
onClick: () => handleCancelOrder(row)
|
||||
})
|
||||
|
||||
@@ -25,26 +25,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { ShopPackageAllocationService } from '@/api/modules'
|
||||
import type { ShopPackageAllocationResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { ShopPackageAllocationService } from '@/api/modules'
|
||||
import type { ShopPackageAllocationResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
|
||||
defineOptions({ name: 'PackageAssignDetail' })
|
||||
defineOptions({ name: 'PackageAssignDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const detailData = ref<ShopPackageAllocationResponse | null>(null)
|
||||
const loading = ref(false)
|
||||
const detailData = ref<ShopPackageAllocationResponse | null>(null)
|
||||
|
||||
// 详情页配置
|
||||
const detailSections: DetailSection[] = [
|
||||
// 详情页配置
|
||||
const detailSections: DetailSection[] = [
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
@@ -78,15 +78,15 @@ const detailSections: DetailSection[] = [
|
||||
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
const id = Number(route.params.id)
|
||||
if (!id) {
|
||||
ElMessage.error('缺少ID参数')
|
||||
@@ -105,25 +105,23 @@ const fetchDetail = async () => {
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
fetchDetail()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.package-assign-detail {
|
||||
.package-assign-detail {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
|
||||
.detail-title {
|
||||
margin: 0;
|
||||
@@ -131,9 +129,9 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -145,5 +143,5 @@ onMounted(() => {
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton type="primary" @click="showDialog('add')" v-permission="'package_assign:add'">新增分配</ElButton>
|
||||
<ElButton type="primary" @click="showDialog('add')" v-permission="'package_assign:add'"
|
||||
>新增分配</ElButton
|
||||
>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
@@ -113,7 +115,12 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ShopPackageAllocationService, PackageManageService, ShopService, ShopSeriesAllocationService } from '@/api/modules'
|
||||
import {
|
||||
ShopPackageAllocationService,
|
||||
PackageManageService,
|
||||
ShopService,
|
||||
ShopSeriesAllocationService
|
||||
} from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElSwitch } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { ShopPackageAllocationResponse, PackageResponse, ShopResponse } from '@/types/api'
|
||||
@@ -386,14 +393,14 @@
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 200,
|
||||
width: 220,
|
||||
fixed: 'right',
|
||||
formatter: (row: ShopPackageAllocationResponse) => {
|
||||
const buttons = []
|
||||
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type:"view",
|
||||
text: '详情',
|
||||
onClick: () => handleViewDetail(row)
|
||||
})
|
||||
)
|
||||
@@ -401,7 +408,7 @@
|
||||
if (hasAuth('package_assign:edit')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type:"edit",
|
||||
text: '编辑',
|
||||
onClick: () => showDialog('edit', row)
|
||||
})
|
||||
)
|
||||
@@ -410,7 +417,7 @@
|
||||
if (hasAuth('package_assign:delete')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type:"delete",
|
||||
text: '删除',
|
||||
onClick: () => deleteAllocation(row)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -25,26 +25,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { PackageManageService } from '@/api/modules'
|
||||
import type { PackageResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { PackageManageService } from '@/api/modules'
|
||||
import type { PackageResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
|
||||
defineOptions({ name: 'PackageDetail' })
|
||||
defineOptions({ name: 'PackageDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const detailData = ref<PackageResponse | null>(null)
|
||||
const loading = ref(false)
|
||||
const detailData = ref<PackageResponse | null>(null)
|
||||
|
||||
// 详情页配置
|
||||
const detailSections: DetailSection[] = [
|
||||
// 详情页配置
|
||||
const detailSections: DetailSection[] = [
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
@@ -150,7 +150,11 @@ const detailSections: DetailSection[] = [
|
||||
{
|
||||
label: '一次性佣金金额',
|
||||
formatter: (_, data) => {
|
||||
if (data.one_time_commission_amount === null || data.one_time_commission_amount === undefined) return '-'
|
||||
if (
|
||||
data.one_time_commission_amount === null ||
|
||||
data.one_time_commission_amount === undefined
|
||||
)
|
||||
return '-'
|
||||
return `¥${(data.one_time_commission_amount / 100).toFixed(2)}`
|
||||
}
|
||||
},
|
||||
@@ -171,21 +175,26 @@ const detailSections: DetailSection[] = [
|
||||
{
|
||||
label: '下一档位阈值',
|
||||
formatter: (_, data) => {
|
||||
if (!data.tier_info || data.tier_info.next_threshold === null || data.tier_info.next_threshold === undefined) return '-'
|
||||
if (
|
||||
!data.tier_info ||
|
||||
data.tier_info.next_threshold === null ||
|
||||
data.tier_info.next_threshold === undefined
|
||||
)
|
||||
return '-'
|
||||
return data.tier_info.next_threshold
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
const id = Number(route.params.id)
|
||||
if (!id) {
|
||||
ElMessage.error('缺少ID参数')
|
||||
@@ -204,25 +213,23 @@ const fetchDetail = async () => {
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
fetchDetail()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.package-detail {
|
||||
.package-detail {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
|
||||
.detail-title {
|
||||
margin: 0;
|
||||
@@ -230,9 +237,9 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -244,5 +251,5 @@ onMounted(() => {
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton type="primary" @click="showDialog('add')" v-permission="'package:add'">新增套餐</ElButton>
|
||||
<ElButton type="primary" @click="showDialog('add')" v-permission="'package:add'"
|
||||
>新增套餐</ElButton
|
||||
>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
@@ -50,13 +52,13 @@
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="150px">
|
||||
<ElFormItem label="套餐编码" prop="package_code">
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<div style="display: flex; gap: 8px">
|
||||
<ElInput
|
||||
v-model="form.package_code"
|
||||
placeholder="请输入套餐编码或点击生成"
|
||||
:disabled="dialogType === 'edit'"
|
||||
clearable
|
||||
style="flex: 1;"
|
||||
style="flex: 1"
|
||||
/>
|
||||
<ElButton v-if="dialogType === 'add'" @click="handleGeneratePackageCode">
|
||||
生成编码
|
||||
@@ -478,14 +480,14 @@
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 200,
|
||||
width: 220,
|
||||
fixed: 'right',
|
||||
formatter: (row: PackageResponse) => {
|
||||
const buttons = []
|
||||
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'view',
|
||||
text: '详情',
|
||||
onClick: () => handleViewDetail(row)
|
||||
})
|
||||
)
|
||||
@@ -493,7 +495,7 @@
|
||||
if (hasAuth('package:edit')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
text: '编辑',
|
||||
onClick: () => showDialog('edit', row)
|
||||
})
|
||||
)
|
||||
@@ -502,7 +504,7 @@
|
||||
if (hasAuth('package:delete')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
text: '删除',
|
||||
onClick: () => deletePackage(row)
|
||||
})
|
||||
)
|
||||
@@ -661,7 +663,9 @@
|
||||
form.virtual_data_mb = row.virtual_data_mb || 0
|
||||
form.duration_months = row.duration_months
|
||||
form.cost_price = row.cost_price / 100 // 分转换为元显示
|
||||
form.suggested_retail_price = row.suggested_retail_price ? row.suggested_retail_price / 100 : undefined
|
||||
form.suggested_retail_price = row.suggested_retail_price
|
||||
? row.suggested_retail_price / 100
|
||||
: undefined
|
||||
form.description = row.description || ''
|
||||
} else {
|
||||
form.id = 0
|
||||
|
||||
@@ -25,26 +25,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { PackageSeriesService } from '@/api/modules'
|
||||
import type { PackageSeriesResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { ref, onMounted, h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { PackageSeriesService } from '@/api/modules'
|
||||
import type { PackageSeriesResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
|
||||
defineOptions({ name: 'PackageSeriesDetail' })
|
||||
defineOptions({ name: 'PackageSeriesDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const detailData = ref<PackageSeriesResponse | null>(null)
|
||||
const loading = ref(false)
|
||||
const detailData = ref<PackageSeriesResponse | null>(null)
|
||||
|
||||
// 详情页配置
|
||||
const detailSections: DetailSection[] = [
|
||||
// 详情页配置
|
||||
const detailSections: DetailSection[] = [
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
@@ -112,21 +112,31 @@ const detailSections: DetailSection[] = [
|
||||
fullWidth: true,
|
||||
render: (data) => {
|
||||
const config = data.one_time_commission_config
|
||||
if (config?.commission_type !== 'tiered' || !config.tiers || config.tiers.length === 0) {
|
||||
if (
|
||||
config?.commission_type !== 'tiered' ||
|
||||
!config.tiers ||
|
||||
config.tiers.length === 0
|
||||
) {
|
||||
return h('span', '-')
|
||||
}
|
||||
|
||||
return h('div', { style: 'display: flex; flex-direction: column; gap: 8px;' },
|
||||
return h(
|
||||
'div',
|
||||
{ style: 'display: flex; flex-direction: column; gap: 8px;' },
|
||||
config.tiers.map((tier: any, index: number) => {
|
||||
const dimensionText = tier.dimension === 'sales_count' ? '销量' : '销售额'
|
||||
const thresholdText = tier.dimension === 'sales_amount'
|
||||
const thresholdText =
|
||||
tier.dimension === 'sales_amount'
|
||||
? `¥${(tier.threshold / 100).toFixed(2)}`
|
||||
: tier.threshold
|
||||
const amountText = `¥${(tier.amount / 100).toFixed(2)}`
|
||||
const scopeText = tier.stat_scope === 'self' ? '仅自己' : '自己+下级'
|
||||
|
||||
return h(ElTag, { type: 'info', size: 'default' },
|
||||
() => `档位${index + 1}: ${dimensionText} ≥ ${thresholdText}, 佣金 ${amountText}, ${scopeText}`
|
||||
return h(
|
||||
ElTag,
|
||||
{ type: 'info', size: 'default' },
|
||||
() =>
|
||||
`档位${index + 1}: ${dimensionText} ≥ ${thresholdText}, 佣金 ${amountText}, ${scopeText}`
|
||||
)
|
||||
})
|
||||
)
|
||||
@@ -192,15 +202,15 @@ const detailSections: DetailSection[] = [
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
const id = Number(route.params.id)
|
||||
if (!id) {
|
||||
ElMessage.error('缺少ID参数')
|
||||
@@ -219,25 +229,23 @@ const fetchDetail = async () => {
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
fetchDetail()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.package-series-detail {
|
||||
.package-series-detail {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
|
||||
.detail-title {
|
||||
margin: 0;
|
||||
@@ -245,9 +253,9 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -259,5 +267,5 @@ onMounted(() => {
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -243,7 +243,13 @@
|
||||
:icon="Delete"
|
||||
circle
|
||||
@click="removeTier(index)"
|
||||
style="flex-shrink: 0; margin-top: 28px; width: 32px; height: 32px; padding: 0"
|
||||
style="
|
||||
flex-shrink: 0;
|
||||
margin-top: 28px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</ElCard>
|
||||
@@ -673,7 +679,7 @@
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 200,
|
||||
width: 220,
|
||||
fixed: 'right',
|
||||
formatter: (row: PackageSeriesResponse) => {
|
||||
const buttons = []
|
||||
@@ -681,7 +687,7 @@
|
||||
// 详情按钮
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'view',
|
||||
text: '详情',
|
||||
onClick: () => handleViewDetail(row)
|
||||
})
|
||||
)
|
||||
@@ -689,7 +695,7 @@
|
||||
if (hasAuth('package_series:edit')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
text: '编辑',
|
||||
onClick: () => showDialog('edit', row)
|
||||
})
|
||||
)
|
||||
@@ -698,7 +704,7 @@
|
||||
if (hasAuth('package_series:delete')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
text: '删除',
|
||||
onClick: () => deleteSeries(row)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -25,26 +25,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { ShopSeriesAllocationService } from '@/api/modules'
|
||||
import type { ShopSeriesAllocationResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { ref, onMounted, h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { ShopSeriesAllocationService } from '@/api/modules'
|
||||
import type { ShopSeriesAllocationResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
|
||||
defineOptions({ name: 'SeriesAssignDetail' })
|
||||
defineOptions({ name: 'SeriesAssignDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const detailData = ref<ShopSeriesAllocationResponse | null>(null)
|
||||
const loading = ref(false)
|
||||
const detailData = ref<ShopSeriesAllocationResponse | null>(null)
|
||||
|
||||
// 详情页配置
|
||||
const detailSections: DetailSection[] = [
|
||||
// 详情页配置
|
||||
const detailSections: DetailSection[] = [
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
@@ -136,15 +136,15 @@ const detailSections: DetailSection[] = [
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
// 获取详情数据
|
||||
const fetchDetail = async () => {
|
||||
const id = Number(route.params.id)
|
||||
if (!id) {
|
||||
ElMessage.error('缺少ID参数')
|
||||
@@ -163,25 +163,23 @@ const fetchDetail = async () => {
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(() => {
|
||||
fetchDetail()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.series-assign-detail {
|
||||
.series-assign-detail {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
|
||||
.detail-title {
|
||||
margin: 0;
|
||||
@@ -189,9 +187,9 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -203,5 +201,5 @@ onMounted(() => {
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton type="primary" @click="showDialog('add')" v-permission="'series_assign:add'">新增系列分配</ElButton>
|
||||
<ElButton type="primary" @click="showDialog('add')" v-permission="'series_assign:add'"
|
||||
>新增系列分配</ElButton
|
||||
>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
@@ -565,7 +567,7 @@
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 180,
|
||||
width: 220,
|
||||
fixed: 'right',
|
||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||
const buttons = []
|
||||
@@ -573,7 +575,7 @@
|
||||
// 详情按钮
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'view',
|
||||
text: '详情',
|
||||
onClick: () => handleViewDetail(row)
|
||||
})
|
||||
)
|
||||
@@ -581,7 +583,7 @@
|
||||
if (hasAuth('series_assign:edit')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
text: '编辑',
|
||||
onClick: () => showDialog('edit', row)
|
||||
})
|
||||
)
|
||||
@@ -590,7 +592,7 @@
|
||||
if (hasAuth('series_assign:delete')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
text: '删除',
|
||||
onClick: () => deleteAllocation(row)
|
||||
})
|
||||
)
|
||||
@@ -800,7 +802,8 @@
|
||||
page_size: pagination.page_size,
|
||||
shop_id: searchForm.shop_id || undefined,
|
||||
series_id: searchForm.series_id || undefined,
|
||||
allocator_shop_id: searchForm.allocator_shop_id !== undefined ? searchForm.allocator_shop_id : undefined,
|
||||
allocator_shop_id:
|
||||
searchForm.allocator_shop_id !== undefined ? searchForm.allocator_shop_id : undefined,
|
||||
status: searchForm.status || undefined
|
||||
}
|
||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocations(params)
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增店铺' : '编辑店铺'"
|
||||
width="800px"
|
||||
width="50%"
|
||||
>
|
||||
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="100px">
|
||||
<ElRow :gutter="20">
|
||||
@@ -162,6 +162,32 @@
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="默认角色" prop="default_role_id">
|
||||
<ElSelect
|
||||
v-model="formData.default_role_id"
|
||||
placeholder="请选择默认角色"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="searchDefaultRoles"
|
||||
:loading="defaultRoleLoading"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
v-for="role in defaultRoleList"
|
||||
:key="role.ID"
|
||||
:label="role.role_name"
|
||||
:value="role.ID"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>{{ role.role_name }}</span>
|
||||
<ElTag type="success" size="small">客户角色</ElTag>
|
||||
</div>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
||||
|
||||
@@ -195,42 +221,38 @@
|
||||
<ElDialog
|
||||
v-model="defaultRolesDialogVisible"
|
||||
:title="`设置默认角色 - ${currentShop?.shop_name || ''}`"
|
||||
width="800px"
|
||||
width="50%"
|
||||
>
|
||||
<div v-loading="defaultRolesLoading">
|
||||
<!-- 当前默认角色列表 -->
|
||||
<div class="default-roles-section">
|
||||
<div class="section-header">
|
||||
<span style="color:white;">当前默认角色</span>
|
||||
<ElButton type="primary" @click="showAddRoleDialog">
|
||||
添加角色
|
||||
</ElButton>
|
||||
<span style="color: white">当前默认角色</span>
|
||||
<ElButton type="primary" @click="showAddRoleDialog"> 设置默认角色 </ElButton>
|
||||
</div>
|
||||
<ElTable :data="currentDefaultRoles" border stripe style="margin-top: 12px">
|
||||
<ElTableColumn prop="role_name" label="角色名称" width="150" />
|
||||
<ElTableColumn prop="role_desc" label="角色描述" min-width="200" />
|
||||
<ElTableColumn prop="status" label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<ElTag :type="row.status === CommonStatus.ENABLED ? 'success' : 'info'" size="small">
|
||||
<ElTag
|
||||
:type="row.status === CommonStatus.ENABLED ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ getStatusText(row.status) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<ElButton
|
||||
type="danger"
|
||||
text
|
||||
size="small"
|
||||
@click="handleDeleteDefaultRole(row)"
|
||||
>
|
||||
<ElButton type="danger" text size="small" @click="handleDeleteDefaultRole(row)">
|
||||
删除
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<template #empty>
|
||||
<div style="padding: 20px 0; color: #909399;">
|
||||
暂无默认角色,请点击"添加角色"按钮进行配置
|
||||
<div style="padding: 20px 0; color: #909399">
|
||||
暂无默认角色,请点击"设置默认角色"按钮进行配置
|
||||
</div>
|
||||
</template>
|
||||
</ElTable>
|
||||
@@ -243,19 +265,13 @@
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 添加角色对话框 -->
|
||||
<ElDialog
|
||||
v-model="addRoleDialogVisible"
|
||||
title="添加默认角色"
|
||||
width="600px"
|
||||
append-to-body
|
||||
>
|
||||
<!-- 设置默认角色对话框 -->
|
||||
<ElDialog v-model="addRoleDialogVisible" title="设置默认角色" width="50%" append-to-body>
|
||||
<div v-loading="rolesLoading">
|
||||
<ElSelect
|
||||
v-model="selectedRoleIds"
|
||||
multiple
|
||||
v-model="selectedRoleId"
|
||||
filterable
|
||||
placeholder="请选择要添加的角色"
|
||||
placeholder="请选择默认角色"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption
|
||||
@@ -263,23 +279,15 @@
|
||||
:key="role.role_id"
|
||||
:label="role.role_name"
|
||||
:value="role.role_id"
|
||||
:disabled="isRoleAlreadyAssigned(role.role_id)"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
|
||||
<div style="display: flex; gap: 8px; align-items: center;">
|
||||
<div style="display: flex; gap: 8px; align-items: center">
|
||||
<span>{{ role.role_name }}</span>
|
||||
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||
</ElTag>
|
||||
</div>
|
||||
<span v-if="isRoleAlreadyAssigned(role.role_id)" style="color: #909399; font-size: 12px;">
|
||||
已添加
|
||||
</span>
|
||||
<ElTag type="success" size="small">客户角色</ElTag>
|
||||
</div>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
<div style="margin-top: 8px; color: #909399; font-size: 12px;">
|
||||
支持多选,已添加的角色将显示为禁用状态
|
||||
<div style="margin-top: 8px; color: #909399; font-size: 12px">
|
||||
只能选择一个客户角色,设置后将覆盖之前的默认角色
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -334,6 +342,8 @@
|
||||
const parentShopLoading = ref(false)
|
||||
const parentShopList = ref<ShopResponse[]>([])
|
||||
const searchParentShopList = ref<ShopResponse[]>([])
|
||||
const defaultRoleLoading = ref(false)
|
||||
const defaultRoleList = ref<any[]>([])
|
||||
|
||||
// 默认角色管理相关状态
|
||||
const defaultRolesDialogVisible = ref(false)
|
||||
@@ -344,7 +354,7 @@
|
||||
const currentShop = ref<ShopResponse | null>(null)
|
||||
const currentDefaultRoles = ref<ShopRoleResponse[]>([])
|
||||
const availableRoles = ref<ShopRoleResponse[]>([])
|
||||
const selectedRoleIds = ref<number[]>([])
|
||||
const selectedRoleId = ref<number | undefined>(undefined)
|
||||
|
||||
// 右键菜单
|
||||
const shopOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
@@ -474,6 +484,9 @@
|
||||
const showDialog = (type: string, row?: ShopResponse) => {
|
||||
dialogType.value = type
|
||||
|
||||
// 先清除验证状态
|
||||
formRef.value?.clearValidate()
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
formData.id = row.id
|
||||
formData.shop_name = row.shop_name
|
||||
@@ -489,6 +502,7 @@
|
||||
formData.init_username = ''
|
||||
formData.init_password = ''
|
||||
formData.init_phone = ''
|
||||
formData.default_role_id = undefined
|
||||
} else {
|
||||
formData.id = 0
|
||||
formData.shop_name = ''
|
||||
@@ -504,9 +518,10 @@
|
||||
formData.init_username = ''
|
||||
formData.init_password = ''
|
||||
formData.init_phone = ''
|
||||
formData.default_role_id = undefined
|
||||
}
|
||||
|
||||
// 重置表单验证状态
|
||||
// 再次确保清除验证状态
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate()
|
||||
})
|
||||
@@ -540,12 +555,13 @@
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
minWidth: 120
|
||||
minWidth: 160,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'shop_code',
|
||||
label: '店铺编号',
|
||||
width: 150,
|
||||
width: 140,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
@@ -560,6 +576,7 @@
|
||||
prop: 'region',
|
||||
label: '所在地区',
|
||||
minWidth: 170,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: ShopResponse) => {
|
||||
const parts: string[] = []
|
||||
if (row.province) parts.push(row.province)
|
||||
@@ -652,13 +669,39 @@
|
||||
status: CommonStatus.ENABLED,
|
||||
init_username: '',
|
||||
init_password: '',
|
||||
init_phone: ''
|
||||
init_phone: '',
|
||||
default_role_id: undefined as number | undefined
|
||||
})
|
||||
|
||||
// 搜索默认角色(仅加载客户角色)
|
||||
const searchDefaultRoles = async (query: string) => {
|
||||
defaultRoleLoading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
role_type: 2, // 仅客户角色
|
||||
status: 1 // 仅启用的角色
|
||||
}
|
||||
if (query) {
|
||||
params.role_name = query
|
||||
}
|
||||
const res = await RoleService.getRoles(params)
|
||||
if (res.code === 0) {
|
||||
defaultRoleList.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取默认角色列表失败:', error)
|
||||
} finally {
|
||||
defaultRoleLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getShopList()
|
||||
loadParentShopList()
|
||||
loadSearchParentShopList()
|
||||
searchDefaultRoles('') // 加载初始默认角色列表
|
||||
})
|
||||
|
||||
// 加载上级店铺列表(用于新增对话框,默认加载20条)
|
||||
@@ -810,6 +853,9 @@
|
||||
{ required: true, message: '请输入初始账号手机号', trigger: 'blur' },
|
||||
{ len: 11, message: '手机号必须为 11 位', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
|
||||
],
|
||||
default_role_id: [
|
||||
{ required: true, message: '请选择默认角色', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
@@ -827,7 +873,8 @@
|
||||
shop_code: formData.shop_code,
|
||||
init_username: formData.init_username,
|
||||
init_password: formData.init_password,
|
||||
init_phone: formData.init_phone
|
||||
init_phone: formData.init_phone,
|
||||
default_role_id: formData.default_role_id
|
||||
}
|
||||
|
||||
// 可选字段
|
||||
@@ -985,31 +1032,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 显示添加角色对话框
|
||||
// 显示设置默认角色对话框
|
||||
const showAddRoleDialog = async () => {
|
||||
addRoleDialogVisible.value = true
|
||||
selectedRoleIds.value = []
|
||||
// 如果已有默认角色,预选第一个
|
||||
selectedRoleId.value = currentDefaultRoles.value.length > 0
|
||||
? currentDefaultRoles.value[0].role_id
|
||||
: undefined
|
||||
await loadAvailableRoles()
|
||||
}
|
||||
|
||||
// 加载可用角色列表
|
||||
// 加载可用角色列表(仅客户角色)
|
||||
const loadAvailableRoles = async () => {
|
||||
rolesLoading.value = true
|
||||
try {
|
||||
const res = await RoleService.getRoles({
|
||||
page: 1,
|
||||
page_size: 9999,
|
||||
role_type: 2, // 仅客户角色
|
||||
status: 1 // RoleStatus.ENABLED
|
||||
})
|
||||
if (res.code === 0) {
|
||||
// 将 PlatformRole 转换为与 ShopRoleResponse 兼容的格式,同时保留 role_type
|
||||
// 将 PlatformRole 转换为与 ShopRoleResponse 兼容的格式
|
||||
availableRoles.value = (res.data.items || []).map((role) => ({
|
||||
...role,
|
||||
role_id: role.ID,
|
||||
role_name: role.role_name,
|
||||
role_desc: role.role_desc,
|
||||
role_type: role.role_type, // 保留角色类型用于显示
|
||||
shop_id: 0 // 这个值在可用角色列表中不使用
|
||||
role_type: role.role_type,
|
||||
shop_id: 0
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -1020,15 +1071,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 判断角色是否已分配
|
||||
const isRoleAlreadyAssigned = (roleId: number) => {
|
||||
return currentDefaultRoles.value.some((r) => r.role_id === roleId)
|
||||
}
|
||||
|
||||
// 添加默认角色
|
||||
// 设置默认角色
|
||||
const handleAddDefaultRoles = async () => {
|
||||
if (selectedRoleIds.value.length === 0) {
|
||||
ElMessage.warning('请至少选择一个角色')
|
||||
if (!selectedRoleId.value) {
|
||||
ElMessage.warning('请选择默认角色')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1039,17 +1085,18 @@
|
||||
|
||||
addRoleLoading.value = true
|
||||
try {
|
||||
// 传递数组,但只包含一个角色ID
|
||||
const res = await ShopService.assignShopRoles(currentShop.value.id, {
|
||||
role_ids: selectedRoleIds.value
|
||||
role_ids: [selectedRoleId.value]
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('添加默认角色成功')
|
||||
ElMessage.success('设置默认角色成功')
|
||||
addRoleDialogVisible.value = false
|
||||
// 刷新默认角色列表
|
||||
await loadShopDefaultRoles(currentShop.value.id)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加默认角色失败:', error)
|
||||
console.error('设置默认角色失败:', error)
|
||||
} finally {
|
||||
addRoleLoading.value = false
|
||||
}
|
||||
|
||||
@@ -127,16 +127,10 @@
|
||||
<template #default="{ node, data }">
|
||||
<span style="display: flex; align-items: center; gap: 8px">
|
||||
<span>{{ node.label }}</span>
|
||||
<ElTag
|
||||
:type="data.perm_type === 1 ? 'info' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
<ElTag :type="data.perm_type === 1 ? 'info' : 'success'" size="small">
|
||||
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
|
||||
</ElTag>
|
||||
<ElTag
|
||||
:type="data.status === 1 ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
<ElTag :type="data.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ data.status === 1 ? '启用' : '禁用' }}
|
||||
</ElTag>
|
||||
</span>
|
||||
@@ -183,16 +177,10 @@
|
||||
<span class="tree-node-content">
|
||||
<span class="tree-node-label">
|
||||
<span>{{ node.label }}</span>
|
||||
<ElTag
|
||||
:type="data.perm_type === 1 ? 'info' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
<ElTag :type="data.perm_type === 1 ? 'info' : 'success'" size="small">
|
||||
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
|
||||
</ElTag>
|
||||
<ElTag
|
||||
:type="data.status === 1 ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
<ElTag :type="data.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ data.status === 1 ? '启用' : '禁用' }}
|
||||
</ElTag>
|
||||
</span>
|
||||
@@ -231,7 +219,9 @@
|
||||
ElTree,
|
||||
ElSwitch,
|
||||
ElButton,
|
||||
ElInput
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElOption
|
||||
} from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { PlatformRole, PermissionTreeNode } from '@/types/api'
|
||||
@@ -268,7 +258,9 @@
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
role_name: ''
|
||||
role_name: '',
|
||||
role_type: undefined as number | undefined,
|
||||
status: undefined as number | undefined
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
@@ -284,6 +276,32 @@
|
||||
clearable: true,
|
||||
placeholder: '请输入角色名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '角色类型',
|
||||
prop: 'role_type',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择角色类型'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '平台角色', value: 1 },
|
||||
{ label: '客户角色', value: 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择状态'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 0 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -373,13 +391,13 @@
|
||||
{
|
||||
prop: 'CreatedAt',
|
||||
label: '创建时间',
|
||||
minWidth: 180,
|
||||
width: 180,
|
||||
formatter: (row: any) => formatDateTime(row.CreatedAt)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 200,
|
||||
width: 240,
|
||||
fixed: 'right',
|
||||
formatter: (row: any) => {
|
||||
const buttons = []
|
||||
@@ -388,7 +406,7 @@
|
||||
if (hasAuth('role:permission')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
icon: '',
|
||||
text: '分配权限',
|
||||
onClick: () => showPermissionDialog(row)
|
||||
})
|
||||
)
|
||||
@@ -398,7 +416,7 @@
|
||||
if (hasAuth('role:edit')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
text: '编辑',
|
||||
onClick: () => showDialog('edit', row)
|
||||
})
|
||||
)
|
||||
@@ -408,7 +426,7 @@
|
||||
if (hasAuth('role:delete')) {
|
||||
buttons.push(
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
text: '删除',
|
||||
onClick: () => deleteRole(row)
|
||||
})
|
||||
)
|
||||
@@ -613,14 +631,14 @@
|
||||
// 保存右侧树的展开节点
|
||||
const expandedKeys = rightTreeRef.value?.store?.nodesMap
|
||||
? Object.keys(rightTreeRef.value.store.nodesMap)
|
||||
.filter(key => rightTreeRef.value.store.nodesMap[key].expanded)
|
||||
.map(key => Number(key))
|
||||
.filter((key) => rightTreeRef.value.store.nodesMap[key].expanded)
|
||||
.map((key) => Number(key))
|
||||
: []
|
||||
|
||||
await RoleService.removePermission(currentRoleId.value, permId)
|
||||
|
||||
// 更新已选权限列表
|
||||
selectedPermissions.value = selectedPermissions.value.filter(id => id !== permId)
|
||||
selectedPermissions.value = selectedPermissions.value.filter((id) => id !== permId)
|
||||
|
||||
// 重新构建左右两侧树
|
||||
const fullTreeData = buildTreeData(originalPermissionTree.value)
|
||||
@@ -641,7 +659,7 @@
|
||||
// 等待DOM更新后恢复展开状态
|
||||
await nextTick()
|
||||
if (rightTreeRef.value && expandedKeys.length > 0) {
|
||||
expandedKeys.forEach(key => {
|
||||
expandedKeys.forEach((key) => {
|
||||
const node = rightTreeRef.value.store.nodesMap[key]
|
||||
if (node && !node.isLeaf) {
|
||||
node.expanded = true
|
||||
@@ -705,7 +723,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 获取角色列表
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
@@ -713,7 +730,9 @@
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
role_name: searchForm.role_name || undefined
|
||||
role_name: searchForm.role_name || undefined,
|
||||
role_type: searchForm.role_type,
|
||||
status: searchForm.status
|
||||
}
|
||||
const res = await RoleService.getRoles(params)
|
||||
if (res.code === 0) {
|
||||
|
||||
Reference in New Issue
Block a user