feat(ui): 为工单对话页新增用户与订单跳转入口

在工单工作台对话页为当前工单用户增加“查看用户”
和“用户订单”入口,支持直接跳转到用户管理与订单管理

用户管理页新增 `user_id/user_email` 路由作用域,
进入后按用户 ID 精准筛选,并支持在重置筛选时清除
该作用域

同步更新 admin-frontend 模块文档、变更归档与测试环境
compose 配置
This commit is contained in:
yinjianm
2026-05-01 19:07:31 +08:00
parent f70dbe4378
commit 77140864ef
11 changed files with 404 additions and 33 deletions
@@ -1,7 +1,8 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ChatLineRound, DataAnalysis, Picture, Search } from '@element-plus/icons-vue'
import { ChatLineRound, DataAnalysis, Picture, Search, Tickets, User } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
import { closeTicket, fetchTickets, getTicketById, replyTicket } from '@/api/admin'
import type { AdminTicketDetail, AdminTicketListItem } from '@/types/api'
import { formatDateTime } from '@/utils/dashboard'
@@ -25,6 +26,7 @@ const emit = defineEmits<{
updated: []
}>()
const router = useRouter()
const loadingSidebar = ref(false)
const loadingDetail = ref(false)
const replying = ref(false)
@@ -138,6 +140,34 @@ async function handleCloseTicket() {
}
}
async function openTicketUser() {
if (!detail.value?.user?.id) {
return
}
await router.push({
name: 'Users',
query: {
user_id: String(detail.value.user.id),
user_email: detail.value.user.email,
},
})
}
async function openTicketUserOrders() {
if (!detail.value?.user?.id) {
return
}
await router.push({
name: 'SubscriptionOrders',
query: {
user_id: String(detail.value.user.id),
user_email: detail.value.user.email,
},
})
}
function closeDialog() {
resetReplyDragState()
emit('update:visible', false)
@@ -189,6 +219,24 @@ watch(
</div>
<div class="workspace-header__actions">
<ElButton
v-if="detail?.user?.id"
text
class="ghost-action"
@click="openTicketUser"
>
<ElIcon><User /></ElIcon>
查看用户
</ElButton>
<ElButton
v-if="detail?.user?.id"
text
class="ghost-action"
@click="openTicketUserOrders"
>
<ElIcon><Tickets /></ElIcon>
用户订单
</ElButton>
<ElButton
v-if="detail?.user?.id"
text
@@ -15,6 +15,19 @@ export function useUserScopedActions() {
const trafficLogUserEmail = ref('')
const resettingTrafficId = ref<number | null>(null)
const scopedUserId = computed(() => {
const raw = route.query.user_id
const value = Array.isArray(raw) ? raw[0] : raw
const numeric = Number(value)
return Number.isFinite(numeric) && numeric > 0 ? numeric : null
})
const scopedUserEmail = computed(() => {
const raw = route.query.user_email
const value = Array.isArray(raw) ? raw[0] : raw
return typeof value === 'string' ? value : ''
})
const scopedInviteUserId = computed(() => {
const raw = route.query.invite_user_id
const value = Array.isArray(raw) ? raw[0] : raw
@@ -34,6 +47,21 @@ export function useUserScopedActions() {
: []
))
const scopedUserFilters = computed<AdminUserFilter[]>(() => (
scopedUserId.value
? [{ id: 'id', value: `eq:${scopedUserId.value}` }]
: []
))
const scopedUserSummaries = computed(() => {
if (!scopedUserId.value) {
return []
}
const label = scopedUserEmail.value || `用户 #${scopedUserId.value}`
return [`用户:${label}`]
})
const scopedInviteSummaries = computed(() => {
if (!scopedInviteUserId.value) {
return []
@@ -43,6 +71,17 @@ export function useUserScopedActions() {
return [`邀请人:${label}`]
})
function clearScopedUserQuery() {
if (!scopedUserId.value && !scopedUserEmail.value) {
return Promise.resolve()
}
const nextQuery = { ...route.query }
delete nextQuery.user_id
delete nextQuery.user_email
return router.replace({ name: 'Users', query: nextQuery })
}
function clearScopedInviteQuery() {
if (!scopedInviteUserId.value && !scopedInviteUserEmail.value) {
return Promise.resolve()
@@ -116,9 +155,13 @@ export function useUserScopedActions() {
trafficLogUserId,
trafficLogUserEmail,
resettingTrafficId,
scopedUserId,
scopedUserFilters,
scopedUserSummaries,
scopedInviteUserId,
scopedInviteFilters,
scopedInviteSummaries,
clearScopedUserQuery,
clearScopedInviteQuery,
openAssignOrder,
handleAssignOrderSuccess,
@@ -66,6 +66,7 @@ export function useUsersManagement() {
planFilter.value,
advancedFilters.value,
),
...scopedActions.scopedUserFilters.value,
...scopedActions.scopedInviteFilters.value,
])
@@ -75,7 +76,10 @@ export function useUsersManagement() {
planFilter.value,
advancedFilters.value,
plans.value,
).concat(scopedActions.scopedInviteSummaries.value))
).concat(
scopedActions.scopedUserSummaries.value,
scopedActions.scopedInviteSummaries.value,
))
const batchActions = useUsersBatchActions({
loading,
@@ -148,9 +152,11 @@ export function useUsersManagement() {
statusFilter.value = 'all'
planFilter.value = 'all'
advancedFilters.value = []
void scopedActions.clearScopedInviteQuery().finally(() => {
refreshUsers(true)
})
void scopedActions.clearScopedUserQuery()
.then(() => scopedActions.clearScopedInviteQuery())
.finally(() => {
refreshUsers(true)
})
}
function clearAdvancedFilters() {
@@ -280,6 +286,8 @@ export function useUsersManagement() {
watch(
() => [
scopedActions.route.query.user_id,
scopedActions.route.query.user_email,
scopedActions.route.query.invite_user_id,
scopedActions.route.query.invite_user_email,
],