-
-
-
- {{ item }}
-
+
+
+
+
+
+
+
+
+
+
+ {{ truncateFileName(item.value, 40) }}
+
+
+
+
+
+
+
-
-
-
- 添加
+
+
+
@@ -482,7 +584,7 @@
import { ref, reactive, onMounted, watch, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
-import { Search, Plus, Delete, UploadFilled, Folder, Document, ArrowRight, Loading } from '@element-plus/icons-vue'
+import { Search, Plus, Delete, UploadFilled, Folder, Document, ArrowRight, Loading, Picture, Edit, Rank } from '@element-plus/icons-vue'
import {
getSettingGroupList,
getSettingGroupInfo,
@@ -497,6 +599,7 @@ import {
deleteSetting
} from '@/api/admin/setting'
import { uploadFile } from '@/api/admin/file'
+import ImageSelector from '@/components/admin/ImageSelector.vue'
const route = useRoute()
@@ -506,6 +609,14 @@ const treeData = ref([])
const allGroupList = ref([]) // 存储所有已加载的配置组
const treeDataMap = ref(new Map()) // 存储树形数据,key为group_id
const selectedNode = ref(null)
+const editableStringList = ref([]) // 弹窗中可编辑的字符串列表
+const draggedIndex = ref(-1) // 拖拽的索引
+
+// 表单相关可编辑列表
+const editableFormStringList = ref([]) // 表单中可编辑的字符串列表
+const editableFormFileList = ref([]) // 表单中可编辑的文件列表
+const formDraggedIndex = ref(-1) // 表单拖拽索引
+const formFileDraggedIndex = ref(-1) // 表单文件拖拽索引
// 查询参数
const queryParams = reactive({
@@ -556,7 +667,6 @@ const groupRules = {
const groupLoading = ref(false)
const groupList = ref([])
const groupTotal = ref(0)
-const selectedRows = ref([])
const groupDialogVisible = ref(false)
const groupDialogTitle = ref('新增配置组')
const groupFormRef = ref(null)
@@ -614,6 +724,12 @@ const fileListInfo = ref([])
const newStringItem = ref('')
const imageViewerVisible = ref(false)
const currentViewImage = ref('')
+const imageSelectorVisible = ref(false)
+const currentImageSelectorFileId = ref('')
+const imageSelectorMode = ref('single') // 'single' 或 'list'
+
+// 批量选择相关
+const selectedRows = ref([])
// 格式化日期时间
const formatDate = (dateString) => {
@@ -709,6 +825,11 @@ const loadGroups = async () => {
// 节点点击事件
const handleNodeClick = (row) => {
selectedNode.value = row
+
+ // 如果是字符串列表类型,初始化可编辑列表
+ if (row.data.type === 'string_list') {
+ initEditableStringList()
+ }
}
// 查询处理
@@ -964,6 +1085,17 @@ const handleEditSetting = async (row) => {
} else {
fileInfo.value = null
}
+ } else if (data.type === 'string_list') {
+ // 处理字符串列表类型,对每个字符串进行截断
+ if (data.parsedValue && Array.isArray(data.parsedValue)) {
+ const truncatedValues = data.parsedValue.map(item => truncateFileName(item, 25))
+ settingForm.value = truncatedValues.join(',')
+ initEditableFormStringList() // 初始化表单可编辑字符串列表
+ } else {
+ settingForm.value = ''
+ editableFormStringList.value = []
+ }
+ newStringItem.value = ''
} else if (data.type === 'file_list') {
// 处理文件列表类型,使用parsedValue来获取文件信息
if (data.parsedValue && Array.isArray(data.parsedValue)) {
@@ -977,11 +1109,14 @@ const handleEditSetting = async (row) => {
size: 0
}
})
+ // 确保在设置fileListInfo后再初始化表单可编辑文件列表
+ nextTick(() => {
+ initEditableFormFileList()
+ })
} else {
fileListInfo.value = []
+ editableFormFileList.value = []
}
- } else if (data.type === 'string_list') {
- newStringItem.value = ''
}
settingDialogVisible.value = true
}
@@ -1036,6 +1171,21 @@ const fetchAllGroupList = async () => {
// 文件相关函数
const handleFileChange = async (file) => {
fileUploading.value = true
+
+ // 创建本地预览URL
+ const localUrl = URL.createObjectURL(file.raw)
+ const isImage = file.raw.type.startsWith('image/')
+
+ // 立即显示本地预览
+ fileInfo.value = {
+ id: '', // 暂时为空,上传成功后会更新
+ url: isImage ? localUrl : '',
+ realName: file.name,
+ saveName: file.name,
+ size: file.size,
+ isLocal: true // 标记为本地文件
+ }
+
try {
const formData = new FormData()
formData.append('file_names', file.name)
@@ -1044,18 +1194,30 @@ const handleFileChange = async (file) => {
formData.append('open_down','true')
const res = await uploadFile(formData)
- if (res.data.code === 200 && res.data.data.length > 0) {
+ if (res.data.code === 200 && res.data.data && res.data.data.length > 0) {
const uploadedFile = res.data.data[0]
- settingForm.value = String(uploadedFile.id)
+ settingForm.value = String(uploadedFile.id || '')
- // 确保上传的文件URL也经过处理
+ // 释放本地URL(暂时不释放,保留用于渲染)
+ // if (fileInfo.value?.isLocal) {
+ // URL.revokeObjectURL(fileInfo.value.url)
+ // }
+
+ // 更新为服务器返回的文件信息,但保留本地URL用于渲染
fileInfo.value = {
- ...uploadedFile,
- url: processImageUrl(uploadedFile.url || uploadedFile.realName)
+ id: uploadedFile.id || '',
+ url: processImageUrl(uploadedFile.url || uploadedFile.realName || ''),
+ localUrl: fileInfo.value?.url || '', // 保留本地URL用于渲染
+ realName: uploadedFile.realName || '文件',
+ saveName: uploadedFile.saveName || 'file',
+ size: uploadedFile.size || 0,
+ isLocal: false // 标记为已上传,但保留本地渲染
}
ElMessage.success('文件上传成功')
} else {
+ // 上传失败,保留本地预览,但显示错误消息
ElMessage.error(res.data.message || '文件上传失败')
+ // 注意:不清理本地预览,让用户可以重新上传或删除
}
} catch (error) {
console.error('文件上传失败:', error)
@@ -1066,6 +1228,14 @@ const handleFileChange = async (file) => {
}
const clearFile = () => {
+ // 释放本地URL
+ if (fileInfo.value?.localUrl) {
+ URL.revokeObjectURL(fileInfo.value.localUrl)
+ }
+ if (fileInfo.value?.isLocal && fileInfo.value?.url) {
+ URL.revokeObjectURL(fileInfo.value.url)
+ }
+
settingForm.value = ''
fileInfo.value = null
}
@@ -1078,6 +1248,462 @@ const formatFileSize = (bytes) => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
+const truncateFileName = (fileName, maxLength = 25) => {
+ if (!fileName) return ''
+ if (fileName.length <= maxLength) return fileName
+
+ const extension = fileName.includes('.') ? '.' + fileName.split('.').pop() : ''
+ const nameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.'))
+
+ if (nameWithoutExt.length <= maxLength) {
+ return fileName
+ }
+
+ const truncatedName = nameWithoutExt.substring(0, maxLength - 3) // 留3个字符给"...和扩展名"
+ console.log('111',truncatedName + '...' + extension)
+ return truncatedName + '...' + extension
+}
+
+// ==================== 可编辑字符串列表功能 ====================
+
+// 获取可编辑的字符串列表
+const getEditableStringList = () => {
+ if (!selectedNode.value || selectedNode.value.data.type !== 'string_list') {
+ return []
+ }
+ return editableStringList.value
+}
+
+// 初始化可编辑字符串列表
+const initEditableStringList = () => {
+ if (!selectedNode.value || selectedNode.value.data.type !== 'string_list') {
+ editableStringList.value = []
+ return
+ }
+
+ const stringItems = getStringList(selectedNode.value.data.value)
+ editableStringList.value = stringItems.map(item => ({
+ value: item,
+ editing: false
+ }))
+}
+
+// 开始拖拽
+const handleDragStart = (event, index) => {
+ draggedIndex.value = index
+ event.dataTransfer.effectAllowed = 'move'
+ event.dataTransfer.setData('text/html', event.target.outerHTML)
+ event.target.style.opacity = '0.5'
+}
+
+// 拖拽放下
+const handleDrop = (event, dropIndex) => {
+ event.preventDefault()
+ const dragIndex = draggedIndex.value
+
+ if (dragIndex === dropIndex) return
+
+ // 重新排列数组
+ const newList = [...editableStringList.value]
+ const [draggedItem] = newList.splice(dragIndex, 1)
+ newList.splice(dropIndex, 0, draggedItem)
+
+ editableStringList.value = newList
+ draggedIndex.value = -1
+
+ // 更新selectedNode中的数据
+ const updatedValues = newList.map(item => item.value)
+ selectedNode.value.data.value = updatedValues.join(',')
+
+ event.target.style.opacity = '1'
+}
+
+// 添加新字符串项目
+const addEditableStringItem = () => {
+ const newItem = {
+ value: '',
+ editing: true
+ }
+ editableStringList.value.push(newItem)
+
+ // 更新selectedNode中的数据
+ const updatedValues = editableStringList.value.map(item => item.value)
+ selectedNode.value.data.value = updatedValues.join(',')
+
+ // 聚焦到新添加的输入框
+ nextTick(() => {
+ const inputs = document.querySelectorAll('.string-list-item input')
+ if (inputs.length > 0) {
+ inputs[inputs.length - 1].focus()
+ }
+ })
+}
+
+// 开始编辑项目
+const startEditItem = (index) => {
+ editableStringList.value[index].editing = true
+
+ nextTick(() => {
+ const inputs = document.querySelectorAll('.string-list-item input')
+ if (inputs[index]) {
+ inputs[index].focus()
+ inputs[index].select()
+ }
+ })
+}
+
+// 完成编辑项目
+const finishEditItem = (index) => {
+ editableStringList.value[index].editing = false
+
+ // 更新selectedNode中的数据
+ const updatedValues = editableStringList.value.map(item => item.value)
+ selectedNode.value.data.value = updatedValues.join(',')
+}
+
+// 删除字符串项目
+const removeEditableStringItem = (index) => {
+ ElMessageBox.confirm('确定要删除这个项目吗?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(() => {
+ editableStringList.value.splice(index, 1)
+
+ // 更新selectedNode中的数据
+ const updatedValues = editableStringList.value.map(item => item.value)
+ selectedNode.value.data.value = updatedValues.join(',')
+
+ ElMessage.success('删除成功')
+ })
+}
+
+// 聚焦输入框
+const focusEditInput = () => {
+ // 这个方法会被 @mounted 调用
+}
+
+// ==================== 表单可编辑字符串列表功能 ====================
+
+// 获取表单可编辑的字符串列表
+const getEditableFormStringList = () => {
+ return editableFormStringList.value
+}
+
+// 初始化表单可编辑字符串列表
+const initEditableFormStringList = () => {
+ const stringItems = getStringList(settingForm.value)
+ editableFormStringList.value = stringItems.map(item => ({
+ value: item,
+ editing: false
+ }))
+}
+
+// 开始表单拖拽
+const handleFormDragStart = (event, index) => {
+ formDraggedIndex.value = index
+ event.dataTransfer.effectAllowed = 'move'
+ event.dataTransfer.setData('text/html', event.target.outerHTML)
+ event.target.style.opacity = '0.5'
+}
+
+// 表单拖拽放下
+const handleFormDrop = (event, dropIndex) => {
+ event.preventDefault()
+ const dragIndex = formDraggedIndex.value
+
+ if (dragIndex === dropIndex) return
+
+ // 重新排列数组
+ const newList = [...editableFormStringList.value]
+ const [draggedItem] = newList.splice(dragIndex, 1)
+ newList.splice(dropIndex, 0, draggedItem)
+
+ editableFormStringList.value = newList
+ formDraggedIndex.value = -1
+
+ // 更新表单值
+ const updatedValues = newList.map(item => item.value)
+ settingForm.value = updatedValues.join(',')
+
+ event.target.style.opacity = '1'
+}
+
+// 添加表单新字符串项目
+const addFormEditableStringItem = () => {
+ const newItem = {
+ value: '',
+ editing: true
+ }
+ editableFormStringList.value.push(newItem)
+
+ // 更新表单值
+ const updatedValues = editableFormStringList.value.map(item => item.value)
+ settingForm.value = updatedValues.join(',')
+
+ // 聚焦到新添加的输入框
+ nextTick(() => {
+ const inputs = document.querySelectorAll('.string-list-item input')
+ if (inputs.length > 0) {
+ inputs[inputs.length - 1].focus()
+ }
+ })
+}
+
+// 开始编辑表单项目
+const startFormEditItem = (index) => {
+ editableFormStringList.value[index].editing = true
+
+ nextTick(() => {
+ const inputs = document.querySelectorAll('.string-list-item input')
+ if (inputs[index]) {
+ inputs[index].focus()
+ inputs[index].select()
+ }
+ })
+}
+
+// 完成编辑表单项目
+const finishFormEditItem = (index) => {
+ editableFormStringList.value[index].editing = false
+
+ // 更新表单值
+ const updatedValues = editableFormStringList.value.map(item => item.value)
+ settingForm.value = updatedValues.join(',')
+}
+
+// 删除表单字符串项目
+const removeFormEditableStringItem = (index) => {
+ ElMessageBox.confirm('确定要删除这个项目吗?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(() => {
+ editableFormStringList.value.splice(index, 1)
+
+ // 更新表单值
+ const updatedValues = editableFormStringList.value.map(item => item.value)
+ settingForm.value = updatedValues.join(',')
+
+ ElMessage.success('删除成功')
+ })
+}
+
+// 聚焦表单输入框
+const focusFormEditInput = () => {
+ // 这个方法会被 @mounted 调用
+}
+
+// ==================== 表单可编辑文件列表功能 ====================
+
+// 获取表单可编辑的文件列表
+const getEditableFormFileList = () => {
+ return editableFormFileList.value
+}
+
+// 初始化表单可编辑文件列表
+const initEditableFormFileList = () => {
+ if (settingForm.type === 'file_list' && fileListInfo.value && fileListInfo.value.length > 0) {
+ editableFormFileList.value = fileListInfo.value.map(file => ({
+ id: file.id || '',
+ url: file.url || '',
+ localUrl: file.localUrl || '', // 保留本地URL字段
+ realName: file.realName || '文件',
+ saveName: file.saveName || 'file',
+ size: file.size || 0,
+ uploading: false
+ }))
+ } else {
+ editableFormFileList.value = []
+ }
+}
+
+// 开始表单文件拖拽
+const handleFormFileDragStart = (event, index) => {
+ formFileDraggedIndex.value = index
+ event.dataTransfer.effectAllowed = 'move'
+ event.dataTransfer.setData('text/html', event.target.outerHTML)
+ event.target.style.opacity = '0.5'
+}
+
+// 表单文件拖拽放下
+const handleFormFileDrop = (event, dropIndex) => {
+ event.preventDefault()
+ const dragIndex = formFileDraggedIndex.value
+
+ if (dragIndex === dropIndex) return
+
+ // 重新排列数组
+ const newList = [...editableFormFileList.value]
+ const [draggedItem] = newList.splice(dragIndex, 1)
+ newList.splice(dropIndex, 0, draggedItem)
+
+ editableFormFileList.value = newList
+ formFileDraggedIndex.value = -1
+
+ // 更新fileListInfo和表单值
+ fileListInfo.value = newList
+ updateFormFileListValue()
+
+ event.target.style.opacity = '1'
+}
+
+// 表单文件列表变更
+const handleFormFileListChange = async (file) => {
+ // 创建本地预览URL
+ const localUrl = URL.createObjectURL(file.raw)
+ const isImage = file.raw.type.startsWith('image/')
+
+ // 立即添加到表单文件列表
+ const tempFile = {
+ id: '', // 暂时为空,上传成功后会更新
+ url: isImage ? localUrl : '',
+ realName: file.name,
+ saveName: file.name,
+ size: file.size,
+ isLocal: true,
+ uploading: true
+ }
+
+ editableFormFileList.value.push(tempFile)
+
+ try {
+ const formData = new FormData()
+ formData.append('file_names', file.name)
+ formData.append('files', file.raw)
+ formData.append('update_type','cover')
+ formData.append('open_down','true')
+
+ const res = await uploadFile(formData)
+ if (res.data.code === 200 && res.data.data && res.data.data.length > 0) {
+ const uploadedFile = res.data.data[0]
+
+ // 找到对应的本地文件并更新
+ const index = editableFormFileList.value.findIndex(f => f.isLocal && f.realName === file.name)
+ if (index !== -1) {
+ editableFormFileList.value[index] = {
+ id: uploadedFile.id || '',
+ url: processImageUrl(uploadedFile.url || uploadedFile.realName || ''),
+ localUrl: localUrl, // 保留本地URL用于渲染
+ realName: uploadedFile.realName || '文件',
+ saveName: uploadedFile.saveName || 'file',
+ size: uploadedFile.size || 0,
+ isLocal: false,
+ uploading: false
+ }
+ }
+
+ updateFormFileListValue()
+ ElMessage.success('文件上传成功')
+ } else {
+ // 上传失败,移除临时文件
+ const index = editableFormFileList.value.findIndex(f => f.isLocal && f.realName === file.name)
+ if (index !== -1) {
+ editableFormFileList.value.splice(index, 1)
+ }
+ ElMessage.error(res.data.message || '文件上传失败')
+ }
+ } catch (error) {
+ console.error('文件上传失败:', error)
+ // 上传失败,移除临时文件
+ const index = editableFormFileList.value.findIndex(f => f.isLocal && f.realName === file.name)
+ if (index !== -1) {
+ editableFormFileList.value.splice(index, 1)
+ }
+ ElMessage.error('文件上传失败')
+ }
+}
+
+// 表单文件替换
+const handleFormFileReplace = async (file, index) => {
+ if (!editableFormFileList.value[index]) return
+
+ // 创建本地预览URL
+ const localUrl = URL.createObjectURL(file.raw)
+ const isImage = file.raw.type.startsWith('image/')
+
+ // 更新文件信息
+ editableFormFileList.value[index] = {
+ ...editableFormFileList.value[index],
+ url: isImage ? localUrl : '',
+ realName: file.name,
+ saveName: file.name,
+ size: file.size,
+ isLocal: true,
+ uploading: true
+ }
+
+ try {
+ const formData = new FormData()
+ formData.append('file_names', file.name)
+ formData.append('files', file.raw)
+ formData.append('update_type','cover')
+ formData.append('open_down','true')
+
+ const res = await uploadFile(formData)
+ if (res.data.code === 200 && res.data.data && res.data.data.length > 0) {
+ const uploadedFile = res.data.data[0]
+
+ // 更新文件信息
+ editableFormFileList.value[index] = {
+ id: uploadedFile.id || '',
+ url: processImageUrl(uploadedFile.url || uploadedFile.realName || ''),
+ localUrl: localUrl, // 保留本地URL用于渲染
+ realName: uploadedFile.realName || '文件',
+ saveName: uploadedFile.saveName || 'file',
+ size: uploadedFile.size || 0,
+ isLocal: false,
+ uploading: false
+ }
+
+ updateFormFileListValue()
+ ElMessage.success('文件替换成功')
+ } else {
+ // 上传失败,恢复原文件信息
+ ElMessage.error(res.data.message || '文件替换失败')
+ }
+ } catch (error) {
+ console.error('文件替换失败:', error)
+ ElMessage.error('文件替换失败')
+ }
+}
+
+// 删除表单文件项目
+const removeFormEditableFileItem = (index) => {
+ ElMessageBox.confirm('确定要删除这个文件吗?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(() => {
+ // 释放本地URL
+ if (editableFormFileList.value[index].localUrl) {
+ URL.revokeObjectURL(editableFormFileList.value[index].localUrl)
+ }
+ if (editableFormFileList.value[index].isLocal && editableFormFileList.value[index].url) {
+ URL.revokeObjectURL(editableFormFileList.value[index].url)
+ }
+
+ editableFormFileList.value.splice(index, 1)
+
+ // 更新fileListInfo和表单值
+ fileListInfo.value = editableFormFileList.value
+ updateFormFileListValue()
+
+ ElMessage.success('删除成功')
+ })
+}
+
+// 更新表单文件列表值
+const updateFormFileListValue = () => {
+ if (!editableFormFileList.value || !Array.isArray(editableFormFileList.value)) {
+ return
+ }
+
+ const fileIds = editableFormFileList.value.map(file => file.id).filter(id => id)
+ settingForm.value = fileIds.join(',')
+}
+
+// 文件预览
const previewFile = (fileId) => {
// 这里可以实现文件预览逻辑
console.log('预览文件:', fileId)
@@ -1117,22 +1743,96 @@ const previewImage = (url) => {
// 处理图片加载失败
const handleImageError = (event) => {
// 图片加载失败时,可以隐藏图片或显示占位符
- console.log('图片加载失败:', event.target.src)
+ console.error('图片加载失败:', event.target.src)
// 这里可以添加更多的错误处理逻辑
}
+// ==================== 图像选择器相关 ====================
+
+// 打开图像选择器(单个文件)
+const openImageSelector = () => {
+ imageSelectorMode.value = 'single'
+ currentImageSelectorFileId.value = settingForm.value || ''
+ imageSelectorVisible.value = true
+}
+
+// 打开图像选择器(文件列表)
+const openImageSelectorForList = () => {
+ imageSelectorMode.value = 'list'
+ currentImageSelectorFileId.value = ''
+ imageSelectorVisible.value = true
+}
+
+// 处理图像选择器确认
+const handleImageSelectorConfirm = (selectedFile) => {
+ if (!selectedFile || !selectedFile.id) {
+ ElMessage.warning('选择的文件无效')
+ return
+ }
+
+ if (imageSelectorMode.value === 'single') {
+ // 单个文件模式
+ settingForm.value = selectedFile.id
+ fileInfo.value = {
+ id: selectedFile.id,
+ url: processImageUrl(selectedFile.url || ''),
+ realName: selectedFile.realName || '文件',
+ saveName: selectedFile.realName || 'file',
+ size: selectedFile.size || 0
+ }
+ } else if (imageSelectorMode.value === 'list') {
+ // 文件列表模式
+ if (!fileListInfo.value) {
+ fileListInfo.value = []
+ }
+
+ const newFile = {
+ id: selectedFile.id,
+ url: processImageUrl(selectedFile.url || ''),
+ realName: selectedFile.realName || '文件',
+ saveName: selectedFile.realName || 'file',
+ size: selectedFile.size || 0
+ }
+ fileListInfo.value.push(newFile)
+ updateFileListValue()
+ }
+
+ imageSelectorVisible.value = false
+}
+
const getFileList = (value) => {
- if (!value) return []
- return value.split(',').filter(id => id.trim())
+ if (!value || typeof value !== 'string') return []
+ return value.split(',').filter(id => id && id.trim())
}
const getStringList = (value) => {
- if (!value) return []
- return value.split(',').filter(str => str.trim())
+ if (!value || typeof value !== 'string') return []
+ return value.split(',').filter(str => str && str.trim())
}
const handleFileListChange = async (file) => {
fileUploading.value = true
+
+ // 创建本地预览URL
+ const localUrl = URL.createObjectURL(file.raw)
+ const isImage = file.raw.type.startsWith('image/')
+
+ // 立即添加本地预览到列表
+ if (!fileListInfo.value) {
+ fileListInfo.value = []
+ }
+
+ const tempFile = {
+ id: '', // 暂时为空,上传成功后会更新
+ url: isImage ? localUrl : '',
+ realName: file.name,
+ saveName: file.name,
+ size: file.size,
+ isLocal: true // 标记为本地文件
+ }
+
+ fileListInfo.value.push(tempFile)
+
try {
const formData = new FormData()
formData.append('file_names', file.name)
@@ -1141,24 +1841,41 @@ const handleFileListChange = async (file) => {
formData.append('open_down','true')
const res = await uploadFile(formData)
- if (res.data.code === 200 && res.data.data.length > 0) {
+ if (res.data.code === 200 && res.data.data && res.data.data.length > 0) {
const uploadedFile = res.data.data[0]
const currentFileIds = getFileList(settingForm.value)
- currentFileIds.push(String(uploadedFile.id))
+ currentFileIds.push(String(uploadedFile.id || ''))
settingForm.value = currentFileIds.join(',')
- // 确保新上传的文件URL也经过处理
- const processedFile = {
- ...uploadedFile,
- url: processImageUrl(uploadedFile.url || uploadedFile.realName)
+ // 找到对应的本地文件并更新
+ const index = fileListInfo.value.findIndex(f => f.isLocal && f.realName === file.name)
+ if (index !== -1) {
+ // 释放本地URL(暂时不释放,保留用于渲染)
+ // if (fileListInfo.value[index].isLocal) {
+ // URL.revokeObjectURL(fileListInfo.value[index].url)
+ // }
+
+ // 更新为服务器返回的文件信息,但保留本地URL用于渲染
+ fileListInfo.value[index] = {
+ id: uploadedFile.id || '',
+ url: processImageUrl(uploadedFile.url || uploadedFile.realName || ''),
+ localUrl: fileListInfo.value[index]?.url || '', // 保留本地URL用于渲染
+ realName: uploadedFile.realName || '文件',
+ saveName: uploadedFile.saveName || 'file',
+ size: uploadedFile.size || 0,
+ isLocal: false // 标记为已上传,但保留本地渲染
+ }
}
- fileListInfo.value.push(processedFile)
+
+ updateFileListValue()
ElMessage.success('文件上传成功')
} else {
+ // 上传失败,不清理本地预览,让用户可以重新上传或删除
ElMessage.error(res.data.message || '文件上传失败')
}
} catch (error) {
console.error('文件上传失败:', error)
+ // 上传失败,不清理本地预览,让用户可以重新上传或删除
ElMessage.error('文件上传失败')
} finally {
fileUploading.value = false
@@ -1166,12 +1883,34 @@ const handleFileListChange = async (file) => {
}
const removeFile = (index) => {
- const currentFileIds = getFileList(settingForm.value)
+ if (!fileListInfo.value || index < 0 || index >= fileListInfo.value.length) {
+ return
+ }
+
+ // 释放本地URL
+ if (fileListInfo.value[index].localUrl) {
+ URL.revokeObjectURL(fileListInfo.value[index].localUrl)
+ }
+ if (fileListInfo.value[index].isLocal && fileListInfo.value[index].url) {
+ URL.revokeObjectURL(fileListInfo.value[index].url)
+ }
+
+ const currentFileIds = getFileList(settingForm.value) || []
currentFileIds.splice(index, 1)
settingForm.value = currentFileIds.join(',')
fileListInfo.value.splice(index, 1)
}
+// 更新文件列表值
+const updateFileListValue = () => {
+ if (!fileListInfo.value || !Array.isArray(fileListInfo.value)) {
+ return
+ }
+
+ const fileIds = fileListInfo.value.map(file => file.id).filter(id => id)
+ settingForm.value = fileIds.join(',')
+}
+
const addStringItem = () => {
if (newStringItem.value.trim()) {
const currentItems = getStringList(settingForm.value)
@@ -1324,6 +2063,8 @@ onMounted(() => {
/* 文件预览样式 */
.file-preview {
margin-top: 8px;
+ position: relative;
+ flex-shrink: 0;
}
.preview-image {
@@ -1356,10 +2097,6 @@ onMounted(() => {
gap: 12px;
}
-.file-preview {
- flex-shrink: 0;
-}
-
.file-placeholder {
width: 80px;
height: 80px;
@@ -1662,6 +2399,45 @@ onMounted(() => {
width: 100%;
}
+.file-upload-options {
+ width: 100%;
+}
+
+.upload-methods {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ align-items: center;
+}
+
+.divider {
+ position: relative;
+ text-align: center;
+ color: #909399;
+ font-size: 14px;
+ margin: 8px 0;
+}
+
+.divider::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: #dcdfe6;
+}
+
+.divider {
+ background: #fff;
+ padding: 0 16px;
+}
+
+.image-selector-btn {
+ width: 200px;
+ height: 40px;
+}
+
:deep(.file-uploader .el-upload) {
width: 100%;
}
@@ -1764,6 +2540,37 @@ onMounted(() => {
color: #409eff;
}
+/* 上传状态指示器样式 */
+.file-item.uploading {
+ border-color: #409eff;
+ background-color: #f0f9ff;
+}
+
+.upload-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.6);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-size: 12px;
+ border-radius: 4px;
+}
+
+.upload-overlay .el-icon {
+ font-size: 20px;
+ margin-bottom: 4px;
+}
+
+.upload-overlay span {
+ font-size: 11px;
+}
+
/* 字符串列表相关样式 */
.string-list-section {
width: 100%;
@@ -1823,6 +2630,212 @@ onMounted(() => {
display: inline-block;
}
+/* 可编辑字符串列表样式 */
+.editable-string-list {
+ border: 1px solid #e4e7ed;
+ border-radius: 4px;
+ padding: 16px;
+ background-color: #fafafa;
+}
+
+.string-list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #ebeef5;
+}
+
+.list-title {
+ font-weight: 500;
+ color: #303133;
+ font-size: 14px;
+}
+
+.string-list-items {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.string-list-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 8px 12px;
+ background-color: #ffffff;
+ border: 1px solid #e4e7ed;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+ cursor: move;
+}
+
+.string-list-item:hover {
+ border-color: #c0c4cc;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.drag-handle {
+ color: #909399;
+ cursor: grab;
+ flex-shrink: 0;
+}
+
+.drag-handle:hover {
+ color: #606266;
+}
+
+.item-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.item-text {
+ padding: 4px 8px;
+ border-radius: 4px;
+ transition: background-color 0.3s ease;
+ cursor: pointer;
+ word-break: break-word;
+ white-space: normal;
+ line-height: 1.4;
+}
+
+.item-text:hover {
+ background-color: #f5f7fa;
+}
+
+.item-actions {
+ display: flex;
+ gap: 4px;
+ flex-shrink: 0;
+}
+
+.danger-btn {
+ color: #f56c6c;
+}
+
+.danger-btn:hover {
+ color: #f78989;
+}
+
+/* 可编辑文件列表样式 */
+.editable-file-list {
+ border: 1px solid #e4e7ed;
+ border-radius: 4px;
+ padding: 16px;
+ background-color: #fafafa;
+}
+
+.file-list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #ebeef5;
+}
+
+.header-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.file-list-items {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.file-list-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px;
+ background-color: #ffffff;
+ border: 1px solid #e4e7ed;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+ cursor: move;
+}
+
+.file-list-item:hover {
+ border-color: #c0c4cc;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.file-list-item .file-preview {
+ width: 60px;
+ height: 60px;
+ border-radius: 4px;
+ overflow: hidden;
+ border: 1px solid #e4e7ed;
+ flex-shrink: 0;
+ position: relative;
+}
+
+.file-list-item .preview-image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ cursor: pointer;
+}
+
+.file-list-item .file-placeholder {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #f5f7fa;
+ color: #909399;
+}
+
+.file-list-item .file-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.file-list-item .file-name {
+ font-weight: 500;
+ color: #303133;
+ margin-bottom: 4px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.file-list-item .file-id {
+ font-size: 12px;
+ color: #909399;
+ margin-bottom: 2px;
+}
+
+.file-list-item .file-size {
+ font-size: 12px;
+ color: #909399;
+}
+
+.file-list-item .file-actions {
+ display: flex;
+ gap: 4px;
+ flex-shrink: 0;
+}
+
+/* 弹窗中值内容的省略号样式 */
+.value-content {
+ max-width: 100%;
+}
+
+.value-content .text-value {
+ display: block;
+ max-width: 100%;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ word-break: break-all;
+}
+
/* 响应式调整 */
@media (max-width: 768px) {
.file-name {