diff --git a/templates/vue/api.ts.j2 b/templates/vue/api.ts.j2 index 0ebc805..54fb1a6 100644 --- a/templates/vue/api.ts.j2 +++ b/templates/vue/api.ts.j2 @@ -58,4 +58,12 @@ export namespace {{entity}}Api { { headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}}); } + /** + * 上传图片 + */ + export function upload(params: any) { + return requestClient.post(applicationConfig.javaURL+'/file/up', params, + { headers: {'Content-Type': 'multipart/form-data', Token: useAccessStore().accessToken, version: '1.0.1'}}); + } + } \ No newline at end of file diff --git a/templates/vue/index.vue.j2 b/templates/vue/index.vue.j2 index af5f625..79132fb 100644 --- a/templates/vue/index.vue.j2 +++ b/templates/vue/index.vue.j2 @@ -6,17 +6,22 @@ import {ref, reactive, onMounted, watch, h} from 'vue'; import {useVbenVxeGrid} from '#/adapter/vxe-table'; import {useVbenModal} from '@vben/common-ui'; import {useVbenForm} from '#/adapter/form'; +import {Upload, message, Image} from 'ant-design-vue'; import dayjs from 'dayjs'; import {columns} from './data'; import {formSchema} from './form'; import { {{entity}}Api } from '#/api/{{entity}}'; -import { Image } from 'ant-design-vue'; - +// ========== 获取 API 基础 URL ========== +const API_BASE_URL = import.meta.env.VITE_GLOB_API_URL || ''; // ========== 状态变量 ========== const currentRow = ref(null); const isEdit = ref(false); const modalTitle = ref('新增'); +const uploadVisible = ref(false); +const uploadFieldName = ref(''); +const uploadImageUrl = ref(''); +const uploadedUrl = ref(''); // 上传成功后返回的 // ========== 动态生成的查询表单 Schema ========== const querySchema = ref([]); // ========== 枚举数据配置 ========== @@ -25,8 +30,14 @@ const enumData = reactive({ list: [], // 存储接口返回的原始枚举数据 }); //todo 枚举数据在查询里面不展示的配置 -const hiddenColumns = ref(['fun_code','graduallyIntervalTime','funMsgTitle','funImg','createdAt', 'updatedBy', 'updatedAt', 'userPassword', 'userId', 'userSys', 'deletedFlag', "userFace"]) -const editFields = [{{editFields}}]; +const hiddenColumns = ref(['fun_code', 'graduallyIntervalTime', 'funMsgTitle', 'funImg', 'createdAt', 'updatedBy', 'updatedAt', 'userPassword', 'userId', 'userSys', 'deletedFlag', "userFace"]) +const editFields = ['userId', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy', 'deletedFlag']; + +// ========== 判断是否是图片字段 ========== +function isImageField(fieldName: string): boolean { + const lowerName = fieldName.toLowerCase(); + return lowerName.includes('img') || lowerName.includes('face') || lowerName.includes('picture'); +} // ========== 初始化枚举数据和查询表单 ========== async function initEnumData() { @@ -121,45 +132,80 @@ watch( {immediate: false} ); +// ========== 表格配置 ========== // ========== 表格配置 ========== const gridOptions = { columns: [ - ...columns.map(col => { - //带img,face,picture 关键词的字段,使用图片渲染 - if (col.field.toLowerCase()?.indexOf("img")>-1 - || col.field.toLowerCase()?.indexOf("face")>-1 - || col.field.toLowerCase()?.indexOf("picture")>-1 ) { - return { - ...col, - width: 96, - align: 'center', - slots: { - default: ({ row }: any) => { - const imgUrl = row[col.field]; - // 如果没有图片,显示提示文字 - if (!imgUrl) { - return h('div', { + ...columns.map(col => { + // ✅ 如果是图片字段 (包含 img, face, picture),使用图片渲染 + 上传按钮 + if (isImageField(col.field)) { + return { + ...col, + width: 200, + align: 'center', + slots: { + default: ({ row }: any) => { + const imgUrl = row[col.field]; + + // 如果没有图片,显示提示文字 + if (!imgUrl) { + return h('div', { + style: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '8px', + height: '50px', + } + }, [ + h('span', { style: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '50px', color: '#999', fontSize: '12px' } - }, '无图片'); - } - // 判断是否是完整的 URL - let fullUrl = imgUrl; - if (!imgUrl.startsWith('http://') && !imgUrl.startsWith('https://')) { - // 如果不是完整 URL,拼接基础 API 地址 - const apiURL = import.meta.env.VITE_GLOB_API_URL || ''; - fullUrl = `${apiURL}${imgUrl}`; - } - console.log('🖼️ 图片 URL:', fullUrl); - // 返回 Image 组件的 VNode - try { - return h(Image, { + }, '无图片'), + h('button', { + onClick: (e: Event) => { + e.stopPropagation(); + // 设置当前行数据 + currentRow.value = row; + isEdit.value = true; // 设置为编辑模式 + openUploadDialog(col.field, imgUrl); + }, + style: { + padding: '4px 8px', + fontSize: '12px', + color: '#1890ff', + backgroundColor: '#e6f7ff', + border: '1px solid #1890ff', + borderRadius: '4px', + cursor: 'pointer', + } + }, '上传') + ]); + } + + // 判断是否是完整的 URL + let fullUrl = imgUrl; + if (!imgUrl.startsWith('http://') && !imgUrl.startsWith('https://')) { + // 如果不是完整 URL,拼接基础 API 地址 + const apiURL = import.meta.env.VITE_GLOB_API_URL || ''; + fullUrl = `${apiURL}${imgUrl}`; + } + + console.log('🖼️ 图片 URL:', fullUrl); + + // 返回图片组件和上传按钮 + try { + return h('div', { + style: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '8px', + } + }, [ + h(Image, { src: fullUrl, height: 60, width: 80, @@ -185,28 +231,47 @@ const gridOptions = { } }, '加载失败'); } - }); - } catch (error) { - console.error('❌ 图片渲染错误:', error); - return h('div', { + }), + h('button', { + onClick: (e: Event) => { + e.stopPropagation(); + // 设置当前行数据 + currentRow.value = row; + isEdit.value = true; // 设置为编辑模式 + openUploadDialog(col.field, imgUrl); + }, style: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '50px', - backgroundColor: '#fffbe6', + padding: '4px 8px', + fontSize: '12px', + color: '#1890ff', + backgroundColor: '#e6f7ff', + border: '1px solid #1890ff', borderRadius: '4px', - color: '#faad14', - fontSize: '12px' + cursor: 'pointer', } - }, '渲染失败'); - } + }, '更换') + ]); + } catch (error) { + console.error('❌ 图片渲染错误:', error); + return h('div', { + style: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '50px', + backgroundColor: '#fffbe6', + borderRadius: '4px', + color: '#faad14', + fontSize: '12px' + } + }, '渲染失败'); } } - }; - } - return col; - }), + } + }; + } + return col; + }), { field: 'action', title: '操作', @@ -305,6 +370,83 @@ const [Modal, modalApi] = useVbenModal({ }, }) + +// ========== 上传弹窗配置 ========== +const uploadFileList = ref([]); +const [UploadModal, uploadModalApi] = useVbenModal({ + centered: true, + closable: true, + maskClosable: false, + draggable: true, + width: 600, + title: '上传图片', + onCancel() { + uploadModalApi.close(); + uploadFileList.value = []; + uploadedUrl.value = ''; + }, + onConfirm: async () => { + console.log('=== 点击确认保存 ===', { + uploadedUrl: uploadedUrl.value, + isEdit: isEdit.value, + currentRowId: currentRow.value?.id, + uploadFieldName: uploadFieldName.value, + currentRowData: currentRow.value, + }); + + // 如果有上传成功的 URL,更新到当前行并保存 + if (uploadedUrl.value) { + try { + // 更新当前行的图片 URL + if (currentRow.value) { + currentRow.value[uploadFieldName.value] = uploadedUrl.value; + console.log('✅ 已更新当前行图片字段:', { + fieldName: uploadFieldName.value, + newUrl: uploadedUrl.value, + updatedRow: currentRow.value, + }); + } + + // 调用 save 接口保存修改(只在编辑模式下) + if (isEdit.value && currentRow.value?.id) { + const submitData = { + ...currentRow.value, + id: currentRow.value.id + }; + console.log('📤 提交保存数据:', submitData); + + await {{entity}}Api.save(submitData); + message.success('保存成功!'); + + // 关闭弹窗并刷新表格 + uploadModalApi.close(); + gridApi.reload(); + + // 重置状态 + uploadFileList.value = []; + uploadedUrl.value = ''; + } else { + console.warn('⚠️ 不满足保存条件:', { + isEdit: isEdit.value, + hasId: !!currentRow.value?.id, + }); + message.warning('非编辑模式或无 ID,仅更新预览'); + + // 只更新预览,不保存 + uploadModalApi.close(); + gridApi.reload(); + } + + } catch (error: any) { + console.error('❌ 保存失败:', error); + message.error(error?.message || '保存失败'); + } + } else { + message.warning('请先选择并上传图片'); + } + }, +}) + // ========== 动态计算编辑表单 Schema ========== // 新增时:过滤掉 id 字段 // 编辑时:包含 id 字段 @@ -335,12 +477,87 @@ const [Form, formApi] = useVbenForm({ }, }) +// ========== 打开上传对话框 ========== +function openUploadDialog(fieldName: string, currentUrl: string) { + console.log('=== 打开上传对话框 ===', { + fieldName, + currentUrl, + isEdit: isEdit.value, + currentRowId: currentRow.value?.id, + currentRowFull: currentRow.value, + }); + + uploadFieldName.value = fieldName; + uploadImageUrl.value = currentUrl || ''; + uploadedUrl.value = ''; // 重置上传后的 URL + uploadFileList.value = []; // 清空文件列表 + uploadVisible.value = true; + uploadModalApi.open(); +} + +// ========== 处理图片上传 - 使用 custom-request ========== +async function handleCustomRequest(options: any) { + const { file, onSuccess, onError } = options; + + try { + console.log('📦 开始上传文件:', file); + console.log('📦 文件详情:', { + name: file.name, + type: file.type, + size: file.size, + lastModified: file.lastModified, + }); + + const formData = new FormData(); + formData.append('file', file); + + // 调用上传 API + const res = await {{entity}}Api.upload(formData); + const resultUrl = res.result || res.message || res.url || res; + + console.log('✅ 上传成功,URL:', resultUrl); + message.success('上传成功!'); + + // 保存上传返回的 URL + uploadedUrl.value = resultUrl; + console.log('=== uploadedUrl 赋值后 ===', uploadedUrl.value); + + // 添加到文件列表用于显示 + uploadFileList.value = [{ + uid: file.uid, + name: file.name, + status: 'done', + url: resultUrl, + }]; + + // 通知组件上传成功 + onSuccess({ url: resultUrl }); + + } catch (error: any) { + console.error('❌ 上传失败:', error); + message.error(error?.message || '上传失败'); + onError(error); + } +} + +// ========== 上传成功或失败后的处理 +function handleChange(info: any) { + const { status } = info.file; + console.log('📋 文件状态变化:', status, info.file); + + if (status === 'done') { + console.log('✅ 上传完成响应:', info); + } else if (status === 'error') { + console.error('❌ 上传失败:', info); + message.error(`${info.file.name} 上传失败`); + } +} // ========== 打开新增弹窗 ========== function handleAdd() { isEdit.value = false; - modalTitle.value = '{{table_comment}}新增'; + modalTitle.value = '功能配置表新增'; // 更新表单 schema (不包含 id) formApi.setState({ schema: getEditFormSchema(), @@ -353,19 +570,19 @@ function handleAdd() { function handleEdit(row: any) { isEdit.value = true; currentRow.value = row; - modalTitle.value = '{{table_comment}}编辑'; + modalTitle.value = '功能配置表编辑'; // 更新表单 schema (包含 id) formApi.setState({ schema: getEditFormSchema(), }); // 设置表单值,只设置 editFields 中包含的字段 - const formValues: any = {}; - editFormSchema.forEach(item => { - const field = item.fieldName; - if (row[field] !== undefined && row[field] !== null) { - formValues[field] = row[field]; - } - }); + const formValues: any = {}; + editFormSchema.forEach(item => { + const field = item.fieldName; + if (row[field] !== undefined && row[field] !== null) { + formValues[field] = row[field]; + } + }); formApi.setValues(formValues); modalApi.open(); @@ -397,14 +614,15 @@ function handleReset() { \ No newline at end of file +