Browse Source

上传图片

master
zhanglei 15 hours ago
parent
commit
b262158dc4
  1. 8
      templates/vue/api.ts.j2
  2. 392
      templates/vue/index.vue.j2

8
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'}});
}
}

392
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<any[]>([]);
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() {
<template>
<div style="height: 100vh; padding: 16px; box-sizing: border-box; display: flex; flex-direction: column;">
<div
style="height: 100vh; padding: 16px; box-sizing: border-box; display: flex; flex-direction: column;">
<!-- 查询表单 -->
<div class="bg-card mb-4 p-4 rounded shadow flex-shrink-0">
<h3 class="text-lg font-semibold mb-3">查询条件</h3>
<div v-if="enumData.loading" class="text-center py-4 text-gray-500">
正在加载查询条件...
</div>
<QueryForm v-else />
<QueryForm v-else/>
<div class="mt-3 flex gap-2">
<button @click="handleSearch"
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
@ -439,10 +657,54 @@ function handleReset() {
<Modal :title="modalTitle">
<Form/>
</Modal>
<!-- 上传对话框 -->
<!-- 上传对话框 -->
<UploadModal>
<div style="padding: 20px;margin-top: 16px; text-align: center;">
<Upload
name="file"
:file-list="uploadFileList"
:custom-request="handleCustomRequest"
:show-upload-list="true"
accept="image/*" style="width: 100%;"
>
<div style="width: 100%; height: 150px; border: 2px dashed #d9d9d9; border-radius: 8px; background: #fafafa; display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer;">
<!-- 使用 SVG 图标代替 -->
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#1890ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-bottom: 8px;">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
<span style="color: #666;">点击或拖拽图片到此上传</span>
<span style="color: #999; font-size: 12px; margin-top: 4px;">支持 JPG、PNG 格式</span>
</div>
</Upload>
<!-- 展示上传成功后的图片 -->
<div v-if="uploadedUrl" style="margin-top: 16px; text-align: center;">
<p style="color: #67c23a; font-size: 14px; margin-bottom: 8px;">✅ 上传成功!</p>
<p style="color: #999; font-size: 12px; margin-bottom: 8px;">新图片预览:</p>
<Image
:src="uploadedUrl" style="max-width: 200px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);"
:preview="true"
/>
<p style="color: #999; font-size: 12px; margin-top: 8px; word-break: break-all;">{{ uploadedUrl }}</p>
</div>
<!-- 展示原图(如果有) -->
<div v-if="uploadImageUrl && !uploadedUrl" style="margin-top: 16px; text-align: center;">
<p style="color: #999; font-size: 12px; margin-bottom: 8px;">当前图片:</p>
<Image
:src="uploadImageUrl.startsWith('http') || uploadImageUrl.startsWith('/') ? uploadImageUrl : `${API_BASE_URL}${uploadImageUrl}`" style="max-width: 200px; border-radius: 4px;"
:preview="true"
/>
</div>
</div>
</UploadModal>
</div>
</template>
<style scoped>
:deep(.vxe-pager--wrapper) {
margin-bottom: 0.5rem;
}
</style>
</style>

Loading…
Cancel
Save