haiwei-ui 1.0.96 → 1.0.98

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.
@@ -1,392 +1,562 @@
1
1
  <template>
2
- <component v-bind:is="`nm-${options.showMode || 'drawer'}`" header footer draggable :padding="10" :title="title" :icon="icon" :width="width" :height="height" :visible.sync="visible_">
3
- <el-form ref="form" :model="model" :rules="rules" :size="fontSize" label-width="120px">
4
- <!-- 文件上传 -->
5
- <el-form-item label="选择文件:" prop="file">
6
- <el-upload
7
- ref="upload"
8
- class="nm-upload"
9
- drag
10
- :action="uploadUrl"
11
- :headers="headers"
12
- :data="uploadData"
13
- :before-upload="beforeUpload"
14
- :on-success="onUploadSuccess"
15
- :on-error="onUploadError"
16
- :on-remove="onFileRemove"
17
- :file-list="fileList"
18
- :limit="1"
19
- accept=".xlsx,.xls"
20
- >
21
- <i class="el-icon-upload"></i>
22
- <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
23
- <div class="el-upload__tip" slot="tip">只能上传Excel文件,且不超过100MB</div>
24
- </el-upload>
25
- </el-form-item>
2
+ <component v-bind:is="`nm-${options.showMode || 'drawer'}`" header footer draggable :padding="20" :title="title" :icon="icon" :width="width" :height="height" :visible.sync="visible_">
3
+ <div class="import-container">
4
+ <!-- 步骤指示器 -->
5
+ <el-steps :active="currentStep" align-center class="import-steps" simple>
6
+ <el-step title="选择文件" />
7
+ <el-step title="配置选项" />
8
+ <el-step title="数据映射" />
9
+ <el-step title="确认导入" />
10
+ </el-steps>
26
11
 
27
- <!-- 解析选项 -->
28
- <el-form-item v-if="fileInfo" label="解析选项:">
29
- <el-row :gutter="20">
30
- <el-col :span="12">
31
- <el-checkbox v-model="model.hasHeader" :disabled="parsing">包含表头</el-checkbox>
32
- </el-col>
33
- <el-col :span="12">
34
- <el-checkbox v-model="model.skipEmptyRows" :disabled="parsing">跳过空行</el-checkbox>
35
- </el-col>
36
- </el-row>
37
- <el-row :gutter="20" class="nm-m-t-10">
38
- <el-col :span="12">
39
- <el-input-number v-model="model.maxPreviewRows" :min="1" :max="1000" :disabled="parsing" controls-position="right" style="width: 100%">
40
- <template slot="prepend">最大预览行数</template>
41
- </el-input-number>
42
- </el-col>
43
- </el-row>
44
- </el-form-item>
12
+ <!-- 步骤1:选择文件 -->
13
+ <div v-if="currentStep === 1" class="step-content">
14
+ <el-card shadow="never" class="step-card">
15
+ <div slot="header" class="step-header">
16
+ <div class="header-left">
17
+ <span class="step-title">选择Excel文件</span>
18
+ <span class="step-subtitle">支持 .xlsx 和 .xls 格式,最大10MB</span>
19
+ </div>
20
+ <div class="header-right">
21
+ <el-upload
22
+ ref="upload"
23
+ :action="uploadUrl"
24
+ :headers="headers"
25
+ :before-upload="beforeUpload"
26
+ :on-success="onUploadSuccess"
27
+ :on-error="onUploadError"
28
+ :on-remove="onFileRemove"
29
+ :file-list="fileList"
30
+ :limit="1"
31
+ accept=".xlsx,.xls"
32
+ :show-file-list="false"
33
+ >
34
+ <el-button type="primary" size="medium" icon="el-icon-upload">
35
+ 选择文件
36
+ </el-button>
37
+ </el-upload>
38
+ </div>
39
+ </div>
40
+
41
+ <div v-if="fileInfo" class="file-info-card">
42
+ <el-descriptions :column="2" border size="medium" class="file-info">
43
+ <el-descriptions-item label="文件名">
44
+ <el-tag type="success">{{ fileInfo.fileName }}</el-tag>
45
+ </el-descriptions-item>
46
+ <el-descriptions-item label="文件大小">
47
+ <el-tag type="info">{{ formatFileSize(fileInfo.fileSize) }}</el-tag>
48
+ </el-descriptions-item>
49
+ <el-descriptions-item label="工作表数量">
50
+ <el-tag>{{ fileInfo.sheets.length }}个</el-tag>
51
+ </el-descriptions-item>
52
+ <el-descriptions-item label="解析时间">
53
+ <el-tag type="warning">{{ fileInfo.parseTime }}</el-tag>
54
+ </el-descriptions-item>
55
+ </el-descriptions>
56
+
57
+ <div v-if="fileInfo.sheets && fileInfo.sheets.length > 0" class="sheet-select">
58
+ <el-form :model="model" label-width="120px" size="medium">
59
+ <el-form-item label="选择工作表:" prop="selectedSheet">
60
+ <el-select v-model="model.selectedSheet" placeholder="请选择要导入的工作表" style="width: 100%">
61
+ <el-option v-for="sheet in fileInfo.sheets" :key="sheet.index" :label="sheet.name" :value="sheet.index">
62
+ <span style="float: left">{{ sheet.name }}</span>
63
+ <span style="float: right; color: #8492a6; font-size: 13px">
64
+ {{ sheet.rowCount }}行 × {{ sheet.columnCount }}列
65
+ </span>
66
+ </el-option>
67
+ </el-select>
68
+ </el-form-item>
69
+ </el-form>
70
+ </div>
71
+ </div>
72
+
73
+ <div v-else class="upload-placeholder">
74
+ <div class="placeholder-content">
75
+ <i class="el-icon-document placeholder-icon"></i>
76
+ <div class="placeholder-text">请选择要导入的Excel文件</div>
77
+ <div class="placeholder-tip">支持 .xlsx 和 .xls 格式,文件大小不超过10MB</div>
78
+ </div>
79
+ </div>
80
+ </el-card>
81
+ </div>
45
82
 
46
- <!-- 文件信息 -->
47
- <el-form-item v-if="fileInfo" label="文件信息:">
48
- <el-descriptions :column="2" border size="small">
49
- <el-descriptions-item label="文件名">{{ fileInfo.fileName }}</el-descriptions-item>
50
- <el-descriptions-item label="文件大小">{{ formatFileSize(fileInfo.fileSize) }}</el-descriptions-item>
51
- <el-descriptions-item label="工作表">{{ fileInfo.sheetCount }}个</el-descriptions-item>
52
- <el-descriptions-item label="总行数">{{ fileInfo.totalRows }}行</el-descriptions-item>
53
- </el-descriptions>
54
- </el-form-item>
83
+ <!-- 步骤2:配置选项 -->
84
+ <div v-if="currentStep === 2" class="step-content">
85
+ <el-card shadow="never" class="step-card">
86
+ <div slot="header" class="step-header">
87
+ <div class="header-left">
88
+ <span class="step-title">配置解析选项</span>
89
+ <span class="step-subtitle">设置Excel解析参数</span>
90
+ </div>
91
+ </div>
55
92
 
