Files
2026-10-20 11:37:25 +08:00

379 lines
11 KiB
Smarty
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- [[$.ModuleComment]]新增/编辑弹窗(树形结构) -->
<el-dialog
v-model="props.visible"
:title="props.[[$.PrimaryPk]] ? '编辑' : '新增'"
width="600"
:close-on-click-modal="false"
:before-close="dialogClose"
>
<el-form ref="formRef" :model="formData" label-width="80px">
[[- range .FormFields]]
[[- if eq .CamelName $.ParentIdCamel]]
<!-- [[.Label]](上级选择) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请选择[[.Label]]', trigger: 'change' }"
[[- end]]
>
<el-tree-select
class="flex-1"
v-model="formData.[[.CamelName]]"
:data="treeOptions"
clearable
node-key="[[$.PrimaryPk]]"
:props="{
label: '[[$.DisplayFieldCamel]]',
}"
:default-expand-all="true"
placeholder="请选择[[.Label]]"
check-strictly
/>
</el-form-item>
[[- else if .IsImage]]
<!-- [[.Label]](图片上传) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请上传[[.Label]]', trigger: 'change' }"
[[- end]]
>
<UploadImg
@changeFileName="(name) => (formData.[[.CamelName]]ImgName = name)"
:fileType="['image/jpeg', 'image/png', 'image/jpg', 'image/gif', 'image/webp']"
name="[[$.AppName]]"
:fileSize="20"
v-model:image-url="formData.[[.CamelName]]"
>
<template #tip>支持扩展名: jpg png jpeg gif webp; 文件大小不超过20M</template>
</UploadImg>
</el-form-item>
[[- else if and (eq .Type "CharField") (.IsTextarea)]]
<!-- [[.Label]](多行文本) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请输入[[.Label]]', trigger: 'blur' }"
[[- end]]
>
<el-input
class="flex-1"
v-model="formData.[[.CamelName]]"
type="textarea"
placeholder="请输入[[.Label]]"
:rows="3"
clearable
/>
</el-form-item>
[[- else if eq .Type "CharField"]]
<!-- [[.Label]](单行文本) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请输入[[.Label]]', trigger: 'blur' }"
[[- end]]
>
<el-input v-model="formData.[[.CamelName]]" placeholder="请输入[[.Label]]" clearable />
</el-form-item>
[[- else if and (eq .Type "IntegerField") (.Choices)]]
<!-- [[.Label]]([[if le (len .Choices) 3]]单选[[else]]下拉选择[[end]]) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请选择[[.Label]]', trigger: 'change' }"
[[- end]]
>
[[- if le (len .Choices) 3]]
<el-radio-group v-model="formData.[[.CamelName]]">
[[- range .Choices]]
<el-radio :value="[[.Value]]">[[.Label]]</el-radio>
[[- end]]
</el-radio-group>
[[- else]]
<el-select
v-model="formData.[[.CamelName]]"
placeholder="请选择[[.Label]]"
clearable
filterable
>
<el-option
v-for="item in optionsData.[[.CamelName]]List"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
[[- end]]
</el-form-item>
[[- else if and (eq .Type "IntegerField") (eq .CamelName "sort")]]
<!-- [[.Label]](排序) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请输入[[.Label]]', trigger: 'blur' }"
[[- end]]
>
<div>
<el-input-number v-model="formData.[[.CamelName]]" :min="[[.MinValue]]" :max="[[.MaxValue]]" placeholder="请输入[[.Label]]" />
<div class="form-tips">数值越小越排前</div>
</div>
</el-form-item>
[[- else if and (eq .Type "IntegerField") (not .Choices)]]
<!-- [[.Label]](数字输入) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请输入[[.Label]]', trigger: 'blur' }"
[[- end]]
>
<el-input-number v-model="formData.[[.CamelName]]" :min="[[.MinValue]]" :max="[[.MaxValue]]" />
</el-form-item>
[[- else if eq .Type "BooleanField"]]
<!-- [[.Label]](开关) -->
<el-form-item label="[[.Label]]" prop="[[.CamelName]]">
<el-switch v-model="formData.[[.CamelName]]" :active-value="1" :inactive-value="0" />
</el-form-item>
[[- else if eq .Type "DateField"]]
<!-- [[.Label]](日期选择) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请选择[[.Label]]', trigger: 'change' }"
[[- end]]
>
<el-date-picker
v-model="formData.[[.CamelName]]"
type="date"
placeholder="请选择[[.Label]]"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
[[- else if eq .Type "DateTimeField"]]
<!-- [[.Label]](日期时间选择) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请选择[[.Label]]', trigger: 'change' }"
[[- end]]
>
<el-date-picker
v-model="formData.[[.CamelName]]"
type="datetime"
placeholder="请选择[[.Label]]"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
[[- else if eq .Type "TextField"]]
<!-- [[.Label]](文本域) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请输入[[.Label]]', trigger: 'blur' }"
[[- end]]
>
<el-input class="flex-1" type="textarea" v-model="formData.[[.CamelName]]" placeholder="请输入[[.Label]]" :rows="3" />
</el-form-item>
[[- else if eq .Type "DecimalField"]]
<!-- [[.Label]](小数输入) -->
<el-form-item
label="[[.Label]]"
prop="[[.CamelName]]"
[[- if .Required]]
:rules="{ required: true, message: '请输入[[.Label]]', trigger: 'blur' }"
[[- end]]
>
<el-input-number
v-model="formData.[[.CamelName]]"
:min="[[.MinValue]]"
:max="[[.MaxValue]]"
:precision="2"
:step="0.1"
/>
</el-form-item>
[[- end]]
[[- end]]
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogClose">取消</el-button>
<el-button :loading="subLoading" type="primary" @click="submit"> 确定 </el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'element-plus';
import {
[[$.ModuleCamel]]Add,
[[$.ModuleCamel]]Update,
get[[$.ModelClass]]List,
get[[$.ModelClass]]Detail,
} from '@/api/[[$.ApiPath]]';
import { onMounted, reactive, ref, shallowRef, watch } from 'vue';
import { message, buildTree } from '@/utils/auth';
import { useLockFn } from '@/utils/useLockFn';
[[- if $.HasImageField]]
import UploadImg from '@/components/Upload/Image.vue';
[[- end]]
const emit = defineEmits(['success', 'update:visible']);
const formRef = shallowRef<FormInstance>();
// 树形选项数据,供上级选择器使用
const treeOptions = ref<any[]>([]);
/**
* 下拉选项数据
*
* 仅当选项数超过 3 个时生成,供 el-select 使用;
* 选项不超过 3 个的字段在模板中直接用 el-radio 渲染,不在此维护。
*/
const optionsData = reactive({
[[- range .FormFields]]
[[- if and (eq .Type "IntegerField") (.Choices) (gt (len .Choices) 3)]]
[[.CamelName]]List: [
[[- range .Choices]]
{
id: [[if eq .ValueType "string"]]'[[.Value]]'[[else]][[.Value]][[end]],
name: '[[.Label]]',
},
[[- end]]
],
[[- end]]
[[- end]]
});
/**
* 表单数据
*/
const formData = reactive({
id: '',
[[- range .FormFields]]
[[.CamelName]]: [[if or (eq .Type "IntegerField") (eq .Type "BooleanField") (eq .Type "FloatField") (eq .Type "DecimalField")]][[.Default]][[else]]''[[end]],
[[- end]]
[[- range .FormFields]]
[[- if .IsImage]]
[[.CamelName]]ImgName: '',
[[- end]]
[[- end]]
});
/**
* 定义接收的参数
*/
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
[[$.PrimaryPk]]: {
type: Number,
required: true,
default: 0,
},
parentId: {
type: Number,
default: 0,
},
});
const dialogClose = () => {
emit('update:visible', false);
};
/**
* 加载树形选项数据
*
* 请求全量列表后通过 buildTree 组装为树,
* 并在根节点前置一个"顶级"虚拟节点(id=0),
* 使新增时可将父级设为顶级。
*/
const get[[$.ModelClass]]Tree = async () => {
const data: any = await get[[$.ModelClass]]List();
const tree: any = [{ id: 0, [[$.DisplayFieldCamel]]: '顶级', children: [] }];
const lists = buildTree(data);
tree[0].children.push(...lists);
treeOptions.value = tree;
};
/**
* 提交表单
*/
const handleSubmit = async () => {
await formRef.value?.validate();
props.[[$.PrimaryPk]] ? await [[$.ModuleCamel]]Update(formData) : await [[$.ModuleCamel]]Add(formData);
message('操作成功');
emit('update:visible', false);
emit('success');
};
const { isLock: subLoading, lockFn: submit } = useLockFn(handleSubmit);
/**
* 设置表单数据
*/
const setFormData = (data: Record<any, any>) => {
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
formData[key] = data[key];
}
}
};
/**
* 获取详情
*/
const getDetail = async () => {
const data = await get[[$.ModelClass]]Detail(props.[[$.PrimaryPk]]);
setFormData(data);
};
/**
* 重置表单
*/
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields();
}
formData.[[$.ParentIdCamel]] = props.parentId || 0;
};
onMounted(() => {
get[[$.ModelClass]]Tree();
if (props.[[$.PrimaryPk]]) {
getDetail();
} else {
resetForm();
}
});
watch(
() => props.visible,
(newVal) => {
if (newVal && props.[[$.PrimaryPk]]) {
getDetail();
} else if (newVal && !props.[[$.PrimaryPk]]) {
resetForm();
get[[$.ModelClass]]Tree();
}
},
);
</script>
<style lang="scss" scoped></style>