1086 lines
32 KiB
Vue
1086 lines
32 KiB
Vue
<template>
|
||
<div class="outpatient-no-management-wrapper">
|
||
<!-- Windows XP风格窗口布局,全屏显示 -->
|
||
<div class="outpatient-no-management">
|
||
<!--标题栏(32px高) -->
|
||
<div class="title-bar">
|
||
<span class="title-text">门诊号码管理</span>
|
||
</div>
|
||
|
||
<!-- 功能按钮区(40px高) -->
|
||
<div class="button-bar">
|
||
<el-row :gutter="10">
|
||
<el-col :span="1.5">
|
||
<el-button type="primary" plain icon="Plus" @click="onAdd">新设(A)</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="onDelete">删除(D)</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="success" plain icon="Check" @click="() => onSave()">保存(S)</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button type="warning" plain icon="Close" @click="onClose">关闭(X)</el-button>
|
||
</el-col>
|
||
<el-col v-if="canToggleViewAll" :span="4">
|
||
<el-switch
|
||
v-model="viewAll"
|
||
active-text="查看全部"
|
||
inactive-text="仅本人"
|
||
@change="getList"
|
||
/>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<!-- 表格内容区(自适应剩余高度) -->
|
||
<div class="table-content">
|
||
<el-table
|
||
v-loading="loading"
|
||
:data="tableData"
|
||
@selection-change="handleSelectionChange"
|
||
:row-class-name="tableRowClassName"
|
||
>
|
||
<el-table-column type="selection" width="50" align="center" />
|
||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||
<el-table-column label="操作员" prop="operatorName" min-width="120" />
|
||
<el-table-column label="员工工号" prop="staffNo" min-width="120" />
|
||
<el-table-column label="领用日期" prop="receiveDate" min-width="140">
|
||
<template #default="{ row }">
|
||
{{ formatReceiveDate(row.receiveDate) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="起始号码" prop="startNo" min-width="140" />
|
||
<el-table-column label="终止号码" prop="endNo" min-width="140" />
|
||
<el-table-column label="使用号码" prop="usedNo" min-width="140" />
|
||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button
|
||
type="primary"
|
||
link
|
||
size="small"
|
||
@click="handleEdit(row)"
|
||
>
|
||
编辑
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<pagination
|
||
v-show="total > 0"
|
||
:total="total"
|
||
v-model:page="queryParams.pageNo"
|
||
v-model:limit="queryParams.pageSize"
|
||
@pagination="getList"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑弹窗 -->
|
||
<el-dialog
|
||
v-model="editDialogVisible"
|
||
title="编辑门诊号码段"
|
||
width="600px"
|
||
:close-on-click-modal="false"
|
||
>
|
||
<el-form
|
||
ref="editFormRef"
|
||
:model="editForm"
|
||
:rules="editFormRules"
|
||
label-width="120px"
|
||
>
|
||
<el-form-item label="操作员" prop="operatorName">
|
||
<el-input v-model="editForm.operatorName" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="员工工号" prop="staffNo">
|
||
<el-input v-model.trim="editForm.staffNo" />
|
||
</el-form-item>
|
||
<el-form-item label="领用日期" prop="receiveDate">
|
||
<el-date-picker
|
||
v-model="editForm.receiveDate"
|
||
type="date"
|
||
value-format="YYYY.MM.DD"
|
||
placeholder="选择日期"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="起始号码" prop="startNo">
|
||
<el-input
|
||
v-model.trim="editForm.startNo"
|
||
@input="onEditFormStartNoChange"
|
||
@blur="validateEditFormField('startNo')"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="终止号码" prop="endNo">
|
||
<el-input
|
||
v-model.trim="editForm.endNo"
|
||
@blur="validateEditFormField('endNo')"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="使用号码" prop="usedNo">
|
||
<el-input
|
||
v-model.trim="editForm.usedNo"
|
||
@blur="validateEditFormField('usedNo')"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="editDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="handleSaveEditForm">保存</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup name="outpatientNoManagement">
|
||
import { ref, reactive, toRefs, onActivated, getCurrentInstance } from 'vue'
|
||
import useUserStore from '@/store/modules/user'
|
||
import { getConfigKey } from '@/api/system/config'
|
||
import { logQuery, logCreate, logUpdate, logDelete } from './components/operationLog'
|
||
import { listOutpatientNo, addOutpatientNo, updateOutpatientNo, deleteOutpatientNo } from './components/outpatientNumber'
|
||
|
||
const { proxy } = getCurrentInstance()
|
||
const userStore = useUserStore()
|
||
|
||
// 获取当前用户信息(用于日志记录)
|
||
const getUserInfo = () => ({
|
||
id: userStore.id,
|
||
name: userStore.name || userStore.nickName
|
||
})
|
||
|
||
// 格式化领用日期为 YYYY.MM.DD 格式(用于显示)
|
||
function formatReceiveDate(dateStr) {
|
||
if (!dateStr) return ''
|
||
// 如果是 YYYY-MM-DD 格式,转换为 YYYY.MM.DD
|
||
if (dateStr.includes('-')) {
|
||
return dateStr.replace(/-/g, '.')
|
||
}
|
||
// 如果已经是 YYYY.MM.DD 格式,直接返回
|
||
if (dateStr.includes('.')) {
|
||
return dateStr
|
||
}
|
||
return dateStr
|
||
}
|
||
|
||
// 将日期格式从 YYYY.MM.DD 转换为 yyyy-MM-dd(用于发送到后端)
|
||
function convertDateForBackend(dateStr) {
|
||
if (!dateStr) return null
|
||
// 如果是 YYYY.MM.DD 格式,转换为 yyyy-MM-dd
|
||
if (dateStr.includes('.')) {
|
||
return dateStr.replace(/\./g, '-')
|
||
}
|
||
// 如果已经是 yyyy-MM-dd 格式,直接返回
|
||
if (dateStr.includes('-')) {
|
||
return dateStr
|
||
}
|
||
return dateStr
|
||
}
|
||
|
||
const loading = ref(false)
|
||
const tableData = ref([])
|
||
const total = ref(0)
|
||
const ids = ref([])
|
||
const multiple = ref(true)
|
||
const viewAll = ref(false)
|
||
const canToggleViewAll = ref(false)
|
||
|
||
// 编辑弹窗相关
|
||
const editDialogVisible = ref(false)
|
||
const editFormRef = ref(null)
|
||
const editForm = reactive({
|
||
id: null,
|
||
operatorId: null,
|
||
operatorName: '',
|
||
staffNo: '',
|
||
receiveDate: '',
|
||
startNo: '',
|
||
endNo: '',
|
||
usedNo: '',
|
||
_originalData: null,
|
||
})
|
||
|
||
// 编辑表单验证规则
|
||
const editFormRules = {
|
||
staffNo: [{ required: true, message: '请输入员工工号', trigger: 'blur' }],
|
||
receiveDate: [{ required: true, message: '请选择领用日期', trigger: 'change' }],
|
||
startNo: [{ required: true, message: '请输入起始号码', trigger: 'blur' }],
|
||
endNo: [{ required: true, message: '请输入终止号码', trigger: 'blur' }],
|
||
usedNo: [{ required: true, message: '请输入使用号码', trigger: 'blur' }],
|
||
}
|
||
|
||
const data = reactive({
|
||
queryParams: {
|
||
pageNo: 1,
|
||
pageSize: 10,
|
||
onlySelf: true,
|
||
},
|
||
})
|
||
const { queryParams } = toRefs(data)
|
||
|
||
initConfig()
|
||
getList()
|
||
|
||
// 解决从标签页关闭后再次进入页面空白的问题:
|
||
// 当页面被 keep-alive 缓存后再次激活,主动刷新列表
|
||
onActivated(() => {
|
||
getList()
|
||
})
|
||
|
||
async function initConfig() {
|
||
try {
|
||
const res = await getConfigKey('outpatient_no_view_all')
|
||
canToggleViewAll.value = (res?.msg === 'Y' || res?.data === 'Y')
|
||
} catch (e) {
|
||
canToggleViewAll.value = false
|
||
}
|
||
}
|
||
|
||
function handleSelectionChange(selection) {
|
||
ids.value = selection.map((item) => item.id)
|
||
multiple.value = !selection.length
|
||
}
|
||
|
||
function onAdd() {
|
||
const now = new Date()
|
||
const yyyy = now.getFullYear()
|
||
const mm = String(now.getMonth() + 1).padStart(2, '0')
|
||
const dd = String(now.getDate()).padStart(2, '0')
|
||
|
||
// 打开编辑弹窗,用于新增
|
||
editForm.id = undefined
|
||
editForm.operatorId = userStore.id
|
||
editForm.operatorName = userStore.name || userStore.nickName
|
||
editForm.staffNo = userStore.id
|
||
editForm.receiveDate = `${yyyy}.${mm}.${dd}`
|
||
editForm.startNo = ''
|
||
editForm.endNo = ''
|
||
editForm.usedNo = ''
|
||
editForm._originalData = null
|
||
editDialogVisible.value = true
|
||
}
|
||
|
||
// 编辑行 - 打开编辑弹窗
|
||
function handleEdit(row) {
|
||
// 复制行数据到编辑表单
|
||
editForm.id = row.id
|
||
editForm.operatorId = row.operatorId
|
||
editForm.operatorName = row.operatorName
|
||
editForm.staffNo = row.staffNo
|
||
editForm.receiveDate = formatReceiveDate(row.receiveDate)
|
||
editForm.startNo = row.startNo
|
||
editForm.endNo = row.endNo
|
||
editForm.usedNo = row.usedNo
|
||
editForm._originalData = { ...row }
|
||
editDialogVisible.value = true
|
||
}
|
||
|
||
// 编辑表单中起始号码变化时自动设置使用号码
|
||
function onEditFormStartNoChange() {
|
||
if (!editForm.id && editForm.startNo) {
|
||
editForm.usedNo = editForm.startNo
|
||
}
|
||
}
|
||
|
||
// 验证编辑表单字段
|
||
function validateEditFormField(field) {
|
||
if (!isTailDigitsValid(editForm[field])) {
|
||
const msg = `最大位数为12位,且必须以数字结尾!`
|
||
alertWarn(msg)
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// 保存编辑表单
|
||
async function handleSaveEditForm() {
|
||
// 表单验证
|
||
if (!editFormRef.value) return
|
||
try {
|
||
const valid = await editFormRef.value.validate()
|
||
if (!valid) return
|
||
} catch (error) {
|
||
return
|
||
}
|
||
|
||
// 字段验证
|
||
if (!validateEditFormField('startNo') || !validateEditFormField('endNo') || !validateEditFormField('usedNo')) {
|
||
return
|
||
}
|
||
|
||
// 构建验证用的行对象
|
||
const validateRowData = {
|
||
startNo: editForm.startNo,
|
||
endNo: editForm.endNo,
|
||
usedNo: editForm.usedNo,
|
||
}
|
||
|
||
// 验证行数据(编辑时排除当前记录的 id,避免误判为重复)
|
||
const rowIndex = editForm.id ? tableData.value.findIndex(r => r.id === editForm.id) : -1
|
||
if (!validateRow(validateRowData, rowIndex, editForm.id)) {
|
||
return
|
||
}
|
||
|
||
// 准备保存的数据
|
||
const saveData = {
|
||
id: editForm.id,
|
||
operatorId: editForm.operatorId,
|
||
operatorName: editForm.operatorName,
|
||
staffNo: editForm.staffNo,
|
||
receiveDate: convertDateForBackend(editForm.receiveDate),
|
||
startNo: editForm.startNo,
|
||
endNo: editForm.endNo,
|
||
usedNo: editForm.usedNo,
|
||
}
|
||
|
||
try {
|
||
let res
|
||
if (saveData.id) {
|
||
// 更新
|
||
res = await updateOutpatientNo(saveData)
|
||
} else {
|
||
// 新增
|
||
res = await addOutpatientNo(saveData)
|
||
}
|
||
|
||
if (res.code === 200) {
|
||
proxy.$modal.msgSuccess('保存成功')
|
||
editDialogVisible.value = false
|
||
getList()
|
||
} else {
|
||
// 显示后端返回的错误信息
|
||
const errorMsg = res.msg || res.message || '保存失败'
|
||
proxy.$modal.msgError(errorMsg)
|
||
if (saveData.id) {
|
||
logUpdate(saveData, false, errorMsg, getUserInfo())
|
||
} else {
|
||
logCreate(saveData, false, errorMsg, getUserInfo())
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('保存失败:', error)
|
||
// 从错误对象中提取错误信息
|
||
let errorMsg = '保存失败'
|
||
|
||
// 优先从 response.data.msg 获取
|
||
if (error.response?.data?.msg) {
|
||
errorMsg = error.response.data.msg
|
||
}
|
||
// 其次从 error.message 获取(request.js 会通过 new Error(msg) 抛出)
|
||
else if (error.message) {
|
||
// 如果 error.message 包含 "Error: " 前缀,去掉它
|
||
errorMsg = error.message.replace(/^Error:\s*/, '')
|
||
}
|
||
// 最后尝试从 error.msg 获取
|
||
else if (error.msg) {
|
||
errorMsg = error.msg
|
||
}
|
||
|
||
// 显示错误信息
|
||
proxy.$modal.msgError(errorMsg)
|
||
|
||
// 记录日志
|
||
if (saveData.id) {
|
||
logUpdate(saveData, false, errorMsg, getUserInfo())
|
||
} else {
|
||
logCreate(saveData, false, errorMsg, getUserInfo())
|
||
}
|
||
}
|
||
}
|
||
|
||
// 新增时,起始号码变化时自动设置使用号码为起始号码
|
||
function onStartNoChange(row) {
|
||
if (!row.id && row._editing) {
|
||
row.usedNo = row.startNo
|
||
}
|
||
}
|
||
|
||
function onClose() {
|
||
// 检查是否有未保存的数据变动
|
||
const hasUnsavedChanges = tableData.value.some(row => row._dirty || row._editing)
|
||
|
||
if (hasUnsavedChanges) {
|
||
// 有未保存的数据,提示用户
|
||
const message = '窗口数据有变动是否进行保存操作?'
|
||
|
||
if (proxy.$modal?.confirm) {
|
||
proxy.$modal.confirm(message).then(() => {
|
||
// 用户选择保存
|
||
onSave()
|
||
// 等待保存完成后关闭页面
|
||
setTimeout(() => {
|
||
proxy.$tab.closePage()
|
||
}, 800)
|
||
}).catch(() => {
|
||
// 用户选择不保存,直接关闭
|
||
proxy.$tab.closePage()
|
||
})
|
||
} else {
|
||
// 降级方案:使用原生 confirm
|
||
if (confirm(message)) {
|
||
onSave()
|
||
setTimeout(() => {
|
||
proxy.$tab.closePage()
|
||
}, 800)
|
||
} else {
|
||
proxy.$tab.closePage()
|
||
}
|
||
}
|
||
} else {
|
||
// 没有未保存的数据,直接关闭
|
||
proxy.$tab.closePage()
|
||
}
|
||
}
|
||
|
||
function tableRowClassName({ row }) {
|
||
return row._error ? 'error-row' : ''
|
||
}
|
||
|
||
// 字母前缀识别规则 - 从末位往前找到第一个字母
|
||
function extractPrefix(value) {
|
||
if (!value) return ''
|
||
const chars = value.split('')
|
||
for (let i = chars.length - 1; i >= 0; i--) {
|
||
if (/[A-Za-z]/.test(chars[i])) {
|
||
return value.slice(0, i + 1) // 包含找到的字母
|
||
}
|
||
}
|
||
return ''
|
||
}
|
||
|
||
function extractTailNumber(value) {
|
||
if (!value) return NaN
|
||
const m = value.match(/(\d+)$/)
|
||
if (!m) return NaN
|
||
return parseInt(m[1], 10)
|
||
}
|
||
|
||
function lengthWithinLimit(value) {
|
||
if (!value) return false
|
||
const m = value.match(/(\d+)$/)
|
||
if (!m) return false
|
||
return m[1].length <= 12
|
||
}
|
||
|
||
function rangesOverlap(aStart, aEnd, bStart, bEnd) {
|
||
return Math.max(aStart, bStart) <= Math.min(aEnd, bEnd)
|
||
}
|
||
|
||
function alertWarn(msg) {
|
||
if (proxy.$modal && proxy.$modal.alertWarning) {
|
||
proxy.$modal.alertWarning(msg)
|
||
} else if (proxy.$message) {
|
||
proxy.$message.warning(msg)
|
||
}
|
||
}
|
||
|
||
// 校验:必须以数字结尾且尾部数字长度≤12
|
||
function isTailDigitsValid(value) {
|
||
const m = String(value || '').match(/(\d+)$/)
|
||
return !!m && m[1].length <= 12
|
||
}
|
||
|
||
function validateNumField(row, field, rowIndex) {
|
||
if (!isTailDigitsValid(row[field])) {
|
||
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData.value.indexOf(row)
|
||
const lineNo = idxInTable >= 0 ? idxInTable + 1 : undefined
|
||
const msg = lineNo ? `第【${lineNo}】行数据中,最大位数为12位,且必须以数字结尾!` : '最大位数为12位,且必须以数字结尾'
|
||
alertWarn(msg)
|
||
row._error = true
|
||
row._warned = row._warned || {}
|
||
row._warned[field] = true
|
||
return false
|
||
}
|
||
row._error = false
|
||
row._warned = row._warned || {}
|
||
row._warned[field] = false
|
||
return true
|
||
}
|
||
|
||
function onNumberInput(row, field) {
|
||
row._warned = row._warned || {}
|
||
const valid = isTailDigitsValid(row[field])
|
||
if (!valid && !row._warned[field]) {
|
||
alertWarn('最大位数为12位,且必须以数字结尾')
|
||
row._warned[field] = true
|
||
}
|
||
if (valid) {
|
||
row._warned[field] = false
|
||
}
|
||
}
|
||
|
||
function validateRow(row, rowIndex, excludeId = null) {
|
||
row._error = false
|
||
if (!lengthWithinLimit(row.startNo) || !lengthWithinLimit(row.endNo) || !lengthWithinLimit(row.usedNo)) {
|
||
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData.value.indexOf(row)
|
||
const lineNo = idxInTable >= 0 ? idxInTable + 1 : undefined
|
||
const msg = lineNo ? `第【${lineNo}】行数据中,最大位数为12位,且必须以数字结尾!` : '最大位数为12位,且必须以数字结尾'
|
||
alertWarn(msg)
|
||
row._error = true
|
||
return false
|
||
}
|
||
if ((row.startNo?.length || 0) !== (row.endNo?.length || 0)) {
|
||
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData.value.indexOf(row)
|
||
const lineNo = idxInTable >= 0 ? idxInTable + 1 : undefined
|
||
const msg = lineNo ? `第【${lineNo}】行数据中,起始号码与终止号码长度必须一致,请修改!` : '起始号码与终止号码长度必须一致'
|
||
alertWarn(msg)
|
||
row._error = true
|
||
return false
|
||
}
|
||
const p1 = extractPrefix(row.startNo)
|
||
const p2 = extractPrefix(row.endNo)
|
||
const p3 = extractPrefix(row.usedNo)
|
||
if (!(p1 === p2 && p2 === p3)) {
|
||
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData.value.indexOf(row)
|
||
const lineNo = idxInTable >= 0 ? idxInTable + 1 : undefined
|
||
const msg = lineNo ? `第【${lineNo}】行数据中,门诊号码的字母前缀必须相同,请修改!` : '行数据中,门诊号码的字母前缀必须相同,请修改!'
|
||
alertWarn(msg)
|
||
row._error = true
|
||
return false
|
||
}
|
||
const sNum = extractTailNumber(row.startNo)
|
||
const eNum = extractTailNumber(row.endNo)
|
||
if (Number.isNaN(sNum) || Number.isNaN(eNum) || sNum > eNum) {
|
||
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData.value.indexOf(row)
|
||
const lineNo = idxInTable >= 0 ? idxInTable + 1 : undefined
|
||
const msg = lineNo ? `第【${lineNo}】行数据中,起始/终止号码不合法` : '起始/终止号码不合法'
|
||
alertWarn(msg)
|
||
row._error = true
|
||
return false
|
||
}
|
||
// 放宽:不再强制"使用号码"必须处于起始与终止范围内
|
||
const prefix = p1
|
||
for (let i = 0; i < tableData.value.length; i++) {
|
||
const other = tableData.value[i]
|
||
// 跳过自身:通过 rowIndex、row 对象比较或 excludeId 排除
|
||
if ((typeof rowIndex === 'number' && i === rowIndex) ||
|
||
other === row ||
|
||
!other.startNo ||
|
||
!other.endNo ||
|
||
(excludeId && other.id === excludeId)) continue
|
||
if (extractPrefix(other.startNo) !== prefix) continue
|
||
const os = extractTailNumber(other.startNo)
|
||
const oe = extractTailNumber(other.endNo)
|
||
if (!Number.isNaN(os) && !Number.isNaN(oe)) {
|
||
if (rangesOverlap(sNum, eNum, os, oe)) {
|
||
const idxInTable = typeof rowIndex === 'number' ? rowIndex : tableData.value.indexOf(row)
|
||
const lineNo = idxInTable >= 0 ? idxInTable + 1 : undefined
|
||
const msg = lineNo ? `第【${lineNo}】行数据中,门诊号码和【${i + 1}】行的门诊号码有冲突,请修改!` : '门诊号码设置重复!'
|
||
alertWarn(msg)
|
||
row._error = true
|
||
return false
|
||
}
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
async function onSave(row) {
|
||
const rows = row ? [row] : tableData.value.filter(r => ids.value.includes(r.id) || r._dirty || r._editing)
|
||
if (!rows.length) {
|
||
proxy.$modal.msgWarning('没有可保存的数据')
|
||
return
|
||
}
|
||
|
||
// 前端校验
|
||
for (const r of rows) {
|
||
const idx = tableData.value.indexOf(r)
|
||
if (!validateRow(r, idx, r.id)) return
|
||
}
|
||
|
||
// 准备保存的数据(将日期格式转换为后端需要的格式)
|
||
const saveData = rows.map((r) => ({
|
||
id: r.id,
|
||
operatorId: r.operatorId,
|
||
operatorName: r.operatorName,
|
||
staffNo: r.staffNo,
|
||
receiveDate: convertDateForBackend(r.receiveDate), // 转换为 yyyy-MM-dd 格式
|
||
startNo: r.startNo,
|
||
endNo: r.endNo,
|
||
usedNo: r.usedNo,
|
||
}))
|
||
|
||
// 批量保存
|
||
let successCount = 0
|
||
let failCount = 0
|
||
|
||
for (const record of saveData) {
|
||
try {
|
||
let res
|
||
if (record.id) {
|
||
// 更新
|
||
res = await updateOutpatientNo(record)
|
||
if (res.code === 200) {
|
||
logUpdate(record, true, null, getUserInfo())
|
||
successCount++
|
||
} else {
|
||
const errorMsg = res.msg || res.message || '保存失败'
|
||
proxy.$modal.msgError(errorMsg)
|
||
logUpdate(record, false, errorMsg, getUserInfo())
|
||
failCount++
|
||
}
|
||
} else {
|
||
// 新增
|
||
res = await addOutpatientNo(record)
|
||
if (res.code === 200) {
|
||
logCreate(record, true, null, getUserInfo())
|
||
successCount++
|
||
} else {
|
||
const errorMsg = res.msg || res.message || '保存失败'
|
||
proxy.$modal.msgError(errorMsg)
|
||
logCreate(record, false, errorMsg, getUserInfo())
|
||
failCount++
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('保存失败:', error)
|
||
// 从错误对象中提取错误信息
|
||
let errorMsg = '保存失败'
|
||
if (error.response?.data?.msg) {
|
||
errorMsg = error.response.data.msg
|
||
} else if (error.message) {
|
||
errorMsg = error.message
|
||
} else if (error.msg) {
|
||
errorMsg = error.msg
|
||
}
|
||
proxy.$modal.msgError(errorMsg)
|
||
if (record.id) {
|
||
logUpdate(record, false, errorMsg, getUserInfo())
|
||
} else {
|
||
logCreate(record, false, errorMsg, getUserInfo())
|
||
}
|
||
failCount++
|
||
}
|
||
}
|
||
|
||
if (successCount > 0) {
|
||
proxy.$modal.msgSuccess(`保存成功${successCount}条`)
|
||
// 保存成功后,退出编辑状态
|
||
rows.forEach(r => {
|
||
r._editing = false
|
||
r._dirty = false
|
||
r._originalData = null // 清除原始数据
|
||
})
|
||
}
|
||
if (failCount > 0) {
|
||
proxy.$modal.msgError(`保存失败${failCount}条`)
|
||
}
|
||
|
||
getList()
|
||
}
|
||
|
||
async function onDelete() {
|
||
console.log('删除操作 - 选中的ID列表:', ids.value)
|
||
console.log('删除操作 - 表格数据:', tableData.value.map(r => ({ id: r.id, operatorId: r.operatorId })))
|
||
|
||
const rows = tableData.value.filter((r) => ids.value.includes(r.id))
|
||
console.log('删除操作 - 筛选后的行数据:', rows.map(r => ({ id: r.id, operatorId: r.operatorId })))
|
||
|
||
if (!rows.length) {
|
||
proxy.$modal.msgWarning('请选择要删除的数据')
|
||
return
|
||
}
|
||
|
||
// 前端预校验(归属权+使用状态)
|
||
for (const r of rows) {
|
||
console.log('检查删除权限 - 行数据:', {
|
||
id: r.id,
|
||
operatorId: r.operatorId,
|
||
operatorIdType: typeof r.operatorId,
|
||
userStoreId: userStore.id,
|
||
userStoreIdType: typeof userStore.id,
|
||
usedNo: r.usedNo,
|
||
startNo: r.startNo,
|
||
usedNoType: typeof r.usedNo,
|
||
startNoType: typeof r.startNo
|
||
})
|
||
|
||
const canDeleteSelf = String(r.operatorId) === String(userStore.id)
|
||
// 确保字符串比较,避免类型问题
|
||
const neverUsed = String(r.usedNo || '') === String(r.startNo || '')
|
||
|
||
if (!canDeleteSelf) {
|
||
console.warn('删除权限检查失败:', {
|
||
operatorId: r.operatorId,
|
||
userStoreId: userStore.id,
|
||
canDeleteSelf
|
||
})
|
||
alertWarn('只能删除自己维护的门诊号码段')
|
||
logDelete(rows, false, '只能删除自己维护的门诊号码段', getUserInfo())
|
||
return
|
||
}
|
||
if (!neverUsed) {
|
||
console.warn('使用状态检查失败:', {
|
||
usedNo: r.usedNo,
|
||
startNo: r.startNo,
|
||
neverUsed
|
||
})
|
||
alertWarn('已有门诊号码段已有使用的门诊号码,请核对!')
|
||
logDelete(rows, false, '已有门诊号码段已有使用的门诊号码', getUserInfo())
|
||
return
|
||
}
|
||
}
|
||
|
||
const doRealDelete = async () => {
|
||
try {
|
||
// 处理大整数 ID:保持为字符串或使用 BigInt,避免精度丢失
|
||
// JavaScript 的 Number.MAX_SAFE_INTEGER = 9007199254740991
|
||
// 如果 ID 超过这个值,转换为数字会丢失精度
|
||
const idsToDelete = rows
|
||
.map((r) => r.id)
|
||
.filter(id => id != null && id !== undefined)
|
||
.map(id => {
|
||
// 如果 ID 是字符串,检查是否需要转换为数字
|
||
if (typeof id === 'string') {
|
||
// 尝试转换为数字,但如果超过安全整数范围,保持为字符串
|
||
const numId = parseInt(id, 10)
|
||
if (!isNaN(numId) && numId <= Number.MAX_SAFE_INTEGER) {
|
||
return numId
|
||
}
|
||
// 大整数保持为字符串,后端应该能处理
|
||
return id
|
||
}
|
||
// 如果已经是数字,检查是否超过安全范围
|
||
if (typeof id === 'number') {
|
||
if (id > Number.MAX_SAFE_INTEGER) {
|
||
// 超过安全范围,转换为字符串
|
||
return String(id)
|
||
}
|
||
return id
|
||
}
|
||
// 其他类型,尝试转换
|
||
return id
|
||
})
|
||
.filter(id => id != null && id !== undefined)
|
||
|
||
if (idsToDelete.length === 0) {
|
||
proxy.$modal.msgWarning('没有可删除的数据')
|
||
return
|
||
}
|
||
|
||
console.log('准备删除的ID列表:', idsToDelete)
|
||
console.log('删除请求数据:', JSON.stringify({ ids: idsToDelete }))
|
||
console.log('删除请求数据类型:', idsToDelete.map(id => ({ value: id, type: typeof id, stringValue: String(id) })))
|
||
|
||
const res = await deleteOutpatientNo({ ids: idsToDelete })
|
||
|
||
console.log('删除响应:', res)
|
||
console.log('删除响应code:', res?.code)
|
||
console.log('删除响应msg:', res?.msg)
|
||
|
||
if (res && (res.code === 200 || res.code === 0)) {
|
||
logDelete(rows, true, null, getUserInfo())
|
||
proxy.$modal.msgSuccess('删除成功')
|
||
getList()
|
||
} else {
|
||
const errorMsg = res?.msg || res?.message || '删除失败'
|
||
logDelete(rows, false, errorMsg, getUserInfo())
|
||
proxy.$modal.msgError(errorMsg)
|
||
}
|
||
} catch (error) {
|
||
console.error('删除失败:', error)
|
||
// 从错误对象中提取错误信息
|
||
let errorMsg = '删除失败'
|
||
|
||
// 优先从 response.data.msg 获取
|
||
if (error.response?.data?.msg) {
|
||
errorMsg = error.response.data.msg
|
||
}
|
||
// 其次从 error.message 获取(request.js 会通过 new Error(msg) 抛出)
|
||
else if (error.message) {
|
||
// 如果 error.message 包含 "Error: " 前缀,去掉它
|
||
errorMsg = error.message.replace(/^Error:\s*/, '')
|
||
}
|
||
// 最后尝试从 error.msg 获取
|
||
else if (error.msg) {
|
||
errorMsg = error.msg
|
||
}
|
||
|
||
// 显示错误信息(参考 PackageManagement 的实现方式)
|
||
proxy.$modal.msgError('删除失败: ' + errorMsg)
|
||
|
||
// 记录日志
|
||
logDelete(rows, false, errorMsg, getUserInfo())
|
||
}
|
||
}
|
||
|
||
if (proxy.$modal?.confirm) {
|
||
proxy.$modal.confirm('是否确认删除选中数据项?').then(doRealDelete).catch(() => {
|
||
// 用户取消删除,不记录日志
|
||
})
|
||
} else {
|
||
doRealDelete()
|
||
}
|
||
}
|
||
|
||
function getList() {
|
||
loading.value = true
|
||
queryParams.value.onlySelf = !viewAll.value
|
||
|
||
// 调用后端API
|
||
listOutpatientNo(queryParams.value).then((res) => {
|
||
if (res.code === 200) {
|
||
tableData.value = (res.data?.records || res.data || []).map((it) => {
|
||
// 处理大整数 ID:如果 ID 超过 JavaScript 安全整数范围,保持为字符串
|
||
let id = it.id
|
||
if (typeof id === 'number' && id > Number.MAX_SAFE_INTEGER) {
|
||
// 数字超过安全范围,转换为字符串以避免精度丢失
|
||
id = String(id)
|
||
} else if (typeof id === 'number') {
|
||
// 数字在安全范围内,保持为数字
|
||
id = id
|
||
} else {
|
||
// 已经是字符串或其他类型,保持原样
|
||
id = id
|
||
}
|
||
|
||
return {
|
||
...it,
|
||
id: id, // 确保 ID 正确处理
|
||
_editing: false,
|
||
_error: false,
|
||
_dirty: false,
|
||
// 确保日期格式正确:后端返回 yyyy-MM-dd,转换为 YYYY.MM.DD 用于显示
|
||
receiveDate: it.receiveDate ? formatReceiveDate(it.receiveDate) : ''
|
||
}
|
||
})
|
||
total.value = res.data?.total || res.data?.length || 0
|
||
|
||
// 记录查询操作日志
|
||
logQuery(total.value, getUserInfo())
|
||
} else {
|
||
proxy.$modal.msgError(res.msg || '查询失败')
|
||
}
|
||
loading.value = false
|
||
}).catch((error) => {
|
||
console.error('查询门诊号码段失败:', error)
|
||
proxy.$modal.msgError('查询失败,请稍后重试')
|
||
loading.value = false
|
||
})
|
||
}
|
||
|
||
</script>
|
||
|
||
<style scoped>
|
||
/*Windows XP风格布局 - 全屏显示 */
|
||
|
||
/* 外层容器 - 全屏显示 */
|
||
.outpatient-no-management-wrapper {
|
||
width: 100%;
|
||
height: calc(100vh - 84px);
|
||
background-color: #f0f0f0;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
/* 主容器 - 全屏显示 */
|
||
.outpatient-no-management {
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: #D4D0C8;
|
||
border: 1px solid #000000;
|
||
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 标题栏(32px高,背景色#D4D0C8) */
|
||
.title-bar {
|
||
height: 32px;
|
||
background: linear-gradient(to bottom, #0055E5 0%, #0F3D8C 100%);
|
||
border-bottom: 1px solid #000000;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 8px;
|
||
}
|
||
|
||
/* 标题文本(14px/700,左对齐) */
|
||
.title-text {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
/* 功能按钮区(40px高) */
|
||
.button-bar {
|
||
height: 40px;
|
||
background-color: #D4D0C8;
|
||
border-bottom: 1px solid #808080;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 8px;
|
||
}
|
||
|
||
.button-bar .el-row {
|
||
width: 100%;
|
||
}
|
||
|
||
/* 按钮样式(90x32px,1px边框,圆角0,背景色#EFEFEF) */
|
||
.button-bar :deep(.el-button) {
|
||
width: 90px;
|
||
height: 32px;
|
||
border-radius: 0;
|
||
border: 1px solid #808080;
|
||
font-size: 13px;
|
||
padding: 0;
|
||
}
|
||
|
||
/* 新设按钮 - 主要操作 */
|
||
.button-bar :deep(.el-button--primary) {
|
||
background: linear-gradient(to bottom, #FFFFFF 0%, #EFEFEF 50%, #DFDFDF 100%);
|
||
color: #000000;
|
||
border-top: 1px solid #FFFFFF;
|
||
border-left: 1px solid #FFFFFF;
|
||
border-right: 1px solid #808080;
|
||
border-bottom: 1px solid #808080;
|
||
}
|
||
|
||
.button-bar :deep(.el-button--primary:hover) {
|
||
background: linear-gradient(to bottom, #FFFEF8 0%, #F5F4EF 50%, #E5E4DF 100%);
|
||
}
|
||
|
||
/* 删除按钮 */
|
||
.button-bar :deep(.el-button--danger) {
|
||
background: linear-gradient(to bottom, #FFFFFF 0%, #EFEFEF 50%, #DFDFDF 100%);
|
||
color: #000000;
|
||
border-top: 1px solid #FFFFFF;
|
||
border-left: 1px solid #FFFFFF;
|
||
border-right: 1px solid #808080;
|
||
border-bottom: 1px solid #808080;
|
||
}
|
||
|
||
/* 保存按钮 */
|
||
.button-bar :deep(.el-button--success) {
|
||
background: linear-gradient(to bottom, #FFFFFF 0%, #EFEFEF 50%, #DFDFDF 100%);
|
||
color: #000000;
|
||
border-top: 1px solid #FFFFFF;
|
||
border-left: 1px solid #FFFFFF;
|
||
border-right: 1px solid #808080;
|
||
border-bottom: 1px solid #808080;
|
||
}
|
||
|
||
/* 关闭按钮(红色背景,白色文字) */
|
||
.button-bar :deep(.el-button--warning) {
|
||
background: linear-gradient(to bottom, #FF6B6B 0%, #EE5A5A 50%, #DD4949 100%);
|
||
color: #FFFFFF;
|
||
font-weight: 600;
|
||
border-top: 1px solid #FF9999;
|
||
border-left: 1px solid #FF9999;
|
||
border-right: 1px solid #AA3333;
|
||
border-bottom: 1px solid #AA3333;
|
||
}
|
||
|
||
.button-bar :deep(.el-button--warning:hover) {
|
||
background: linear-gradient(to bottom, #FF7B7B 0%, #FE6A6A 50%, #ED5959 100%);
|
||
}
|
||
|
||
/* 按钮禁用状态 */
|
||
.button-bar :deep(.el-button:disabled) {
|
||
background: #D4D0C8;
|
||
color: #808080;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/*表格内容区(自适应剩余高度) */
|
||
.table-content {
|
||
flex: 1;
|
||
background-color: #FFFFFF;
|
||
padding: 8px;
|
||
overflow: auto;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 表格样式(1px实线边框#CCCCCC,表头背景#F0F0F0) */
|
||
.table-content :deep(.el-table) {
|
||
border: 1px solid #CCCCCC;
|
||
font-size: 13px;
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.table-content :deep(.el-table th) {
|
||
background: linear-gradient(to bottom, #FFFFFF 0%, #F0F0F0 100%);
|
||
border: 1px solid #CCCCCC;
|
||
color: #000000;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
padding: 8px 4px;
|
||
}
|
||
|
||
.table-content :deep(.el-table td) {
|
||
border: 1px solid #CCCCCC;
|
||
padding: 6px 4px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.table-content :deep(.el-table__body tr:hover > td) {
|
||
background-color: #E5F3FF !important;
|
||
}
|
||
|
||
/* 错误行样式 */
|
||
:deep(.error-row) {
|
||
--el-table-tr-bg-color: #fff7e6;
|
||
}
|
||
|
||
/* 分页样式 */
|
||
.table-content :deep(.pagination-container) {
|
||
margin-top: 10px;
|
||
padding: 10px 0;
|
||
border-top: 1px solid #CCCCCC;
|
||
}
|
||
|
||
/* 输入框样式 */
|
||
.table-content :deep(.el-input__inner) {
|
||
border: 1px solid #7FB4FF;
|
||
border-radius: 0;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.table-content :deep(.el-input__inner:focus) {
|
||
border: 2px solid #0055E5;
|
||
}
|
||
|
||
/* 日期选择器样式 */
|
||
.table-content :deep(.el-date-editor) {
|
||
width: 100%;
|
||
}
|
||
|
||
/* 开关样式 */
|
||
.button-bar :deep(.el-switch) {
|
||
height: 24px;
|
||
}
|
||
|
||
/* 滚动条样式(Windows XP风格) */
|
||
.table-content::-webkit-scrollbar {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
.table-content::-webkit-scrollbar-track {
|
||
background-color: #D4D0C8;
|
||
border: 1px solid #808080;
|
||
}
|
||
|
||
.table-content::-webkit-scrollbar-thumb {
|
||
background: linear-gradient(to bottom, #FFFFFF 0%, #EFEFEF 50%, #DFDFDF 100%);
|
||
border: 1px solid #808080;
|
||
}
|
||
|
||
.table-content::-webkit-scrollbar-thumb:hover {
|
||
background: linear-gradient(to bottom, #FFFEF8 0%, #F5F4EF 50%, #E5E4DF 100%);
|
||
}
|
||
|
||
/* 表格容器样式调整 */
|
||
.table-content :deep(.el-table__body-wrapper) {
|
||
flex: 1;
|
||
overflow: auto;
|
||
}
|
||
</style>
|
||
|
||
|
||
|