56
- <!-- 工作表选择 -->
57
- <el-form-item v-if="fileInfo && fileInfo.sheets && fileInfo.sheets.length > 1" label="选择工作表:" prop="selectedSheet">
58
- <el-select v-model="model.selectedSheet" placeholder="请选择工作表" :disabled="parsing" style="width: 100%">
59
- <el-option v-for="sheet in fileInfo.sheets" :key="sheet.index" :label="sheet.name" :value="sheet.index">
60
- <span style="float: left">{{ sheet.name }}</span>
61
- <span style="float: right; color: #8492a6; font-size: 13px">{{ sheet.rowCount }}行 × {{ sheet.columnCount }}列</span>
62
- </el-option>
63
- </el-select>
64
- </el-form-item>
93
+ <el-form ref="optionsForm" :model="model" :rules="rules" label-width="140px" size="medium">
94
+ <el-row :gutter="30">
95
+ <el-col :span="12">
96
+ <el-form-item label="是否包含表头:" prop="hasHeader">
97
+ <el-switch
98
+ v-model="model.hasHeader"
99
+ active-text="是"
100
+ inactive-text="否"
101
+ active-color="#13ce66"
102
+ inactive-color="#ff4949"
103
+ />
104
+ </el-form-item>
105
+ </el-col>
106
+ <el-col :span="12">
107
+ <el-form-item label="跳过空行:" prop="skipEmptyRows">
108
+ <el-switch
109
+ v-model="model.skipEmptyRows"
110
+ active-text="是"
111
+ inactive-text="否"
112
+ active-color="#13ce66"
113
+ inactive-color="#ff4949"
114
+ />
115
+ </el-form-item>
116
+ </el-col>
117
+ </el-row>
65
118
 
66
- <!-- 数据预览 -->
67
- <el-form-item v-if="parseResult" label="数据预览:">
68
- <el-alert v-if="parseResult.basicInfo" :title="`已解析 ${parseResult.basicInfo.rowCount} 行数据,${parseResult.basicInfo.columnCount} 列`" type="info" show-icon :closable="false" class="nm-m-b-10" />
69
-
70
- <el-table :data="parseResult.previewData" border stripe size="mini" max-height="400" v-loading="parsing">
71
- <el-table-column v-for="(col, index) in parseResult.columns" :key="index" :prop="col.name" :label="col.label" :width="col.width">
72
- <template v-slot:header>
73
- <div class="nm-text-center">
74
- <div>{{ col.label }}</div>
75
- <div class="nm-text-xs nm-text-muted">({{ col.name }})</div>
119
+ <el-row :gutter="30">
120
+ <el-col :span="12">
121
+ <el-form-item label="表头行行号:" prop="headerRowIndex">
122
+ <el-input-number
123
+ v-model="model.headerRowIndex"
124
+ :min="0"
125
+ :max="100"
126
+ controls-position="right"
127
+ style="width: 100%"
128
+ placeholder="表头所在行号(从0开始)"
129
+ />
130
+ </el-form-item>
131
+ </el-col>
132
+ <el-col :span="12">
133
+ <el-form-item label="最大预览行数:" prop="maxPreviewRows">
134
+ <el-input-number
135
+ v-model="model.maxPreviewRows"
136
+ :min="1"
137
+ :max="1000"
138
+ controls-position="right"
139
+ style="width: 100%"
140
+ placeholder="最多预览多少行数据"
141
+ />
142
+ </el-form-item>
143
+ </el-col>
144
+ </el-row>
145
+
146
+ <el-row :gutter="30">
147
+ <el-col :span="12">
148
+ <el-form-item label="数据去重:" prop="deduplicate">
149
+ <el-switch
150
+ v-model="model.deduplicate"
151
+ active-text="是"
152
+ inactive-text="否"
153
+ active-color="#13ce66"
154
+ inactive-color="#ff4949"
155
+ />
156
+ </el-form-item>
157
+ </el-col>
158
+ </el-row>
159
+
160
+ <div class="form-actions">
161
+ <el-button type="primary" :loading="parsing" @click="onParse" style="width: 200px;">
162
+ <i class="el-icon-search"></i> 解析文件
163
+ </el-button>
164
+ <el-button @click="currentStep = 1" style="margin-left: 20px;">
165
+ <i class="el-icon-back"></i> 返回上一步
166
+ </el-button>
167
+ </div>
168
+ </el-form>
169
+ </el-card>
170
+ </div>
171
+
172
+ <!-- 步骤3:数据映射 -->
173
+ <div v-if="currentStep === 3" class="step-content">
174
+ <el-card shadow="never" class="step-card">
175
+ <div slot="header" class="step-header">
176
+ <div class="header-left">
177
+ <span class="step-title">配置字段映射</span>
178
+ <span class="step-subtitle">将Excel列映射到目标字段</span>
179
+ </div>
180
+ </div>
181
+
182
+ <div v-if="parseResult" class="mapping-container">
183
+ <!-- 数据预览 -->
184
+ <div class="preview-section">
185
+ <el-alert
186
+ :title="`已解析 ${parseResult.basicInfo.rowCount} 行数据,${parseResult.basicInfo.columnCount} 列`"
187
+ type="info"
188
+ show-icon
189
+ :closable="false"
190
+ class="preview-alert"
191
+ />
192
+
193
+ <el-table
194
+ :data="parseResult.previewData"
195
+ border
196
+ stripe
197
+ size="mini"
198
+ max-height="300"
199
+ v-loading="parsing"
200
+ class="preview-table"
201
+ >
202
+ <el-table-column
203
+ v-for="(col, index) in parseResult.columns"
204
+ :key="index"
205
+ :prop="col.name"
206
+ :label="col.label"
207
+ :width="col.width"
208
+ show-overflow-tooltip
209
+ >
210
+ <template v-slot:header>
211
+ <div class="column-header">
212
+ <div class="column-label">{{ col.label }}</div>
213
+ <div class="column-name">({{ col.name }})</div>
214
+ </div>
215
+ </template>
216
+ </el-table-column>
217
+ </el-table>
218
+
219
+ <div v-if="parseResult.basicInfo && parseResult.basicInfo.totalRows > parseResult.basicInfo.rowCount" class="preview-tip">
220
+ 仅显示前{{ parseResult.basicInfo.rowCount }}行,共{{ parseResult.basicInfo.totalRows }}行数据
76
221
  </div>
