499 lines
14 KiB
Vue
499 lines
14 KiB
Vue
<template>
|
||
<div class="container">
|
||
<div class="header">
|
||
<div>字典名称</div>
|
||
<el-select v-model="currentVarName"
|
||
placeholder="请选择字典项"
|
||
@change="handleDictChange">
|
||
<el-option v-for="item in dictList"
|
||
:key="item.varName"
|
||
:label="item.varName"
|
||
:value="item.varName" />
|
||
</el-select>
|
||
</div>
|
||
|
||
<div>字典修改</div>
|
||
|
||
<!-- 区域字典特殊表格 -->
|
||
<el-table v-if="isRegionDict"
|
||
:data="regionData"
|
||
stripe
|
||
style="width: 777px">
|
||
<el-table-column prop="key" label="大区名称">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.key"
|
||
placeholder="请输入大区名称" />
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column prop="value" label="包含省份(点击修改省份/地区)" width="521">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" @click="showProvinceDialog(row)"
|
||
link style="width: 100%; padding: 0; height: auto; min-height: 32px; white-space: normal; word-break: break-all; line-height: 1.5; display: inline-flex; align-items: center;">
|
||
<span style="text-align: left;">
|
||
{{ row.value.join(',') || '点击选择省份' }}
|
||
</span>
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
|
||
<el-table-column label="操作" width="63">
|
||
<template #default="{ $index }">
|
||
<el-button type="danger"
|
||
:icon="Delete"
|
||
circle
|
||
@click="deleteRegion($index)" />
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 普通字典表格 -->
|
||
<el-table v-else
|
||
:data="currentValues"
|
||
stripe
|
||
style="width: 777px">
|
||
<el-table-column prop="value" label="字典值">
|
||
<template #default="{ $index }">
|
||
<el-input v-model="currentValues[$index]" placeholder="请输入" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="63">
|
||
<template #default="{ $index }">
|
||
<el-button type="danger"
|
||
:icon="Delete"
|
||
circle
|
||
@click="handleDelete($index)" />
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 操作按钮区域 -->
|
||
<div class="footer">
|
||
<template v-if="isRegionDict">
|
||
<el-button type="primary"
|
||
:icon="Plus"
|
||
plain
|
||
round
|
||
@click="addNewRegion">
|
||
添加大区
|
||
</el-button>
|
||
</template>
|
||
<template v-else>
|
||
<el-button type="primary"
|
||
:icon="Plus"
|
||
plain
|
||
round
|
||
@click="handleAdd">
|
||
添加值
|
||
</el-button>
|
||
</template>
|
||
|
||
<el-button type="success"
|
||
:icon="FolderChecked"
|
||
@click="handleSave">
|
||
保存
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 省份选择对话框 -->
|
||
<el-dialog v-model="provinceDialogVisible"
|
||
title="选择省份/地区"
|
||
width="50%">
|
||
<el-checkbox-group v-model="selectedProvinces">
|
||
<el-row :gutter="20">
|
||
<el-col v-for="province in allProvinces"
|
||
:key="province"
|
||
:span="8"
|
||
style="margin-bottom: 15px;">
|
||
<el-checkbox :label="province"
|
||
:value="province" />
|
||
</el-col>
|
||
</el-row>
|
||
</el-checkbox-group>
|
||
|
||
<template #footer>
|
||
<el-button @click="provinceDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="confirmProvinceSelection">
|
||
确定
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted, inject, computed } from 'vue';
|
||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||
import { Plus, Delete, Edit, UploadFilled, RefreshLeft, Upload, EditPen, FolderChecked } from '@element-plus/icons-vue';
|
||
|
||
const $http = inject('$http')
|
||
const config = inject('config');
|
||
|
||
// 初始字典数据
|
||
const dictList = reactive([]);
|
||
// 新增响应式数据
|
||
const provinceDialogVisible = ref(false);
|
||
const selectedProvinces = ref([]);
|
||
const currentEditingRegion = ref(null);
|
||
const allProvinces = computed(() => {
|
||
const provinceDict = dictList.find(d => d.varName == '省份地区');
|
||
return provinceDict?.varValue || [];
|
||
});
|
||
// 获取字典
|
||
const getDic = async () => {
|
||
try {
|
||
const response = await $http.post('ConfigPY/GetConfigString');
|
||
const serverData = response.data.response || [];
|
||
|
||
//console.log(response);
|
||
// 清空原有数据
|
||
dictList.length = 0;
|
||
|
||
// 转换数据格式
|
||
serverData.forEach(item => {
|
||
dictList.push({
|
||
varName: item.varName,
|
||
varValue: tryParseJson(item.varValue) || []
|
||
});
|
||
});
|
||
//console.log(dictList);
|
||
// 默认选中第一个(如果存在)
|
||
if (dictList.length > 0) {
|
||
currentVarName.value = dictList[0].varName;
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('获取字典失败:', error);
|
||
ElMessage.error('字典数据加载失败');
|
||
}
|
||
};
|
||
|
||
// JSON安全解析方法
|
||
const tryParseJson = (str) => {
|
||
try {
|
||
return JSON.parse(str);
|
||
} catch {
|
||
return null;
|
||
}
|
||
};
|
||
|
||
const currentVarName = ref('');
|
||
const currentDict = computed(() =>
|
||
dictList.find(item => item.varName === currentVarName.value)
|
||
);
|
||
const currentValues = computed({
|
||
get: () => currentDict.value?.varValue || [],
|
||
set: (val) => {
|
||
if (currentDict.value) {
|
||
currentDict.value.varValue = val;
|
||
}
|
||
}
|
||
});
|
||
|
||
// 处理字典项变化
|
||
const handleDictChange = (val) => {
|
||
if (!val) return;
|
||
};
|
||
// 判断是否是区域字典
|
||
const isRegionDict = computed(() => currentVarName.value === '区域');
|
||
|
||
// 修改区域数据计算属性(移除setter)
|
||
const regionData = computed(() => {
|
||
if (!isRegionDict.value) return [];
|
||
const regionDict = dictList.find(d => d.varName === '区域');
|
||
return Object.entries(regionDict?.varValue || {}).map(([key, value]) => ({
|
||
key,
|
||
value: Array.isArray(value) ? value : [value]
|
||
}));
|
||
});
|
||
|
||
// 显示省份选择对话框
|
||
const showProvinceDialog = (row) => {
|
||
currentEditingRegion.value = row;
|
||
selectedProvinces.value = [...row.value];
|
||
provinceDialogVisible.value = true;
|
||
};
|
||
|
||
// 修改省份选择确认方法
|
||
const confirmProvinceSelection = async () => {
|
||
try {
|
||
if (!currentEditingRegion.value) return;
|
||
|
||
// 获取当前编辑的大区信息
|
||
const currentKey = currentEditingRegion.value.key;
|
||
const selectedProvincesList = selectedProvinces.value;
|
||
|
||
// 获取区域字典数据
|
||
const regionDict = dictList.find(d => d.varName === '区域');
|
||
if (!regionDict) return;
|
||
|
||
// 收集所有其他大区的省份映射
|
||
const provinceMap = new Map();
|
||
Object.entries(regionDict.varValue).forEach(([region, provinces]) => {
|
||
if (region !== currentKey) {
|
||
provinces.forEach(province => {
|
||
provinceMap.set(province, region);
|
||
});
|
||
}
|
||
});
|
||
|
||
// 检查重复省份
|
||
const duplicates = [];
|
||
selectedProvincesList.forEach(province => {
|
||
if (provinceMap.has(province)) {
|
||
duplicates.push({
|
||
province,
|
||
region: provinceMap.get(province)
|
||
});
|
||
}
|
||
});
|
||
|
||
// 如果有重复则提示
|
||
if (duplicates.length > 0) {
|
||
const confirmMessage = `
|
||
以下省份已在其他大区存在:<br>
|
||
${duplicates.map(d => `${d.province} → ${d.region}`).join('<br>')}<br>
|
||
是否确认继续保存?
|
||
`;
|
||
|
||
await ElMessageBox.confirm(confirmMessage, '重复省份警告', {
|
||
confirmButtonText: '强制保存',
|
||
cancelButtonText: '返回修改',
|
||
type: 'warning',
|
||
dangerouslyUseHTMLString: true
|
||
});
|
||
}
|
||
|
||
// 执行保存操作
|
||
regionDict.varValue = {
|
||
...regionDict.varValue,
|
||
[currentKey]: selectedProvincesList
|
||
};
|
||
provinceDialogVisible.value = false;
|
||
} catch (error) {
|
||
if (error !== 'cancel') {
|
||
console.error(error);
|
||
}
|
||
return; // 用户取消则中断流程
|
||
}
|
||
};
|
||
/* const confirmProvinceSelection = () => {
|
||
if (currentEditingRegion.value) {
|
||
const regionDict = dictList.find(d => d.varName === '区域');
|
||
if (regionDict) {
|
||
regionDict.varValue = {
|
||
...regionDict.varValue,
|
||
[currentEditingRegion.value.key]: selectedProvinces.value
|
||
};
|
||
}
|
||
}
|
||
provinceDialogVisible.value = false;
|
||
};*/
|
||
|
||
// 修改添加大区方法
|
||
const addNewRegion = () => {
|
||
const regionDict = dictList.find(d => d.varName === '区域');
|
||
if (!regionDict) return;
|
||
|
||
// 生成唯一键名
|
||
let newKey = "新增大区";
|
||
let counter = 1;
|
||
while (regionDict.varValue[newKey]) {
|
||
newKey = `新增大区${counter++}`;
|
||
}
|
||
|
||
// 直接修改原始数据
|
||
regionDict.varValue = {
|
||
...regionDict.varValue,
|
||
[newKey]: []
|
||
};
|
||
};
|
||
|
||
// 修改删除大区方法
|
||
const deleteRegion = (index) => {
|
||
ElMessageBox.confirm('确定要删除该大区吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
const regionDict = dictList.find(d => d.varName === '区域');
|
||
if (!regionDict) return;
|
||
|
||
const keyToDelete = regionData.value[index].key;
|
||
const newValue = { ...regionDict.varValue };
|
||
delete newValue[keyToDelete];
|
||
regionDict.varValue = newValue;
|
||
});
|
||
};
|
||
|
||
// 添加行
|
||
const handleAdd = () => {
|
||
if (!currentDict.value) return;
|
||
currentDict.value.varValue.push('');
|
||
};
|
||
|
||
// 删除行
|
||
const handleDelete = (index) => {
|
||
ElMessageBox.confirm('确定要删除该行吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
currentDict.value.varValue.splice(index, 1);
|
||
});
|
||
};
|
||
|
||
// 保存字典
|
||
const saveDic = async (name, value) => {
|
||
try {
|
||
let filteredValue;
|
||
|
||
// 根据数据类型处理
|
||
if (Array.isArray(value)) {
|
||
// 处理普通数组格式
|
||
filteredValue = value
|
||
.map(item => String(item).trim())
|
||
.filter(item => item !== "");
|
||
|
||
if (filteredValue.length === 0) {
|
||
throw new Error("不能保存空字典项");
|
||
}
|
||
} else if (typeof value === 'object' && value !== null) {
|
||
// 处理区域字典的对象格式
|
||
filteredValue = Object.entries(value).reduce((acc, [key, values]) => {
|
||
const filtered = values
|
||
.map(item => String(item).trim())
|
||
.filter(item => item !== "");
|
||
|
||
// 保留key即使值为空数组(如"其他")
|
||
if (key === '其他' || filtered.length > 0) {
|
||
acc[key] = filtered;
|
||
}
|
||
return acc;
|
||
}, {});
|
||
|
||
// 检查是否为空对象
|
||
if (Object.keys(filteredValue).length === 0) {
|
||
throw new Error("至少需要保留一个有效区域");
|
||
}
|
||
} else {
|
||
throw new Error("无效的字典格式");
|
||
}
|
||
|
||
const valueJs = JSON.stringify(filteredValue);
|
||
const rs = await $http.post('ConfigPY/SaveOrAddConfigString', {
|
||
VarName: name,
|
||
VarValue: valueJs
|
||
});
|
||
|
||
return filteredValue;
|
||
} catch (error) {
|
||
console.error(error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 保存处理
|
||
const handleSave = async () => {
|
||
if (!currentDict.value) {
|
||
ElMessage.warning('请先选择要修改的字典项');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
'此操作将永久修改字典数据,是否继续?',
|
||
'警告',
|
||
{
|
||
confirmButtonText: '确认保存',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
center: true,
|
||
dangerouslyUseHTMLString: true,
|
||
beforeClose: (action, instance, done) => {
|
||
if (action === 'confirm') {
|
||
instance.confirmButtonLoading = true;
|
||
saveDic(currentDict.value.varName, currentDict.value.varValue)
|
||
.then(() => {
|
||
done();
|
||
ElMessage.success('保存成功');
|
||
})
|
||
.catch(() => {
|
||
instance.confirmButtonLoading = false;
|
||
ElMessage.error('保存失败');
|
||
});
|
||
} else {
|
||
done();
|
||
}
|
||
}
|
||
}
|
||
);
|
||
// 处理区域字典的特殊结构
|
||
let saveData = currentDict.value.varValue;
|
||
|
||
if (isRegionDict.value) {
|
||
// 转换回原始对象格式
|
||
saveData = regionData.value.reduce((acc, cur) => {
|
||
acc[cur.key] = cur.value;
|
||
return acc;
|
||
}, {});
|
||
}
|
||
|
||
// 保存并获取处理后的数据
|
||
const filteredData = await saveDic(
|
||
currentDict.value.varName,
|
||
saveData
|
||
);
|
||
|
||
// 更新前端数据
|
||
currentDict.value.varValue = isRegionDict.value
|
||
? { ...filteredData } // 对象格式
|
||
: [...filteredData]; // 数组格式
|
||
|
||
} catch (error) {
|
||
if (error === 'cancel') {
|
||
//console.log('用户取消保存');
|
||
} else if (error.message === '不能保存空字典项') {
|
||
ElMessage.warning('字典项至少需要包含一个有效值');
|
||
} else {
|
||
ElMessage.error('保存失败: ' + error.message);
|
||
}
|
||
}
|
||
};
|
||
|
||
onMounted(() => {
|
||
getDic()
|
||
// 初始化选择第一个
|
||
if (dictList.length > 0) {
|
||
currentVarName.value = dictList[0].varName;
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
display: block;
|
||
padding: 20px;
|
||
width: 100%;
|
||
}
|
||
|
||
.header {
|
||
width: 333px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.footer {
|
||
width: 777px;
|
||
display: flex;
|
||
justify-content: space-between; /* 推荐方案 */
|
||
gap: 10px;
|
||
margin-top: 20px;
|
||
}
|
||
.el-message-box {
|
||
max-height: 70vh;
|
||
overflow-y: auto;
|
||
}
|
||
</style>
|