This commit is contained in:
@@ -422,7 +422,7 @@
|
|||||||
"agent": "代理商管理",
|
"agent": "代理商管理",
|
||||||
"customerAccount": "客户账号",
|
"customerAccount": "客户账号",
|
||||||
"enterpriseCustomer": "企业客户",
|
"enterpriseCustomer": "企业客户",
|
||||||
"enterpriseCustomerAccounts": "客户账号列表",
|
"enterpriseCustomerAccounts": "关联账号列表",
|
||||||
"enterpriseCards": "企业卡管理",
|
"enterpriseCards": "企业卡管理",
|
||||||
"customerCommission": "客户账号佣金"
|
"customerCommission": "客户账号佣金"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -296,6 +296,15 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
roles: ['R_SUPER']
|
roles: ['R_SUPER']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'carrier-management',
|
||||||
|
name: 'CarrierManagement',
|
||||||
|
component: RoutesAlias.CarrierManagement,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.account.carrierManagement',
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'user-center',
|
path: 'user-center',
|
||||||
name: 'UserCenter',
|
name: 'UserCenter',
|
||||||
@@ -611,98 +620,98 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// 物联网卡管理系统模块
|
// 物联网卡管理系统模块
|
||||||
{
|
// {
|
||||||
path: '/card-management',
|
// path: '/card-management',
|
||||||
name: 'CardManagement',
|
// name: 'CardManagement',
|
||||||
component: RoutesAlias.Home,
|
// component: RoutesAlias.Home,
|
||||||
meta: {
|
// meta: {
|
||||||
title: 'menus.cardManagement.title',
|
// title: 'menus.cardManagement.title',
|
||||||
icon: ''
|
// icon: ''
|
||||||
},
|
// },
|
||||||
children: [
|
// children: [
|
||||||
// {
|
// {
|
||||||
// path: 'card-detail',
|
// path: 'card-detail',
|
||||||
// name: 'CardDetail',
|
// name: 'CardDetail',
|
||||||
// component: RoutesAlias.CardDetail,
|
// component: RoutesAlias.CardDetail,
|
||||||
// meta: {
|
// meta: {
|
||||||
// title: 'menus.cardManagement.cardDetail',
|
// title: 'menus.cardManagement.cardDetail',
|
||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
{
|
// {
|
||||||
path: 'single-card',
|
// path: 'single-card',
|
||||||
name: 'SingleCard',
|
// name: 'SingleCard',
|
||||||
component: RoutesAlias.SingleCard,
|
// component: RoutesAlias.SingleCard,
|
||||||
meta: {
|
// meta: {
|
||||||
title: 'menus.cardManagement.singleCard',
|
// title: 'menus.cardManagement.singleCard',
|
||||||
keepAlive: true
|
// keepAlive: true
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
// {
|
// {
|
||||||
// path: 'card-assign',
|
// path: 'card-assign',
|
||||||
// name: 'CardAssign',
|
// name: 'CardAssign',
|
||||||
// component: RoutesAlias.CardAssign,
|
// component: RoutesAlias.CardAssign,
|
||||||
// meta: {
|
// meta: {
|
||||||
// title: 'menus.cardManagement.cardAssign',
|
// title: 'menus.cardManagement.cardAssign',
|
||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// path: 'card-shutdown',
|
// path: 'card-shutdown',
|
||||||
// name: 'CardShutdown',
|
// name: 'CardShutdown',
|
||||||
// component: RoutesAlias.CardShutdown,
|
// component: RoutesAlias.CardShutdown,
|
||||||
// meta: {
|
// meta: {
|
||||||
// title: 'menus.cardManagement.cardShutdown',
|
// title: 'menus.cardManagement.cardShutdown',
|
||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// path: 'my-cards',
|
// path: 'my-cards',
|
||||||
// name: 'MyCards',
|
// name: 'MyCards',
|
||||||
// component: RoutesAlias.MyCards,
|
// component: RoutesAlias.MyCards,
|
||||||
// meta: {
|
// meta: {
|
||||||
// title: 'menus.cardManagement.myCards',
|
// title: 'menus.cardManagement.myCards',
|
||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// path: 'card-transfer',
|
// path: 'card-transfer',
|
||||||
// name: 'CardTransfer',
|
// name: 'CardTransfer',
|
||||||
// component: RoutesAlias.CardTransfer,
|
// component: RoutesAlias.CardTransfer,
|
||||||
// meta: {
|
// meta: {
|
||||||
// title: 'menus.cardManagement.cardTransfer',
|
// title: 'menus.cardManagement.cardTransfer',
|
||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// path: 'card-replacement',
|
// path: 'card-replacement',
|
||||||
// name: 'CardReplacement',
|
// name: 'CardReplacement',
|
||||||
// component: RoutesAlias.CardReplacement,
|
// component: RoutesAlias.CardReplacement,
|
||||||
// meta: {
|
// meta: {
|
||||||
// title: 'menus.cardManagement.cardReplacement',
|
// title: 'menus.cardManagement.cardReplacement',
|
||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// path: 'package-gift',
|
// path: 'package-gift',
|
||||||
// name: 'PackageGift',
|
// name: 'PackageGift',
|
||||||
// component: RoutesAlias.PackageGift,
|
// component: RoutesAlias.PackageGift,
|
||||||
// meta: {
|
// meta: {
|
||||||
// title: 'menus.cardManagement.packageGift',
|
// title: 'menus.cardManagement.packageGift',
|
||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// path: 'card-change-card',
|
// path: 'card-change-card',
|
||||||
// name: 'CardChangeCard',
|
// name: 'CardChangeCard',
|
||||||
// component: RoutesAlias.CardChangeCard,
|
// component: RoutesAlias.CardChangeCard,
|
||||||
// meta: {
|
// meta: {
|
||||||
// title: 'menus.cardManagement.cardChange',
|
// title: 'menus.cardManagement.cardChange',
|
||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
path: '/package-management',
|
path: '/package-management',
|
||||||
name: 'PackageManagement',
|
name: 'PackageManagement',
|
||||||
@@ -969,6 +978,15 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
icon: ''
|
icon: ''
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: 'single-card',
|
||||||
|
name: 'SingleCard',
|
||||||
|
component: RoutesAlias.SingleCard,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.cardManagement.singleCard',
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'iot-card-management',
|
path: 'iot-card-management',
|
||||||
name: 'StandaloneCardList',
|
name: 'StandaloneCardList',
|
||||||
@@ -1072,15 +1090,6 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
{
|
|
||||||
path: 'carrier-management',
|
|
||||||
name: 'CarrierManagement',
|
|
||||||
component: RoutesAlias.CarrierManagement,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.account.carrierManagement',
|
|
||||||
keepAlive: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'orders',
|
path: 'orders',
|
||||||
name: 'OrderManagement',
|
name: 'OrderManagement',
|
||||||
@@ -1089,7 +1098,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
title: 'menus.account.orders',
|
title: 'menus.account.orders',
|
||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// {
|
// {
|
||||||
// path: 'my-account',
|
// path: 'my-account',
|
||||||
// name: 'MyAccount',
|
// name: 'MyAccount',
|
||||||
@@ -1099,56 +1108,56 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// keepAlive: true
|
// keepAlive: true
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/commission',
|
||||||
|
name: 'CommissionManagement',
|
||||||
|
component: RoutesAlias.Home,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.commission.menu.management',
|
||||||
|
icon: ''
|
||||||
|
},
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
path: 'commission',
|
path: 'withdrawal-approval',
|
||||||
name: 'CommissionManagement',
|
name: 'WithdrawalApproval',
|
||||||
component: '',
|
component: RoutesAlias.WithdrawalApproval,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'menus.commission.menu.management',
|
title: 'menus.commission.menu.withdrawal',
|
||||||
icon: ''
|
keepAlive: true,
|
||||||
},
|
roles: ['R_SUPER', 'R_ADMIN']
|
||||||
children: [
|
}
|
||||||
{
|
},
|
||||||
path: 'withdrawal-approval',
|
{
|
||||||
name: 'WithdrawalApproval',
|
path: 'withdrawal-settings',
|
||||||
component: RoutesAlias.WithdrawalApproval,
|
name: 'CommissionWithdrawalSettings',
|
||||||
meta: {
|
component: RoutesAlias.CommissionWithdrawalSettings,
|
||||||
title: 'menus.commission.menu.withdrawal',
|
meta: {
|
||||||
keepAlive: true,
|
title: 'menus.commission.menu.withdrawalSettings',
|
||||||
roles: ['R_SUPER', 'R_ADMIN']
|
keepAlive: true,
|
||||||
}
|
roles: ['R_SUPER', 'R_ADMIN']
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
path: 'withdrawal-settings',
|
{
|
||||||
name: 'CommissionWithdrawalSettings',
|
path: 'my-commission',
|
||||||
component: RoutesAlias.CommissionWithdrawalSettings,
|
name: 'MyCommission',
|
||||||
meta: {
|
component: RoutesAlias.MyCommission,
|
||||||
title: 'menus.commission.menu.withdrawalSettings',
|
meta: {
|
||||||
keepAlive: true,
|
title: 'menus.commission.menu.myCommission',
|
||||||
roles: ['R_SUPER', 'R_ADMIN']
|
keepAlive: true,
|
||||||
}
|
roles: ['R_AGENT']
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
path: 'my-commission',
|
{
|
||||||
name: 'MyCommission',
|
path: 'agent-commission',
|
||||||
component: RoutesAlias.MyCommission,
|
name: 'AgentCommission',
|
||||||
meta: {
|
component: RoutesAlias.AgentCommission,
|
||||||
title: 'menus.commission.menu.myCommission',
|
meta: {
|
||||||
keepAlive: true,
|
title: 'menus.commission.menu.agentCommission',
|
||||||
roles: ['R_AGENT']
|
keepAlive: true,
|
||||||
}
|
roles: ['R_SUPER', 'R_ADMIN']
|
||||||
},
|
}
|
||||||
{
|
|
||||||
path: 'agent-commission',
|
|
||||||
name: 'AgentCommission',
|
|
||||||
component: RoutesAlias.AgentCommission,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.commission.menu.agentCommission',
|
|
||||||
keepAlive: true,
|
|
||||||
roles: ['R_SUPER', 'R_ADMIN']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,44 +68,6 @@
|
|||||||
>
|
>
|
||||||
<ElOption label="超级管理员" :value="1" />
|
<ElOption label="超级管理员" :value="1" />
|
||||||
<ElOption label="平台用户" :value="2" />
|
<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>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
@@ -124,6 +86,9 @@
|
|||||||
<span class="dialog-title">分配角色</span>
|
<span class="dialog-title">分配角色</span>
|
||||||
<div class="account-info">
|
<div class="account-info">
|
||||||
<span class="account-name">{{ currentAccountName }}</span>
|
<span class="account-name">{{ currentAccountName }}</span>
|
||||||
|
<ElTag v-if="currentAccountType === 3" type="warning" size="small" style="margin-left: 8px">
|
||||||
|
代理账号只能分配一个客户角色
|
||||||
|
</ElTag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -142,18 +107,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<ElCheckboxGroup v-model="rolesToAdd" class="role-list">
|
<ElCheckboxGroup v-model="rolesToAdd" class="role-list">
|
||||||
<div
|
<div v-for="role in filteredAvailableRoles" :key="role.ID" class="role-item">
|
||||||
v-for="role in filteredAvailableRoles"
|
|
||||||
:key="role.ID"
|
|
||||||
class="role-item"
|
|
||||||
>
|
|
||||||
<ElCheckbox :label="role.ID" :disabled="selectedRoles.includes(role.ID)">
|
<ElCheckbox :label="role.ID" :disabled="selectedRoles.includes(role.ID)">
|
||||||
<span class="role-info">
|
<span class="role-info">
|
||||||
<span>{{ role.role_name }}</span>
|
<span>{{ role.role_name }}</span>
|
||||||
<ElTag
|
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||||
:type="role.role_type === 1 ? 'primary' : 'success'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</span>
|
</span>
|
||||||
@@ -196,19 +154,11 @@
|
|||||||
>
|
>
|
||||||
<span class="role-info">
|
<span class="role-info">
|
||||||
<span>{{ role.role_name }}</span>
|
<span>{{ role.role_name }}</span>
|
||||||
<ElTag
|
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||||||
:type="role.role_type === 1 ? 'primary' : 'success'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</span>
|
</span>
|
||||||
<ElButton
|
<ElButton type="danger" size="small" link @click="removeSingleRole(role.ID)">
|
||||||
type="danger"
|
|
||||||
size="small"
|
|
||||||
link
|
|
||||||
@click="removeSingleRole(role.ID)"
|
|
||||||
>
|
|
||||||
移除
|
移除
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -256,6 +206,7 @@
|
|||||||
const roleSubmitLoading = ref(false)
|
const roleSubmitLoading = ref(false)
|
||||||
const currentAccountId = ref<number>(0)
|
const currentAccountId = ref<number>(0)
|
||||||
const currentAccountName = ref<string>('')
|
const currentAccountName = ref<string>('')
|
||||||
|
const currentAccountType = ref<number>(0)
|
||||||
const selectedRoles = ref<number[]>([])
|
const selectedRoles = ref<number[]>([])
|
||||||
const allRoles = ref<PlatformRole[]>([])
|
const allRoles = ref<PlatformRole[]>([])
|
||||||
const rolesToAdd = ref<number[]>([])
|
const rolesToAdd = ref<number[]>([])
|
||||||
@@ -517,7 +468,7 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 200,
|
width: 240,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: any) => {
|
formatter: (row: any) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
@@ -525,7 +476,7 @@
|
|||||||
if (hasAuth('account:patch_role')) {
|
if (hasAuth('account:patch_role')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
icon: '',
|
text: '分配角色',
|
||||||
onClick: () => showRoleDialog(row)
|
onClick: () => showRoleDialog(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -534,7 +485,7 @@
|
|||||||
if (hasAuth('account:edit')) {
|
if (hasAuth('account:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'edit',
|
text: '编辑',
|
||||||
onClick: () => showDialog('edit', row)
|
onClick: () => showDialog('edit', row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -543,7 +494,7 @@
|
|||||||
if (hasAuth('account:delete')) {
|
if (hasAuth('account:delete')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'delete',
|
text: '删除',
|
||||||
onClick: () => deleteAccount(row)
|
onClick: () => deleteAccount(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -593,11 +544,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算属性:过滤后的可分配角色
|
// 计算属性:过滤后的可分配角色(根据账号类型过滤)
|
||||||
const filteredAvailableRoles = computed(() => {
|
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()
|
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) => {
|
const showRoleDialog = async (row: any) => {
|
||||||
currentAccountId.value = row.id
|
currentAccountId.value = row.id
|
||||||
currentAccountName.value = row.username
|
currentAccountName.value = row.username
|
||||||
|
currentAccountType.value = row.user_type
|
||||||
selectedRoles.value = []
|
selectedRoles.value = []
|
||||||
rolesToAdd.value = []
|
rolesToAdd.value = []
|
||||||
leftRoleFilter.value = ''
|
leftRoleFilter.value = ''
|
||||||
@@ -641,18 +605,31 @@
|
|||||||
if (rolesToAdd.value.length === 0) return
|
if (rolesToAdd.value.length === 0) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 将选中的角色添加到已分配列表
|
let newRoles: number[]
|
||||||
const newRoles = [...new Set([...selectedRoles.value, ...rolesToAdd.value])]
|
|
||||||
|
// 代理账号只能分配一个角色,会覆盖之前的角色
|
||||||
|
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)
|
await AccountService.assignRolesToAccount(currentAccountId.value, newRoles)
|
||||||
|
|
||||||
selectedRoles.value = newRoles
|
selectedRoles.value = newRoles
|
||||||
rolesToAdd.value = []
|
rolesToAdd.value = []
|
||||||
|
|
||||||
ElMessage.success('角色添加成功')
|
ElMessage.success('角色分配成功')
|
||||||
// 刷新列表以更新角色显示
|
// 刷新列表以更新角色显示
|
||||||
await getAccountList()
|
await getAccountList()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('添加角色失败:', error)
|
console.error('分配角色失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,25 +39,39 @@
|
|||||||
<!-- 授权详情对话框 -->
|
<!-- 授权详情对话框 -->
|
||||||
<ElDialog v-model="detailDialogVisible" title="授权详情" width="700px">
|
<ElDialog v-model="detailDialogVisible" title="授权详情" width="700px">
|
||||||
<ElDescriptions v-if="currentRecord" :column="2" border>
|
<ElDescriptions v-if="currentRecord" :column="2" border>
|
||||||
<ElDescriptionsItem label="企业ID">{{ currentRecord.enterprise_id }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="企业ID">{{
|
||||||
<ElDescriptionsItem label="企业名称">{{ currentRecord.enterprise_name }}</ElDescriptionsItem>
|
currentRecord.enterprise_id
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="企业名称">{{
|
||||||
|
currentRecord.enterprise_name
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="卡ID">{{ currentRecord.card_id }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="卡ID">{{ currentRecord.card_id }}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="ICCID">{{ currentRecord.iccid }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="ICCID">{{ currentRecord.iccid }}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="手机号">{{ currentRecord.msisdn || '--' }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="手机号">{{
|
||||||
<ElDescriptionsItem label="授权人ID">{{ currentRecord.authorized_by }}</ElDescriptionsItem>
|
currentRecord.msisdn || '--'
|
||||||
<ElDescriptionsItem label="授权人">{{ currentRecord.authorizer_name }}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="授权人ID">{{
|
||||||
|
currentRecord.authorized_by
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
|
<ElDescriptionsItem label="授权人">{{
|
||||||
|
currentRecord.authorizer_name
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="授权人类型">
|
<ElDescriptionsItem label="授权人类型">
|
||||||
<ElTag :type="getAuthorizerTypeTag(currentRecord.authorizer_type)">
|
<ElTag :type="getAuthorizerTypeTag(currentRecord.authorizer_type)">
|
||||||
{{ getAuthorizerTypeText(currentRecord.authorizer_type) }}
|
{{ getAuthorizerTypeText(currentRecord.authorizer_type) }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="授权时间" :span="2">{{ formatDateTime(currentRecord.authorized_at) }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="授权时间" :span="2">{{
|
||||||
|
formatDateTime(currentRecord.authorized_at)
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="状态" :span="2">
|
<ElDescriptionsItem label="状态" :span="2">
|
||||||
<ElTag :type="getStatusTag(currentRecord.status)">
|
<ElTag :type="getStatusTag(currentRecord.status)">
|
||||||
{{ getStatusText(currentRecord.status) }}
|
{{ getStatusText(currentRecord.status) }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="备注" :span="2">{{ currentRecord.remark || '--' }}</ElDescriptionsItem>
|
<ElDescriptionsItem label="备注" :span="2">{{
|
||||||
|
currentRecord.remark || '--'
|
||||||
|
}}</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
@@ -311,7 +325,7 @@
|
|||||||
if (hasAuth('authorization_records:update_remark')) {
|
if (hasAuth('authorization_records:update_remark')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'edit',
|
text: '编辑',
|
||||||
onClick: () => showRemarkDialog(row)
|
onClick: () => showRemarkDialog(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -188,26 +188,6 @@
|
|||||||
@close="handleRecallDialogClose"
|
@close="handleRecallDialogClose"
|
||||||
>
|
>
|
||||||
<ElForm ref="recallFormRef" :model="recallForm" :rules="recallRules" label-width="120px">
|
<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">
|
<ElFormItem label="选卡方式" prop="selection_type">
|
||||||
<ElRadioGroup v-model="recallForm.selection_type">
|
<ElRadioGroup v-model="recallForm.selection_type">
|
||||||
<ElRadio label="list">ICCID列表</ElRadio>
|
<ElRadio label="list">ICCID列表</ElRadio>
|
||||||
@@ -755,7 +735,6 @@
|
|||||||
// 批量回收表单
|
// 批量回收表单
|
||||||
const recallForm = reactive<Partial<RecallStandaloneCardsRequest>>({
|
const recallForm = reactive<Partial<RecallStandaloneCardsRequest>>({
|
||||||
selection_type: 'list',
|
selection_type: 'list',
|
||||||
from_shop_id: undefined,
|
|
||||||
iccids: [],
|
iccids: [],
|
||||||
iccid_start: '',
|
iccid_start: '',
|
||||||
iccid_end: '',
|
iccid_end: '',
|
||||||
@@ -766,7 +745,6 @@
|
|||||||
|
|
||||||
// 批量回收表单验证规则
|
// 批量回收表单验证规则
|
||||||
const recallRules = reactive<FormRules>({
|
const recallRules = reactive<FormRules>({
|
||||||
from_shop_id: [{ required: true, message: '请选择来源店铺', trigger: 'change' }],
|
|
||||||
selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }],
|
selection_type: [{ required: true, message: '请选择选卡方式', trigger: 'change' }],
|
||||||
iccid_start: [
|
iccid_start: [
|
||||||
{
|
{
|
||||||
@@ -1285,7 +1263,6 @@
|
|||||||
recallDialogVisible.value = true
|
recallDialogVisible.value = true
|
||||||
Object.assign(recallForm, {
|
Object.assign(recallForm, {
|
||||||
selection_type: 'list',
|
selection_type: 'list',
|
||||||
from_shop_id: undefined,
|
|
||||||
iccids: selectedCards.value.map((card) => card.iccid),
|
iccids: selectedCards.value.map((card) => card.iccid),
|
||||||
iccid_start: '',
|
iccid_start: '',
|
||||||
iccid_end: '',
|
iccid_end: '',
|
||||||
@@ -1293,8 +1270,6 @@
|
|||||||
batch_no: '',
|
batch_no: '',
|
||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
// 加载默认店铺列表
|
|
||||||
loadFromShops()
|
|
||||||
if (recallFormRef.value) {
|
if (recallFormRef.value) {
|
||||||
recallFormRef.value.resetFields()
|
recallFormRef.value.resetFields()
|
||||||
}
|
}
|
||||||
@@ -1378,7 +1353,6 @@
|
|||||||
// 根据选卡方式构建请求参数
|
// 根据选卡方式构建请求参数
|
||||||
const params: Partial<RecallStandaloneCardsRequest> = {
|
const params: Partial<RecallStandaloneCardsRequest> = {
|
||||||
selection_type: recallForm.selection_type!,
|
selection_type: recallForm.selection_type!,
|
||||||
from_shop_id: recallForm.from_shop_id!,
|
|
||||||
remark: recallForm.remark
|
remark: recallForm.remark
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,89 @@
|
|||||||
<template>
|
<template>
|
||||||
<ArtTableFullScreen>
|
<ArtTableFullScreen>
|
||||||
<div class="single-card-page" id="table-full-screen">
|
<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">
|
<ElCard shadow="never" class="card-info-card" style="margin-bottom: 16px">
|
||||||
<template #header>
|
<template #header>
|
||||||
<span>网卡信息</span>
|
<span>网卡信息</span>
|
||||||
</template>
|
</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">
|
<ElRow :gutter="24">
|
||||||
<ElCol :span="8">
|
<ElCol :span="8">
|
||||||
<ElFormItem label="ICCID:">
|
<ElFormItem label="ICCID:">
|
||||||
<span>{{ cardInfo.iccid }}</span>
|
<span>{{ cardInfo.iccid || '-' }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="8">
|
<ElCol :span="8">
|
||||||
<ElFormItem label="IMSI:">
|
<ElFormItem label="IMSI:">
|
||||||
<span>{{ cardInfo.imsi }}</span>
|
<span>{{ cardInfo.imsi || '-' }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="8">
|
<ElCol :span="8">
|
||||||
<ElFormItem label="手机号码:">
|
<ElFormItem label="手机号码:">
|
||||||
<span>{{ cardInfo.msisdn }}</span>
|
<span>{{ cardInfo.msisdn || '-' }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
<ElRow :gutter="24">
|
<ElRow :gutter="24">
|
||||||
<ElCol :span="8">
|
<ElCol :span="8">
|
||||||
<ElFormItem label="运营商:">
|
<ElFormItem label="运营商:">
|
||||||
<ElTag :type="getOperatorTagType(cardInfo.operator)">{{
|
<ElTag v-if="cardInfo.operatorName" :type="getOperatorTagType(cardInfo.operator)">
|
||||||
cardInfo.operatorName
|
{{ cardInfo.operatorName }}
|
||||||
}}</ElTag>
|
</ElTag>
|
||||||
|
<span v-else>-</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="8">
|
<ElCol :span="8">
|
||||||
<ElFormItem label="网络类型:">
|
<ElFormItem label="网络类型:">
|
||||||
<span>{{ cardInfo.networkType }}</span>
|
<span>{{ cardInfo.networkType || '-' }}</span>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="8">
|
<ElCol :span="8">
|
||||||
<ElFormItem label="状态:">
|
<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>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
@@ -47,7 +91,7 @@
|
|||||||
</ElCard>
|
</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>
|
<template #header>
|
||||||
<span>操作区域</span>
|
<span>操作区域</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -66,7 +110,7 @@
|
|||||||
</ElCard>
|
</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>
|
<template #header>
|
||||||
<span>流量信息</span>
|
<span>流量信息</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -100,7 +144,7 @@
|
|||||||
</ElCard>
|
</ElCard>
|
||||||
|
|
||||||
<!-- 使用记录 -->
|
<!-- 使用记录 -->
|
||||||
<ElCard shadow="never" class="art-table-card">
|
<ElCard v-if="hasSearched" shadow="never" class="art-table-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<span>使用记录</span>
|
<span>使用记录</span>
|
||||||
<ElButton style="float: right" @click="refreshUsageRecords">刷新</ElButton>
|
<ElButton style="float: right" @click="refreshUsageRecords">刷新</ElButton>
|
||||||
@@ -158,6 +202,7 @@
|
|||||||
import { ElTag, ElMessageBox, ElMessage, FormInstance } from 'element-plus'
|
import { ElTag, ElMessageBox, ElMessage, FormInstance } from 'element-plus'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import type { FormRules } from 'element-plus'
|
import type { FormRules } from 'element-plus'
|
||||||
|
import { CardService } from '@/api/modules'
|
||||||
|
|
||||||
defineOptions({ name: 'SingleCard' })
|
defineOptions({ name: 'SingleCard' })
|
||||||
|
|
||||||
@@ -165,26 +210,36 @@
|
|||||||
const rechargeDialogVisible = ref(false)
|
const rechargeDialogVisible = ref(false)
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
// 网卡信息
|
// ICCID搜索相关
|
||||||
const cardInfo = reactive({
|
const searchIccid = ref('')
|
||||||
iccid: '89860123456789012345',
|
const hasSearched = ref(false)
|
||||||
imsi: '460012345678901',
|
|
||||||
msisdn: '13800138001',
|
// 格式化显示的ICCID(4位一组,用横杠分隔)
|
||||||
operator: 'mobile',
|
const formattedIccid = computed(() => {
|
||||||
operatorName: '中国移动',
|
if (!cardInfo.iccid) return ''
|
||||||
networkType: '4G',
|
return cardInfo.iccid.replace(/(\d{4})(?=\d)/g, '$1-')
|
||||||
status: '1',
|
|
||||||
statusName: '激活',
|
|
||||||
activatedDate: '2024-01-15',
|
|
||||||
expiryDate: '2025-01-15'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 流量信息
|
// 网卡信息 - 默认为空
|
||||||
|
const cardInfo = reactive({
|
||||||
|
iccid: '',
|
||||||
|
imsi: '',
|
||||||
|
msisdn: '',
|
||||||
|
operator: '',
|
||||||
|
operatorName: '',
|
||||||
|
networkType: '',
|
||||||
|
status: '',
|
||||||
|
statusName: '',
|
||||||
|
activatedDate: '',
|
||||||
|
expiryDate: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 流量信息 - 默认为空
|
||||||
const trafficInfo = reactive({
|
const trafficInfo = reactive({
|
||||||
totalTraffic: '10GB',
|
totalTraffic: '0MB',
|
||||||
usedTraffic: '2.5GB',
|
usedTraffic: '0MB',
|
||||||
remainingTraffic: '7.5GB',
|
remainingTraffic: '0MB',
|
||||||
usagePercentage: '25'
|
usagePercentage: '0'
|
||||||
})
|
})
|
||||||
|
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
@@ -193,25 +248,8 @@
|
|||||||
total: 0
|
total: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// 使用记录
|
// 使用记录 - 默认为空
|
||||||
const usageRecords = ref([
|
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 rechargeForm = reactive({
|
const rechargeForm = reactive({
|
||||||
@@ -394,40 +432,107 @@
|
|||||||
pagination.total = usageRecords.value.length
|
pagination.total = usageRecords.value.length
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 处理ICCID搜索
|
||||||
|
const handleSearchCard = async () => {
|
||||||
|
if (!searchIccid.value.trim()) {
|
||||||
|
ElMessage.warning('请输入ICCID')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await loadCardInfoByIccid(searchIccid.value.trim())
|
||||||
|
}
|
||||||
|
|
||||||
// 根据ICCID加载卡片信息
|
// 根据ICCID加载卡片信息
|
||||||
const loadCardInfoByIccid = async (iccid: string) => {
|
const loadCardInfoByIccid = async (iccid: string) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// 这里应该调用API根据ICCID获取卡片详细信息
|
const response = await CardService.getIotCardDetailByIccid(iccid)
|
||||||
// 模拟API调用
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
||||||
|
|
||||||
// 模拟更新卡片信息(实际应该从API获取)
|
if (response.code === 200 && response.data) {
|
||||||
Object.assign(cardInfo, {
|
const data = response.data
|
||||||
iccid: iccid,
|
hasSearched.value = true
|
||||||
imsi: '460012345678901',
|
|
||||||
msisdn: '13800138001',
|
|
||||||
operator: 'mobile',
|
|
||||||
operatorName: '中国移动',
|
|
||||||
networkType: '4G',
|
|
||||||
status: '1',
|
|
||||||
statusName: '激活',
|
|
||||||
activatedDate: '2024-01-15',
|
|
||||||
expiryDate: '2025-01-15'
|
|
||||||
})
|
|
||||||
|
|
||||||
ElMessage.success(`已加载ICCID ${iccid} 的详细信息`)
|
// 更新网卡基本信息
|
||||||
} catch (error) {
|
Object.assign(cardInfo, {
|
||||||
|
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 || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新流量信息
|
||||||
|
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)
|
console.error('获取卡片信息失败:', error)
|
||||||
ElMessage.error('获取卡片信息失败')
|
ElMessage.error(error?.message || '获取卡片信息失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.single-card-page {
|
.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 {
|
.operation-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
<ElCard shadow="never">
|
<ElCard shadow="never">
|
||||||
<div class="detail-header">
|
<div class="detail-header">
|
||||||
<ElButton @click="handleBack">
|
<ElButton @click="handleBack">
|
||||||
<template #icon><ElIcon><ArrowLeft /></ElIcon></template>
|
<template #icon
|
||||||
|
><ElIcon><ArrowLeft /></ElIcon
|
||||||
|
></template>
|
||||||
返回
|
返回
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<h2 class="detail-title">{{ pageTitle }}</h2>
|
<h2 class="detail-title">{{ pageTitle }}</h2>
|
||||||
@@ -19,6 +21,13 @@
|
|||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
></ArtSearchBar>
|
></ArtSearchBar>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div style="margin: 10px 0">
|
||||||
|
<ElButton @click="showAddAccountDialog">
|
||||||
|
{{ isShopType ? '新增店铺账号' : '新增企业账号' }}
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<ArtTable
|
<ArtTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
@@ -28,28 +37,176 @@
|
|||||||
:currentPage="pagination.page"
|
:currentPage="pagination.page"
|
||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"v
|
:marginTop="10"
|
||||||
|
v
|
||||||
height="60vh"
|
height="60vh"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
>
|
>
|
||||||
<template #default>
|
<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>
|
</template>
|
||||||
</ArtTable>
|
</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>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
</ArtTableFullScreen>
|
</ArtTableFullScreen>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 { 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 { 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 { SearchFormItem } from '@/types'
|
||||||
import type { PlatformAccount } from '@/types/api'
|
import type { PlatformAccount, PlatformRole } from '@/types/api'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
defineOptions({ name: 'CommonAccountList' })
|
defineOptions({ name: 'CommonAccountList' })
|
||||||
@@ -86,6 +243,17 @@
|
|||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const accountList = ref<PlatformAccount[]>([])
|
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 = {
|
const initialSearchState = {
|
||||||
username: '',
|
username: '',
|
||||||
@@ -124,19 +292,6 @@
|
|||||||
placeholder: '请输入手机号'
|
placeholder: '请输入手机号'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '用户类型',
|
|
||||||
prop: 'user_type',
|
|
||||||
type: 'select',
|
|
||||||
config: {
|
|
||||||
clearable: true,
|
|
||||||
placeholder: '全部'
|
|
||||||
},
|
|
||||||
options: () => [
|
|
||||||
{ label: '代理账号', value: 3 },
|
|
||||||
{ label: '企业账号', value: 4 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '状态',
|
label: '状态',
|
||||||
prop: 'status',
|
prop: 'status',
|
||||||
@@ -163,52 +318,74 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 列配置
|
// 列配置
|
||||||
const columns = computed(() => [
|
const columns = computed(() => {
|
||||||
{
|
const baseColumns = [
|
||||||
prop: 'username',
|
{
|
||||||
label: '用户名',
|
prop: 'username',
|
||||||
minWidth: 150
|
label: '用户名'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'phone',
|
prop: 'phone',
|
||||||
label: '手机号',
|
label: '手机号'
|
||||||
width: 130
|
},
|
||||||
},
|
{
|
||||||
{
|
prop: 'user_type',
|
||||||
prop: 'user_type',
|
label: '用户类型',
|
||||||
label: '用户类型',
|
formatter: (row: PlatformAccount) => {
|
||||||
width: 110,
|
return h(ElTag, { type: getUserTypeTag(row.user_type) }, () =>
|
||||||
formatter: (row: PlatformAccount) => {
|
getUserTypeName(row.user_type)
|
||||||
return h(ElTag, { type: getUserTypeTag(row.user_type) }, () => getUserTypeName(row.user_type))
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
{
|
|
||||||
prop: 'shop_id',
|
// 根据页面类型添加不同的列
|
||||||
label: '店铺ID',
|
if (isShopType.value) {
|
||||||
width: 100,
|
// 店铺类型:显示店铺名称
|
||||||
formatter: (row: PlatformAccount) => row.shop_id || '-'
|
baseColumns.push({
|
||||||
},
|
prop: 'shop_name',
|
||||||
{
|
label: '店铺名称',
|
||||||
prop: 'enterprise_id',
|
formatter: (row: PlatformAccount) => (row as any).shop_name || '-'
|
||||||
label: '企业ID',
|
})
|
||||||
width: 100,
|
} else {
|
||||||
formatter: (row: PlatformAccount) => row.enterprise_id || '-'
|
// 企业类型:显示企业名称
|
||||||
},
|
baseColumns.push({
|
||||||
{
|
prop: 'enterprise_name',
|
||||||
prop: 'status',
|
label: '企业名称',
|
||||||
label: '状态',
|
formatter: (row: PlatformAccount) => (row as any).enterprise_name || '-'
|
||||||
width: 100,
|
})
|
||||||
formatter: (row: PlatformAccount) => {
|
|
||||||
return h(ElTag, { type: getStatusTag(row.status) }, () => getStatusName(row.status))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'CreatedAt',
|
|
||||||
label: '创建时间',
|
|
||||||
width: 180,
|
|
||||||
formatter: (row: PlatformAccount) => formatDateTime(row.CreatedAt)
|
|
||||||
}
|
}
|
||||||
])
|
|
||||||
|
// 添加状态和创建时间
|
||||||
|
baseColumns.push(
|
||||||
|
{
|
||||||
|
prop: 'status',
|
||||||
|
label: '状态',
|
||||||
|
formatter: (row: PlatformAccount) => {
|
||||||
|
return h(ElTag, { type: getStatusTag(row.status) }, () => getStatusName(row.status))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'created_at',
|
||||||
|
label: '创建时间',
|
||||||
|
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 () => {
|
const getTableData = async () => {
|
||||||
@@ -220,7 +397,7 @@
|
|||||||
pageSize: pagination.pageSize,
|
pageSize: pagination.pageSize,
|
||||||
username: searchForm.username || undefined,
|
username: searchForm.username || undefined,
|
||||||
phone: searchForm.phone || undefined,
|
phone: searchForm.phone || undefined,
|
||||||
user_type: searchForm.user_type,
|
user_type: isShopType.value ? 3 : 4, // 根据页面类型自动设置: 3:代理账号, 4:企业账号
|
||||||
status: searchForm.status,
|
status: searchForm.status,
|
||||||
[filterParamKey.value]: entityId // 动态设置 shop_id 或 enterprise_id
|
[filterParamKey.value]: entityId // 动态设置 shop_id 或 enterprise_id
|
||||||
}
|
}
|
||||||
@@ -274,8 +451,214 @@
|
|||||||
router.back()
|
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(() => {
|
onMounted(() => {
|
||||||
getTableData()
|
getTableData()
|
||||||
|
loadAllRoles()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -285,9 +668,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-bottom: 24px;
|
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
border-bottom: 1px solid #e4e7ed;
|
|
||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
margin: 0;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -387,7 +387,7 @@
|
|||||||
if (hasAuth('agent_commission:detail')) {
|
if (hasAuth('agent_commission:detail')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
icon: '',
|
text: '详情',
|
||||||
onClick: () => showDetail(row)
|
onClick: () => showDetail(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="single-card-page">
|
<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">
|
<div v-if="cardInfo" class="card-content-area slide-in">
|
||||||
<!-- 主要内容区域 -->
|
<!-- 主要内容区域 -->
|
||||||
@@ -150,12 +188,12 @@
|
|||||||
min-width="200"
|
min-width="200"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
<ElTableColumn prop="packageType" label="类型" width="100" />
|
<ElTableColumn prop="packageType" label="类型" />
|
||||||
<ElTableColumn prop="totalFlow" label="总流量" width="100" />
|
<ElTableColumn prop="totalFlow" label="总流量" />
|
||||||
<ElTableColumn prop="usedFlow" label="已用" width="100" />
|
<ElTableColumn prop="usedFlow" label="已用" />
|
||||||
<ElTableColumn prop="remainFlow" label="剩余" width="100" />
|
<ElTableColumn prop="remainFlow" label="剩余" />
|
||||||
<ElTableColumn prop="expireTime" label="到期时间" width="120" />
|
<ElTableColumn prop="expireTime" label="到期时间" />
|
||||||
<ElTableColumn prop="status" label="状态" width="100">
|
<ElTableColumn prop="status" label="状态">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<ElTag :type="getPackageStatusType(scope.row.status)" size="small">
|
<ElTag :type="getPackageStatusType(scope.row.status)" size="small">
|
||||||
{{ scope.row.status }}
|
{{ scope.row.status }}
|
||||||
@@ -287,15 +325,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
|
||||||
<!--<div v-else-if="loading" class="loading-state">-->
|
|
||||||
<!-- <ElSkeleton :rows="10" animated />-->
|
|
||||||
<!--</div>-->
|
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<!--<div v-else class="empty-state">-->
|
<div v-else class="empty-state">
|
||||||
<!-- <ElEmpty description="暂无卡片数据" />-->
|
<ElEmpty description="请在上方输入ICCID进行查询" />
|
||||||
<!--</div>-->
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -314,13 +347,30 @@
|
|||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { EnterpriseService } from '@/api/modules/enterprise'
|
import { EnterpriseService } from '@/api/modules/enterprise'
|
||||||
|
import { CardService } from '@/api/modules'
|
||||||
|
|
||||||
defineOptions({ name: 'SingleCard' })
|
defineOptions({ name: 'SingleCard' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const loading = ref(true)
|
const loading = ref(false)
|
||||||
const operationLoading = 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 获取参数
|
// 从 URL 获取参数
|
||||||
const enterpriseId = computed(() => {
|
const enterpriseId = computed(() => {
|
||||||
const id = route.query.enterpriseId || route.query.enterprise_id
|
const id = route.query.enterpriseId || route.query.enterprise_id
|
||||||
@@ -336,10 +386,139 @@
|
|||||||
return cardInfo.value?.id ? Number(cardInfo.value.id) : null
|
return cardInfo.value?.id ? Number(cardInfo.value.id) : null
|
||||||
})
|
})
|
||||||
|
|
||||||
// 卡片信息
|
// 卡片信息 - 默认为null,等待查询
|
||||||
const cardInfo = ref<any>(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 = {
|
const mockCardData = {
|
||||||
id: 1, // 卡片ID
|
id: 1, // 卡片ID
|
||||||
iccid: '8986062357007989203',
|
iccid: '8986062357007989203',
|
||||||
@@ -381,24 +560,9 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取卡片详情
|
// 页面初始化 - 不自动加载数据,等待用户输入ICCID查询
|
||||||
const fetchCardDetail = async () => {
|
|
||||||
try {
|
|
||||||
loading.value = true
|
|
||||||
// 模拟API调用
|
|
||||||
setTimeout(() => {
|
|
||||||
cardInfo.value = { ...mockCardData }
|
|
||||||
loading.value = false
|
|
||||||
}, 500)
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('获取卡片详情失败')
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面初始化时加载数据
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchCardDetail()
|
// 不再自动加载模拟数据,等待用户查询
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取状态标签类型
|
// 获取状态标签类型
|
||||||
@@ -550,6 +714,89 @@
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.single-card-page {
|
.single-card-page {
|
||||||
padding: 20px 0;
|
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 {
|
.card-content-area {
|
||||||
&.slide-in {
|
&.slide-in {
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
>
|
>
|
||||||
<template #left>
|
<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>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
@@ -130,7 +132,11 @@
|
|||||||
reserve-keyword
|
reserve-keyword
|
||||||
:remote-method="handlePackageSearch"
|
:remote-method="handlePackageSearch"
|
||||||
:loading="packageSearchLoading"
|
: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
|
clearable
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
@@ -627,14 +633,14 @@
|
|||||||
formatter: (row: Order) => {
|
formatter: (row: Order) => {
|
||||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
icon: '',
|
text: '详情',
|
||||||
tooltip: t('orderManagement.actions.viewDetail'),
|
tooltip: t('orderManagement.actions.viewDetail'),
|
||||||
onClick: () => showOrderDetail(row)
|
onClick: () => showOrderDetail(row)
|
||||||
}),
|
}),
|
||||||
// 只有待支付和已支付的订单可以取消
|
// 只有待支付和已支付的订单可以取消
|
||||||
row.payment_status === 1 || row.payment_status === 2
|
row.payment_status === 1 || row.payment_status === 2
|
||||||
? h(ArtButtonTable, {
|
? h(ArtButtonTable, {
|
||||||
type: 'delete',
|
text: '删除',
|
||||||
tooltip: t('orderManagement.actions.cancel'),
|
tooltip: t('orderManagement.actions.cancel'),
|
||||||
onClick: () => handleCancelOrder(row)
|
onClick: () => handleCancelOrder(row)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,125 +25,123 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
||||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
import DetailPage from '@/components/common/DetailPage.vue'
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||||
import { ShopPackageAllocationService } from '@/api/modules'
|
import { ShopPackageAllocationService } from '@/api/modules'
|
||||||
import type { ShopPackageAllocationResponse } from '@/types/api'
|
import type { ShopPackageAllocationResponse } from '@/types/api'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
defineOptions({ name: 'PackageAssignDetail' })
|
defineOptions({ name: 'PackageAssignDetail' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detailData = ref<ShopPackageAllocationResponse | null>(null)
|
const detailData = ref<ShopPackageAllocationResponse | null>(null)
|
||||||
|
|
||||||
// 详情页配置
|
// 详情页配置
|
||||||
const detailSections: DetailSection[] = [
|
const detailSections: DetailSection[] = [
|
||||||
{
|
{
|
||||||
title: '基本信息',
|
title: '基本信息',
|
||||||
fields: [
|
fields: [
|
||||||
{ label: 'ID', prop: 'id' },
|
{ label: 'ID', prop: 'id' },
|
||||||
{ label: '套餐编码', prop: 'package_code' },
|
{ label: '套餐编码', prop: 'package_code' },
|
||||||
{ label: '套餐名称', prop: 'package_name' },
|
{ label: '套餐名称', prop: 'package_name' },
|
||||||
{ label: '系列名称', prop: 'series_name' },
|
{ label: '系列名称', prop: 'series_name' },
|
||||||
{ label: '被分配店铺', prop: 'shop_name' },
|
{ label: '被分配店铺', prop: 'shop_name' },
|
||||||
{
|
{
|
||||||
label: '分配者店铺',
|
label: '分配者店铺',
|
||||||
formatter: (_, data) => {
|
formatter: (_, data) => {
|
||||||
if (data.allocator_shop_id === 0) {
|
if (data.allocator_shop_id === 0) {
|
||||||
return '平台'
|
return '平台'
|
||||||
|
}
|
||||||
|
return data.allocator_shop_name || '-'
|
||||||
}
|
}
|
||||||
return data.allocator_shop_name || '-'
|
},
|
||||||
}
|
{
|
||||||
},
|
label: '成本价',
|
||||||
{
|
formatter: (_, data) => {
|
||||||
label: '成本价',
|
return `¥${(data.cost_price / 100).toFixed(2)}`
|
||||||
formatter: (_, data) => {
|
}
|
||||||
return `¥${(data.cost_price / 100).toFixed(2)}`
|
},
|
||||||
}
|
{
|
||||||
},
|
label: '状态',
|
||||||
{
|
formatter: (_, data) => {
|
||||||
label: '状态',
|
return data.status === 1 ? '启用' : '禁用'
|
||||||
formatter: (_, data) => {
|
}
|
||||||
return data.status === 1 ? '启用' : '禁用'
|
},
|
||||||
}
|
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
||||||
},
|
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
||||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
]
|
||||||
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 返回上一页
|
|
||||||
const handleBack = () => {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取详情数据
|
|
||||||
const fetchDetail = async () => {
|
|
||||||
const id = Number(route.params.id)
|
|
||||||
if (!id) {
|
|
||||||
ElMessage.error('缺少ID参数')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res = await ShopPackageAllocationService.getShopPackageAllocationDetail(id)
|
|
||||||
if (res.code === 0) {
|
|
||||||
detailData.value = res.data
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
]
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('获取详情失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
// 返回上一页
|
||||||
fetchDetail()
|
const handleBack = () => {
|
||||||
})
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取详情数据
|
||||||
|
const fetchDetail = async () => {
|
||||||
|
const id = Number(route.params.id)
|
||||||
|
if (!id) {
|
||||||
|
ElMessage.error('缺少ID参数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await ShopPackageAllocationService.getShopPackageAllocationDetail(id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
detailData.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('获取详情失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchDetail()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.package-assign-detail {
|
.package-assign-detail {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
align-items: center;
|
gap: 16px;
|
||||||
justify-content: center;
|
padding-bottom: 16px;
|
||||||
padding: 60px 20px;
|
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
|
|
||||||
.el-icon {
|
.detail-title {
|
||||||
font-size: 32px;
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
>
|
>
|
||||||
<template #left>
|
<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>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
@@ -113,7 +115,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ShopPackageAllocationService, PackageManageService, ShopService, ShopSeriesAllocationService } from '@/api/modules'
|
import {
|
||||||
|
ShopPackageAllocationService,
|
||||||
|
PackageManageService,
|
||||||
|
ShopService,
|
||||||
|
ShopSeriesAllocationService
|
||||||
|
} from '@/api/modules'
|
||||||
import { ElMessage, ElMessageBox, ElSwitch } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElSwitch } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import type { ShopPackageAllocationResponse, PackageResponse, ShopResponse } from '@/types/api'
|
import type { ShopPackageAllocationResponse, PackageResponse, ShopResponse } from '@/types/api'
|
||||||
@@ -386,14 +393,14 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 200,
|
width: 220,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: ShopPackageAllocationResponse) => {
|
formatter: (row: ShopPackageAllocationResponse) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type:"view",
|
text: '详情',
|
||||||
onClick: () => handleViewDetail(row)
|
onClick: () => handleViewDetail(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -401,7 +408,7 @@
|
|||||||
if (hasAuth('package_assign:edit')) {
|
if (hasAuth('package_assign:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type:"edit",
|
text: '编辑',
|
||||||
onClick: () => showDialog('edit', row)
|
onClick: () => showDialog('edit', row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -410,7 +417,7 @@
|
|||||||
if (hasAuth('package_assign:delete')) {
|
if (hasAuth('package_assign:delete')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type:"delete",
|
text: '删除',
|
||||||
onClick: () => deleteAllocation(row)
|
onClick: () => deleteAllocation(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,224 +25,231 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
import { ElCard, ElButton, ElIcon, ElMessage } from 'element-plus'
|
||||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
import DetailPage from '@/components/common/DetailPage.vue'
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||||
import { PackageManageService } from '@/api/modules'
|
import { PackageManageService } from '@/api/modules'
|
||||||
import type { PackageResponse } from '@/types/api'
|
import type { PackageResponse } from '@/types/api'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
defineOptions({ name: 'PackageDetail' })
|
defineOptions({ name: 'PackageDetail' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detailData = ref<PackageResponse | null>(null)
|
const detailData = ref<PackageResponse | null>(null)
|
||||||
|
|
||||||
// 详情页配置
|
// 详情页配置
|
||||||
const detailSections: DetailSection[] = [
|
const detailSections: DetailSection[] = [
|
||||||
{
|
{
|
||||||
title: '基本信息',
|
title: '基本信息',
|
||||||
fields: [
|
fields: [
|
||||||
{ label: 'ID', prop: 'id' },
|
{ label: 'ID', prop: 'id' },
|
||||||
{ label: '套餐编码', prop: 'package_code' },
|
{ label: '套餐编码', prop: 'package_code' },
|
||||||
{ label: '套餐名称', prop: 'package_name' },
|
{ label: '套餐名称', prop: 'package_name' },
|
||||||
{ label: '套餐系列', prop: 'series_name' },
|
{ label: '套餐系列', prop: 'series_name' },
|
||||||
{
|
{
|
||||||
label: '套餐类型',
|
label: '套餐类型',
|
||||||
formatter: (_, data) => {
|
formatter: (_, data) => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
formal: '正式套餐',
|
formal: '正式套餐',
|
||||||
addon: '附加套餐'
|
addon: '附加套餐'
|
||||||
|
}
|
||||||
|
return typeMap[data.package_type] || data.package_type
|
||||||
}
|
}
|
||||||
return typeMap[data.package_type] || data.package_type
|
},
|
||||||
}
|
{
|
||||||
},
|
label: '套餐时长',
|
||||||
{
|
formatter: (_, data) => {
|
||||||
label: '套餐时长',
|
return `${data.duration_months} 个月`
|
||||||
formatter: (_, data) => {
|
|
||||||
return `${data.duration_months} 个月`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '状态',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.status === 1 ? '启用' : '禁用'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '上架状态',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.shelf_status === 1 ? '上架' : '下架'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
|
||||||
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '流量配置',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: '真流量额度',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (data.real_data_mb >= 1024) {
|
|
||||||
return `${(data.real_data_mb / 1024).toFixed(2)} GB`
|
|
||||||
}
|
}
|
||||||
return `${data.real_data_mb} MB`
|
},
|
||||||
}
|
{
|
||||||
},
|
label: '状态',
|
||||||
{
|
formatter: (_, data) => {
|
||||||
label: '启用虚流量',
|
return data.status === 1 ? '启用' : '禁用'
|
||||||
formatter: (_, data) => {
|
}
|
||||||
return data.enable_virtual_data ? '是' : '否'
|
},
|
||||||
}
|
{
|
||||||
},
|
label: '上架状态',
|
||||||
{
|
formatter: (_, data) => {
|
||||||
label: '虚流量额度',
|
return data.shelf_status === 1 ? '上架' : '下架'
|
||||||
formatter: (_, data) => {
|
}
|
||||||
if (!data.enable_virtual_data) return '-'
|
},
|
||||||
if (data.virtual_data_mb >= 1024) {
|
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
||||||
return `${(data.virtual_data_mb / 1024).toFixed(2)} GB`
|
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '流量配置',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '真流量额度',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (data.real_data_mb >= 1024) {
|
||||||
|
return `${(data.real_data_mb / 1024).toFixed(2)} GB`
|
||||||
|
}
|
||||||
|
return `${data.real_data_mb} MB`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '启用虚流量',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return data.enable_virtual_data ? '是' : '否'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '虚流量额度',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (!data.enable_virtual_data) return '-'
|
||||||
|
if (data.virtual_data_mb >= 1024) {
|
||||||
|
return `${(data.virtual_data_mb / 1024).toFixed(2)} GB`
|
||||||
|
}
|
||||||
|
return `${data.virtual_data_mb} MB`
|
||||||
}
|
}
|
||||||
return `${data.virtual_data_mb} MB`
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
{
|
title: '价格信息',
|
||||||
title: '价格信息',
|
fields: [
|
||||||
fields: [
|
{
|
||||||
{
|
label: '成本价',
|
||||||
label: '成本价',
|
formatter: (_, data) => {
|
||||||
formatter: (_, data) => {
|
return `¥${(data.cost_price / 100).toFixed(2)}`
|
||||||
return `¥${(data.cost_price / 100).toFixed(2)}`
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '建议售价',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return `¥${(data.suggested_retail_price / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '利润空间',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (data.profit_margin === null || data.profit_margin === undefined) return '-'
|
||||||
|
return `¥${(data.profit_margin / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
label: '建议售价',
|
{
|
||||||
formatter: (_, data) => {
|
title: '佣金配置',
|
||||||
return `¥${(data.suggested_retail_price / 100).toFixed(2)}`
|
fields: [
|
||||||
|
{
|
||||||
|
label: '当前返佣比例',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
return data.current_commission_rate || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '一次性佣金金额',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (
|
||||||
|
data.one_time_commission_amount === null ||
|
||||||
|
data.one_time_commission_amount === undefined
|
||||||
|
)
|
||||||
|
return '-'
|
||||||
|
return `¥${(data.one_time_commission_amount / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '当前返佣档位',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (!data.tier_info || !data.tier_info.current_rate) return '-'
|
||||||
|
return data.tier_info.current_rate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '下一档位比例',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (!data.tier_info || !data.tier_info.next_rate) return '-'
|
||||||
|
return data.tier_info.next_rate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '下一档位阈值',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (
|
||||||
|
!data.tier_info ||
|
||||||
|
data.tier_info.next_threshold === null ||
|
||||||
|
data.tier_info.next_threshold === undefined
|
||||||
|
)
|
||||||
|
return '-'
|
||||||
|
return data.tier_info.next_threshold
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
{
|
|
||||||
label: '利润空间',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (data.profit_margin === null || data.profit_margin === undefined) return '-'
|
|
||||||
return `¥${(data.profit_margin / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '佣金配置',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: '当前返佣比例',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.current_commission_rate || '-'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '一次性佣金金额',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (data.one_time_commission_amount === null || data.one_time_commission_amount === undefined) return '-'
|
|
||||||
return `¥${(data.one_time_commission_amount / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '当前返佣档位',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.tier_info || !data.tier_info.current_rate) return '-'
|
|
||||||
return data.tier_info.current_rate
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '下一档位比例',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.tier_info || !data.tier_info.next_rate) return '-'
|
|
||||||
return data.tier_info.next_rate
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '下一档位阈值',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
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 = () => {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取详情数据
|
|
||||||
const fetchDetail = async () => {
|
|
||||||
const id = Number(route.params.id)
|
|
||||||
if (!id) {
|
|
||||||
ElMessage.error('缺少ID参数')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res = await PackageManageService.getPackageDetail(id)
|
|
||||||
if (res.code === 0) {
|
|
||||||
detailData.value = res.data
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
]
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('获取详情失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
// 返回上一页
|
||||||
fetchDetail()
|
const handleBack = () => {
|
||||||
})
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取详情数据
|
||||||
|
const fetchDetail = async () => {
|
||||||
|
const id = Number(route.params.id)
|
||||||
|
if (!id) {
|
||||||
|
ElMessage.error('缺少ID参数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await PackageManageService.getPackageDetail(id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
detailData.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('获取详情失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchDetail()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.package-detail {
|
.package-detail {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
align-items: center;
|
gap: 16px;
|
||||||
justify-content: center;
|
padding-bottom: 16px;
|
||||||
padding: 60px 20px;
|
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
|
|
||||||
.el-icon {
|
.detail-title {
|
||||||
font-size: 32px;
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
>
|
>
|
||||||
<template #left>
|
<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>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
@@ -50,13 +52,13 @@
|
|||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="150px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="150px">
|
||||||
<ElFormItem label="套餐编码" prop="package_code">
|
<ElFormItem label="套餐编码" prop="package_code">
|
||||||
<div style="display: flex; gap: 8px;">
|
<div style="display: flex; gap: 8px">
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="form.package_code"
|
v-model="form.package_code"
|
||||||
placeholder="请输入套餐编码或点击生成"
|
placeholder="请输入套餐编码或点击生成"
|
||||||
:disabled="dialogType === 'edit'"
|
:disabled="dialogType === 'edit'"
|
||||||
clearable
|
clearable
|
||||||
style="flex: 1;"
|
style="flex: 1"
|
||||||
/>
|
/>
|
||||||
<ElButton v-if="dialogType === 'add'" @click="handleGeneratePackageCode">
|
<ElButton v-if="dialogType === 'add'" @click="handleGeneratePackageCode">
|
||||||
生成编码
|
生成编码
|
||||||
@@ -478,14 +480,14 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 200,
|
width: 220,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: PackageResponse) => {
|
formatter: (row: PackageResponse) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'view',
|
text: '详情',
|
||||||
onClick: () => handleViewDetail(row)
|
onClick: () => handleViewDetail(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -493,7 +495,7 @@
|
|||||||
if (hasAuth('package:edit')) {
|
if (hasAuth('package:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'edit',
|
text: '编辑',
|
||||||
onClick: () => showDialog('edit', row)
|
onClick: () => showDialog('edit', row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -502,7 +504,7 @@
|
|||||||
if (hasAuth('package:delete')) {
|
if (hasAuth('package:delete')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'delete',
|
text: '删除',
|
||||||
onClick: () => deletePackage(row)
|
onClick: () => deletePackage(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -661,7 +663,9 @@
|
|||||||
form.virtual_data_mb = row.virtual_data_mb || 0
|
form.virtual_data_mb = row.virtual_data_mb || 0
|
||||||
form.duration_months = row.duration_months
|
form.duration_months = row.duration_months
|
||||||
form.cost_price = row.cost_price / 100 // 分转换为元显示
|
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 || ''
|
form.description = row.description || ''
|
||||||
} else {
|
} else {
|
||||||
form.id = 0
|
form.id = 0
|
||||||
|
|||||||
@@ -25,239 +25,247 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, h } from 'vue'
|
import { ref, onMounted, h } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
import DetailPage from '@/components/common/DetailPage.vue'
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||||
import { PackageSeriesService } from '@/api/modules'
|
import { PackageSeriesService } from '@/api/modules'
|
||||||
import type { PackageSeriesResponse } from '@/types/api'
|
import type { PackageSeriesResponse } from '@/types/api'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
defineOptions({ name: 'PackageSeriesDetail' })
|
defineOptions({ name: 'PackageSeriesDetail' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detailData = ref<PackageSeriesResponse | null>(null)
|
const detailData = ref<PackageSeriesResponse | null>(null)
|
||||||
|
|
||||||
// 详情页配置
|
// 详情页配置
|
||||||
const detailSections: DetailSection[] = [
|
const detailSections: DetailSection[] = [
|
||||||
{
|
{
|
||||||
title: '基本信息',
|
title: '基本信息',
|
||||||
fields: [
|
fields: [
|
||||||
{ label: 'ID', prop: 'id' },
|
{ label: 'ID', prop: 'id' },
|
||||||
{ label: '系列编码', prop: 'series_code' },
|
{ label: '系列编码', prop: 'series_code' },
|
||||||
{ label: '系列名称', prop: 'series_name' },
|
{ label: '系列名称', prop: 'series_name' },
|
||||||
{
|
{
|
||||||
label: '状态',
|
label: '状态',
|
||||||
formatter: (_, data) => {
|
formatter: (_, data) => {
|
||||||
return data.status === 1 ? '启用' : '禁用'
|
return data.status === 1 ? '启用' : '禁用'
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '描述',
|
|
||||||
prop: 'description',
|
|
||||||
fullWidth: true
|
|
||||||
},
|
|
||||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
|
||||||
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '一次性佣金配置',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: '启用状态',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.one_time_commission_config?.enable ? '已启用' : '未启用'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '佣金类型',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
const config = data.one_time_commission_config
|
|
||||||
if (!config?.commission_type) return '-'
|
|
||||||
return config.commission_type === 'fixed' ? '固定佣金' : '梯度佣金'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '固定佣金金额',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
const config = data.one_time_commission_config
|
|
||||||
if (config?.commission_type !== 'fixed' || !config.commission_amount) return '-'
|
|
||||||
return `¥${(config.commission_amount / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '触发阈值',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
const config = data.one_time_commission_config
|
|
||||||
if (!config?.threshold) return '-'
|
|
||||||
return `¥${(config.threshold / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '触发类型',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
const config = data.one_time_commission_config
|
|
||||||
if (!config?.trigger_type) return '-'
|
|
||||||
return config.trigger_type === 'first_recharge' ? '首次充值' : '累计充值'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '梯度配置',
|
|
||||||
fullWidth: true,
|
|
||||||
render: (data) => {
|
|
||||||
const config = data.one_time_commission_config
|
|
||||||
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;' },
|
{
|
||||||
config.tiers.map((tier: any, index: number) => {
|
label: '描述',
|
||||||
const dimensionText = tier.dimension === 'sales_count' ? '销量' : '销售额'
|
prop: 'description',
|
||||||
const thresholdText = tier.dimension === 'sales_amount'
|
fullWidth: true
|
||||||
? `¥${(tier.threshold / 100).toFixed(2)}`
|
},
|
||||||
: tier.threshold
|
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
||||||
const amountText = `¥${(tier.amount / 100).toFixed(2)}`
|
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
||||||
const scopeText = tier.stat_scope === 'self' ? '仅自己' : '自己+下级'
|
]
|
||||||
|
},
|
||||||
return h(ElTag, { type: 'info', size: 'default' },
|
{
|
||||||
() => `档位${index + 1}: ${dimensionText} ≥ ${thresholdText}, 佣金 ${amountText}, ${scopeText}`
|
title: '一次性佣金配置',
|
||||||
)
|
fields: [
|
||||||
})
|
{
|
||||||
)
|
label: '启用状态',
|
||||||
}
|
formatter: (_, data) => {
|
||||||
}
|
return data.one_time_commission_config?.enable ? '已启用' : '未启用'
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '强充配置',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: '启用状态',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.one_time_commission_config?.enable_force_recharge ? '已启用' : '未启用'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '强充金额',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
const config = data.one_time_commission_config
|
|
||||||
if (!config?.force_amount) return '-'
|
|
||||||
return `¥${(config.force_amount / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '强充计算类型',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
const config = data.one_time_commission_config
|
|
||||||
if (!config?.force_calc_type) return '-'
|
|
||||||
return config.force_calc_type === 'fixed' ? '固定' : '动态'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '时效配置',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: '时效类型',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
const config = data.one_time_commission_config
|
|
||||||
if (!config?.validity_type) return '-'
|
|
||||||
const typeMap = {
|
|
||||||
permanent: '永久',
|
|
||||||
fixed_date: '固定日期',
|
|
||||||
relative: '相对时长'
|
|
||||||
}
|
}
|
||||||
return typeMap[config.validity_type] || '-'
|
},
|
||||||
}
|
{
|
||||||
},
|
label: '佣金类型',
|
||||||
{
|
formatter: (_, data) => {
|
||||||
label: '时效值',
|
const config = data.one_time_commission_config
|
||||||
formatter: (_, data) => {
|
if (!config?.commission_type) return '-'
|
||||||
const config = data.one_time_commission_config
|
return config.commission_type === 'fixed' ? '固定佣金' : '梯度佣金'
|
||||||
if (!config?.validity_value) return '-'
|
}
|
||||||
if (config.validity_type === 'relative') {
|
},
|
||||||
return `${config.validity_value}个月`
|
{
|
||||||
} else if (config.validity_type === 'fixed_date') {
|
label: '固定佣金金额',
|
||||||
return config.validity_value
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (config?.commission_type !== 'fixed' || !config.commission_amount) return '-'
|
||||||
|
return `¥${(config.commission_amount / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '触发阈值',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.threshold) return '-'
|
||||||
|
return `¥${(config.threshold / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '触发类型',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.trigger_type) return '-'
|
||||||
|
return config.trigger_type === 'first_recharge' ? '首次充值' : '累计充值'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '梯度配置',
|
||||||
|
fullWidth: true,
|
||||||
|
render: (data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
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;' },
|
||||||
|
config.tiers.map((tier: any, index: number) => {
|
||||||
|
const dimensionText = tier.dimension === 'sales_count' ? '销量' : '销售额'
|
||||||
|
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 '-'
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
]
|
title: '强充配置',
|
||||||
|
fields: [
|
||||||
// 返回上一页
|
{
|
||||||
const handleBack = () => {
|
label: '启用状态',
|
||||||
router.back()
|
formatter: (_, data) => {
|
||||||
}
|
return data.one_time_commission_config?.enable_force_recharge ? '已启用' : '未启用'
|
||||||
|
}
|
||||||
// 获取详情数据
|
},
|
||||||
const fetchDetail = async () => {
|
{
|
||||||
const id = Number(route.params.id)
|
label: '强充金额',
|
||||||
if (!id) {
|
formatter: (_, data) => {
|
||||||
ElMessage.error('缺少ID参数')
|
const config = data.one_time_commission_config
|
||||||
return
|
if (!config?.force_amount) return '-'
|
||||||
}
|
return `¥${(config.force_amount / 100).toFixed(2)}`
|
||||||
|
}
|
||||||
loading.value = true
|
},
|
||||||
try {
|
{
|
||||||
const res = await PackageSeriesService.getPackageSeriesDetail(id)
|
label: '强充计算类型',
|
||||||
if (res.code === 0) {
|
formatter: (_, data) => {
|
||||||
detailData.value = res.data
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.force_calc_type) return '-'
|
||||||
|
return config.force_calc_type === 'fixed' ? '固定' : '动态'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '时效配置',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: '时效类型',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.validity_type) return '-'
|
||||||
|
const typeMap = {
|
||||||
|
permanent: '永久',
|
||||||
|
fixed_date: '固定日期',
|
||||||
|
relative: '相对时长'
|
||||||
|
}
|
||||||
|
return typeMap[config.validity_type] || '-'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '时效值',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
const config = data.one_time_commission_config
|
||||||
|
if (!config?.validity_value) return '-'
|
||||||
|
if (config.validity_type === 'relative') {
|
||||||
|
return `${config.validity_value}个月`
|
||||||
|
} else if (config.validity_type === 'fixed_date') {
|
||||||
|
return config.validity_value
|
||||||
|
}
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
} catch (error) {
|
]
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('获取详情失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
// 返回上一页
|
||||||
fetchDetail()
|
const handleBack = () => {
|
||||||
})
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取详情数据
|
||||||
|
const fetchDetail = async () => {
|
||||||
|
const id = Number(route.params.id)
|
||||||
|
if (!id) {
|
||||||
|
ElMessage.error('缺少ID参数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await PackageSeriesService.getPackageSeriesDetail(id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
detailData.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('获取详情失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchDetail()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.package-series-detail {
|
.package-series-detail {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
align-items: center;
|
gap: 16px;
|
||||||
justify-content: center;
|
padding-bottom: 16px;
|
||||||
padding: 60px 20px;
|
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
|
|
||||||
.el-icon {
|
.detail-title {
|
||||||
font-size: 32px;
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -243,7 +243,13 @@
|
|||||||
:icon="Delete"
|
:icon="Delete"
|
||||||
circle
|
circle
|
||||||
@click="removeTier(index)"
|
@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>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
@@ -673,7 +679,7 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 200,
|
width: 220,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: PackageSeriesResponse) => {
|
formatter: (row: PackageSeriesResponse) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
@@ -681,7 +687,7 @@
|
|||||||
// 详情按钮
|
// 详情按钮
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'view',
|
text: '详情',
|
||||||
onClick: () => handleViewDetail(row)
|
onClick: () => handleViewDetail(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -689,7 +695,7 @@
|
|||||||
if (hasAuth('package_series:edit')) {
|
if (hasAuth('package_series:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'edit',
|
text: '编辑',
|
||||||
onClick: () => showDialog('edit', row)
|
onClick: () => showDialog('edit', row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -698,7 +704,7 @@
|
|||||||
if (hasAuth('package_series:delete')) {
|
if (hasAuth('package_series:delete')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'delete',
|
text: '删除',
|
||||||
onClick: () => deleteSeries(row)
|
onClick: () => deleteSeries(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,183 +25,181 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, h } from 'vue'
|
import { ref, onMounted, h } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
import DetailPage from '@/components/common/DetailPage.vue'
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||||
import { ShopSeriesAllocationService } from '@/api/modules'
|
import { ShopSeriesAllocationService } from '@/api/modules'
|
||||||
import type { ShopSeriesAllocationResponse } from '@/types/api'
|
import type { ShopSeriesAllocationResponse } from '@/types/api'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
defineOptions({ name: 'SeriesAssignDetail' })
|
defineOptions({ name: 'SeriesAssignDetail' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detailData = ref<ShopSeriesAllocationResponse | null>(null)
|
const detailData = ref<ShopSeriesAllocationResponse | null>(null)
|
||||||
|
|
||||||
// 详情页配置
|
// 详情页配置
|
||||||
const detailSections: DetailSection[] = [
|
const detailSections: DetailSection[] = [
|
||||||
{
|
{
|
||||||
title: '基本信息',
|
title: '基本信息',
|
||||||
fields: [
|
fields: [
|
||||||
{ label: 'ID', prop: 'id' },
|
{ label: 'ID', prop: 'id' },
|
||||||
{ label: '系列编码', prop: 'series_code' },
|
{ label: '系列编码', prop: 'series_code' },
|
||||||
{ label: '系列名称', prop: 'series_name' },
|
{ label: '系列名称', prop: 'series_name' },
|
||||||
{ label: '店铺名称', prop: 'shop_name' },
|
{ label: '店铺名称', prop: 'shop_name' },
|
||||||
{
|
{
|
||||||
label: '分配者店铺',
|
label: '分配者店铺',
|
||||||
formatter: (_, data) => {
|
formatter: (_, data) => {
|
||||||
if (data.allocator_shop_id === 0) {
|
if (data.allocator_shop_id === 0) {
|
||||||
return '平台'
|
return '平台'
|
||||||
|
}
|
||||||
|
return data.allocator_shop_name || '-'
|
||||||
}
|
}
|
||||||
return data.allocator_shop_name || '-'
|
},
|
||||||
}
|
{
|
||||||
},
|
label: '状态',
|
||||||
{
|
formatter: (_, data) => {
|
||||||
label: '状态',
|
return data.status === 1 ? '启用' : '禁用'
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.status === 1 ? '启用' : '禁用'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
|
||||||
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '一次性佣金配置',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: '启用状态',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
return data.enable_one_time_commission ? '已启用' : '未启用'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '佣金金额上限',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.one_time_commission_amount) return '-'
|
|
||||||
return `¥${(data.one_time_commission_amount / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '触发阈值',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.one_time_commission_threshold) return '-'
|
|
||||||
return `¥${(data.one_time_commission_threshold / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '触发类型',
|
|
||||||
formatter: (_, data) => {
|
|
||||||
if (!data.one_time_commission_trigger) return '-'
|
|
||||||
const typeMap = {
|
|
||||||
first_recharge: '首次充值',
|
|
||||||
accumulated_recharge: '累计充值'
|
|
||||||
}
|
}
|
||||||
return typeMap[data.one_time_commission_trigger] || '-'
|
},
|
||||||
}
|
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
||||||
}
|
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '强制充值配置',
|
title: '一次性佣金配置',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
label: '启用状态',
|
label: '启用状态',
|
||||||
formatter: (_, data) => {
|
formatter: (_, data) => {
|
||||||
return data.enable_force_recharge ? '已启用' : '未启用'
|
return data.enable_one_time_commission ? '已启用' : '未启用'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '强充金额',
|
label: '佣金金额上限',
|
||||||
formatter: (_, data) => {
|
formatter: (_, data) => {
|
||||||
if (!data.force_recharge_amount) return '-'
|
if (!data.one_time_commission_amount) return '-'
|
||||||
return `¥${(data.force_recharge_amount / 100).toFixed(2)}`
|
return `¥${(data.one_time_commission_amount / 100).toFixed(2)}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '强充触发类型',
|
label: '触发阈值',
|
||||||
formatter: (_, data) => {
|
formatter: (_, data) => {
|
||||||
if (!data.force_recharge_trigger_type) return '-'
|
if (!data.one_time_commission_threshold) return '-'
|
||||||
const typeMap = {
|
return `¥${(data.one_time_commission_threshold / 100).toFixed(2)}`
|
||||||
1: '单次充值',
|
}
|
||||||
2: '累计充值'
|
},
|
||||||
|
{
|
||||||
|
label: '触发类型',
|
||||||
|
formatter: (_, data) => {
|
||||||
|
if (!data.one_time_commission_trigger) return '-'
|
||||||
|
const typeMap = {
|
||||||
|
first_recharge: '首次充值',
|
||||||
|
accumulated_recharge: '累计充值'
|
||||||
|
}
|
||||||
|
return typeMap[data.one_time_commission_trigger] || '-'
|
||||||
}
|
}
|
||||||
return typeMap[data.force_recharge_trigger_type] || '-'
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
},
|
||||||
}
|
{
|
||||||
]
|
title: '强制充值配置',
|
||||||
|
fields: [
|
||||||
// 返回上一页
|
{
|
||||||
const handleBack = () => {
|
label: '启用状态',
|
||||||
router.back()
|
formatter: (_, data) => {
|
||||||
}
|
return data.enable_force_recharge ? '已启用' : '未启用'
|
||||||
|
}
|
||||||
// 获取详情数据
|
},
|
||||||
const fetchDetail = async () => {
|
{
|
||||||
const id = Number(route.params.id)
|
label: '强充金额',
|
||||||
if (!id) {
|
formatter: (_, data) => {
|
||||||
ElMessage.error('缺少ID参数')
|
if (!data.force_recharge_amount) return '-'
|
||||||
return
|
return `¥${(data.force_recharge_amount / 100).toFixed(2)}`
|
||||||
}
|
}
|
||||||
|
},
|
||||||
loading.value = true
|
{
|
||||||
try {
|
label: '强充触发类型',
|
||||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocationDetail(id)
|
formatter: (_, data) => {
|
||||||
if (res.code === 0) {
|
if (!data.force_recharge_trigger_type) return '-'
|
||||||
detailData.value = res.data
|
const typeMap = {
|
||||||
|
1: '单次充值',
|
||||||
|
2: '累计充值'
|
||||||
|
}
|
||||||
|
return typeMap[data.force_recharge_trigger_type] || '-'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
} catch (error) {
|
]
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('获取详情失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
// 返回上一页
|
||||||
fetchDetail()
|
const handleBack = () => {
|
||||||
})
|
router.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取详情数据
|
||||||
|
const fetchDetail = async () => {
|
||||||
|
const id = Number(route.params.id)
|
||||||
|
if (!id) {
|
||||||
|
ElMessage.error('缺少ID参数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await ShopSeriesAllocationService.getShopSeriesAllocationDetail(id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
detailData.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('获取详情失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchDetail()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.series-assign-detail {
|
.series-assign-detail {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
align-items: center;
|
gap: 16px;
|
||||||
justify-content: center;
|
padding-bottom: 16px;
|
||||||
padding: 60px 20px;
|
|
||||||
gap: 12px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
|
|
||||||
.el-icon {
|
.detail-title {
|
||||||
font-size: 32px;
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
gap: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
>
|
>
|
||||||
<template #left>
|
<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>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
@@ -565,7 +567,7 @@
|
|||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 180,
|
width: 220,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: ShopSeriesAllocationResponse) => {
|
formatter: (row: ShopSeriesAllocationResponse) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
@@ -573,7 +575,7 @@
|
|||||||
// 详情按钮
|
// 详情按钮
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'view',
|
text: '详情',
|
||||||
onClick: () => handleViewDetail(row)
|
onClick: () => handleViewDetail(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -581,7 +583,7 @@
|
|||||||
if (hasAuth('series_assign:edit')) {
|
if (hasAuth('series_assign:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'edit',
|
text: '编辑',
|
||||||
onClick: () => showDialog('edit', row)
|
onClick: () => showDialog('edit', row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -590,7 +592,7 @@
|
|||||||
if (hasAuth('series_assign:delete')) {
|
if (hasAuth('series_assign:delete')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'delete',
|
text: '删除',
|
||||||
onClick: () => deleteAllocation(row)
|
onClick: () => deleteAllocation(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -800,7 +802,8 @@
|
|||||||
page_size: pagination.page_size,
|
page_size: pagination.page_size,
|
||||||
shop_id: searchForm.shop_id || undefined,
|
shop_id: searchForm.shop_id || undefined,
|
||||||
series_id: searchForm.series_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
|
status: searchForm.status || undefined
|
||||||
}
|
}
|
||||||
const res = await ShopSeriesAllocationService.getShopSeriesAllocations(params)
|
const res = await ShopSeriesAllocationService.getShopSeriesAllocations(params)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '新增店铺' : '编辑店铺'"
|
:title="dialogType === 'add' ? '新增店铺' : '编辑店铺'"
|
||||||
width="800px"
|
width="50%"
|
||||||
>
|
>
|
||||||
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="100px">
|
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="100px">
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
@@ -162,6 +162,32 @@
|
|||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</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>
|
</ElRow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -195,42 +221,38 @@
|
|||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="defaultRolesDialogVisible"
|
v-model="defaultRolesDialogVisible"
|
||||||
:title="`设置默认角色 - ${currentShop?.shop_name || ''}`"
|
:title="`设置默认角色 - ${currentShop?.shop_name || ''}`"
|
||||||
width="800px"
|
width="50%"
|
||||||
>
|
>
|
||||||
<div v-loading="defaultRolesLoading">
|
<div v-loading="defaultRolesLoading">
|
||||||
<!-- 当前默认角色列表 -->
|
<!-- 当前默认角色列表 -->
|
||||||
<div class="default-roles-section">
|
<div class="default-roles-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span style="color:white;">当前默认角色</span>
|
<span style="color: white">当前默认角色</span>
|
||||||
<ElButton type="primary" @click="showAddRoleDialog">
|
<ElButton type="primary" @click="showAddRoleDialog"> 设置默认角色 </ElButton>
|
||||||
添加角色
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
</div>
|
||||||
<ElTable :data="currentDefaultRoles" border stripe style="margin-top: 12px">
|
<ElTable :data="currentDefaultRoles" border stripe style="margin-top: 12px">
|
||||||
<ElTableColumn prop="role_name" label="角色名称" width="150" />
|
<ElTableColumn prop="role_name" label="角色名称" width="150" />
|
||||||
<ElTableColumn prop="role_desc" label="角色描述" min-width="200" />
|
<ElTableColumn prop="role_desc" label="角色描述" min-width="200" />
|
||||||
<ElTableColumn prop="status" label="状态" width="80">
|
<ElTableColumn prop="status" label="状态" width="80">
|
||||||
<template #default="{ row }">
|
<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) }}
|
{{ getStatusText(row.status) }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="操作" width="100" fixed="right">
|
<ElTableColumn label="操作" width="100" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<ElButton
|
<ElButton type="danger" text size="small" @click="handleDeleteDefaultRole(row)">
|
||||||
type="danger"
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
@click="handleDeleteDefaultRole(row)"
|
|
||||||
>
|
|
||||||
删除
|
删除
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div style="padding: 20px 0; color: #909399;">
|
<div style="padding: 20px 0; color: #909399">
|
||||||
暂无默认角色,请点击"添加角色"按钮进行配置
|
暂无默认角色,请点击"设置默认角色"按钮进行配置
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElTable>
|
</ElTable>
|
||||||
@@ -243,19 +265,13 @@
|
|||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 添加角色对话框 -->
|
<!-- 设置默认角色对话框 -->
|
||||||
<ElDialog
|
<ElDialog v-model="addRoleDialogVisible" title="设置默认角色" width="50%" append-to-body>
|
||||||
v-model="addRoleDialogVisible"
|
|
||||||
title="添加默认角色"
|
|
||||||
width="600px"
|
|
||||||
append-to-body
|
|
||||||
>
|
|
||||||
<div v-loading="rolesLoading">
|
<div v-loading="rolesLoading">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="selectedRoleIds"
|
v-model="selectedRoleId"
|
||||||
multiple
|
|
||||||
filterable
|
filterable
|
||||||
placeholder="请选择要添加的角色"
|
placeholder="请选择默认角色"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<ElOption
|
<ElOption
|
||||||
@@ -263,23 +279,15 @@
|
|||||||
:key="role.role_id"
|
:key="role.role_id"
|
||||||
:label="role.role_name"
|
:label="role.role_name"
|
||||||
:value="role.role_id"
|
: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>
|
||||||
<span>{{ role.role_name }}</span>
|
<ElTag type="success" size="small">客户角色</ElTag>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</ElOption>
|
</ElOption>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
<div style="margin-top: 8px; color: #909399; font-size: 12px;">
|
<div style="margin-top: 8px; color: #909399; font-size: 12px">
|
||||||
支持多选,已添加的角色将显示为禁用状态
|
只能选择一个客户角色,设置后将覆盖之前的默认角色
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -334,6 +342,8 @@
|
|||||||
const parentShopLoading = ref(false)
|
const parentShopLoading = ref(false)
|
||||||
const parentShopList = ref<ShopResponse[]>([])
|
const parentShopList = ref<ShopResponse[]>([])
|
||||||
const searchParentShopList = ref<ShopResponse[]>([])
|
const searchParentShopList = ref<ShopResponse[]>([])
|
||||||
|
const defaultRoleLoading = ref(false)
|
||||||
|
const defaultRoleList = ref<any[]>([])
|
||||||
|
|
||||||
// 默认角色管理相关状态
|
// 默认角色管理相关状态
|
||||||
const defaultRolesDialogVisible = ref(false)
|
const defaultRolesDialogVisible = ref(false)
|
||||||
@@ -344,7 +354,7 @@
|
|||||||
const currentShop = ref<ShopResponse | null>(null)
|
const currentShop = ref<ShopResponse | null>(null)
|
||||||
const currentDefaultRoles = ref<ShopRoleResponse[]>([])
|
const currentDefaultRoles = ref<ShopRoleResponse[]>([])
|
||||||
const availableRoles = ref<ShopRoleResponse[]>([])
|
const availableRoles = ref<ShopRoleResponse[]>([])
|
||||||
const selectedRoleIds = ref<number[]>([])
|
const selectedRoleId = ref<number | undefined>(undefined)
|
||||||
|
|
||||||
// 右键菜单
|
// 右键菜单
|
||||||
const shopOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const shopOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
@@ -474,6 +484,9 @@
|
|||||||
const showDialog = (type: string, row?: ShopResponse) => {
|
const showDialog = (type: string, row?: ShopResponse) => {
|
||||||
dialogType.value = type
|
dialogType.value = type
|
||||||
|
|
||||||
|
// 先清除验证状态
|
||||||
|
formRef.value?.clearValidate()
|
||||||
|
|
||||||
if (type === 'edit' && row) {
|
if (type === 'edit' && row) {
|
||||||
formData.id = row.id
|
formData.id = row.id
|
||||||
formData.shop_name = row.shop_name
|
formData.shop_name = row.shop_name
|
||||||
@@ -489,6 +502,7 @@
|
|||||||
formData.init_username = ''
|
formData.init_username = ''
|
||||||
formData.init_password = ''
|
formData.init_password = ''
|
||||||
formData.init_phone = ''
|
formData.init_phone = ''
|
||||||
|
formData.default_role_id = undefined
|
||||||
} else {
|
} else {
|
||||||
formData.id = 0
|
formData.id = 0
|
||||||
formData.shop_name = ''
|
formData.shop_name = ''
|
||||||
@@ -504,9 +518,10 @@
|
|||||||
formData.init_username = ''
|
formData.init_username = ''
|
||||||
formData.init_password = ''
|
formData.init_password = ''
|
||||||
formData.init_phone = ''
|
formData.init_phone = ''
|
||||||
|
formData.default_role_id = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置表单验证状态
|
// 再次确保清除验证状态
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
formRef.value?.clearValidate()
|
formRef.value?.clearValidate()
|
||||||
})
|
})
|
||||||
@@ -540,12 +555,13 @@
|
|||||||
{
|
{
|
||||||
prop: 'shop_name',
|
prop: 'shop_name',
|
||||||
label: '店铺名称',
|
label: '店铺名称',
|
||||||
minWidth: 120
|
minWidth: 160,
|
||||||
|
showOverflowTooltip: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'shop_code',
|
prop: 'shop_code',
|
||||||
label: '店铺编号',
|
label: '店铺编号',
|
||||||
width: 150,
|
width: 140,
|
||||||
showOverflowTooltip: true
|
showOverflowTooltip: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -560,6 +576,7 @@
|
|||||||
prop: 'region',
|
prop: 'region',
|
||||||
label: '所在地区',
|
label: '所在地区',
|
||||||
minWidth: 170,
|
minWidth: 170,
|
||||||
|
showOverflowTooltip: true,
|
||||||
formatter: (row: ShopResponse) => {
|
formatter: (row: ShopResponse) => {
|
||||||
const parts: string[] = []
|
const parts: string[] = []
|
||||||
if (row.province) parts.push(row.province)
|
if (row.province) parts.push(row.province)
|
||||||
@@ -652,13 +669,39 @@
|
|||||||
status: CommonStatus.ENABLED,
|
status: CommonStatus.ENABLED,
|
||||||
init_username: '',
|
init_username: '',
|
||||||
init_password: '',
|
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(() => {
|
onMounted(() => {
|
||||||
getShopList()
|
getShopList()
|
||||||
loadParentShopList()
|
loadParentShopList()
|
||||||
loadSearchParentShopList()
|
loadSearchParentShopList()
|
||||||
|
searchDefaultRoles('') // 加载初始默认角色列表
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载上级店铺列表(用于新增对话框,默认加载20条)
|
// 加载上级店铺列表(用于新增对话框,默认加载20条)
|
||||||
@@ -810,6 +853,9 @@
|
|||||||
{ required: true, message: '请输入初始账号手机号', trigger: 'blur' },
|
{ required: true, message: '请输入初始账号手机号', trigger: 'blur' },
|
||||||
{ len: 11, message: '手机号必须为 11 位', trigger: 'blur' },
|
{ len: 11, message: '手机号必须为 11 位', trigger: 'blur' },
|
||||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', 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,
|
shop_code: formData.shop_code,
|
||||||
init_username: formData.init_username,
|
init_username: formData.init_username,
|
||||||
init_password: formData.init_password,
|
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 () => {
|
const showAddRoleDialog = async () => {
|
||||||
addRoleDialogVisible.value = true
|
addRoleDialogVisible.value = true
|
||||||
selectedRoleIds.value = []
|
// 如果已有默认角色,预选第一个
|
||||||
|
selectedRoleId.value = currentDefaultRoles.value.length > 0
|
||||||
|
? currentDefaultRoles.value[0].role_id
|
||||||
|
: undefined
|
||||||
await loadAvailableRoles()
|
await loadAvailableRoles()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载可用角色列表
|
// 加载可用角色列表(仅客户角色)
|
||||||
const loadAvailableRoles = async () => {
|
const loadAvailableRoles = async () => {
|
||||||
rolesLoading.value = true
|
rolesLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await RoleService.getRoles({
|
const res = await RoleService.getRoles({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 9999,
|
page_size: 9999,
|
||||||
|
role_type: 2, // 仅客户角色
|
||||||
status: 1 // RoleStatus.ENABLED
|
status: 1 // RoleStatus.ENABLED
|
||||||
})
|
})
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
// 将 PlatformRole 转换为与 ShopRoleResponse 兼容的格式,同时保留 role_type
|
// 将 PlatformRole 转换为与 ShopRoleResponse 兼容的格式
|
||||||
availableRoles.value = (res.data.items || []).map((role) => ({
|
availableRoles.value = (res.data.items || []).map((role) => ({
|
||||||
...role,
|
...role,
|
||||||
role_id: role.ID,
|
role_id: role.ID,
|
||||||
role_name: role.role_name,
|
role_name: role.role_name,
|
||||||
role_desc: role.role_desc,
|
role_desc: role.role_desc,
|
||||||
role_type: role.role_type, // 保留角色类型用于显示
|
role_type: role.role_type,
|
||||||
shop_id: 0 // 这个值在可用角色列表中不使用
|
shop_id: 0
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1020,15 +1071,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断角色是否已分配
|
// 设置默认角色
|
||||||
const isRoleAlreadyAssigned = (roleId: number) => {
|
|
||||||
return currentDefaultRoles.value.some((r) => r.role_id === roleId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加默认角色
|
|
||||||
const handleAddDefaultRoles = async () => {
|
const handleAddDefaultRoles = async () => {
|
||||||
if (selectedRoleIds.value.length === 0) {
|
if (!selectedRoleId.value) {
|
||||||
ElMessage.warning('请至少选择一个角色')
|
ElMessage.warning('请选择默认角色')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1039,17 +1085,18 @@
|
|||||||
|
|
||||||
addRoleLoading.value = true
|
addRoleLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
// 传递数组,但只包含一个角色ID
|
||||||
const res = await ShopService.assignShopRoles(currentShop.value.id, {
|
const res = await ShopService.assignShopRoles(currentShop.value.id, {
|
||||||
role_ids: selectedRoleIds.value
|
role_ids: [selectedRoleId.value]
|
||||||
})
|
})
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage.success('添加默认角色成功')
|
ElMessage.success('设置默认角色成功')
|
||||||
addRoleDialogVisible.value = false
|
addRoleDialogVisible.value = false
|
||||||
// 刷新默认角色列表
|
// 刷新默认角色列表
|
||||||
await loadShopDefaultRoles(currentShop.value.id)
|
await loadShopDefaultRoles(currentShop.value.id)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('添加默认角色失败:', error)
|
console.error('设置默认角色失败:', error)
|
||||||
} finally {
|
} finally {
|
||||||
addRoleLoading.value = false
|
addRoleLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,16 +127,10 @@
|
|||||||
<template #default="{ node, data }">
|
<template #default="{ node, data }">
|
||||||
<span style="display: flex; align-items: center; gap: 8px">
|
<span style="display: flex; align-items: center; gap: 8px">
|
||||||
<span>{{ node.label }}</span>
|
<span>{{ node.label }}</span>
|
||||||
<ElTag
|
<ElTag :type="data.perm_type === 1 ? 'info' : 'success'" size="small">
|
||||||
:type="data.perm_type === 1 ? 'info' : 'success'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
|
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
<ElTag
|
<ElTag :type="data.status === 1 ? 'success' : 'info'" size="small">
|
||||||
:type="data.status === 1 ? 'success' : 'info'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ data.status === 1 ? '启用' : '禁用' }}
|
{{ data.status === 1 ? '启用' : '禁用' }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</span>
|
</span>
|
||||||
@@ -183,16 +177,10 @@
|
|||||||
<span class="tree-node-content">
|
<span class="tree-node-content">
|
||||||
<span class="tree-node-label">
|
<span class="tree-node-label">
|
||||||
<span>{{ node.label }}</span>
|
<span>{{ node.label }}</span>
|
||||||
<ElTag
|
<ElTag :type="data.perm_type === 1 ? 'info' : 'success'" size="small">
|
||||||
:type="data.perm_type === 1 ? 'info' : 'success'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
|
{{ data.perm_type === 1 ? '菜单' : '按钮' }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
<ElTag
|
<ElTag :type="data.status === 1 ? 'success' : 'info'" size="small">
|
||||||
:type="data.status === 1 ? 'success' : 'info'"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{{ data.status === 1 ? '启用' : '禁用' }}
|
{{ data.status === 1 ? '启用' : '禁用' }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</span>
|
</span>
|
||||||
@@ -231,7 +219,9 @@
|
|||||||
ElTree,
|
ElTree,
|
||||||
ElSwitch,
|
ElSwitch,
|
||||||
ElButton,
|
ElButton,
|
||||||
ElInput
|
ElInput,
|
||||||
|
ElSelect,
|
||||||
|
ElOption
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import type { PlatformRole, PermissionTreeNode } from '@/types/api'
|
import type { PlatformRole, PermissionTreeNode } from '@/types/api'
|
||||||
@@ -268,7 +258,9 @@
|
|||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
role_name: ''
|
role_name: '',
|
||||||
|
role_type: undefined as number | undefined,
|
||||||
|
status: undefined as number | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
@@ -284,6 +276,32 @@
|
|||||||
clearable: true,
|
clearable: true,
|
||||||
placeholder: '请输入角色名称'
|
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',
|
prop: 'CreatedAt',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
minWidth: 180,
|
width: 180,
|
||||||
formatter: (row: any) => formatDateTime(row.CreatedAt)
|
formatter: (row: any) => formatDateTime(row.CreatedAt)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '操作',
|
||||||
width: 200,
|
width: 240,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
formatter: (row: any) => {
|
formatter: (row: any) => {
|
||||||
const buttons = []
|
const buttons = []
|
||||||
@@ -388,7 +406,7 @@
|
|||||||
if (hasAuth('role:permission')) {
|
if (hasAuth('role:permission')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
icon: '',
|
text: '分配权限',
|
||||||
onClick: () => showPermissionDialog(row)
|
onClick: () => showPermissionDialog(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -398,7 +416,7 @@
|
|||||||
if (hasAuth('role:edit')) {
|
if (hasAuth('role:edit')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'edit',
|
text: '编辑',
|
||||||
onClick: () => showDialog('edit', row)
|
onClick: () => showDialog('edit', row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -408,7 +426,7 @@
|
|||||||
if (hasAuth('role:delete')) {
|
if (hasAuth('role:delete')) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
h(ArtButtonTable, {
|
h(ArtButtonTable, {
|
||||||
type: 'delete',
|
text: '删除',
|
||||||
onClick: () => deleteRole(row)
|
onClick: () => deleteRole(row)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -613,14 +631,14 @@
|
|||||||
// 保存右侧树的展开节点
|
// 保存右侧树的展开节点
|
||||||
const expandedKeys = rightTreeRef.value?.store?.nodesMap
|
const expandedKeys = rightTreeRef.value?.store?.nodesMap
|
||||||
? Object.keys(rightTreeRef.value.store.nodesMap)
|
? Object.keys(rightTreeRef.value.store.nodesMap)
|
||||||
.filter(key => rightTreeRef.value.store.nodesMap[key].expanded)
|
.filter((key) => rightTreeRef.value.store.nodesMap[key].expanded)
|
||||||
.map(key => Number(key))
|
.map((key) => Number(key))
|
||||||
: []
|
: []
|
||||||
|
|
||||||
await RoleService.removePermission(currentRoleId.value, permId)
|
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)
|
const fullTreeData = buildTreeData(originalPermissionTree.value)
|
||||||
@@ -641,7 +659,7 @@
|
|||||||
// 等待DOM更新后恢复展开状态
|
// 等待DOM更新后恢复展开状态
|
||||||
await nextTick()
|
await nextTick()
|
||||||
if (rightTreeRef.value && expandedKeys.length > 0) {
|
if (rightTreeRef.value && expandedKeys.length > 0) {
|
||||||
expandedKeys.forEach(key => {
|
expandedKeys.forEach((key) => {
|
||||||
const node = rightTreeRef.value.store.nodesMap[key]
|
const node = rightTreeRef.value.store.nodesMap[key]
|
||||||
if (node && !node.isLeaf) {
|
if (node && !node.isLeaf) {
|
||||||
node.expanded = true
|
node.expanded = true
|
||||||
@@ -705,7 +723,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 获取角色列表
|
// 获取角色列表
|
||||||
const getTableData = async () => {
|
const getTableData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -713,7 +730,9 @@
|
|||||||
const params = {
|
const params = {
|
||||||
page: pagination.page,
|
page: pagination.page,
|
||||||
pageSize: pagination.pageSize,
|
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)
|
const res = await RoleService.getRoles(params)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user