77
- </template>
78
- <template v-slot="{ row }">
79
- <span :title="row[col.name]">{{ row[col.name] }}</span>
80
- </template>
81
- </el-table-column>
82
- </el-table>
83
-
84
- <div v-if="parseResult.basicInfo && parseResult.basicInfo.totalRows > parseResult.basicInfo.rowCount" class="nm-text-center nm-m-t-10 nm-text-muted">
85
- 仅显示前{{ parseResult.basicInfo.rowCount }}行,共{{ parseResult.basicInfo.totalRows }}行数据
86
- </div>
87
- </el-form-item>
222
+ </div>
88
223
 
89
- <!-- 列映射配置 -->
90
- <el-form-item v-if="parseResult && parseResult.columns && parseResult.columns.length > 0" label="列映射配置:">
91
- <el-table :data="columnMapping" border stripe size="mini" max-height="300">
92
- <el-table-column prop="excelColumn" label="Excel列" width="150">
93
- <template v-slot="{ row }">
94
- <span>{{ row.excelColumn.label }} <span class="nm-text-muted">({{ row.excelColumn.name }})</span></span>
95
- </template>
96
- </el-table-column>
97
- <el-table-column prop="targetColumn" label="目标字段" width="200">
98
- <template v-slot="{ row }">
99
- <el-select v-model="row.targetColumn" placeholder="请选择目标字段" style="width: 100%">
100
- <el-option label="不导入此列" :value="null"></el-option>
101
- <el-option v-for="col in targetColumns" :key="col.name" :label="col.label" :value="col.name">
102
- <span style="float: left">{{ col.label }}</span>
103
- <span style="float: right; color: #8492a6; font-size: 13px">{{ col.name }}</span>
104
- </el-option>
105
- </el-select>
106
- </template>
107
- </el-table-column>
108
- <el-table-column prop="required" label="必填" width="80" align="center">
109
- <template v-slot="{ row }">
110
- <el-checkbox v-model="row.required" :disabled="!row.targetColumn"></el-checkbox>
111
- </template>
112
- </el-table-column>
113
- <el-table-column prop="defaultValue" label="默认值">
114
- <template v-slot="{ row }">
115
- <el-input v-model="row.defaultValue" :disabled="!row.targetColumn" placeholder="当Excel为空时使用此默认值"></el-input>
116
- </template>
117
- </el-table-column>
118
- </el-table>
119
- </el-form-item>
224
+ <!-- 列映射配置 -->
225
+ <div class="mapping-section">
226
+ <el-table :data="columnMapping" border stripe size="mini" max-height="300" class="mapping-table">
227
+ <el-table-column prop="excelColumn" label="Excel列" width="180" fixed>
228
+ <template v-slot="{ row }">
229
+ <div class="excel-column">
230
+ <div class="column-label">{{ row.excelColumn.label }}</div>
231
+ <div class="column-name">{{ row.excelColumn.name }}</div>
232
+ </div>
233
+ </template>
234
+ </el-table-column>
235
+ <el-table-column prop="targetColumn" label="目标字段" width="220">
236
+ <template v-slot="{ row }">
237
+ <el-select
238
+ v-model="row.targetColumn"
239
+ placeholder="请选择目标字段"
240
+ style="width: 100%"
241
+ clearable
242
+ filterable
243
+ >
244
+ <el-option label="不导入此列" :value="null"></el-option>
245
+ <el-option
246
+ v-for="col in targetColumns"
247
+ :key="col.name"
248
+ :label="col.label"
249
+ :value="col.name"
250
+ >
251
+ <span style="float: left">{{ col.label }}</span>
252
+ <span style="float: right; color: #8492a6; font-size: 13px">{{ col.name }}</span>
253
+ </el-option>
254
+ </el-select>
255
+ </template>
256
+ </el-table-column>
257
+ <el-table-column prop="required" label="必填" width="80" align="center">
258
+ <template v-slot="{ row }">
259
+ <el-checkbox v-model="row.required" :disabled="!row.targetColumn"></el-checkbox>
260
+ </template>
261
+ </el-table-column>
262
+ <el-table-column prop="defaultValue" label="默认值" min-width="150">
263
+ <template v-slot="{ row }">
264
+ <el-input
265
+ v-model="row.defaultValue"
266
+ :disabled="!row.targetColumn"
267
+ placeholder="当Excel为空时使用此默认值"
268
+ size="mini"
269
+ />
270
+ </template>
271
+ </el-table-column>
272
+ </el-table>
273
+ </div>
120
274
 
121
- <!-- 导入模式 -->
122
- <el-form-item v-if="parseResult" label="导入模式:" prop="importMode">
123
- <el-radio-group v-model="model.importMode">
124
- <el-radio :label="0">新增数据</el-radio>
125
- <el-radio :label="1">更新数据</el-radio>
126
- <el-radio :label="2">新增或更新</el-radio>
127
- </el-radio-group>
128
- </el-form-item>
275
+ <div class="form-actions">
276
+ <el-button type="primary" @click="currentStep = 4" style="width: 200px;">
277
+ <i class="el-icon-arrow-right"></i> 下一步:确认导入
278
+ </el-button>
279
+ <el-button @click="currentStep = 2" style="margin-left: 20px;">
280
+ <i class="el-icon-back"></i> 返回上一步
281
+ </el-button>
282
+ <el-button type="info" @click="onParse" :loading="parsing" style="margin-left: 20px;">
283
+ <i class="el-icon-refresh"></i> 重新解析
284
+ </el-button>
285
+ </div>
286
+ </div>
287
+ </el-card>
288
+ </div>
129
289
 
