完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
524 lines
14 KiB
Vue
524 lines
14 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="package-batch-page" id="table-full-screen">
|
||
<!-- 搜索栏 -->
|
||
<ArtSearchBar
|
||
v-model:filter="formFilters"
|
||
:items="formItems"
|
||
@reset="handleReset"
|
||
@search="handleSearch"
|
||
></ArtSearchBar>
|
||
|
||
<ElCard shadow="never" class="art-table-card">
|
||
<!-- 表格头部 -->
|
||
<ArtTableHeader
|
||
:columnList="columnOptions"
|
||
v-model:columns="columnChecks"
|
||
@refresh="handleRefresh"
|
||
>
|
||
<template #left>
|
||
<ElButton type="primary" @click="handleSearch">查询</ElButton>
|
||
<ElButton type="success" @click="showImportDialog">导入</ElButton>
|
||
</template>
|
||
</ArtTableHeader>
|
||
|
||
<!-- 表格 -->
|
||
<ArtTable
|
||
ref="tableRef"
|
||
row-key="id"
|
||
:loading="loading"
|
||
:data="tableData"
|
||
:currentPage="pagination.currentPage"
|
||
:pageSize="pagination.pageSize"
|
||
:total="pagination.total"
|
||
:marginTop="10"
|
||
@selection-change="handleSelectionChange"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
>
|
||
<template #default>
|
||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||
</template>
|
||
</ArtTable>
|
||
|
||
<!-- 导入对话框 -->
|
||
<ElDialog
|
||
v-model="importDialogVisible"
|
||
title="批量导入套餐"
|
||
width="500px"
|
||
align-center
|
||
:close-on-click-modal="false"
|
||
>
|
||
<!-- 顶部下载模板按钮 -->
|
||
<div class="template-section">
|
||
<ElButton type="primary" @click="downloadTemplate" icon="Download"> 下载模板 </ElButton>
|
||
<span class="template-tip">请先下载模板,按模板格式填写后上传</span>
|
||
</div>
|
||
|
||
<ElDivider />
|
||
|
||
<ElForm
|
||
ref="importFormRef"
|
||
:model="importFormData"
|
||
:rules="importRules"
|
||
label-width="120px"
|
||
>
|
||
<ElFormItem label="导入类型" prop="importType">
|
||
<ElSelect
|
||
v-model="importFormData.importType"
|
||
placeholder="请选择导入类型"
|
||
style="width: 100%"
|
||
clearable
|
||
>
|
||
<ElOption label="套餐批量导入" value="package_batch" />
|
||
<ElOption label="套餐更新导入" value="package_update" />
|
||
<ElOption label="套餐停用导入" value="package_disable" />
|
||
<ElOption label="套餐启用导入" value="package_enable" />
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
|
||
<ElFormItem label="上传Excel文件" prop="excelFile">
|
||
<ElUpload
|
||
ref="uploadRef"
|
||
:limit="1"
|
||
:on-exceed="handleExceed"
|
||
:before-upload="beforeUpload"
|
||
:on-change="handleFileChange"
|
||
:auto-upload="false"
|
||
accept=".xlsx,.xls"
|
||
drag
|
||
>
|
||
<ElIcon class="el-icon--upload"><UploadFilled /></ElIcon>
|
||
<div class="el-upload__text"> 将文件拖到此处,或<em>点击上传</em> </div>
|
||
<template #tip>
|
||
<div class="el-upload__tip"> 只能上传 xlsx/xls 文件,且不超过 10MB </div>
|
||
</template>
|
||
</ElUpload>
|
||
</ElFormItem>
|
||
|
||
<ElFormItem label="备注说明" prop="remark">
|
||
<ElInput
|
||
v-model="importFormData.remark"
|
||
type="textarea"
|
||
placeholder="请输入备注说明(可选)"
|
||
:rows="3"
|
||
maxlength="200"
|
||
show-word-limit
|
||
/>
|
||
</ElFormItem>
|
||
</ElForm>
|
||
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="importDialogVisible = false">取消</ElButton>
|
||
<ElButton type="primary" @click="handleImportSubmit" :loading="importLoading">
|
||
确认导入
|
||
</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
</ElCard>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { h } from 'vue'
|
||
import { ElTag, ElMessage, ElUpload, ElIcon, ElDivider } from 'element-plus'
|
||
import { UploadFilled, Download } from '@element-plus/icons-vue'
|
||
import type { FormInstance, UploadInstance, UploadRawFile, UploadFile } from 'element-plus'
|
||
import type { FormRules } from 'element-plus'
|
||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||
import { SearchChangeParams, SearchFormItem } from '@/types'
|
||
|
||
defineOptions({ name: 'PackageBatch' })
|
||
|
||
const importDialogVisible = ref(false)
|
||
const loading = ref(false)
|
||
const importLoading = ref(false)
|
||
|
||
// 定义表单搜索初始值
|
||
const initialSearchState = {
|
||
importType: ''
|
||
}
|
||
|
||
// 响应式表单数据
|
||
const formFilters = reactive({ ...initialSearchState })
|
||
|
||
const pagination = reactive({
|
||
currentPage: 1,
|
||
pageSize: 20,
|
||
total: 0
|
||
})
|
||
|
||
// 表格数据
|
||
const tableData = ref<any[]>([])
|
||
|
||
// 表格实例引用
|
||
const tableRef = ref()
|
||
|
||
// 选中的行数据
|
||
const selectedRows = ref<any[]>([])
|
||
|
||
// 导入表单实例
|
||
const importFormRef = ref<FormInstance>()
|
||
const uploadRef = ref<UploadInstance>()
|
||
|
||
// 导入表单数据
|
||
const importFormData = reactive({
|
||
importType: '',
|
||
excelFile: null as File | null,
|
||
remark: ''
|
||
})
|
||
|
||
// 模拟数据
|
||
const mockData = [
|
||
{
|
||
id: 1,
|
||
importTotal: 1000,
|
||
success: 980,
|
||
failed: 20,
|
||
importType: '套餐批量导入',
|
||
operator: '张若暄',
|
||
operationTime: '2025-11-08 10:30:00'
|
||
},
|
||
{
|
||
id: 2,
|
||
importTotal: 500,
|
||
success: 500,
|
||
failed: 0,
|
||
importType: '套餐更新导入',
|
||
operator: '孔丽娟',
|
||
operationTime: '2025-11-07 14:15:00'
|
||
},
|
||
{
|
||
id: 3,
|
||
importTotal: 300,
|
||
success: 285,
|
||
failed: 15,
|
||
importType: '套餐停用导入',
|
||
operator: '李佳音',
|
||
operationTime: '2025-11-06 09:45:00'
|
||
},
|
||
{
|
||
id: 4,
|
||
importTotal: 800,
|
||
success: 795,
|
||
failed: 5,
|
||
importType: '套餐启用导入',
|
||
operator: '赵强',
|
||
operationTime: '2025-11-05 16:20:00'
|
||
},
|
||
{
|
||
id: 5,
|
||
importTotal: 150,
|
||
success: 145,
|
||
failed: 5,
|
||
importType: '套餐批量导入',
|
||
operator: '张若暄',
|
||
operationTime: '2025-11-04 11:30:00'
|
||
}
|
||
]
|
||
|
||
// 重置表单
|
||
const handleReset = () => {
|
||
Object.assign(formFilters, { ...initialSearchState })
|
||
pagination.currentPage = 1
|
||
getBatchImportList()
|
||
}
|
||
|
||
// 搜索处理
|
||
const handleSearch = () => {
|
||
console.log('搜索参数:', formFilters)
|
||
pagination.currentPage = 1
|
||
getBatchImportList()
|
||
}
|
||
|
||
// 表单项变更处理
|
||
const handleFormChange = (params: SearchChangeParams): void => {
|
||
console.log('表单项变更:', params)
|
||
}
|
||
|
||
// 表单配置项
|
||
const formItems: SearchFormItem[] = [
|
||
{
|
||
label: '导入类型',
|
||
prop: 'importType',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '全部'
|
||
},
|
||
options: () => [
|
||
{ label: '套餐批量导入', value: 'package_batch' },
|
||
{ label: '套餐更新导入', value: 'package_update' },
|
||
{ label: '套餐停用导入', value: 'package_disable' },
|
||
{ label: '套餐启用导入', value: 'package_enable' }
|
||
],
|
||
onChange: handleFormChange
|
||
}
|
||
]
|
||
|
||
// 列配置
|
||
const columnOptions = [
|
||
{ label: '勾选', type: 'selection' },
|
||
{ label: '导入总数', prop: 'importTotal' },
|
||
{ label: '成功', prop: 'success' },
|
||
{ label: '失败', prop: 'failed' },
|
||
{ label: '导入类型', prop: 'importType' },
|
||
{ label: '操作人', prop: 'operator' },
|
||
{ label: '操作时间', prop: 'operationTime' },
|
||
{ label: '操作', prop: 'operation' }
|
||
]
|
||
|
||
// 获取导入类型标签类型
|
||
const getImportTypeTagType = (type: string) => {
|
||
switch (type) {
|
||
case '套餐批量导入':
|
||
return 'primary'
|
||
case '套餐更新导入':
|
||
return 'success'
|
||
case '套餐停用导入':
|
||
return 'danger'
|
||
case '套餐启用导入':
|
||
return 'warning'
|
||
default:
|
||
return 'info'
|
||
}
|
||
}
|
||
|
||
// 显示导入对话框
|
||
const showImportDialog = () => {
|
||
importDialogVisible.value = true
|
||
// 重置表单
|
||
if (importFormRef.value) {
|
||
importFormRef.value.resetFields()
|
||
}
|
||
importFormData.importType = ''
|
||
importFormData.excelFile = null
|
||
importFormData.remark = ''
|
||
}
|
||
|
||
// 下载模板
|
||
const downloadTemplate = () => {
|
||
ElMessage.success('正在下载批量导入模板...')
|
||
// 这里可以实现实际的模板下载功能
|
||
}
|
||
|
||
// 查看详情
|
||
const viewDetails = (row: any) => {
|
||
ElMessage.info(`查看导入详情: ${row.importType} - 导入总数:${row.importTotal}`)
|
||
}
|
||
|
||
// 重新导入
|
||
const retryImport = (row: any) => {
|
||
ElMessage.info(`重新导入: ${row.importType}`)
|
||
}
|
||
|
||
// 删除记录
|
||
const deleteRecord = (row: any) => {
|
||
ElMessage.info(`删除导入记录: ${row.id}`)
|
||
}
|
||
|
||
// 动态列配置
|
||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||
{ type: 'selection' },
|
||
{
|
||
prop: 'importTotal',
|
||
label: '导入总数',
|
||
formatter: (row) => `${row.importTotal} `
|
||
},
|
||
{
|
||
prop: 'success',
|
||
label: '成功',
|
||
formatter: (row) => {
|
||
return h(ElTag, { type: 'success' }, () => `${row.success}`)
|
||
}
|
||
},
|
||
{
|
||
prop: 'failed',
|
||
label: '失败',
|
||
formatter: (row) => {
|
||
return h(ElTag, { type: row.failed > 0 ? 'danger' : 'success' }, () => `${row.failed}`)
|
||
}
|
||
},
|
||
{
|
||
prop: 'importType',
|
||
label: '导入类型',
|
||
formatter: (row) => {
|
||
return h(ElTag, { type: getImportTypeTagType(row.importType) }, () => row.importType)
|
||
}
|
||
},
|
||
{
|
||
prop: 'operator',
|
||
label: '操作人'
|
||
},
|
||
{
|
||
prop: 'operationTime',
|
||
label: '操作时间'
|
||
},
|
||
{
|
||
prop: 'operation',
|
||
label: '操作',
|
||
width: 220,
|
||
formatter: (row: any) => {
|
||
return h('div', { class: 'operation-buttons' }, [
|
||
h(ArtButtonTable, {
|
||
text: '查看',
|
||
onClick: () => viewDetails(row)
|
||
}),
|
||
h(ArtButtonTable, {
|
||
text: '重试',
|
||
onClick: () => retryImport(row)
|
||
}),
|
||
h(ArtButtonTable, {
|
||
text: '删除',
|
||
onClick: () => deleteRecord(row)
|
||
})
|
||
])
|
||
}
|
||
}
|
||
])
|
||
|
||
onMounted(() => {
|
||
getBatchImportList()
|
||
})
|
||
|
||
// 获取批量导入列表
|
||
const getBatchImportList = async () => {
|
||
loading.value = true
|
||
try {
|
||
// 模拟API调用
|
||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||
|
||
const startIndex = (pagination.currentPage - 1) * pagination.pageSize
|
||
const endIndex = startIndex + pagination.pageSize
|
||
const paginatedData = mockData.slice(startIndex, endIndex)
|
||
|
||
tableData.value = paginatedData
|
||
pagination.total = mockData.length
|
||
loading.value = false
|
||
} catch (error) {
|
||
console.error('获取批量导入列表失败:', error)
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const handleRefresh = () => {
|
||
getBatchImportList()
|
||
}
|
||
|
||
// 处理表格行选择变化
|
||
const handleSelectionChange = (selection: any[]) => {
|
||
selectedRows.value = selection
|
||
}
|
||
|
||
// 处理表格分页变化
|
||
const handleSizeChange = (newPageSize: number) => {
|
||
pagination.pageSize = newPageSize
|
||
getBatchImportList()
|
||
}
|
||
|
||
const handleCurrentChange = (newCurrentPage: number) => {
|
||
pagination.currentPage = newCurrentPage
|
||
getBatchImportList()
|
||
}
|
||
|
||
// 文件上传限制
|
||
const handleExceed = () => {
|
||
ElMessage.warning('最多只能上传一个文件')
|
||
}
|
||
|
||
// 文件上传前检查
|
||
const beforeUpload = (file: UploadRawFile) => {
|
||
const isExcel =
|
||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||
file.type === 'application/vnd.ms-excel'
|
||
const isLt10M = file.size / 1024 / 1024 < 10
|
||
|
||
if (!isExcel) {
|
||
ElMessage.error('只能上传 Excel 文件!')
|
||
return false
|
||
}
|
||
if (!isLt10M) {
|
||
ElMessage.error('上传文件大小不能超过 10MB!')
|
||
return false
|
||
}
|
||
return false // 阻止自动上传
|
||
}
|
||
|
||
// 文件变化处理
|
||
const handleFileChange = (file: UploadFile) => {
|
||
if (file.raw) {
|
||
importFormData.excelFile = file.raw
|
||
}
|
||
}
|
||
|
||
// 导入表单验证规则
|
||
const importRules = reactive<FormRules>({
|
||
importType: [{ required: true, message: '请选择导入类型', trigger: 'change' }],
|
||
excelFile: [{ required: true, message: '请上传Excel文件', trigger: 'change' }]
|
||
})
|
||
|
||
// 提交导入
|
||
const handleImportSubmit = async () => {
|
||
if (!importFormRef.value) return
|
||
|
||
// 检查文件是否上传
|
||
if (!importFormData.excelFile) {
|
||
ElMessage.error('请先上传Excel文件')
|
||
return
|
||
}
|
||
|
||
await importFormRef.value.validate((valid) => {
|
||
if (valid) {
|
||
importLoading.value = true
|
||
|
||
// 模拟导入过程
|
||
setTimeout(() => {
|
||
ElMessage.success(`${importFormData.importType}批量导入提交成功!`)
|
||
importDialogVisible.value = false
|
||
importLoading.value = false
|
||
getBatchImportList()
|
||
}, 2000)
|
||
}
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.package-batch-page {
|
||
// 可以添加特定样式
|
||
}
|
||
|
||
.template-section {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
|
||
.template-tip {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
:deep(.operation-buttons) {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.dialog-footer {
|
||
text-align: right;
|
||
}
|
||
|
||
:deep(.el-upload-dragger) {
|
||
padding: 40px;
|
||
}
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|