336 lines
11 KiB
Vue
336 lines
11 KiB
Vue
<template>
|
||
<div style="display: flex; align-items: center;" v-loading="loading">
|
||
<!-- 下拉选择器:绑定计算属性做双向绑定,保留原有样式+清空功能 -->
|
||
<el-select
|
||
v-model="innerValue"
|
||
:placeholder="placeholder"
|
||
clearable filterable
|
||
style="width: 200px;"
|
||
:multiple="multiple" collapse-tags>
|
||
<el-option
|
||
v-for="item in dictOptions"
|
||
:key="item.dictValue"
|
||
:label="item.dictLabel"
|
||
:value="item.dictValue"
|
||
/>
|
||
</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>
|
||
import { addData, updateData, delData, listData } from '@/api/system/dict/data'
|
||
import { listType } from '@/api/system/dict/type'
|
||
|
||
export default {
|
||
name: 'DictSelectEdit',
|
||
props: {
|
||
dictType: { type: String, default: '' },
|
||
editable: { type: Boolean, default: true },
|
||
kisv: { type: Boolean, default: false },
|
||
value: { type: String, default: '' },
|
||
placeholder: { type: String, default: '请选择' },
|
||
refresh: { type: Boolean, default: true },
|
||
multiple: { type: Boolean, default: false },
|
||
},
|
||
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: {
|
||
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)
|
||
}
|
||
}
|
||
}
|
||
},
|
||
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
|
||
},
|
||
// 新增:刷新字典数据
|
||
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> |