支持后台转移发布所有权
This commit is contained in:
@@ -1,29 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { readError } from '@/shared/utils/error'
|
||||
import { CopyDocument } from '@element-plus/icons-vue'
|
||||
import { CopyDocument, Search } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { fetchAdminUsers, type AdminUserItem } from '@/features/admin/api/adminUsers'
|
||||
import { fetchAdminFileBlob } from '@/shared/api/files'
|
||||
import { adminPath } from '@/shared/utils/adminPath'
|
||||
import { formatCentWithSymbol } from '@/shared/utils/money'
|
||||
import {
|
||||
adminMarkListingAbnormal,
|
||||
adminOfflineListing,
|
||||
adminTransferListingOwner,
|
||||
fetchAdminListing,
|
||||
type Listing,
|
||||
} from '@/features/listings'
|
||||
import { listingReviewStatusLabel, listingStatusLabel } from '@/shared/utils/statusLabels'
|
||||
import {
|
||||
listingReviewStatusLabel,
|
||||
listingStatusLabel,
|
||||
realnameStatusLabel,
|
||||
userStatusLabel,
|
||||
} from '@/shared/utils/statusLabels'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { formatListingCode } from '@/shared/utils/listingDisplay'
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const userSearching = ref(false)
|
||||
const listing = ref<Listing | null>(null)
|
||||
const actionType = ref<'offline' | 'abnormal' | ''>('')
|
||||
const reason = ref('')
|
||||
const transferVisible = ref(false)
|
||||
const transferReason = ref('')
|
||||
const userKeyword = ref('')
|
||||
const userOptions = ref<AdminUserItem[]>([])
|
||||
const selectedUser = ref<AdminUserItem | null>(null)
|
||||
|
||||
const actionTitle = computed(() =>
|
||||
actionType.value === 'offline' ? '强制下架商品' : '标记商品异常'
|
||||
@@ -34,6 +47,10 @@ const canOperate = computed(
|
||||
listing.value.status !== 'rented' &&
|
||||
!['offline', 'abnormal'].includes(listing.value.status)
|
||||
)
|
||||
const canTransfer = computed(() => !!listing.value && listing.value.status !== 'rented')
|
||||
const selectedUserAvailable = computed(
|
||||
() => selectedUser.value?.status === 'active' && selectedUser.value.realname_status === 'verified'
|
||||
)
|
||||
|
||||
onMounted(loadListing)
|
||||
|
||||
@@ -61,6 +78,33 @@ function openAction(type: 'offline' | 'abnormal') {
|
||||
reason.value = ''
|
||||
}
|
||||
|
||||
function openTransfer() {
|
||||
transferVisible.value = true
|
||||
transferReason.value = ''
|
||||
userKeyword.value = ''
|
||||
selectedUser.value = null
|
||||
userOptions.value = []
|
||||
}
|
||||
|
||||
async function searchUsers() {
|
||||
userSearching.value = true
|
||||
try {
|
||||
const result = await fetchAdminUsers(1, 8, {
|
||||
keyword: userKeyword.value.trim(),
|
||||
status: 'active',
|
||||
})
|
||||
userOptions.value = result.items.filter(user => user.realname_status === 'verified')
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '查询用户失败'))
|
||||
} finally {
|
||||
userSearching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function selectUser(user: AdminUserItem) {
|
||||
selectedUser.value = user
|
||||
}
|
||||
|
||||
async function submitAction() {
|
||||
if (!listing.value || !actionType.value) return
|
||||
submitting.value = true
|
||||
@@ -80,6 +124,32 @@ async function submitAction() {
|
||||
}
|
||||
}
|
||||
|
||||
async function submitTransferOwner() {
|
||||
if (!listing.value || !selectedUser.value) return
|
||||
if (!selectedUserAvailable.value) {
|
||||
ElMessage.warning('只能转移给正常且已实名的用户')
|
||||
return
|
||||
}
|
||||
if (!transferReason.value.trim()) {
|
||||
ElMessage.warning('请填写转移原因')
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
listing.value = await adminTransferListingOwner(
|
||||
listing.value.id,
|
||||
selectedUser.value.id,
|
||||
transferReason.value.trim()
|
||||
)
|
||||
ElMessage.success('所有权已转移')
|
||||
transferVisible.value = false
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '转移失败'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function moneyCent(value: number) {
|
||||
return formatCentWithSymbol(value)
|
||||
}
|
||||
@@ -120,6 +190,9 @@ async function openScreenshot(url: string) {
|
||||
<RouterLink :to="adminPath('listings')">
|
||||
<el-button>返回列表</el-button>
|
||||
</RouterLink>
|
||||
<el-button type="primary" plain :disabled="!canTransfer" @click="openTransfer"
|
||||
>转移所有权</el-button
|
||||
>
|
||||
<el-button type="warning" :disabled="!canOperate" @click="openAction('offline')"
|
||||
>强制下架</el-button
|
||||
>
|
||||
@@ -211,5 +284,147 @@ async function openScreenshot(url: string) {
|
||||
<el-button type="danger" :loading="submitting" @click="submitAction">确认操作</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="transferVisible" title="转移发布所有权" width="640px">
|
||||
<div v-if="listing" class="dialog-body transfer-dialog">
|
||||
<div class="transfer-current">
|
||||
<span>当前号主</span>
|
||||
<strong>{{ listing.owner_phone || listing.owner_nickname || listing.owner_id }}</strong>
|
||||
<small>ID:{{ listing.owner_id }}</small>
|
||||
</div>
|
||||
|
||||
<el-input
|
||||
v-model="userKeyword"
|
||||
clearable
|
||||
placeholder="搜索手机号 / 昵称 / 用户ID"
|
||||
@keyup.enter="searchUsers"
|
||||
@clear="searchUsers"
|
||||
>
|
||||
<template #append>
|
||||
<el-button :icon="Search" :loading="userSearching" @click="searchUsers">查询</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<div class="transfer-user-list">
|
||||
<button
|
||||
v-for="user in userOptions"
|
||||
:key="user.id"
|
||||
class="transfer-user"
|
||||
:class="{ 'is-selected': selectedUser?.id === user.id }"
|
||||
type="button"
|
||||
@click="selectUser(user)"
|
||||
>
|
||||
<span>
|
||||
<strong>{{ user.nickname || user.phone || `用户${user.id}` }}</strong>
|
||||
<small>{{ user.phone || '-' }}</small>
|
||||
</span>
|
||||
<span class="transfer-user-meta">
|
||||
<el-tag size="small" type="success">{{ userStatusLabel(user.status) }}</el-tag>
|
||||
<el-tag size="small">{{ realnameStatusLabel(user.realname_status) }}</el-tag>
|
||||
<small>ID:{{ user.id }}</small>
|
||||
</span>
|
||||
</button>
|
||||
<el-empty
|
||||
v-if="!userOptions.length"
|
||||
description="输入关键词后查询已实名用户"
|
||||
:image-size="72"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-input
|
||||
v-model="transferReason"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="填写转移原因,会写入审计日志"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="transferVisible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
:disabled="!selectedUser || !selectedUserAvailable"
|
||||
@click="submitTransferOwner"
|
||||
>
|
||||
确认转移
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.transfer-dialog {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.transfer-current {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.transfer-current strong {
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.transfer-current small {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.transfer-user-list {
|
||||
min-height: 120px;
|
||||
max-height: 260px;
|
||||
overflow: auto;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.transfer-user {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 12px 14px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
background: #fff;
|
||||
color: #334155;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.transfer-user:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.transfer-user:hover,
|
||||
.transfer-user.is-selected {
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.transfer-user strong {
|
||||
display: block;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.transfer-user small {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.transfer-user-meta {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user