130
- <!-- 错误处理 -->
131
- <el-form-item v-if="parseResult" label="错误处理:" prop="errorHandling">
132
- <el-radio-group v-model="model.errorHandling">
133
- <el-radio :label="0">遇到错误停止导入</el-radio>
134
- <el-radio :label="1">跳过错误继续导入</el-radio>
135
- </el-radio-group>
136
- </el-form-item>
137
- </el-form>
290
+ <!-- 步骤4:确认导入 -->
291
+ <div v-if="currentStep === 4" class="step-content">
292
+ <el-card shadow="never" class="step-card">
293
+ <div slot="header" class="step-header">
294
+ <div class="header-left">
295
+ <span class="step-title">确认导入</span>
296
+ <span class="step-subtitle">确认数据映射并执行导入</span>
297
+ </div>
298
+ </div>
299
+
300
+ <div class="import-summary">
301
+ <el-alert
302
+ :title="`准备导入 ${parseResult ? parseResult.basicInfo.rowCount : 0} 条数据`"
303
+ type="warning"
304
+ show-icon
305
+ :closable="false"
306
+ />
307
+
308
+ <div class="mapping-summary">
309
+ <el-descriptions :column="2" border size="medium" class="summary-info">
310
+ <el-descriptions-item label="Excel文件">
311
+ <el-tag type="success">{{ fileInfo.fileName }}</el-tag>
312
+ </el-descriptions-item>
313
+ <el-descriptions-item label="工作表">
314
+ <el-tag>{{ getSelectedSheetName() }}</el-tag>
315
+ </el-descriptions-item>
316
+ <el-descriptions-item label="总行数">
317
+ <el-tag type="info">{{ parseResult.basicInfo.totalRows }}</el-tag>
318
+ </el-descriptions-item>
319
+ <el-descriptions-item label="总列数">
320
+ <el-tag type="info">{{ parseResult.basicInfo.totalColumns }}</el-tag>
321
+ </el-descriptions-item>
322
+ <el-descriptions-item label="已配置映射">
323
+ <el-tag type="success">{{ getMappedColumnsCount() }}列</el-tag>
324
+ </el-descriptions-item>
325
+ <el-descriptions-item label="未配置映射">
326
+ <el-tag v-if="getUnmappedColumnsCount() > 0" type="danger">{{ getUnmappedColumnsCount() }}列</el-tag>
327
+ <el-tag v-else type="success">全部已配置</el-tag>
328
+ </el-descriptions-item>
329
+ </el-descriptions>
330
+ </div>
331
+ </div>
332
+
333
+ <div class="form-actions">
334
+ <el-button type="success" :loading="importing" @click="onImport" style="width: 200px;">
335
+ <i class="el-icon-upload2"></i> 开始导入
336
+ </el-button>
337
+ <el-button @click="currentStep = 3" style="margin-left: 20px;">
338
+ <i class="el-icon-back"></i> 返回上一步
339
+ </el-button>
340
+ </div>
341
+ </el-card>
342
+ </div>
343
+ </div>
138
344
 
139
345
  <!--底部-->
140
346
  <template v-slot:footer>
141
347
  <el-button-group>
142
- <nm-button v-if="fileInfo && !parseResult" type="primary" :loading="parsing" text="解析文件" @click="onParse" />
143
- <nm-button v-if="parseResult" type="success" :loading="importing" text="开始导入" @click="onImport" />
144
- <nm-button type="info" text="取消" @click="hide" />
348
+ <el-button v-if="currentStep > 1" @click="currentStep--">
349
+ <i class="el-icon-arrow-left"></i> 上一步
350
+ </el-button>
351
+ <el-button v-if="currentStep < 4 && fileInfo" type="primary" @click="currentStep++">
352
+ 下一步 <i class="el-icon-arrow-right"></i>
353
+ </el-button>
354
+ <el-button type="info" @click="hide">
355
+ <i class="el-icon-close"></i> 取消
356
+ </el-button>
145
357
  </el-button-group>
146
358
  </template>
147
359
  </component>
148
360
  </template>
361
+
149
362
  <script>
