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.
448 lines
13 KiB
448 lines
13 KiB
<script setup lang="ts">
|
|
/**
|
|
* {{entity}}管理页面
|
|
*/
|
|
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 dayjs from 'dayjs';
|
|
import {columns} from './data';
|
|
import {formSchema} from './form';
|
|
import { {{entity}}Api } from '#/api/{{entity}}';
|
|
import { Image } from 'ant-design-vue';
|
|
|
|
|
|
// ========== 状态变量 ==========
|
|
const currentRow = ref(null);
|
|
const isEdit = ref(false);
|
|
const modalTitle = ref('新增');
|
|
// ========== 动态生成的查询表单 Schema ==========
|
|
const querySchema = ref([]);
|
|
// ========== 枚举数据配置 ==========
|
|
const enumData = reactive({
|
|
loading: true,
|
|
list: [], // 存储接口返回的原始枚举数据
|
|
});
|
|
//todo 枚举数据在查询里面不展示的配置
|
|
const hiddenColumns = ref(['fun_code','graduallyIntervalTime','funMsgTitle','funImg','createdAt', 'updatedBy', 'updatedAt', 'userPassword', 'userId', 'userSys', 'deletedFlag', "userFace"])
|
|
const editFields = [{{editFields}}];
|
|
|
|
// ========== 初始化枚举数据和查询表单 ==========
|
|
async function initEnumData() {
|
|
try {
|
|
enumData.loading = true;
|
|
|
|
// 调用枚举列表接口
|
|
const res = await {{entity}}Api.enumList({});
|
|
const enums = res.result || res;
|
|
|
|
// 保存原始枚举数据
|
|
enumData.list = enums;
|
|
|
|
// 根据 queryFields 和枚举数据动态生成查询表单 schema`
|
|
querySchema.value = formSchema.filter(formItem => {
|
|
// 2. 排除隐藏的字段
|
|
if (hiddenColumns.value.includes(formItem.fieldName)) {
|
|
console.log('跳过隐藏字段:', formItem.fieldName)
|
|
return false;
|
|
}
|
|
return true;
|
|
}).map(formSchemaTmp => {
|
|
// 在枚举列表中查找匹配的字段
|
|
const matchedEnums = enums.filter(item => item.fieldName === formSchemaTmp.fieldName);
|
|
|
|
// 合并相同 fieldName 的所有 options
|
|
const allOptions = matchedEnums.reduce((acc, item) => {
|
|
if (item.options && Array.isArray(item.options)) {
|
|
return [...acc, ...item.options];
|
|
}
|
|
return acc;
|
|
}, []);
|
|
// 判断是否有匹配的枚举选项
|
|
if (allOptions.length > 0) {
|
|
return {
|
|
component: 'Select',
|
|
fieldName: formSchemaTmp.fieldName,
|
|
label: formSchemaTmp.label,
|
|
componentProps: {
|
|
placeholder: `请选择`,
|
|
allowClear: true,
|
|
options: allOptions.map(opt => ({
|
|
label: opt.label,
|
|
value: String(opt.value), // 确保 value 是字符串
|
|
})),
|
|
},
|
|
};
|
|
} else {
|
|
// ❌ 未匹配到枚举 → 使用 Input 组件
|
|
return {
|
|
component: 'Input',
|
|
fieldName: formSchemaTmp.fieldName,
|
|
label: formSchemaTmp.label,
|
|
componentProps: {
|
|
placeholder: `请输入${formSchemaTmp.label}`,
|
|
allowClear: true,
|
|
},
|
|
};
|
|
}
|
|
});
|
|
} catch (error) {
|
|
} finally {
|
|
enumData.loading = false;
|
|
}
|
|
}
|
|
|
|
// 页面加载时自动执行初始化
|
|
onMounted(() => {
|
|
initEnumData();
|
|
});
|
|
|
|
|
|
// ========== 查询表单配置 (初始为空 schema) ==========
|
|
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);
|
|
// ✅ 使用 setState 方法更新 schema
|
|
queryFormApi.setState({
|
|
schema: newSchema,
|
|
});
|
|
}
|
|
},
|
|
{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', {
|
|
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, {
|
|
src: fullUrl,
|
|
height: 60,
|
|
width: 80,
|
|
preview: true,
|
|
style: {
|
|
borderRadius: '4px',
|
|
objectFit: 'cover',
|
|
cursor: 'pointer',
|
|
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
|
},
|
|
onError: () => {
|
|
console.error('❌ 图片加载失败:', fullUrl);
|
|
return h('div', {
|
|
style: {
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
height: '50px',
|
|
backgroundColor: '#f5f5f5',
|
|
borderRadius: '4px',
|
|
color: '#999',
|
|
fontSize: '12px'
|
|
}
|
|
}, '加载失败');
|
|
}
|
|
});
|
|
} 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;
|
|
}),
|
|
{
|
|
field: 'action',
|
|
title: '操作',
|
|
width: 120,
|
|
fixed: 'right',
|
|
slots: {default: 'action'},
|
|
},
|
|
],
|
|
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 {{entity}}Api.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})
|
|
// ========== 过滤后的编辑表单 Schema ==========
|
|
const editFormSchema = formSchema.filter(item => !editFields.includes(item.fieldName));
|
|
// ========== 弹窗配置 ==========
|
|
const [Modal, modalApi] = useVbenModal({
|
|
centered: true,
|
|
closable: true,
|
|
maskClosable: false,
|
|
draggable: true,
|
|
width: 800,
|
|
onCancel() {
|
|
modalApi.close();
|
|
},
|
|
onConfirm: async () => {
|
|
try {
|
|
const values = await formApi.validateAndSubmitForm();
|
|
if (!values) return;
|
|
|
|
// 处理日期格式,将 Day.js 对象转换为字符串
|
|
const submitValues = {
|
|
...values,
|
|
userBirthday: values.userBirthday ? dayjs(values.userBirthday).format('YYYY-MM-DD') : null,
|
|
};
|
|
|
|
if (isEdit.value && currentRow.value?.id) {
|
|
await {{entity}}Api.save({...submitValues, id: currentRow.value.id});
|
|
} else {
|
|
await {{entity}}Api.add(submitValues);
|
|
}
|
|
|
|
modalApi.close();
|
|
gridApi.reload();
|
|
} catch (error) {
|
|
console.error('保存失败:', error);
|
|
}
|
|
},
|
|
onOpenChange(isOpen: boolean) {
|
|
if (!isOpen && !isEdit.value) {
|
|
formApi.resetForm();
|
|
}
|
|
},
|
|
})
|
|
|
|
// ========== 动态计算编辑表单 Schema ==========
|
|
// 新增时:过滤掉 id 字段
|
|
// 编辑时:包含 id 字段
|
|
const getEditFormSchema = () => {
|
|
return formSchema.filter(item => {
|
|
// 不在 editFields 中的才显示
|
|
if (!editFields.includes(item.fieldName)) {
|
|
// 新增时排除 id 字段
|
|
if (!isEdit.value && item.fieldName === 'id') {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
};
|
|
|
|
// ========== 表单配置 ==========
|
|
const [Form, formApi] = useVbenForm({
|
|
schema: editFormSchema,
|
|
showDefaultActions: false,
|
|
wrapperClass: 'grid-cols-1 md:grid-cols-2',
|
|
commonConfig: {
|
|
componentProps: {
|
|
class: 'w-full',
|
|
autocomplete: 'off',
|
|
},
|
|
},
|
|
})
|
|
|
|
|
|
|
|
// ========== 打开新增弹窗 ==========
|
|
function handleAdd() {
|
|
isEdit.value = false;
|
|
modalTitle.value = '{{table_comment}}新增';
|
|
// 更新表单 schema (不包含 id)
|
|
formApi.setState({
|
|
schema: getEditFormSchema(),
|
|
});
|
|
formApi.resetForm();
|
|
modalApi.open();
|
|
}
|
|
|
|
// ========== 打开编辑弹窗 ==========
|
|
function handleEdit(row: any) {
|
|
isEdit.value = true;
|
|
currentRow.value = row;
|
|
modalTitle.value = '{{table_comment}}编辑';
|
|
// 更新表单 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];
|
|
}
|
|
});
|
|
|
|
formApi.setValues(formValues);
|
|
modalApi.open();
|
|
}
|
|
|
|
// ========== 删除确认 ==========
|
|
function handleDelete(row: any) {
|
|
if (!row.id) return;
|
|
window.confirm(`确定要删除 "${row.userName}" 吗?`) &&
|
|
{{entity}}Api.remove(row.id).then(() => {
|
|
gridApi.reload();
|
|
});
|
|
}
|
|
|
|
// ========== 查询功能 ==========
|
|
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>
|
|
<div v-if="enumData.loading" class="text-center py-4 text-gray-500">
|
|
正在加载查询条件...
|
|
</div>
|
|
<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">
|
|
🔍 查询
|
|
</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>
|
|
</div>
|
|
</template>
|
|
<style scoped>
|
|
:deep(.vxe-pager--wrapper) {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
</style>
|