添加批量删除、批量复制和导出选中链接到Excel的功能,同时更新链接状态和导出CSV的逻辑,优化了链接生成和显示的相关代码
This commit is contained in:
@@ -18,4 +18,8 @@ export function updateLink(id, payload) {
|
|||||||
return http.patch(`/api/link/${id}`, payload)
|
return http.patch(`/api/link/${id}`, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function batchDeleteLinks(codeNos) {
|
||||||
|
return http.post('/api/link/batch-delete', { codeNos })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,38 +19,40 @@ export const LINK_CONFIG = {
|
|||||||
|
|
||||||
// 状态配置
|
// 状态配置
|
||||||
export const STATUS_CONFIG = {
|
export const STATUS_CONFIG = {
|
||||||
// 状态标签类型
|
// 状态标签类型映射
|
||||||
LABEL_TYPES: {
|
LABEL_TYPES: {
|
||||||
NORMAL: 'success', // 正常
|
NEW: 'success', // 新建
|
||||||
EXPIRING: 'warning', // 即将过期
|
USED: 'info', // 已使用
|
||||||
EXPIRED: 'danger', // 已过期
|
EXPIRED: 'danger', // 已过期
|
||||||
UNKNOWN: 'info' // 未知
|
UNKNOWN: 'info' // 未知
|
||||||
},
|
},
|
||||||
|
|
||||||
// 状态文本
|
// 状态文本映射
|
||||||
LABEL_TEXTS: {
|
LABEL_TEXTS: {
|
||||||
NORMAL: '正常',
|
NEW: '新建',
|
||||||
EXPIRING: '即将过期',
|
USED: '已使用',
|
||||||
EXPIRED: '已过期',
|
EXPIRED: '已过期',
|
||||||
UNKNOWN: '未知'
|
UNKNOWN: '未知'
|
||||||
},
|
},
|
||||||
|
|
||||||
// 过期时间阈值(毫秒)
|
// 过期警告阈值(秒)
|
||||||
EXPIRING_THRESHOLD: 24 * 60 * 60 * 1000, // 24小时
|
EXPIRING_THRESHOLD: 24 * 60 * 60, // 24小时
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出配置
|
// 导出配置
|
||||||
export const EXPORT_CONFIG = {
|
export const EXPORT_CONFIG = {
|
||||||
// CSV文件前缀
|
// CSV文件前缀
|
||||||
FILE_PREFIX: 'links',
|
FILE_PREFIX: 'redemption_codes',
|
||||||
|
|
||||||
// 默认列配置
|
// 默认列配置
|
||||||
DEFAULT_COLUMNS: [
|
DEFAULT_COLUMNS: [
|
||||||
|
{ key: 'codeNo', label: '兑换码' },
|
||||||
{ key: 'batchId', label: '批次ID' },
|
{ key: 'batchId', label: '批次ID' },
|
||||||
{ key: 'codeNos', label: '机器编号' },
|
{ key: 'quantity', label: '数量' },
|
||||||
{ key: 'deductPoints', label: '扣除积分' },
|
{ key: 'times', label: '次数' },
|
||||||
|
{ key: 'totalPoints', label: '总积分' },
|
||||||
|
{ key: 'statusDesc', label: '状态' },
|
||||||
{ key: 'expireAt', label: '过期时间' },
|
{ key: 'expireAt', label: '过期时间' },
|
||||||
{ key: 'status', label: '状态' },
|
|
||||||
{ key: 'createdAt', label: '创建时间' }
|
{ key: 'createdAt', label: '创建时间' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export async function copyToClipboard(text) {
|
|||||||
// 导出CSV数据
|
// 导出CSV数据
|
||||||
export function exportToCSV(data, headers, filename) {
|
export function exportToCSV(data, headers, filename) {
|
||||||
const csvContent = [
|
const csvContent = [
|
||||||
headers.join(','),
|
headers.map(h => h.label).join(','),
|
||||||
...data.map(row =>
|
...data.map(row =>
|
||||||
headers.map(header => {
|
headers.map(header => {
|
||||||
const value = row[header.key] || ''
|
const value = row[header.key] || ''
|
||||||
@@ -91,4 +91,46 @@ export function exportToCSV(data, headers, filename) {
|
|||||||
link.click()
|
link.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导出Excel数据
|
||||||
|
export function exportToExcel(data, headers, filename) {
|
||||||
|
// 创建HTML表格格式
|
||||||
|
const tableHTML = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
table { border-collapse: collapse; width: 100%; }
|
||||||
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||||
|
th { background-color: #f2f2f2; font-weight: bold; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
${headers.map(header => `<th>${header.label}</th>`).join('')}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${data.map(row =>
|
||||||
|
`<tr>
|
||||||
|
${headers.map(header => `<td>${row[header.key] || ''}</td>`).join('')}
|
||||||
|
</tr>`
|
||||||
|
).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
// 创建Blob并下载
|
||||||
|
const blob = new Blob([tableHTML], {
|
||||||
|
type: 'application/vnd.ms-excel;charset=utf-8;'
|
||||||
|
})
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = URL.createObjectURL(blob)
|
||||||
|
link.download = filename
|
||||||
|
link.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -75,36 +75,81 @@
|
|||||||
>
|
>
|
||||||
导出CSV
|
导出CSV
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
|
<!-- 批量操作按钮 -->
|
||||||
|
<div v-if="showBatchActions" class="batch-actions">
|
||||||
|
<el-button
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
@click="selectAll"
|
||||||
|
>
|
||||||
|
全选
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
@click="clearSelection"
|
||||||
|
>
|
||||||
|
取消选择
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="batchCopyLinks"
|
||||||
|
:disabled="selectedRows.length === 0"
|
||||||
|
>
|
||||||
|
批量复制链接 ({{ selectedRows.length }})
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
size="small"
|
||||||
|
@click="exportSelectedToExcel"
|
||||||
|
:disabled="selectedRows.length === 0"
|
||||||
|
>
|
||||||
|
导出选中Excel
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
size="small"
|
||||||
|
@click="handleBatchDelete"
|
||||||
|
:disabled="selectedRows.length === 0"
|
||||||
|
>
|
||||||
|
批量删除 ({{ selectedRows.length }})
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
|
ref="tableRef"
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="linkList"
|
:data="linkList"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
border
|
border
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
>
|
>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column prop="codeNo" label="兑换码" width="120" />
|
||||||
<el-table-column prop="batchId" label="批次ID" width="100" />
|
<el-table-column prop="batchId" label="批次ID" width="100" />
|
||||||
<el-table-column prop="codeNos" label="机器编号" width="200">
|
<el-table-column prop="quantity" label="数量" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div v-if="row.codeNos && row.codeNos.length > 0">
|
<el-tag type="info" size="small">
|
||||||
<el-tag
|
{{ row.quantity }}
|
||||||
v-for="codeNo in row.codeNos"
|
|
||||||
:key="codeNo"
|
|
||||||
size="small"
|
|
||||||
style="margin-right: 5px; margin-bottom: 5px;"
|
|
||||||
>
|
|
||||||
{{ codeNo }}
|
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="deductPoints" label="扣除积分" width="100">
|
<el-table-column prop="times" label="次数" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag type="success" size="small">
|
||||||
|
{{ row.times }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="totalPoints" label="总积分" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag type="warning" size="small">
|
<el-tag type="warning" size="small">
|
||||||
{{ row.deductPoints || 0 }}
|
{{ row.totalPoints }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -115,46 +160,44 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="链接地址" min-width="200">
|
<el-table-column label="链接地址" min-width="200">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div v-if="row.codeNos && row.codeNos.length > 0">
|
|
||||||
<div
|
|
||||||
v-for="codeNo in row.codeNos"
|
|
||||||
:key="codeNo"
|
|
||||||
style="margin-bottom: 8px;"
|
|
||||||
>
|
|
||||||
<el-link
|
<el-link
|
||||||
:href="generateLinkUrl(codeNo)"
|
:href="generateLinkUrl(row.codeNo)"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
type="primary"
|
type="primary"
|
||||||
:underline="false"
|
:underline="false"
|
||||||
>
|
>
|
||||||
{{ generateLinkUrl(codeNo) }}
|
{{ generateLinkUrl(row.codeNo) }}
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-button
|
<el-button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
@click="copyToClipboard(generateLinkUrl(codeNo))"
|
@click="copyToClipboard(generateLinkUrl(row.codeNo))"
|
||||||
style="margin-left: 5px;"
|
style="margin-left: 5px;"
|
||||||
>
|
>
|
||||||
复制
|
复制
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="状态" width="100">
|
<el-table-column label="状态" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag
|
<el-tag
|
||||||
:type="getLinkStatusByExpire(row.expireAt)"
|
:type="getStatusType(row)"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
{{ getLinkStatusByExpire(row.expireAt, true) }}
|
{{ row.statusDesc }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="剩余时间" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.isExpired" class="expired-text">已过期</span>
|
||||||
|
<span v-else-if="row.remainingSeconds > 0">{{ formatRemainingTime(row.remainingSeconds) }}</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="创建时间" width="180">
|
<el-table-column label="创建时间" width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ formatDateTime(new Date()) }}
|
{{ formatDateTime(row.createdAt) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="150" fixed="right">
|
<el-table-column label="操作" width="150" fixed="right">
|
||||||
@@ -169,7 +212,7 @@
|
|||||||
<el-button
|
<el-button
|
||||||
type="danger"
|
type="danger"
|
||||||
size="small"
|
size="small"
|
||||||
@click="deleteLinkItem(row.batchId)"
|
@click="deleteLinkItem(row.codeNo)"
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -222,10 +265,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { generateLinks, fetchLinks, deleteLink } from '@/api/links'
|
import { generateLinks, fetchLinks, deleteLink, batchDeleteLinks } from '@/api/links'
|
||||||
import { formatLinkStatus, getLinkStatusType, generateQRCodeUrl, downloadImage, copyToClipboard as copyText, exportToCSV as exportCSV } from '@/utils/links'
|
import { formatLinkStatus, getLinkStatusType, generateQRCodeUrl, downloadImage, copyToClipboard as copyText, exportToCSV as exportCSV, exportToExcel } from '@/utils/links'
|
||||||
import { LINK_CONFIG, STATUS_CONFIG, EXPORT_CONFIG } from '@/config/links'
|
import { LINK_CONFIG, STATUS_CONFIG, EXPORT_CONFIG } from '@/config/links'
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
@@ -236,10 +279,15 @@ const linkList = ref([])
|
|||||||
const qrCodeDialogVisible = ref(false)
|
const qrCodeDialogVisible = ref(false)
|
||||||
const currentQRCode = ref('')
|
const currentQRCode = ref('')
|
||||||
|
|
||||||
|
// 多选相关状态
|
||||||
|
const selectedRows = ref([])
|
||||||
|
const showBatchActions = computed(() => selectedRows.value.length > 0)
|
||||||
|
const tableRef = ref()
|
||||||
|
|
||||||
// 生成表单
|
// 生成表单
|
||||||
const generateForm = reactive({
|
const generateForm = reactive({
|
||||||
times: 1,
|
times: null,
|
||||||
linkCount: 5
|
linkCount: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
// 分页
|
// 分页
|
||||||
@@ -308,8 +356,12 @@ const getLinkList = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetchLinks(params)
|
const response = await fetchLinks(params)
|
||||||
linkList.value = response.data.items || []
|
const data = response.data
|
||||||
pagination.total = response.data.total || 0
|
|
||||||
|
linkList.value = data.items || []
|
||||||
|
pagination.total = data.total || 0
|
||||||
|
pagination.page = data.page || 1
|
||||||
|
pagination.pageSize = data.pageSize || 20
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取链接列表失败:', error)
|
console.error('获取链接列表失败:', error)
|
||||||
@@ -360,13 +412,12 @@ const deleteLinkItem = async (id) => {
|
|||||||
|
|
||||||
// 查看二维码
|
// 查看二维码
|
||||||
const viewQRCode = (row) => {
|
const viewQRCode = (row) => {
|
||||||
if (row.codeNos && row.codeNos.length > 0) {
|
if (row.codeNo) {
|
||||||
// 使用第一个机器编号生成二维码
|
const linkUrl = generateLinkUrl(row.codeNo)
|
||||||
const linkUrl = generateLinkUrl(row.codeNos[0])
|
|
||||||
currentQRCode.value = generateQRCodeUrl(linkUrl, 200)
|
currentQRCode.value = generateQRCodeUrl(linkUrl, 200)
|
||||||
qrCodeDialogVisible.value = true
|
qrCodeDialogVisible.value = true
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning('没有可用的机器编号')
|
ElMessage.warning('没有可用的兑换码')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,16 +440,24 @@ const copyToClipboard = async (text) => {
|
|||||||
|
|
||||||
// 导出CSV
|
// 导出CSV
|
||||||
const exportToCSV = () => {
|
const exportToCSV = () => {
|
||||||
const headers = EXPORT_CONFIG.DEFAULT_COLUMNS
|
const headers = [
|
||||||
|
{ key: 'codeNo', label: '兑换码' },
|
||||||
|
{ key: 'batchId', label: '批次ID' },
|
||||||
|
{ key: 'quantity', label: '数量' },
|
||||||
|
{ key: 'times', label: '次数' },
|
||||||
|
{ key: 'totalPoints', label: '总积分' },
|
||||||
|
{ key: 'statusDesc', label: '状态' },
|
||||||
|
{ key: 'expireAt', label: '过期时间' },
|
||||||
|
{ key: 'createdAt', label: '创建时间' }
|
||||||
|
]
|
||||||
|
|
||||||
const data = linkList.value.map(item => ({
|
const data = linkList.value.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
codeNos: (item.codeNos || []).join(', '),
|
expireAt: formatDateTime(item.expireAt),
|
||||||
status: getLinkStatusByExpire(item.expireAt, true),
|
createdAt: formatDateTime(item.createdAt)
|
||||||
createdAt: formatDateTime(new Date())
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
exportCSV(data, headers, `${EXPORT_CONFIG.FILE_PREFIX}_${new Date().toISOString().split('T')[0]}.csv`)
|
exportCSV(data, headers, `兑换码列表_${new Date().toISOString().split('T')[0]}.csv`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成链接URL
|
// 生成链接URL
|
||||||
@@ -406,19 +465,38 @@ const generateLinkUrl = (codeNo) => {
|
|||||||
return LINK_CONFIG.getLinkUrl(codeNo)
|
return LINK_CONFIG.getLinkUrl(codeNo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据过期时间判断链接状态
|
// 根据状态获取标签类型
|
||||||
const getLinkStatusByExpire = (expireAt, returnText = false) => {
|
const getStatusType = (row) => {
|
||||||
if (!expireAt) return returnText ? STATUS_CONFIG.LABEL_TEXTS.UNKNOWN : STATUS_CONFIG.LABEL_TYPES.UNKNOWN
|
if (row.isExpired) {
|
||||||
|
return 'danger'
|
||||||
|
}
|
||||||
|
|
||||||
const now = new Date()
|
switch (row.status) {
|
||||||
const expire = new Date(expireAt)
|
case 'NEW':
|
||||||
|
return 'success'
|
||||||
|
case 'USED':
|
||||||
|
return 'info'
|
||||||
|
case 'EXPIRED':
|
||||||
|
return 'danger'
|
||||||
|
default:
|
||||||
|
return 'info'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (expire < now) {
|
// 格式化剩余时间(秒)
|
||||||
return returnText ? STATUS_CONFIG.LABEL_TEXTS.EXPIRED : STATUS_CONFIG.LABEL_TYPES.EXPIRED
|
const formatRemainingTime = (seconds) => {
|
||||||
} else if (expire - now < STATUS_CONFIG.EXPIRING_THRESHOLD) {
|
if (seconds <= 0) return '已过期'
|
||||||
return returnText ? STATUS_CONFIG.LABEL_TEXTS.EXPIRING : STATUS_CONFIG.LABEL_TYPES.EXPIRING
|
|
||||||
|
const days = Math.floor(seconds / (24 * 60 * 60))
|
||||||
|
const hours = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60))
|
||||||
|
const minutes = Math.floor((seconds % (60 * 60)) / 60)
|
||||||
|
|
||||||
|
if (days > 0) {
|
||||||
|
return `${days}天${hours}小时`
|
||||||
|
} else if (hours > 0) {
|
||||||
|
return `${hours}小时${minutes}分钟`
|
||||||
} else {
|
} else {
|
||||||
return returnText ? STATUS_CONFIG.LABEL_TEXTS.NORMAL : STATUS_CONFIG.LABEL_TYPES.NORMAL
|
return `${minutes}分钟`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +514,113 @@ const formatDateTime = (dateString) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 多选处理函数
|
||||||
|
const handleSelectionChange = (selection) => {
|
||||||
|
selectedRows.value = selection
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
const handleBatchDelete = async () => {
|
||||||
|
if (selectedRows.value.length === 0) {
|
||||||
|
ElMessage.warning('请先选择要删除的兑换码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确定要删除选中的 ${selectedRows.value.length} 个兑换码吗?此操作不可恢复!`,
|
||||||
|
'批量删除确认',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定删除',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 使用批量删除API
|
||||||
|
const codeNos = selectedRows.value.map(row => row.codeNo)
|
||||||
|
await batchDeleteLinks(codeNos)
|
||||||
|
|
||||||
|
ElMessage.success(`成功删除 ${selectedRows.value.length} 个兑换码`)
|
||||||
|
selectedRows.value = []
|
||||||
|
await getLinkList()
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('批量删除失败:', error)
|
||||||
|
ElMessage.error('批量删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量复制链接
|
||||||
|
const batchCopyLinks = async () => {
|
||||||
|
if (selectedRows.value.length === 0) {
|
||||||
|
ElMessage.warning('请先选择要复制的兑换码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const links = selectedRows.value.map(row => generateLinkUrl(row.codeNo))
|
||||||
|
const linkText = links.join('\n')
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(linkText)
|
||||||
|
ElMessage.success(`成功复制 ${selectedRows.value.length} 个链接到剪贴板`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量复制失败:', error)
|
||||||
|
ElMessage.error('复制失败,请手动复制')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出选中的Excel
|
||||||
|
const exportSelectedToExcel = () => {
|
||||||
|
if (selectedRows.value.length === 0) {
|
||||||
|
ElMessage.warning('请先选择要导出的兑换码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{ key: 'codeNo', label: '兑换码' },
|
||||||
|
{ key: 'batchId', label: '批次ID' },
|
||||||
|
{ key: 'quantity', label: '数量' },
|
||||||
|
{ key: 'times', label: '次数' },
|
||||||
|
{ key: 'totalPoints', label: '总积分' },
|
||||||
|
{ key: 'statusDesc', label: '状态' },
|
||||||
|
{ key: 'remainingTime', label: '剩余时间' },
|
||||||
|
{ key: 'linkUrl', label: '链接地址' },
|
||||||
|
{ key: 'expireAt', label: '过期时间' },
|
||||||
|
{ key: 'createdAt', label: '创建时间' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const data = selectedRows.value.map(item => ({
|
||||||
|
...item,
|
||||||
|
remainingTime: item.isExpired ? '已过期' : (item.remainingSeconds > 0 ? formatRemainingTime(item.remainingSeconds) : '-'),
|
||||||
|
linkUrl: generateLinkUrl(item.codeNo),
|
||||||
|
expireAt: formatDateTime(item.expireAt),
|
||||||
|
createdAt: formatDateTime(item.createdAt)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const filename = `选中兑换码_${selectedRows.value.length}个_${new Date().toISOString().split('T')[0]}.xls`
|
||||||
|
exportToExcel(data, headers, filename)
|
||||||
|
|
||||||
|
ElMessage.success(`成功导出 ${selectedRows.value.length} 个兑换码到Excel`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全选
|
||||||
|
const selectAll = () => {
|
||||||
|
if (tableRef.value) {
|
||||||
|
linkList.value.forEach(row => {
|
||||||
|
tableRef.value.toggleRowSelection(row, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消选择
|
||||||
|
const clearSelection = () => {
|
||||||
|
if (tableRef.value) {
|
||||||
|
tableRef.value.clearSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 页面加载时获取数据
|
// 页面加载时获取数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getLinkList()
|
getLinkList()
|
||||||
@@ -491,4 +676,24 @@ onMounted(() => {
|
|||||||
.qr-code-placeholder {
|
.qr-code-placeholder {
|
||||||
padding: 40px 0;
|
padding: 40px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expired-text {
|
||||||
|
color: #f56c6c;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-actions .el-button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user