Files
2026-10-20 19:07:36 +08:00

218 lines
5.9 KiB
Smarty

<template>
<!-- [[$.ModuleComment]]详情弹窗 -->
<el-dialog
v-model="props.visible"
title="[[$.ModuleComment]]详情"
width="750"
:close-on-click-modal="false"
:before-close="dialogClose"
>
<el-descriptions column="2" border>
[[- range .FormFields]]
<el-descriptions-item label="[[.Label]]:" label-class-name="des-width">
[[- if .IsImage]]
<!-- 图片:支持点击预览 -->
<el-image
v-if="formData.[[.CamelName]]"
:src="formData.[[.CamelName]]"
:preview-src-list="[formData.[[.CamelName]]]"
fit="cover"
style="width: 80px; height: 80px"
/>
<span v-else>-</span>
[[- else if .IsRichText]]
<!-- 富文本:直接渲染 HTML 内容 -->
<div class="rich-text-content" v-html="formData.[[.CamelName]]"></div>
[[- else if eq .Type "BooleanField"]]
<!-- 布尔:用标签区分是与否 -->
<el-tag :type="formData.[[.CamelName]] ? 'success' : 'danger'">
{{ formData.[[.CamelName]] ? "是" : "否" }}
</el-tag>
[[- else if and (eq .Type "IntegerField") (.Choices)]]
<!-- 选项:按选项映射为可读文字 -->
<span>{{ getChoiceLabel('[[.CamelName]]', formData.[[.CamelName]]) }}</span>
[[- else]]
<!-- 普通文本:空值显示短横线 -->
{{ formData.[[.CamelName]] || '-' }}
[[- end]]
</el-descriptions-item>
[[- end]]
</el-descriptions>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogClose">关闭</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { get[[$.ModelClass]]Detail } from '@/api/[[$.ApiPath]]';
import { onMounted, reactive, watch } from 'vue';
[[- if $.HasImageField]]
import { ElImage } from 'element-plus';
[[- end]]
const emit = defineEmits(['update:visible']);
/**
* 表单数据
*
* 字段与 form_fields 一一对应,按类型初始化默认值:
* 字符串为空串、数字为 0、布尔为 false。
*/
const formData = reactive({
id: '',
[[- range .FormFields]]
[[.CamelName]]: [[if eq .Type "CharField"]]''[[else if eq .Type "IntegerField"]]0[[else if eq .Type "BooleanField"]]false[[else if eq .Type "FloatField"]]0[[else]]''[[end]],
[[- end]]
});
/**
* 组件接收的参数
*
* @property visible 弹窗是否显示,配合 update:visible 双向绑定
* @property [[$.PrimaryPk]] [[$.ModuleComment]]ID,为 0 时展示空数据
*/
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
[[$.PrimaryPk]]: {
type: Number,
required: true,
default: 0,
},
});
/**
* 获取选项字段的显示文字
*
* 按字段名与值从 choiceMap 中查找标签,
* 未匹配到时回退为原值,仍为空则返回短横线。
*
* @param fieldName 字段名(驼峰)
* @param value 字段值
* @returns 选项标签或原始值
*/
const getChoiceLabel = (fieldName: string, value: any) => {
const choiceMap: Record<string, Record<string, string>> = {
[[- range .FormFields]]
[[- if and (eq .Type "IntegerField") (.Choices)]]
[[.CamelName]]: {
[[- range .Choices]]
[[.Value]]: '[[.Label]]',
[[- end]]
},
[[- end]]
[[- end]]
};
return choiceMap[fieldName]?.[value] || value || '-';
};
/**
* 关闭弹窗
*
* 通过 update:visible 通知父组件隐藏。
*/
const dialogClose = () => {
emit('update:visible', false);
};
/**
* 加载详情数据
*
* 按主键请求详情接口,逐字段回填到 formData;
* 仅覆盖接口返回的非空字段,避免空值冲掉默认值。
* 请求失败时在控制台输出错误,不阻断页面。
*/
const setFormData = async () => {
try {
const data = await get[[$.ModelClass]]Detail(props.[[$.PrimaryPk]]);
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
//@ts-ignore
formData[key] = data[key];
}
}
} catch (error) {
console.error('获取[[$.ModuleComment]]详情失败', error);
}
};
/**
* 重置表单数据
*
* 按字段当前类型还原为初始值,用于无主键时清空展示。
*/
const resetFormData = () => {
for (const key in formData) {
//@ts-ignore
if (typeof formData[key] === 'string') {
//@ts-ignore
formData[key] = '';
} else if (typeof formData[key] === 'number') {
//@ts-ignore
formData[key] = 0;
} else if (typeof formData[key] === 'boolean') {
//@ts-ignore
formData[key] = false;
}
}
};
/**
* 监听弹窗显示状态
*
* 显示时:有主键则加载详情,无主键则重置为空数据。
* immediate 保证首次挂载即按当前状态处理。
*/
watch(
() => props.visible,
(newVal) => {
if (newVal && props.[[$.PrimaryPk]]) {
setFormData();
} else if (newVal && !props.[[$.PrimaryPk]]) {
resetFormData();
}
},
{ immediate: true },
);
/**
* 组件挂载后处理
*
* 若初始即为显示状态且带主键,则主动加载一次详情。
*/
onMounted(() => {
if (props.visible && props.[[$.PrimaryPk]]) {
setFormData();
}
});
</script>
<style lang="scss" scoped>
/* 标签列固定宽度 */
:deep(.des-width) {
width: 120px;
}
/* 长文本自动换行,避免撑破布局 */
:deep(.el-descriptions__body .el-descriptions__table .el-descriptions__cell) {
word-break: break-all;
}
/* 富文本容器:限高滚动,内嵌图片自适应宽度 */
.rich-text-content {
max-height: 300px;
overflow-y: auto;
border: 1px solid #e4e7ed;
padding: 10px;
border-radius: 4px;
background-color: #f8f9fa;
:deep(img) {
max-width: 100%;
}
}
</style>