This commit is contained in:
@@ -14,7 +14,9 @@
|
|||||||
"Bash(npm run build:*)",
|
"Bash(npm run build:*)",
|
||||||
"Bash(tree:*)",
|
"Bash(tree:*)",
|
||||||
"Bash(npm run dev:*)",
|
"Bash(npm run dev:*)",
|
||||||
"Bash(timeout:*)"
|
"Bash(timeout:*)",
|
||||||
|
"Read(//d/**)",
|
||||||
|
"Bash(findstr:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
38
src/components/core/others/TableContextMenuHint.vue
Normal file
38
src/components/core/others/TableContextMenuHint.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!-- 表格右键菜单悬浮提示组件 -->
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-show="visible"
|
||||||
|
class="table-context-menu-hint"
|
||||||
|
:style="{ left: position.x + 'px', top: position.y + 'px' }"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
visible: boolean
|
||||||
|
position: { x: number; y: number }
|
||||||
|
text?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
text: '右键查看更多操作'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.table-context-menu-hint {
|
||||||
|
position: fixed;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 9999;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
:row-key="rowKey"
|
:row-key="rowKey"
|
||||||
|
:row-class-name="rowClassName"
|
||||||
:height="height"
|
:height="height"
|
||||||
:max-height="maxHeight"
|
:max-height="maxHeight"
|
||||||
:show-header="showHeader"
|
:show-header="showHeader"
|
||||||
@@ -30,6 +31,8 @@
|
|||||||
@row-click="handleRowClick"
|
@row-click="handleRowClick"
|
||||||
@row-contextmenu="handleRowContextmenu"
|
@row-contextmenu="handleRowContextmenu"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<!-- 序号列 -->
|
<!-- 序号列 -->
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -86,6 +89,8 @@
|
|||||||
loading?: boolean
|
loading?: boolean
|
||||||
/** 行数据的 Key,用于标识每一行数据 */
|
/** 行数据的 Key,用于标识每一行数据 */
|
||||||
rowKey?: string
|
rowKey?: string
|
||||||
|
/** 行的 className 的回调方法 */
|
||||||
|
rowClassName?: ((data: { row: any; rowIndex: number }) => string) | string
|
||||||
/** 是否显示边框 */
|
/** 是否显示边框 */
|
||||||
border?: boolean | null
|
border?: boolean | null
|
||||||
/** 是否使用斑马纹样式 */
|
/** 是否使用斑马纹样式 */
|
||||||
@@ -136,6 +141,7 @@
|
|||||||
data: () => [],
|
data: () => [],
|
||||||
loading: false,
|
loading: false,
|
||||||
rowKey: 'id',
|
rowKey: 'id',
|
||||||
|
rowClassName: undefined,
|
||||||
border: null,
|
border: null,
|
||||||
stripe: null,
|
stripe: null,
|
||||||
index: false,
|
index: false,
|
||||||
@@ -178,7 +184,9 @@
|
|||||||
'row-contextmenu',
|
'row-contextmenu',
|
||||||
'size-change',
|
'size-change',
|
||||||
'current-change',
|
'current-change',
|
||||||
'selection-change'
|
'selection-change',
|
||||||
|
'cell-mouse-enter',
|
||||||
|
'cell-mouse-leave'
|
||||||
])
|
])
|
||||||
|
|
||||||
const tableStore = useTableStore()
|
const tableStore = useTableStore()
|
||||||
@@ -281,6 +289,16 @@
|
|||||||
emit('row-contextmenu', row, column, event)
|
emit('row-contextmenu', row, column, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 单元格鼠标进入事件
|
||||||
|
const handleCellMouseEnter = (row: any, column: any, cell: any, event: any) => {
|
||||||
|
emit('cell-mouse-enter', row, column, cell, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单元格鼠标离开事件
|
||||||
|
const handleCellMouseLeave = (row: any, column: any, cell: any, event: any) => {
|
||||||
|
emit('cell-mouse-leave', row, column, cell, event)
|
||||||
|
}
|
||||||
|
|
||||||
// 选择变化事件
|
// 选择变化事件
|
||||||
const handleSelectionChange = (selection: any) => {
|
const handleSelectionChange = (selection: any) => {
|
||||||
emit('selection-change', selection)
|
emit('selection-change', selection)
|
||||||
|
|||||||
63
src/composables/useTableContextMenu.ts
Normal file
63
src/composables/useTableContextMenu.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* 表格右键菜单的组合式函数
|
||||||
|
* 提供右键菜单功能和鼠标悬浮提示
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
|
||||||
|
export function useTableContextMenu() {
|
||||||
|
// 鼠标悬浮提示相关
|
||||||
|
const showContextMenuHint = ref(false)
|
||||||
|
const hintPosition = reactive({ x: 0, y: 0 })
|
||||||
|
let hintTimer: any = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为表格行添加类名
|
||||||
|
*/
|
||||||
|
const getRowClassName = ({ row, rowIndex }: { row: any; rowIndex: number }) => {
|
||||||
|
return 'table-row-with-context-menu'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单元格鼠标进入事件处理
|
||||||
|
*/
|
||||||
|
const handleCellMouseEnter = (
|
||||||
|
row: any,
|
||||||
|
column: any,
|
||||||
|
cell: HTMLElement,
|
||||||
|
event: MouseEvent
|
||||||
|
) => {
|
||||||
|
// 清除之前的定时器
|
||||||
|
if (hintTimer) {
|
||||||
|
clearTimeout(hintTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟显示提示,避免快速划过时闪烁
|
||||||
|
hintTimer = setTimeout(() => {
|
||||||
|
hintPosition.x = event.clientX + 15
|
||||||
|
hintPosition.y = event.clientY + 10
|
||||||
|
showContextMenuHint.value = true
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单元格鼠标离开事件处理
|
||||||
|
*/
|
||||||
|
const handleCellMouseLeave = () => {
|
||||||
|
if (hintTimer) {
|
||||||
|
clearTimeout(hintTimer)
|
||||||
|
}
|
||||||
|
showContextMenuHint.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -450,11 +450,15 @@
|
|||||||
"devices": "Device Management",
|
"devices": "Device Management",
|
||||||
"deviceDetail": "Device Details",
|
"deviceDetail": "Device Details",
|
||||||
"assetAssign": "Allocation Records",
|
"assetAssign": "Allocation Records",
|
||||||
|
"assetAssignDetail": "Asset Allocation Details",
|
||||||
"allocationRecordDetail": "Allocation Record Details",
|
"allocationRecordDetail": "Allocation Record Details",
|
||||||
"cardReplacementRequest": "Card Replacement Request",
|
"cardReplacementRequest": "Card Replacement Request",
|
||||||
"authorizationRecords": "Authorization Records",
|
"authorizationRecords": "Authorization Records",
|
||||||
"authorizationDetail": "Authorization Details",
|
"authorizationDetail": "Authorization Details",
|
||||||
"enterpriseDevices": "Enterprise Devices"
|
"authorizationRecordDetail": "Authorization Record Details",
|
||||||
|
"enterpriseDevices": "Enterprise Devices",
|
||||||
|
"recordsManagement": "Records Management",
|
||||||
|
"taskManagement": "Task Management"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings Management",
|
"title": "Settings Management",
|
||||||
|
|||||||
@@ -385,7 +385,7 @@
|
|||||||
},
|
},
|
||||||
"cardManagement": {
|
"cardManagement": {
|
||||||
"title": "我的网卡",
|
"title": "我的网卡",
|
||||||
"singleCard": "单卡信息",
|
"singleCard": "资产信息",
|
||||||
"cardList": "网卡管理",
|
"cardList": "网卡管理",
|
||||||
"cardDetail": "网卡明细",
|
"cardDetail": "网卡明细",
|
||||||
"cardAssign": "网卡分配",
|
"cardAssign": "网卡分配",
|
||||||
@@ -453,7 +453,9 @@
|
|||||||
"authorizationRecords": "授权记录",
|
"authorizationRecords": "授权记录",
|
||||||
"authorizationDetail": "授权记录详情",
|
"authorizationDetail": "授权记录详情",
|
||||||
"authorizationRecordDetail": "授权记录详情",
|
"authorizationRecordDetail": "授权记录详情",
|
||||||
"enterpriseDevices": "企业设备列表"
|
"enterpriseDevices": "企业设备列表",
|
||||||
|
"recordsManagement": "记录管理",
|
||||||
|
"taskManagement": "任务管理"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"title": "账户管理",
|
"title": "账户管理",
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// path: '/widgets',
|
// path: '/widgets',
|
||||||
// name: 'Widgets',
|
// name: 'Widgets',
|
||||||
@@ -266,6 +267,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
// },
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/system',
|
path: '/system',
|
||||||
name: 'System',
|
name: 'System',
|
||||||
@@ -429,6 +431,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// }
|
// }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// path: '/article',
|
// path: '/article',
|
||||||
// name: 'Article',
|
// name: 'Article',
|
||||||
@@ -712,6 +715,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
// },
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/package-management',
|
path: '/package-management',
|
||||||
name: 'PackageManagement',
|
name: 'PackageManagement',
|
||||||
@@ -780,6 +784,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/shop-management',
|
path: '/shop-management',
|
||||||
name: 'ShopManagement',
|
name: 'ShopManagement',
|
||||||
@@ -800,6 +805,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/account-management',
|
path: '/account-management',
|
||||||
name: 'AccountManagement',
|
name: 'AccountManagement',
|
||||||
@@ -885,6 +891,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// path: '/product',
|
// path: '/product',
|
||||||
// name: 'Product',
|
// name: 'Product',
|
||||||
@@ -950,6 +957,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// }
|
// }
|
||||||
// ]
|
// ]
|
||||||
// },
|
// },
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/asset-management',
|
path: '/asset-management',
|
||||||
name: 'AssetManagement',
|
name: 'AssetManagement',
|
||||||
@@ -978,15 +986,84 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'iot-card-management/detail',
|
path: 'devices',
|
||||||
name: 'IotCardDetail',
|
name: 'DeviceList',
|
||||||
component: RoutesAlias.StandaloneCardList + '/detail',
|
component: RoutesAlias.DeviceList,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'menus.assetManagement.iotCardDetail',
|
title: 'menus.assetManagement.devices',
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'enterprise-devices',
|
||||||
|
name: 'EnterpriseDevices',
|
||||||
|
component: RoutesAlias.EnterpriseDevices,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.assetManagement.enterpriseDevices',
|
||||||
isHide: true,
|
isHide: true,
|
||||||
keepAlive: false
|
keepAlive: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 记录管理
|
||||||
|
{
|
||||||
|
path: 'records',
|
||||||
|
name: 'RecordsManagement',
|
||||||
|
component: '',
|
||||||
|
meta: {
|
||||||
|
title: 'menus.assetManagement.recordsManagement',
|
||||||
|
keepAlive: true
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'asset-assign',
|
||||||
|
name: 'AssetAssign',
|
||||||
|
component: RoutesAlias.AssetAssign,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.assetManagement.assetAssign',
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'asset-assign/detail/:id',
|
||||||
|
name: 'AssetAssignDetail',
|
||||||
|
component: RoutesAlias.AssetAssignDetail,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.assetManagement.assetAssignDetail',
|
||||||
|
isHide: true,
|
||||||
|
keepAlive: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'authorization-records',
|
||||||
|
name: 'AuthorizationRecords',
|
||||||
|
component: RoutesAlias.AuthorizationRecords,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.assetManagement.authorizationRecords',
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'authorization-records/detail/:id',
|
||||||
|
name: 'AuthorizationRecordDetail',
|
||||||
|
component: RoutesAlias.AuthorizationRecordDetail,
|
||||||
|
meta: {
|
||||||
|
title: 'menus.assetManagement.authorizationRecordDetail',
|
||||||
|
isHide: true,
|
||||||
|
keepAlive: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 任务管理
|
||||||
|
{
|
||||||
|
path: 'tasks',
|
||||||
|
name: 'TaskManagement',
|
||||||
|
component: '',
|
||||||
|
meta: {
|
||||||
|
title: 'menus.assetManagement.taskManagement',
|
||||||
|
keepAlive: true
|
||||||
|
},
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
path: 'iot-card-task',
|
path: 'iot-card-task',
|
||||||
name: 'IotCardTask',
|
name: 'IotCardTask',
|
||||||
@@ -1014,85 +1091,12 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
isHide: true,
|
isHide: true,
|
||||||
keepAlive: false
|
keepAlive: false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'devices',
|
|
||||||
name: 'DeviceList',
|
|
||||||
component: RoutesAlias.DeviceList,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.assetManagement.devices',
|
|
||||||
keepAlive: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'device-detail',
|
|
||||||
name: 'DeviceDetail',
|
|
||||||
component: RoutesAlias.DeviceDetail,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.assetManagement.deviceDetail',
|
|
||||||
isHide: true,
|
|
||||||
keepAlive: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'asset-assign',
|
|
||||||
name: 'AssetAssign',
|
|
||||||
component: RoutesAlias.AssetAssign,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.assetManagement.assetAssign',
|
|
||||||
keepAlive: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'asset-assign/detail/:id',
|
|
||||||
name: 'AssetAssignDetail',
|
|
||||||
component: RoutesAlias.AssetAssignDetail,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.assetManagement.assetAssignDetail',
|
|
||||||
isHide: true,
|
|
||||||
keepAlive: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// path: 'card-replacement-request',
|
|
||||||
// name: 'CardReplacementRequest',
|
|
||||||
// component: RoutesAlias.CardReplacementRequest,
|
|
||||||
// meta: {
|
|
||||||
// title: 'menus.assetManagement.cardReplacementRequest',
|
|
||||||
// keepAlive: true
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
path: 'authorization-records',
|
|
||||||
name: 'AuthorizationRecords',
|
|
||||||
component: RoutesAlias.AuthorizationRecords,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.assetManagement.authorizationRecords',
|
|
||||||
keepAlive: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'authorization-records/detail/:id',
|
|
||||||
name: 'AuthorizationRecordDetail',
|
|
||||||
component: RoutesAlias.AuthorizationRecordDetail,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.assetManagement.authorizationRecordDetail',
|
|
||||||
isHide: true,
|
|
||||||
keepAlive: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'enterprise-devices',
|
|
||||||
name: 'EnterpriseDevices',
|
|
||||||
component: RoutesAlias.EnterpriseDevices,
|
|
||||||
meta: {
|
|
||||||
title: 'menus.assetManagement.enterpriseDevices',
|
|
||||||
isHide: true,
|
|
||||||
keepAlive: false
|
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/account',
|
path: '/account',
|
||||||
name: 'Finance',
|
name: 'Finance',
|
||||||
@@ -1141,6 +1145,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
// }
|
// }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/commission',
|
path: '/commission',
|
||||||
name: 'CommissionManagement',
|
name: 'CommissionManagement',
|
||||||
@@ -1192,6 +1197,7 @@ export const asyncRoutes: AppRouteRecord[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// path: '/settings',
|
// path: '/settings',
|
||||||
// name: 'Settings',
|
// name: 'Settings',
|
||||||
|
|||||||
3
src/types/components.d.ts
vendored
3
src/types/components.d.ts
vendored
@@ -122,6 +122,8 @@ declare module 'vue' {
|
|||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||||
ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
|
ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
|
||||||
|
ElStep: typeof import('element-plus/es')['ElStep']
|
||||||
|
ElSteps: typeof import('element-plus/es')['ElSteps']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
@@ -152,6 +154,7 @@ declare module 'vue' {
|
|||||||
SettingHeader: typeof import('./../components/core/layouts/art-settings-panel/widget/SettingHeader.vue')['default']
|
SettingHeader: typeof import('./../components/core/layouts/art-settings-panel/widget/SettingHeader.vue')['default']
|
||||||
SettingItem: typeof import('./../components/core/layouts/art-settings-panel/widget/SettingItem.vue')['default']
|
SettingItem: typeof import('./../components/core/layouts/art-settings-panel/widget/SettingItem.vue')['default']
|
||||||
SidebarSubmenu: typeof import('./../components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue')['default']
|
SidebarSubmenu: typeof import('./../components/core/layouts/art-menus/art-sidebar-menu/widget/SidebarSubmenu.vue')['default']
|
||||||
|
TableContextMenuHint: typeof import('./../components/core/others/TableContextMenuHint.vue')['default']
|
||||||
ThemeSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ThemeSettings.vue')['default']
|
ThemeSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ThemeSettings.vue')['default']
|
||||||
}
|
}
|
||||||
export interface ComponentCustomProperties {
|
export interface ComponentCustomProperties {
|
||||||
|
|||||||
@@ -31,16 +31,22 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -191,8 +197,10 @@
|
|||||||
import type { FormRules } from 'element-plus'
|
import type { FormRules } from 'element-plus'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { AccountService } from '@/api/modules/account'
|
import { AccountService } from '@/api/modules/account'
|
||||||
import { RoleService } from '@/api/modules/role'
|
import { RoleService } from '@/api/modules/role'
|
||||||
@@ -207,6 +215,15 @@
|
|||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const dialogType = ref('add')
|
const dialogType = ref('add')
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const roleDialogVisible = ref(false)
|
const roleDialogVisible = ref(false)
|
||||||
@@ -865,6 +882,10 @@
|
|||||||
// 账号管理页面样式
|
// 账号管理页面样式
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -64,10 +64,13 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn type="selection" width="55" />
|
<ElTableColumn type="selection" width="55" />
|
||||||
@@ -75,6 +78,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 授权卡对话框 -->
|
<!-- 授权卡对话框 -->
|
||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="allocateDialogVisible"
|
v-model="allocateDialogVisible"
|
||||||
@@ -232,8 +238,10 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import { BgColorEnum } from '@/enums/appEnum'
|
import { BgColorEnum } from '@/enums/appEnum'
|
||||||
@@ -251,6 +259,16 @@
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const allocateDialogVisible = ref(false)
|
const allocateDialogVisible = ref(false)
|
||||||
const allocateLoading = ref(false)
|
const allocateLoading = ref(false)
|
||||||
@@ -1064,4 +1082,8 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -35,15 +35,21 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 新增/编辑对话框 -->
|
<!-- 新增/编辑对话框 -->
|
||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
@@ -221,8 +227,10 @@
|
|||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
@@ -233,6 +241,15 @@
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
// 判断是否是代理账号 (user_type === 3)
|
// 判断是否是代理账号 (user_type === 3)
|
||||||
const isAgentAccount = computed(() => userStore.info.user_type === 3)
|
const isAgentAccount = computed(() => userStore.info.user_type === 3)
|
||||||
|
|
||||||
@@ -811,3 +828,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -28,15 +28,21 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -57,16 +63,28 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import type { AssetAllocationRecord, AllocationTypeEnum, AssetTypeEnum } from '@/types/api/card'
|
import type { AssetAllocationRecord, AllocationTypeEnum, AssetTypeEnum } from '@/types/api/card'
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'AssetAllocationRecords' })
|
defineOptions({ name: 'AssetAllocationRecords' })
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
@@ -374,4 +392,8 @@
|
|||||||
.asset-allocation-records-page {
|
.asset-allocation-records-page {
|
||||||
// Allocation records page styles
|
// Allocation records page styles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -28,15 +28,21 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -82,9 +88,11 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import type {
|
import type {
|
||||||
AuthorizationItem,
|
AuthorizationItem,
|
||||||
@@ -98,6 +106,16 @@
|
|||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const remarkDialogVisible = ref(false)
|
const remarkDialogVisible = ref(false)
|
||||||
const remarkLoading = ref(false)
|
const remarkLoading = ref(false)
|
||||||
@@ -442,4 +460,8 @@
|
|||||||
.authorization-records-page {
|
.authorization-records-page {
|
||||||
// Authorization records page styles
|
// Authorization records page styles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,446 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="device-detail">
|
|
||||||
<ElCard shadow="never">
|
|
||||||
<!-- 页面头部 -->
|
|
||||||
<div class="detail-header">
|
|
||||||
<ElButton @click="handleBack">
|
|
||||||
<template #icon>
|
|
||||||
<ElIcon><ArrowLeft /></ElIcon>
|
|
||||||
</template>
|
|
||||||
返回
|
|
||||||
</ElButton>
|
|
||||||
<h2 class="detail-title">设备详情</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 详情内容 -->
|
|
||||||
<DetailPage v-if="detailData" :sections="detailSections" :data="detailData" />
|
|
||||||
|
|
||||||
<!-- 加载中 -->
|
|
||||||
<div v-if="loading" class="loading-container">
|
|
||||||
<ElIcon class="is-loading"><Loading /></ElIcon>
|
|
||||||
<span>加载中...</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 绑定卡片列表 -->
|
|
||||||
<ElCard v-if="detailData" shadow="never" class="cards-section" style="margin-top: 20px">
|
|
||||||
<template #header>
|
|
||||||
<div class="section-header">
|
|
||||||
<span class="section-title">绑定的卡列表</span>
|
|
||||||
<ElButton
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
@click="showBindDialog"
|
|
||||||
:disabled="!detailData || detailData.bound_card_count >= detailData.max_sim_slots"
|
|
||||||
>
|
|
||||||
绑定新卡
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ElTable :data="cardList" border v-loading="cardsLoading">
|
|
||||||
<ElTableColumn prop="slot_position" label="插槽位置" width="100" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<ElTag type="info" size="small">插槽 {{ row.slot_position }}</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="iccid" label="ICCID" minWidth="180" />
|
|
||||||
<ElTableColumn prop="msisdn" label="接入号" width="150">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.msisdn || '-' }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="carrier_name" label="运营商" width="120" />
|
|
||||||
<ElTableColumn prop="status" label="卡状态" width="100">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<ElTag :type="cardStatusTypeMap[row.status]" size="small">
|
|
||||||
{{ cardStatusTextMap[row.status] || '未知' }}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn prop="bind_time" label="绑定时间" width="180">
|
|
||||||
<template #default="{ row }">
|
|
||||||
{{ row.bind_time ? formatDateTime(row.bind_time) : '-' }}
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
<ElTableColumn label="操作" width="100" fixed="right" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<ElButton type="danger" text size="small" @click="handleUnbindCard(row)">
|
|
||||||
解绑
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
|
||||||
</ElTableColumn>
|
|
||||||
</ElTable>
|
|
||||||
|
|
||||||
<ElEmpty v-if="!cardList.length && !cardsLoading" description="暂无绑定的卡" />
|
|
||||||
</ElCard>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<!-- 绑定卡对话框 -->
|
|
||||||
<ElDialog v-model="bindDialogVisible" title="绑定卡到设备" width="500px">
|
|
||||||
<ElForm ref="bindFormRef" :model="bindForm" :rules="bindRules" label-width="100px">
|
|
||||||
<ElFormItem label="选择卡" prop="iot_card_id">
|
|
||||||
<ElSelect
|
|
||||||
v-model="bindForm.iot_card_id"
|
|
||||||
placeholder="请搜索并选择卡"
|
|
||||||
filterable
|
|
||||||
remote
|
|
||||||
:remote-method="searchCards"
|
|
||||||
:loading="searchCardsLoading"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<ElOption
|
|
||||||
v-for="card in availableCards"
|
|
||||||
:key="card.id"
|
|
||||||
:label="`${card.iccid} - ${card.carrier_name}`"
|
|
||||||
:value="card.id"
|
|
||||||
>
|
|
||||||
<div style="display: flex; justify-content: space-between">
|
|
||||||
<span>{{ card.iccid }}</span>
|
|
||||||
<ElTag size="small" type="info">{{ card.carrier_name }}</ElTag>
|
|
||||||
</div>
|
|
||||||
</ElOption>
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="插槽位置" prop="slot_position">
|
|
||||||
<ElSelect
|
|
||||||
v-model="bindForm.slot_position"
|
|
||||||
placeholder="请选择插槽位置"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<ElOption
|
|
||||||
v-for="slot in availableSlots"
|
|
||||||
:key="slot"
|
|
||||||
:label="`插槽 ${slot}`"
|
|
||||||
:value="slot"
|
|
||||||
/>
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElForm>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<ElButton @click="bindDialogVisible = false">取消</ElButton>
|
|
||||||
<ElButton type="primary" @click="handleConfirmBind" :loading="bindLoading">
|
|
||||||
确认绑定
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ElDialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, reactive, onMounted, computed, h } from 'vue'
|
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
|
||||||
import { ElCard, ElButton, ElIcon, ElMessage, ElMessageBox, ElTag } from 'element-plus'
|
|
||||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
|
||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
|
||||||
import { DeviceService, CardService } from '@/api/modules'
|
|
||||||
import type { Device, DeviceCardBinding } from '@/types/api'
|
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
|
||||||
|
|
||||||
defineOptions({ name: 'DeviceDetail' })
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const cardsLoading = ref(false)
|
|
||||||
const bindLoading = ref(false)
|
|
||||||
const searchCardsLoading = ref(false)
|
|
||||||
const bindDialogVisible = ref(false)
|
|
||||||
const bindFormRef = ref<FormInstance>()
|
|
||||||
const detailData = ref<Device | null>(null)
|
|
||||||
const cardList = ref<DeviceCardBinding[]>([])
|
|
||||||
const availableCards = ref<any[]>([])
|
|
||||||
|
|
||||||
// 状态类型映射
|
|
||||||
const statusTypeMap: Record<number, any> = {
|
|
||||||
1: 'info',
|
|
||||||
2: 'primary',
|
|
||||||
3: 'success',
|
|
||||||
4: 'danger'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 卡状态类型映射
|
|
||||||
const cardStatusTypeMap: Record<number, any> = {
|
|
||||||
1: 'info',
|
|
||||||
2: 'primary',
|
|
||||||
3: 'success',
|
|
||||||
4: 'danger'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 卡状态文本映射
|
|
||||||
const cardStatusTextMap: Record<number, string> = {
|
|
||||||
1: '在库',
|
|
||||||
2: '已分销',
|
|
||||||
3: '已激活',
|
|
||||||
4: '已停用'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定表单
|
|
||||||
const bindForm = reactive({
|
|
||||||
iot_card_id: undefined as number | undefined,
|
|
||||||
slot_position: undefined as number | undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
// 绑定表单验证规则
|
|
||||||
const bindRules = reactive<FormRules>({
|
|
||||||
iot_card_id: [{ required: true, message: '请选择卡', trigger: 'change' }],
|
|
||||||
slot_position: [{ required: true, message: '请选择插槽位置', trigger: 'change' }]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 可用插槽
|
|
||||||
const availableSlots = computed(() => {
|
|
||||||
if (!detailData.value) return []
|
|
||||||
const occupiedSlots = cardList.value.map((card) => card.slot_position)
|
|
||||||
const allSlots = Array.from({ length: detailData.value.max_sim_slots }, (_, i) => i + 1)
|
|
||||||
return allSlots.filter((slot) => !occupiedSlots.includes(slot))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 详情页配置
|
|
||||||
const detailSections: DetailSection[] = [
|
|
||||||
{
|
|
||||||
title: '基本信息',
|
|
||||||
fields: [
|
|
||||||
{ label: '设备ID', prop: 'id' },
|
|
||||||
{ label: '设备号', prop: 'device_no' },
|
|
||||||
{ label: '设备名称', prop: 'device_name', formatter: (value) => value || '-' },
|
|
||||||
{ label: '设备型号', prop: 'device_model', formatter: (value) => value || '-' },
|
|
||||||
{ label: '设备类型', prop: 'device_type', formatter: (value) => value || '-' },
|
|
||||||
{ label: '制造商', prop: 'manufacturer', formatter: (value) => value || '-' },
|
|
||||||
{ label: '最大插槽数', prop: 'max_sim_slots' },
|
|
||||||
{
|
|
||||||
label: '已绑定卡数',
|
|
||||||
render: (data: Device) => {
|
|
||||||
const color = data.bound_card_count > 0 ? '#67c23a' : '#909399'
|
|
||||||
return h('span', { style: { color, fontWeight: 'bold' } },
|
|
||||||
`${data.bound_card_count} / ${data.max_sim_slots}`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '状态',
|
|
||||||
render: (data: Device) => {
|
|
||||||
const statusMap: Record<number, { text: string; type: any }> = {
|
|
||||||
1: { text: '在库', type: 'info' },
|
|
||||||
2: { text: '已分销', type: 'primary' },
|
|
||||||
3: { text: '已激活', type: 'success' },
|
|
||||||
4: { text: '已停用', type: 'danger' }
|
|
||||||
}
|
|
||||||
const status = statusMap[data.status] || { text: '未知', type: 'info' }
|
|
||||||
return h(ElTag, { type: status.type }, () => status.text)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ label: '所属店铺', prop: 'shop_name', formatter: (value) => value || '平台库存' },
|
|
||||||
{ label: '批次号', prop: 'batch_no', formatter: (value) => value || '-' },
|
|
||||||
{
|
|
||||||
label: '激活时间',
|
|
||||||
prop: 'activated_at',
|
|
||||||
formatter: (value) => (value ? formatDateTime(value) : '-')
|
|
||||||
},
|
|
||||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// 返回上一页
|
|
||||||
const handleBack = () => {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取详情数据
|
|
||||||
const fetchDetail = async (id?: number, deviceNo?: string) => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
let res
|
|
||||||
if (id) {
|
|
||||||
res = await DeviceService.getDeviceById(id)
|
|
||||||
} else if (deviceNo) {
|
|
||||||
res = await DeviceService.getDeviceByImei(deviceNo)
|
|
||||||
} else {
|
|
||||||
ElMessage.error('缺少设备参数')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.code === 0 && res.data) {
|
|
||||||
detailData.value = res.data
|
|
||||||
// 加载绑定的卡列表
|
|
||||||
if (res.data.id) {
|
|
||||||
loadDeviceCards(res.data.id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.message || '获取设备详情失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('获取设备详情失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载设备绑定的卡列表
|
|
||||||
const loadDeviceCards = async (id: number) => {
|
|
||||||
cardsLoading.value = true
|
|
||||||
try {
|
|
||||||
const res = await DeviceService.getDeviceCards(id)
|
|
||||||
if (res.code === 0) {
|
|
||||||
cardList.value = res.data.bindings || []
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('获取绑定卡列表失败')
|
|
||||||
} finally {
|
|
||||||
cardsLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索可用的卡
|
|
||||||
const searchCards = async (query: string) => {
|
|
||||||
if (!query) {
|
|
||||||
availableCards.value = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
searchCardsLoading.value = true
|
|
||||||
try {
|
|
||||||
const res = await CardService.getStandaloneIotCards({
|
|
||||||
iccid: query,
|
|
||||||
page: 1,
|
|
||||||
page_size: 20
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
|
||||||
availableCards.value = res.data.items || []
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
} finally {
|
|
||||||
searchCardsLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示绑定对话框
|
|
||||||
const showBindDialog = () => {
|
|
||||||
bindForm.iot_card_id = undefined
|
|
||||||
bindForm.slot_position = undefined
|
|
||||||
availableCards.value = []
|
|
||||||
bindDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认绑定
|
|
||||||
const handleConfirmBind = async () => {
|
|
||||||
if (!bindFormRef.value || !detailData.value) return
|
|
||||||
|
|
||||||
await bindFormRef.value.validate(async (valid) => {
|
|
||||||
if (valid) {
|
|
||||||
bindLoading.value = true
|
|
||||||
try {
|
|
||||||
const data = {
|
|
||||||
iot_card_id: bindForm.iot_card_id!,
|
|
||||||
slot_position: bindForm.slot_position!
|
|
||||||
}
|
|
||||||
const res = await DeviceService.bindCard(detailData.value!.id, data)
|
|
||||||
if (res.code === 0) {
|
|
||||||
ElMessage.success('绑定成功')
|
|
||||||
bindDialogVisible.value = false
|
|
||||||
await fetchDetail(detailData.value!.id)
|
|
||||||
if (bindFormRef.value) {
|
|
||||||
bindFormRef.value.resetFields()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.message || '绑定失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('绑定失败')
|
|
||||||
} finally {
|
|
||||||
bindLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解绑卡
|
|
||||||
const handleUnbindCard = (card: DeviceCardBinding) => {
|
|
||||||
ElMessageBox.confirm(`确定解绑卡 ${card.iccid} 吗?`, '解绑确认', {
|
|
||||||
confirmButtonText: '确定',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
try {
|
|
||||||
await DeviceService.unbindCard(detailData.value!.id, card.iot_card_id)
|
|
||||||
ElMessage.success('解绑成功')
|
|
||||||
await fetchDetail(detailData.value!.id)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
ElMessage.error('解绑失败')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// 用户取消
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const deviceId = route.query.id
|
|
||||||
const deviceNo = route.query.device_no
|
|
||||||
|
|
||||||
if (deviceId) {
|
|
||||||
fetchDetail(Number(deviceId))
|
|
||||||
} else if (deviceNo) {
|
|
||||||
fetchDetail(undefined, String(deviceNo))
|
|
||||||
} else {
|
|
||||||
ElMessage.error('缺少设备参数')
|
|
||||||
handleBack()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.device-detail {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
|
|
||||||
.detail-title {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cards-section {
|
|
||||||
.section-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -53,10 +53,13 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn type="selection" width="55" />
|
<ElTableColumn type="selection" width="55" />
|
||||||
@@ -64,6 +67,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 批量分配对话框 -->
|
<!-- 批量分配对话框 -->
|
||||||
<ElDialog v-model="allocateDialogVisible" title="批量分配设备" width="600px">
|
<ElDialog v-model="allocateDialogVisible" title="批量分配设备" width="600px">
|
||||||
<ElForm
|
<ElForm
|
||||||
@@ -570,8 +576,10 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { CommonStatus, getStatusText } from '@/config/constants'
|
import { CommonStatus, getStatusText } from '@/config/constants'
|
||||||
@@ -581,6 +589,16 @@
|
|||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const allocateLoading = ref(false)
|
const allocateLoading = ref(false)
|
||||||
const recallLoading = ref(false)
|
const recallLoading = ref(false)
|
||||||
@@ -801,11 +819,11 @@
|
|||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 跳转到设备详情页面
|
// 跳转到资产信息页面(原设备详情)
|
||||||
const goToDeviceSearchDetail = (deviceNo: string) => {
|
const goToDeviceSearchDetail = (deviceNo: string) => {
|
||||||
if (hasAuth('device:view_detail')) {
|
if (hasAuth('device:view_detail')) {
|
||||||
router.push({
|
router.push({
|
||||||
path: '/asset-management/device-detail',
|
path: '/asset-management/single-card',
|
||||||
query: {
|
query: {
|
||||||
device_no: deviceNo
|
device_no: deviceNo
|
||||||
}
|
}
|
||||||
@@ -1697,4 +1715,8 @@
|
|||||||
.device-list-page {
|
.device-list-page {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -39,15 +39,21 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -125,9 +131,11 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { StorageService } from '@/api/modules/storage'
|
import { StorageService } from '@/api/modules/storage'
|
||||||
import type { DeviceImportTask, DeviceImportTaskStatus } from '@/types/api/device'
|
import type { DeviceImportTask, DeviceImportTaskStatus } from '@/types/api/device'
|
||||||
@@ -137,6 +145,16 @@
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const uploadRef = ref<UploadInstance>()
|
const uploadRef = ref<UploadInstance>()
|
||||||
@@ -679,4 +697,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,312 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="iot-card-detail-page">
|
|
||||||
<ElCard shadow="never">
|
|
||||||
<!-- 页面头部 -->
|
|
||||||
<div class="detail-header">
|
|
||||||
<ElButton @click="handleBack">
|
|
||||||
<template #icon>
|
|
||||||
<ElIcon><ArrowLeft /></ElIcon>
|
|
||||||
</template>
|
|
||||||
返回
|
|
||||||
</ElButton>
|
|
||||||
<h2 class="detail-title">IoT卡详情</h2>
|
|
||||||
<div class="header-actions">
|
|
||||||
<ElButton type="primary" @click="handleRefresh" :loading="loading">
|
|
||||||
<Icon name="refresh" /> 刷新
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
|
||||||
<div v-if="loading && !cardDetail" class="loading-container">
|
|
||||||
<ElIcon class="is-loading" :size="60"><Loading /></ElIcon>
|
|
||||||
<div class="loading-text">加载中...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 详情内容 -->
|
|
||||||
<DetailPage v-if="cardDetail" :sections="detailSections" :data="cardDetail" />
|
|
||||||
|
|
||||||
<!-- 未找到卡片 -->
|
|
||||||
<div v-if="!cardDetail && !loading" class="empty-container">
|
|
||||||
<ElEmpty description="未找到该卡片信息" />
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { h } from 'vue'
|
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
|
||||||
import { CardService } from '@/api/modules'
|
|
||||||
import { ElMessage, ElIcon, ElTag } from 'element-plus'
|
|
||||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
|
||||||
import DetailPage from '@/components/common/DetailPage.vue'
|
|
||||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
|
||||||
|
|
||||||
defineOptions({ name: 'IotCardDetail' })
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const cardDetail = ref<any>(null)
|
|
||||||
const iccid = ref<string>('')
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
iccid.value = (route.query.iccid as string) || ''
|
|
||||||
if (iccid.value) {
|
|
||||||
loadCardDetail()
|
|
||||||
} else {
|
|
||||||
ElMessage.error('缺少ICCID参数')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载卡片详情
|
|
||||||
const loadCardDetail = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res = await CardService.getIotCardDetailByIccid(iccid.value)
|
|
||||||
if (res.code === 0) {
|
|
||||||
cardDetail.value = res.data
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.msg || '获取卡片详情失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取卡片详情失败:', error)
|
|
||||||
ElMessage.error('获取卡片详情失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回上一页
|
|
||||||
const handleBack = () => {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新
|
|
||||||
const handleRefresh = () => {
|
|
||||||
loadCardDetail()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 运营商类型文本
|
|
||||||
const getCarrierTypeText = (type: string) => {
|
|
||||||
const typeMap: Record<string, string> = {
|
|
||||||
CMCC: '中国移动',
|
|
||||||
CUCC: '中国联通',
|
|
||||||
CTCC: '中国电信',
|
|
||||||
CBN: '中国广电'
|
|
||||||
}
|
|
||||||
return typeMap[type] || type || '--'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 卡业务类型文本
|
|
||||||
const getCardCategoryText = (category: string) => {
|
|
||||||
const categoryMap: Record<string, string> = {
|
|
||||||
normal: '普通卡',
|
|
||||||
industry: '行业卡'
|
|
||||||
}
|
|
||||||
return categoryMap[category] || category || '--'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态文本
|
|
||||||
const getStatusText = (status: number) => {
|
|
||||||
const statusMap: Record<number, string> = {
|
|
||||||
1: '在库',
|
|
||||||
2: '已分销',
|
|
||||||
3: '已激活',
|
|
||||||
4: '已停用'
|
|
||||||
}
|
|
||||||
return statusMap[status] || '未知'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态标签类型
|
|
||||||
const getStatusTagType = (status: number) => {
|
|
||||||
const typeMap: Record<number, any> = {
|
|
||||||
1: 'info',
|
|
||||||
2: 'warning',
|
|
||||||
3: 'success',
|
|
||||||
4: 'danger'
|
|
||||||
}
|
|
||||||
return typeMap[status] || 'info'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化价格
|
|
||||||
const formatCardPrice = (price: number) => {
|
|
||||||
return `¥${((price || 0) / 100).toFixed(2)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetailPage 配置
|
|
||||||
const detailSections: DetailSection[] = [
|
|
||||||
{
|
|
||||||
title: '基本信息',
|
|
||||||
fields: [
|
|
||||||
{ label: '卡ID', prop: 'id' },
|
|
||||||
{
|
|
||||||
label: 'ICCID',
|
|
||||||
render: (data) => {
|
|
||||||
return h(
|
|
||||||
'span',
|
|
||||||
{
|
|
||||||
style: {
|
|
||||||
padding: '3px 8px',
|
|
||||||
fontFamily: "'SF Mono', Monaco, Inconsolata, 'Roboto Mono', monospace",
|
|
||||||
fontSize: '13px',
|
|
||||||
fontWeight: '500',
|
|
||||||
color: 'var(--el-text-color-regular)',
|
|
||||||
background: 'var(--el-fill-color-light)',
|
|
||||||
border: '1px solid var(--el-border-color-lighter)',
|
|
||||||
borderRadius: '4px'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data.iccid
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '卡接入号',
|
|
||||||
render: (data) => {
|
|
||||||
return h(
|
|
||||||
'span',
|
|
||||||
{
|
|
||||||
style: {
|
|
||||||
padding: '3px 8px',
|
|
||||||
fontFamily: "'SF Mono', Monaco, Inconsolata, 'Roboto Mono', monospace",
|
|
||||||
fontSize: '13px',
|
|
||||||
fontWeight: '500',
|
|
||||||
color: 'var(--el-text-color-regular)',
|
|
||||||
background: 'var(--el-fill-color-light)',
|
|
||||||
border: '1px solid var(--el-border-color-lighter)',
|
|
||||||
borderRadius: '4px'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data.msisdn || '--'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ label: '运营商', prop: 'carrier_name', formatter: (value) => value || '--' },
|
|
||||||
{
|
|
||||||
label: '运营商类型',
|
|
||||||
prop: 'carrier_type',
|
|
||||||
formatter: (value) => getCarrierTypeText(value)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '卡业务类型',
|
|
||||||
prop: 'card_category',
|
|
||||||
formatter: (value) => getCardCategoryText(value)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '状态',
|
|
||||||
render: (data) => {
|
|
||||||
return h(ElTag, { type: getStatusTagType(data.status) }, () => getStatusText(data.status))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ label: '套餐系列', prop: 'series_name', formatter: (value) => value || '--' },
|
|
||||||
{
|
|
||||||
label: '激活状态',
|
|
||||||
render: (data) => {
|
|
||||||
return h(
|
|
||||||
ElTag,
|
|
||||||
{ type: data.activation_status === 1 ? 'success' : 'info' },
|
|
||||||
() => (data.activation_status === 1 ? '已激活' : '未激活')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '实名状态',
|
|
||||||
render: (data) => {
|
|
||||||
return h(
|
|
||||||
ElTag,
|
|
||||||
{ type: data.real_name_status === 1 ? 'success' : 'warning' },
|
|
||||||
() => (data.real_name_status === 1 ? '已实名' : '未实名')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '网络状态',
|
|
||||||
render: (data) => {
|
|
||||||
return h(
|
|
||||||
ElTag,
|
|
||||||
{ type: data.network_status === 1 ? 'success' : 'danger' },
|
|
||||||
() => (data.network_status === 1 ? '开机' : '停机')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '累计流量使用',
|
|
||||||
prop: 'data_usage_mb',
|
|
||||||
formatter: (value) => `${value} MB`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '一次性佣金',
|
|
||||||
render: (data) => {
|
|
||||||
return h(
|
|
||||||
ElTag,
|
|
||||||
{ type: data.first_commission_paid ? 'success' : 'info' },
|
|
||||||
() => (data.first_commission_paid ? '已产生' : '未产生')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '累计充值',
|
|
||||||
prop: 'accumulated_recharge',
|
|
||||||
formatter: (value) => formatCardPrice(value)
|
|
||||||
},
|
|
||||||
{ label: '所属店铺', prop: 'shop_name', formatter: (value) => value || '--' },
|
|
||||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
|
||||||
{ label: '更新时间', prop: 'updated_at', formatter: (value) => formatDateTime(value) }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.iot-card-detail-page {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
|
|
||||||
.detail-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-text {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-container {
|
|
||||||
padding: 60px 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
hasAuth('iot_card:batch_download')
|
hasAuth('iot_card:batch_download')
|
||||||
"
|
"
|
||||||
type="info"
|
type="info"
|
||||||
@contextmenu.prevent="showMoreMenu"
|
@click="showMoreMenuOnClick"
|
||||||
>
|
>
|
||||||
更多操作
|
更多操作
|
||||||
</ElButton>
|
</ElButton>
|
||||||
@@ -68,10 +68,13 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn type="selection" width="55" />
|
<ElTableColumn type="selection" width="55" />
|
||||||
@@ -79,6 +82,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 批量分配对话框 -->
|
<!-- 批量分配对话框 -->
|
||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="allocateDialogVisible"
|
v-model="allocateDialogVisible"
|
||||||
@@ -597,8 +603,10 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import type {
|
import type {
|
||||||
@@ -616,6 +624,16 @@
|
|||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const allocateDialogVisible = ref(false)
|
const allocateDialogVisible = ref(false)
|
||||||
const allocateLoading = ref(false)
|
const allocateLoading = ref(false)
|
||||||
@@ -910,11 +928,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转到IoT卡详情页面
|
// 跳转到资产信息页面(原IoT卡详情)
|
||||||
const goToCardDetail = (iccid: string) => {
|
const goToCardDetail = (iccid: string) => {
|
||||||
if (hasAuth('iot_card:view_detail')) {
|
if (hasAuth('iot_card:view_detail')) {
|
||||||
router.push({
|
router.push({
|
||||||
path: RoutesAlias.StandaloneCardList + '/detail',
|
path: '/asset-management/single-card',
|
||||||
query: {
|
query: {
|
||||||
iccid: iccid
|
iccid: iccid
|
||||||
}
|
}
|
||||||
@@ -1575,13 +1593,19 @@
|
|||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
// 显示更多操作菜单
|
// 显示更多操作菜单 (右键)
|
||||||
const showMoreMenu = (e: MouseEvent) => {
|
const showMoreMenu = (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
moreMenuRef.value?.show(e)
|
moreMenuRef.value?.show(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示更多操作菜单 (左键点击)
|
||||||
|
const showMoreMenuOnClick = (e: MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
moreMenuRef.value?.show(e)
|
||||||
|
}
|
||||||
|
|
||||||
// 处理更多操作菜单选择
|
// 处理更多操作菜单选择
|
||||||
const handleMoreMenuSelect = (item: MenuItemType) => {
|
const handleMoreMenuSelect = (item: MenuItemType) => {
|
||||||
switch (item.key) {
|
switch (item.key) {
|
||||||
@@ -1829,4 +1853,8 @@
|
|||||||
.standalone-card-list-page {
|
.standalone-card-list-page {
|
||||||
// Card list page styles
|
// Card list page styles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -39,15 +39,21 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -139,8 +145,10 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { StorageService } from '@/api/modules/storage'
|
import { StorageService } from '@/api/modules/storage'
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
@@ -152,6 +160,16 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const uploadRef = ref<UploadInstance>()
|
const uploadRef = ref<UploadInstance>()
|
||||||
@@ -733,4 +751,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="analysis-dashboard">
|
<div class="analysis-dashboard">
|
||||||
开发中敬请期待...
|
<!-- 佣金概览 -->
|
||||||
|
<div class="section-title">佣金概览</div>
|
||||||
|
<CommissionSummary />
|
||||||
|
|
||||||
|
<!-- 提现配置 -->
|
||||||
|
<div class="section-title">提现配置</div>
|
||||||
|
<WithdrawalSettings />
|
||||||
|
|
||||||
<!--<el-row :gutter="20">-->
|
<!--<el-row :gutter="20">-->
|
||||||
<!-- <el-col :xl="14" :lg="15" :xs="24">-->
|
<!-- <el-col :xl="14" :lg="15" :xs="24">-->
|
||||||
<!-- <TodaySales />-->
|
<!-- <TodaySales />-->
|
||||||
@@ -45,10 +52,24 @@
|
|||||||
import TopProducts from './widget/TopProducts.vue'
|
import TopProducts from './widget/TopProducts.vue'
|
||||||
import SalesMappingByCountry from './widget/SalesMappingByCountry.vue'
|
import SalesMappingByCountry from './widget/SalesMappingByCountry.vue'
|
||||||
import VolumeServiceLevel from './widget/VolumeServiceLevel.vue'
|
import VolumeServiceLevel from './widget/VolumeServiceLevel.vue'
|
||||||
|
import CommissionSummary from './widget/CommissionSummary.vue'
|
||||||
|
import WithdrawalSettings from './widget/WithdrawalSettings.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'Analysis' })
|
defineOptions({ name: 'Analysis' })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@use './style';
|
@use './style';
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-card {
|
.el-card {
|
||||||
border: 1px solid #e8ebf1;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
175
src/views/dashboard/analysis/widget/CommissionSummary.vue
Normal file
175
src/views/dashboard/analysis/widget/CommissionSummary.vue
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<template>
|
||||||
|
<ElRow :gutter="20" class="commission-summary-widget">
|
||||||
|
<ElCol :xs="24" :sm="12" :md="12" :lg="8" :xl="4">
|
||||||
|
<ElCard shadow="hover" class="stat-card-wrapper">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div
|
||||||
|
class="stat-icon"
|
||||||
|
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||||
|
>
|
||||||
|
<i class="iconfont-sys"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">总佣金</div>
|
||||||
|
<div class="stat-value">{{ formatMoney(summary.total_commission) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :xs="24" :sm="12" :md="12" :lg="8" :xl="4">
|
||||||
|
<ElCard shadow="hover" class="stat-card-wrapper">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div
|
||||||
|
class="stat-icon"
|
||||||
|
style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
|
||||||
|
>
|
||||||
|
<i class="iconfont-sys"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">可提现佣金</div>
|
||||||
|
<div class="stat-value">{{ formatMoney(summary.available_commission) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :xs="24" :sm="12" :md="12" :lg="8" :xl="4">
|
||||||
|
<ElCard shadow="hover" class="stat-card-wrapper">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div
|
||||||
|
class="stat-icon"
|
||||||
|
style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
|
||||||
|
>
|
||||||
|
<i class="iconfont-sys"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">冻结佣金</div>
|
||||||
|
<div class="stat-value">{{ formatMoney(summary.frozen_commission) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :xs="24" :sm="12" :md="12" :lg="8" :xl="4">
|
||||||
|
<ElCard shadow="hover" class="stat-card-wrapper">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div
|
||||||
|
class="stat-icon"
|
||||||
|
style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%)"
|
||||||
|
>
|
||||||
|
<i class="iconfont-sys"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">提现中佣金</div>
|
||||||
|
<div class="stat-value">{{ formatMoney(summary.withdrawing_commission) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :xs="24" :sm="12" :md="12" :lg="8" :xl="4">
|
||||||
|
<ElCard shadow="hover" class="stat-card-wrapper">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div
|
||||||
|
class="stat-icon"
|
||||||
|
style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
|
||||||
|
>
|
||||||
|
<i class="iconfont-sys"></i>
|
||||||
|
</div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">已提现佣金</div>
|
||||||
|
<div class="stat-value">{{ formatMoney(summary.withdrawn_commission) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CommissionService } from '@/api/modules'
|
||||||
|
import type { MyCommissionSummary } from '@/types/api/commission'
|
||||||
|
import { formatMoney } from '@/utils/business/format'
|
||||||
|
|
||||||
|
defineOptions({ name: 'CommissionSummaryWidget' })
|
||||||
|
|
||||||
|
// 佣金概览
|
||||||
|
const summary = ref<MyCommissionSummary>({
|
||||||
|
total_commission: 0,
|
||||||
|
available_commission: 0,
|
||||||
|
frozen_commission: 0,
|
||||||
|
withdrawing_commission: 0,
|
||||||
|
withdrawn_commission: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载佣金概览
|
||||||
|
const loadSummary = async () => {
|
||||||
|
try {
|
||||||
|
const res = await CommissionService.getMyCommissionSummary()
|
||||||
|
if (res.code === 0) {
|
||||||
|
summary.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取佣金概览失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadSummary()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.commission-summary-widget {
|
||||||
|
.stat-card-wrapper {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 24px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.commission-summary-widget {
|
||||||
|
.stat-card-wrapper {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
223
src/views/dashboard/analysis/widget/WithdrawalSettings.vue
Normal file
223
src/views/dashboard/analysis/widget/WithdrawalSettings.vue
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<template>
|
||||||
|
<ElCard shadow="never" class="withdrawal-settings-widget" v-if="currentSetting">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<span class="header-title">当前生效配置</span>
|
||||||
|
<ElTag type="success" effect="dark" size="small">生效中</ElTag>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<span class="creator-info"
|
||||||
|
>{{ currentSetting.creator_name || '-' }} 创建于
|
||||||
|
{{ formatDateTime(currentSetting.created_at) }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="setting-info">
|
||||||
|
<div class="info-card">
|
||||||
|
<div
|
||||||
|
class="info-icon"
|
||||||
|
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||||
|
>
|
||||||
|
<i class="el-icon">💰</i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">最低提现金额</div>
|
||||||
|
<div class="info-value">{{ formatMoney(currentSetting.min_withdrawal_amount) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div
|
||||||
|
class="info-icon"
|
||||||
|
style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
|
||||||
|
>
|
||||||
|
<i class="el-icon">📊</i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">手续费率</div>
|
||||||
|
<div class="info-value">{{ formatFeeRate(currentSetting.fee_rate) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div
|
||||||
|
class="info-icon"
|
||||||
|
style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
|
||||||
|
>
|
||||||
|
<i class="el-icon">🔢</i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">每日提现次数</div>
|
||||||
|
<div class="info-value">{{ currentSetting.daily_withdrawal_limit }} 次</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<div
|
||||||
|
class="info-icon"
|
||||||
|
style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
|
||||||
|
>
|
||||||
|
<i class="el-icon">⏰</i>
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<div class="info-label">到账天数</div>
|
||||||
|
<div class="info-value">{{
|
||||||
|
currentSetting.arrival_days === 0 ? '实时到账' : `${currentSetting.arrival_days} 天`
|
||||||
|
}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
<ElCard shadow="never" v-else class="withdrawal-settings-widget empty-state">
|
||||||
|
<div class="empty-content">
|
||||||
|
<i class="el-icon-info" style="font-size: 48px; color: var(--el-text-color-placeholder)"></i>
|
||||||
|
<p>暂无提现配置</p>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CommissionService } from '@/api/modules'
|
||||||
|
import { ElTag } from 'element-plus'
|
||||||
|
import type { WithdrawalSettingItem } from '@/types/api/commission'
|
||||||
|
import { formatDateTime, formatMoney, formatFeeRate } from '@/utils/business/format'
|
||||||
|
|
||||||
|
defineOptions({ name: 'WithdrawalSettingsWidget' })
|
||||||
|
|
||||||
|
// 当前生效的配置
|
||||||
|
const currentSetting = ref<WithdrawalSettingItem | null>(null)
|
||||||
|
|
||||||
|
// 加载当前生效配置
|
||||||
|
const loadCurrentSetting = async () => {
|
||||||
|
try {
|
||||||
|
const res = await CommissionService.getCurrentWithdrawalSetting()
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
currentSetting.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取当前配置失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadCurrentSetting()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.withdrawal-settings-widget {
|
||||||
|
:deep(.el-card__header) {
|
||||||
|
padding: 18px 20px;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
.creator-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--el-fill-color-blank);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.empty-state {
|
||||||
|
.empty-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.withdrawal-settings-widget {
|
||||||
|
.card-header {
|
||||||
|
.header-left,
|
||||||
|
.header-right {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-info {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -33,14 +33,29 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
|
<!-- 右键菜单 -->
|
||||||
|
<ArtMenuRight
|
||||||
|
ref="contextMenuRef"
|
||||||
|
:menu-items="contextMenuItems"
|
||||||
|
:menu-width="120"
|
||||||
|
@select="handleContextMenuSelect"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 新增/编辑对话框 -->
|
<!-- 新增/编辑对话框 -->
|
||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
@@ -105,7 +120,11 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import {
|
import {
|
||||||
CommonStatus,
|
CommonStatus,
|
||||||
@@ -122,6 +141,17 @@
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
|
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
|
const currentRow = ref<any>(null)
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
@@ -188,8 +218,7 @@
|
|||||||
{ label: '运营商类型', prop: 'carrier_type' },
|
{ label: '运营商类型', prop: 'carrier_type' },
|
||||||
{ label: '运营商描述', prop: 'description' },
|
{ label: '运营商描述', prop: 'description' },
|
||||||
{ label: '状态', prop: 'status' },
|
{ label: '状态', prop: 'status' },
|
||||||
{ label: '创建时间', prop: 'created_at' },
|
{ label: '创建时间', prop: 'created_at' }
|
||||||
{ label: '操作', prop: 'operation' }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
@@ -271,35 +300,6 @@
|
|||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
width: 180,
|
width: 180,
|
||||||
formatter: (row: any) => formatDateTime(row.created_at)
|
formatter: (row: any) => formatDateTime(row.created_at)
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'operation',
|
|
||||||
label: '操作',
|
|
||||||
width: 150,
|
|
||||||
fixed: 'right',
|
|
||||||
formatter: (row: any) => {
|
|
||||||
const buttons = []
|
|
||||||
|
|
||||||
if (hasAuth('carrier:edit')) {
|
|
||||||
buttons.push(
|
|
||||||
h(ArtButtonTable, {
|
|
||||||
type: 'edit',
|
|
||||||
onClick: () => showDialog('edit', row)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('carrier:delete')) {
|
|
||||||
buttons.push(
|
|
||||||
h(ArtButtonTable, {
|
|
||||||
type: 'delete',
|
|
||||||
onClick: () => deleteCarrier(row)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return h('div', { style: 'display: flex; gap: 8px;' }, buttons)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -469,10 +469,51 @@
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 右键菜单项配置
|
||||||
|
const contextMenuItems = computed((): MenuItemType[] => {
|
||||||
|
const items: MenuItemType[] = []
|
||||||
|
|
||||||
|
if (hasAuth('carrier:edit')) {
|
||||||
|
items.push({ key: 'edit', label: '编辑' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAuth('carrier:delete')) {
|
||||||
|
items.push({ key: 'delete', label: '删除' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理表格行右键菜单
|
||||||
|
const handleRowContextMenu = (row: any, column: any, event: MouseEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
currentRow.value = row
|
||||||
|
contextMenuRef.value?.show(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理右键菜单选择
|
||||||
|
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||||
|
if (!currentRow.value) return
|
||||||
|
|
||||||
|
switch (item.key) {
|
||||||
|
case 'edit':
|
||||||
|
showDialog('edit', currentRow.value)
|
||||||
|
break
|
||||||
|
case 'delete':
|
||||||
|
deleteCarrier(currentRow.value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.carrier-page {
|
.carrier-page {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -46,15 +46,21 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -242,8 +248,10 @@
|
|||||||
} from '@/types/api/commission'
|
} from '@/types/api/commission'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime, formatMoney } from '@/utils/business/format'
|
import { formatDateTime, formatMoney } from '@/utils/business/format'
|
||||||
import {
|
import {
|
||||||
@@ -259,6 +267,15 @@
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
// 主表格状态
|
// 主表格状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
@@ -629,4 +646,8 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,89 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="my-commission-page">
|
<div class="my-commission-page">
|
||||||
<!-- 佣金概览卡片 -->
|
|
||||||
<ElRow :gutter="20" style="margin-bottom: 20px">
|
|
||||||
<ElCol :xs="24" :sm="12" :md="8" :lg="4">
|
|
||||||
<ElCard shadow="hover">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div
|
|
||||||
class="stat-icon"
|
|
||||||
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
|
||||||
>
|
|
||||||
<i class="iconfont-sys"></i>
|
|
||||||
</div>
|
|
||||||
<div class="stat-content">
|
|
||||||
<div class="stat-label">总佣金</div>
|
|
||||||
<div class="stat-value">{{ formatMoney(summary.total_commission) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="8" :lg="4">
|
|
||||||
<ElCard shadow="hover">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div
|
|
||||||
class="stat-icon"
|
|
||||||
style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
|
|
||||||
>
|
|
||||||
<i class="iconfont-sys"></i>
|
|
||||||
</div>
|
|
||||||
<div class="stat-content">
|
|
||||||
<div class="stat-label">可提现佣金</div>
|
|
||||||
<div class="stat-value">{{ formatMoney(summary.available_commission) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="8" :lg="4">
|
|
||||||
<ElCard shadow="hover">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div
|
|
||||||
class="stat-icon"
|
|
||||||
style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
|
|
||||||
>
|
|
||||||
<i class="iconfont-sys"></i>
|
|
||||||
</div>
|
|
||||||
<div class="stat-content">
|
|
||||||
<div class="stat-label">冻结佣金</div>
|
|
||||||
<div class="stat-value">{{ formatMoney(summary.frozen_commission) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="8" :lg="4">
|
|
||||||
<ElCard shadow="hover">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div
|
|
||||||
class="stat-icon"
|
|
||||||
style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%)"
|
|
||||||
>
|
|
||||||
<i class="iconfont-sys"></i>
|
|
||||||
</div>
|
|
||||||
<div class="stat-content">
|
|
||||||
<div class="stat-label">提现中佣金</div>
|
|
||||||
<div class="stat-value">{{ formatMoney(summary.withdrawing_commission) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</ElCol>
|
|
||||||
<ElCol :xs="24" :sm="12" :md="8" :lg="4">
|
|
||||||
<ElCard shadow="hover">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div
|
|
||||||
class="stat-icon"
|
|
||||||
style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
|
|
||||||
>
|
|
||||||
<i class="iconfont-sys"></i>
|
|
||||||
</div>
|
|
||||||
<div class="stat-content">
|
|
||||||
<div class="stat-label">已提现佣金</div>
|
|
||||||
<div class="stat-value">{{ formatMoney(summary.withdrawn_commission) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
|
|
||||||
<!-- 标签页 -->
|
<!-- 标签页 -->
|
||||||
<ElCard shadow="never">
|
<ElCard shadow="never">
|
||||||
<ElTabs v-model="activeTab">
|
<ElTabs v-model="activeTab">
|
||||||
@@ -841,37 +757,6 @@
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.my-commission-page {
|
.my-commission-page {
|
||||||
.stat-card {
|
// 样式已移动到分析页的佣金概览组件
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.stat-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
font-size: 28px;
|
|
||||||
color: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-content {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,78 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<ArtTableFullScreen>
|
<ArtTableFullScreen>
|
||||||
<div class="withdrawal-settings-page" id="table-full-screen">
|
<div class="withdrawal-settings-page" id="table-full-screen">
|
||||||
<!-- 当前生效配置卡片 -->
|
|
||||||
<ElCard shadow="never" class="current-setting-card" v-if="currentSetting">
|
|
||||||
<template #header>
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="header-left">
|
|
||||||
<span class="header-title">当前生效配置</span>
|
|
||||||
<ElTag type="success" effect="dark">生效中</ElTag>
|
|
||||||
</div>
|
|
||||||
<div class="header-right">
|
|
||||||
<span class="creator-info"
|
|
||||||
>{{ currentSetting.creator_name || '-' }} 创建于
|
|
||||||
{{ formatDateTime(currentSetting.created_at) }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div class="setting-info">
|
|
||||||
<div class="info-card">
|
|
||||||
<div
|
|
||||||
class="info-icon"
|
|
||||||
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
|
||||||
>
|
|
||||||
<i class="el-icon">💰</i>
|
|
||||||
</div>
|
|
||||||
<div class="info-content">
|
|
||||||
<div class="info-label">最低提现金额</div>
|
|
||||||
<div class="info-value">{{ formatMoney(currentSetting.min_withdrawal_amount) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-card">
|
|
||||||
<div
|
|
||||||
class="info-icon"
|
|
||||||
style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
|
|
||||||
>
|
|
||||||
<i class="el-icon">📊</i>
|
|
||||||
</div>
|
|
||||||
<div class="info-content">
|
|
||||||
<div class="info-label">手续费率</div>
|
|
||||||
<div class="info-value">{{ formatFeeRate(currentSetting.fee_rate) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-card">
|
|
||||||
<div
|
|
||||||
class="info-icon"
|
|
||||||
style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
|
|
||||||
>
|
|
||||||
<i class="el-icon">🔢</i>
|
|
||||||
</div>
|
|
||||||
<div class="info-content">
|
|
||||||
<div class="info-label">每日提现次数</div>
|
|
||||||
<div class="info-value">{{ currentSetting.daily_withdrawal_limit }} 次</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-card">
|
|
||||||
<div
|
|
||||||
class="info-icon"
|
|
||||||
style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
|
|
||||||
>
|
|
||||||
<i class="el-icon">⏰</i>
|
|
||||||
</div>
|
|
||||||
<div class="info-content">
|
|
||||||
<div class="info-label">到账天数</div>
|
|
||||||
<div class="info-value">{{
|
|
||||||
currentSetting.arrival_days === 0 ? '实时到账' : `${currentSetting.arrival_days} 天`
|
|
||||||
}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<!-- 配置列表 -->
|
<!-- 配置列表 -->
|
||||||
<ElCard shadow="never" class="art-table-card" style="margin-top: 20px">
|
<ElCard shadow="never" class="art-table-card">
|
||||||
<!-- 表格头部 -->
|
<!-- 表格头部 -->
|
||||||
<ArtTableHeader
|
<ArtTableHeader
|
||||||
:columnList="columnOptions"
|
:columnList="columnOptions"
|
||||||
@@ -168,9 +98,6 @@
|
|||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
// 当前生效的配置
|
|
||||||
const currentSetting = ref<WithdrawalSettingItem | null>(null)
|
|
||||||
|
|
||||||
// 配置列表
|
// 配置列表
|
||||||
const settingsList = ref<WithdrawalSettingItem[]>([])
|
const settingsList = ref<WithdrawalSettingItem[]>([])
|
||||||
|
|
||||||
@@ -254,26 +181,9 @@
|
|||||||
])
|
])
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadData()
|
loadSettingsList()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载数据
|
|
||||||
const loadData = async () => {
|
|
||||||
await Promise.all([loadCurrentSetting(), loadSettingsList()])
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载当前生效配置
|
|
||||||
const loadCurrentSetting = async () => {
|
|
||||||
try {
|
|
||||||
const res = await CommissionService.getCurrentWithdrawalSetting()
|
|
||||||
if (res.code === 0 && res.data) {
|
|
||||||
currentSetting.value = res.data
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取当前配置失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载配置列表
|
// 加载配置列表
|
||||||
const loadSettingsList = async () => {
|
const loadSettingsList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -291,7 +201,7 @@
|
|||||||
|
|
||||||
// 刷新数据
|
// 刷新数据
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
loadData()
|
loadSettingsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示新增对话框
|
// 显示新增对话框
|
||||||
@@ -323,7 +233,7 @@
|
|||||||
ElMessage.success('新增配置成功')
|
ElMessage.success('新增配置成功')
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
formEl.resetFields()
|
formEl.resetFields()
|
||||||
loadData()
|
loadSettingsList()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -336,99 +246,6 @@
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.withdrawal-settings-page {
|
.withdrawal-settings-page {
|
||||||
.current-setting-card {
|
|
||||||
:deep(.el-card__header) {
|
|
||||||
padding: 20px;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.header-left {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-right {
|
|
||||||
.creator-info {
|
|
||||||
font-size: 13px;
|
|
||||||
color: rgb(255 255 255 / 90%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-info {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 20px;
|
|
||||||
padding: 4px 0;
|
|
||||||
|
|
||||||
@media (width <= 1400px) {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 768px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--el-fill-color-light);
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-icon {
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
font-size: 24px;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--el-text-color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-tip {
|
.form-tip {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="single-card-page">
|
<div class="single-card-page">
|
||||||
|
<!-- 占位符,用于在卡片固定时保持布局 -->
|
||||||
|
<div v-if="isSearchCardFixed" ref="searchCardPlaceholder" class="search-card-placeholder"></div>
|
||||||
|
|
||||||
<!-- ICCID查询区域 -->
|
<!-- ICCID查询区域 -->
|
||||||
<ElCard shadow="never" class="search-card" style="margin-bottom: 24px">
|
<ElCard
|
||||||
|
ref="searchCardRef"
|
||||||
|
shadow="never"
|
||||||
|
class="search-card"
|
||||||
|
:class="{ 'is-fixed': isSearchCardFixed }"
|
||||||
|
:style="fixedCardStyle"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>ICCID查询</span>
|
<span>ICCID查询</span>
|
||||||
@@ -35,6 +44,60 @@
|
|||||||
style="margin-left: 16px"
|
style="margin-left: 16px"
|
||||||
>查询</ElButton
|
>查询</ElButton
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<!-- 操作按钮组 -->
|
||||||
|
<div v-if="cardInfo" class="operation-button-group">
|
||||||
|
<ElDropdown trigger="click" @command="handleOperation">
|
||||||
|
<ElButton> 主要操作<i class="el-icon-arrow-down el-icon--right"></i> </ElButton>
|
||||||
|
<template #dropdown>
|
||||||
|
<ElDropdownMenu>
|
||||||
|
<ElDropdownItem command="recharge">套餐充值</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="activate">激活</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="suspend">保号停机</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="resume">保号复机</ElDropdownItem>
|
||||||
|
</ElDropdownMenu>
|
||||||
|
</template>
|
||||||
|
</ElDropdown>
|
||||||
|
|
||||||
|
<ElDropdown trigger="click" @command="handleOperation">
|
||||||
|
<ElButton> 查询记录<i class="el-icon-arrow-down el-icon--right"></i> </ElButton>
|
||||||
|
<template #dropdown>
|
||||||
|
<ElDropdownMenu>
|
||||||
|
<ElDropdownItem command="trafficDetail">流量详单</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="suspendRecord">停复机记录</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="orderHistory">往期订单</ElDropdownItem>
|
||||||
|
</ElDropdownMenu>
|
||||||
|
</template>
|
||||||
|
</ElDropdown>
|
||||||
|
|
||||||
|
<ElDropdown trigger="click" @command="handleOperation">
|
||||||
|
<ElButton> 管理操作<i class="el-icon-arrow-down el-icon--right"></i> </ElButton>
|
||||||
|
<template #dropdown>
|
||||||
|
<ElDropdownMenu>
|
||||||
|
<ElDropdownItem command="rebind">机卡重绑</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="changeExpire">更改过期时间</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="transferCard">转新卡</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="adjustTraffic">增减流量</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="speedLimit">单卡限速</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="instantLimit">即时限速</ElDropdownItem>
|
||||||
|
</ElDropdownMenu>
|
||||||
|
</template>
|
||||||
|
</ElDropdown>
|
||||||
|
|
||||||
|
<ElDropdown trigger="click" @command="handleOperation">
|
||||||
|
<ElButton> 其他操作<i class="el-icon-arrow-down el-icon--right"></i> </ElButton>
|
||||||
|
<template #dropdown>
|
||||||
|
<ElDropdownMenu>
|
||||||
|
<ElDropdownItem command="changeBalance">变更钱包余额</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="resetPassword">重置支付密码</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="renewRecharge">续充</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="deviceOperation">设备操作</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="recoverFromRoaming">窜卡复机</ElDropdownItem>
|
||||||
|
<ElDropdownItem command="roaming">窜卡</ElDropdownItem>
|
||||||
|
</ElDropdownMenu>
|
||||||
|
</template>
|
||||||
|
</ElDropdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
|
|
||||||
@@ -42,47 +105,61 @@
|
|||||||
<div v-if="cardInfo" class="card-content-area slide-in">
|
<div v-if="cardInfo" class="card-content-area slide-in">
|
||||||
<!-- 主要内容区域 -->
|
<!-- 主要内容区域 -->
|
||||||
<div class="main-content-layout">
|
<div class="main-content-layout">
|
||||||
<!-- 第一行:流量统计 -->
|
<!-- 第一行:当前套餐 -->
|
||||||
<div class="row full-width">
|
<div class="row full-width">
|
||||||
<ElCard shadow="never" class="info-card traffic-info">
|
<ElCard shadow="never" class="info-card traffic-info">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>流量统计</span>
|
<span class="header-title">当前套餐: </span>
|
||||||
|
<span class="package-series-value">{{ cardInfo.packageSeries || '--' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- 流量使用情况 -->
|
<!-- 流量使用情况 -->
|
||||||
<div class="traffic-overview horizontal">
|
<div class="traffic-overview">
|
||||||
<!-- 左侧:主要流量指标 -->
|
<!-- 流量进度条 -->
|
||||||
<div class="traffic-left">
|
<div class="traffic-progress-section">
|
||||||
<div class="traffic-stats-grid">
|
<!-- 进度条 -->
|
||||||
<ElCard shadow="never" class="stat-card">
|
<div class="progress-bar-wrapper">
|
||||||
<div class="stat-label">套餐系列</div>
|
<div class="progress-info">
|
||||||
<div class="stat-value">{{ cardInfo.packageSeries || '--' }}</div>
|
<span class="progress-label">流量使用情况</span>
|
||||||
</ElCard>
|
<span class="progress-percentage">{{
|
||||||
<ElCard shadow="never" class="stat-card">
|
cardInfo.usedFlowPercentage || '0.00%'
|
||||||
<div class="stat-label">套餐总流量</div>
|
}}</span>
|
||||||
<div class="stat-value">{{ cardInfo.packageTotalFlow || '--' }}</div>
|
</div>
|
||||||
</ElCard>
|
<ElProgress
|
||||||
<ElCard shadow="never" class="stat-card">
|
:percentage="parseFloat(cardInfo.usedFlowPercentage) || 0"
|
||||||
<div class="stat-label">已使用流量</div>
|
:color="getProgressColor(parseFloat(cardInfo.usedFlowPercentage) || 0)"
|
||||||
<div class="stat-value">{{ cardInfo.usedFlow || '--' }}</div>
|
:stroke-width="20"
|
||||||
</ElCard>
|
:show-text="false"
|
||||||
<ElCard shadow="never" class="stat-card">
|
/>
|
||||||
<div class="stat-label">已使用流量(真)</div>
|
<div class="progress-stats">
|
||||||
<div class="stat-value">{{ cardInfo.realUsedFlow || '--' }}</div>
|
<div class="stat-item">
|
||||||
</ElCard>
|
<span class="stat-label">总流量</span>
|
||||||
<ElCard shadow="never" class="stat-card">
|
<span class="stat-value">{{ cardInfo.packageTotalFlow || '0.00MB' }}</span>
|
||||||
<div class="stat-label">实际流量</div>
|
</div>
|
||||||
<div class="stat-value">{{ cardInfo.actualFlow || '--' }}</div>
|
<div class="stat-item">
|
||||||
</ElCard>
|
<span class="stat-label">已使用</span>
|
||||||
<ElCard shadow="never" class="stat-card">
|
<span class="stat-value used">{{ cardInfo.usedFlow || '0.00MB' }}</span>
|
||||||
<div class="stat-label">剩余流量</div>
|
</div>
|
||||||
<div class="stat-value">{{ cardInfo.remainFlow || '--' }}</div>
|
<div class="stat-item">
|
||||||
</ElCard>
|
<span class="stat-label">剩余</span>
|
||||||
<ElCard shadow="never" class="stat-card">
|
<span class="stat-value remaining">{{
|
||||||
<div class="stat-label">已使用流量百分比</div>
|
cardInfo.remainFlow || '0.00MB'
|
||||||
<div class="stat-value">{{ cardInfo.usedFlowPercentage || '未设置' }}</div>
|
}}</span>
|
||||||
</ElCard>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 额外流量信息 -->
|
||||||
|
<div class="extra-traffic-info">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">已使用流量(真)</span>
|
||||||
|
<span class="value">{{ cardInfo.realUsedFlow || '0.00MB' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="label">实际流量</span>
|
||||||
|
<span class="value">{{ cardInfo.actualFlow || '0.00MB' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -144,11 +221,6 @@
|
|||||||
<ElDescriptionsItem label="运营商实名">{{
|
<ElDescriptionsItem label="运营商实名">{{
|
||||||
cardInfo?.operatorRealName || '--'
|
cardInfo?.operatorRealName || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="国政通实名">
|
|
||||||
<ElTag :type="cardInfo?.realNameAuth ? 'success' : 'danger'" size="small">
|
|
||||||
{{ cardInfo?.realNameAuth ? '是' : '否' }}
|
|
||||||
</ElTag>
|
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="供应商">{{
|
<ElDescriptionsItem label="供应商">{{
|
||||||
cardInfo?.supplier || '--'
|
cardInfo?.supplier || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
@@ -165,19 +237,19 @@
|
|||||||
cardInfo?.walletPasswordStatus || '--'
|
cardInfo?.walletPasswordStatus || '--'
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
<ElDescriptionsItem label="导入时间">{{
|
<ElDescriptionsItem label="导入时间">{{
|
||||||
cardInfo?.importTime || '--'
|
formatDateTime(cardInfo?.importTime)
|
||||||
}}</ElDescriptionsItem>
|
}}</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</ElDescriptions>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第三行:当前套餐 -->
|
<!-- 第三行:套餐列表 -->
|
||||||
<div class="row full-width">
|
<div class="row full-width">
|
||||||
<ElCard shadow="never" class="info-card package-info">
|
<ElCard shadow="never" class="info-card package-info">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>当前套餐</span>
|
<span>套餐列表</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="package-table-wrapper">
|
<div class="package-table-wrapper">
|
||||||
@@ -204,124 +276,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第四行:常规操作 -->
|
|
||||||
<div class="row two-columns">
|
|
||||||
<!-- 左侧操作 -->
|
|
||||||
<div class="col">
|
|
||||||
<ElCard shadow="never" class="info-card operation-card">
|
|
||||||
<div class="operations-grid">
|
|
||||||
<!-- 主要操作 -->
|
|
||||||
<div class="operation-group primary-operations">
|
|
||||||
<h4 class="group-title">主要操作</h4>
|
|
||||||
<div class="operation-buttons">
|
|
||||||
<ElButton
|
|
||||||
@click="handleOperation('recharge')"
|
|
||||||
:loading="operationLoading"
|
|
||||||
class="operation-btn"
|
|
||||||
>
|
|
||||||
套餐充值
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
@click="handleOperation('activate')"
|
|
||||||
:loading="operationLoading"
|
|
||||||
class="operation-btn"
|
|
||||||
>
|
|
||||||
激活
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
@click="handleOperation('suspend')"
|
|
||||||
:loading="operationLoading"
|
|
||||||
class="operation-btn"
|
|
||||||
>
|
|
||||||
保号停机
|
|
||||||
</ElButton>
|
|
||||||
<ElButton
|
|
||||||
@click="handleOperation('resume')"
|
|
||||||
:loading="operationLoading"
|
|
||||||
class="operation-btn"
|
|
||||||
>
|
|
||||||
保号复机
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 管理操作 -->
|
|
||||||
<div class="operation-group management-operations">
|
|
||||||
<h4 class="group-title">管理操作</h4>
|
|
||||||
<div class="operation-buttons">
|
|
||||||
<ElButton @click="handleOperation('rebind')" class="operation-btn"
|
|
||||||
>机卡重绑</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('changeExpire')" class="operation-btn"
|
|
||||||
>更改过期时间</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('transferCard')" class="operation-btn"
|
|
||||||
>转新卡</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('adjustTraffic')" class="operation-btn"
|
|
||||||
>增减流量</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('speedLimit')" class="operation-btn"
|
|
||||||
>单卡限速</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('instantLimit')" class="operation-btn"
|
|
||||||
>即时限速</ElButton
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右侧操作 -->
|
|
||||||
<div class="col">
|
|
||||||
<ElCard shadow="never" class="info-card operation-card">
|
|
||||||
<div class="operations-grid">
|
|
||||||
<!-- 查询操作 -->
|
|
||||||
<div class="operation-group query-operations">
|
|
||||||
<h4 class="group-title">查询记录</h4>
|
|
||||||
<div class="operation-buttons">
|
|
||||||
<ElButton @click="handleOperation('trafficDetail')" class="operation-btn"
|
|
||||||
>流量详单</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('suspendRecord')" class="operation-btn"
|
|
||||||
>停复机记录</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('orderHistory')" class="operation-btn"
|
|
||||||
>往期订单</ElButton
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 其他操作 -->
|
|
||||||
<div class="operation-group other-operations">
|
|
||||||
<h4 class="group-title">其他操作</h4>
|
|
||||||
<div class="operation-buttons">
|
|
||||||
<ElButton @click="handleOperation('changeBalance')" class="operation-btn"
|
|
||||||
>变更钱包余额</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('resetPassword')" class="operation-btn"
|
|
||||||
>重置支付密码</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('renewRecharge')" class="operation-btn"
|
|
||||||
>续充</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('deviceOperation')" class="operation-btn"
|
|
||||||
>设备操作</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('recoverFromRoaming')" class="operation-btn"
|
|
||||||
>窜卡复机</ElButton
|
|
||||||
>
|
|
||||||
<ElButton @click="handleOperation('roaming')" class="operation-btn"
|
|
||||||
>窜卡</ElButton
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -343,17 +297,27 @@
|
|||||||
ElSkeleton,
|
ElSkeleton,
|
||||||
ElDescriptions,
|
ElDescriptions,
|
||||||
ElDescriptionsItem,
|
ElDescriptionsItem,
|
||||||
ElMessageBox
|
ElMessageBox,
|
||||||
|
ElDropdown,
|
||||||
|
ElDropdownMenu,
|
||||||
|
ElDropdownItem
|
||||||
} 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'
|
import { CardService, DeviceService } from '@/api/modules'
|
||||||
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
|
|
||||||
defineOptions({ name: 'SingleCard' })
|
defineOptions({ name: 'SingleCard' })
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const operationLoading = ref(false)
|
const operationLoading = ref(false)
|
||||||
|
const isSearchCardFixed = ref(false)
|
||||||
|
const searchCardRef = ref<HTMLElement | null>(null)
|
||||||
|
const searchCardPlaceholder = ref<HTMLElement | null>(null)
|
||||||
|
const cardOriginalTop = ref(0)
|
||||||
|
const cardLeft = ref(0)
|
||||||
|
const cardWidth = ref(0)
|
||||||
|
|
||||||
// ICCID搜索相关
|
// ICCID搜索相关
|
||||||
const searchIccid = ref('')
|
const searchIccid = ref('')
|
||||||
@@ -438,7 +402,7 @@
|
|||||||
usedFlowPercentage:
|
usedFlowPercentage:
|
||||||
data.data_usage_mb > 0
|
data.data_usage_mb > 0
|
||||||
? `${((data.current_month_usage_mb / data.data_usage_mb) * 100).toFixed(2)}%`
|
? `${((data.current_month_usage_mb / data.data_usage_mb) * 100).toFixed(2)}%`
|
||||||
: '未设置',
|
: '0 %',
|
||||||
realUsedFlow: formatDataSize(data.current_month_usage_mb || 0),
|
realUsedFlow: formatDataSize(data.current_month_usage_mb || 0),
|
||||||
actualFlow: formatDataSize(data.current_month_usage_mb || 0),
|
actualFlow: formatDataSize(data.current_month_usage_mb || 0),
|
||||||
packageList: data.packages || []
|
packageList: data.packages || []
|
||||||
@@ -521,49 +485,143 @@
|
|||||||
// 模拟卡片数据(保留作为参考)
|
// 模拟卡片数据(保留作为参考)
|
||||||
const mockCardData = {
|
const mockCardData = {
|
||||||
id: 1, // 卡片ID
|
id: 1, // 卡片ID
|
||||||
iccid: '8986062357007989203',
|
iccid: '',
|
||||||
accessNumber: '1440012345678',
|
accessNumber: '',
|
||||||
imei: '860123456789012',
|
imei: '',
|
||||||
expireTime: '2025-12-31',
|
expireTime: '',
|
||||||
operator: '中国联通',
|
operator: '',
|
||||||
cardStatus: '正常',
|
cardStatus: '',
|
||||||
cardType: '流量卡',
|
cardType: '',
|
||||||
supplier: '华为技术有限公司',
|
supplier: '',
|
||||||
importTime: '2024-01-15 10:30:00',
|
importTime: '',
|
||||||
phoneBind: '138****5678',
|
phoneBind: '',
|
||||||
trafficPool: '全国流量池',
|
trafficPool: '',
|
||||||
agent: '张丽丽',
|
agent: '',
|
||||||
operatorStatus: '激活',
|
operatorStatus: '',
|
||||||
operatorRealName: '已实名',
|
operatorRealName: '',
|
||||||
walletBalance: '50.00元',
|
walletBalance: '',
|
||||||
walletPasswordStatus: '已设置',
|
walletPasswordStatus: '',
|
||||||
realNameAuth: true,
|
realNameAuth: true,
|
||||||
virtualNumber: '10655****1234',
|
virtualNumber: '',
|
||||||
// 流量信息 - 根据提供的数据更新
|
// 流量信息 - 根据提供的数据更新
|
||||||
packageSeries: 'UFI设备',
|
packageSeries: '',
|
||||||
packageTotalFlow: '3072000MB', // 套餐总流量
|
packageTotalFlow: '', // 套餐总流量
|
||||||
usedFlow: '196.16MB', // 已使用流量
|
usedFlow: '', // 已使用流量
|
||||||
remainFlow: '3071803.84MB', // 剩余流量
|
remainFlow: '', // 剩余流量
|
||||||
usedFlowPercentage: '未设置', // 增加已使用流量百分比
|
usedFlowPercentage: '', // 增加已使用流量百分比
|
||||||
realUsedFlow: '196.16MB', // 已使用流量(真)
|
realUsedFlow: '', // 已使用流量(真)
|
||||||
actualFlow: '196.16MB', // 实际流量
|
actualFlow: '', // 实际流量
|
||||||
packageList: [
|
packageList: []
|
||||||
{
|
|
||||||
packageName: '随意联畅玩年卡套餐(12个月)',
|
|
||||||
packageType: '年卡套餐',
|
|
||||||
totalFlow: '3072000MB',
|
|
||||||
usedFlow: '196.16MB',
|
|
||||||
remainFlow: '3071803.84MB',
|
|
||||||
expireTime: '2026-11-07',
|
|
||||||
status: '正常'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面初始化 - 不自动加载数据,等待用户输入ICCID查询
|
// 页面初始化 - 检查URL参数自动加载
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 不再自动加载模拟数据,等待用户查询
|
autoLoadFromQuery()
|
||||||
|
// 初始化位置信息
|
||||||
|
nextTick(() => {
|
||||||
|
updateCardPosition()
|
||||||
})
|
})
|
||||||
|
window.addEventListener('scroll', handleScroll, true)
|
||||||
|
window.addEventListener('resize', updateCardPosition)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('scroll', handleScroll, true)
|
||||||
|
window.removeEventListener('resize', updateCardPosition)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新卡片位置信息
|
||||||
|
const updateCardPosition = () => {
|
||||||
|
if (searchCardRef.value) {
|
||||||
|
const rect = searchCardRef.value.getBoundingClientRect()
|
||||||
|
cardOriginalTop.value = rect.top + window.scrollY
|
||||||
|
cardLeft.value = rect.left + window.scrollX
|
||||||
|
cardWidth.value = rect.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理滚动事件
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (!searchCardRef.value) return
|
||||||
|
|
||||||
|
const scrollTop = window.scrollY || window.pageYOffset || document.documentElement.scrollTop
|
||||||
|
|
||||||
|
// 判断是否应该固定:当滚动超过卡片原始位置时
|
||||||
|
const shouldBeFixed = scrollTop > cardOriginalTop.value - 20
|
||||||
|
|
||||||
|
// 只在状态变化时更新
|
||||||
|
if (shouldBeFixed !== isSearchCardFixed.value) {
|
||||||
|
if (shouldBeFixed && !isSearchCardFixed.value) {
|
||||||
|
// 在变成固定之前,更新位置信息
|
||||||
|
updateCardPosition()
|
||||||
|
}
|
||||||
|
isSearchCardFixed.value = shouldBeFixed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算固定时的样式
|
||||||
|
const fixedCardStyle = computed(() => {
|
||||||
|
if (isSearchCardFixed.value && cardWidth.value > 0) {
|
||||||
|
return {
|
||||||
|
left: `${cardLeft.value}px`,
|
||||||
|
width: `${cardWidth.value}px`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听路由查询参数变化
|
||||||
|
watch(
|
||||||
|
() => route.query,
|
||||||
|
() => {
|
||||||
|
autoLoadFromQuery()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 自动加载逻辑
|
||||||
|
const autoLoadFromQuery = () => {
|
||||||
|
const iccidFromQuery = route.query.iccid as string
|
||||||
|
const deviceNoFromQuery = route.query.device_no as string
|
||||||
|
|
||||||
|
if (iccidFromQuery) {
|
||||||
|
// 如果有ICCID参数,自动填充并搜索
|
||||||
|
searchIccid.value = iccidFromQuery
|
||||||
|
fetchCardDetailByIccid(iccidFromQuery)
|
||||||
|
} else if (deviceNoFromQuery) {
|
||||||
|
// 如果有设备号参数,先查询设备获取ICCID
|
||||||
|
searchIccid.value = deviceNoFromQuery
|
||||||
|
fetchCardDetailByDeviceNo(deviceNoFromQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据设备号获取卡片详情
|
||||||
|
const fetchCardDetailByDeviceNo = async (deviceNo: string) => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const response = await DeviceService.getDeviceByImei(deviceNo)
|
||||||
|
|
||||||
|
if (response.code === 0 && response.data) {
|
||||||
|
const deviceData = response.data
|
||||||
|
// 如果设备有绑定的ICCID,使用ICCID查询卡片信息
|
||||||
|
if (deviceData.iccid) {
|
||||||
|
searchIccid.value = deviceData.iccid
|
||||||
|
await fetchCardDetailByIccid(deviceData.iccid)
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(`设备 ${deviceNo} 未绑定SIM卡`)
|
||||||
|
cardInfo.value = null
|
||||||
|
}
|
||||||
|
} 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 getStatusType = (status: string) => {
|
const getStatusType = (status: string) => {
|
||||||
@@ -713,20 +771,44 @@
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.single-card-page {
|
.single-card-page {
|
||||||
padding: 20px 0;
|
padding: 20px;
|
||||||
|
|
||||||
|
// 占位符
|
||||||
|
.search-card-placeholder {
|
||||||
|
height: 140px; // 大约等于搜索卡片的高度
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
// ICCID搜索卡片
|
// ICCID搜索卡片
|
||||||
.search-card {
|
.search-card {
|
||||||
position: relative;
|
margin-bottom: 20px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
background: var(--el-bg-color, #fff);
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
|
||||||
|
&.is-fixed {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
z-index: 100;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__header) {
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
:deep(.el-card__body) {
|
||||||
|
padding: 16px 20px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iccid-search {
|
.iccid-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
||||||
.iccid-input-wrapper {
|
.iccid-input-wrapper {
|
||||||
@@ -739,6 +821,7 @@
|
|||||||
top: calc(100% + 12px);
|
top: calc(100% + 12px);
|
||||||
left: 0;
|
left: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
// 三角箭头 - 上边框样式
|
// 三角箭头 - 上边框样式
|
||||||
.magnifier-arrow {
|
.magnifier-arrow {
|
||||||
@@ -772,6 +855,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 操作按钮组
|
||||||
|
.operation-button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
.card-content-area,
|
||||||
|
.empty-state {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动端
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.search-card {
|
||||||
|
top: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
:deep(.el-card__header) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,6 +987,15 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--el-text-color-primary, #1f2937);
|
color: var(--el-text-color-primary, #1f2937);
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-series-value {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #667eea;
|
color: #667eea;
|
||||||
@@ -889,130 +1012,106 @@
|
|||||||
.traffic-info {
|
.traffic-info {
|
||||||
// 流量概览
|
// 流量概览
|
||||||
.traffic-overview {
|
.traffic-overview {
|
||||||
&.horizontal {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
gap: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
// 左侧:流量统计网格
|
// 流量进度条区域
|
||||||
.traffic-left {
|
.traffic-progress-section {
|
||||||
flex: 1;
|
display: flex;
|
||||||
min-width: 0;
|
flex-direction: column;
|
||||||
|
|
||||||
.traffic-stats-grid {
|
.progress-bar-wrapper {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|
||||||
.stat-card {
|
.progress-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
padding: 20px 16px;
|
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
.progress-label {
|
||||||
display: flex;
|
font-size: 15px;
|
||||||
flex-direction: column;
|
font-weight: 500;
|
||||||
align-items: center;
|
color: var(--el-text-color-regular);
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.progress-percentage {
|
||||||
margin-bottom: 8px;
|
font-size: 24px;
|
||||||
font-size: 14px;
|
font-weight: 700;
|
||||||
font-weight: 500;
|
color: var(--el-text-color-primary);
|
||||||
line-height: 1.4;
|
}
|
||||||
color: var(--el-text-color-regular);
|
}
|
||||||
|
|
||||||
|
:deep(.el-progress) {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
border-radius: 6px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.2;
|
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
text-align: center;
|
|
||||||
word-break: break-all;
|
&.used {
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.remaining {
|
||||||
|
color: var(--el-color-success);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 中间:使用率显示
|
// 额外流量信息
|
||||||
.traffic-center {
|
.extra-traffic-info {
|
||||||
flex: 1;
|
display: flex;
|
||||||
min-width: 0;
|
gap: 16px;
|
||||||
|
|
||||||
.usage-display {
|
.info-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
gap: 4px;
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--el-bg-color, #fff);
|
|
||||||
border: 1px solid var(--el-border-color-light);
|
|
||||||
border-radius: 12px;
|
|
||||||
|
|
||||||
.usage-title {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--el-text-color-regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
.usage-chart {
|
|
||||||
.chart-value {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1;
|
|
||||||
color: #667eea;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右侧:套餐信息
|
|
||||||
.traffic-right {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
padding: 12px;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
.package-display {
|
border-radius: 8px;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: var(--el-bg-color, #fff);
|
|
||||||
border: 1px solid var(--el-border-color-light);
|
|
||||||
border-radius: 12px;
|
|
||||||
|
|
||||||
.package-label {
|
.label {
|
||||||
margin-bottom: 8px;
|
font-size: 13px;
|
||||||
font-size: 14px;
|
color: var(--el-text-color-secondary);
|
||||||
font-weight: 500;
|
|
||||||
color: var(--el-text-color-regular);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.package-name {
|
.value {
|
||||||
margin-bottom: 8px;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.4;
|
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.package-actual {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #667eea;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1032,51 +1131,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 操作卡片
|
|
||||||
.operation-card {
|
|
||||||
:deep(.el-card__body) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operations-grid {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.operation-group {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
flex: 1;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-title {
|
|
||||||
padding-bottom: 8px;
|
|
||||||
margin: 0 0 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--el-text-color-primary, #374151);
|
|
||||||
border-bottom: 2px solid var(--el-border-color-light, #e5e7eb);
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
.operation-btn {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载和空状态
|
// 加载和空状态
|
||||||
.loading-state,
|
.loading-state,
|
||||||
.empty-state {
|
.empty-state {
|
||||||
@@ -1115,51 +1169,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.traffic-info .traffic-overview {
|
.traffic-info .traffic-overview {
|
||||||
&.horizontal {
|
.progress-stats {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.traffic-left .traffic-stats-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
padding: 12px 8px;
|
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.extra-traffic-info {
|
||||||
margin-bottom: 6px;
|
flex-direction: column;
|
||||||
font-size: 12px;
|
gap: 12px;
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.traffic-center .usage-display {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
.usage-chart .chart-value {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.traffic-right .package-display {
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
.package-name {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.package-actual {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1169,20 +1186,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 480px) {
|
@media (width <= 480px) {
|
||||||
.traffic-left .traffic-stats-grid {
|
.traffic-info .traffic-overview {
|
||||||
grid-template-columns: 1fr;
|
.progress-percentage {
|
||||||
gap: 6px;
|
font-size: 20px;
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
padding: 10px 6px;
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-stats .stat-item {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,15 +34,21 @@
|
|||||||
:pageSize="pagination.page_size"
|
:pageSize="pagination.page_size"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -311,8 +317,10 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
@@ -323,6 +331,15 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const createLoading = ref(false)
|
const createLoading = ref(false)
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
@@ -1108,4 +1125,8 @@
|
|||||||
width: 140px;
|
width: 140px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -34,15 +34,21 @@
|
|||||||
:pageSize="pagination.page_size"
|
:pageSize="pagination.page_size"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -251,8 +257,10 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
@@ -283,6 +291,15 @@
|
|||||||
const seriesOptions = ref<SeriesSelectOption[]>([])
|
const seriesOptions = ref<SeriesSelectOption[]>([])
|
||||||
const searchSeriesOptions = ref<SeriesSelectOption[]>([])
|
const searchSeriesOptions = ref<SeriesSelectOption[]>([])
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
package_name: '',
|
package_name: '',
|
||||||
@@ -952,6 +969,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.package-list-page {
|
.package-list-page {
|
||||||
// 可以添加特定样式
|
// 可以添加特定样式
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,15 +35,21 @@
|
|||||||
:pageSize="pagination.page_size"
|
:pageSize="pagination.page_size"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 套餐系列操作右键菜单 -->
|
<!-- 套餐系列操作右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="seriesOperationMenuRef"
|
ref="seriesOperationMenuRef"
|
||||||
@@ -245,7 +251,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<ElOption label="仅自己" value="self" />
|
<ElOption label="仅自己" value="self" />
|
||||||
<ElOption label="自己+下级" value="self_and_sub" />
|
<!--<ElOption label="自己+下级" value="self_and_sub" />-->
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -396,8 +402,10 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import {
|
import {
|
||||||
@@ -1112,6 +1120,15 @@
|
|||||||
ElMessage.warning('您没有查看详情的权限')
|
ElMessage.warning('您没有查看详情的权限')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -1122,4 +1139,8 @@
|
|||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -91,13 +91,14 @@
|
|||||||
<ElTableColumn label="统计范围" width="120">
|
<ElTableColumn label="统计范围" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<ElTag size="small" type="warning">
|
<ElTag size="small" type="warning">
|
||||||
{{
|
仅自己
|
||||||
row.stat_scope === 'self'
|
<!--{{-->
|
||||||
? '仅自己'
|
<!-- row.stat_scope === 'self'-->
|
||||||
: row.stat_scope === 'self_and_sub'
|
<!-- ? '仅自己'-->
|
||||||
? '自己+下级'
|
<!-- : row.stat_scope === 'self_and_sub'-->
|
||||||
: '-'
|
<!-- ? '自己+下级'-->
|
||||||
}}
|
<!-- : '-'-->
|
||||||
|
<!--}}-->
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
|
|||||||
@@ -35,15 +35,21 @@
|
|||||||
:pageSize="pagination.page_size"
|
:pageSize="pagination.page_size"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -52,19 +58,181 @@
|
|||||||
@select="handleContextMenuSelect"
|
@select="handleContextMenuSelect"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 套餐列表对话框 -->
|
||||||
|
<ElDialog
|
||||||
|
v-model="packageListDialogVisible"
|
||||||
|
title="套餐列表"
|
||||||
|
width="65%"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@closed="handlePackageListDialogClosed"
|
||||||
|
>
|
||||||
|
<div class="package-list-dialog-content">
|
||||||
|
<!-- 添加授权套餐按钮 -->
|
||||||
|
<div class="package-list-header">
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
@click="showAddPackageDialog"
|
||||||
|
v-permission="'series_grants:manage_packages'"
|
||||||
|
>
|
||||||
|
添加授权套餐
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 套餐列表 -->
|
||||||
|
<ElTable
|
||||||
|
v-if="currentGrantPackages.length > 0"
|
||||||
|
:data="currentGrantPackages"
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
style="margin-top: 12px"
|
||||||
|
>
|
||||||
|
<ElTableColumn prop="package_name" label="套餐名称" />
|
||||||
|
<ElTableColumn prop="package_code" label="套餐编码" />
|
||||||
|
<ElTableColumn label="成本价">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="amount-value">¥{{ (row.cost_price / 100).toFixed(2) }}</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="上架状态" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag v-if="row.shelf_status === 1" type="success" size="small">上架</ElTag>
|
||||||
|
<ElTag v-else-if="row.shelf_status === 2" type="info" size="small">下架</ElTag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag v-if="row.status === 1" type="success" size="small">启用</ElTag>
|
||||||
|
<ElTag v-else-if="row.status === 2" type="danger" size="small">禁用</ElTag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="操作" width="150" align="center" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="showEditPackageDialog(row)"
|
||||||
|
v-permission="'series_grants:edit_packages'"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
link
|
||||||
|
@click="handleDeletePackage(row)"
|
||||||
|
v-permission="'series_grants:delete_packages'"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
<ElEmpty v-else description="暂无套餐" :image-size="80" />
|
||||||
|
</div>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
|
<!-- 添加/编辑套餐对话框 -->
|
||||||
|
<ElDialog
|
||||||
|
v-model="packageDialogVisible"
|
||||||
|
:title="packageDialogType === 'add' ? '添加套餐' : '编辑套餐'"
|
||||||
|
width="500px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@closed="handlePackageDialogClosed"
|
||||||
|
>
|
||||||
|
<ElForm
|
||||||
|
ref="packageFormRef"
|
||||||
|
:model="packageForm"
|
||||||
|
:rules="packageFormRules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<!-- 添加模式:选择套餐 -->
|
||||||
|
<ElFormItem label="选择套餐" prop="package_id" v-if="packageDialogType === 'add'">
|
||||||
|
<ElSelect
|
||||||
|
v-model="packageForm.package_id"
|
||||||
|
placeholder="请选择套餐"
|
||||||
|
style="width: 100%"
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
:remote-method="searchAvailablePackages"
|
||||||
|
:loading="packageLoading"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="availablePackages.length === 0 && !packageLoading && currentGrantSeriesId"
|
||||||
|
>
|
||||||
|
<ElOption disabled value="" label="该系列没有可选套餐" />
|
||||||
|
</template>
|
||||||
|
<ElOption
|
||||||
|
v-for="pkg in availablePackages"
|
||||||
|
:key="pkg.id"
|
||||||
|
:label="`${pkg.package_name} (${pkg.package_code})`"
|
||||||
|
:value="pkg.id"
|
||||||
|
:disabled="isPackageAlreadyAdded(pkg.id)"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 编辑模式:显示套餐信息 -->
|
||||||
|
<ElFormItem label="套餐名称" v-if="packageDialogType === 'edit'">
|
||||||
|
<span>{{ packageForm.package_name }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
<ElFormItem label="套餐编码" v-if="packageDialogType === 'edit'">
|
||||||
|
<span>{{ packageForm.package_code }}</span>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 成本价 -->
|
||||||
|
<ElFormItem label="成本价(元)" prop="cost_price_yuan">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="packageForm.cost_price_yuan"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入成本价"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElForm>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<ElButton @click="packageDialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton type="primary" @click="handleSavePackage" :loading="packageSubmitLoading">
|
||||||
|
保存
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
|
||||||
<!-- 新增/编辑对话框 -->
|
<!-- 新增/编辑对话框 -->
|
||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="dialogType === 'add' ? '新增代理系列授权' : '编辑代理系列授权'"
|
:title="dialogType === 'add' ? '新增代理系列授权' : '编辑代理系列授权'"
|
||||||
width="50%"
|
width="60%"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@closed="handleDialogClosed"
|
@closed="handleDialogClosed"
|
||||||
>
|
>
|
||||||
|
<!-- 新增模式:分步骤表单 -->
|
||||||
|
<template v-if="dialogType === 'add'">
|
||||||
|
<!-- 步骤条 -->
|
||||||
|
<ElSteps
|
||||||
|
:active="currentStep"
|
||||||
|
finish-status="success"
|
||||||
|
align-center
|
||||||
|
style="margin-bottom: 30px"
|
||||||
|
>
|
||||||
|
<ElStep title="基本信息" description="选择系列和店铺" />
|
||||||
|
<ElStep title="佣金和充值配置" description="设置佣金和强制充值" />
|
||||||
|
</ElSteps>
|
||||||
|
|
||||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="130px">
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="130px">
|
||||||
<!-- 新增模式:基本信息 - 2列布局 -->
|
<!-- 第一步:基本信息 -->
|
||||||
<div v-if="dialogType === 'add'">
|
<div v-show="currentStep === 0">
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="24">
|
||||||
<ElFormItem label="选择套餐系列" prop="series_id">
|
<ElFormItem label="选择套餐系列" prop="series_id">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="form.series_id"
|
v-model="form.series_id"
|
||||||
@@ -85,7 +253,10 @@
|
|||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
</ElRow>
|
||||||
|
|
||||||
|
<ElRow :gutter="20" v-if="form.series_id">
|
||||||
|
<ElCol :span="24">
|
||||||
<ElFormItem label="选择店铺" prop="shop_id">
|
<ElFormItem label="选择店铺" prop="shop_id">
|
||||||
<ElTreeSelect
|
<ElTreeSelect
|
||||||
v-model="form.shop_id"
|
v-model="form.shop_id"
|
||||||
@@ -102,10 +273,228 @@
|
|||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
|
|
||||||
|
<!-- 套餐配置 -->
|
||||||
|
<div v-if="form.shop_id" class="form-section-title">
|
||||||
|
<span class="title-text">套餐配置(可选)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template v-if="form.shop_id">
|
||||||
|
<!-- 选择套餐 -->
|
||||||
|
<ElFormItem label="选择套餐">
|
||||||
|
<ElSelect
|
||||||
|
v-model="selectedPackageIds"
|
||||||
|
placeholder="请选择套餐"
|
||||||
|
style="width: 100%"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
:remote-method="searchPackages"
|
||||||
|
:loading="packageLoading"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="packageOptions.length === 0 && !packageLoading && form.series_id"
|
||||||
|
>
|
||||||
|
<ElOption disabled value="" label="该系列没有可选套餐" />
|
||||||
|
</template>
|
||||||
|
<ElOption
|
||||||
|
v-for="pkg in packageOptions"
|
||||||
|
:key="pkg.id"
|
||||||
|
:label="pkg.package_name"
|
||||||
|
:value="pkg.id"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
<div class="form-tip">选择该授权下包含的套餐</div>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
|
<!-- 套餐成本价 -->
|
||||||
|
<ElFormItem label="套餐成本价" v-if="form.packages.length > 0">
|
||||||
|
<div class="package-list">
|
||||||
|
<div
|
||||||
|
v-for="(pkg, index) in form.packages"
|
||||||
|
:key="pkg.package_id"
|
||||||
|
class="package-item"
|
||||||
|
>
|
||||||
|
<span class="package-name">{{ getPackageName(pkg.package_id) }}</span>
|
||||||
|
<div class="cost-price-input-wrapper">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="pkg.cost_price"
|
||||||
|
:min="pkg.original_cost_price || 0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
placeholder="成本价(元)"
|
||||||
|
style="width: 150px"
|
||||||
|
/>
|
||||||
|
<span v-if="pkg.original_cost_price" class="min-cost-hint">
|
||||||
|
(成本价: ¥{{ pkg.original_cost_price.toFixed(2) }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ElButton type="danger" size="small" @click="removePackage(index)"
|
||||||
|
>删除</ElButton
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-tip"
|
||||||
|
>设置每个套餐的成本价(单位:元),不能低于套餐原始成本价</div
|
||||||
|
>
|
||||||
|
</ElFormItem>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第二步:佣金和强制充值配置 -->
|
||||||
|
<div v-show="currentStep === 1">
|
||||||
|
<!-- 一次性佣金配置 -->
|
||||||
|
<div class="form-section-title">
|
||||||
|
<span class="title-text">一次性佣金配置</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 佣金类型和金额 - 2列布局 -->
|
||||||
|
<ElRow :gutter="20">
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="佣金类型">
|
||||||
|
<div class="commission-type-display">
|
||||||
|
<ElTag
|
||||||
|
:type="form.commission_type === 'fixed' ? 'success' : 'warning'"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
{{ form.commission_type === 'fixed' ? '固定佣金' : '梯度佣金' }}
|
||||||
|
</ElTag>
|
||||||
|
<span class="type-hint">(从套餐系列配置继承)</span>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12" v-if="form.commission_type === 'fixed'">
|
||||||
|
<ElFormItem label="佣金金额(元)" prop="one_time_commission_amount">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.one_time_commission_amount"
|
||||||
|
:min="0"
|
||||||
|
:max="form.series_max_commission_amount"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入固定佣金金额(元)"
|
||||||
|
/>
|
||||||
|
<div class="form-tip">
|
||||||
|
该代理能获得的固定佣金金额(单位:元)
|
||||||
|
<span v-if="form.series_max_commission_amount > 0" class="max-amount-hint">
|
||||||
|
<br />
|
||||||
|
该系列最大佣金金额:
|
||||||
|
<span class="amount-value"
|
||||||
|
>¥{{ form.series_max_commission_amount.toFixed(2) }}</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
|
||||||
|
<!-- 梯度佣金配置 -->
|
||||||
|
<template v-if="form.commission_type === 'tiered'">
|
||||||
|
<ElFormItem label="梯度配置" prop="commission_tiers">
|
||||||
|
<ElTable :data="form.commission_tiers" border style="width: 100%">
|
||||||
|
<ElTableColumn label="比较运算符" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag size="small" type="success">{{ row.operator || '>=' }}</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="达标阈值" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="readonly-value">{{ row.threshold }}</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="统计维度" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag size="small" type="info">
|
||||||
|
{{ row.dimension === 'sales_count' ? '销量' : '销售额' }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="统计范围" width="140">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ElTag size="small" type="warning"> 仅自己 </ElTag>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
<ElTableColumn label="佣金金额(元)" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 4px">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="row.amount"
|
||||||
|
:min="0"
|
||||||
|
:max="row.max_amount"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
placeholder="请输入佣金金额"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="row.max_amount"
|
||||||
|
style="font-size: 12px; color: var(--el-text-color-secondary)"
|
||||||
|
>
|
||||||
|
最大: ¥{{ row.max_amount.toFixed(2) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
<div class="form-tip" style="margin-top: 8px">
|
||||||
|
梯度配置从套餐系列继承,达标阈值、统计维度、统计范围为只读,只能修改佣金金额
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 强制充值配置 -->
|
||||||
|
<div class="form-section-title">
|
||||||
|
<span class="title-text">强制充值配置(可选)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 启用强制充值和强充金额 - 2列布局 -->
|
||||||
|
<ElRow :gutter="20">
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="启用强制充值">
|
||||||
|
<ElSwitch v-model="form.enable_force_recharge" />
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12" v-if="form.enable_force_recharge">
|
||||||
|
<ElFormItem label="强充金额(元)" prop="force_recharge_amount">
|
||||||
|
<ElInputNumber
|
||||||
|
v-model="form.force_recharge_amount"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
style="width: 100%"
|
||||||
|
placeholder="请输入强制充值金额(元)"
|
||||||
|
/>
|
||||||
|
<div class="form-tip">
|
||||||
|
用户需要达到的强制充值金额
|
||||||
|
<span
|
||||||
|
v-if="form.series_name && form.series_force_recharge_amount > 0"
|
||||||
|
class="series-force-hint"
|
||||||
|
>
|
||||||
|
<br />
|
||||||
|
可参考
|
||||||
|
<span class="amount-value">{{ form.series_name }}</span
|
||||||
|
>系列强充金额:
|
||||||
|
<span class="amount-value"
|
||||||
|
>¥{{ form.series_force_recharge_amount.toFixed(2) }}</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
</div>
|
||||||
|
</ElForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 编辑模式:原有的表单布局 -->
|
||||||
|
<ElForm v-else ref="formRef" :model="form" :rules="rules" label-width="130px">
|
||||||
<!-- 编辑模式:显示只读信息 -->
|
<!-- 编辑模式:显示只读信息 -->
|
||||||
<div v-if="dialogType === 'edit'" class="info-row">
|
<div class="info-row">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">系列名称:</span>
|
<span class="info-label">系列名称:</span>
|
||||||
<span class="info-value">{{ form.series_name || '-' }}</span>
|
<span class="info-value">{{ form.series_name || '-' }}</span>
|
||||||
@@ -189,15 +578,7 @@
|
|||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="统计范围" width="140">
|
<ElTableColumn label="统计范围" width="140">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<ElTag size="small" type="warning">
|
<ElTag size="small" type="warning"> 仅自己 </ElTag>
|
||||||
{{
|
|
||||||
row.stat_scope === 'self'
|
|
||||||
? '仅自己'
|
|
||||||
: row.stat_scope === 'self_and_sub'
|
|
||||||
? '自己+下级'
|
|
||||||
: '-'
|
|
||||||
}}
|
|
||||||
</ElTag>
|
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="佣金金额(元)" min-width="180">
|
<ElTableColumn label="佣金金额(元)" min-width="180">
|
||||||
@@ -271,13 +652,13 @@
|
|||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
|
|
||||||
<!-- 套餐配置 -->
|
<!-- 套餐配置(编辑模式下禁用) -->
|
||||||
<div class="form-section-title">
|
<!-- <div class="form-section-title">
|
||||||
<span class="title-text">套餐配置(可选)</span>
|
<span class="title-text">套餐配置(可选)</span>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- 选择套餐 -->
|
<!-- 编辑模式下不显示套餐配置区域,请使用右键菜单中的"添加授权套餐"功能 -->
|
||||||
<ElFormItem label="选择套餐">
|
<!-- <ElFormItem label="选择套餐">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="selectedPackageIds"
|
v-model="selectedPackageIds"
|
||||||
placeholder="请选择套餐"
|
placeholder="请选择套餐"
|
||||||
@@ -300,10 +681,10 @@
|
|||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
<div class="form-tip">选择该授权下包含的套餐</div>
|
<div class="form-tip">选择该授权下包含的套餐</div>
|
||||||
</ElFormItem>
|
</ElFormItem> -->
|
||||||
|
|
||||||
<!-- 套餐成本价 -->
|
<!-- 套餐成本价 -->
|
||||||
<ElFormItem label="套餐成本价" v-if="form.packages.length > 0">
|
<!-- <ElFormItem label="套餐成本价" v-if="form.packages.length > 0">
|
||||||
<div class="package-list">
|
<div class="package-list">
|
||||||
<div
|
<div
|
||||||
v-for="(pkg, index) in form.packages"
|
v-for="(pkg, index) in form.packages"
|
||||||
@@ -329,14 +710,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-tip">设置每个套餐的成本价(单位:元),不能低于套餐原始成本价</div>
|
<div class="form-tip">设置每个套餐的成本价(单位:元),不能低于套餐原始成本价</div>
|
||||||
</ElFormItem>
|
</ElFormItem> -->
|
||||||
</ElForm>
|
</ElForm>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
|
<template v-if="dialogType === 'add'">
|
||||||
|
<ElButton v-if="currentStep > 0" @click="handlePrevStep">上一步</ElButton>
|
||||||
|
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||||
|
<ElButton v-if="currentStep < 1" type="primary" @click="handleNextStep"
|
||||||
|
>下一步</ElButton
|
||||||
|
>
|
||||||
|
<ElButton
|
||||||
|
v-else
|
||||||
|
type="primary"
|
||||||
|
@click="handleSubmit(formRef)"
|
||||||
|
:loading="submitLoading"
|
||||||
|
>
|
||||||
|
提交
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||||
<ElButton type="primary" @click="handleSubmit(formRef)" :loading="submitLoading">
|
<ElButton type="primary" @click="handleSubmit(formRef)" :loading="submitLoading">
|
||||||
提交
|
提交
|
||||||
</ElButton>
|
</ElButton>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
@@ -346,7 +745,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import { h, ref, reactive, computed, watch, onMounted, nextTick } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
ShopSeriesGrantService,
|
ShopSeriesGrantService,
|
||||||
@@ -354,20 +753,34 @@
|
|||||||
ShopService,
|
ShopService,
|
||||||
PackageManageService
|
PackageManageService
|
||||||
} from '@/api/modules'
|
} from '@/api/modules'
|
||||||
import { ElMessage, ElMessageBox, ElSwitch, ElTag, ElRow, ElCol } from 'element-plus'
|
import {
|
||||||
|
ElMessage,
|
||||||
|
ElMessageBox,
|
||||||
|
ElSwitch,
|
||||||
|
ElTag,
|
||||||
|
ElRow,
|
||||||
|
ElCol,
|
||||||
|
ElSteps,
|
||||||
|
ElStep,
|
||||||
|
ElEmpty
|
||||||
|
} from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import type {
|
import type {
|
||||||
ShopSeriesGrantResponse,
|
ShopSeriesGrantResponse,
|
||||||
PackageSeriesResponse,
|
PackageSeriesResponse,
|
||||||
ShopResponse,
|
ShopResponse,
|
||||||
PackageResponse,
|
PackageResponse,
|
||||||
CommissionTier
|
CommissionTier,
|
||||||
|
GrantPackageItem,
|
||||||
|
GrantPackageInfo
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import {
|
import {
|
||||||
@@ -392,6 +805,42 @@
|
|||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
const currentRow = ref<ShopSeriesGrantResponse | null>(null)
|
const currentRow = ref<ShopSeriesGrantResponse | null>(null)
|
||||||
|
const currentStep = ref(0) // 当前步骤,0 为第一步,1 为第二步
|
||||||
|
|
||||||
|
// 套餐列表对话框相关
|
||||||
|
const packageListDialogVisible = ref(false)
|
||||||
|
const currentGrantId = ref<number>(0)
|
||||||
|
const currentGrantSeriesId = ref<number>(0)
|
||||||
|
const currentGrantPackages = ref<GrantPackageInfo[]>([])
|
||||||
|
|
||||||
|
// 套餐管理相关
|
||||||
|
const packageDialogVisible = ref(false)
|
||||||
|
const packageDialogType = ref<'add' | 'edit'>('add')
|
||||||
|
const packageSubmitLoading = ref(false)
|
||||||
|
const availablePackages = ref<PackageResponse[]>([])
|
||||||
|
const packageFormRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
// 套餐表单
|
||||||
|
const packageForm = ref<{
|
||||||
|
package_id?: number
|
||||||
|
package_name?: string
|
||||||
|
package_code?: string
|
||||||
|
cost_price_yuan: number
|
||||||
|
}>({
|
||||||
|
cost_price_yuan: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 套餐表单验证规则
|
||||||
|
const packageFormRules = computed<FormRules>(() => ({
|
||||||
|
package_id: [
|
||||||
|
{ required: packageDialogType.value === 'add', message: '请选择套餐', trigger: 'change' }
|
||||||
|
],
|
||||||
|
cost_price_yuan: [
|
||||||
|
{ required: true, message: '请输入成本价', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 0, message: '成本价不能小于0', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
|
||||||
const seriesOptions = ref<PackageSeriesResponse[]>([])
|
const seriesOptions = ref<PackageSeriesResponse[]>([])
|
||||||
const shopOptions = ref<ShopResponse[]>([])
|
const shopOptions = ref<ShopResponse[]>([])
|
||||||
const shopTreeData = ref<ShopResponse[]>([])
|
const shopTreeData = ref<ShopResponse[]>([])
|
||||||
@@ -1277,6 +1726,8 @@
|
|||||||
const handleDialogClosed = () => {
|
const handleDialogClosed = () => {
|
||||||
// 清除表单验证状态
|
// 清除表单验证状态
|
||||||
formRef.value?.clearValidate()
|
formRef.value?.clearValidate()
|
||||||
|
// 重置步骤
|
||||||
|
currentStep.value = 0
|
||||||
// 重置表单数据
|
// 重置表单数据
|
||||||
form.id = 0
|
form.id = 0
|
||||||
form.series_id = undefined
|
form.series_id = undefined
|
||||||
@@ -1414,6 +1865,10 @@
|
|||||||
const contextMenuItems = computed((): MenuItemType[] => {
|
const contextMenuItems = computed((): MenuItemType[] => {
|
||||||
const items: MenuItemType[] = []
|
const items: MenuItemType[] = []
|
||||||
|
|
||||||
|
if (hasAuth('series_grants:detail')) {
|
||||||
|
items.push({ key: 'view_packages', label: '查看套餐列表' })
|
||||||
|
}
|
||||||
|
|
||||||
if (hasAuth('series_grants:edit')) {
|
if (hasAuth('series_grants:edit')) {
|
||||||
items.push({ key: 'edit', label: '编辑' })
|
items.push({ key: 'edit', label: '编辑' })
|
||||||
}
|
}
|
||||||
@@ -1438,6 +1893,9 @@
|
|||||||
if (!currentRow.value) return
|
if (!currentRow.value) return
|
||||||
|
|
||||||
switch (item.key) {
|
switch (item.key) {
|
||||||
|
case 'view_packages':
|
||||||
|
showPackageListDialog(currentRow.value)
|
||||||
|
break
|
||||||
case 'edit':
|
case 'edit':
|
||||||
showDialog('edit', currentRow.value)
|
showDialog('edit', currentRow.value)
|
||||||
break
|
break
|
||||||
@@ -1446,6 +1904,242 @@
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示套餐列表对话框
|
||||||
|
const showPackageListDialog = async (row: ShopSeriesGrantResponse) => {
|
||||||
|
currentGrantId.value = row.id
|
||||||
|
currentGrantSeriesId.value = row.series_id
|
||||||
|
|
||||||
|
// 获取该授权的详细信息,包括套餐列表
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await ShopSeriesGrantService.getShopSeriesGrantDetail(row.id)
|
||||||
|
if (res.code === 0) {
|
||||||
|
currentGrantPackages.value = res.data.packages || []
|
||||||
|
packageListDialogVisible.value = true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取套餐列表失败:', error)
|
||||||
|
ElMessage.error('获取套餐列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示添加套餐对话框
|
||||||
|
const showAddPackageDialog = () => {
|
||||||
|
packageDialogType.value = 'add'
|
||||||
|
packageForm.value = {
|
||||||
|
package_id: undefined,
|
||||||
|
cost_price_yuan: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载可用套餐
|
||||||
|
if (currentGrantSeriesId.value) {
|
||||||
|
loadAvailablePackages()
|
||||||
|
}
|
||||||
|
|
||||||
|
packageDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示编辑套餐对话框
|
||||||
|
const showEditPackageDialog = (row: GrantPackageInfo) => {
|
||||||
|
packageDialogType.value = 'edit'
|
||||||
|
packageForm.value = {
|
||||||
|
package_id: row.package_id,
|
||||||
|
package_name: row.package_name,
|
||||||
|
package_code: row.package_code,
|
||||||
|
cost_price_yuan: row.cost_price / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
packageDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载可用套餐
|
||||||
|
const loadAvailablePackages = async (packageName?: string) => {
|
||||||
|
if (!currentGrantSeriesId.value) return
|
||||||
|
|
||||||
|
packageLoading.value = true
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
page: 1,
|
||||||
|
page_size: 50,
|
||||||
|
series_id: currentGrantSeriesId.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageName) {
|
||||||
|
params.package_name = packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await PackageManageService.getPackages(params)
|
||||||
|
if (res.code === 0) {
|
||||||
|
availablePackages.value = res.data.items
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载套餐选项失败:', error)
|
||||||
|
} finally {
|
||||||
|
packageLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索可用套餐
|
||||||
|
const searchAvailablePackages = (query: string) => {
|
||||||
|
if (query) {
|
||||||
|
loadAvailablePackages(query)
|
||||||
|
} else {
|
||||||
|
loadAvailablePackages()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查套餐是否已添加
|
||||||
|
const isPackageAlreadyAdded = (packageId: number) => {
|
||||||
|
return currentGrantPackages.value.some((p) => p.package_id === packageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存套餐
|
||||||
|
const handleSavePackage = async () => {
|
||||||
|
if (!packageFormRef.value || !currentGrantId.value) return
|
||||||
|
|
||||||
|
await packageFormRef.value.validate(async (valid) => {
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
packageSubmitLoading.value = true
|
||||||
|
try {
|
||||||
|
const packages: GrantPackageItem[] = []
|
||||||
|
|
||||||
|
if (packageDialogType.value === 'add') {
|
||||||
|
// 添加模式:添加新套餐
|
||||||
|
packages.push({
|
||||||
|
package_id: packageForm.value.package_id,
|
||||||
|
cost_price: Math.round(packageForm.value.cost_price_yuan * 100)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 编辑模式:更新套餐成本价
|
||||||
|
packages.push({
|
||||||
|
package_id: packageForm.value.package_id,
|
||||||
|
cost_price: Math.round(packageForm.value.cost_price_yuan * 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await ShopSeriesGrantService.manageGrantPackages(currentGrantId.value, { packages })
|
||||||
|
ElMessage.success(packageDialogType.value === 'add' ? '添加成功' : '更新成功')
|
||||||
|
packageDialogVisible.value = false
|
||||||
|
|
||||||
|
// 刷新套餐列表
|
||||||
|
await refreshPackageList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error(packageDialogType.value === 'add' ? '添加失败' : '更新失败')
|
||||||
|
} finally {
|
||||||
|
packageSubmitLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除套餐
|
||||||
|
const handleDeletePackage = (row: GrantPackageInfo) => {
|
||||||
|
ElMessageBox.confirm(`确定删除套餐 ${row.package_name} 的授权吗?`, '删除确认', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
if (!currentGrantId.value) return
|
||||||
|
|
||||||
|
packageSubmitLoading.value = true
|
||||||
|
try {
|
||||||
|
const packages: GrantPackageItem[] = [
|
||||||
|
{
|
||||||
|
package_id: row.package_id,
|
||||||
|
remove: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
await ShopSeriesGrantService.manageGrantPackages(currentGrantId.value, { packages })
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
|
||||||
|
// 刷新套餐列表
|
||||||
|
await refreshPackageList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
} finally {
|
||||||
|
packageSubmitLoading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// 用户取消
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新套餐列表
|
||||||
|
const refreshPackageList = async () => {
|
||||||
|
if (!currentGrantId.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await ShopSeriesGrantService.getShopSeriesGrantDetail(currentGrantId.value)
|
||||||
|
if (res.code === 0) {
|
||||||
|
currentGrantPackages.value = res.data.packages || []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('刷新套餐列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭套餐对话框
|
||||||
|
const handlePackageDialogClosed = () => {
|
||||||
|
packageFormRef.value?.resetFields()
|
||||||
|
packageForm.value = {
|
||||||
|
cost_price_yuan: 0
|
||||||
|
}
|
||||||
|
availablePackages.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭套餐列表对话框
|
||||||
|
const handlePackageListDialogClosed = () => {
|
||||||
|
currentGrantId.value = 0
|
||||||
|
currentGrantSeriesId.value = 0
|
||||||
|
currentGrantPackages.value = []
|
||||||
|
availablePackages.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
|
// 下一步
|
||||||
|
const handleNextStep = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
// 根据当前步骤验证不同的字段
|
||||||
|
let fieldsToValidate: string[] = []
|
||||||
|
|
||||||
|
if (currentStep.value === 0) {
|
||||||
|
// 第一步:验证系列和店铺选择
|
||||||
|
fieldsToValidate = ['series_id', 'shop_id']
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 验证当前步骤的字段
|
||||||
|
await formRef.value.validateField(fieldsToValidate)
|
||||||
|
// 验证通过,进入下一步
|
||||||
|
currentStep.value++
|
||||||
|
} catch (error) {
|
||||||
|
console.error('表单验证失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上一步
|
||||||
|
const handlePrevStep = () => {
|
||||||
|
if (currentStep.value > 0) {
|
||||||
|
currentStep.value--
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -1453,6 +2147,10 @@
|
|||||||
// 可以添加特定样式
|
// 可以添加特定样式
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -1595,4 +2293,17 @@
|
|||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.package-list-dialog-content {
|
||||||
|
.package-list-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -34,16 +34,22 @@
|
|||||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||||
:default-expand-all="false"
|
:default-expand-all="false"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 新增/编辑对话框 -->
|
<!-- 新增/编辑对话框 -->
|
||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
@@ -297,8 +303,10 @@
|
|||||||
import type { FormRules } from 'element-plus'
|
import type { FormRules } from 'element-plus'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { ShopService, RoleService } from '@/api/modules'
|
import { ShopService, RoleService } from '@/api/modules'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
@@ -313,6 +321,15 @@
|
|||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
const dialogType = ref('add')
|
const dialogType = ref('add')
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -1118,4 +1135,8 @@
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -31,12 +31,27 @@
|
|||||||
:default-expand-all="false"
|
:default-expand-all="false"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
:show-pagination="false"
|
:show-pagination="false"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
|
<!-- 右键菜单 -->
|
||||||
|
<ArtMenuRight
|
||||||
|
ref="contextMenuRef"
|
||||||
|
:menu-items="contextMenuItems"
|
||||||
|
:menu-width="120"
|
||||||
|
@select="handleContextMenuSelect"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 新增/编辑对话框 -->
|
<!-- 新增/编辑对话框 -->
|
||||||
<ElDialog
|
<ElDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
@@ -127,7 +142,11 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import {
|
import {
|
||||||
PermissionType,
|
PermissionType,
|
||||||
PERMISSION_TYPE_OPTIONS,
|
PERMISSION_TYPE_OPTIONS,
|
||||||
@@ -191,10 +210,8 @@
|
|||||||
{ label: '权限类型', prop: 'perm_type' },
|
{ label: '权限类型', prop: 'perm_type' },
|
||||||
{ label: '菜单路径', prop: 'url' },
|
{ label: '菜单路径', prop: 'url' },
|
||||||
{ label: '适用端口', prop: 'platform' },
|
{ label: '适用端口', prop: 'platform' },
|
||||||
// { label: '可用角色类型', prop: 'available_for_role_types' },
|
|
||||||
{ label: '状态', prop: 'status' },
|
{ label: '状态', prop: 'status' },
|
||||||
{ label: '排序', prop: 'sort' },
|
{ label: '排序', prop: 'sort' }
|
||||||
{ label: '操作', prop: 'operation' }
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// 权限列表(树形结构)
|
// 权限列表(树形结构)
|
||||||
@@ -208,6 +225,16 @@
|
|||||||
const currentRow = ref<PermissionTreeNode | null>(null)
|
const currentRow = ref<PermissionTreeNode | null>(null)
|
||||||
const currentPermissionId = ref<number>(0)
|
const currentPermissionId = ref<number>(0)
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
|
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
// 表单引用和数据
|
// 表单引用和数据
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
@@ -315,35 +342,6 @@
|
|||||||
prop: 'sort',
|
prop: 'sort',
|
||||||
label: '排序',
|
label: '排序',
|
||||||
width: 80
|
width: 80
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'operation',
|
|
||||||
label: '操作',
|
|
||||||
width: 120,
|
|
||||||
fixed: 'right',
|
|
||||||
formatter: (row: PermissionTreeNode) => {
|
|
||||||
const buttons = []
|
|
||||||
|
|
||||||
if (hasAuth('permission:edit')) {
|
|
||||||
buttons.push(
|
|
||||||
h(ArtButtonTable, {
|
|
||||||
type: 'edit',
|
|
||||||
onClick: () => showDialog('edit', row)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAuth('permission:delete')) {
|
|
||||||
buttons.push(
|
|
||||||
h(ArtButtonTable, {
|
|
||||||
type: 'delete',
|
|
||||||
onClick: () => deletePermission(row)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return h('div', { style: 'display: flex; gap: 8px;' }, buttons)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -557,4 +555,47 @@
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getPermissionList()
|
getPermissionList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 右键菜单项配置
|
||||||
|
const contextMenuItems = computed((): MenuItemType[] => {
|
||||||
|
const items: MenuItemType[] = []
|
||||||
|
|
||||||
|
if (hasAuth('permission:edit')) {
|
||||||
|
items.push({ key: 'edit', label: '编辑' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAuth('permission:delete')) {
|
||||||
|
items.push({ key: 'delete', label: '删除' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理表格行右键菜单
|
||||||
|
const handleRowContextMenu = (row: PermissionTreeNode, column: any, event: MouseEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
currentRow.value = row
|
||||||
|
contextMenuRef.value?.show(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理右键菜单选择
|
||||||
|
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||||
|
if (!currentRow.value) return
|
||||||
|
|
||||||
|
switch (item.key) {
|
||||||
|
case 'edit':
|
||||||
|
showDialog('edit', currentRow.value)
|
||||||
|
break
|
||||||
|
case 'delete':
|
||||||
|
deletePermission(currentRow.value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -32,15 +32,21 @@
|
|||||||
:pageSize="pagination.pageSize"
|
:pageSize="pagination.pageSize"
|
||||||
:total="pagination.total"
|
:total="pagination.total"
|
||||||
:marginTop="10"
|
:marginTop="10"
|
||||||
|
:row-class-name="getRowClassName"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
@cell-mouse-enter="handleCellMouseEnter"
|
||||||
|
@cell-mouse-leave="handleCellMouseLeave"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<ArtMenuRight
|
<ArtMenuRight
|
||||||
ref="contextMenuRef"
|
ref="contextMenuRef"
|
||||||
@@ -234,8 +240,10 @@
|
|||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { CommonStatus, getStatusText } from '@/config/constants'
|
import { CommonStatus, getStatusText } from '@/config/constants'
|
||||||
@@ -267,6 +275,15 @@
|
|||||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||||
const currentRow = ref<PlatformRole | null>(null)
|
const currentRow = ref<PlatformRole | null>(null)
|
||||||
|
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()
|
||||||
|
|
||||||
// 搜索表单初始值
|
// 搜索表单初始值
|
||||||
const initialSearchState = {
|
const initialSearchState = {
|
||||||
role_name: '',
|
role_name: '',
|
||||||
@@ -1056,6 +1073,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
220
update_context_menu.py
Normal file
220
update_context_menu.py
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
批量为表格页面添加右键菜单和悬浮提示功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
def add_imports(content):
|
||||||
|
"""添加必要的导入"""
|
||||||
|
# 检查是否已经导入
|
||||||
|
if 'useTableContextMenu' in content:
|
||||||
|
return content
|
||||||
|
|
||||||
|
# 找到 useAuth 导入位置
|
||||||
|
import_pattern = r"(import\s+{\s+useAuth\s+}\s+from\s+'@/composables/useAuth')"
|
||||||
|
|
||||||
|
if re.search(import_pattern, content):
|
||||||
|
# 在 useAuth 后面添加 useTableContextMenu
|
||||||
|
content = re.sub(
|
||||||
|
import_pattern,
|
||||||
|
r"\1\n import { useTableContextMenu } from '@/composables/useTableContextMenu'",
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加 TableContextMenuHint 组件导入
|
||||||
|
arttable_pattern = r"(import\s+ArtButtonTable\s+from\s+'@/components/core/forms/ArtButtonTable\.vue')"
|
||||||
|
if re.search(arttable_pattern, content):
|
||||||
|
content = re.sub(
|
||||||
|
arttable_pattern,
|
||||||
|
r"\1\n import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'",
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
# 如果没有 ArtMenuRight,添加它
|
||||||
|
if 'ArtMenuRight' not in content:
|
||||||
|
content = re.sub(
|
||||||
|
arttable_pattern,
|
||||||
|
r"\1\n import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'\n import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'",
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def add_context_menu_usage(content):
|
||||||
|
"""添加 useTableContextMenu 的使用"""
|
||||||
|
if 'useTableContextMenu()' in content:
|
||||||
|
return content
|
||||||
|
|
||||||
|
# 查找 const currentRow = ref 或类似的变量声明
|
||||||
|
pattern = r"(const\s+currentRow\s*=\s*ref[^\n]+)"
|
||||||
|
|
||||||
|
usage_code = """
|
||||||
|
// 使用表格右键菜单功能
|
||||||
|
const {
|
||||||
|
showContextMenuHint,
|
||||||
|
hintPosition,
|
||||||
|
getRowClassName,
|
||||||
|
handleCellMouseEnter,
|
||||||
|
handleCellMouseLeave
|
||||||
|
} = useTableContextMenu()"""
|
||||||
|
|
||||||
|
if re.search(pattern, content):
|
||||||
|
content = re.sub(pattern, r"\1" + usage_code, content)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def add_table_events(content):
|
||||||
|
"""为 ArtTable 添加事件监听"""
|
||||||
|
# 查找 ArtTable 标签
|
||||||
|
table_pattern = r"(<ArtTable[^>]*?)(\s*@row-contextmenu=\"[^\"]+\")?([^>]*>)"
|
||||||
|
|
||||||
|
if '@cell-mouse-enter' in content:
|
||||||
|
return content
|
||||||
|
|
||||||
|
# 添加必要的属性和事件
|
||||||
|
def replace_table(match):
|
||||||
|
prefix = match.group(1)
|
||||||
|
existing_contextmenu = match.group(2) or ''
|
||||||
|
suffix = match.group(3)
|
||||||
|
|
||||||
|
# 如果没有 row-class-name,添加它
|
||||||
|
if ':row-class-name' not in prefix and 'row-class-name' not in prefix:
|
||||||
|
prefix += '\n :row-class-name="getRowClassName"'
|
||||||
|
|
||||||
|
# 如果没有 row-contextmenu,添加它
|
||||||
|
if not existing_contextmenu and '@row-contextmenu' not in prefix:
|
||||||
|
existing_contextmenu = '\n @row-contextmenu="handleRowContextMenu"'
|
||||||
|
|
||||||
|
# 添加 cell mouse 事件
|
||||||
|
cell_events = '\n @cell-mouse-enter="handleCellMouseEnter"\n @cell-mouse-leave="handleCellMouseLeave"'
|
||||||
|
|
||||||
|
return prefix + existing_contextmenu + cell_events + suffix
|
||||||
|
|
||||||
|
content = re.sub(table_pattern, replace_table, content, flags=re.DOTALL)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def add_hint_component(content):
|
||||||
|
"""添加悬浮提示组件"""
|
||||||
|
if 'TableContextMenuHint' in content and ':visible="showContextMenuHint"' in content:
|
||||||
|
return content
|
||||||
|
|
||||||
|
# 在 </ArtTable> 后添加提示组件
|
||||||
|
table_end_pattern = r"(</ArtTable>)"
|
||||||
|
|
||||||
|
hint_component = r"""\1
|
||||||
|
|
||||||
|
<!-- 鼠标悬浮提示 -->
|
||||||
|
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />"""
|
||||||
|
|
||||||
|
content = re.sub(table_end_pattern, hint_component, content)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def add_context_menu_component(content):
|
||||||
|
"""添加右键菜单组件"""
|
||||||
|
if 'ArtMenuRight' in content and 'contextMenuRef' in content:
|
||||||
|
return content
|
||||||
|
|
||||||
|
# 在提示组件后添加右键菜单
|
||||||
|
hint_pattern = r"(<TableContextMenuHint[^>]+/>)"
|
||||||
|
|
||||||
|
menu_component = r"""\1
|
||||||
|
|
||||||
|
<!-- 右键菜单 -->
|
||||||
|
<ArtMenuRight
|
||||||
|
ref="contextMenuRef"
|
||||||
|
:menu-items="contextMenuItems"
|
||||||
|
:menu-width="120"
|
||||||
|
@select="handleContextMenuSelect"
|
||||||
|
/>"""
|
||||||
|
|
||||||
|
content = re.sub(hint_pattern, menu_component, content)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def add_css_styles(content):
|
||||||
|
"""添加 CSS 样式"""
|
||||||
|
if 'table-row-with-context-menu' in content:
|
||||||
|
return content
|
||||||
|
|
||||||
|
# 查找 <style> 标签
|
||||||
|
style_pattern = r"(<style[^>]*>)"
|
||||||
|
|
||||||
|
css_code = r"""\1
|
||||||
|
:deep(.el-table__row.table-row-with-context-menu) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
content = re.sub(style_pattern, css_code, content)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def process_file(file_path):
|
||||||
|
"""处理单个文件"""
|
||||||
|
print(f"Processing: {file_path}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
original_content = content
|
||||||
|
|
||||||
|
# 执行所有转换
|
||||||
|
content = add_imports(content)
|
||||||
|
content = add_context_menu_usage(content)
|
||||||
|
content = add_table_events(content)
|
||||||
|
content = add_hint_component(content)
|
||||||
|
content = add_context_menu_component(content)
|
||||||
|
content = add_css_styles(content)
|
||||||
|
|
||||||
|
# 如果内容有变化,写回文件
|
||||||
|
if content != original_content:
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f" ✓ Updated")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f" - No changes needed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ Error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
# 定义需要处理的文件列表
|
||||||
|
files_to_process = [
|
||||||
|
"src/views/package-management/package-list/index.vue",
|
||||||
|
"src/views/account-management/account/index.vue",
|
||||||
|
"src/views/account-management/enterprise-customer/index.vue",
|
||||||
|
"src/views/account-management/enterprise-cards/index.vue",
|
||||||
|
"src/views/asset-management/iot-card-management/index.vue",
|
||||||
|
"src/views/asset-management/iot-card-task/index.vue",
|
||||||
|
"src/views/asset-management/device-task/index.vue",
|
||||||
|
"src/views/asset-management/asset-assign/index.vue",
|
||||||
|
"src/views/asset-management/authorization-records/index.vue",
|
||||||
|
"src/views/finance/commission/agent-commission/index.vue",
|
||||||
|
]
|
||||||
|
|
||||||
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
updated_count = 0
|
||||||
|
for file_rel_path in files_to_process:
|
||||||
|
file_path = os.path.join(base_dir, file_rel_path)
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
if process_file(file_path):
|
||||||
|
updated_count += 1
|
||||||
|
else:
|
||||||
|
print(f"File not found: {file_path}")
|
||||||
|
|
||||||
|
print(f"\n完成! 更新了 {updated_count} 个文件")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user