Files
his/openhis-ui-vue3/src/views/doctorstation/doctorphrase/index.vue
HuangXinQuan 50ceb98e83 87 门诊医生站-》增加医生常用语维护界面
医生常用语这个实体类继承了基类HisBaseEntity,使用里面的delete_flag的字段进行逻辑删除,保留了enable_flag字段,方便后续实现启用和禁用的功能
2026-02-11 14:56:13 +08:00

534 lines
18 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 class="doctor-phrase-container">
<!-- 搜索栏 -->
<div class="search-bar">
<el-select v-model="searchScope" placeholder="范围" style="width: 120px;">
<el-option label="个人" :value="1"></el-option>
<el-option label="科室" :value="2"></el-option>
<el-option label="全院" :value="3"></el-option>
</el-select>
<el-input v-model="searchKeyword" placeholder="请输入名称" style="width: 240px; margin-left: 10px;"
@keyup.enter="handleSearch"></el-input>
<el-button style="margin-left: 10px;" @click="handleSearch"><el-icon>
<Search />
</el-icon> 查询</el-button>
<el-button style="margin-left: 10px;" @click="handleReset"><el-icon>
<Refresh />
</el-icon> 重置</el-button>
<el-button type="primary" style="margin-left: 10px;" @click="showAddDialog"><el-icon>
<Plus />
</el-icon> 增加</el-button>
</div>
<!-- 表格 -->
<el-table :data="tableData" style="width: 100%; margin-top: 20px;">
<el-table-column prop="sortNo" label="排序号" width="200">
</el-table-column>
<el-table-column prop="phraseName" label="名称" width="250">
</el-table-column>
<el-table-column prop="phraseContent" label="内容">
</el-table-column>
<el-table-column label="范围" width="250">
<template #default="scope">
{{ getScopeName(scope.row.phraseType) }}
</template>
</el-table-column>
<!-- 业务分类列使用枚举转换中文名称 -->
<el-table-column label="业务分类" width="200">
<template #default="scope">
{{ getBusinessTypeName(scope.row.phraseCategory) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" @click="showEditDialog(scope.row)">
编辑
</el-button>
<el-button type="danger" size="small" style="margin-left: 5px;" @click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage"
:page-sizes="[15, 30, 50]" :page-size="pageSize" layout="total, prev, pager, next, jumper, sizes"
:total="total"></el-pagination>
</div>
<!-- 新增模态框 -->
<el-dialog v-model="addDialogVisible" title="新增医生常用语" width="600px" center>
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="100px" class="add-form">
<el-form-item label="名称" prop="phraseName">
<el-input v-model="addForm.phraseName" placeholder="请输入常用语名称(不超过50字)" maxlength="50" show-word-limit
style="width: 100%;"></el-input>
</el-form-item>
<el-form-item label="内容" prop="phraseContent">
<el-input v-model="addForm.phraseContent" placeholder="请输入常用语内容" type="textarea" :rows="4"
style="width: 100%;"></el-input>
</el-form-item>
<el-form-item label="排序号">
<el-input-number v-model="addForm.sortNo" :min="1" :step="1" style="width: 100%;"></el-input-number>
</el-form-item>
<el-form-item label="业务分类" prop="phraseCategory">
<el-select v-model="addForm.phraseCategory" placeholder="请选择业务分类" style="width: 100%;">
<el-option v-for="item in businessTypeOptions" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="范围" prop="phraseType">
<el-select v-model="addForm.phraseType" placeholder="请选择范围" style="width: 100%;">
<el-option label="个人" :value="1"></el-option>
<el-option label="科室" :value="2"></el-option>
<el-option label="全院" :value="3"></el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleAdd">确定</el-button>
</span>
</template>
</el-dialog>
<!-- 编辑模态框 -->
<el-dialog v-model="editDialogVisible" title="编辑医生常用语" width="600px" center>
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="100px" class="add-form">
<el-form-item label="名称" prop="phraseName">
<el-input v-model="editForm.phraseName" placeholder="请输入常用语名称(不超过50字)" maxlength="50" show-word-limit
style="width: 100%;"></el-input>
</el-form-item>
<el-form-item label="内容" prop="phraseContent">
<el-input v-model="editForm.phraseContent" placeholder="请输入常用语内容" type="textarea" :rows="4"
style="width: 100%;"></el-input>
</el-form-item>
<el-form-item label="排序号">
<el-input-number v-model="editForm.sortNo" :min="1" :step="1" style="width: 100%;"></el-input-number>
</el-form-item>
<el-form-item label="业务分类" prop="phraseCategory">
<el-select v-model="editForm.phraseCategory" placeholder="请选择业务分类" style="width: 100%;">
<el-option v-for="item in businessTypeOptions" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="范围" prop="phraseType">
<el-select v-model="editForm.phraseType" placeholder="请选择范围" style="width: 100%;">
<el-option label="个人" :value="1"></el-option>
<el-option label="科室" :value="2"></el-option>
<el-option label="全院" :value="3"></el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleEditSave">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { Check, Close, Plus, Refresh, Search } from '@element-plus/icons-vue'
import {
addDoctorPhrase,
deleteDoctorPhrase,
getDoctorPhraseList,
searchDoctorPhraseList,
updateDoctorPhrase
} from './api'
import { ElMessage, ElMessageBox } from 'element-plus'
// 如果数据字典未对齐后端枚举暂时注释useDict改用固定枚举后续可替换为dict
// import { useDict } from '@/utils/dict'
// 核心配置定义和后端枚举对齐的业务分类选项替换原businessclassification
const businessTypeOptions = ref([
{ label: '主诉', value: 'MAIN_COMPLAINT' },
{ label: '现病史', value: 'PRESENT_HISTORY' },
{ label: '术前', value: 'PRE_OPERATION' },
{ label: '术后', value: 'POST_OPERATION' },
{ label: '既往史', value: 'PAST_HISTORY' }
])
// 搜索条件
const searchScope = ref(null) // null表示未选择数字类型1=个人2=科室3=全院
const searchKeyword = ref('')
// 表格数据
const tableData = ref([])
// 分页参数配置
const currentPage = ref(1)
const pageSize = ref(15)
const total = ref(0)
// 新增模态框相关配置
const addDialogVisible = ref(false)
const addFormRef = ref()
// 新增表单默认值:业务分类默认空,强制用户选择
const addForm = ref({
phraseName: '',
phraseContent: '',
sortNo: 1,
phraseType: 1,
phraseCategory: ''
})
// 完善新增表单验证规则补充业务分类必填、名称50字限制
const addRules = {
phraseName: [
{ required: true, message: '请输入常用语名称', trigger: 'blur' },
{ max: 50, message: '常用语名称不能超过50字', trigger: 'blur' }
],
phraseContent: [
{ required: true, message: '请输入常用语内容', trigger: 'blur' }
],
phraseType: [
{ required: true, message: '请选择范围', trigger: 'change' }
],
phraseCategory: [
{ required: true, message: '请选择业务分类', trigger: 'change' }
]
}
// 编辑模态框相关配置
const editDialogVisible = ref(false)
const editFormRef = ref()
// 编辑表单默认值配置
const editForm = ref({
id: '',
phraseName: '',
phraseContent: '',
sortNo: 1,
phraseType: 1,
phraseCategory: ''
})
// 完善编辑表单验证规则(和新增一致,保证校验统一)
const editRules = {
phraseName: [
{ required: true, message: '请输入常用语名称', trigger: 'blur' },
{ max: 50, message: '常用语名称不能超过50字', trigger: 'blur' }
],
phraseContent: [
{ required: true, message: '请输入常用语内容', trigger: 'blur' }
],
phraseType: [
{ required: true, message: '请选择范围', trigger: 'change' }
],
phraseCategory: [
{ required: true, message: '请选择业务分类', trigger: 'change' }
]
}
// 重构业务分类名称转换方法(适配后端编码→中文展示)
const getBusinessTypeName = (code) => {
if (!code) return '未知类型'
const item = businessTypeOptions.value.find(item => item.value === code)
return item ? item.label : '未知类型'
}
// 获取范围名称转换方法
const getScopeName = (scope) => {
const scopeMap = {
1: '个人',
2: '科室',
3: '全院'
}
return scopeMap[scope] || '未知范围'
}
// 名称唯一性校验函数(新增/编辑通用编辑时排除自身ID
const validatePhraseName = (phraseName, excludeId = null) => {
if (!phraseName || !phraseName.trim()) {
return { valid: false, message: '请输入常用语名称' }
}
// 检查字数限制严格控制50字
if (phraseName.trim().length > 50) {
return { valid: false, message: '常用语名称不能超过50字' }
}
// 检查名称是否已存在,编辑时排除当前记录
const existingPhrase = allData.value.find(item => {
if (excludeId && item.id === excludeId) {
return false
}
return item.phraseName.trim() === phraseName.trim()
})
if (existingPhrase) {
return { valid: false, message: '常用语名称已存在,请输入不同的名称' }
}
return { valid: true, message: '' }
}
// 所有数据(用于客户端分页处理)
const allData = ref([])
// 获取医生常用语列表数据
// 获取医生常用语列表数据
const fetchDoctorPhraseList = async () => {
try {
const response = await getDoctorPhraseList()
// 【关键修改】去掉 response.data.data直接取 response.data
if (response.code === 200 && response.data) {
// 按照sortNo由小到大排序保证列表顺序正确
allData.value = response.data.sort((a, b) => a.sortNo - b.sortNo)
total.value = allData.value.length
// 执行客户端分页逻辑
applyPagination()
} else {
ElMessage.error('获取数据失败: ' + (response.msg || '未知错误'))
allData.value = []
total.value = 0
}
} catch (error) {
console.error('获取列表失败:', error)
ElMessage.error('获取数据失败: 网络请求错误')
allData.value = []
total.value = 0
}
}
// 重置功能方法
const handleReset = () => {
// 重置搜索条件
searchScope.value = null
searchKeyword.value = ''
// 重置分页到第一页
currentPage.value = 1
// 重新加载所有数据
fetchDoctorPhraseList()
}
// 客户端分页处理核心方法
const applyPagination = () => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
tableData.value = allData.value.slice(start, end)
}
// 分页条数改变事件
const handleSizeChange = (val) => {
pageSize.value = val
applyPagination()
}
// 分页页码改变事件
const handleCurrentChange = (val) => {
currentPage.value = val
applyPagination()
}
// 搜索功能核心方法
// 搜索功能核心方法
const handleSearch = async () => {
try {
const phraseType = searchScope.value === null ? undefined : searchScope.value
const response = await searchDoctorPhraseList(searchKeyword.value, phraseType)
// 【关键修改】去掉 response.data.data直接取 response.data
if (response.code === 200 && response.data) {
allData.value = response.data.sort((a, b) => a.sortNo - b.sortNo)
total.value = allData.value.length
currentPage.value = 1
applyPagination()
} else {
ElMessage.error('搜索失败: ' + (response.msg || '未知错误'))
allData.value = []
total.value = 0
}
} catch (error) {
console.error('搜索失败:', error)
ElMessage.error('搜索失败: 网络请求错误')
allData.value = []
total.value = 0
}
}
// 打开新增模态框方法
// index.vue
const showAddDialog = () => {
// 1. 算出当前最大的排序号
// 如果列表是空的,就从 1 开始;如果不空,取第一条(因为我们排过序了)或遍历找最大值
let maxSortNo = 0
if (allData.value && allData.value.length > 0) {
// 既然 allData 已经按 sortNo 排序了,那最后一个就是最大的?
// 或者保险起见,用 Math.max 算一下
maxSortNo = Math.max(...allData.value.map(item => item.sortNo || 0))
}
// 2. 重置表单,并将排序号设为 最大值 + 1
addForm.value = {
phraseName: '',
phraseContent: '',
sortNo: maxSortNo + 1, // <--- 这样每次打开就是 2, 3, 4...
phraseType: 1,
phraseCategory: ''
}
if (addFormRef.value) {
addFormRef.value.clearValidate()
}
addDialogVisible.value = true
}
// 提交新增表单方法
const handleAdd = async () => {
try {
// 先执行表单验证
const validateResult = await addFormRef.value.validate()
if (!validateResult) return
// 名称唯一性校验
const nameValidation = validatePhraseName(addForm.value.phraseName)
if (!nameValidation.valid) {
ElMessage.error(nameValidation.message)
return
}
// 添加数据库要求的必填默认字段
const formData = {
...addForm.value,
phraseCode: 'PHR000', // 建议后端自动生成,前端可传空
enableFlag: 1
// 不手动设置 createTime、updateTime 等审计字段,让后端自动填充
}
// 调用新增接口
const response = await addDoctorPhrase(formData)
// 处理新增结果
if (response.code === 200) {
ElMessage.success('新增成功')
addDialogVisible.value = false
// 重新拉取数据比手动unshift更可靠避免数据不一致
fetchDoctorPhraseList()
} else {
ElMessage.error('新增失败: ' + (response.msg || '未知错误'))
}
} catch (error) {
console.error('新增失败:', error)
// 表单验证失败或其他错误
ElMessage.error('新增失败: 请填写完整信息或网络异常')
}
}
// 删除功能方法
const handleDelete = async (row) => {
try {
// 弹出确认对话框
await ElMessageBox.confirm(
'确定要删除该常用语吗?删除后无法恢复!',
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
// 调用删除接口
const response = await deleteDoctorPhrase(row.id)
if (response.code === 200) {
ElMessage.success('删除成功')
// 重新拉取数据
fetchDoctorPhraseList()
} else {
ElMessage.error('删除失败: ' + (response.msg || '未知错误'))
}
} catch (error) {
// 用户取消删除时不提示错误
if (error !== 'cancel') {
console.error('删除失败:', error)
}
}
}
// 打开编辑弹窗核心方法 - 点击表格编辑按钮触发
const showEditDialog = (row) => {
// 深拷贝当前行数据到编辑表单,避免原数据被修改
editForm.value = JSON.parse(JSON.stringify(row))
// 确保数字类型正确,防止表单赋值异常
editForm.value.sortNo = Number(editForm.value.sortNo) || 1
editForm.value.phraseType = Number(editForm.value.phraseType) || 1
// 重置表单验证状态
if (editFormRef.value) {
editFormRef.value.clearValidate()
}
// 打开编辑弹窗
editDialogVisible.value = true
}
// 编辑表单提交保存方法
// 修改 index.vue 中的 handleEditSave 方法
const handleEditSave = async () => {
try {
// 1. 表单校验
const validateResult = await editFormRef.value.validate()
if (!validateResult) return
// 2. 名称唯一性校验
const nameValidation = validatePhraseName(editForm.value.phraseName, editForm.value.id)
if (!nameValidation.valid) {
ElMessage.error(nameValidation.message)
return
}
// 3. 准备数据
const updateData = {
...editForm.value,
enableFlag: 1
// 不手动设置 updateTime让后端自动填充
}
// 4. 调用接口
const response = await updateDoctorPhrase(updateData)
// 【核心修改】直接判断 code === 200 即可
// 因为后端现在失败会返回 R.fail (code!=200),所以只要是 200 就是成功
if (response.code === 200) {
ElMessage.success(response.msg || '更新成功') // 优先显示后端返回的消息
editDialogVisible.value = false
fetchDoctorPhraseList()
} else {
ElMessage.error(response.msg || '更新失败')
}
} catch (error) {
console.error('更新失败:', error)
}
}
// 组件挂载时初始化加载数据
onMounted(() => {
fetchDoctorPhraseList()
})
</script>
<style scoped>
.doctor-phrase-container {
padding: 20px;
}
.search-bar {
display: flex;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
/* 适配小屏幕,防止按钮换行溢出 */
gap: 10px;
/* 替换margin-left更优雅的间距 */
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
/* 新增表单样式适配 */
.add-form {
padding: 10px 0;
}
</style>