You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
736 lines
23 KiB
736 lines
23 KiB
<script setup lang="ts">
|
|
/**
|
|
* fun管理页面
|
|
*/
|
|
import {ref, reactive, onMounted, watch, h, computed} from 'vue';
|
|
import {useVbenVxeGrid} from '#/adapter/vxe-table';
|
|
import {useVbenModal} from '@vben/common-ui';
|
|
import {useVbenForm} from '#/adapter/form';
|
|
import {Upload, message, Image, Tag} from 'ant-design-vue';
|
|
import dayjs from 'dayjs';
|
|
import {columns} from './data';
|
|
import {formSchema} from './form';
|
|
import { funApi } from '#/api/fun';
|
|
|
|
// ========== 获取 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 uploadFieldName = ref('');
|
|
const uploadImageUrl = ref('');
|
|
const uploadedUrl = ref('');
|
|
const formUploadUrls = ref<Record<string, string>>({});
|
|
|
|
// ========== 枚举数据存储 ==========
|
|
const enumOptionsMap = ref<Record<string, Array<{label: string, value: any}>>>({});
|
|
|
|
// ========== 需要优化的枚举字段配置 ==========
|
|
const enumFields = [
|
|
{ fieldName: 'funWarnLevel', label: '告警级别' },
|
|
{ fieldName: 'funIndex', label: '首页展示' },
|
|
{ fieldName: 'deviceType', label: '设备类型' },
|
|
{ fieldName: 'funStatus', label: '功能状态' },
|
|
];
|
|
|
|
// ========== 动态生成的查询表单 Schema ==========
|
|
const querySchema = ref([]);
|
|
|
|
// ========== 隐藏字段配置 ==========
|
|
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(field: string) {
|
|
return /img|face|picture/i.test(field);
|
|
}
|
|
|
|
// ========== 判断是否是枚举字段 ==========
|
|
function isEnumField(field: string) {
|
|
return enumFields.some(item => item.fieldName === field);
|
|
}
|
|
|
|
// ========== 获取枚举值的显示文本 ==========
|
|
function getEnumLabel(fieldName: string, value: any): string {
|
|
const options = enumOptionsMap.value[fieldName];
|
|
if (!options || !Array.isArray(options)) {
|
|
return value !== undefined && value !== null ? String(value) : '-';
|
|
}
|
|
const option = options.find(opt => String(opt.value) === String(value));
|
|
return option ? option.label : (value !== undefined && value !== null ? String(value) : '-');
|
|
}
|
|
|
|
// ========== 获取枚举选项(使用 getEnumOptionsBatch)==========
|
|
async function loadEnumOptions() {
|
|
try {
|
|
const fieldNames = enumFields.map(item => item.fieldName);
|
|
const res = await funApi.getEnumOptionsBatch(fieldNames);
|
|
const data = res.result || res;
|
|
|
|
// 处理返回的枚举数据
|
|
if (data && Array.isArray(data)) {
|
|
data.forEach((item: any) => {
|
|
if (item.fieldName && item.options && Array.isArray(item.options)) {
|
|
enumOptionsMap.value[item.fieldName] = item.options.map((opt: any) => ({
|
|
label: opt.label,
|
|
value: opt.value,
|
|
}));
|
|
console.log(`✅ 加载枚举 ${item.fieldName}:`, enumOptionsMap.value[item.fieldName]);
|
|
}
|
|
});
|
|
} else if (data && typeof data === 'object') {
|
|
Object.keys(data).forEach(key => {
|
|
if (Array.isArray(data[key])) {
|
|
enumOptionsMap.value[key] = data[key].map((opt: any) => ({
|
|
label: opt.label,
|
|
value: opt.value,
|
|
}));
|
|
console.log(`✅ 加载枚举 ${key}:`, enumOptionsMap.value[key]);
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log('✅ 所有枚举选项加载完成:', enumOptionsMap.value);
|
|
} catch (error) {
|
|
console.error('❌ 加载枚举选项失败:', error);
|
|
message.error('加载枚举选项失败');
|
|
}
|
|
}
|
|
|
|
// ========== 初始化枚举数据和查询表单 ==========
|
|
async function initEnumData() {
|
|
try {
|
|
// 先加载枚举选项
|
|
await loadEnumOptions();
|
|
|
|
// 根据 queryFields 和枚举数据动态生成查询表单 schema
|
|
querySchema.value = formSchema.filter(formItem => {
|
|
if (hiddenColumns.value.includes(formItem.fieldName)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}).map(formSchemaTmp => {
|
|
// 检查是否是枚举字段
|
|
const enumField = enumFields.find(item => item.fieldName === formSchemaTmp.fieldName);
|
|
|
|
if (enumField && enumOptionsMap.value[formSchemaTmp.fieldName]) {
|
|
const options = enumOptionsMap.value[formSchemaTmp.fieldName];
|
|
return {
|
|
component: 'Select',
|
|
fieldName: formSchemaTmp.fieldName,
|
|
label: enumField.label,
|
|
componentProps: {
|
|
placeholder: `请选择${enumField.label}`,
|
|
allowClear: true,
|
|
options: options.map(opt => ({
|
|
label: opt.label,
|
|
value: opt.value,
|
|
})),
|
|
},
|
|
};
|
|
} else {
|
|
// 普通输入框
|
|
return {
|
|
component: 'Input',
|
|
fieldName: formSchemaTmp.fieldName,
|
|
label: formSchemaTmp.label,
|
|
componentProps: {
|
|
placeholder: `请输入${formSchemaTmp.label}`,
|
|
allowClear: true,
|
|
},
|
|
};
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('初始化枚举数据失败:', error);
|
|
}
|
|
}
|
|
|
|
// 页面加载时自动执行初始化
|
|
onMounted(() => {
|
|
initEnumData();
|
|
});
|
|
|
|
// ========== 查询表单配置 ==========
|
|
const [QueryForm, queryFormApi] = useVbenForm({
|
|
schema: [],
|
|
layout: 'inline',
|
|
showDefaultActions: false,
|
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-5',
|
|
});
|
|
|
|
// ========== 监听 querySchema 变化并更新表单 ==========
|
|
watch(
|
|
querySchema,
|
|
(newSchema) => {
|
|
if (newSchema && newSchema.length > 0) {
|
|
console.log('🔄 更新查询表单 schema:', newSchema);
|
|
queryFormApi.setState({ schema: newSchema });
|
|
}
|
|
},
|
|
{ immediate: false }
|
|
);
|
|
|
|
// ========== 表格配置 ==========
|
|
function getFullUrl(url: string) {
|
|
if (!url) return '';
|
|
if (url.startsWith('http')) return url;
|
|
return `${API_BASE_URL}${url}`;
|
|
}
|
|
|
|
// 表格列配置(动态添加枚举字段的渲染)
|
|
const gridColumns = computed(() => {
|
|
return [
|
|
...columns.map(col => {
|
|
// 如果是枚举字段,使用 Tag 渲染
|
|
if (isEnumField(col.field)) {
|
|
return {
|
|
...col,
|
|
width: 120,
|
|
slots: {
|
|
default: ({row}: any) => {
|
|
const value = row[col.field];
|
|
const label = getEnumLabel(col.field, value);
|
|
|
|
// 根据不同的字段使用不同的颜色
|
|
let color = 'default';
|
|
if (col.field === 'funWarnLevel') {
|
|
const colors: Record<string, string> = { '0': 'default', '1': 'warning', '2': 'error', '3': 'error' };
|
|
color = colors[String(value)] || 'default';
|
|
} else if (col.field === 'funIndex') {
|
|
color = value === 0 ? 'success' : 'default';
|
|
} else if (col.field === 'funStatus') {
|
|
const colors: Record<string, string> = { '0': 'success', '1': 'error', '2': 'warning' };
|
|
color = colors[String(value)] || 'default';
|
|
}
|
|
|
|
return h(Tag, { color }, () => label);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// 如果是图片字段,使用图片渲染
|
|
if (isImageField(col.field)) {
|
|
return {
|
|
...col,
|
|
width: 200,
|
|
align: 'center',
|
|
slots: {
|
|
default: ({row}: any) => {
|
|
const imgUrl = row[col.field];
|
|
const handleClick = (e: Event) => {
|
|
e.stopPropagation();
|
|
currentRow.value = row;
|
|
isEdit.value = true;
|
|
openUploadDialog(col.field, imgUrl);
|
|
};
|
|
|
|
if (!imgUrl) {
|
|
return h('div', { class: 'flex items-center justify-center gap-2 h-[50px]' }, [
|
|
h('span', { class: 'text-gray-400 text-xs' }, '无图片'),
|
|
h('button', { onClick: handleClick, class: 'text-blue-500 text-xs' }, '上传'),
|
|
]);
|
|
}
|
|
|
|
return h('div', { class: 'flex items-center gap-2 justify-center' }, [
|
|
h(Image, {
|
|
src: getFullUrl(imgUrl),
|
|
width: 80,
|
|
height: 60,
|
|
preview: true,
|
|
}),
|
|
h('button', { onClick: handleClick, class: 'text-blue-500 text-xs' }, '更换'),
|
|
]);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
return col;
|
|
}),
|
|
{
|
|
field: 'action',
|
|
title: '操作',
|
|
width: 120,
|
|
fixed: 'right',
|
|
slots: { default: 'action' },
|
|
},
|
|
];
|
|
});
|
|
|
|
const gridOptions = {
|
|
columns: gridColumns.value,
|
|
proxyConfig: {
|
|
ajax: {
|
|
query: async ({page}) => {
|
|
try {
|
|
const queryValues = await queryFormApi.getValues();
|
|
|
|
console.log('=== 查询参数 ===', {
|
|
pageNum: page?.currentPage || 1,
|
|
pageSize: page?.pageSize || 10,
|
|
...queryValues,
|
|
});
|
|
|
|
const res = await funApi.page({
|
|
pageNum: page.currentPage || 1,
|
|
pageSize: page?.pageSize || 10,
|
|
...queryValues,
|
|
});
|
|
|
|
const data = res.error?.result || res.result || res;
|
|
return {
|
|
items: data.records || [],
|
|
total: data.total || 0
|
|
};
|
|
} catch (error) {
|
|
console.error('查询列表失败:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
},
|
|
response: {
|
|
result: 'items',
|
|
total: 'total'
|
|
}
|
|
},
|
|
pagerConfig: {
|
|
enabled: true,
|
|
pageSize: 10,
|
|
},
|
|
showOverflow: true,
|
|
minHeight: '100%',
|
|
maxHeight: 'auto',
|
|
showHeaderOverflow: true,
|
|
};
|
|
|
|
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions });
|
|
|
|
// 监听 gridColumns 变化,更新表格列
|
|
watch(gridColumns, (newColumns) => {
|
|
gridApi.setOptions({ columns: newColumns });
|
|
}, { deep: true });
|
|
|
|
// ========== 过滤后的编辑表单 Schema ==========
|
|
const editFormSchema = formSchema.filter(item => !editFields.includes(item.fieldName));
|
|
|
|
// ========== 动态计算编辑表单 Schema ==========
|
|
const getEditFormSchema = () => {
|
|
return formSchema.filter(item => {
|
|
if (!editFields.includes(item.fieldName)) {
|
|
if (!isEdit.value && item.fieldName === 'id') {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}).map(item => {
|
|
// 检查是否是枚举字段
|
|
const enumField = enumFields.find(field => field.fieldName === item.fieldName);
|
|
|
|
if (enumField && enumOptionsMap.value[item.fieldName]) {
|
|
// 使用下拉选择器
|
|
return {
|
|
...item,
|
|
component: 'Select',
|
|
componentProps: {
|
|
placeholder: `请选择${enumField.label}`,
|
|
options: enumOptionsMap.value[item.fieldName].map(opt => ({
|
|
label: opt.label,
|
|
value: opt.value,
|
|
})),
|
|
},
|
|
};
|
|
}
|
|
|
|
// 只在新增模式下,且是图片字段,添加上传组件
|
|
if (!isEdit.value && isImageField(item.fieldName)) {
|
|
const currentImageUrl = formUploadUrls.value[item.fieldName] || '';
|
|
return {
|
|
...item,
|
|
component: 'Upload',
|
|
componentProps: {
|
|
class: 'w-full',
|
|
accept: 'image/*',
|
|
maxCount: 1,
|
|
listType: 'picture-card',
|
|
fileList: currentImageUrl && typeof currentImageUrl === 'string' ? [{
|
|
uid: '-1',
|
|
name: 'image.png',
|
|
status: 'done',
|
|
url: currentImageUrl,
|
|
}] : (Array.isArray(currentImageUrl) ? currentImageUrl : []),
|
|
beforeUpload: async (file: any) => {
|
|
console.log('📦 新增表单内上传文件:', file);
|
|
try {
|
|
const resultUrl = await uploadFile(file);
|
|
console.log(item.fieldName + ' 新增表单内上传成功,URL:', resultUrl);
|
|
message.success('上传成功!');
|
|
formUploadUrls.value[item.fieldName] = resultUrl;
|
|
setTimeout(() => {
|
|
formApi.setState({ schema: getEditFormSchema() });
|
|
}, 0);
|
|
return false;
|
|
} catch (error: any) {
|
|
console.error('❌ 新增表单内上传失败:', error);
|
|
message.error(error?.message || '上传失败');
|
|
return false;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
}
|
|
return item;
|
|
});
|
|
};
|
|
|
|
// ========== 表单配置 ==========
|
|
const [Form, formApi] = useVbenForm({
|
|
schema: editFormSchema,
|
|
showDefaultActions: false,
|
|
wrapperClass: 'grid-cols-1 md:grid-cols-2',
|
|
commonConfig: {
|
|
componentProps: {
|
|
class: 'w-full',
|
|
autocomplete: 'off',
|
|
},
|
|
},
|
|
});
|
|
|
|
// ========== 弹窗配置 ==========
|
|
const [Modal, modalApi] = useVbenModal({
|
|
centered: true,
|
|
closable: true,
|
|
maskClosable: false,
|
|
draggable: true,
|
|
width: 800,
|
|
onCancel() {
|
|
modalApi.close();
|
|
},
|
|
onConfirm: async () => {
|
|
let loadingMessage: any = null;
|
|
try {
|
|
const values = await formApi.validateAndSubmitForm();
|
|
if (!values) return;
|
|
|
|
// 处理枚举字段,确保值是数字类型
|
|
const processedValues = { ...values };
|
|
enumFields.forEach(field => {
|
|
if (processedValues[field.fieldName] !== undefined && processedValues[field.fieldName] !== null) {
|
|
processedValues[field.fieldName] = Number(processedValues[field.fieldName]);
|
|
}
|
|
});
|
|
|
|
const submitValues = !isEdit.value
|
|
? {...processedValues, ...formUploadUrls.value}
|
|
: processedValues;
|
|
|
|
const finalSubmitValues = {
|
|
...submitValues,
|
|
userBirthday: submitValues.userBirthday ? dayjs(submitValues.userBirthday).format('YYYY-MM-DD') : null,
|
|
};
|
|
|
|
loadingMessage = message.loading(isEdit.value ? '保存中...' : '新增中...', 0);
|
|
|
|
if (isEdit.value && currentRow.value?.id) {
|
|
await funApi.save({...finalSubmitValues, id: currentRow.value.id});
|
|
Object.assign(currentRow.value, finalSubmitValues);
|
|
modalApi.close();
|
|
gridApi.reload(); // 使用 reload 刷新表格
|
|
loadingMessage();
|
|
isEdit.value = false;
|
|
message.success('保存成功!');
|
|
} else {
|
|
await funApi.add(finalSubmitValues);
|
|
modalApi.close();
|
|
gridApi.reload();
|
|
formUploadUrls.value = {};
|
|
loadingMessage();
|
|
message.success('新增成功!');
|
|
}
|
|
} catch (error: any) {
|
|
console.error('保存失败:', error);
|
|
if (loadingMessage) {
|
|
loadingMessage();
|
|
}
|
|
message.error(error?.message || '保存失败');
|
|
}
|
|
},
|
|
onOpenChange(isOpen: boolean) {
|
|
if (!isOpen && !isEdit.value) {
|
|
formApi.resetForm();
|
|
formUploadUrls.value = {};
|
|
}
|
|
},
|
|
});
|
|
|
|
// ========== 上传弹窗配置 ==========
|
|
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 () => {
|
|
let loadingMessage: any = null;
|
|
console.log('=== 点击确认保存 ===', {
|
|
uploadedUrl: uploadedUrl.value,
|
|
isEdit: isEdit.value,
|
|
currentRowId: currentRow.value?.id,
|
|
uploadFieldName: uploadFieldName.value,
|
|
});
|
|
|
|
if (uploadedUrl.value) {
|
|
try {
|
|
if (currentRow.value) {
|
|
currentRow.value[uploadFieldName.value] = uploadedUrl.value;
|
|
}
|
|
|
|
if (isEdit.value && currentRow.value?.id) {
|
|
const submitData = {
|
|
...currentRow.value,
|
|
id: currentRow.value.id
|
|
};
|
|
loadingMessage = message.loading('保存图片中...', 0);
|
|
await funApi.save(submitData);
|
|
message.success('保存成功!');
|
|
uploadModalApi.close();
|
|
loadingMessage();
|
|
uploadFileList.value = [];
|
|
uploadedUrl.value = '';
|
|
gridApi.reload(); // 刷新表格
|
|
} else {
|
|
message.warning('非编辑模式或无 ID,仅更新预览');
|
|
uploadModalApi.close();
|
|
}
|
|
} catch (error: any) {
|
|
console.error('❌ 保存失败:', error);
|
|
if (loadingMessage) {
|
|
loadingMessage();
|
|
}
|
|
message.error(error?.message || '保存失败');
|
|
}
|
|
} else {
|
|
message.warning('请先选择并上传图片');
|
|
}
|
|
},
|
|
});
|
|
|
|
// ========== 上传文件 ==========
|
|
async function uploadFile(file: File) {
|
|
let loadingMessage: any = null;
|
|
loadingMessage = message.loading('图片上传中...', 0);
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
const res = await funApi.upload(formData);
|
|
loadingMessage();
|
|
return res.result || res.message || res.url || res;
|
|
}
|
|
|
|
// ========== 打开上传对话框 ==========
|
|
function openUploadDialog(fieldName: string, currentUrl: string) {
|
|
console.log('=== 打开上传对话框 ===', {
|
|
fieldName,
|
|
currentUrl,
|
|
isEdit: isEdit.value,
|
|
currentRowId: currentRow.value?.id,
|
|
});
|
|
|
|
uploadFieldName.value = fieldName;
|
|
uploadImageUrl.value = currentUrl || '';
|
|
uploadedUrl.value = '';
|
|
uploadFileList.value = [];
|
|
uploadModalApi.open();
|
|
}
|
|
|
|
// ========== 处理图片上传 ==========
|
|
async function handleCustomRequest(options: any) {
|
|
const {file, onSuccess, onError} = options;
|
|
|
|
try {
|
|
const resultUrl = await uploadFile(file);
|
|
message.success('上传成功!');
|
|
uploadedUrl.value = resultUrl;
|
|
|
|
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 handleAdd() {
|
|
isEdit.value = false;
|
|
modalTitle.value = '功能配置表新增';
|
|
formUploadUrls.value = {};
|
|
formApi.setState({ schema: getEditFormSchema() });
|
|
formApi.resetForm();
|
|
modalApi.open();
|
|
}
|
|
|
|
// ========== 打开编辑弹窗 ==========
|
|
function handleEdit(row: any) {
|
|
isEdit.value = true;
|
|
currentRow.value = row;
|
|
modalTitle.value = '功能配置表编辑';
|
|
formApi.setState({ schema: getEditFormSchema() });
|
|
|
|
// 设置表单值,对枚举字段进行类型匹配
|
|
const formValues: any = {};
|
|
editFormSchema.forEach(item => {
|
|
const field = item.fieldName;
|
|
if (row[field] !== undefined && row[field] !== null) {
|
|
if (isEnumField(field)) {
|
|
// 枚举字段:从选项中查找匹配的值
|
|
const options = enumOptionsMap.value[field];
|
|
if (options && options.length > 0) {
|
|
const matchedOption = options.find(opt => String(opt.value) === String(row[field]));
|
|
formValues[field] = matchedOption ? matchedOption.value : row[field];
|
|
} else {
|
|
formValues[field] = row[field];
|
|
}
|
|
} else {
|
|
formValues[field] = row[field];
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log('📝 设置编辑表单值:', formValues);
|
|
formApi.setValues(formValues);
|
|
modalApi.open();
|
|
}
|
|
|
|
// ========== 删除确认 ==========
|
|
function handleDelete(row: any) {
|
|
if (!row.id) return;
|
|
if (window.confirm(`确定要删除 "${row.funName || row.userName}" 吗?`)) {
|
|
funApi.remove(row.id).then(() => {
|
|
gridApi.reload();
|
|
message.success('删除成功!');
|
|
}).catch(() => {
|
|
message.error('删除失败!');
|
|
});
|
|
}
|
|
}
|
|
|
|
// ========== 查询功能 ==========
|
|
function handleSearch() {
|
|
console.log('=== 点击查询按钮 ===');
|
|
gridApi.query();
|
|
}
|
|
|
|
// ========== 重置查询 ==========
|
|
function handleReset() {
|
|
queryFormApi.resetForm();
|
|
gridApi.reload();
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<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>
|
|
<QueryForm />
|
|
<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">
|
|
🔍 查询
|
|
</button>
|
|
<button @click="handleReset" class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600">
|
|
🔄 重置
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 数据表格 -->
|
|
<div class="flex-1 overflow-hidden bg-card rounded shadow">
|
|
<Grid>
|
|
<template #toolbar-tools>
|
|
<button @click="handleAdd" class="mr-2">➕ 新增</button>
|
|
<button @click="() => gridApi.reload()">🔄 刷新</button>
|
|
</template>
|
|
|
|
<template #action="{ row }">
|
|
<div class="flex gap-2">
|
|
<button @click="() => handleEdit(row)" class="text-blue-500 hover:text-blue-700">✏️ 编辑</button>
|
|
<button @click="() => handleDelete(row)" class="text-red-500 hover:text-red-700">🗑️ 删除</button>
|
|
</div>
|
|
</template>
|
|
</Grid>
|
|
</div>
|
|
|
|
<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 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"
|
|
/>
|
|
</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="getFullUrl(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>
|
|
|