20260518172616

This commit is contained in:
oneao committed 2026-05-18 17:26:17 +08:00
1 parent 2e704bbbb0
commit 5cfc87eee8
8 files changed
+500 -276

No files matched your search

@@ -304,4 +304,81 @@ public class FileController {
}
}
@PostMapping("/deletes")
@Transactional(rollbackFor = Exception.class)
public Result<Void> deleteFiles(@RequestBody List<Object> subids) {
try {
if (subids == null || subids.isEmpty()) {
return Result.ok();
}
for (Object subidObj : subids) {
if (subidObj == null) {
continue;
}
Long subid = Long.valueOf(subidObj.toString());
// 1. 查询文件记录
List<Map<String, Object>> maps = dbUtils.loadData(
"SELECT * FROM bf_file WHERE subid = ?",
subid
);
if (maps.isEmpty()) {
continue;
}
Map<String, Object> objectMap = maps.get(0);
// 2. 获取文件路径
String mxMapfilename = objectMap.get("mx_mapfilename") == null
? ""
: objectMap.get("mx_mapfilename").toString();
// 3. 删除本地文件
if (StringUtils.isNotBlank(mxMapfilename)) {
String filePath = PathUtils.joinFilePath(
fileConfig.getBaseDir(),
mxMapfilename
);
boolean deleted = FileUtils.deleteFileByPath(filePath);
if (!deleted) {
System.out.println("文件删除失败或不存在: " + filePath);
}
}
// 4. 删除数据库记录
dbUtils.deleteById("bf_file", "subid", subid);
// 5. 记录日志
Map<String, Object> mapData = new HashMap<>();
ObjectMapper objectMapper = new ObjectMapper();
String dataStr = objectMapper.writeValueAsString(objectMap);
String currentDateTime = TimeUtils.getDateTime();
mapData.put("b_table", "bf_file");
mapData.put("b_option", "DELETE");
mapData.put("b_datas", dataStr);
mapData.put("b_inputdatetime", currentDateTime);
mapData.put("b_inputuser_id", UserContext.get().getUserId());
dbUtils.insert("log_datas", "b_id", mapData);
}
return Result.ok();
} catch (Exception e) {
e.printStackTrace();
return Result.fail("批量删除失败: " + e.getMessage());
}
}
}
@@ -16,7 +16,7 @@ spring:
time-zone: "Asia/Shanghai"
date-format: yyyy-MM-dd HH:mm:ss
profiles:
active: build
active: dev
file:
base-dir: D:/data/file
@@ -1,10 +1,10 @@
#url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR
#username=sa
#password=zhang520..
#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3ContainerOCR
url=jdbc:sqlserver://118.89.70.199:1433;databaseName=G3BoxOCR
username=sa
password=123456@aaa
password=zhang520..
driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
#url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3ContainerOCR
#username=sa
#password=123456@aaa
#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
@@ -2,7 +2,7 @@
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
import { dialog, isImageUrl, isVideoUrl, toast } from 'sard-uniapp'
import { getOrgBaseUrl } from '@/request'
import { deleteFileApi, loadDataApi, saveObjectApi, uploadContainerFileApi } from '@/request/api'
import { deleteFileApi, deleteFilesApi, loadDataApi, saveObjectApi, uploadContainerFileApi } from '@/request/api'
import { useUserStore } from '@/store'
import { buildPubicUrl, downloadFile, getFileExtension, imageCompress, isImage } from '@/utils/file'
@@ -14,6 +14,12 @@ const form = ref<any>({
b_container_img: '',
})
const subFileListRef = ref()
const subFileListRef2 = ref()
const oldSubFileList = ref([])
const oldSubFileList2 = ref([])
const subFileList = ref([]) // 装货照片
const subFileList2 = ref([]) // 发货单、签收单、过磅单
@@ -101,6 +107,9 @@ async function loadMainData() {
subFileList2.value = allFiles
.filter(f => f.mx_cate_id === '2')
.map(formatFile)
oldSubFileList.value = JSON.parse(JSON.stringify(subFileList.value))
oldSubFileList2.value = JSON.parse(JSON.stringify(subFileList2.value))
}
}
catch (err) {
@@ -111,51 +120,19 @@ async function loadMainData() {
}
}
function onItemClick(fileItem: UploadFileItem) {
console.log(fileItem)
if (!isImageUrl(fileItem.mx_mapfilename) && !isVideoUrl(fileItem.mx_mapfilename)) {
downloadFile(fileItem.mx_mapfilename, fileItem.name)
}
}
function beforeRead(file: UploadFile): boolean | Promise<UploadFile> {
// 非图片直接允许上传
if (!isImage(file.name))
return true
// 图片异步压缩,返回修改后的 UploadFile 对象
return imageCompress(file.path).then((res) => {
file.path = res.path
file.size = res.size
return file
}).catch((err) => {
console.error('图片压缩失败:', err)
return file
function handleBeforeRemove({ file, fileList, resolve }) {
dialog.confirm({
title: '提示',
message: '确定删除该文件吗?',
onConfirm: () => {
resolve(true)
},
onCancel: () => {
resolve(false)
},
})
}
function beforeRemove(_index: number, _fileItem: UploadFileItem): boolean | Promise<void> {
return new Promise((resolve, reject) => {
dialog.confirm({
title: '提示',
message: `确定删除该张图片吗?`,
onConfirm: () => resolve(),
onCancel: () => reject(),
onClose: () => reject(),
})
})
}
function onRemove(index: number, item: UploadFileItem) {
if (item.subid) {
deleteFileApi(item.subid).then((res) => {
if (res.success) {
toast.success('删除成功')
}
})
}
}
async function handleConfirm() {
if (form.value.b_container_img === '') {
toast.fail('集装箱号图片不能为空')
@@ -177,91 +154,209 @@ async function handleConfirm() {
transparent: false,
})
const fileInserts = []
const fileUpdates = []
try {
const fileInserts = []
const fileUpdates = []
const fileDeletes = []
const allFiles = [
...subFileList.value.map(item => ({ ...item, cate: '1' })),
...subFileList2.value.map(item => ({ ...item, cate: '2' })),
]
// =========================
// 当前上传组件文件
// =========================
const subFiles
= subFileListRef.value?.$.setupState.uploadFiles || []
const grouped = allFiles.reduce((acc, item) => {
if (!acc[item.cate])
acc[item.cate] = []
acc[item.cate].push(item)
return acc
}, {})
const subFiles2
= subFileListRef2.value?.$.setupState.uploadFiles || []
for (const cate in grouped) {
let index = 1
// 当前所有文件
const allFiles = [
...subFiles.map(item => ({
...item,
cate: '1',
})),
...subFiles2.map(item => ({
...item,
cate: '2',
})),
]
for (const fileItem of grouped[cate]) {
const mx_xh = index++
// 原始文件
const oldFiles = [
...(oldSubFileList.value || []).map(item => ({
...item,
cate: '1',
})),
...(oldSubFileList2.value || []).map(item => ({
...item,
cate: '2',
})),
]
// 已存在 → update
if (fileItem.subid) {
fileUpdates.push({
subid: fileItem.subid,
mx_xh,
mx_cate_id: cate,
})
continue
// =========================
// 删除文件
// =========================
const currentSubIds = new Set(
allFiles
.map(item => item.subid)
.filter(Boolean),
)
fileDeletes.push(
...oldFiles
.filter(item =>
item.subid
&& !currentSubIds.has(item.subid),
)
.map(item => ({
subid: item.subid,
})),
)
// =========================
// 分类排序
// =========================
const grouped = allFiles.reduce((acc, item) => {
if (!acc[item.cate]) {
acc[item.cate] = []
}
// 新增 → upload
const res = await uploadContainerFileApi(fileItem.file.path, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
})
acc[item.cate].push(item)
if (!res.success)
continue
return acc
}, {})
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh,
mx_filename: res.data.fileName,
mx_filesize: fileItem.file.size,
mx_fileext: getFileExtension(fileItem.file.name),
mx_mapfilename: res.data.path,
mx_moduleid: 'b_container',
mx_cate_id: cate,
})
// =========================
// 处理新增/更新
// =========================
for (const cate in grouped) {
let index = 1
fileItem.subid = res.data.uid
for (const fileItem of grouped[cate]) {
const mx_xh = index++
// 已存在 → update
if (fileItem.subid) {
fileUpdates.push({
subid: fileItem.subid,
mx_xh,
mx_cate_id: cate,
})
continue
}
try {
// 上传路径
let uploadPath
= fileItem.path
|| fileItem.url
|| fileItem.tempFilePath
// 图片压缩
if (isImage(fileItem.name)) {
const compressRes = await imageCompress(uploadPath)
uploadPath = compressRes.path
}
// 上传文件
const res = await uploadContainerFileApi(
uploadPath,
{
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
},
)
if (!res.success) {
continue
}
// 新增记录
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh,
mx_filename: res.data.fileName,
mx_filesize: fileItem.size,
mx_fileext: getFileExtension(fileItem.name),
mx_mapfilename: res.data.path,
mx_moduleid: 'b_container',
mx_cate_id: cate,
})
// 回填 subid
fileItem.subid = res.data.uid
}
catch (err) {
console.error('附件上传失败:', fileItem, err)
}
}
}
}
if (fileInserts.length === 0 && fileUpdates.length === 0) {
toast.success('保存成功')
// =========================
// 先删除物理文件
// =========================
if (fileDeletes.length > 0) {
const deleteRes = await deleteFilesApi(
fileDeletes.map(item => item.subid),
)
setTimeout(() => {
loadMainData()
}, 1000)
return
}
if (!deleteRes.success) {
toast.fail('删除文件失败')
const reqData = [
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: fileUpdates,
deletes: [],
},
]
return
}
}
saveObjectApi(reqData).then((res) => {
if (res.success) {
// =========================
// 无新增/更新
// 删除已经单独处理了
// =========================
if (
fileInserts.length === 0
&& fileUpdates.length === 0
) {
toast.success('保存成功')
setTimeout(() => {
loadMainData()
}, 1000)
return
}
})
// =========================
// 保存数据
// =========================
const reqData = [
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: fileUpdates,
deletes: [],
},
]
const res = await saveObjectApi(reqData)
if (res.success) {
toast.success('保存成功')
}
else {
toast.fail('保存失败')
}
}
catch (err) {
console.error(err)
toast.fail('保存失败')
}
finally {
setTimeout(() => {
toast.hide()
loadMainData()
}, 1000)
}
}
// 集装箱号校验
@@ -385,13 +480,14 @@ const templateCollapse = ref<string[]>(['template1'])
</view>
</template>
<view class="step-content">
<sar-upload
v-model="subFileList"
<wd-upload
ref="subFileListRef"
v-model:file-list="subFileList"
:auto-upload="false"
:before-remove="handleBeforeRemove"
multiple
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
@remove="onRemove"
accept="all"
:max-size="50 * 1024 * 1024"
/>
</view>
</wd-cell-group>
@@ -406,13 +502,14 @@ const templateCollapse = ref<string[]>(['template1'])
</view>
</template>
<view class="step-content">
<sar-upload
v-model="subFileList2"
<wd-upload
ref="subFileListRef2"
v-model:file-list="subFileList2"
:auto-upload="false"
:before-remove="handleBeforeRemove"
multiple
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
@remove="onRemove"
accept="all"
:max-size="50 * 1024 * 1024"
/>
</view>
</wd-cell-group>
@@ -19,6 +19,9 @@ const form = ref({
ocrNo: '',
})
const subFileListRef = ref()
const subFileListRef2 = ref()
const subFileList = ref([]) // 装货照片
const subFileList2 = ref([]) // 发货单、签收单、过磅单
@@ -114,38 +117,16 @@ function handleDeleteBoxImage() {
resetForm()
}
function onItemClick(fileItem: UploadFileItem) {
console.log(fileItem)
if (!isImageUrl(fileItem.mx_mapfilename) && !isVideoUrl(fileItem.mx_mapfilename)) {
downloadFile(fileItem.mx_mapfilename, fileItem.name)
}
}
function beforeRead(file: UploadFile): boolean | Promise<UploadFile> {
// 非图片直接允许上传
if (!isImage(file.name))
return true
// 图片异步压缩,返回修改后的 UploadFile 对象
return imageCompress(file.path).then((res) => {
file.path = res.path
file.size = res.size
return file
}).catch((err) => {
console.error('图片压缩失败:', err)
return file
})
}
function beforeRemove(_index: number, _fileItem: UploadFileItem): boolean | Promise<void> {
return new Promise((resolve, reject) => {
dialog.confirm({
title: '提示',
message: `确定删除该张图片吗?`,
onConfirm: () => resolve(),
onCancel: () => reject(),
onClose: () => reject(),
})
function handleBeforeRemove({ file, fileList, resolve }) {
dialog.confirm({
title: '提示',
message: '确定删除该文件吗?',
onConfirm: () => {
resolve(true)
},
onCancel: () => {
resolve(false)
},
})
}
@@ -155,7 +136,7 @@ async function handleConfirm() {
return
}
if (form.value.b_container_no === '') {
if (!form.value.b_container_no) {
toast.fail('集装箱号不能为空')
return
}
@@ -170,112 +151,154 @@ async function handleConfirm() {
transparent: false,
})
const mainTable = {
b_id: form.value.b_id,
b_container_no: form.value.b_container_no,
b_container_img: form.value.b_container_img,
b_ocr_no: form.value.ocrNo,
b_ocr_score: form.value.ocrScore || 0.00,
}
const fileInserts = []
try {
const mainTable = {
b_id: form.value.b_id,
b_container_no: form.value.b_container_no,
b_container_img: form.value.b_container_img,
b_ocr_no: form.value.ocrNo,
b_ocr_score: form.value.ocrScore || 0.00,
}
// 主表
await uploadContainerFileApi(boxImage.value.path, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: boxImage.value.name,
}).then((res) => {
if (res.success) {
mainTable.b_container_img = res.data.path
const fileInserts = []
// =========================
// 上传主图
// =========================
const mainRes = await uploadContainerFileApi(
boxImage.value.path,
{
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: boxImage.value.name,
},
)
if (mainRes.success) {
mainTable.b_container_img = mainRes.data.path
fileInserts.push({
subid: res.data.uid,
subid: mainRes.data.uid,
father: form.value.b_id,
mx_xh: 0,
mx_filename: res.data.fileName,
mx_filename: mainRes.data.fileName,
mx_filesize: boxImage.value.size,
mx_fileext: getFileExtension(boxImage.value.name),
mx_mapfilename: res.data.path,
mx_mapfilename: mainRes.data.path,
mx_moduleid: 'b_container',
mx_cate_id: '0',
})
}
})
const indexMap = {}
// =========================
// 附件排序
// =========================
const indexMap = {}
const getNextIndex = (cate) => {
if (!indexMap[cate]) {
indexMap[cate] = 1
const getNextIndex = (cate) => {
if (!indexMap[cate]) {
indexMap[cate] = 1
}
return indexMap[cate]++
}
return indexMap[cate]++
}
const allFiles = [
...subFileList.value.map(item => ({
...item,
cate: '1',
})),
...subFileList2.value.map(item => ({
...item,
cate: '2',
})),
]
// =========================
// 获取附件
// =========================
const subFiles = subFileListRef.value?.$.setupState.uploadFiles || []
const subFiles2 = subFileListRef2.value?.$.setupState.uploadFiles || []
for (const fileItem of allFiles) {
const res = await uploadContainerFileApi(fileItem.file.path, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
})
const allFiles = [
...subFiles.map(item => ({
...item,
cate: '1',
})),
...subFiles2.map(item => ({
...item,
cate: '2',
})),
]
if (!res.success)
continue
// =========================
// 上传附件
// =========================
for (const fileItem of allFiles) {
try {
let uploadPath = fileItem.url
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh: getNextIndex(fileItem.cate),
mx_filename: res.data.fileName,
mx_filesize: fileItem.file.size,
mx_fileext: getFileExtension(fileItem.file.name),
mx_mapfilename: res.data.path,
mx_moduleid: 'b_container',
mx_cate_id: fileItem.cate,
})
}
// 图片压缩
if (isImage(fileItem.name)) {
const compressRes = await imageCompress(fileItem.url)
uploadPath = compressRes.path
}
const reqData = [
{
tableName: 'b_container',
keyField: 'b_id',
inserts: [mainTable],
updates: [],
deletes: [],
},
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: [],
deletes: [],
},
]
const res = await uploadContainerFileApi(uploadPath, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
})
saveObjectApi(reqData).then((res) => {
if (res.success) {
if (!res.success) {
continue
}
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh: getNextIndex(fileItem.cate),
mx_filename: res.data.fileName,
mx_filesize: fileItem.size,
mx_fileext: getFileExtension(fileItem.name),
mx_mapfilename: res.data.path,
mx_moduleid: 'b_container',
mx_cate_id: fileItem.cate,
})
}
catch (err) {
console.error('附件上传失败:', fileItem, err)
}
}
// =========================
// 保存数据
// =========================
const reqData = [
{
tableName: 'b_container',
keyField: 'b_id',
inserts: [mainTable],
updates: [],
deletes: [],
},
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: [],
deletes: [],
},
]
const saveRes = await saveObjectApi(reqData)
if (saveRes.success) {
toast.success('保存成功')
tipContainerNo.value = form.value.b_container_no
showResult.value = true
}
else {
toast.fail('保存失败')
}
}).catch(() => {
}
catch (err) {
console.error(err)
toast.fail('保存失败')
}).finally(() => {
}
finally {
toast.hide()
})
}
}
// 集装箱号校验
@@ -446,12 +469,14 @@ const templateCollapse = ref<string[]>(['template1'])
</view>
</template>
<view class="step-content">
<sar-upload
v-model="subFileList"
<wd-upload
ref="subFileListRef"
v-model:file-list="subFileList"
:auto-upload="false"
:before-remove="handleBeforeRemove"
multiple
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
accept="all"
:max-size="50 * 1024 * 1024"
/>
</view>
</wd-cell-group>
@@ -465,12 +490,14 @@ const templateCollapse = ref<string[]>(['template1'])
</view>
</template>
<view class="step-content">
<sar-upload
v-model="subFileList2"
<wd-upload
ref="subFileListRef2"
v-model:file-list="subFileList2"
:auto-upload="false"
:before-remove="handleBeforeRemove"
multiple
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
accept="all"
:max-size="50 * 1024 * 1024"
/>
</view>
</wd-cell-group>
@@ -48,6 +48,10 @@ export function deleteFileApi(subid) {
})
}
export function deleteFilesApi(subids: any[]) {
return httpPost(`file/deletes`, subids)
}
export function deleteAliyunFileApi(fileUrl) {
return httpPost(`/file/deleteAliyunFile`, {
bucketName: aliyunBucket,
+45 -18
View File
@@ -126,35 +126,63 @@ export function formatFileSize(size: number): string {
/** 获取文件名:从 URL 提取最后一段 */
function getFileName(fileUrl: string) {
return decodeURIComponent(fileUrl.split('/').pop() || '未命名文件')
return decodeURIComponent(
fileUrl.split('/').pop() || '未命名文件',
)
}
/** 获取文件后缀 */
export function getFileExtension(fileUrl: string) {
const clean = fileUrl.split('?')[0].split('#')[0].trim()
/** 获取文件后缀(根据文件名) */
export function getFileExtension(fileName: string) {
const clean = fileName
.split('?')[0]
.split('#')[0]
.trim()
const lastDot = clean.lastIndexOf('.')
return lastDot === -1 ? '' : clean.substring(lastDot + 1).toLowerCase()
return lastDot === -1
? ''
: clean.substring(lastDot + 1).toLowerCase()
}
/** 文件类型判断 */
export function isImage(fileUrl: string) {
return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(getFileExtension(fileUrl))
/** 文件类型判断(根据文件名) */
export function isImage(fileName: string) {
return [
'jpg',
'jpeg',
'png',
'gif',
'webp',
'bmp',
].includes(getFileExtension(fileName))
}
export function isVideo(fileUrl: string) {
return ['mp4', 'avi', 'mov', 'mkv', 'flv', 'wmv', '3gp'].includes(getFileExtension(fileUrl))
export function isVideo(fileName: string) {
return [
'mp4',
'avi',
'mov',
'mkv',
'flv',
'wmv',
'3gp',
].includes(getFileExtension(fileName))
}
/** =================== 下载入口 =================== */
export function downloadFile(fileUrl: string, fileName?: string) {
const type: 'image' | 'video' | 'file' = isImage(fileUrl)
? 'image'
: isVideo(fileUrl)
? 'video'
: 'file'
export function downloadFile(
fileUrl: string,
fileName?: string,
) {
const finalName = fileName ?? getFileName(fileUrl)
const type: 'image' | 'video' | 'file'
= isImage(finalName)
? 'image'
: isVideo(finalName)
? 'video'
: 'file'
if (isH5) {
downloadH5(fileUrl, finalName)
}
@@ -165,7 +193,6 @@ export function downloadFile(fileUrl: string, fileName?: string) {
downloadApp(fileUrl, type, finalName)
}
}
/** =================== H5 下载 =================== */
async function downloadH5(fileUrl: string, fileName: string) {
try {
-8
View File
@@ -18,7 +18,6 @@ declare module 'vue' {
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
@@ -28,23 +27,16 @@ declare module 'vue' {
ElInput: typeof import('element-plus/es')['ElInput']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElResult: typeof import('element-plus/es')['ElResult']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSplitter: typeof import('element-plus/es')['ElSplitter']
ElSplitterPanel: typeof import('element-plus/es')['ElSplitterPanel']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElText: typeof import('element-plus/es')['ElText']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
G3Column: typeof import('./src/components/g3-table/g3-column.vue')['default']