Files
klp-oa/klp-ui/src/components/DictSelect/index.vue

365 lines
12 KiB
Vue
Raw Normal View History

<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>