2026-01-17 14:51:01 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div style="display: flex; align-items: center;" v-loading="loading">
|
|
|
|
|
|
<!-- 下拉选择器:绑定计算属性做双向绑定,保留原有样式+清空功能 -->
|
2026-01-21 16:24:57 +08:00
|
|
|
|
<el-select
|
|
|
|
|
|
v-model="innerValue"
|
|
|
|
|
|
:placeholder="placeholder"
|
|
|
|
|
|
clearable filterable
|
|
|
|
|
|
style="width: 200px;"
|
|
|
|
|
|
:multiple="multiple" collapse-tags>
|
2026-01-17 14:51:01 +08:00
|
|
|
|
<el-option
|
2026-03-02 14:11:47 +08:00
|
|
|
|
v-for="item in disabledOptions"
|
2026-01-17 14:51:01 +08:00
|
|
|
|
:key="item.dictValue"
|
|
|
|
|
|
:label="item.dictLabel"
|
|
|
|
|
|
:value="item.dictValue"
|
2026-03-02 14:11:47 +08:00
|
|
|
|
:disabled="item.disabled"
|
2026-01-17 14:51:01 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 编辑按钮:点击打开弹窗 -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="editable"
|
|
|
|
|
|
@click="openDictDialog"
|
|
|
|
|
|
style="cursor: pointer; height: 24px; 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"
|
|
|
|
|
|
@click="handleRefresh"
|
|
|
|
|
|
style="cursor: pointer; height: 24px; 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="字典数据配置"
|
|
|
|
|
|
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>
|
2026-01-21 16:24:57 +08:00
|
|
|
|
import { addData, updateData, delData, listData } from '@/api/system/dict/data'
|
2026-01-17 14:51:01 +08:00
|
|
|
|
import { listType } from '@/api/system/dict/type'
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'DictSelectEdit',
|
|
|
|
|
|
props: {
|
|
|
|
|
|
dictType: { type: String, default: '' },
|
|
|
|
|
|
editable: { type: Boolean, default: true },
|
2026-03-02 14:11:47 +08:00
|
|
|
|
kisv: { type: Boolean, default: true },
|
2026-01-17 14:51:01 +08:00
|
|
|
|
value: { type: String, default: '' },
|
|
|
|
|
|
placeholder: { type: String, default: '请选择' },
|
|
|
|
|
|
refresh: { type: Boolean, default: true },
|
2026-01-21 16:24:57 +08:00
|
|
|
|
multiple: { type: Boolean, default: false },
|
2026-03-02 14:11:47 +08:00
|
|
|
|
disables: { type: String, default: '' },
|
2026-01-17 14:51:01 +08:00
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
dictOptions: [],
|
|
|
|
|
|
dictId: '',
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
btnLoading: false,
|
|
|
|
|
|
delBtnLoading: '',
|
|
|
|
|
|
open: false,
|
|
|
|
|
|
editRowId: '', // 控制当前编辑行,为空则所有单元格都是文本状态
|
|
|
|
|
|
form: {
|
|
|
|
|
|
dictId: '',
|
|
|
|
|
|
dictLabel: '',
|
|
|
|
|
|
dictValue: '',
|
|
|
|
|
|
dictType: '',
|
|
|
|
|
|
sort: 0
|
|
|
|
|
|
},
|
|
|
|
|
|
dictRules: {
|
|
|
|
|
|
dictLabel: [{ required: true, message: '请输入字典标签', trigger: 'blur' }],
|
|
|
|
|
|
dictValue: [{ required: true, message: '请输入字典值', trigger: 'blur' }]
|
|
|
|
|
|
},
|
|
|
|
|
|
dictFormRef: null
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
innerValue: {
|
2026-01-21 16:24:57 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-02 14:11:47 +08:00
|
|
|
|
},
|
|
|
|
|
|
disabledOptions() {
|
|
|
|
|
|
return this.dictOptions.map(item => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
disabled: this.disabledFormat(item.dictValue)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
2026-01-17 14:51:01 +08:00
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
dictType: {
|
|
|
|
|
|
async handler(newVal) {
|
|
|
|
|
|
if (newVal) {
|
|
|
|
|
|
this.loading = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const dictId = await this.getDictId(newVal)
|
|
|
|
|
|
await this.getDictOptions(dictId)
|
|
|
|
|
|
} 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: {
|
|
|
|
|
|
async getDictId(type) {
|
|
|
|
|
|
const res = await listType({ dictType: type })
|
|
|
|
|
|
if (res.rows?.length !== 1) {
|
|
|
|
|
|
this.$message.error('字典类型异常,未查询到对应配置')
|
|
|
|
|
|
return Promise.reject('字典类型异常')
|
|
|
|
|
|
}
|
|
|
|
|
|
this.dictId = res.rows[0].dictId
|
|
|
|
|
|
return this.dictId
|
|
|
|
|
|
},
|
2026-03-02 14:11:47 +08:00
|
|
|
|
disabledFormat(item) {
|
|
|
|
|
|
if (this.disables) {
|
|
|
|
|
|
const list = this.disables.split(',')
|
|
|
|
|
|
return list.includes(item)
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
},
|
2026-01-17 14:51:01 +08:00
|
|
|
|
// 新增:刷新字典数据
|
|
|
|
|
|
async handleRefresh() {
|
|
|
|
|
|
this.loading = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
await this.getDictOptions(this.dictId)
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('刷新字典失败:', err)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
async getDictOptions(dictId) {
|
|
|
|
|
|
const res = await listData({ dictType: this.dictType, pageSize: 1000 })
|
|
|
|
|
|
this.dictOptions = res.rows || []
|
|
|
|
|
|
return this.dictOptions
|
|
|
|
|
|
},
|
|
|
|
|
|
openDictDialog() {
|
|
|
|
|
|
this.open = true
|
|
|
|
|
|
this.resetDictForm()
|
|
|
|
|
|
this.form.dictId = this.dictId
|
|
|
|
|
|
this.form.dictType = this.dictType
|
|
|
|
|
|
this.editRowId = '' // 打开弹窗重置编辑状态
|
|
|
|
|
|
},
|
|
|
|
|
|
resetDictForm() {
|
|
|
|
|
|
this.form = { dictId: this.dictId, dictLabel: '', dictValue: '', dictType: this.dictType, sort: 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.getDictOptions(this.dictId)
|
|
|
|
|
|
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.getDictOptions(this.dictId)
|
|
|
|
|
|
this.editRowId = '' // 校验失败,也必须还原单元格
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
row.sort = 0
|
|
|
|
|
|
this.loading = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
await updateData(row)
|
|
|
|
|
|
this.$message.success('字典项修改成功!')
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
this.$message.error('修改失败,请稍后重试')
|
|
|
|
|
|
await this.getDictOptions(this.dictId)
|
|
|
|
|
|
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.getDictOptions(this.dictId)
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
this.$message.error('删除失败,请稍后重试')
|
|
|
|
|
|
console.error(err)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
this.delBtnLoading = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|