vue2server7 5.0.9 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<!-- ImportTableDialog.vue -->
|
|
2
|
+
<template>
|
|
3
|
+
<el-dialog
|
|
4
|
+
v-model="dialogVisible"
|
|
5
|
+
:title="title"
|
|
6
|
+
width="520px"
|
|
7
|
+
destroy-on-close
|
|
8
|
+
@closed="handleClosed"
|
|
9
|
+
>
|
|
10
|
+
<el-upload
|
|
11
|
+
ref="uploadRef"
|
|
12
|
+
v-model:file-list="fileList"
|
|
13
|
+
drag
|
|
14
|
+
action="#"
|
|
15
|
+
:auto-upload="false"
|
|
16
|
+
:limit="1"
|
|
17
|
+
:accept="accept"
|
|
18
|
+
:on-change="handleChange"
|
|
19
|
+
:on-exceed="handleExceed"
|
|
20
|
+
:on-remove="handleRemove"
|
|
21
|
+
>
|
|
22
|
+
<el-icon class="el-icon--upload">
|
|
23
|
+
<UploadFilled />
|
|
24
|
+
</el-icon>
|
|
25
|
+
|
|
26
|
+
<div class="el-upload__text">
|
|
27
|
+
将表格文件拖到这里,或 <em>点击选择</em>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<template #tip>
|
|
31
|
+
<div class="el-upload__tip">
|
|
32
|
+
仅支持 xls / xlsx / csv,每次只允许上传 1 个文件
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
</el-upload>
|
|
36
|
+
|
|
37
|
+
<template #footer>
|
|
38
|
+
<el-button :disabled="loading" @click="dialogVisible = false">
|
|
39
|
+
取消
|
|
40
|
+
</el-button>
|
|
41
|
+
<el-button type="primary" :loading="loading" @click="handleConfirm">
|
|
42
|
+
导入
|
|
43
|
+
</el-button>
|
|
44
|
+
</template>
|
|
45
|
+
</el-dialog>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script setup>
|
|
49
|
+
import { computed, ref } from 'vue'
|
|
50
|
+
import { ElMessage, genFileId } from 'element-plus'
|
|
51
|
+
import { UploadFilled } from '@element-plus/icons-vue'
|
|
52
|
+
|
|
53
|
+
const props = defineProps({
|
|
54
|
+
modelValue: {
|
|
55
|
+
type: Boolean,
|
|
56
|
+
default: false,
|
|
57
|
+
},
|
|
58
|
+
title: {
|
|
59
|
+
type: String,
|
|
60
|
+
default: '导入表格',
|
|
61
|
+
},
|
|
62
|
+
loading: {
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const emit = defineEmits(['update:modelValue', 'confirm'])
|
|
69
|
+
|
|
70
|
+
const dialogVisible = computed({
|
|
71
|
+
get: () => props.modelValue,
|
|
72
|
+
set: (value) => emit('update:modelValue', value),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const uploadRef = ref()
|
|
76
|
+
const fileList = ref([])
|
|
77
|
+
const selectedFile = ref(null)
|
|
78
|
+
|
|
79
|
+
const accept = '.xls,.xlsx,.csv'
|
|
80
|
+
const allowExt = ['xls', 'xlsx', 'csv']
|
|
81
|
+
|
|
82
|
+
const isTableFile = (file) => {
|
|
83
|
+
const ext = file.name?.split('.').pop()?.toLowerCase()
|
|
84
|
+
return allowExt.includes(ext)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const reset = () => {
|
|
88
|
+
selectedFile.value = null
|
|
89
|
+
fileList.value = []
|
|
90
|
+
uploadRef.value?.clearFiles()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const handleChange = (uploadFile, uploadFiles) => {
|
|
94
|
+
const rawFile = uploadFile.raw
|
|
95
|
+
if (!rawFile) return
|
|
96
|
+
|
|
97
|
+
if (!isTableFile(rawFile)) {
|
|
98
|
+
ElMessage.error('只能上传表格文件(xls、xlsx、csv)')
|
|
99
|
+
reset()
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 只保留最后一个文件
|
|
104
|
+
fileList.value = uploadFiles.slice(-1)
|
|
105
|
+
selectedFile.value = rawFile
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const handleExceed = (files) => {
|
|
109
|
+
const file = files[0]
|
|
110
|
+
|
|
111
|
+
if (!isTableFile(file)) {
|
|
112
|
+
ElMessage.error('只能上传表格文件(xls、xlsx、csv)')
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 超出 1 个时,用新文件覆盖旧文件
|
|
117
|
+
uploadRef.value?.clearFiles()
|
|
118
|
+
file.uid = genFileId()
|
|
119
|
+
uploadRef.value?.handleStart(file)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const handleRemove = () => {
|
|
123
|
+
selectedFile.value = null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const handleConfirm = () => {
|
|
127
|
+
if (!selectedFile.value) {
|
|
128
|
+
ElMessage.warning('请先选择一个表格文件')
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 把原始 File 对象交给父组件
|
|
133
|
+
emit('confirm', selectedFile.value)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const handleClosed = () => {
|
|
137
|
+
reset()
|
|
138
|
+
}
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<style scoped>
|
|
142
|
+
:deep(.el-upload),
|
|
143
|
+
:deep(.el-upload-dragger) {
|
|
144
|
+
width: 100%;
|
|
145
|
+
}
|
|
146
|
+
</style>
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="page import-page">
|
|
3
|
+
<h1 class="title">导入表格</h1>
|
|
4
|
+
|
|
5
|
+
<el-card class="upload-card">
|
|
6
|
+
<template #header>
|
|
7
|
+
<div class="card-header">
|
|
8
|
+
<span>数据导入</span>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<div class="upload-content">
|
|
13
|
+
<el-button type="primary" @click="showImportDialog">
|
|
14
|
+
导入表格文件
|
|
15
|
+
</el-button>
|
|
16
|
+
|
|
17
|
+
<div v-if="uploadedFile" class="file-info">
|
|
18
|
+
<el-alert
|
|
19
|
+
title="已选择文件"
|
|
20
|
+
type="success"
|
|
21
|
+
:closable="false"
|
|
22
|
+
show-icon
|
|
23
|
+
>
|
|
24
|
+
<div class="file-details">
|
|
25
|
+
<p><strong>文件名:</strong>{{ uploadedFile.name }}</p>
|
|
26
|
+
<p><strong>文件大小:</strong>{{ formatFileSize(uploadedFile.size) }}</p>
|
|
27
|
+
<p><strong>文件类型:</strong>{{ uploadedFile.type || '未知' }}</p>
|
|
28
|
+
</div>
|
|
29
|
+
</el-alert>
|
|
30
|
+
|
|
31
|
+
<el-button type="danger" size="small" @click="clearFile" style="margin-top: 12px">
|
|
32
|
+
清除文件
|
|
33
|
+
</el-button>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div v-else class="upload-tips">
|
|
37
|
+
<el-alert
|
|
38
|
+
title="上传说明"
|
|
39
|
+
type="info"
|
|
40
|
+
:closable="false"
|
|
41
|
+
show-icon
|
|
42
|
+
>
|
|
43
|
+
<template #default>
|
|
44
|
+
<ul>
|
|
45
|
+
<li>支持的文件格式:xls、xlsx、csv</li>
|
|
46
|
+
<li>每次只能上传一个文件</li>
|
|
47
|
+
<li>上传后可以在这里查看文件信息</li>
|
|
48
|
+
</ul>
|
|
49
|
+
</template>
|
|
50
|
+
</el-alert>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</el-card>
|
|
54
|
+
|
|
55
|
+
<!-- 导入对话框 -->
|
|
56
|
+
<ImportTableDialog
|
|
57
|
+
v-model="dialogVisible"
|
|
58
|
+
:loading="loading"
|
|
59
|
+
@confirm="handleImportConfirm"
|
|
60
|
+
/>
|
|
61
|
+
</section>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script setup>
|
|
65
|
+
import { ref } from 'vue'
|
|
66
|
+
import { ElMessage } from 'element-plus'
|
|
67
|
+
import ImportTableDialog from '../components/ImportTableDialog.vue'
|
|
68
|
+
|
|
69
|
+
const dialogVisible = ref(false)
|
|
70
|
+
const loading = ref(false)
|
|
71
|
+
const uploadedFile = ref(null)
|
|
72
|
+
|
|
73
|
+
const showImportDialog = () => {
|
|
74
|
+
dialogVisible.value = true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const handleImportConfirm = (file) => {
|
|
78
|
+
loading.value = true
|
|
79
|
+
|
|
80
|
+
// 模拟上传处理
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
loading.value = false
|
|
83
|
+
dialogVisible.value = false
|
|
84
|
+
uploadedFile.value = file
|
|
85
|
+
|
|
86
|
+
ElMessage.success(`文件 "${file.name}" 上传成功!`)
|
|
87
|
+
|
|
88
|
+
// 这里可以添加实际的文件上传逻辑
|
|
89
|
+
console.log('准备上传的文件:', file)
|
|
90
|
+
// 例如:调用 API 上传文件
|
|
91
|
+
// uploadFileApi(file).then(res => { ... })
|
|
92
|
+
}, 1000)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const clearFile = () => {
|
|
96
|
+
uploadedFile.value = null
|
|
97
|
+
ElMessage.info('已清除文件')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const formatFileSize = (bytes) => {
|
|
101
|
+
if (!bytes) return '0 B'
|
|
102
|
+
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
103
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024))
|
|
104
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`
|
|
105
|
+
}
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<style scoped>
|
|
109
|
+
.page.import-page {
|
|
110
|
+
padding: 16px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.title {
|
|
114
|
+
font-size: 18px;
|
|
115
|
+
margin-bottom: 12px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.upload-card {
|
|
119
|
+
max-width: 800px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.card-header {
|
|
123
|
+
display: flex;
|
|
124
|
+
justify-content: space-between;
|
|
125
|
+
align-items: center;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.upload-content {
|
|
129
|
+
min-height: 200px;
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: center;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.file-info {
|
|
137
|
+
margin-top: 16px;
|
|
138
|
+
text-align: center;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.file-details {
|
|
142
|
+
text-align: left;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.file-details p {
|
|
146
|
+
margin: 8px 0;
|
|
147
|
+
font-size: 14px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.upload-tips {
|
|
151
|
+
margin-top: 16px;
|
|
152
|
+
width: 100%;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.upload-tips ul {
|
|
156
|
+
margin: 8px 0;
|
|
157
|
+
padding-left: 20px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.upload-tips li {
|
|
161
|
+
margin: 4px 0;
|
|
162
|
+
font-size: 14px;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import TablePage from '../pages/TablePage.vue'
|
|
2
2
|
import CascaderPage from '../pages/CascaderPage.vue'
|
|
3
3
|
import ExportExcelPage from '../pages/ExportExcelPage.vue'
|
|
4
|
+
import ImportTablePage from '../pages/ImportTablePage.vue'
|
|
4
5
|
|
|
5
6
|
export const routes = [
|
|
6
7
|
{
|
|
@@ -30,7 +31,16 @@ export const routes = [
|
|
|
30
31
|
name: 'ExportExcel',
|
|
31
32
|
component: ExportExcelPage,
|
|
32
33
|
meta: {
|
|
33
|
-
title: '导出Excel',
|
|
34
|
+
title: '导出 Excel',
|
|
35
|
+
showInMenu: true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
path: '/import-table',
|
|
40
|
+
name: 'ImportTable',
|
|
41
|
+
component: ImportTablePage,
|
|
42
|
+
meta: {
|
|
43
|
+
title: '导入表格',
|
|
34
44
|
showInMenu: true
|
|
35
45
|
}
|
|
36
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vue2server7",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "nodemon --watch src --ext ts --exec \"ts-node src/app.ts\"",
|
|
@@ -30,19 +30,18 @@
|
|
|
30
30
|
"author": "",
|
|
31
31
|
"license": "ISC",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@element-plus/icons-vue": "^2.3.1",
|
|
34
|
-
"axios": "^1.13.2",
|
|
35
33
|
"cors": "^2.8.5",
|
|
36
34
|
"dotenv": "^16.6.1",
|
|
37
|
-
"echarts": "^5.5.1",
|
|
38
|
-
"element-plus": "^2.8.4",
|
|
39
35
|
"express": "^5.2.1",
|
|
40
36
|
"helmet": "^8.1.0",
|
|
41
37
|
"morgan": "^1.10.1",
|
|
38
|
+
"@element-plus/icons-vue": "^2.3.1",
|
|
39
|
+
"axios": "^1.13.2",
|
|
40
|
+
"echarts": "^5.5.1",
|
|
41
|
+
"element-plus": "^2.8.4",
|
|
42
42
|
"sortablejs": "^1.15.6",
|
|
43
43
|
"vue": "^3.5.13",
|
|
44
|
-
"vue-router": "^4.4.5"
|
|
45
|
-
"vue2webstrom": "^0.0.1"
|
|
44
|
+
"vue-router": "^4.4.5"
|
|
46
45
|
},
|
|
47
46
|
"devDependencies": {
|
|
48
47
|
"@types/cors": "^2.8.19",
|
|
@@ -51,12 +50,12 @@
|
|
|
51
50
|
"@types/node": "^22.10.5",
|
|
52
51
|
"@typescript-eslint/parser": "^8.19.0",
|
|
53
52
|
"@vitejs/plugin-vue": "^6.0.0",
|
|
54
|
-
"concurrently": "^9.1.0",
|
|
55
53
|
"eslint": "^9.18.0",
|
|
56
|
-
"nodemon": "^3.0.1",
|
|
57
|
-
"supertest": "^7.1.4",
|
|
58
54
|
"ts-node": "^10.9.2",
|
|
55
|
+
"supertest": "^7.1.4",
|
|
59
56
|
"typescript": "^5.7.3",
|
|
57
|
+
"nodemon": "^3.0.1",
|
|
58
|
+
"concurrently": "^9.1.0",
|
|
60
59
|
"vite": "^6.0.0",
|
|
61
60
|
"vite-plugin-vue-devtools": "^8.0.6"
|
|
62
61
|
},
|