提交产品列表

This commit is contained in:
wuweihua
2026-09-07 18:09:59 +08:00
parent 3994bd7998
commit 9e98cd791e
14 changed files with 528 additions and 39 deletions
+10 -1
View File
@@ -46,6 +46,7 @@
:height="height"
:max-height="maxHeight"
:default-sort="defaultSort"
:span-method="spanMethod"
@selection-change="handleSelectionChange"
@row-click="(row, col, e) => $emit('row-click', row, col, e)"
@sort-change="(c) => $emit('sort-change', c)"
@@ -241,6 +242,11 @@ const props = defineProps({
type: Object,
default: undefined
},
// 单元格合并方法(透传 el-table span-method),签名同 el-table:({ row, column, rowIndex, columnIndex }) => { rowspan, colspan }
spanMethod: {
type: Function,
default: null
},
// 每页条数可选项
pageSizes: {
type: Array,
@@ -425,7 +431,10 @@ defineExpose({
getTableRef,
getSearchFormRef,
getQueryParams,
setQueryParams
setQueryParams,
getData: () => tableData.value,
/** 当前页数据(响应式,父组件 span-method 可直接订阅) */
data: tableData
})
if (props.autoLoad) {
@@ -1,22 +1,69 @@
<template>
<el-dialog :title="title" v-model="visible" :width="width" append-to-body @close="handleClose">
<el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="headers" :action="uploadUrl" :disabled="isUploading" :on-progress="handleProgress" :on-change="handleFileChange" :on-remove="handleFileRemove" :on-success="handleSuccess" :auto-upload="false" drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<div class="el-upload__tip">
<el-checkbox v-model="updateSupport"> {{ updateSupportLabel }} </el-checkbox>
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link v-if="templateUrl" type="primary" underline="never" style="font-size: 12px; vertical-align: baseline" @click="handleDownloadTemplate">下载模板</el-link>
<el-dialog :title="title" v-model="visible" :width="width" append-to-body @close="handleClose" class="excel-import-dialog">
<div class="import-body">
<!-- 业务说明(可选) -->
<div v-if="importDesc.length" class="import-desc">
<el-icon class="desc-icon"><Info-Filled /></el-icon>
<ul class="desc-list">
<li v-for="(line, i) in importDesc" :key="i">{{ line }}</li>
</ul>
</div>
<!-- 上传拖拽区 -->
<el-upload
ref="uploadRef"
:limit="1"
:accept="accept"
:headers="headers"
:action="uploadUrl"
:disabled="isUploading"
:on-progress="handleProgress"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:on-success="handleSuccess"
:auto-upload="false"
drag
class="upload-area"
>
<div class="upload-placeholder">
<el-icon class="upload-icon"><upload-filled /></el-icon>
<div class="upload-title">点击或拖拽上传文件</div>
<div class="upload-hint">支持 {{ accept }} 格式,单次最多导入 1 个文件,文件大小不超过 {{ fileSizeMb }} MB</div>
</div>
</template>
</el-upload>
</el-upload>
<!-- 已选文件 + 进度 -->
<div v-if="selectedFile || isUploading" class="file-progress">
<div v-if="selectedFile" class="selected-file">
<el-icon class="file-icon"><Document /></el-icon>
<span class="file-name" :title="selectedFile.name">{{ selectedFile.name }}</span>
<el-icon v-if="!isUploading" class="file-remove" @click="handleClearFile"><CircleClose /></el-icon>
</div>
<el-progress v-if="isUploading" :percentage="progressPercent" :stroke-width="6" :show-text="true" status="success" class="upload-progress" />
</div>
<!-- 校验错误 -->
<div v-if="validateError" class="validate-error">
<el-icon><Warning-Filled /></el-icon>
<span>{{ validateError }}</span>
</div>
<!-- 选项与提示 -->
<div class="import-options">
<el-checkbox v-if="showUpdateSupport" v-model="updateSupport" class="update-checkbox">{{ updateSupportLabel }}</el-checkbox>
<div class="import-tips">
<el-icon class="tip-icon"><Info-Filled /></el-icon>
<div class="tip-text">
仅允许上传 {{ accept }} 格式文件,文件大小不超过 {{ fileSizeMb }} MB。
<el-link v-if="templateUrl" type="primary" underline="never" class="template-link" @click="handleDownloadTemplate">下载模板</el-link>
</div>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit">确 定</el-button>
<el-button @click="visible = false">取 消</el-button>
<el-button type="primary" :disabled="!selectedFile || isUploading" :loading="isUploading" @click="handleSubmit">{{ submitText }}</el-button>
<el-button @click="visible = false" :disabled="isUploading">取 消</el-button>
</div>
</template>
</el-dialog>
@@ -36,7 +83,7 @@ const props = defineProps({
// 对话框宽度
width: {
type: String,
default: '400px'
default: '420px'
},
// 上传接口地址(必传)
action: {
@@ -62,6 +109,31 @@ const props = defineProps({
defaultUpdateSupport: {
type: Boolean,
default: false
},
// 是否显示「覆盖更新」勾选(设为 false 可完全隐藏该选项)
showUpdateSupport: {
type: Boolean,
default: true
},
// 接收的文件类型(扩展名或 MIME),默认 xlsx/xls
accept: {
type: String,
default: '.xlsx, .xls'
},
// 单文件大小上限(MB),默认 50
fileSizeMb: {
type: Number,
default: 50
},
// 业务说明(字符串数组),显示在弹窗顶部 importDesc 段
importDesc: {
type: Array,
default: () => []
},
// 确定按钮文字
submitText: {
type: String,
default: '确 定'
}
})
@@ -72,6 +144,8 @@ const visible = ref(false)
const selectedFile = ref(null)
const isUploading = ref(false)
const updateSupport = ref(false)
const progressPercent = ref(0)
const validateError = ref('')
const headers = { Authorization: 'Bearer ' + getToken() }
const uploadUrl = computed(() => {
@@ -84,6 +158,8 @@ const templateUrl = computed(() => !!props.templateAction)
function open() {
updateSupport.value = props.defaultUpdateSupport
isUploading.value = false
progressPercent.value = 0
validateError.value = ''
visible.value = true
nextTick(() => {
selectedFile.value = null
@@ -94,6 +170,8 @@ function open() {
// 关闭时清理
function handleClose() {
isUploading.value = false
progressPercent.value = 0
validateError.value = ''
selectedFile.value = null
uploadRef.value?.clearFiles()
}
@@ -104,24 +182,64 @@ function handleDownloadTemplate() {
}
// 上传进度
function handleProgress() {
function handleProgress(event, file) {
isUploading.value = true
if (file && typeof file.percent === 'number') {
progressPercent.value = Math.round(file.percent)
}
}
// 文件类型/大小校验
function checkFile(file) {
if (!file) return '请先选择文件'
const name = (file.name || '').toLowerCase()
const types = props.accept.split(',').map(s => s.trim().toLowerCase()).filter(Boolean)
if (types.length) {
const ok = types.some(t => {
const ext = t.startsWith('.') ? t : '.' + t
return name.endsWith(ext)
})
if (!ok) return `仅允许上传 ${props.accept} 格式文件`
}
const sizeMb = file.size / 1024 / 1024
if (sizeMb > props.fileSizeMb) return `文件大小不能超过 ${props.fileSizeMb} MB`
return ''
}
/** 文件选择处理 */
const handleFileChange = (file, fileList) => {
progressPercent.value = 0
const err = checkFile(file)
if (err) {
validateError.value = err
selectedFile.value = null
nextTick(() => uploadRef.value?.clearFiles())
return
}
validateError.value = ''
selectedFile.value = file
}
/** 文件删除处理 */
const handleFileRemove = (file, fileList) => {
selectedFile.value = null
progressPercent.value = 0
validateError.value = ''
}
/** 手动清除已选文件 */
function handleClearFile() {
selectedFile.value = null
validateError.value = ''
uploadRef.value?.clearFiles()
}
// 上传成功
function handleSuccess(response) {
visible.value = false
isUploading.value = false
progressPercent.value = 0
validateError.value = ''
selectedFile.value = null
uploadRef.value?.clearFiles()
proxy.$alert("<div style='overflow:auto;overflow-x:hidden;max-height:70vh;padding:10px 20px 0;'>" + response.msg + '</div>', '导入结果', { dangerouslyUseHTMLString: true })
@@ -131,8 +249,9 @@ function handleSuccess(response) {
// 提交上传
function handleSubmit() {
const file = selectedFile.value
if (!file || file.length === 0 || !file.name.toLowerCase().endsWith('.xls') && !file.name.toLowerCase().endsWith('.xlsx')) {
proxy.$modal.msgError("请选择后缀为 “xls”或“xlsx”的文件。")
const err = checkFile(file)
if (err) {
proxy.$modal.msgError(err)
return
}
uploadRef.value.submit()
@@ -140,3 +259,167 @@ function handleSubmit() {
defineExpose({ open })
</script>
<style scoped>
.excel-import-dialog :deep(.el-dialog__body) {
padding-bottom: 8px;
}
.import-body {
padding: 0 10px;
}
.upload-area :deep(.el-upload-dragger) {
padding: 44px 20px;
border: 1px dashed var(--el-border-color);
border-radius: 8px;
transition: border-color 0.2s, background-color 0.2s;
}
.upload-area :deep(.el-upload-dragger:hover),
.upload-area :deep(.el-upload-dragger.is-dragover) {
border-color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
}
.upload-placeholder {
text-align: center;
color: var(--el-text-color-regular);
}
.upload-icon {
font-size: 46px;
color: var(--el-color-primary);
}
.upload-title {
font-size: 16px;
margin-top: 14px;
color: var(--el-text-color-primary);
font-weight: 500;
}
.upload-hint {
font-size: 13px;
margin-top: 8px;
color: var(--el-text-color-secondary);
}
.selected-file {
margin-top: 12px;
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
background: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-7);
border-radius: 6px;
color: var(--el-text-color-primary);
}
.file-icon {
color: var(--el-color-primary);
font-size: 18px;
flex-shrink: 0;
}
.file-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
}
.file-remove {
color: var(--el-text-color-secondary);
cursor: pointer;
font-size: 18px;
flex-shrink: 0;
}
.file-remove:hover {
color: var(--el-color-danger);
}
.import-options {
margin-top: 16px;
padding: 14px 16px;
background: var(--el-fill-color-light);
border-radius: 8px;
}
.update-checkbox {
font-weight: 500;
color: var(--el-text-color-primary);
}
.import-tips {
margin-top: 10px;
display: flex;
align-items: flex-start;
gap: 6px;
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.5;
}
.tip-icon {
margin-top: 1px;
font-size: 14px;
color: var(--el-color-info);
flex-shrink: 0;
}
.template-link {
font-size: 12px;
vertical-align: baseline;
}
.import-desc {
margin-bottom: 14px;
padding: 12px 14px;
background: var(--el-color-info-light-9);
border: 1px solid var(--el-color-info-light-7);
border-radius: 8px;
display: flex;
align-items: flex-start;
gap: 8px;
color: var(--el-text-color-regular);
font-size: 13px;
line-height: 1.6;
}
.desc-icon {
margin-top: 2px;
color: var(--el-color-info);
flex-shrink: 0;
}
.desc-list {
margin: 0;
padding-left: 18px;
flex: 1;
}
.file-progress {
margin-top: 12px;
}
.upload-progress {
margin-top: 8px;
}
.validate-error {
margin-top: 12px;
padding: 8px 12px;
background: var(--el-color-danger-light-9);
border: 1px solid var(--el-color-danger-light-7);
border-radius: 6px;
color: var(--el-color-danger);
font-size: 13px;
display: flex;
align-items: center;
gap: 6px;
}
</style>