150
- import dialog from '../../../../mixins/components/dialog'
151
- import { mapState } from 'vuex'
152
- import token from '../../../../utils/token'
153
- export default {
154
- mixins: [dialog],
155
- data() {
363
+ import dialog from '../../../../mixins/components/dialog'
364
+ import { mapState } from 'vuex'
365
+ import token from '../../../../utils/token'
366
+
367
+ export default {
368
+ mixins: [dialog],
369
+ data() {
370
+ return {
371
+ title: '数据导入',
372
+ icon: 'import',
373
+ width: '1000px',
374
+ height: '750px',
375
+ currentStep: 1,
376
+ model: {
377
+ file: null,
378
+ selectedSheet: 0,
379
+ hasHeader: true,
380
+ skipEmptyRows: false,
381
+ headerRowIndex: 0,
382
+ maxPreviewRows: 100,
383
+ deduplicate: true,
384
+ deduplicateFields: []
385
+ },
386
+ rules: {
387
+ selectedSheet: [{ required: true, message: '请选择工作表', trigger: 'change' }],
388
+ headerRowIndex: [{ required: true, message: '请输入表头行行号', trigger: 'blur' }],
389
+ maxPreviewRows: [{ required: true, message: '请输入最大预览行数', trigger: 'blur' }]
390
+ },
391
+ fileList: [],
392
+ fileInfo: null,
393
+ parseResult: null,
394
+ parsing: false,
395
+ importing: false,
396
+ columnMapping: [],
397
+ targetColumns: []
398
+ }
399
+ },
400
+ props: {
401
+ cols: Array,
402
+ options: Object,
403
+ action: Function
404
+ },
405
+ computed: {
406
+ ...mapState('app/user', { token: 'token' }),
407
+ uploadUrl() {
408
+ let baseURL = this.$http?.axios?.defaults?.baseURL || window.__HAIWEI_API_BASE_URL__ || '/api'
409
+ if (!baseURL.endsWith('/')) {
410
+ baseURL += '/'
411
+ }
412
+ const path = 'admin/excel/getexcelinfo'.replace(/^\//, '')
413
+ return baseURL + path
414
+ },
415
+ headers() {
416
+ const t = token.get()
417
+ const accessToken = t && t.accessToken ? t.accessToken : ''
156
418
  return {
157
- title: '导入数据',
158
- icon: 'import',
159
- width: '900px',
160
- height: '700px',
161
- model: {
162
- file: null,
163
- hasHeader: true,
164
- skipEmptyRows: false,
165
- maxPreviewRows: 100,
166
- selectedSheet: 0,
167
- importMode: 0,
168
- errorHandling: 0
169
- },
170
- rules: {
171
- file: [{ required: true, message: '请选择要导入的文件', trigger: 'change' }],
172
- selectedSheet: [{ required: true, message: '请选择工作表', trigger: 'change' }]
173
- },
174
- fileList: [],
175
- fileInfo: null,
176
- parseResult: null,
177
- parsing: false,
178
- importing: false,
179
- columnMapping: [],
180
- targetColumns: []
419
+ 'Authorization': `Bearer ${accessToken}`
181
420
  }
421
+ }
422
+ },
423
+ methods: {
424
+ init() {
425
+ this.currentStep = 1
426
+ this.fileList = []
427
+ this.fileInfo = null
428
+ this.parseResult = null
429
+ this.parsing = false
430
+ this.importing = false
431
+ this.columnMapping = []
432
+
433
+ this.initTargetColumns()
434
+
435
+ const { hasHeader = true, maxPreviewRows = 100, skipEmptyRows = false } = this.options
436
+ this.model.hasHeader = hasHeader
437
+ this.model.maxPreviewRows = maxPreviewRows
438
+ this.model.skipEmptyRows = skipEmptyRows
182
439
  },
183
- props: {
184
- /** 目标列配置 */
185
- cols: Array,
186
- /** 导入配置 */
187
- options: Object,
188
- /** 导入方法 */
189
- action: Function
440
+
441
+ initTargetColumns() {
442
+ if (this.cols && this.cols.length > 0) {
443
+ this.targetColumns = this.cols
444
+ .filter(col => col.show && col.name)
445
+ .map(col => ({
446
+ name: col.name,
447
+ label: col.label || col.name,
448
+ type: col.type || 'string'
449
+ }))
450
+ } else {
451
+ this.targetColumns = []
452
+ }
190
453
  },
191
- computed: {
192
- ...mapState('app/user', { token: 'token' }),
193
- uploadUrl() {
194
- // 从axios默认配置中获取baseURL,如果不存在则使用全局变量或默认值
195
- let baseURL = this.$http?.axios?.defaults?.baseURL || window.__HAIWEI_API_BASE_URL__ || '/api'
196
- // 确保baseURL以斜杠结尾,但不要有双斜杠
197
- if (!baseURL.endsWith('/')) {
198
- baseURL += '/'
199
- }
200
- // 移除路径开头的斜杠,避免双斜杠
201
- const path = 'admin/excel/parseexcel'.replace(/^\//, '')
202
- return baseURL + path
203
- },
204
- headers() {
205
- // 从token模块获取token
206
- const t = token.get()
207
- const accessToken = t && t.accessToken ? t.accessToken : ''
208
- return {
209
- 'Authorization': `Bearer ${accessToken}`
210
- }
211
- },
212
- uploadData() {
213
- return {
214
- hasHeader: this.model.hasHeader,
215
- maxPreviewRows: this.model.maxPreviewRows,
216
- skipEmptyRows: this.model.skipEmptyRows
217
- }
454
+
455
+ beforeUpload(file) {
456
+ const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
457
+ file.type === 'application/vnd.ms-excel'
458
+ const isLt10M = file.size / 1024 / 1024 < 10
459
+
460
+ if (!isExcel) {
461
+ this._error('只能上传Excel文件!')
462
+ return false
463
+ }
464
+ if (!isLt10M) {
465
+ this._error('文件大小不能超过10MB!')
466
+ return false
218
467
  }
468
+
469
+ this.model.file = file
470
+ return true
219
471
  },
220
- methods: {
221
- /**初始化 */
222
- init() {
223
- // 重置状态
224
- this.fileList = []
225
- this.fileInfo = null
226
- this.parseResult = null
227
- this.parsing = false
228
- this.importing = false
229
- this.columnMapping = []
230
-
231
- // 初始化目标列
232
- this.initTargetColumns()
233
-
234
- // 应用选项配置
235
- const { hasHeader = true, maxPreviewRows = 100, skipEmptyRows = false } = this.options
236
- this.model.hasHeader = hasHeader
237
- this.model.maxPreviewRows = maxPreviewRows
238
- this.model.skipEmptyRows = skipEmptyRows
239
- },
472
+
473
+ onUploadSuccess(response, file, fileList) {
474
+ if (response && response.code === 1) {
475
+ this.fileInfo = response.data
476
+ this._success('文件上传成功,请选择工作表并配置解析选项')
477
+ } else {
478
+ this._error(response.msg || '文件上传失败')
479
+ this.$refs.upload.clearFiles()
480
+ }
481
+ },
482
+
483
+ onUploadError(err, file, fileList) {
484
+ this._error('文件上传失败')
485
+ this.$refs.upload.clearFiles()
486
+ },
487
+
488
+ onFileRemove(file, fileList) {
489
+ this.model.file = null
490
+ this.fileInfo = null
491
+ this.parseResult = null
492
+ this.columnMapping = []
493
+ this.currentStep = 1
494
+ },
495
+
496
+ onParse() {
497
+ if (!this.model.file) {
498
+ this._error('请先选择文件')
499
+ return
500
+ }
240
501
 
241
- /**初始化目标列 */
242
- initTargetColumns() {
243
- if (this.cols && this.cols.length > 0) {
244
- this.targetColumns = this.cols
245
- .filter(col => col.show && col.name)
246
- .map(col => ({
247
- name: col.name,
248
- label: col.label || col.name,
249
- type: col.type || 'string'
250
- }))
251
- } else {
252
- this.targetColumns = []
253
- }
254
- },
502
+ this.parsing = true
503
+ const formData = new FormData()
504
+ formData.append('file', this.model.file)
255
505
 
256
- /**上传前验证 */
257
- beforeUpload(file) {
258
- const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
259
- file.type === 'application/vnd.ms-excel'
260
- const isLt100M = file.size / 1024 / 1024 < 100
261
-
262
- if (!isExcel) {
263
- this._error('只能上传Excel文件!')
264
- return false
265
- }
266
- if (!isLt100M) {
267
- this._error('文件大小不能超过100MB!')
268
- return false
269
- }
270
-
271
- this.model.file = file
272
- return true
273
- },
506
+ // 获取选中的工作表名称
507
+ const selectedSheet = this.fileInfo.sheets.find(sheet => sheet.index === this.model.selectedSheet)
508
+ const sheetName = selectedSheet ? selectedSheet.name : null
274
509
 
275
- /**上传成功 */
276
- onUploadSuccess(response, file, fileList) {
277
- // 检查响应格式
278
- if (response && response.code === 1) {
279
- this.fileInfo = response.data.basicInfo
280
- this.parseResult = {
281
- basicInfo: response.data.basicInfo,
282
- columns: response.data.headers.map((header, index) => ({
283
- name: `col${index}`,
284
- label: header.name,
285
- width: 120
286
- })),
287
- previewData: response.data.data.map(row => {
288
- const obj = {}
289
- response.data.headers.forEach((header, colIndex) => {
290
- obj[`col${colIndex}`] = row[colIndex]
291
- })
292
- return obj
293
- })
294
- }
295
- this.initColumnMapping()
296
- this._success('文件上传成功')
297
- } else {
298
- this._error(response.msg || '文件上传失败')
299
- this.$refs.upload.clearFiles()
510
+ // 添加解析参数
511
+ formData.append('hasHeader', this.model.hasHeader)
512
+ formData.append('skipEmptyRows', this.model.skipEmptyRows)
513
+ formData.append('headerRowIndex', this.model.headerRowIndex)
514
+ formData.append('maxPreviewRows', this.model.maxPreviewRows)
515
+ if (sheetName) {
516
+ formData.append('sheetName', sheetName)
517
+ }
518
+
519
+ // 从token模块获取token
520
+ const t = token.get()
521
+ const accessToken = t && t.accessToken ? t.accessToken : ''
522
+ const config = {
523
+ headers: {
524
+ 'Content-Type': 'multipart/form-data',
525
+ 'Authorization': `Bearer ${accessToken}`
300
526
  }
301
- },
527
+ }
302
528
 
303
- /**上传失败 */
304
- onUploadError(err, file, fileList) {
305
- this._error('文件上传失败')
306
- this.$refs.upload.clearFiles()
307
- },
529
+ // 构建解析URL
530
+ let baseURL = this.$http?.axios?.defaults?.baseURL || window.__HAIWEI_API_BASE_URL__ || '/api'
531
+ if (!baseURL.endsWith('/')) {
532
+ baseURL += '/'
533
+ }
534
+ const path = 'admin/excel/parseexcel'.replace(/^\//, '')
535
+ const parseUrl = baseURL + path
308
536
 
309
- /**文件移除 */
310
- onFileRemove(file, fileList) {
311
- this.model.file = null
312
- this.fileInfo = null
313
- this.parseResult = null
314
- this.columnMapping = []
315
- },
537
+ // 添加查询参数
538
+ const queryParams = new URLSearchParams()
539
+ queryParams.append('hasHeader', this.model.hasHeader)
540
+ queryParams.append('skipEmptyRows', this.model.skipEmptyRows)
541
+ queryParams.append('headerRowIndex', this.model.headerRowIndex)
542
+ queryParams.append('maxPreviewRows', this.model.maxPreviewRows)
543
+ if (sheetName) {
544
+ queryParams.append('sheetName', sheetName)
545
+ }
316
546
 
317
- /**初始化列映射 */
318
- initColumnMapping() {
319
- if (!this.parseResult || !this.parseResult.columns) return
320
-
321
- this.columnMapping = this.parseResult.columns.map(col => ({
322
- excelColumn: col,
323
- targetColumn: this.findBestMatch(col),
324
- required: false,
325
- defaultValue: ''
326
- }))
327
- },
547
+ const fullUrl = `${parseUrl}?${queryParams.toString()}`
328
548
 
329
- /**查找最佳匹配的目标列 */
330
- findBestMatch(excelColumn) {
331
- if (!this.targetColumns || this.targetColumns.length === 0) return null
332
-
333
- const excelLabel = excelColumn.label.toLowerCase()
334
- const excelName = excelColumn.name.toLowerCase()
335
-
336
- // 1. 尝试完全匹配标签
337
- let match = this.targetColumns.find(col => col.label.toLowerCase() === excelLabel)
338
- if (match) return match.name
339
-
340
- // 2. 尝试完全匹配名称
341
- match = this.targetColumns.find(col => col.name.toLowerCase() === excelName)
342
- if (match) return match.name
343
-
344
- // 3. 尝试包含匹配
345
- match = this.targetColumns.find(col =>
346
- excelLabel.includes(col.label.toLowerCase()) ||
347
- col.label.toLowerCase().includes(excelLabel)
348
- )
349
- if (match) return match.name
350
-
351
- return null
352
- },
549
+ // 使用axios发送请求,兼容不同的Vue配置
550
+ const axiosInstance = this.$http?.axios || this.$http || window.axios
353
551
 
354
- /**解析文件 */
355
- onParse() {
356
- if (!this.model.file) {
357
- this._error('请先选择文件')
358
- return
359
- }
360
-
361
- this.parsing = true
362
- const formData = new FormData()
363
- formData.append('file', this.model.file)
364
- formData.append('hasHeader', this.model.hasHeader)
365
- formData.append('maxPreviewRows', this.model.maxPreviewRows)
366
- formData.append('skipEmptyRows', this.model.skipEmptyRows)
367
-
368
- // 使用axios直接发送请求,避免$api可能为undefined的问题
369
- // 从token模块获取token
370
- const t = token.get()
371
- const accessToken = t && t.accessToken ? t.accessToken : ''
372
- const config = {
373
- headers: {
374
- 'Content-Type': 'multipart/form-data',
375
- 'Authorization': `Bearer ${accessToken}`
376
- }
377
- }
378
-
379
- // 使用与uploadUrl相同的逻辑获取API基础URL
380
- let baseURL = this.$http?.axios?.defaults?.baseURL || window.__HAIWEI_API_BASE_URL__ || '/api'
381
- // 确保baseURL以斜杠结尾,但不要有双斜杠
382
- if (!baseURL.endsWith('/')) {
383
- baseURL += '/'
384
- }
385
- // 移除路径开头的斜杠,避免双斜杠
386
- const path = 'admin/excel/parseexcel'.replace(/^\//, '')
387
- this.$http.post(baseURL + path, formData, config)
552
+ if (!axiosInstance) {
553
+ this._error('HTTP客户端未初始化')
554
+ this.parsing = false
555
+ return
556
+ }
557
+
558
+ axiosInstance.post(fullUrl, formData, config)
388
559
  .then(response => {
389
- // 检查响应格式
390
560
  if (response.data && response.data.code === 1) {
391
561
  this.fileInfo = response.data.data.basicInfo
392
562
  this.parseResult = {
@@ -405,6 +575,7 @@
405
575
  })
406
576
  }
407
577
  this.initColumnMapping()
578
+ this.currentStep = 3
408
579
  this._success('文件解析成功')
409
580
  } else {
410
581
  this._error(response.data.msg || '文件解析失败')
@@ -416,121 +587,365 @@
416
587
  .finally(() => {
417
588
  this.parsing = false
418
589
  })
419
- },
590
+ },
591
+
592
+ initColumnMapping() {
593
+ if (!this.parseResult || !this.parseResult.columns) return
594
+
595
+ this.columnMapping = this.parseResult.columns.map(col => ({
596
+ excelColumn: col,
597
+ targetColumn: this.findBestMatch(col),
598
+ required: false,
599
+ defaultValue: ''
600
+ }))
601
+ },
602
+
603
+ findBestMatch(excelColumn) {
604
+ if (!this.targetColumns || this.targetColumns.length === 0) return null
605
+
606
+ const excelLabel = excelColumn.label.toLowerCase()
607
+ const excelName = excelColumn.name.toLowerCase()
608
+
609
+ // 1. 尝试完全匹配标签
610
+ let match = this.targetColumns.find(col => col.label.toLowerCase() === excelLabel)
611
+ if (match) return match.name
612
+
613
+ // 2. 尝试完全匹配名称
614
+ match = this.targetColumns.find(col => col.name.toLowerCase() === excelName)
615
+ if (match) return match.name
616
+
617
+ // 3. 尝试包含匹配
618
+ match = this.targetColumns.find(col =>
619
+ excelLabel.includes(col.label.toLowerCase()) ||
620
+ col.label.toLowerCase().includes(excelLabel)
621
+ )
622
+ if (match) return match.name
623
+
624
+ return null
625
+ },
626
+
627
+ onImport() {
628
+ if (!this.parseResult) {
629
+ this._error('请先解析文件')
630
+ return
631
+ }
632
+
633
+ // 验证必填字段
634
+ const missingRequired = this.columnMapping
635
+ .filter(mapping => mapping.required && !mapping.targetColumn)
636
+ .map(mapping => mapping.excelColumn.label)
637
+
638
+ if (missingRequired.length > 0) {
639
+ this._error(`以下必填字段未配置映射:${missingRequired.join('、')}`)
640
+ return
641
+ }
642
+
643
+ this.importing = true
420
644
 
421
- /**开始导入 */
422
- onImport() {
423
- if (!this.parseResult) {
424
- this._error('请先解析文件')
645
+ try {
646
+ // 根据列映射配置生成AddModel列表
647
+ const addModels = this.generateAddModels()
648
+
649
+ if (addModels.length === 0) {
650
+ this._error('没有可导入的数据')
651
+ this.importing = false
425
652
  return
426
653
  }
427
654
 
428
- // 验证必填字段
429
- const missingRequired = this.columnMapping
430
- .filter(mapping => mapping.required && !mapping.targetColumn)
431
- .map(mapping => mapping.excelColumn.label)
655
+ // 调用导入方法,传递AddModel列表
656
+ if (this.action && typeof this.action === 'function') {
657
+ this.action(addModels)
658
+ .then(() => {
659
+ this._success(`成功导入${addModels.length}条数据`)
660
+ this.hide()
661
+ this.$emit('import-success')
662
+ })
663
+ .catch(error => {
664
+ this._error('数据导入失败:' + (error.message || '未知错误'))
665
+ })
666
+ .finally(() => {
667
+ this.importing = false
668
+ })
669
+ } else {
670
+ this._error('未配置导入方法')
671
+ this.importing = false
672
+ }
673
+ } catch (error) {
674
+ this._error('数据转换失败:' + error.message)
675
+ this.importing = false
676
+ }
677
+ },
678
+
679
+ generateAddModels() {
680
+ if (!this.parseResult || !this.parseResult.previewData) {
681
+ return []
682
+ }
683
+
684
+ // 获取有效的列映射(有目标字段的映射)
685
+ const validMappings = this.columnMapping.filter(mapping => mapping.targetColumn)
686
+
687
+ // 转换每一行数据为AddModel
688
+ const allAddModels = this.parseResult.previewData.map((row, rowIndex) => {
689
+ const addModel = {}
432
690
 
433
- if (missingRequired.length > 0) {
434
- this._error(`以下必填字段未配置映射:${missingRequired.join('、')}`)
435
- return
691
+ // 根据列映射配置设置字段值
692
+ validMappings.forEach(mapping => {
693
+ const excelColumnName = mapping.excelColumn.name
694
+ const targetColumn = mapping.targetColumn
695
+ let value = row[excelColumnName]
696
+
697
+ // 如果值为空且设置了默认值,使用默认值
698
+ if ((value === null || value === undefined || value === '') && mapping.defaultValue) {
699
+ value = mapping.defaultValue
700
+ }
701
+
702
+ // 设置到AddModel中
703
+ addModel[targetColumn] = value
704
+ })
705
+
706
+ return addModel
707
+ })
708
+
709
+ // 如果启用了去重,进行去重处理
710
+ if (this.model.deduplicate && allAddModels.length > 0) {
711
+ return this.deduplicateAddModels(allAddModels)
712
+ }
713
+
714
+ return allAddModels
715
+ },
716
+
717
+ deduplicateAddModels(addModels) {
718
+ if (!addModels || addModels.length === 0) {
719
+ return []
720
+ }
721
+
722
+ // 获取所有已配置的目标字段
723
+ const targetFields = this.columnMapping
724
+ .filter(mapping => mapping.targetColumn)
725
+ .map(mapping => mapping.targetColumn)
726
+
727
+ // 如果没有目标字段,无法去重
728
+ if (targetFields.length === 0) {
729
+ return addModels
730
+ }
731
+
732
+ // 使用第一个目标字段作为去重依据(通常是最重要的字段,如员工号、工具编码等)
733
+ // 在实际应用中,可能需要更复杂的去重逻辑,比如多个字段组合
734
+ const deduplicateField = targetFields[0]
735
+
736
+ // 使用Map进行去重,保留第一次出现的数据
737
+ const uniqueMap = new Map()
738
+ const deduplicatedModels = []
739
+
740
+ for (const model of addModels) {
741
+ const key = model[deduplicateField]
742
+
743
+ // 如果key为空,跳过去重
744
+ if (key === null || key === undefined || key === '') {
745
+ deduplicatedModels.push(model)
746
+ continue
436
747
  }
437
748
 
438
- this.importing = true
749
+ // 如果还没有这个key,添加到Map和结果列表
750
+ if (!uniqueMap.has(key)) {
751
+ uniqueMap.set(key, true)
752
+ deduplicatedModels.push(model)
753
+ } else {
754
+ // 重复数据,记录日志(可选)
755
+ console.log(`发现重复数据,字段 ${deduplicateField} = ${key},已跳过`)
756
+ }
757
+ }
758
+
759
+ // 记录去重统计
760
+ const originalCount = addModels.length
761
+ const deduplicatedCount = deduplicatedModels.length
762
+ const removedCount = originalCount - deduplicatedCount
763
+
764
+ if (removedCount > 0) {
765
+ this._info(`数据去重:原始 ${originalCount} 条,去重后 ${deduplicatedCount} 条,移除 ${removedCount} 条重复数据`)
766
+ }
767
+
768
+ return deduplicatedModels
769
+ },
770
+
771
+ formatFileSize(bytes) {
772
+ if (bytes === 0) return '0 B'
773
+ const k = 1024
774
+ const sizes = ['B', 'KB', 'MB', 'GB']
775
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
776
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
777
+ },
778
+
779
+ getSelectedSheetName() {
780
+ if (!this.fileInfo || !this.fileInfo.sheets) return ''
781
+ const selectedSheet = this.fileInfo.sheets.find(sheet => sheet.index === this.model.selectedSheet)
782
+ return selectedSheet ? selectedSheet.name : ''
783
+ },
784
+
785
+ getMappedColumnsCount() {
786
+ if (!this.columnMapping) return 0
787
+ return this.columnMapping.filter(mapping => mapping.targetColumn).length
788
+ },
789
+
790
+ getUnmappedColumnsCount() {
791
+ if (!this.columnMapping) return 0
792
+ return this.columnMapping.filter(mapping => !mapping.targetColumn).length
793
+ }
794
+ },
795
+ created() {
796
+ this.init()
797
+ }
798
+ }
799
+ </script>
800
+
801
+ <style lang="scss" scoped>
802
+ .import-container {
803
+ .import-steps {
804
+ margin-bottom: 20px;
805
+ }
806
+
807
+ .step-content {
808
+ .step-card {
809
+ border: 1px solid #dcdfe6;
810
+ border-radius: 4px;
811
+
812
+ .step-header {
813
+ display: flex;
814
+ justify-content: space-between;
815
+ align-items: center;
816
+ padding: 12px 16px;
817
+ border-bottom: 1px solid #dcdfe6;
818
+ background-color: #f5f7fa;
439
819
 
440
- try {
441
- // 根据列映射配置生成AddModel列表
442
- const addModels = this.generateAddModels()
820
+ .header-left {
821
+ display: flex;
822
+ flex-direction: column;
443
823
 
444
- if (addModels.length === 0) {
445
- this._error('没有可导入的数据')
446
- this.importing = false
447
- return
824
+ .step-title {
825
+ font-size: 14px;
826
+ font-weight: 600;
827
+ color: #303133;
828
+ margin-bottom: 4px;
448
829
  }
449
830
 
450
- // 调用导入方法,传递AddModel列表
451
- if (this.action && typeof this.action === 'function') {
452
- this.action(addModels)
453
- .then(() => {
454
- this._success(`成功导入${addModels.length}条数据`)
455
- this.hide()
456
- this.$emit('import-success')
457
- })
458
- .catch(error => {
459
- this._error('数据导入失败:' + (error.message || '未知错误'))
460
- })
461
- .finally(() => {
462
- this.importing = false
463
- })
464
- } else {
465
- this._error('未配置导入方法')
466
- this.importing = false
831
+ .step-subtitle {
832
+ font-size: 12px;
833
+ color: #909399;
467
834
  }
468
- } catch (error) {
469
- this._error('数据转换失败:' + error.message)
470
- this.importing = false
471
- }
472
- },
473
-
474
- /**根据列映射配置生成AddModel列表 */
475
- generateAddModels() {
476
- if (!this.parseResult || !this.parseResult.previewData) {
477
- return []
478
835
  }
479
836
 
480
- // 获取有效的列映射(有目标字段的映射)
481
- const validMappings = this.columnMapping.filter(mapping => mapping.targetColumn)
837
+ .header-right {
838
+ display: flex;
839
+ align-items: center;
840
+ }
841
+ }
842
+ }
843
+ }
844
+
845
+ .upload-placeholder {
846
+ display: flex;
847
+ flex-direction: column;
848
+ align-items: center;
849
+ justify-content: center;
850
+ padding: 40px 16px;
851
+ text-align: center;
852
+
853
+ .placeholder-content {
854
+ .placeholder-icon {
855
+ font-size: 36px;
856
+ color: #c0c4cc;
857
+ margin-bottom: 16px;
858
+ }
859
+
860
+ .placeholder-text {
861
+ font-size: 14px;
862
+ color: #606266;
863
+ margin-bottom: 8px;
864
+ font-weight: 500;
865
+ }
866
+
867
+ .placeholder-tip {
868
+ font-size: 12px;
869
+ color: #909399;
870
+ }
871
+ }
872
+ }
873
+
874
+ .file-info-card {
875
+ margin-top: 16px;
876
+ padding: 16px;
877
+ background-color: #f8f9fa;
878
+ border-radius: 4px;
879
+
880
+ .file-info {
881
+ margin-bottom: 16px;
882
+ }
883
+
884
+ .sheet-select {
885
+ margin-top: 16px;
886
+ }
887
+ }
888
+
889
+ .form-actions {
890
+ display: flex;
891
+ justify-content: center;
892
+ margin-top: 24px;
893
+ padding-top: 16px;
894
+ border-top: 1px solid #dcdfe6;
895
+ }
896
+
897
+ .mapping-container {
898
+ .preview-section {
899
+ margin-bottom: 24px;
900
+
901
+ .preview-alert {
902
+ margin-bottom: 12px;
903
+ }
904
+
905
+ .preview-table {
906
+ margin-bottom: 8px;
482
907
 
483
- // 转换每一行数据为AddModel
484
- return this.parseResult.previewData.map((row, rowIndex) => {
485
- const addModel = {}
908
+ .column-header {
909
+ text-align: center;
486
910
 
487
- // 根据列映射配置设置字段值
488
- validMappings.forEach(mapping => {
489
- const excelColumnName = mapping.excelColumn.name
490
- const targetColumn = mapping.targetColumn
491
- let value = row[excelColumnName]
492
-
493
- // 如果值为空且设置了默认值,使用默认值
494
- if ((value === null || value === undefined || value === '') && mapping.defaultValue) {
495
- value = mapping.defaultValue
496
- }
497
-
498
- // 设置到AddModel中
499
- addModel[targetColumn] = value
500
- })
911
+ .column-label {
912
+ font-weight: 500;
913
+ }
501
914
 
502
- return addModel
503
- })
504
- },
915
+ .column-name {
916
+ font-size: 11px;
917
+ color: #909399;
918
+ }
919
+ }
920
+ }
505
921
 
506
- /**格式化文件大小 */
507
- formatFileSize(bytes) {
508
- if (bytes === 0) return '0 B'
509
- const k = 1024
510
- const sizes = ['B', 'KB', 'MB', 'GB']
511
- const i = Math.floor(Math.log(bytes) / Math.log(k))
512
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
922
+ .preview-tip {
923
+ text-align: center;
924
+ font-size: 12px;
925
+ color: #909399;
926
+ margin-top: 8px;
513
927
  }
514
- },
515
- created() {
516
- this.init()
517
928
  }
518
- }
519
- </script>
520
-
521
- <style lang="scss">
522
- .nm-upload {
523
- .el-upload {
524
- width: 100%;
525
929
 
526
- .el-upload-dragger {
527
- width: 100%;
528
- height: 180px;
529
- display: flex;
530
- flex-direction: column;
531
- justify-content: center;
532
- align-items: center;
930
+ .mapping-section {
931
+ .mapping-table {
932
+ .excel-column {
933
+ .column-label {
934
+ font-weight: 500;
935
+ margin-bottom: 2px;
936
+ }
937
+
938
+ .column-name {
939
+ font-size: 11px;
940
+ color: #909399;
941
+ }
942
+ }
943
+ }
533
944
  }
534
945
  }
946
+
947
+ .import-summary {
948
+ margin: 16px 0;
949
+ }
535
950
  }
536
951
  </style>