| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- <template>
- <div class="upload-container">
- <!-- 上传按钮和清空按钮 -->
- <div class="upload-buttons">
- <!-- <el-button
- plain
- type="primary"
- size="small"
- :disabled="isUploadDisabled || isDisabled"
- @click="showConfirmDialog"
- >
- {{ buttonText }}
- </el-button> -->
- <el-upload
- :action="uploadUrl"
- :auto-upload="false"
- :on-change="handleChange"
- :file-list="uploadList"
- :accept="accept"
- :multiple="uploadMode == 'single' ? false : true"
- :data="{ businessFolder: businessFolder }"
- style="display: inline-block"
- >
- <el-tooltip effect="dark" placement="top">
- <el-button
- plain
- :type="props.btnType ? props.btnType : 'primary'"
- size="small"
- :disabled="isUploadDisabled || isDisabled"
- >
- {{ buttonText }}
- </el-button>
- <div slot="content">
- 请上传(大小不超过{{ formatFileSize(maxSize) }},格式:{{
- allowedTypes.join('/')
- }})
- </div>
- </el-tooltip>
- </el-upload>
- <!-- <el-dialog
- title="选择文件"
- :visible.sync="confirmDialogVisible"
- width="30%"
- :modal-append-to-body="true"
- :append-to-body="true"
- >
- <el-upload
- :action="uploadUrl"
- :auto-upload="false"
- :on-change="handleChange"
- :file-list="uploadList"
- accept=".xlsx,.xls,.doc,.docx,.pdf"
- :multiple="uploadMode == 'single' ? false : true"
- :data="{ businessFolder: businessFolder }"
- style="display: inline-block"
- >
- <el-button
- plain
- type="primary"
- size="small"
- :disabled="isUploadDisabled"
- >
- 选择文件
- </el-button>
- </el-upload>
- <div slot="footer" class="dialog-footer">
- <el-button type="primary" size="small" @click="confirmUpload">
- 确认
- </el-button>
- <el-button
- plain
- type="primary"
- size="small"
- @click="confirmDialogVisible = false"
- >
- 取消
- </el-button>
- </div>
- </el-dialog> -->
- <!-- <el-button plain type="primary" size="small" @click="clearFiles">
- 清空
- </el-button> -->
- </div>
- <!-- 附件列表(3个以内) v-if="files.length <= 3" -->
- <div class="file-list-simple">
- <div v-for="(file, index) in files" :key="index" class="file-item">
- <i
- class="fileIcon iconfont-5039297"
- :class="fileIcon(file.fileExtension)"
- ></i>
- <span class="file-fileName" @click="handlePreview(file)">
- {{ file.fileName }}
- </span>
- <!-- 状态标签 -->
- <span class="file-status">
- <el-tag size="mini" :type="getStatusType(file.status)">
- {{ getStatusText(file.status) }}
- </el-tag>
- </span>
- <span
- class="delete-btn"
- :disabled="isDisabled"
- :class="{ 'disabled-text': isDisabled }"
- @click="removeFile(index)"
- >
- <el-tooltip effect="dark" content="删除" placement="top">
- <i class="iconfont-5039297 icon-shanchu1"></i>
- </el-tooltip>
- </span>
- </div>
- </div>
- <!-- 表格形式(超过3个) -->
- <!-- <div v-else class="file-list-table">
- <el-table :data="files" style="width: 100%">
- <el-table-column type="index" label="序号" width="60" />
- <el-table-column prop="fileName" label="附件名称" />
- <el-table-column prop="type" label="附件类型" width="100">
- <template slot-scope="scope">
- {{ scope.row.fileExtension || '' }}
- </template>
- </el-table-column>
- <el-table-column prop="fileSize" label="附件大小" width="100" />
- <el-table-column label="状态" width="80">
- <template slot-scope="scope">
- <el-tag size="mini" :type="getStatusType(scope.row.status)">
- {{ getStatusText(scope.row.status) }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="200">
- <template slot-scope="scope">
- <el-button size="mini" @click="handleSort(scope.$index, 'up')">
- ↑
- </el-button>
- <el-button size="mini" @click="handleSort(scope.$index, 'down')">
- ↓
- </el-button>
- <el-button
- size="mini"
- type="danger"
- @click="removeFile(scope.$index)"
- >
- 删除
- </el-button>
- </template>
- </el-table-column>
- </el-table> -->
- <!-- </div> -->
- <!-- 底部按钮(仅当超过3个时显示) -->
- <!-- <div v-if="files.length > 3" class="table-footer">
- <el-button type="primary" size="small" @click="saveFiles">保存</el-button>
- <el-button size="small">关闭</el-button>
- </div> -->
- </div>
- </template>
- <script>
- import { uploadFile } from '@/api/file'
- export default {
- name: 'UploadComponent',
- props: {
- filesList: {
- type: Array,
- default: () => [],
- },
- uploadMode: {
- type: String,
- default: 'single', // single 或 multiple
- },
- actionUrl: {
- type: String,
- default: '', // 上传路径
- },
- businessFolder: {
- type: String,
- default: '',
- },
- buttonText: {
- type: String,
- default: '上传',
- },
- maxSize: {
- type: Number,
- default: 50 * 1024 * 1024, // 默认50MB
- validator(value) {
- return value >= 0
- },
- },
- allowedTypes: {
- type: Array,
- default: () => ['xlsx', 'xls', 'doc', 'docx', 'pdf'],
- },
- isDisabled: {
- type: Boolean,
- default: false,
- },
- props: {
- type: Object,
- default: () => ({}),
- },
- },
- data() {
- return {
- files: [],
- uploadList: [],
- uploadUrl: '/api/file/v1/upload', // 单文件上传地址
- uploading: false,
- confirmDialogVisible: false,
- }
- },
- computed: {
- // 计算属性:判断上传按钮是否应该禁用
- isUploadDisabled() {
- // 只在单文件上传模式下检查
- if (this.uploadMode === 'single') {
- // 如果已经有文件(无论是上传的还是回显的),则禁用上传按钮
- return this.files.length > 0 || this.uploadList.length > 0
- }
- // 多文件上传模式下不禁用
- return false
- },
- accept() {
- let allowedTypes = this.allowedTypes.map((type) => {
- return '.' + type
- })
- return allowedTypes.join(',')
- },
- },
- watch: {
- filesList: {
- handler(newVal) {
- this.files = newVal.map((item) => {
- // 如果 item 是字符串(文件路径)
- if (typeof item === 'string') {
- return {
- fileName: item.substring(item.lastIndexOf('/') + 1),
- filePath: item,
- status: 'success',
- }
- }
- // 如果 item 已经是对象
- return {
- ...item,
- status: item.status || 'success',
- }
- })
- },
- immediate: true, // 立即执行一次,用于初始化回显
- },
- actionUrl: {
- handler(newVal) {
- this.uploadUrl = newVal || '/api/file/v1/upload'
- },
- immediate: true,
- },
- },
- methods: {
- // 在 methods 中添加格式化方法
- formatFileSize(bytes) {
- if (bytes === 0) return '0 Bytes'
- const k = 1024
- const sizes = ['Bytes', 'KB', 'MB', 'GB']
- const i = Math.floor(Math.log(bytes) / Math.log(k))
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
- },
- showConfirmDialog() {
- this.confirmDialogVisible = true
- },
- clearFiles() {
- this.files = []
- this.uploadList = []
- },
- handleChange(file, fileList) {
- // 校验文件大小
- if (this.maxSize && file.size > this.maxSize) {
- this.$message.error(
- `文件大小不能超过 ${this.formatFileSize(this.maxSize)}`
- )
- this.uploadList = this.uploadList.filter((f) => f !== file)
- return
- }
- // 校验文件类型
- const fileName = file.name.toLowerCase()
- const fileExtension = fileName.split('.').pop()
- if (!this.allowedTypes.includes(fileExtension)) {
- this.$message.error(
- `不支持的文件格式,请上传 ${this.allowedTypes.join(
- ', '
- )} 格式的文件`
- )
- this.uploadList = this.uploadList.filter((f) => f !== file)
- return
- }
- this.uploadList = fileList
- this.confirmUpload()
- // 根据上传模式决定是否立即上传
- // if (this.uploadMode === 'single') {
- // this.confirmUpload()
- // }
- },
- confirmUpload() {
- if (this.uploading) return
- this.uploading = true
- const formData = new FormData()
- formData.append('file', this.uploadList[0].raw)
- // if (this.uploadMode === 'single' && this.uploadList.length > 0) {
- // formData.append('file', this.uploadList[0].raw)
- // } else if (this.uploadMode === 'multiple') {
- // // 多文件上传模式
- // // 正确的多文件上传方式:为每个文件单独添加到formData中,使用相同的键名
- // this.uploadList.forEach((file) => {
- // formData.append('files', file.raw)
- // })
- // }
- // 发送上传请求
- this.uploadFiles(formData)
- },
- async uploadFiles(formData) {
- try {
- this.uploading = true
- // 注意:根据实际情况选择合适的API调用方式
- const res = await uploadFile(this.uploadUrl, formData, {
- onUploadProgress: (progressEvent) => {
- // 可添加上传进度显示
- if (progressEvent.total) {
- const percentComplete = Math.round(
- (progressEvent.loaded * 100) / progressEvent.total
- )
- console.log('上传进度:', percentComplete, '%')
- // 如需显示进度条可在此添加逻辑
- }
- },
- timeout: 60000, // 添加60秒超时设置
- })
- if (res && res.value) {
- if (this.uploadUrl == '/api/file/v1/tempUpload') {
- this.files.push({
- ...res.value,
- ...res.value.fileUploadResult,
- status: 'success',
- })
- } else {
- this.files.push({
- ...res.value,
- status: 'success',
- })
- }
- // if (this.uploadMode === 'single') {
- // this.files.push({
- // ...res.value,
- // status: 'success',
- // })
- // } else {
- // // 多文件上传模式
- // res.value.forEach((item) => {
- // this.files.push({
- // ...item,
- // status: 'success',
- // })
- // })
- // }
- this.uploadList = []
- this.$message.success('上传成功')
- this.saveFiles() // 自动保存上传结果
- } else {
- throw new Error('上传返回数据格式不正确')
- }
- } catch (error) {
- console.error('文件上传失败:', error)
- // 重置上传状态,确保用户可以再次尝试
- this.uploadList = []
- } finally {
- this.uploading = false
- }
- },
- removeFile(index) {
- if (this.isDisabled) return
- // 删除文件
- const removedFile = this.files.splice(index, 1)[0]
- // 通知父组件文件已被删除,并传递当前文件列表
- this.$emit('removeFile', index, removedFile, this.files, this.props)
- },
- handleSort(index, direction) {
- if (direction === 'up' && index > 0) {
- this.files.splice(index - 1, 0, this.files.splice(index, 1)[0])
- } else if (direction === 'down' && index < this.files.length - 1) {
- this.files.splice(index + 1, 0, this.files.splice(index, 1)[0])
- }
- },
- getStatusText(status) {
- const map = {
- pending: '待上传',
- success: '已上传',
- uploading: '上传中',
- failed: '上传失败',
- }
- return map[status] || status
- },
- // 添加文件图标类名方法
- fileIcon(extension) {
- const map = {
- xlsx: 'icon-exel',
- xls: 'icon-exel',
- doc: 'icon-word',
- docx: 'icon-word',
- pdf: 'icon-word',
- }
- return map[extension] || 'icon-word'
- },
- getStatusType(status) {
- const map = {
- pending: 'warning',
- success: 'success',
- uploading: 'info',
- failed: 'danger',
- }
- return map[status] || 'default'
- },
- // 添加获取文件扩展名的方法
- getFileExtension(fileName) {
- // 从文件名中提取扩展名并转为小写
- const lastDotIndex = fileName.lastIndexOf('.')
- if (lastDotIndex > -1 && lastDotIndex < fileName.length - 1) {
- return fileName.substring(lastDotIndex + 1).toLowerCase()
- }
- return 'unknown' // 如果没有扩展名,返回unknown
- },
- // 保存文件列表
- saveFiles() {
- this.$emit('saveFiles', this.files, this.props)
- this.confirmDialogVisible = false
- },
- handlePreview(file) {
- // 对文件URL进行Base64编码
- const encodedUrl = encodeURIComponent(
- Base64.encode(window.context.form + file.filePath)
- )
- // 构建 kkFileView 预览URL
- // onlinePreview - 在线预览
- // onlinePreview?type=pdf - 强制使用PDF模式预览
- window.open(`${host}:8012/onlinePreview?url=${encodedUrl}`)
- },
- },
- }
- </script>
- <style scoped lang="scss">
- @import '@/styles/costAudit.scss';
- // 上传按钮区域
- .upload-buttons {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- margin-bottom: 20px;
- }
- // 文件列表
- .file-list-simple {
- margin-bottom: 0;
- }
- // 文件项样式
- .file-item {
- display: flex;
- align-items: center;
- padding: 12px 15px;
- margin-bottom: 8px;
- background-color: #fff;
- border: 1px solid #ebeef5;
- border-radius: 6px;
- font-size: 14px;
- transition: all 0.3s ease;
- &:hover {
- border-color: $base-color-default;
- box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.06);
- transform: translateY(-1px);
- }
- &:last-child {
- margin-bottom: 0;
- }
- }
- // 文件图标
- .fileIcon {
- font-size: 24px;
- color: $base-color-default;
- margin-right: 12px;
- width: 40px;
- height: 40px;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: rgba($base-color-default, 0.1);
- border-radius: 4px;
- transition: all 0.3s ease;
- .file-item:hover & {
- background-color: rgba($base-color-default, 0.2);
- }
- }
- // 文件名
- .file-fileName {
- flex: 1;
- color: #303133;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- transition: all 0.3s ease;
- &:hover {
- color: $base-color-default;
- cursor: pointer;
- text-decoration: underline;
- }
- }
- // 状态标签
- .file-status {
- margin: 0 15px;
- }
- // 删除按钮
- .file-item .delete-btn {
- margin-left: 10px;
- color: #909399;
- cursor: pointer;
- font-size: 16px;
- padding: 4px;
- border-radius: 4px;
- transition: all 0.3s ease;
- &:not(.disabled-text):hover {
- color: #f56c6c;
- background-color: rgba(245, 108, 108, 0.1);
- }
- &.disabled-text {
- cursor: not-allowed;
- opacity: 0.6;
- }
- }
- // 表格形式文件列表
- .file-list-table {
- margin-top: 10px;
- background-color: #fff;
- border-radius: 6px;
- overflow: hidden;
- }
- // 表格底部按钮
- .table-footer {
- margin-top: 20px;
- text-align: right;
- }
- </style>
|