<script setup lang="tsx">
|
import type { DataTableColumns } from 'naive-ui'
|
import { NButton, useDialog, useMessage } from 'naive-ui'
|
import { useBoolean } from '@/hooks'
|
import { downloadFile } from '@/utils'
|
import SearchPanel from '@/components/custom/SearchPanel.vue'
|
import OperationLogModal from '@/components/custom/OperationLogModal.vue'
|
import { deleteInvitationCodes, exportInvitationExcel, getInvitationPageList, getInvitationTemplate, importInvitationExcel } from '@/service'
|
import GenerateModal from './components/GenerateModal.vue'
|
import dayjs from 'dayjs'
|
|
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
const message = useMessage()
|
const dialog = useDialog()
|
|
// 搜索条件初始值
|
const initialModel = {
|
invitationCode: '', // 邀请码
|
bindAccount: '', // 绑定账号
|
status: null as number | null, // 状态
|
}
|
|
const model = ref({ ...initialModel })
|
const pageParams = ref({
|
pageNum: 1,
|
pageSize: 10,
|
})
|
const formRef = ref<any>()
|
|
// 状态选项
|
// const statusOptions = [
|
// { label: '未绑定', value: 1 },
|
// { label: '已绑定', value: 2 },
|
// { label: '已解绑', value: 3 },
|
// ]
|
|
// 搜索项配置
|
const filterItems = computed(() => [
|
{
|
label: '邀请码',
|
path: 'invitationCode',
|
render: () => <n-input v-model:value={model.value.invitationCode} placeholder="请输入" clearable />,
|
},
|
{
|
label: '绑定账号',
|
path: 'bindAccount',
|
render: () => <n-input v-model:value={model.value.bindAccount} placeholder="请输入" clearable />,
|
},
|
])
|
|
const listData = ref<any[]>([])
|
const count = ref(0)
|
const selectedRowKeys = ref<number[]>([])
|
|
// 文件导入相关
|
const fileInputRef = ref<HTMLInputElement | null>(null)
|
|
// 生成邀请码弹窗
|
const showGenerateModal = ref(false)
|
|
const showOperationLogModal = ref(false)
|
|
// 表格列定义
|
const columns = computed<DataTableColumns<any>>(() => [
|
{
|
type: 'selection',
|
width: 50,
|
},
|
{
|
title: '序号',
|
align: 'left',
|
key: 'index',
|
width: 80,
|
resizable: true,
|
render: (row, index) => <span>{(pageParams.value.pageNum - 1) * pageParams.value.pageSize + index + 1}</span>,
|
},
|
{
|
title: '邀请码',
|
align: 'left',
|
ellipsis: true,
|
key: 'invitationCode',
|
resizable: true,
|
render: row => <span>{row.invitationCode}</span>,
|
},
|
{
|
title: '绑定账号',
|
align: 'left',
|
ellipsis: true,
|
key: 'bindAccount',
|
resizable: true,
|
render: row => <span>{row.bindAccount || '-'}</span>,
|
},
|
{
|
title: '创建时间',
|
align: 'left',
|
key: 'createTime',
|
ellipsis: true,
|
resizable: true,
|
render: row => (
|
<span>
|
{row.createTime ? dayjs(row.createTime).format('YYYY/MM/DD HH:mm') : '-'}
|
{' '}
|
</span>
|
),
|
},
|
{
|
title: '绑定时间',
|
align: 'left',
|
key: 'bindTime',
|
ellipsis: true,
|
resizable: true,
|
render: row => (
|
<span>
|
{row.bindTime ? dayjs(row.bindTime).format('YYYY/MM/DD HH:mm') : '-'}
|
{' '}
|
</span>
|
),
|
},
|
{
|
title: '操作',
|
align: 'left',
|
key: 'actions',
|
width: 140,
|
fixed: 'right',
|
render: row => (
|
<div class="flex justify-start">
|
<NButton
|
text
|
type="primary"
|
v-permission="30115"
|
onClick={() => deleteInvitationCode(row.id)}
|
>
|
删除
|
</NButton>
|
</div>
|
),
|
},
|
])
|
|
function handleOpenOperationLog() {
|
showOperationLogModal.value = true
|
}
|
|
// 重置搜索
|
function handleResetSearch() {
|
model.value = { ...initialModel }
|
pageParams.value.pageNum = 1
|
getInvitationCodeList()
|
}
|
|
// 搜索
|
function handleSearch() {
|
pageParams.value.pageNum = 1
|
getInvitationCodeList()
|
}
|
|
// 获取邀请码列表
|
async function getInvitationCodeList() {
|
startLoading()
|
try {
|
const params = {
|
...model.value,
|
...pageParams.value,
|
}
|
const { data } = await getInvitationPageList(params)
|
if (data) {
|
listData.value = data.records || []
|
count.value = data.total || 0
|
}
|
}
|
catch (error) {
|
console.error('获取列表失败:', error)
|
}
|
finally {
|
endLoading()
|
}
|
}
|
|
// 分页变化
|
function changePage(page: number, size: number) {
|
pageParams.value.pageNum = page
|
pageParams.value.pageSize = size
|
getInvitationCodeList()
|
}
|
|
// 生成邀请码
|
function handleGenerate() {
|
showGenerateModal.value = true
|
}
|
|
// 生成邀请码成功
|
function handleGenerateSuccess() {
|
handleSearch()
|
}
|
|
// 批量删除
|
function handleBatchDelete() {
|
if (selectedRowKeys.value.length === 0) {
|
return
|
}
|
|
dialog.warning({
|
title: '是否确认删除',
|
content: '删除后数据不可恢复,请谨慎操作',
|
positiveText: '确定',
|
negativeText: '取消',
|
maskClosable: false,
|
autoFocus: false,
|
onPositiveClick: async () => {
|
try {
|
const ids = selectedRowKeys.value.join(',')
|
const { code } = await deleteInvitationCodes(ids)
|
if (code === 200) {
|
message.success('批量删除成功')
|
selectedRowKeys.value = []
|
handleSearch()
|
}
|
}
|
catch (error) {
|
console.error('批量删除失败:', error)
|
}
|
},
|
})
|
}
|
|
// 导入
|
function handleImport() {
|
fileInputRef.value?.click()
|
}
|
|
// 处理文件选择
|
async function handleFileChange(event: Event) {
|
const target = event.target as HTMLInputElement
|
const file = target.files?.[0]
|
if (!file)
|
return
|
|
try {
|
const { code } = await importInvitationExcel(file)
|
if (code === 200) {
|
message.success('导入成功')
|
handleSearch()
|
}
|
}
|
catch (error) {
|
console.error('导入失败:', error)
|
}
|
finally {
|
// 清空 input 方便下次选择同一个文件
|
target.value = ''
|
}
|
}
|
|
// 导出
|
async function handleExport() {
|
try {
|
let params: any = {}
|
if (selectedRowKeys.value.length > 0) {
|
params = { ids: selectedRowKeys.value }
|
}
|
else {
|
params = {
|
...model.value,
|
...pageParams.value,
|
}
|
}
|
const response = await exportInvitationExcel(params)
|
if (response instanceof Blob) {
|
downloadFile(response, `邀请码列表_${new Date().getTime()}.xlsx`)
|
message.success(`导出成功${selectedRowKeys.value.length > 0 ? `,共${selectedRowKeys.value.length}条数据` : ''}`)
|
}
|
else {
|
message.error('导出失败:接口未返回正确的文件流')
|
}
|
}
|
catch (error) {
|
console.error('导出失败:', error)
|
}
|
}
|
|
// 下载模板
|
async function handleDownloadTemplate() {
|
try {
|
const response = await getInvitationTemplate()
|
if (response instanceof Blob) {
|
downloadFile(response, `邀请码导入模板_${new Date().getTime()}.xlsx`)
|
message.success('下载成功')
|
}
|
else {
|
message.error('下载失败:接口未返回正确的文件模板')
|
}
|
}
|
catch (error) {
|
console.error('下载模板失败:', error)
|
}
|
}
|
|
// 删除单个邀请码
|
function deleteInvitationCode(id: number) {
|
dialog.warning({
|
title: '是否确认删除',
|
content: '删除后数据不可恢复,请谨慎操作',
|
positiveText: '确定',
|
negativeText: '取消',
|
maskClosable: false,
|
autoFocus: false,
|
onPositiveClick: async () => {
|
try {
|
const { code } = await deleteInvitationCodes(id.toString())
|
if (code === 200) {
|
message.success('删除成功')
|
getInvitationCodeList()
|
}
|
}
|
catch (error) {
|
console.error('删除失败:', error)
|
}
|
},
|
})
|
}
|
|
// 行选择
|
// function handleCheck(rowKeys: number[]) {
|
// selectedRowKeys.value = rowKeys
|
// }
|
|
const tableHeight = ref(0)
|
let resizeObserver: ResizeObserver | null = null
|
|
function calcTableHeight() {
|
if (!formRef.value)
|
return
|
|
const formHeight = formRef.value.offsetHeight
|
tableHeight.value = window.innerHeight - formHeight - 261
|
}
|
|
onMounted(() => {
|
getInvitationCodeList()
|
|
if (formRef.value) {
|
resizeObserver = new ResizeObserver(() => {
|
calcTableHeight()
|
})
|
|
resizeObserver.observe(formRef.value)
|
}
|
|
// 初始化计算一次
|
calcTableHeight()
|
})
|
|
onBeforeUnmount(() => {
|
resizeObserver?.disconnect()
|
})
|
</script>
|
|
<template>
|
<div class="roboTaxi w-full h-full">
|
<!-- 搜索区域 -->
|
<div ref="formRef" class="roboTaxi-form-card mb-12px">
|
<n-form
|
:model="model"
|
label-placement="left"
|
inline
|
:show-feedback="false"
|
>
|
<SearchPanel show-toggle>
|
<n-gi v-for="item in filterItems" :key="item.path">
|
<n-form-item :label="item.label" :path="item.path">
|
<component :is="item.render" />
|
</n-form-item>
|
</n-gi>
|
<template #action>
|
<NButton @click="handleResetSearch">
|
{{ $t("common.reset") }}
|
</NButton>
|
<NButton type="primary" @click="handleSearch">
|
{{ $t("common.search") }}
|
</NButton>
|
</template>
|
</SearchPanel>
|
</n-form>
|
</div>
|
|
<!-- 表格区域 -->
|
<n-card class="flex-1 robotaxi-table-card" content-style="display: flex; flex-direction: column; flex: 1; min-height: 0;">
|
<template #header>
|
<n-flex justify="space-between" align="center">
|
<div class="flex gap-2 items-center">
|
<span v-if="selectedRowKeys.length > 0" style="color: rgba(23, 31, 38, 0.6);font-weight: 400;">
|
{{ $t('accountManagement.messages.selectedRecords', { count: selectedRowKeys.length }) }}
|
</span>
|
<NButton
|
v-permission="30111"
|
:disabled="selectedRowKeys.length === 0"
|
|
@click="handleBatchDelete"
|
>
|
批量删除
|
</NButton>
|
<NButton v-permission="30116" @click="handleOpenOperationLog">
|
操作日志
|
</NButton>
|
</div>
|
<n-flex align="center">
|
<NButton v-permission="30112" @click="handleDownloadTemplate">
|
下载模板
|
</NButton>
|
<NButton v-permission="30113" @click="handleExport">
|
导出
|
</NButton>
|
<NButton v-permission="30112" @click="handleImport">
|
导入
|
</NButton>
|
<NButton
|
v-permission="30114"
|
type="primary"
|
secondary
|
|
@click="handleGenerate"
|
>
|
生成邀请码
|
</NButton>
|
</n-flex>
|
</n-flex>
|
</template>
|
|
<div class="flex-1 flex flex-col h-full">
|
<n-data-table
|
v-model:checked-row-keys="selectedRowKeys"
|
class="flex-1"
|
:columns="columns"
|
:data="listData"
|
:loading="loading"
|
:row-key="(row: any) => row.id"
|
flex-height
|
:bordered="false"
|
:header-height="18"
|
:row-height="18"
|
:scroll-x="1400"
|
>
|
<template #empty>
|
<div class="flex-center">
|
<img src="/src/assets/element-img/no-data.png" style="width:123px;height:125px" @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }">
|
</div>
|
</template>
|
</n-data-table>
|
<Pagination :count="count" :page-size="pageParams.pageSize" :page="pageParams.pageNum" class="robotaxi-pagination mt-12px" @change="changePage" />
|
</div>
|
</n-card>
|
|
<input
|
ref="fileInputRef"
|
type="file"
|
style="display: none"
|
accept=".xlsx, .xls"
|
@change="handleFileChange"
|
>
|
|
<!-- 生成邀请码弹窗 -->
|
<GenerateModal
|
v-model:visible="showGenerateModal"
|
@success="handleGenerateSuccess"
|
/>
|
|
<OperationLogModal v-model:show="showOperationLogModal" />
|
</div>
|
</template>
|
|
<style scoped lang="scss">
|
</style>
|