Files
klp-oa/klp-ui/src/components/DictSelect/index.vue
王文昊 5a56094e4f refactor(dict): 优化字典数据查询逻辑和接口
- 移除不必要的 ISysDictTypeService 依赖,简化 SysDictDataController
- 新增 selectDictDataByTypeRealtime 方法,支持实时查询字典数据,避免缓存问题
- 更新 SysDictDataController 中的字典数据查询逻辑,使用新方法
- 在 SysDictTypeController 中添加按字典类型编码精确查询的接口
- 更新前端组件以支持新的字典查询接口,优化字典选择器的加载逻辑
2026-04-28 19:12:50 +08:00

365 lines
12 KiB
Vue
Raw 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>
<div style="display: flex; align-items: center;" v-loading="loading">
<!-- 下拉选择器绑定计算属性做双向绑定保留原有样式+清空功能 -->
<el-select
v-if="!toolbarOnly"
v-model="innerValue"
:placeholder="placeholder"
clearable filterable
style="width: 200px;"
:multiple="multiple" collapse-tags>
<el-option
v-for="item in disabledOptions"
:key="item.dictValue"
:label="item.dictLabel"
:value="item.dictValue"
:disabled="item.disabled"
/>
</el-select>
<!-- 编辑按钮点击打开弹窗 -->
<div
v-if="editable"
@click="openDictDialog"
:style="toolbarOnly ? 'cursor: pointer; min-height: 24px; min-width: 24px; margin-top: 4px; display: flex; align-items: center; justify-content: center; border: 1px solid #828991; border-radius: 2px;' : 'cursor: pointer; min-height: 24px; min-width: 24px; margin-top: 4px; display: flex; align-items: center; justify-content: center; border: 1px solid #828991; margin-left: 8px; border-radius: 2px;'"
>
<i class="el-icon-setting"></i>
</div>
<div
v-if="refresh && !toolbarOnly"
@click="handleRefresh"
style="cursor: pointer; min-height: 24px; min-width: 24px; margin-top: 4px; display: flex; align-items: center; justify-content: center; border: 1px solid #828991; margin-left: 8px; border-radius: 2px;"
>
<i class="el-icon-refresh"></i>
</div>
<!-- 字典编辑弹窗 -->
<el-dialog
v-if="editable"
:visible.sync="open"
:title="panelTitle || '字典数据配置'"
width="600px"
append-to-body
>
<el-form
ref="dictFormRef"
:model="form"
:rules="dictRules"
label-width="68px"
label-position="left"
style="margin-bottom: 20px;"
>
<el-row :gutter="15">
<el-col :span="kisv ? 24 : 12">
<el-form-item label="值/标签" prop="dictValue" v-if="kisv">
<el-input v-model="form.dictValue" placeholder="请输入字典值,在此处输入用于新增字典项" />
</el-form-item>
<el-form-item label="字典标签" prop="dictLabel" v-else>
<el-input v-model="form.dictLabel" placeholder="请输入字典标签" />
</el-form-item>
</el-col>
<el-col :span="12" v-if="!kisv">
<el-form-item label="字典值" prop="dictValue">
<el-input v-model="form.dictValue" placeholder="请输入字典值" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div style="margin-bottom: 20px;">
<el-button type="primary" size="small" @click="submitAddDict" :loading="btnLoading">新增字典项</el-button>
<el-button size="small" @click="resetDictForm">清空表单</el-button>
<span style="margin-left: 10px;">提示双击单元格中的文字可以快速编辑</span>
</div>
<!-- 字典列表 核心修改双击单元格激活输入框失焦还原文本 -->
<el-table
:data="dictOptions"
border
stripe
size="mini"
style="width: 100%;"
empty-text="暂无字典数据"
>
<!-- 字典标签列kisv=true隐藏false正常展示 -->
<el-table-column label="字典标签" align="center" v-if="!kisv">
<template #default="scope">
<!-- 双击span触发编辑失焦/回车后变回span -->
<span
v-if="editRowId !== scope.row.dictCode"
@dblclick.stop="handleDbEdit(scope.row)"
style="cursor: pointer;"
>
{{ scope.row.dictLabel }}
</span>
<el-input
v-else
v-model="scope.row.dictLabel"
size="mini"
style="width: 100%;"
@change="handleSaveRow(scope.row)"
auto-focus
/>
</template>
</el-table-column>
<!-- 字典值列kisv=true时标题合并 -->
<el-table-column :label="kisv ? '字典值/标签' : '字典值'" align="center">
<template #default="scope">
<!-- 核心默认文本双击才显示输入框失焦立刻消失 -->
<span
v-if="editRowId !== scope.row.dictCode"
@dblclick.stop="handleDbEdit(scope.row)"
style="cursor: pointer;"
>
{{ scope.row.dictValue }}
</span>
<el-input
v-else
v-model="scope.row.dictValue"
size="mini"
style="width: 100%;"
@input="() => handleKisvTableSync(scope.row)"
@change="handleSaveRow(scope.row)"
auto-focus
/>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template #default="scope">
<el-button
icon="el-icon-delete"
size="mini"
type="text"
text-danger
@click="handleDelete(scope.row)"
:loading="delBtnLoading === scope.row.dictCode"
>删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import { addData, updateData, delData, getDicts } from '@/api/system/dict/data'
export default {
name: 'DictSelectEdit',
props: {
dictType: { type: String, default: '' },
editable: { type: Boolean, default: true },
/** 仅展示字典配置入口(齿轮)与弹窗,不渲染下拉框 — 用于列表页 Tab 旁内联维护字典 */
toolbarOnly: { type: Boolean, default: false },
/** 字典配置弹窗标题 */
panelTitle: { type: String, default: '' },
kisv: { type: Boolean, default: true },
value: { type: String, default: '' },
placeholder: { type: String, default: '请选择' },
refresh: { type: Boolean, default: true },
multiple: { type: Boolean, default: false },
disables: { type: String, default: '' },
},
data() {
return {
dictOptions: [],
loading: false,
btnLoading: false,
delBtnLoading: '',
open: false,
editRowId: '', // 控制当前编辑行,为空则所有单元格都是文本状态
form: {
dictLabel: '',
dictValue: '',
dictType: '',
dictSort: 0,
status: '0'
},
dictRules: {
dictLabel: [{ required: true, message: '请输入字典标签', trigger: 'blur' }],
dictValue: [{ required: true, message: '请输入字典值', trigger: 'blur' }]
},
dictFormRef: null
}
},
computed: {
innerValue: {
get() {
if (this.multiple) {
if (!this.value) return []
return this.value?.split(',') || []
}
return this.value
},
set(val) {
if (this.multiple) {
this.$emit('input', val?.join(',') || '')
this.$emit('change', val)
} else {
this.$emit('input', val)
this.$emit('change', val)
}
}
},
disabledOptions() {
return this.dictOptions.map(item => {
return {
...item,
disabled: this.disabledFormat(item.dictValue)
}
})
},
},
watch: {
dictType: {
async handler(newVal) {
if (!newVal) return
// toolbarOnly仅打开弹窗时加载避免无谓请求下拉模式按 dict_type 加载选项
if (this.toolbarOnly) return
this.loading = true
try {
await this.loadDictRows()
} catch (err) {
console.error('加载字典失败:', err)
} finally {
this.loading = false
}
},
immediate: true
},
'form.dictValue': {
handler(val) {
if (this.kisv && val) this.form.dictLabel = val
},
immediate: true
}
},
methods: {
/** 读字典数据行:优先 getDicts/dict/data/type/{type}),无权限点与系统字典页 list 不一致问题 */
async loadDictRows() {
const res = await getDicts(this.dictType)
this.dictOptions = res.data || []
return this.dictOptions
},
disabledFormat(item) {
if (this.disables) {
const list = this.disables.split(',')
return list.includes(item)
}
return false
},
// 新增:刷新字典数据
async handleRefresh() {
this.loading = true
try {
await this.loadDictRows()
} catch (err) {
console.error('刷新字典失败:', err)
} finally {
this.loading = false
}
},
async openDictDialog() {
this.open = true
this.editRowId = ''
this.loading = true
try {
await this.loadDictRows()
this.resetDictForm()
} catch (err) {
this.dictOptions = []
console.error('打开字典配置失败:', err)
this.$message.error('字典数据加载失败,请检查字典类型是否正确或稍后重试')
} finally {
this.loading = false
}
},
resetDictForm() {
this.form = {
dictLabel: '',
dictValue: '',
dictType: this.dictType,
dictSort: 0,
status: '0'
}
this.$refs.dictFormRef && this.$refs.dictFormRef.clearValidate()
},
handleKisvTableSync(row) {
if (this.kisv && row.dictValue) row.dictLabel = row.dictValue
},
async submitAddDict() {
const valid = await this.$refs.dictFormRef.validate().catch(() => false)
if (!valid) return
this.loading = true
this.btnLoading = true
try {
await addData(this.form)
this.$message.success('字典项新增成功!')
await this.loadDictRows()
this.$emit('dict-updated')
this.resetDictForm()
} catch (err) {
this.$message.error('新增失败,请稍后重试')
console.error(err)
} finally {
this.loading = false
this.btnLoading = false
}
},
// ✅ 新增:双击单元格 激活编辑状态 核心方法
handleDbEdit(row) {
// 同一时间只允许编辑一行,双击其他行关闭当前行编辑
this.editRowId = row.dictCode
},
// ✅ 核心完善:失去焦点/回车 保存数据 + 强制关闭编辑态 → 还原成普通单元格
async handleSaveRow(row) {
if (!row.dictLabel || !row.dictValue) {
this.$message.warning('字典标签和字典值不能为空!')
await this.loadDictRows()
this.editRowId = '' // 校验失败,也必须还原单元格
return
}
if (row.dictSort == null) row.dictSort = 0
this.loading = true
try {
await updateData(row)
this.$message.success('字典项修改成功!')
this.$emit('dict-updated')
} catch (err) {
this.$message.error('修改失败,请稍后重试')
await this.loadDictRows()
console.error(err)
} finally {
this.loading = false
this.editRowId = '' // ✅必加:保存完成,立刻关闭编辑态,变回文本
}
},
async handleDelete(row) {
const confirm = await this.$confirm('确定要删除该字典项吗?删除后不可恢复!', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).catch(() => false)
if (!confirm) return
this.loading = true
this.delBtnLoading = row.dictCode
try {
await delData(row.dictCode)
this.$message.success('删除成功!')
await this.loadDictRows()
this.$emit('dict-updated')
} catch (err) {
this.$message.error('删除失败,请稍后重试')
console.error(err)
} finally {
this.loading = false
this.delBtnLoading = ''
}
}
}
}
</script>