2025-12-05 10:17:26 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="rounded bg-[var(--el-bg-color)] border border-[var(--el-border-color)] p-5 h-full md:flex flex-1 flex-col md:overflow-auto"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 表格工具栏 -->
|
|
|
|
|
|
<div class="flex flex-col md:flex-row justify-between gap-y-2.5 mb-2.5">
|
|
|
|
|
|
<!-- 左侧工具栏 -->
|
|
|
|
|
|
<div class="toolbar-left flex gap-y-2.5 gap-x-2 md:gap-x-3 flex-wrap">
|
|
|
|
|
|
<template v-for="(btn, index) in toolbarLeftBtn" :key="index">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-hasPerm="btn.perm ?? '*:*:*'"
|
|
|
|
|
|
v-bind="btn.attrs"
|
|
|
|
|
|
:disabled="btn.name === 'delete' && removeIds.length === 0"
|
|
|
|
|
|
@click="handleToolbar(btn.name)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ btn.text }}
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 右侧工具栏 -->
|
|
|
|
|
|
<div class="toolbar-right flex gap-y-2.5 gap-x-2 md:gap-x-3 flex-wrap">
|
|
|
|
|
|
<template v-for="(btn, index) in toolbarRightBtn" :key="index">
|
|
|
|
|
|
<el-popover v-if="btn.name === 'filter'" placement="bottom" trigger="click">
|
|
|
|
|
|
<template #reference>
|
|
|
|
|
|
<el-button v-bind="btn.attrs"></el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<el-scrollbar max-height="350px">
|
|
|
|
|
|
<template v-for="col in cols" :key="col.prop">
|
|
|
|
|
|
<el-checkbox v-if="col.prop" v-model="col.show" :label="col.label" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
|
</el-popover>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-else
|
|
|
|
|
|
v-hasPerm="btn.perm ?? '*:*:*'"
|
|
|
|
|
|
v-bind="btn.attrs"
|
|
|
|
|
|
@click="handleToolbar(btn.name)"
|
|
|
|
|
|
></el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 列表 -->
|
|
|
|
|
|
<el-table
|
|
|
|
|
|
ref="tableRef"
|
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
|
v-bind="contentConfig.table"
|
|
|
|
|
|
:data="pageData"
|
|
|
|
|
|
:row-key="pk"
|
|
|
|
|
|
class="flex-1"
|
|
|
|
|
|
@selection-change="handleSelectionChange"
|
|
|
|
|
|
@filter-change="handleFilterChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template v-for="col in cols" :key="col.prop">
|
|
|
|
|
|
<el-table-column v-if="col.show" v-bind="col">
|
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<!-- 显示图片 -->
|
|
|
|
|
|
<template v-if="col.templet === 'image'">
|
|
|
|
|
|
<template v-if="col.prop">
|
|
|
|
|
|
<template v-if="Array.isArray(scope.row[col.prop])">
|
|
|
|
|
|
<template v-for="(item, index) in scope.row[col.prop]" :key="item">
|
|
|
|
|
|
<el-image
|
|
|
|
|
|
:src="item"
|
|
|
|
|
|
:preview-src-list="scope.row[col.prop]"
|
|
|
|
|
|
:initial-index="index"
|
|
|
|
|
|
:preview-teleported="true"
|
|
|
|
|
|
:style="`width: ${col.imageWidth ?? 40}px; height: ${col.imageHeight ?? 40}px`"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<el-image
|
|
|
|
|
|
:src="scope.row[col.prop]"
|
|
|
|
|
|
:preview-src-list="[scope.row[col.prop]]"
|
|
|
|
|
|
:preview-teleported="true"
|
|
|
|
|
|
:style="`width: ${col.imageWidth ?? 40}px; height: ${col.imageHeight ?? 40}px`"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 根据行的selectList属性返回对应列表值 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'list'">
|
|
|
|
|
|
<template v-if="col.prop">
|
|
|
|
|
|
{{ (col.selectList ?? {})[scope.row[col.prop]] }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 格式化显示链接 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'url'">
|
|
|
|
|
|
<template v-if="col.prop">
|
|
|
|
|
|
<el-link type="primary" :href="scope.row[col.prop]" target="_blank">
|
|
|
|
|
|
{{ scope.row[col.prop] }}
|
|
|
|
|
|
</el-link>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 生成开关组件 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'switch'">
|
|
|
|
|
|
<template v-if="col.prop">
|
|
|
|
|
|
<!-- pageData.length>0: 解决el-switch组件会在表格初始化的时候触发一次change事件 -->
|
|
|
|
|
|
<el-switch
|
|
|
|
|
|
v-model="scope.row[col.prop]"
|
|
|
|
|
|
:active-value="col.activeValue ?? 1"
|
|
|
|
|
|
:inactive-value="col.inactiveValue ?? 0"
|
|
|
|
|
|
:inline-prompt="true"
|
|
|
|
|
|
:active-text="col.activeText ?? ''"
|
|
|
|
|
|
:inactive-text="col.inactiveText ?? ''"
|
|
|
|
|
|
:validate-event="false"
|
|
|
|
|
|
:disabled="!hasButtonPerm(col.prop)"
|
|
|
|
|
|
@change="
|
|
|
|
|
|
pageData.length > 0 && handleModify(col.prop, scope.row[col.prop], scope.row)
|
|
|
|
|
|
"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 生成输入框组件 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'input'">
|
|
|
|
|
|
<template v-if="col.prop">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="scope.row[col.prop]"
|
|
|
|
|
|
:type="col.inputType ?? 'text'"
|
|
|
|
|
|
:disabled="!hasButtonPerm(col.prop)"
|
|
|
|
|
|
@blur="handleModify(col.prop, scope.row[col.prop], scope.row)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 格式化为价格 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'price'">
|
|
|
|
|
|
<template v-if="col.prop">
|
2025-12-11 17:13:37 +08:00
|
|
|
|
{{ `${col.priceFormat ?? '¥'}${scope.row[col.prop]}` }}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 格式化为百分比 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'percent'">
|
|
|
|
|
|
<template v-if="col.prop">{{ scope.row[col.prop] }}%</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 显示图标 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'icon'">
|
|
|
|
|
|
<template v-if="col.prop">
|
|
|
|
|
|
<template v-if="scope.row[col.prop].startsWith('el-icon-')">
|
|
|
|
|
|
<el-icon>
|
|
|
|
|
|
<component :is="scope.row[col.prop].replace('el-icon-', '')" />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<div class="i-svg:{{ scope.row[col.prop] }}" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 格式化时间 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'date'">
|
|
|
|
|
|
<template v-if="col.prop">
|
|
|
|
|
|
{{
|
|
|
|
|
|
scope.row[col.prop]
|
2025-12-11 17:13:37 +08:00
|
|
|
|
? useDateFormat(scope.row[col.prop], col.dateFormat ?? 'YYYY-MM-DD HH:mm:ss')
|
2025-12-05 10:17:26 +08:00
|
|
|
|
.value
|
2025-12-11 17:13:37 +08:00
|
|
|
|
: ''
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 列操作栏 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'tool'">
|
|
|
|
|
|
<template v-for="(btn, index) in tableToolbarBtn" :key="index">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="btn.render === undefined || btn.render(scope.row)"
|
|
|
|
|
|
v-hasPerm="btn.perm ?? '*:*:*'"
|
|
|
|
|
|
v-bind="btn.attrs"
|
|
|
|
|
|
@click="
|
|
|
|
|
|
handleOperate({
|
|
|
|
|
|
name: btn.name,
|
|
|
|
|
|
row: scope.row,
|
|
|
|
|
|
column: scope.column,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
$index: scope.$index
|
2025-12-05 10:17:26 +08:00
|
|
|
|
})
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ btn.text }}
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 自定义 -->
|
|
|
|
|
|
<template v-else-if="col.templet === 'custom'">
|
|
|
|
|
|
<slot :name="col.slotName ?? col.prop" :prop="col.prop" v-bind="scope" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
|
<div v-if="showPagination" class="mt-4">
|
|
|
|
|
|
<el-scrollbar :class="['h-8!', { 'flex-x-end': contentConfig?.pagePosition === 'right' }]">
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
|
v-bind="pagination"
|
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 导出弹窗 -->
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="exportsModalVisible"
|
|
|
|
|
|
:align-center="true"
|
|
|
|
|
|
title="导出数据"
|
|
|
|
|
|
width="600px"
|
|
|
|
|
|
style="padding-right: 0"
|
|
|
|
|
|
@close="handleCloseExportsModal"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 滚动 -->
|
|
|
|
|
|
<el-scrollbar max-height="60vh">
|
|
|
|
|
|
<!-- 表单 -->
|
|
|
|
|
|
<el-form
|
|
|
|
|
|
ref="exportsFormRef"
|
|
|
|
|
|
style="padding-right: var(--el-dialog-padding-primary)"
|
|
|
|
|
|
:model="exportsFormData"
|
|
|
|
|
|
:rules="exportsFormRules"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-form-item label="文件名" prop="filename">
|
|
|
|
|
|
<el-input v-model="exportsFormData.filename" clearable />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="工作表名" prop="sheetname">
|
|
|
|
|
|
<el-input v-model="exportsFormData.sheetname" clearable />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="数据源" prop="origin">
|
|
|
|
|
|
<el-select v-model="exportsFormData.origin">
|
|
|
|
|
|
<el-option label="当前数据 (当前页的数据)" :value="ExportsOriginEnum.CURRENT" />
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
label="选中数据 (所有选中的数据)"
|
|
|
|
|
|
:value="ExportsOriginEnum.SELECTED"
|
|
|
|
|
|
:disabled="selectionData.length <= 0"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
label="全量数据 (所有分页的数据)"
|
|
|
|
|
|
:value="ExportsOriginEnum.REMOTE"
|
|
|
|
|
|
:disabled="contentConfig.exportsAction === undefined"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="字段" prop="fields">
|
|
|
|
|
|
<el-checkbox-group v-model="exportsFormData.fields">
|
|
|
|
|
|
<template v-for="col in cols" :key="col.prop">
|
|
|
|
|
|
<el-checkbox v-if="col.prop" :value="col.prop" :label="col.label" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-checkbox-group>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
|
<!-- 弹窗底部操作按钮 -->
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<div style="padding-right: var(--el-dialog-padding-primary)">
|
|
|
|
|
|
<el-button type="primary" @click="handleExportsSubmit">确 定</el-button>
|
|
|
|
|
|
<el-button @click="handleCloseExportsModal">取 消</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 导入弹窗 -->
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="importModalVisible"
|
|
|
|
|
|
:align-center="true"
|
|
|
|
|
|
title="导入数据"
|
|
|
|
|
|
width="600px"
|
|
|
|
|
|
style="padding-right: 0"
|
|
|
|
|
|
@close="handleCloseImportModal"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 滚动 -->
|
|
|
|
|
|
<el-scrollbar max-height="60vh">
|
|
|
|
|
|
<!-- 表单 -->
|
|
|
|
|
|
<el-form
|
|
|
|
|
|
ref="importFormRef"
|
|
|
|
|
|
style="padding-right: var(--el-dialog-padding-primary)"
|
|
|
|
|
|
:model="importFormData"
|
|
|
|
|
|
:rules="importFormRules"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-form-item label="文件名" prop="files">
|
|
|
|
|
|
<el-upload
|
|
|
|
|
|
ref="uploadRef"
|
|
|
|
|
|
v-model:file-list="importFormData.files"
|
|
|
|
|
|
class="w-full"
|
|
|
|
|
|
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
|
|
|
|
|
:drag="true"
|
|
|
|
|
|
:limit="1"
|
|
|
|
|
|
:auto-upload="false"
|
|
|
|
|
|
:on-exceed="handleFileExceed"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
|
|
|
|
|
<div class="el-upload__text">
|
|
|
|
|
|
<span>将文件拖到此处,或</span>
|
|
|
|
|
|
<em>点击上传</em>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template #tip>
|
|
|
|
|
|
<div class="el-upload__tip">
|
|
|
|
|
|
*.xlsx / *.xls
|
|
|
|
|
|
<el-link
|
|
|
|
|
|
v-if="contentConfig.importTemplate"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
icon="download"
|
|
|
|
|
|
underline="never"
|
|
|
|
|
|
@click="handleDownloadTemplate"
|
|
|
|
|
|
>
|
|
|
|
|
|
下载模板
|
|
|
|
|
|
</el-link>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-upload>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
|
<!-- 弹窗底部操作按钮 -->
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<div style="padding-right: var(--el-dialog-padding-primary)">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
:disabled="importFormData.files.length === 0"
|
|
|
|
|
|
@click="handleImportSubmit"
|
|
|
|
|
|
>
|
|
|
|
|
|
确 定
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button @click="handleCloseImportModal">取 消</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-12-11 17:13:37 +08:00
|
|
|
|
import { hasPerm } from '@/utils/auth'
|
|
|
|
|
|
import { useDateFormat, useThrottleFn } from '@vueuse/core'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
import {
|
|
|
|
|
|
genFileId,
|
|
|
|
|
|
type FormInstance,
|
|
|
|
|
|
type FormRules,
|
|
|
|
|
|
type UploadInstance,
|
|
|
|
|
|
type UploadRawFile,
|
|
|
|
|
|
type UploadUserFile,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
type TableInstance
|
|
|
|
|
|
} from 'element-plus'
|
|
|
|
|
|
import ExcelJS from 'exceljs'
|
|
|
|
|
|
import { reactive, ref, computed } from 'vue'
|
|
|
|
|
|
import type { IContentConfig, IObject, IOperateData } from './types'
|
|
|
|
|
|
import type { IToolsButton } from './types'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 定义接收的属性
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const props = defineProps<{ contentConfig: IContentConfig }>()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 定义自定义事件
|
|
|
|
|
|
const emit = defineEmits<{
|
2025-12-11 17:13:37 +08:00
|
|
|
|
addClick: []
|
|
|
|
|
|
exportClick: []
|
|
|
|
|
|
searchClick: []
|
|
|
|
|
|
toolbarClick: [name: string]
|
|
|
|
|
|
editClick: [row: IObject]
|
|
|
|
|
|
filterChange: [data: IObject]
|
|
|
|
|
|
operateClick: [data: IOperateData]
|
|
|
|
|
|
}>()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 表格工具栏按钮配置
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const config = computed(() => props.contentConfig)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
const buttonConfig = reactive<Record<string, IObject>>({
|
2025-12-11 17:13:37 +08:00
|
|
|
|
add: { text: '新增', attrs: { icon: 'plus', type: 'success' }, perm: 'add' },
|
|
|
|
|
|
delete: { text: '删除', attrs: { icon: 'delete', type: 'danger' }, perm: 'delete' },
|
|
|
|
|
|
import: { text: '导入', attrs: { icon: 'upload', type: '' }, perm: 'import' },
|
|
|
|
|
|
export: { text: '导出', attrs: { icon: 'download', type: '' }, perm: 'export' },
|
|
|
|
|
|
refresh: { text: '刷新', attrs: { icon: 'refresh', type: '' }, perm: '*:*:*' },
|
|
|
|
|
|
filter: { text: '筛选列', attrs: { icon: 'operation', type: '' }, perm: '*:*:*' },
|
|
|
|
|
|
search: { text: '搜索', attrs: { icon: 'search', type: '' }, perm: 'search' },
|
|
|
|
|
|
imports: { text: '批量导入', attrs: { icon: 'upload', type: '' }, perm: 'imports' },
|
|
|
|
|
|
exports: { text: '批量导出', attrs: { icon: 'download', type: '' }, perm: 'exports' },
|
|
|
|
|
|
view: { text: '查看', attrs: { icon: 'view', type: 'primary' }, perm: 'view' },
|
|
|
|
|
|
edit: { text: '编辑', attrs: { icon: 'edit', type: 'primary' }, perm: 'edit' }
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 主键
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const pk = props.contentConfig.pk ?? 'id'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 权限名称前缀
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const authPrefix = computed(() => props.contentConfig.permPrefix)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取按钮权限标识
|
|
|
|
|
|
function getButtonPerm(action: string): string | null {
|
|
|
|
|
|
// 如果action已经包含完整路径(包含冒号),则直接使用
|
2025-12-11 17:13:37 +08:00
|
|
|
|
if (action.includes(':')) {
|
|
|
|
|
|
return action
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 否则使用权限前缀组合
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return authPrefix.value ? `${authPrefix.value}:${action}` : null
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有权限
|
|
|
|
|
|
function hasButtonPerm(action: string): boolean {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const perm = getButtonPerm(action)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 如果没有设置权限标识,则默认具有权限
|
2025-12-11 17:13:37 +08:00
|
|
|
|
if (!perm) return true
|
|
|
|
|
|
return hasPerm(perm)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建工具栏按钮
|
|
|
|
|
|
function createToolbar(toolbar: Array<string | IToolsButton>, attr = {}) {
|
|
|
|
|
|
return toolbar.map((item) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const isString = typeof item === 'string'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
return {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
name: isString ? item : item?.name || '',
|
2025-12-05 10:17:26 +08:00
|
|
|
|
text: isString ? buttonConfig[item].text : item?.text,
|
|
|
|
|
|
attrs: {
|
|
|
|
|
|
...attr,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
...(isString ? buttonConfig[item].attrs : item?.attrs)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
},
|
|
|
|
|
|
render: isString ? undefined : (item?.render ?? undefined),
|
|
|
|
|
|
perm: isString
|
|
|
|
|
|
? getButtonPerm(buttonConfig[item].perm)
|
|
|
|
|
|
: item?.perm
|
|
|
|
|
|
? getButtonPerm(item.perm as string)
|
2025-12-11 17:13:37 +08:00
|
|
|
|
: '*:*:*'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 左侧工具栏按钮
|
|
|
|
|
|
const toolbarLeftBtn = computed(() => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
if (!config.value.toolbar || config.value.toolbar.length === 0) return []
|
|
|
|
|
|
return createToolbar(config.value.toolbar, {})
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 右侧工具栏按钮
|
|
|
|
|
|
const toolbarRightBtn = computed(() => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
if (!config.value.defaultToolbar || config.value.defaultToolbar.length === 0) return []
|
|
|
|
|
|
return createToolbar(config.value.defaultToolbar, { circle: true })
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 表格操作工具栏
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const tableToolbar = config.value.cols[config.value.cols.length - 1].operat ?? ['edit', 'delete']
|
|
|
|
|
|
const tableToolbarBtn = createToolbar(tableToolbar, { link: true, size: 'small' })
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 表格列
|
|
|
|
|
|
const cols = ref(
|
|
|
|
|
|
props.contentConfig.cols.map((col) => {
|
|
|
|
|
|
if (col.initFn) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
col.initFn(col)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (col.show === undefined) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
col.show = true
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
if (col.prop !== undefined && col.columnKey === undefined && col['column-key'] === undefined) {
|
|
|
|
|
|
col.columnKey = col.prop
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (
|
2025-12-11 17:13:37 +08:00
|
|
|
|
col.type === 'selection' &&
|
2025-12-05 10:17:26 +08:00
|
|
|
|
col.reserveSelection === undefined &&
|
2025-12-11 17:13:37 +08:00
|
|
|
|
col['reserve-selection'] === undefined
|
2025-12-05 10:17:26 +08:00
|
|
|
|
) {
|
|
|
|
|
|
// 配合表格row-key实现跨页多选
|
2025-12-11 17:13:37 +08:00
|
|
|
|
col.reserveSelection = true
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return col
|
2025-12-05 10:17:26 +08:00
|
|
|
|
})
|
2025-12-11 17:13:37 +08:00
|
|
|
|
)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 加载状态
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const loading = ref(false)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 列表数据
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const pageData = ref<IObject[]>([])
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 显示分页
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const showPagination = props.contentConfig.pagination !== false
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 分页配置
|
|
|
|
|
|
const defaultPagination = {
|
|
|
|
|
|
background: true,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
layout: 'total, sizes, prev, pager, next, jumper',
|
2025-12-05 10:17:26 +08:00
|
|
|
|
pageSize: 20,
|
|
|
|
|
|
pageSizes: [10, 20, 30, 50],
|
|
|
|
|
|
total: 0,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
currentPage: 1
|
|
|
|
|
|
}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
const pagination = reactive(
|
2025-12-11 17:13:37 +08:00
|
|
|
|
typeof props.contentConfig.pagination === 'object'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
? { ...defaultPagination, ...props.contentConfig.pagination }
|
|
|
|
|
|
: defaultPagination
|
2025-12-11 17:13:37 +08:00
|
|
|
|
)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 分页相关的请求参数
|
|
|
|
|
|
const request = props.contentConfig.request ?? {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
pageName: 'pageNum',
|
|
|
|
|
|
limitName: 'pageSize'
|
|
|
|
|
|
}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const tableRef = ref<TableInstance>()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 行选中
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const selectionData = ref<IObject[]>([])
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 删除ID集合 用于批量删除
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const removeIds = ref<(number | string)[]>([])
|
2025-12-05 10:17:26 +08:00
|
|
|
|
function handleSelectionChange(selection: any[]) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
selectionData.value = selection
|
|
|
|
|
|
removeIds.value = selection.map((item) => item[pk])
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取行选中
|
|
|
|
|
|
function getSelectionData() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return selectionData.value
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新
|
|
|
|
|
|
function handleRefresh(isRestart = false) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
fetchPageData(lastFormData, isRestart)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除
|
|
|
|
|
|
function handleDelete(id?: number | string) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const ids = [id || removeIds.value].join(',')
|
2025-12-05 10:17:26 +08:00
|
|
|
|
if (!ids) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.warning('请勾选删除项')
|
|
|
|
|
|
return
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessageBox.confirm('确认删除?', '警告', {
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
})
|
|
|
|
|
|
.then(function () {
|
|
|
|
|
|
if (props.contentConfig.deleteAction) {
|
|
|
|
|
|
props.contentConfig
|
|
|
|
|
|
.deleteAction(ids)
|
|
|
|
|
|
.then(() => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
|
removeIds.value = []
|
2025-12-05 10:17:26 +08:00
|
|
|
|
//清空选中项
|
2025-12-11 17:13:37 +08:00
|
|
|
|
tableRef.value?.clearSelection()
|
|
|
|
|
|
handleRefresh(true)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
})
|
2025-12-11 17:13:37 +08:00
|
|
|
|
.catch(() => {})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.error('未配置deleteAction')
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-12-11 17:13:37 +08:00
|
|
|
|
.catch(() => {})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出表单
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const fields: string[] = []
|
2025-12-05 10:17:26 +08:00
|
|
|
|
cols.value.forEach((item) => {
|
|
|
|
|
|
if (item.prop !== undefined) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
fields.push(item.prop)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
const enum ExportsOriginEnum {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
CURRENT = 'current',
|
|
|
|
|
|
SELECTED = 'selected',
|
|
|
|
|
|
REMOTE = 'remote'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const exportsModalVisible = ref(false)
|
|
|
|
|
|
const exportsFormRef = ref<FormInstance>()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
const exportsFormData = reactive({
|
2025-12-11 17:13:37 +08:00
|
|
|
|
filename: '',
|
|
|
|
|
|
sheetname: '',
|
2025-12-05 10:17:26 +08:00
|
|
|
|
fields,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
origin: ExportsOriginEnum.CURRENT
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
const exportsFormRules: FormRules = {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
fields: [{ required: true, message: '请选择字段' }],
|
|
|
|
|
|
origin: [{ required: true, message: '请选择数据源' }]
|
|
|
|
|
|
}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 打开导出弹窗
|
|
|
|
|
|
function handleOpenExportsModal() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
exportsModalVisible.value = true
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 导出确认
|
|
|
|
|
|
const handleExportsSubmit = useThrottleFn(() => {
|
|
|
|
|
|
exportsFormRef.value?.validate((valid: boolean) => {
|
|
|
|
|
|
if (valid) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
handleExports()
|
|
|
|
|
|
handleCloseExportsModal()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
})
|
|
|
|
|
|
}, 3000)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 关闭导出弹窗
|
|
|
|
|
|
function handleCloseExportsModal() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
exportsModalVisible.value = false
|
|
|
|
|
|
exportsFormRef.value?.resetFields()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
nextTick(() => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
exportsFormRef.value?.clearValidate()
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 导出
|
|
|
|
|
|
function handleExports() {
|
|
|
|
|
|
const filename = exportsFormData.filename
|
|
|
|
|
|
? exportsFormData.filename
|
2025-12-11 17:13:37 +08:00
|
|
|
|
: props.contentConfig.permPrefix || 'export'
|
|
|
|
|
|
const sheetname = exportsFormData.sheetname ? exportsFormData.sheetname : 'sheet'
|
|
|
|
|
|
const workbook = new ExcelJS.Workbook()
|
|
|
|
|
|
const worksheet = workbook.addWorksheet(sheetname)
|
|
|
|
|
|
const columns: Partial<ExcelJS.Column>[] = []
|
2025-12-05 10:17:26 +08:00
|
|
|
|
cols.value.forEach((col) => {
|
|
|
|
|
|
if (col.label && col.prop && exportsFormData.fields.includes(col.prop)) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
columns.push({ header: col.label, key: col.prop })
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
})
|
|
|
|
|
|
worksheet.columns = columns
|
2025-12-05 10:17:26 +08:00
|
|
|
|
if (exportsFormData.origin === ExportsOriginEnum.REMOTE) {
|
|
|
|
|
|
if (props.contentConfig.exportsAction) {
|
|
|
|
|
|
props.contentConfig.exportsAction(lastFormData).then((res) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
worksheet.addRows(res)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
workbook.xlsx
|
|
|
|
|
|
.writeBuffer()
|
|
|
|
|
|
.then((buffer) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
saveXlsx(buffer, filename as string)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
})
|
2025-12-11 17:13:37 +08:00
|
|
|
|
.catch((error) => console.log(error))
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.error('未配置exportsAction')
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
worksheet.addRows(
|
|
|
|
|
|
exportsFormData.origin === ExportsOriginEnum.SELECTED ? selectionData.value : pageData.value
|
2025-12-11 17:13:37 +08:00
|
|
|
|
)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
workbook.xlsx
|
|
|
|
|
|
.writeBuffer()
|
|
|
|
|
|
.then((buffer) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
saveXlsx(buffer, filename as string)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
})
|
2025-12-11 17:13:37 +08:00
|
|
|
|
.catch((error) => console.log(error))
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导入表单
|
2025-12-11 17:13:37 +08:00
|
|
|
|
let isFileImport = false
|
|
|
|
|
|
const uploadRef = ref<UploadInstance>()
|
|
|
|
|
|
const importModalVisible = ref(false)
|
|
|
|
|
|
const importFormRef = ref<FormInstance>()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
const importFormData = reactive<{
|
2025-12-11 17:13:37 +08:00
|
|
|
|
files: UploadUserFile[]
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}>({
|
2025-12-11 17:13:37 +08:00
|
|
|
|
files: []
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
const importFormRules: FormRules = {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
files: [{ required: true, message: '请选择文件' }]
|
|
|
|
|
|
}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 打开导入弹窗
|
|
|
|
|
|
function handleOpenImportModal(isFile: boolean = false) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
importModalVisible.value = true
|
|
|
|
|
|
isFileImport = isFile
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 覆盖前一个文件
|
|
|
|
|
|
function handleFileExceed(files: File[]) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
uploadRef.value!.clearFiles()
|
|
|
|
|
|
const file = files[0] as UploadRawFile
|
|
|
|
|
|
file.uid = genFileId()
|
|
|
|
|
|
uploadRef.value!.handleStart(file)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 下载导入模板
|
|
|
|
|
|
function handleDownloadTemplate() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const importTemplate = props.contentConfig.importTemplate
|
|
|
|
|
|
if (typeof importTemplate === 'string') {
|
|
|
|
|
|
window.open(importTemplate)
|
|
|
|
|
|
} else if (typeof importTemplate === 'function') {
|
2025-12-05 10:17:26 +08:00
|
|
|
|
importTemplate().then((response) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const fileData = response.data
|
2025-12-05 10:17:26 +08:00
|
|
|
|
const fileName = decodeURI(
|
2025-12-11 17:13:37 +08:00
|
|
|
|
response.headers['content-disposition'].split(';')[1].split('=')[1]
|
|
|
|
|
|
)
|
|
|
|
|
|
saveXlsx(fileData, fileName)
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.error('未配置importTemplate')
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 导入确认
|
|
|
|
|
|
const handleImportSubmit = useThrottleFn(() => {
|
|
|
|
|
|
importFormRef.value?.validate((valid: boolean) => {
|
|
|
|
|
|
if (valid) {
|
|
|
|
|
|
if (isFileImport) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
handleImport()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
handleImports()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
})
|
|
|
|
|
|
}, 3000)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 关闭导入弹窗
|
|
|
|
|
|
function handleCloseImportModal() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
importModalVisible.value = false
|
|
|
|
|
|
importFormRef.value?.resetFields()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
nextTick(() => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
importFormRef.value?.clearValidate()
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 文件导入
|
|
|
|
|
|
function handleImport() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const importAction = props.contentConfig.importAction
|
2025-12-05 10:17:26 +08:00
|
|
|
|
if (importAction === undefined) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.error('未配置importAction')
|
|
|
|
|
|
return
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
importAction(importFormData.files[0].raw as File).then(() => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.success('导入数据成功')
|
|
|
|
|
|
handleCloseImportModal()
|
|
|
|
|
|
handleRefresh(true)
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 导入
|
|
|
|
|
|
function handleImports() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const importsAction = props.contentConfig.importsAction
|
2025-12-05 10:17:26 +08:00
|
|
|
|
if (importsAction === undefined) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.error('未配置importsAction')
|
|
|
|
|
|
return
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 获取选择的文件
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const file = importFormData.files[0].raw as File
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 创建Workbook实例
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const workbook = new ExcelJS.Workbook()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 使用FileReader对象来读取文件内容
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const fileReader = new FileReader()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 二进制字符串的形式加载文件
|
2025-12-11 17:13:37 +08:00
|
|
|
|
fileReader.readAsArrayBuffer(file)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
fileReader.onload = (ev) => {
|
|
|
|
|
|
if (ev.target !== null && ev.target.result !== null) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const result = ev.target.result as ArrayBuffer
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 从 buffer中加载数据解析
|
|
|
|
|
|
workbook.xlsx
|
|
|
|
|
|
.load(result)
|
|
|
|
|
|
.then((workbook) => {
|
|
|
|
|
|
// 解析后的数据
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const data = []
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 获取第一个worksheet内容
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const worksheet = workbook.getWorksheet(1)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
if (worksheet) {
|
|
|
|
|
|
// 获取第一行的标题
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const fields: any[] = []
|
2025-12-05 10:17:26 +08:00
|
|
|
|
worksheet.getRow(1).eachCell((cell) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
fields.push(cell.value)
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 遍历工作表的每一行(从第二行开始,因为第一行通常是标题行)
|
|
|
|
|
|
for (let rowNumber = 2; rowNumber <= worksheet.rowCount; rowNumber++) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const rowData: IObject = {}
|
|
|
|
|
|
const row = worksheet.getRow(rowNumber)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 遍历当前行的每个单元格
|
|
|
|
|
|
row.eachCell((cell, colNumber) => {
|
|
|
|
|
|
// 获取标题对应的键,并将当前单元格的值存储到相应的属性名中
|
2025-12-11 17:13:37 +08:00
|
|
|
|
rowData[fields[colNumber - 1]] = cell.value
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 将当前行的数据对象添加到数组中
|
2025-12-11 17:13:37 +08:00
|
|
|
|
data.push(rowData)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (data.length === 0) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.error('未解析到数据')
|
|
|
|
|
|
return
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
importsAction(data).then(() => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.success('导入数据成功')
|
|
|
|
|
|
handleCloseImportModal()
|
|
|
|
|
|
handleRefresh(true)
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
})
|
2025-12-11 17:13:37 +08:00
|
|
|
|
.catch((error) => console.log(error))
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.error('读取文件失败')
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 操作栏
|
|
|
|
|
|
function handleToolbar(name: string) {
|
|
|
|
|
|
switch (name) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
case 'refresh':
|
|
|
|
|
|
handleRefresh()
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'exports':
|
|
|
|
|
|
handleOpenExportsModal()
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'imports':
|
|
|
|
|
|
handleOpenImportModal()
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'search':
|
|
|
|
|
|
emit('searchClick')
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'add':
|
|
|
|
|
|
emit('addClick')
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'delete':
|
|
|
|
|
|
handleDelete()
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'import':
|
|
|
|
|
|
handleOpenImportModal(true)
|
|
|
|
|
|
break
|
|
|
|
|
|
case 'export':
|
|
|
|
|
|
emit('exportClick')
|
|
|
|
|
|
break
|
2025-12-05 10:17:26 +08:00
|
|
|
|
default:
|
2025-12-11 17:13:37 +08:00
|
|
|
|
emit('toolbarClick', name)
|
|
|
|
|
|
break
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 操作列
|
|
|
|
|
|
function handleOperate(data: IOperateData) {
|
|
|
|
|
|
switch (data.name) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
case 'delete':
|
2025-12-05 10:17:26 +08:00
|
|
|
|
if (props.contentConfig?.deleteAction) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
handleDelete(data.row[pk])
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
emit('operateClick', data)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
break
|
2025-12-05 10:17:26 +08:00
|
|
|
|
default:
|
2025-12-11 17:13:37 +08:00
|
|
|
|
emit('operateClick', data)
|
|
|
|
|
|
break
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 属性修改
|
|
|
|
|
|
function handleModify(field: string, value: boolean | string | number, row: Record<string, any>) {
|
|
|
|
|
|
if (props.contentConfig.modifyAction) {
|
|
|
|
|
|
props.contentConfig.modifyAction({
|
|
|
|
|
|
[pk]: row[pk],
|
|
|
|
|
|
field,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
value
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.error('未配置modifyAction')
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 分页切换
|
|
|
|
|
|
function handleSizeChange(value: number) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
pagination.pageSize = value
|
|
|
|
|
|
handleRefresh()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
function handleCurrentChange(value: number) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
pagination.currentPage = value
|
|
|
|
|
|
handleRefresh()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 远程数据筛选
|
2025-12-11 17:13:37 +08:00
|
|
|
|
let filterParams: IObject = {}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
function handleFilterChange(newFilters: any) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const filters: IObject = {}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
for (const key in newFilters) {
|
|
|
|
|
|
const col = cols.value.find((col) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return col.columnKey === key || col['column-key'] === key
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
if (col && col.filterJoin !== undefined) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
filters[key] = newFilters[key].join(col.filterJoin)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
filters[key] = newFilters[key]
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
filterParams = { ...filterParams, ...filters }
|
|
|
|
|
|
emit('filterChange', filterParams)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取筛选条件
|
|
|
|
|
|
function getFilterParams() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return filterParams
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取分页数据
|
2025-12-11 17:13:37 +08:00
|
|
|
|
let lastFormData = {}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
function fetchPageData(formData: IObject = {}, isRestart = false) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
loading.value = true
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 上一次搜索条件
|
2025-12-11 17:13:37 +08:00
|
|
|
|
lastFormData = formData
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 重置页码
|
|
|
|
|
|
if (isRestart) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
pagination.currentPage = 1
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
props.contentConfig
|
|
|
|
|
|
.indexAction(
|
|
|
|
|
|
showPagination
|
|
|
|
|
|
? {
|
|
|
|
|
|
[request.pageName]: pagination.currentPage,
|
|
|
|
|
|
[request.limitName]: pagination.pageSize,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
...formData
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
: formData
|
|
|
|
|
|
)
|
|
|
|
|
|
.then((data) => {
|
|
|
|
|
|
if (showPagination) {
|
|
|
|
|
|
if (props.contentConfig.parseData) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
data = props.contentConfig.parseData(data)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
pagination.total = data.total
|
|
|
|
|
|
pageData.value = data.list
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
pageData.value = data
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
loading.value = false
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
fetchPageData()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 导出Excel
|
|
|
|
|
|
function exportPageData(formData: IObject = {}) {
|
|
|
|
|
|
if (props.contentConfig.exportAction) {
|
|
|
|
|
|
props.contentConfig.exportAction(formData).then((response) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const fileData = response.data
|
2025-12-05 10:17:26 +08:00
|
|
|
|
const fileName = decodeURI(
|
2025-12-11 17:13:37 +08:00
|
|
|
|
response.headers['content-disposition'].split(';')[1].split('=')[1]
|
|
|
|
|
|
)
|
|
|
|
|
|
saveXlsx(fileData, fileName)
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.error('未配置exportAction')
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 浏览器保存文件
|
|
|
|
|
|
function saveXlsx(fileData: any, fileName: string) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const blob = new Blob([fileData], { type: fileType })
|
|
|
|
|
|
const downloadUrl = window.URL.createObjectURL(blob)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const downloadLink = document.createElement('a')
|
|
|
|
|
|
downloadLink.href = downloadUrl
|
|
|
|
|
|
downloadLink.download = fileName
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
2025-12-11 17:13:37 +08:00
|
|
|
|
document.body.appendChild(downloadLink)
|
|
|
|
|
|
downloadLink.click()
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
2025-12-11 17:13:37 +08:00
|
|
|
|
document.body.removeChild(downloadLink)
|
|
|
|
|
|
window.URL.revokeObjectURL(downloadUrl)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 暴露的属性和方法
|
2025-12-11 17:13:37 +08:00
|
|
|
|
defineExpose({ fetchPageData, exportPageData, getFilterParams, getSelectionData, handleRefresh })
|
2025-12-05 10:17:26 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.toolbar-left,
|
|
|
|
|
|
.toolbar-right {
|
|
|
|
|
|
.el-button {
|
|
|
|
|
|
margin-right: 0 !important;
|
|
|
|
|
|
margin-left: 0 !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|