门诊号码管理维护界面-》优化
This commit is contained in:
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright ©2023 CJB-CNIT Team. All rights reserved
|
|
||||||
*/
|
|
||||||
package com.openhis.web.businessrule.appservice;
|
|
||||||
|
|
||||||
import com.openhis.web.businessrule.dto.OutpatientNoLogDto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 门诊号码管理服务接口
|
|
||||||
*
|
|
||||||
* @author system
|
|
||||||
* @date 2025-01-XX
|
|
||||||
*/
|
|
||||||
public interface IOutpatientNoAppService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录操作日志
|
|
||||||
*
|
|
||||||
* @param logDto 日志数据
|
|
||||||
*/
|
|
||||||
void addOperationLog(OutpatientNoLogDto logDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright ©2023 CJB-CNIT Team. All rights reserved
|
|
||||||
*/
|
|
||||||
package com.openhis.web.businessrule.appservice.impl;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import com.openhis.web.businessrule.appservice.IOutpatientNoAppService;
|
|
||||||
import com.openhis.web.businessrule.dto.OutpatientNoLogDto;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 门诊号码管理服务实现
|
|
||||||
*
|
|
||||||
* @author system
|
|
||||||
* @date 2025-01-XX
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class OutpatientNoAppServiceImpl implements IOutpatientNoAppService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录操作日志
|
|
||||||
*
|
|
||||||
* @param logDto 日志数据
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void addOperationLog(OutpatientNoLogDto logDto) {
|
|
||||||
// 记录日志到系统日志
|
|
||||||
log.info("门诊号码管理操作日志 - 操作类型: {}, 操作详情: {}, 操作结果: {}, 用户: {} (ID: {})",
|
|
||||||
logDto.getOperation(),
|
|
||||||
logDto.getDetails(),
|
|
||||||
logDto.getSuccess() ? "成功" : "失败",
|
|
||||||
logDto.getUserName(),
|
|
||||||
logDto.getUserId());
|
|
||||||
|
|
||||||
if (logDto.getErrorMessage() != null && !logDto.getErrorMessage().isEmpty()) {
|
|
||||||
log.warn("操作失败原因: {}", logDto.getErrorMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 如果需要保存到数据库,可以在这里添加数据库操作
|
|
||||||
// 例如:保存到 sys_oper_log 表或其他日志表
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright ©2023 CJB-CNIT Team. All rights reserved
|
|
||||||
*/
|
|
||||||
package com.openhis.web.businessrule.controller;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import com.core.common.core.domain.R;
|
|
||||||
import com.openhis.web.businessrule.appservice.IOutpatientNoAppService;
|
|
||||||
import com.openhis.web.businessrule.dto.OutpatientNoLogDto;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 门诊号码管理 controller
|
|
||||||
*
|
|
||||||
* @author system
|
|
||||||
* @date 2025-01-XX
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/business-rule/outpatient-no")
|
|
||||||
@Slf4j
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class OutpatientNoController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IOutpatientNoAppService outpatientNoAppService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录操作日志
|
|
||||||
*
|
|
||||||
* @param logDto 日志数据
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
@PostMapping("/log")
|
|
||||||
public R<?> addOperationLog(@RequestBody OutpatientNoLogDto logDto) {
|
|
||||||
try {
|
|
||||||
outpatientNoAppService.addOperationLog(logDto);
|
|
||||||
return R.ok("操作日志记录成功");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("记录操作日志失败", e);
|
|
||||||
return R.fail("操作日志记录失败:" + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright ©2023 CJB-CNIT Team. All rights reserved
|
|
||||||
*/
|
|
||||||
package com.openhis.web.businessrule.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 门诊号码操作日志DTO
|
|
||||||
*
|
|
||||||
* @author system
|
|
||||||
* @date 2025-01-XX
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Accessors(chain = true)
|
|
||||||
public class OutpatientNoLogDto {
|
|
||||||
|
|
||||||
/** 操作类型(新增/修改/删除/查询) */
|
|
||||||
private String operation;
|
|
||||||
|
|
||||||
/** 操作详情 */
|
|
||||||
private String details;
|
|
||||||
|
|
||||||
/** 操作是否成功 */
|
|
||||||
private Boolean success;
|
|
||||||
|
|
||||||
/** 错误信息 */
|
|
||||||
private String errorMessage;
|
|
||||||
|
|
||||||
/** 时间戳 */
|
|
||||||
private String timestamp;
|
|
||||||
|
|
||||||
/** 用户ID */
|
|
||||||
private Long userId;
|
|
||||||
|
|
||||||
/** 用户名称 */
|
|
||||||
private String userName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,849 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- 全屏布局 -->
|
|
||||||
<div class="outpatient-no-management-wrapper">
|
|
||||||
<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"
|
|
||||||
style="width: 100%;"
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input
|
|
||||||
v-if="row._editing"
|
|
||||||
v-model.trim="row.staffNo"
|
|
||||||
placeholder="请输入员工工号"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ row.staffNo }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="领用日期" prop="receiveDate" min-width="140">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-date-picker
|
|
||||||
v-if="row._editing"
|
|
||||||
v-model="row.receiveDate"
|
|
||||||
type="date"
|
|
||||||
value-format="YYYY-MM-DD"
|
|
||||||
placeholder="选择日期"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ row.receiveDate }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="起始号码" prop="startNo" min-width="140">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input
|
|
||||||
v-if="row._editing"
|
|
||||||
v-model.trim="row.startNo"
|
|
||||||
@input="() => onStartNoChange(row)"
|
|
||||||
@blur="() => validateNumField(row, 'startNo')"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ row.startNo }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="终止号码" prop="endNo" min-width="140">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input
|
|
||||||
v-if="row._editing"
|
|
||||||
v-model.trim="row.endNo"
|
|
||||||
@blur="() => validateNumField(row, 'endNo')"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ row.endNo }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="使用号码" prop="usedNo" min-width="140">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input
|
|
||||||
v-if="row._editing"
|
|
||||||
v-model.trim="row.usedNo"
|
|
||||||
@blur="() => validateNumField(row, 'usedNo')"
|
|
||||||
/>
|
|
||||||
<span v-else>{{ row.usedNo }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="100" fixed="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button type="primary" link icon="Edit" @click="() => openEdit(scope.row, scope.$index)">编辑</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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 编辑弹窗 -->
|
|
||||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" append-to-body>
|
|
||||||
<el-form label-width="100px">
|
|
||||||
<el-form-item label="操作员">
|
|
||||||
<el-input v-model="editForm.operatorName" disabled />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="员工工号">
|
|
||||||
<el-input v-model.trim="editForm.staffNo" placeholder="请输入员工工号" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="领用日期">
|
|
||||||
<el-date-picker v-model="editForm.receiveDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="起始号码">
|
|
||||||
<el-input v-model.trim="editForm.startNo" @blur="() => validateNumField(editForm, 'startNo')" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="终止号码">
|
|
||||||
<el-input v-model.trim="editForm.endNo" @blur="() => validateNumField(editForm, 'endNo')" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="使用号码">
|
|
||||||
<el-input v-model.trim="editForm.usedNo" @blur="() => validateNumField(editForm, 'usedNo')" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
|
||||||
<el-button type="primary" @click="confirmEdit">确 定</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup name="outpatientNoManagement">
|
|
||||||
import useUserStore from '@/store/modules/user'
|
|
||||||
import { getConfigKey } from '@/api/system/config'
|
|
||||||
import { logQuery, logCreate, logUpdate, logDelete } from './components/operationLog'
|
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance()
|
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
// 获取当前用户信息(用于日志记录)
|
|
||||||
const getUserInfo = () => ({
|
|
||||||
id: userStore.id,
|
|
||||||
name: userStore.name || userStore.nickName
|
|
||||||
})
|
|
||||||
|
|
||||||
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 dialogVisible = ref(false)
|
|
||||||
const dialogTitle = ref('编辑门诊号码段')
|
|
||||||
const editIndex = ref(-1)
|
|
||||||
const editForm = reactive({
|
|
||||||
receiveDate: '',
|
|
||||||
startNo: '',
|
|
||||||
endNo: '',
|
|
||||||
usedNo: '',
|
|
||||||
operatorName: '',
|
|
||||||
staffNo: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
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')
|
|
||||||
// 员工工号默认为当前用户ID(文本格式)
|
|
||||||
const defaultStaffNo = String(userStore.id || '')
|
|
||||||
tableData.value.push({
|
|
||||||
id: undefined,
|
|
||||||
operatorId: userStore.id,
|
|
||||||
operatorName: userStore.name || userStore.nickName,
|
|
||||||
staffNo: defaultStaffNo, // 默认取当前员工工号(用户ID)
|
|
||||||
receiveDate: `${yyyy}-${mm}-${dd}`,
|
|
||||||
startNo: '',
|
|
||||||
endNo: '',
|
|
||||||
usedNo: '',
|
|
||||||
_editing: true,
|
|
||||||
_error: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增时,起始号码变化时自动设置使用号码为起始号码
|
|
||||||
function onStartNoChange(row) {
|
|
||||||
if (!row.id && row._editing) {
|
|
||||||
row.usedNo = row.startNo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClose() {
|
|
||||||
proxy.$tab.closePage()
|
|
||||||
}
|
|
||||||
|
|
||||||
function tableRowClassName({ row }) {
|
|
||||||
return row._error ? 'error-row' : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function openEdit(row, index) {
|
|
||||||
editIndex.value = index
|
|
||||||
dialogTitle.value = '编辑门诊号码段'
|
|
||||||
editForm.receiveDate = row.receiveDate
|
|
||||||
editForm.startNo = row.startNo
|
|
||||||
editForm.endNo = row.endNo
|
|
||||||
editForm.usedNo = row.usedNo
|
|
||||||
editForm.operatorName = row.operatorName
|
|
||||||
// 员工工号可编辑,如果为空则默认取当前用户ID
|
|
||||||
editForm.staffNo = row.staffNo || String(userStore.id || '')
|
|
||||||
dialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmEdit() {
|
|
||||||
const tmp = { ...tableData.value[editIndex.value], ...editForm }
|
|
||||||
if (!validateRow(tmp, editIndex.value)) return
|
|
||||||
tableData.value[editIndex.value] = {
|
|
||||||
...tableData.value[editIndex.value],
|
|
||||||
...editForm,
|
|
||||||
_dirty: true, // 标记为已修改,顶部保存时提交
|
|
||||||
}
|
|
||||||
dialogVisible.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 字母前缀识别规则 - 从末位往前找到第一个字母
|
|
||||||
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 : (editIndex.value >= 0 ? editIndex.value + 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) {
|
|
||||||
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 : (editIndex.value >= 0 ? editIndex.value + 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 : (editIndex.value >= 0 ? editIndex.value + 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 : (editIndex.value >= 0 ? editIndex.value + 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 : (editIndex.value >= 0 ? editIndex.value + 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]
|
|
||||||
// 跳过自身:当从弹窗校验时 row 为临时对象,需用下标判断
|
|
||||||
if ((typeof rowIndex === 'number' && i === rowIndex) || other === row || !other.startNo || !other.endNo) 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 : (editIndex.value >= 0 ? editIndex.value + 1 : undefined)
|
|
||||||
const msg = lineNo ? `第【${lineNo}】行数据中,门诊号码和【${i + 1}】行的门诊号码有冲突,请修改!` : '门诊号码设置重复!'
|
|
||||||
alertWarn(msg)
|
|
||||||
row._error = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSave(row) {
|
|
||||||
const rows = row ? [row] : tableData.value.filter(r => ids.value.includes(r.id) || r._dirty || r._editing)
|
|
||||||
if (!rows.length) return
|
|
||||||
for (const r of rows) {
|
|
||||||
const idx = tableData.value.indexOf(r)
|
|
||||||
if (!validateRow(r, idx)) return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 准备保存的数据
|
|
||||||
const saveData = rows.map((r) => ({
|
|
||||||
id: r.id,
|
|
||||||
operatorId: r.operatorId,
|
|
||||||
operatorName: r.operatorName,
|
|
||||||
staffNo: r.staffNo,
|
|
||||||
receiveDate: r.receiveDate,
|
|
||||||
startNo: r.startNo,
|
|
||||||
endNo: r.endNo,
|
|
||||||
usedNo: r.usedNo,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const ok = lcUpsertMany(saveData)
|
|
||||||
if (!ok) {
|
|
||||||
// 记录失败的操作日志
|
|
||||||
for (const record of saveData) {
|
|
||||||
if (record.id) {
|
|
||||||
logUpdate(record, false, '门诊号码设置重复', getUserInfo())
|
|
||||||
} else {
|
|
||||||
logCreate(record, false, '门诊号码设置重复', getUserInfo())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录成功的操作日志
|
|
||||||
for (const record of saveData) {
|
|
||||||
if (record.id) {
|
|
||||||
logUpdate(record, true, null, getUserInfo())
|
|
||||||
} else {
|
|
||||||
logCreate(record, true, null, getUserInfo())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proxy.$modal?.alertSuccess) {
|
|
||||||
proxy.$modal.alertSuccess('保存成功!')
|
|
||||||
} else {
|
|
||||||
proxy.$message.success('保存成功')
|
|
||||||
}
|
|
||||||
getList()
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDelete() {
|
|
||||||
const rows = tableData.value.filter((r) => ids.value.includes(r.id))
|
|
||||||
if (!rows.length) return
|
|
||||||
|
|
||||||
// 双重校验(归属权+使用状态)
|
|
||||||
for (const r of rows) {
|
|
||||||
const canDeleteSelf = String(r.operatorId) === String(userStore.id)
|
|
||||||
const neverUsed = r.usedNo === r.startNo
|
|
||||||
if (!canDeleteSelf) {
|
|
||||||
// 权限不足提示
|
|
||||||
alertWarn('只能删除自己维护的门诊号码段')
|
|
||||||
logDelete(rows, false, '只能删除自己维护的门诊号码段', getUserInfo())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!neverUsed) {
|
|
||||||
// 已使用提示
|
|
||||||
alertWarn('已有门诊号码段已有使用的门诊号码,请核对!')
|
|
||||||
logDelete(rows, false, '已有门诊号码段已有使用的门诊号码', getUserInfo())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const doRealDelete = () => {
|
|
||||||
lcDeleteByIds(rows.map((r) => r.id))
|
|
||||||
|
|
||||||
//记录成功的删除操作日志
|
|
||||||
logDelete(rows, true, null, getUserInfo())
|
|
||||||
|
|
||||||
if (proxy.$modal?.alertSuccess) {
|
|
||||||
proxy.$modal.alertSuccess('删除成功')
|
|
||||||
} else {
|
|
||||||
proxy.$message.success('删除成功')
|
|
||||||
}
|
|
||||||
getList()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proxy.$modal?.confirm) {
|
|
||||||
proxy.$modal.confirm('是否确认删除选中数据项?').then(doRealDelete).catch(() => {
|
|
||||||
// 用户取消删除,不记录日志
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
doRealDelete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开界面系统自动获取当前账号下维护的所有号码段记录
|
|
||||||
function getList() {
|
|
||||||
loading.value = true
|
|
||||||
// 默认只查询当前账号下的记录(onlySelf = true)
|
|
||||||
queryParams.value.onlySelf = !viewAll.value
|
|
||||||
const res = lcList({ ...queryParams.value })
|
|
||||||
tableData.value = res.records.map((it) => ({
|
|
||||||
...it,
|
|
||||||
_editing: false,
|
|
||||||
_error: false,
|
|
||||||
_dirty: false,
|
|
||||||
// 确保员工工号为文本格式
|
|
||||||
staffNo: it.staffNo ? String(it.staffNo) : String(userStore.id || ''),
|
|
||||||
}))
|
|
||||||
total.value = res.total
|
|
||||||
loading.value = false
|
|
||||||
|
|
||||||
// 记录查询操作日志
|
|
||||||
logQuery(total.value, getUserInfo())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 纯前端本地持久化方法(localStorage)
|
|
||||||
const STORAGE_KEY = 'ohis_outpatient_no_segments'
|
|
||||||
|
|
||||||
function lcReadAll() {
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem(STORAGE_KEY)
|
|
||||||
const arr = raw ? JSON.parse(raw) : []
|
|
||||||
return Array.isArray(arr) ? arr : []
|
|
||||||
} catch (e) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function lcWriteAll(list) {
|
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(list || []))
|
|
||||||
}
|
|
||||||
|
|
||||||
function lcList({ pageNo = 1, pageSize = 10, onlySelf = true }) {
|
|
||||||
const all = lcReadAll()
|
|
||||||
const filtered = onlySelf ? all.filter((x) => String(x.operatorId) === String(userStore.id)) : all
|
|
||||||
const start = (pageNo - 1) * pageSize
|
|
||||||
const end = start + pageSize
|
|
||||||
return { records: filtered.slice(start, end), total: filtered.length, all }
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkOverlapAll(row, all) {
|
|
||||||
const prefix = extractPrefix(row.startNo)
|
|
||||||
const sNum = extractTailNumber(row.startNo)
|
|
||||||
const eNum = extractTailNumber(row.endNo)
|
|
||||||
for (const it of all) {
|
|
||||||
if (row.id && it.id === row.id) continue
|
|
||||||
if (!it.startNo || !it.endNo) continue
|
|
||||||
if (extractPrefix(it.startNo) !== prefix) continue
|
|
||||||
const os = extractTailNumber(it.startNo)
|
|
||||||
const oe = extractTailNumber(it.endNo)
|
|
||||||
if (!Number.isNaN(os) && !Number.isNaN(oe)) {
|
|
||||||
if (rangesOverlap(sNum, eNum, os, oe)) return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function lcUpsertMany(rows) {
|
|
||||||
const all = lcReadAll()
|
|
||||||
for (const r of rows) {
|
|
||||||
if (checkOverlapAll(r, all)) {
|
|
||||||
alertWarn('门诊号码设置重复!')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!r.id) {
|
|
||||||
r.id = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
||||||
}
|
|
||||||
const idx = all.findIndex((x) => x.id === r.id)
|
|
||||||
if (idx >= 0) all[idx] = { ...all[idx], ...r }
|
|
||||||
else all.push({ ...r })
|
|
||||||
}
|
|
||||||
lcWriteAll(all)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function lcDeleteByIds(idList) {
|
|
||||||
const all = lcReadAll()
|
|
||||||
const remain = all.filter((x) => !idList.includes(x.id))
|
|
||||||
lcWriteAll(remain)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* 全屏布局 */
|
|
||||||
|
|
||||||
/* 外层容器 - 全屏显示 */
|
|
||||||
.outpatient-no-management-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-height: calc(100vh - 84px);
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 主容器 - 全屏宽度 */
|
|
||||||
.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;
|
|
||||||
flex: 1;
|
|
||||||
min-height: calc(100vh - 84px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 标题栏(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: hidden;
|
|
||||||
min-height: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表格样式(1px实线边框#CCCCCC,表头背景#F0F0F0) */
|
|
||||||
.table-content :deep(.el-table) {
|
|
||||||
border: 1px solid #CCCCCC;
|
|
||||||
font-size: 13px;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-content :deep(.el-table__body-wrapper) {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 编辑弹窗样式 */
|
|
||||||
:deep(.el-dialog) {
|
|
||||||
border-radius: 0;
|
|
||||||
border: 2px solid #0055E5;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-dialog__header) {
|
|
||||||
background: linear-gradient(to bottom, #0055E5 0%, #0F3D8C 100%);
|
|
||||||
padding: 10px 15px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-dialog__title) {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-dialog__close) {
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式调整 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.outpatient-no-management {
|
|
||||||
min-height: calc(100vh - 84px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user