优化路由逻辑,根据用户类型动态重定向到不同的默认页面,增强登录后的用户体验;更新退单管理界面,改进搜索功能和响应式设计,提升移动端用户体验。
This commit is contained in:
@@ -177,3 +177,4 @@ src/
|
|||||||
---
|
---
|
||||||
|
|
||||||
*最后更新时间: 2024年1月*
|
*最后更新时间: 2024年1月*
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,20 @@ export const routes = [
|
|||||||
path: '/',
|
path: '/',
|
||||||
component: AdminLayout,
|
component: AdminLayout,
|
||||||
children: [
|
children: [
|
||||||
{ path: '', redirect: '/users' },
|
{
|
||||||
|
path: '',
|
||||||
|
redirect: (to) => {
|
||||||
|
// 根据用户类型重定向到不同的默认页面
|
||||||
|
const { getCurrentUserType } = require('@/utils/permission')
|
||||||
|
const userType = getCurrentUserType()
|
||||||
|
|
||||||
|
if (userType?.toLowerCase() === 'agent') {
|
||||||
|
return '/links' // 代理商跳转到链接管理
|
||||||
|
} else {
|
||||||
|
return '/users' // 管理员跳转到用户管理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{ path: 'users', name: 'Users', component: UserList, meta: { title: '用户管理' } },
|
{ path: 'users', name: 'Users', component: UserList, meta: { title: '用户管理' } },
|
||||||
{ path: 'settings', name: 'Settings', component: Settings, meta: { title: '系统设置' } },
|
{ path: 'settings', name: 'Settings', component: Settings, meta: { title: '系统设置' } },
|
||||||
{ path: 'links', name: 'Links', component: LinkGenerate, meta: { title: '链接管理' } },
|
{ path: 'links', name: 'Links', component: LinkGenerate, meta: { title: '链接管理' } },
|
||||||
@@ -44,7 +57,17 @@ router.beforeEach((to, from, next) => {
|
|||||||
|
|
||||||
// 检查路由权限
|
// 检查路由权限
|
||||||
if (to.name && !canAccessRoute(to.name)) {
|
if (to.name && !canAccessRoute(to.name)) {
|
||||||
return next({ name: 'Users' }) // 无权限时跳转到用户管理
|
// 根据用户类型跳转到有权限的默认页面
|
||||||
|
const { getCurrentUserType } = require('@/utils/permission')
|
||||||
|
const userType = getCurrentUserType()
|
||||||
|
|
||||||
|
if (userType?.toLowerCase() === 'agent') {
|
||||||
|
// 代理商跳转到链接管理页面
|
||||||
|
return next({ name: 'Links' })
|
||||||
|
} else {
|
||||||
|
// 管理员或其他用户跳转到用户管理
|
||||||
|
return next({ name: 'Users' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next()
|
next()
|
||||||
|
|||||||
@@ -127,8 +127,19 @@ async function onSubmit() {
|
|||||||
showSuccessMessage('登录成功')
|
showSuccessMessage('登录成功')
|
||||||
persistRemember()
|
persistRemember()
|
||||||
console.debug('login response:', res.data)
|
console.debug('login response:', res.data)
|
||||||
const redirect = route.query.redirect || '/'
|
|
||||||
router.replace(String(redirect))
|
// 如果有 redirect 参数,直接跳转
|
||||||
|
if (route.query.redirect) {
|
||||||
|
router.replace(String(route.query.redirect))
|
||||||
|
} else {
|
||||||
|
// 根据用户类型跳转到合适的默认页面
|
||||||
|
const userType = data?.userType?.toLowerCase()
|
||||||
|
if (userType === 'agent') {
|
||||||
|
router.replace('/links') // 代理商跳转到链接管理
|
||||||
|
} else {
|
||||||
|
router.replace('/users') // 管理员跳转到用户管理
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(e, '登录失败')
|
showErrorMessage(e, '登录失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -5,47 +5,74 @@
|
|||||||
<p class="page-description">管理用户的退单申请,支持按链接编号查询和执行退单操作</p>
|
<p class="page-description">管理用户的退单申请,支持按链接编号查询和执行退单操作</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 搜索区域 -->
|
<!-- 搜索区域(响应式) -->
|
||||||
<div class="search-section">
|
<div class="search-section">
|
||||||
<el-card>
|
<el-card>
|
||||||
<el-row :gutter="20">
|
<el-form :model="searchForm" @submit.prevent>
|
||||||
<el-col :span="8">
|
<el-row :gutter="12">
|
||||||
<el-input
|
<!-- 链接编号:手机端独占一行,桌面端占较宽比例 -->
|
||||||
v-model="searchForm.codeNo"
|
<el-col :xs="24" :sm="12" :md="10" :lg="10">
|
||||||
placeholder="请输入链接编号"
|
<div class="code-input-wrap">
|
||||||
clearable
|
<el-input
|
||||||
@keyup.enter="handleSearch"
|
v-model="searchForm.codeNo"
|
||||||
>
|
size="large"
|
||||||
<template #prepend>链接编号</template>
|
clearable
|
||||||
</el-input>
|
placeholder="请输入链接编号(支持粘贴后回车)"
|
||||||
</el-col>
|
@keyup.enter="handleSearch"
|
||||||
<el-col :span="8">
|
>
|
||||||
<el-select
|
<template #prefix>
|
||||||
v-model="searchForm.status"
|
<el-icon><Link /></el-icon>
|
||||||
placeholder="选择状态"
|
</template>
|
||||||
clearable
|
</el-input>
|
||||||
style="width: 100%"
|
<el-tooltip content="从剪贴板粘贴" placement="top">
|
||||||
>
|
<el-button
|
||||||
<el-option label="全部状态" value="" />
|
class="paste-btn"
|
||||||
<el-option label="新建" value="NEW" />
|
:icon="DocumentCopy"
|
||||||
<el-option label="使用中" value="USING" />
|
size="large"
|
||||||
<el-option label="已登录" value="LOGGED_IN" />
|
@click="pasteFromClipboard"
|
||||||
<el-option label="已完成" value="COMPLETED" />
|
/>
|
||||||
<el-option label="已退单" value="REFUNDED" />
|
</el-tooltip>
|
||||||
<el-option label="已过期" value="EXPIRED" />
|
</div>
|
||||||
</el-select>
|
</el-col>
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
<!-- 状态筛选:可选,主要用于查看信息时做个提示筛选(不影响后端查询) -->
|
||||||
<el-button type="primary" @click="handleSearch" :loading="loading">
|
<el-col :xs="24" :sm="8" :md="8" :lg="6">
|
||||||
<el-icon><Search /></el-icon>
|
<el-select
|
||||||
查询
|
v-model="searchForm.status"
|
||||||
</el-button>
|
size="large"
|
||||||
<el-button @click="handleReset">
|
placeholder="选择状态"
|
||||||
<el-icon><Refresh /></el-icon>
|
clearable
|
||||||
重置
|
style="width: 100%"
|
||||||
</el-button>
|
>
|
||||||
</el-col>
|
<el-option label="全部状态" value="" />
|
||||||
</el-row>
|
<el-option label="新建" value="NEW" />
|
||||||
|
<el-option label="使用中" value="USING" />
|
||||||
|
<el-option label="已登录" value="LOGGED_IN" />
|
||||||
|
<el-option label="已完成" value="COMPLETED" />
|
||||||
|
<el-option label="已退单" value="REFUNDED" />
|
||||||
|
<el-option label="已过期" value="EXPIRED" />
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<!-- 按钮区:手机端纵向铺满 -->
|
||||||
|
<el-col :xs="24" :sm="4" :md="6" :lg="8">
|
||||||
|
<div class="btn-group">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="handleSearch"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="!searchForm.codeNo.trim()"
|
||||||
|
>
|
||||||
|
<el-icon class="mr-6"><Search /></el-icon> 查询
|
||||||
|
</el-button>
|
||||||
|
<el-button size="large" @click="handleReset">
|
||||||
|
<el-icon class="mr-6"><Refresh /></el-icon> 重置
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -54,7 +81,20 @@
|
|||||||
<el-card>
|
<el-card>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span>链接信息</span>
|
<div class="code-and-status">
|
||||||
|
<span>链接信息</span>
|
||||||
|
<span class="code-chip">
|
||||||
|
<span class="mono">{{ linkInfo.codeNo }}</span>
|
||||||
|
<el-tooltip content="复制链接编号" placement="top">
|
||||||
|
<el-button
|
||||||
|
:icon="DocumentCopy"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
@click="copyCodeNo(linkInfo.codeNo)"
|
||||||
|
/>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<el-tag :type="getStatusTagType(linkInfo.status)">
|
<el-tag :type="getStatusTagType(linkInfo.status)">
|
||||||
{{ getStatusText(linkInfo.status) }}
|
{{ getStatusText(linkInfo.status) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
@@ -62,7 +102,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-descriptions :column="2" border>
|
<el-descriptions :column="2" border>
|
||||||
<el-descriptions-item label="链接编号">{{ linkInfo.codeNo }}</el-descriptions-item>
|
<el-descriptions-item label="链接编号">
|
||||||
|
<span class="mono">{{ linkInfo.codeNo }}</span>
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="当前状态">
|
<el-descriptions-item label="当前状态">
|
||||||
<el-tag :type="getStatusTagType(linkInfo.status)">
|
<el-tag :type="getStatusTagType(linkInfo.status)">
|
||||||
{{ getStatusText(linkInfo.status) }}
|
{{ getStatusText(linkInfo.status) }}
|
||||||
@@ -70,10 +112,14 @@
|
|||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="创建时间">{{ formatDateTime(linkInfo.createdAt) }}</el-descriptions-item>
|
<el-descriptions-item label="创建时间">{{ formatDateTime(linkInfo.createdAt) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="更新时间">{{ formatDateTime(linkInfo.updatedAt) }}</el-descriptions-item>
|
<el-descriptions-item label="更新时间">{{ formatDateTime(linkInfo.updatedAt) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="代理ID">{{ linkInfo.agentId || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="代理ID">
|
||||||
<el-descriptions-item label="关联设备">{{ linkInfo.machineId || '-' }}</el-descriptions-item>
|
<span class="mono">{{ linkInfo.agentId || '-' }}</span>
|
||||||
<el-descriptions-item label="总点数">{{ linkInfo.totalPoints || '-' }}</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="当前点数">{{ linkInfo.currentPoints || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="关联设备">
|
||||||
|
<span class="mono">{{ linkInfo.machineId || '-' }}</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="总点数">{{ linkInfo.totalPoints ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="当前点数">{{ linkInfo.currentPoints ?? '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item v-if="linkInfo.refundAt" label="退单时间">
|
<el-descriptions-item v-if="linkInfo.refundAt" label="退单时间">
|
||||||
{{ formatDateTime(linkInfo.refundAt) }}
|
{{ formatDateTime(linkInfo.refundAt) }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
@@ -87,24 +133,13 @@
|
|||||||
@click="handleRefund"
|
@click="handleRefund"
|
||||||
:loading="refunding"
|
:loading="refunding"
|
||||||
>
|
>
|
||||||
<el-icon><RefreshLeft /></el-icon>
|
<el-icon class="mr-6"><RefreshLeft /></el-icon> 执行退单
|
||||||
执行退单
|
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button v-else-if="linkInfo.status === 'REFUNDED'" disabled type="info">
|
||||||
v-else-if="linkInfo.status === 'REFUNDED'"
|
<el-icon class="mr-6"><Check /></el-icon> 已退单
|
||||||
disabled
|
|
||||||
type="info"
|
|
||||||
>
|
|
||||||
<el-icon><Check /></el-icon>
|
|
||||||
已退单
|
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button v-else disabled type="info">
|
||||||
v-else
|
<el-icon class="mr-6"><Warning /></el-icon> 当前状态不允许退单
|
||||||
disabled
|
|
||||||
type="info"
|
|
||||||
>
|
|
||||||
<el-icon><Warning /></el-icon>
|
|
||||||
当前状态不允许退单
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -162,18 +197,22 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 退单确认对话框 -->
|
<!-- 退单确认对话框(移动端全屏) -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="refundDialogVisible"
|
v-model="refundDialogVisible"
|
||||||
title="确认退单"
|
title="确认退单"
|
||||||
width="500px"
|
:width="isMobile ? '96vw' : '500px'"
|
||||||
|
:fullscreen="isMobile"
|
||||||
|
:append-to-body="true"
|
||||||
|
:close-on-click-modal="false"
|
||||||
:before-close="handleRefundDialogClose"
|
:before-close="handleRefundDialogClose"
|
||||||
|
top="8vh"
|
||||||
>
|
>
|
||||||
<div class="refund-confirm">
|
<div class="refund-confirm">
|
||||||
<el-icon class="warning-icon"><WarningFilled /></el-icon>
|
<el-icon class="warning-icon"><WarningFilled /></el-icon>
|
||||||
<div class="confirm-content">
|
<div class="confirm-content">
|
||||||
<h3>确认要对以下链接执行退单操作吗?</h3>
|
<h3>确认要对以下链接执行退单操作吗?</h3>
|
||||||
<p class="link-code">链接编号:<strong>{{ linkInfo?.codeNo }}</strong></p>
|
<p class="link-code">链接编号:<strong class="mono">{{ linkInfo?.codeNo }}</strong></p>
|
||||||
<p class="warning-text">
|
<p class="warning-text">
|
||||||
<el-icon><Warning /></el-icon>
|
<el-icon><Warning /></el-icon>
|
||||||
退单操作不可逆,执行后链接将无法继续使用
|
退单操作不可逆,执行后链接将无法继续使用
|
||||||
@@ -183,22 +222,25 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="refundDialogVisible = false">取消</el-button>
|
<el-button @click="refundDialogVisible = false">取消</el-button>
|
||||||
<el-button
|
<el-button type="danger" @click="confirmRefund" :loading="refunding">确认退单</el-button>
|
||||||
type="danger"
|
|
||||||
@click="confirmRefund"
|
|
||||||
:loading="refunding"
|
|
||||||
>
|
|
||||||
确认退单
|
|
||||||
</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { Search, Refresh, RefreshLeft, Check, Warning, WarningFilled } from '@element-plus/icons-vue'
|
import {
|
||||||
|
Search,
|
||||||
|
Refresh,
|
||||||
|
RefreshLeft,
|
||||||
|
Check,
|
||||||
|
Warning,
|
||||||
|
WarningFilled,
|
||||||
|
Link,
|
||||||
|
DocumentCopy
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
import { getLinkStatus, refundLink } from '@/api/links'
|
import { getLinkStatus, refundLink } from '@/api/links'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -209,92 +251,135 @@ export default {
|
|||||||
RefreshLeft,
|
RefreshLeft,
|
||||||
Check,
|
Check,
|
||||||
Warning,
|
Warning,
|
||||||
WarningFilled
|
WarningFilled,
|
||||||
|
Link,
|
||||||
|
DocumentCopy
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
// 响应式数据
|
// 视口与状态
|
||||||
|
const isMobile = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const refunding = ref(false)
|
const refunding = ref(false)
|
||||||
const refundDialogVisible = ref(false)
|
const refundDialogVisible = ref(false)
|
||||||
const linkInfo = ref(null)
|
const linkInfo = ref(null)
|
||||||
|
|
||||||
|
// 表单
|
||||||
const searchForm = reactive({
|
const searchForm = reactive({
|
||||||
codeNo: '',
|
codeNo: '',
|
||||||
status: ''
|
status: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 查询链接信息
|
// 自适应
|
||||||
|
const updateIsMobile = () => {
|
||||||
|
isMobile.value = window.innerWidth <= 768
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
updateIsMobile()
|
||||||
|
window.addEventListener('resize', updateIsMobile)
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', updateIsMobile)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 粘贴剪贴板
|
||||||
|
const pasteFromClipboard = async () => {
|
||||||
|
try {
|
||||||
|
if (!navigator.clipboard) {
|
||||||
|
ElMessage.warning('当前环境不支持剪贴板读取')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const text = await navigator.clipboard.readText()
|
||||||
|
if (!text) {
|
||||||
|
ElMessage.info('剪贴板为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
searchForm.codeNo = text.trim()
|
||||||
|
// 自动触发查询(可按需注释)
|
||||||
|
if (searchForm.codeNo) handleSearch()
|
||||||
|
} catch (err) {
|
||||||
|
ElMessage.error('读取剪贴板失败,请手动粘贴')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制编号
|
||||||
|
const copyCodeNo = async (code) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(code || '')
|
||||||
|
ElMessage.success('已复制链接编号')
|
||||||
|
} catch {
|
||||||
|
ElMessage.error('复制失败,请手动选择复制')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
if (!searchForm.codeNo.trim()) {
|
if (!searchForm.codeNo.trim()) {
|
||||||
ElMessage.warning('请输入链接编号')
|
ElMessage.warning('请输入链接编号')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await getLinkStatus(searchForm.codeNo)
|
const response = await getLinkStatus(searchForm.codeNo.trim())
|
||||||
linkInfo.value = response.data
|
linkInfo.value = response.data
|
||||||
|
|
||||||
|
// 如果选择了状态筛选,仅做前端提示
|
||||||
|
if (linkInfo.value && searchForm.status && linkInfo.value.status !== searchForm.status) {
|
||||||
|
ElMessage.info(`已找到链接,但状态为「${getStatusText(linkInfo.value.status)}」`)
|
||||||
|
}
|
||||||
|
|
||||||
if (!linkInfo.value) {
|
if (!linkInfo.value) {
|
||||||
ElMessage.warning('未找到相关链接信息')
|
ElMessage.warning('未找到相关链接信息')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('查询链接失败:', error)
|
console.error('查询链接失败:', error)
|
||||||
linkInfo.value = null
|
linkInfo.value = null
|
||||||
|
if (error?.response?.status === 404) {
|
||||||
// 根据错误状态显示不同消息
|
|
||||||
if (error.response?.status === 404) {
|
|
||||||
ElMessage.error('链接不存在')
|
ElMessage.error('链接不存在')
|
||||||
} else if (error.response?.status === 403) {
|
} else if (error?.response?.status === 403) {
|
||||||
ElMessage.error('无权限查看此链接')
|
ElMessage.error('无权限查看此链接')
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error('查询失败,请稍后重试')
|
ElMessage.error(error?.response?.data?.message || '查询失败,请稍后重试')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置搜索
|
// 重置
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
searchForm.codeNo = ''
|
searchForm.codeNo = ''
|
||||||
searchForm.status = ''
|
searchForm.status = ''
|
||||||
linkInfo.value = null
|
linkInfo.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否可以退单
|
// 资格判断
|
||||||
const canRefund = (status) => {
|
const canRefund = (status) => ['NEW', 'USING', 'LOGGED_IN'].includes(status)
|
||||||
return ['NEW', 'USING', 'LOGGED_IN'].includes(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理退单按钮点击
|
// 打开确认弹窗
|
||||||
const handleRefund = () => {
|
const handleRefund = () => {
|
||||||
if (!linkInfo.value) return
|
if (!linkInfo.value) return
|
||||||
|
|
||||||
refundDialogVisible.value = true
|
refundDialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认退单
|
// 确认退单
|
||||||
const confirmRefund = async () => {
|
const confirmRefund = async () => {
|
||||||
if (!linkInfo.value) return
|
if (!linkInfo.value) return
|
||||||
|
|
||||||
refunding.value = true
|
refunding.value = true
|
||||||
try {
|
try {
|
||||||
await refundLink(linkInfo.value.codeNo)
|
await refundLink(linkInfo.value.codeNo)
|
||||||
|
|
||||||
// 更新本地状态
|
// 更新本地状态
|
||||||
linkInfo.value.status = 'REFUNDED'
|
const nowISO = new Date().toISOString()
|
||||||
linkInfo.value.refundAt = new Date().toISOString()
|
linkInfo.value = {
|
||||||
linkInfo.value.updatedAt = new Date().toISOString()
|
...linkInfo.value,
|
||||||
|
status: 'REFUNDED',
|
||||||
|
refundAt: nowISO,
|
||||||
|
updatedAt: nowISO
|
||||||
|
}
|
||||||
refundDialogVisible.value = false
|
refundDialogVisible.value = false
|
||||||
ElMessage.success('退单操作成功')
|
ElMessage.success('退单操作成功')
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('退单失败:', error)
|
console.error('退单失败:', error)
|
||||||
|
if (error?.response?.status === 400) {
|
||||||
// 根据错误状态显示不同消息
|
|
||||||
if (error.response?.status === 400) {
|
|
||||||
const errorCode = error.response.data?.code
|
const errorCode = error.response.data?.code
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
case 'LINK_003':
|
case 'LINK_003':
|
||||||
@@ -309,53 +394,50 @@ export default {
|
|||||||
default:
|
default:
|
||||||
ElMessage.error(error.response.data?.message || '退单失败')
|
ElMessage.error(error.response.data?.message || '退单失败')
|
||||||
}
|
}
|
||||||
} else if (error.response?.status === 403) {
|
} else if (error?.response?.status === 403) {
|
||||||
ElMessage.error('无权限操作此链接')
|
ElMessage.error('无权限操作此链接')
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error('退单失败,请稍后重试')
|
ElMessage.error(error?.response?.data?.message || '退单失败,请稍后重试')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
refunding.value = false
|
refunding.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭退单对话框
|
// 关闭弹窗
|
||||||
const handleRefundDialogClose = () => {
|
const handleRefundDialogClose = () => {
|
||||||
if (!refunding.value) {
|
if (!refunding.value) {
|
||||||
refundDialogVisible.value = false
|
refundDialogVisible.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态标签类型
|
// 状态展示
|
||||||
const getStatusTagType = (status) => {
|
const getStatusTagType = (status) => {
|
||||||
const statusTypes = {
|
const map = {
|
||||||
'NEW': 'info',
|
NEW: 'info',
|
||||||
'USING': 'warning',
|
USING: 'warning',
|
||||||
'LOGGED_IN': 'primary',
|
LOGGED_IN: 'primary',
|
||||||
'COMPLETED': 'success',
|
COMPLETED: 'success',
|
||||||
'REFUNDED': 'info',
|
REFUNDED: 'info',
|
||||||
'EXPIRED': 'danger'
|
EXPIRED: 'danger'
|
||||||
}
|
}
|
||||||
return statusTypes[status] || 'info'
|
return map[status] || 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态文本
|
|
||||||
const getStatusText = (status) => {
|
const getStatusText = (status) => {
|
||||||
const statusTexts = {
|
const map = {
|
||||||
'NEW': '新建',
|
NEW: '新建',
|
||||||
'USING': '使用中',
|
USING: '使用中',
|
||||||
'LOGGED_IN': '已登录',
|
LOGGED_IN: '已登录',
|
||||||
'COMPLETED': '已完成',
|
COMPLETED: '已完成',
|
||||||
'REFUNDED': '已退单',
|
REFUNDED: '已退单',
|
||||||
'EXPIRED': '已过期'
|
EXPIRED: '已过期'
|
||||||
}
|
}
|
||||||
return statusTexts[status] || status
|
return map[status] || status
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化时间
|
// 时间格式化
|
||||||
const formatDateTime = (dateTime) => {
|
const formatDateTime = (dateTime) => {
|
||||||
if (!dateTime) return '-'
|
if (!dateTime) return '-'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const date = new Date(dateTime)
|
const date = new Date(dateTime)
|
||||||
return date.toLocaleString('zh-CN', {
|
return date.toLocaleString('zh-CN', {
|
||||||
@@ -366,23 +448,29 @@ export default {
|
|||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
second: '2-digit'
|
second: '2-digit'
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch {
|
||||||
return dateTime
|
return dateTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// state
|
||||||
|
isMobile,
|
||||||
loading,
|
loading,
|
||||||
refunding,
|
refunding,
|
||||||
refundDialogVisible,
|
refundDialogVisible,
|
||||||
linkInfo,
|
linkInfo,
|
||||||
searchForm,
|
searchForm,
|
||||||
|
// actions
|
||||||
|
pasteFromClipboard,
|
||||||
|
copyCodeNo,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
handleReset,
|
handleReset,
|
||||||
canRefund,
|
canRefund,
|
||||||
handleRefund,
|
handleRefund,
|
||||||
confirmRefund,
|
confirmRefund,
|
||||||
handleRefundDialogClose,
|
handleRefundDialogClose,
|
||||||
|
// helpers
|
||||||
getStatusTagType,
|
getStatusTagType,
|
||||||
getStatusText,
|
getStatusText,
|
||||||
formatDateTime
|
formatDateTime
|
||||||
@@ -399,35 +487,60 @@ export default {
|
|||||||
.page-header {
|
.page-header {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header h2 {
|
.page-header h2 {
|
||||||
margin: 0 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-description {
|
.page-description {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #909399;
|
color: #909399;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 搜索区样式 */
|
||||||
.search-section {
|
.search-section {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
.code-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.code-input-wrap .paste-btn {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 结果卡片 */
|
||||||
.link-info-section {
|
.link-info-section {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.code-and-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.code-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
.mono {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 操作按钮 */
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
@@ -435,24 +548,23 @@ export default {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 空状态 */
|
||||||
.empty-state {
|
.empty-state {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 帮助卡片 */
|
||||||
.help-section {
|
.help-section {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-list {
|
.help-list {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-list li {
|
.help-list li {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-list .el-tag {
|
.help-list .el-tag {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
@@ -463,87 +575,67 @@ export default {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-icon {
|
.warning-icon {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #e6a23c;
|
color: #e6a23c;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-content {
|
.confirm-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-content h3 {
|
.confirm-content h3 {
|
||||||
margin: 0 0 12px 0;
|
margin: 0 0 12px 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-code {
|
.link-code {
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #606266;
|
color: #606266;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-code strong {
|
.link-code strong {
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-text {
|
/* 按钮组(响应式) */
|
||||||
margin: 12px 0 0 0;
|
.btn-group {
|
||||||
color: #e6a23c;
|
display: grid;
|
||||||
font-size: 14px;
|
grid-template-columns: 1fr 1fr;
|
||||||
display: flex;
|
gap: 8px;
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 响应式设计 */
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.refund-management {
|
.refund-management {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header h2 {
|
.page-header h2 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-descriptions {
|
.el-descriptions {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.refund-confirm {
|
.refund-confirm {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-icon {
|
.warning-icon {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
/* 手机端按钮纵向排列更易点按 */
|
||||||
|
.btn-group {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 深色主题适配 */
|
/* 深色主题适配 */
|
||||||
.dark .page-header h2 {
|
.dark .page-header h2 { color: #e5eaf3; }
|
||||||
color: #e5eaf3;
|
.dark .page-description { color: #a3a6ad; }
|
||||||
}
|
.dark .confirm-content h3 { color: #e5eaf3; }
|
||||||
|
.dark .link-code { color: #a3a6ad; }
|
||||||
|
|
||||||
.dark .page-description {
|
/* 小图标间隔 */
|
||||||
color: #a3a6ad;
|
.mr-6 { margin-right: 6px; }
|
||||||
}
|
|
||||||
|
|
||||||
.dark .confirm-content h3 {
|
|
||||||
color: #e5eaf3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .link-code {
|
|
||||||
color: #a3a6ad;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user