| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- <template>
- <el-drawer :model-value="true" title="新建字典" @close="handleClose" :size="600">
- <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" class="add-dict-form">
- <el-form-item label="字典名称" prop="dictName">
- <el-input v-model="formData.dictName" />
- </el-form-item>
- <el-form-item label="字典编码" prop="dictCode">
- <el-input v-model="formData.dictCode" />
- </el-form-item>
- <el-form-item label="字典分类" prop="dictType">
- <el-select v-model="formData.dictType" placeholder="请选择">
- <el-option v-for="(item, index) in dictionaryTypeOptions" :key="index" :label="item.label"
- :value="item.value" />
- </el-select>
- </el-form-item>
- <div class="subDictWrapper">
- <div class="el-form-item__label" style="width: 100px">字典项</div>
- <div class="subDictList">
- <div>
- <div v-for="(item, index) in formData.sysDictDataList" :key="index" class="dict-item-group">
- <!-- 移动按钮 -->
- <div class="dict-item-header">
- <div class="move-buttons">
- <el-button v-if="index > 0" type="primary" link @click="moveDictItem(index, 'up')">
- <el-icon>
- <Top />
- </el-icon>
- </el-button>
- <el-button v-if="index < formData.sysDictDataList.length - 1" type="primary" link
- @click="moveDictItem(index, 'down')">
- <el-icon>
- <Bottom />
- </el-icon>
- </el-button>
- <el-button v-if="formData.sysDictDataList.length > 1" type="danger" link
- @click="removeDictItem(index)">
- <el-icon>
- <Delete />
- </el-icon>删除
- </el-button>
- </div>
- </div>
- <el-form-item :label="`字典项值`" :prop="`sysDictDataList.${index}.itemValue`"
- :rules="[{ required: true, message: '请输入字典项值', trigger: 'blur' }]">
- <el-input v-model="item.itemValue" placeholder="请输入" />
- </el-form-item>
- <el-form-item :label="`字典项编码`" :prop="`sysDictDataList.${index}.itemCode`"
- :rules="[{ required: true, message: '请输入字典项编码', trigger: 'blur' }]">
- <el-input v-model="item.itemCode" placeholder="请输入" />
- </el-form-item>
- <el-form-item label="图标">
- <el-upload
- auto-upload="false"
- :action="actionUrl"
- list-type="picture-card"
- :limit="1"
- :on-preview="(file) => handlePictureCardPreview(file, index)" :on-remove="() => handleRemove(index)"
- :on-change="(file, fileList) => handleChange(file, fileList, index)"
- >
- <el-icon>
- <Plus />
- </el-icon>
- </el-upload>
- </el-form-item>
- </div>
- </div>
- </div>
- </div>
- <el-form-item>
- <el-button type="primary" link @click="addDictItem">
- <el-icon>
- <CirclePlus />
- </el-icon> 新增字典分类
- </el-button>
- </el-form-item>
- <el-form-item label="字典描述" prop="description">
- <el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请描述灾害处置过程, 不超过1000字"
- maxlength="1000" show-word-limit />
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-radio-group v-model="formData.status">
- <el-radio-button v-for="item in dictionaryStatusOptions" :key="item.value" :value="item.value">{{ item.label
- }}</el-radio-button>
- </el-radio-group>
- </el-form-item>
- <el-form-item class="form-footer">
- <el-button @click="handleCancel">取消</el-button>
- <el-button type="primary" @click="handleSubmit">提交</el-button>
- </el-form-item>
- </el-form>
- </el-drawer>
- <el-dialog v-model="dialogVisible">
- <div class="dialog-content">
- <img :src="dialogImageUrl" alt="Preview Image" />
- </div>
- </el-dialog>
- </template>
- <script lang="ts" setup>
- import { ref, reactive, PropType, watch, computed } from 'vue';
- import {
- ElForm,
- ElFormItem,
- ElInput,
- ElSelect,
- ElOption,
- ElButton,
- ElRadioGroup,
- ElRadioButton,
- ElInputNumber,
- ElUpload,
- ElIcon,
- ElDialog,
- ElDivider,
- FormInstance,
- FormRules,
- UploadProps,
- UploadUserFile,
- } from 'element-plus';
- import { Plus, Delete, CirclePlus, Minus, Picture, Top, Bottom } from '@element-plus/icons-vue';
- import { dictionaryTypeOptions, DictionaryStatus, dictionaryStatusOptions } from '../constants';
- import { queryDictTypeDetail, uploadPresetImageApi } from '@/api/dict'
- import { getHeaders } from '@/utils/http/axios';
- import { useGlobSetting } from '@/hooks/setting';
- import urlJoin from 'url-join';
- interface SysDictDataItem {
- id?: string | number; // 可选,用于编辑时
- dictId: string | number | undefined; // 关联的字典ID
- dictCode: string;
- itemValue: string;
- itemCode: string;
- itemSort: number | undefined;
- isDefault: 0 | 1,
- imageUrl?: string; // 用于显示已上传的图片
- status: DictionaryStatus.disabled | DictionaryStatus.enabled;
- }
- interface FormData {
- dictId?: string | number; // 可选,用于编辑时
- dictName: string;
- dictCode: string;
- dictType: string;
- sysDictDataList: SysDictDataItem[];
- description: string;
- status: DictionaryStatus.disabled | DictionaryStatus.enabled;
- }
- const props = defineProps({
- dictCode: {
- type: String,
- default: '',
- }
- });
- const { urlPrefix } = useGlobSetting();
- const actionUrl = computed(() => {
- return urlJoin(urlPrefix!, `/admin/minio/uploadFile`);
- });
- const emit = defineEmits(['submit', 'close']);
- const handleClose = () => {
- emit('close');
- };
- const formRef = ref<FormInstance>();
- const formData = reactive<FormData>({
- dictName: '',
- dictCode: '',
- dictType: '',
- sysDictDataList: [
- {
- id: undefined,
- dictId: undefined,
- dictCode: '',
- itemCode: '',
- itemValue: '',
- itemSort: 1, // 默认排序值
- // iconFile: null,
- isDefault: 0,
- status: DictionaryStatus.disabled,
- imageUrl: '',
- },
- ],
- description: '',
- status: DictionaryStatus.disabled,
- ...props.initialData, // 使用 initialData 初始化表单
- });
- watch(() => props.dictCode, (newData) => {
- if (newData) {
- queryDictTypeDetail(newData).then((res) => {
- Object.assign(formData, {
- ...res,
- // 单独处理字典项数组保持响应式更新
- sysDictDataList: res.sysDictDataList || []
- });
- })
- }
- }, { deep: true, immediate: true })
- const formRules = reactive<FormRules<FormData>>({
- dictName: [{ required: true, message: '请选择字典名称', trigger: 'change' }],
- dictCode: [{ required: true, message: '请选择字典编码', trigger: 'change' }],
- dictType: [{ required: true, message: '请选择字典分类', trigger: 'change' }],
- description: [{ max: 1000, message: '描述不超过1000字', trigger: 'blur' }],
- status: [{ required: true, message: '请选择状态', trigger: 'change' }],
- });
- const addDictItem = () => {
- formData.sysDictDataList.push({
- id: undefined,
- dictId: formData.dictId,
- dictCode: formData.dictCode,
- itemCode: '',
- itemValue: '',
- itemSort: 1, // 默认排序值
- isDefault: 0,
- status: DictionaryStatus.disabled,
- imageUrl: '',
- },
- );
- };
- const removeDictItem = (index: number) => {
- formData.sysDictDataList.splice(index, 1);
- };
- // 图片上传相关
- const dialogImageUrl = ref('');
- const dialogVisible = ref(false);
- const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
- dialogImageUrl.value = uploadFile.url!;
- dialogVisible.value = true;
- };
- const handleRemove = (itemIndex: number) => {
- if (formData.sysDictDataList[itemIndex]) {
- // formData.sysDictDataList[itemIndex].iconFile = null;
- formData.sysDictDataList[itemIndex].imageUrl = ''; // 如果有单独的 URL 字段
- }
- };
- const handleChange = (uploadFile: UploadUserFile, uploadFiles: UploadUserFile[], itemIndex: number) => {
- // Element Plus 的 onChange 会在文件状态改变时触发多次,通常在 ready 状态时文件已可选
- // 这里简单地将文件对象存起来,实际上传应在 handleSubmit 中处理
- if (formData.sysDictDataList[itemIndex]) {
- // formData.sysDictDataList[itemIndex].iconFile = uploadFile;
- // console.log('uploadFile:', uploadFile);
- // uploadPresetImageApi(uploadFile.raw, 'CAMERA_IMAGE').then((res) => {
- // // formData.sysDictDataList[itemIndex].imageUrl = res.data;
- // })
- }
- };
- const handleUpload = (res: any) => {
- console.log('res:', res)
- };
- const handleSubmit = async () => {
- if (!formRef.value) return;
- await formRef.value.validate((valid) => {
- if (valid) {
- // 在这里处理实际的文件上传逻辑,例如使用 FormData
- // const submissionData = new FormData();
- // Object.keys(formData).forEach(key => {
- // if (key === 'sysDictDataList') {
- // formData.sysDictDataList.forEach((item, index) => {
- // submissionData.append(`sysDictDataList[${index}][value]`, item.value);
- // submissionData.append(`sysDictDataList[${index}][code]`, item.code);
- // submissionData.append(`sysDictDataList[${index}][sortOrder]`, String(item.sortOrder));
- // if (item.iconFile && item.iconFile.raw) {
- // submissionData.append(`sysDictDataList[${index}][icon]`, item.iconFile.raw);
- // }
- // });
- // } else {
- // submissionData.append(key, formData[key as keyof Omit<FormData, 'sysDictDataList'>]);
- // }
- // });
- // emit('submit', submissionData);
- formData.sysDictDataList.forEach(item => {
- item.dictCode = formData.dictCode;
- });
-
- emit('submit', JSON.parse(JSON.stringify(formData))); // 暂时发送原始数据,上传需单独处理
- } else {
- return false;
- }
- });
- };
- // 移动方法
- const moveDictItem = (index: number, direction: 'up' | 'down') => {
- const items = formData.sysDictDataList;
- if (direction === 'up' && index > 0) {
- // 交换位置
- [items[index - 1], items[index]] = [items[index], items[index - 1]];
- } else if (direction === 'down' && index < items.length - 1) {
- // 交换位置
- [items[index + 1], items[index]] = [items[index], items[index + 1]];
- }
-
- // 根据最新位置重新设置排序值
- items.forEach((item, idx) => {
- item.itemSort = idx + 1;
- });
- };
-
- const handleCancel = () => {
- emit('close');
- };
- // 暴露方法给父组件,例如重置表单
- defineExpose({
- resetForm: () => {
- formRef.value?.resetFields();
- formData.sysDictDataList = [
- {
- id: undefined,
- dictId: undefined,
- dictCode: '',
- itemCode: '',
- itemValue: '',
- itemSort: 1, // 默认排序值
- isDefault: 0,
- status: DictionaryStatus.disabled,
- imageUrl: '',
- },
- ];
-
- },
- getFormData: () => formData, // 允许父组件获取当前表单数据
- });
- </script>
- <style lang="scss" scoped>
- .add-dict-form {
- .el-select,
- .el-input-number {
- width: 100%;
- }
- }
- .dict-item-group {
- padding: 15px;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- margin-bottom: 20px;
- position: relative;
- .dict-item-header {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- margin-bottom: 10px;
- h4 {
- margin: 0;
- font-size: 16px;
- }
- }
- }
- .form-footer {
- text-align: right;
- margin-top: 20px;
- }
- // 覆盖 el-upload 的样式,使其适应小图标场景
- :deep(.el-upload--picture-card) {
- width: 100px;
- height: 100px;
- line-height: 110px;
- }
- :deep(.el-upload-list--picture-card .el-upload-list__item) {
- width: 100px;
- height: 100px;
- }
- .subDictWrapper {
- display: flex;
- .dictItemsLabel {
- width: 30%;
- font-size: 14px;
- color: #606266;
- text-align: right;
- margin-right: 26px;
- }
- .subDictList {
- flex: 1;
- }
- }
- .dict-item-header {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- margin-bottom: 10px;
-
- .move-buttons {
- margin-right: auto;
- .el-button {
- padding: 5px;
- margin-right: 8px;
- }
- }
- }
- .dialog-content {
- width: 100%;
- height: 100%;
- img {
- width: 100%;
- height: 100%;
- }
- }
- </style>
|