qx
2 天以前 59dd5c96e8bca3341d1166a9a350f0731436817b
Merge branch 'master' of http://101.42.27.146:5001/r/ltkj_peisweb
7个文件已修改
1个文件已添加
1个文件已删除
1152 ■■■■ 已修改文件
src/111.js 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/config.js 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main copy.js 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.js 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/doctor/inspectCheck/index.vue 508 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/hosp/project/index.vue 187 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reservation/reservations/index.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/111.js
New file
@@ -0,0 +1,97 @@
// src/main.js
import Vue from "vue";
import Cookies from "js-cookie";
import "babel-polyfill";
import Element from "element-ui";
import "./assets/styles/element-variables.scss";
import "@/assets/styles/index.scss";
import "@/assets/styles/ruoyi.scss";
import App from "./App";
import store from "./store";
import router from "./router";
import directive from "./directive";
import plugins from "./plugins";
import { download } from "@/utils/request";
import Print from "vue-print-nb";
import JsonExcel from "vue-json-excel";
import "./assets/icons";
import "./permission";
import { getDicts } from "@/api/system/dict/data";
import { getConfigKey } from "@/api/system/config";
import {
  parseTime,
  resetForm,
  addDateRange,
  selectDictLabel,
  selectDictLabels,
  handleTree,
} from "@/utils/ruoyi";
import Pagination from "@/components/Pagination";
import Editor from "@/components/Editor";
import FileUpload from "@/components/FileUpload";
import ImageUpload from "@/components/ImageUpload";
import ImagePreview from "@/components/ImagePreview";
import DictTag from "@/components/DictTag";
import VueMeta from "vue-meta";
import DictData from "@/components/DictData";
import * as echarts from "echarts";
import VueBarcode from "vue-barcode";
Vue.component("downloadExcel", JsonExcel);
Vue.component("barcode", VueBarcode);
Vue.component("DictTag", DictTag);
Vue.component("Pagination", Pagination);
Vue.component("Editor", Editor);
Vue.component("FileUpload", FileUpload);
Vue.component("ImageUpload", ImageUpload);
Vue.component("ImagePreview", ImagePreview);
Vue.prototype.getDicts = getDicts;
Vue.prototype.getConfigKey = getConfigKey;
Vue.prototype.parseTime = parseTime;
Vue.prototype.resetForm = resetForm;
Vue.prototype.addDateRange = addDateRange;
Vue.prototype.selectDictLabel = selectDictLabel;
Vue.prototype.selectDictLabels = selectDictLabels;
Vue.prototype.download = download;
Vue.prototype.handleTree = handleTree;
Vue.prototype.$echarts = echarts;
// 保留 $showNotification,支持手动触发通知
Vue.prototype.$showNotification = function (type, title, message, onClick) {
  console.log('触发通知:', { type, title, message }, new Date().toLocaleString());
  Vue.prototype.$notify({
    title,
    message,
    type,
    duration: 5000,
    position: 'top-right',
    offset: 50,
    onClick,
    customClass: 'global-notification',
    appendTo: document.body
  });
};
// 监听路由变化
router.afterEach(() => {
  console.log('路由切换完成,当前路径:', router.currentRoute.path);
});
const app = new Vue({
  el: "#app",
  router,
  store,
  render: (h) => h(App)
});
Vue.use(directive);
Vue.use(plugins);
Vue.use(VueMeta);
Vue.use(Print);
Vue.use(Element, {
  size: Cookies.get("size") || "medium",
});
DictData.install();
Vue.config.productionTip = false;
src/api/system/config.js
@@ -33,7 +33,14 @@
    data: data
  })
}
// 新增参数配置
export function yidu(data) {
  return request({
    url: '/system/notice/readNotice ',
    method: 'post',
    data: data
  })
}
// 修改参数配置
export function updateConfig(data) {
  return request({
src/main copy.js
File was deleted
src/main.js
@@ -17,7 +17,7 @@
import "./assets/icons";
import "./permission";
import { getDicts } from "@/api/system/dict/data";
import { getConfigKey } from "@/api/system/config";
import { getConfigKey, yidu } from "@/api/system/config";
import {
  parseTime,
  resetForm,
@@ -36,7 +36,10 @@
import DictData from "@/components/DictData";
import * as echarts from "echarts";
import VueBarcode from "vue-barcode";
import { initWebSocket, closeWebSocket } from "@/utils/websocket";
import RightToolbar from "@/components/RightToolbar"
// 注册全局组件
Vue.component("downloadExcel", JsonExcel);
Vue.component("barcode", VueBarcode);
Vue.component("DictTag", DictTag);
@@ -45,7 +48,9 @@
Vue.component("FileUpload", FileUpload);
Vue.component("ImageUpload", ImageUpload);
Vue.component("ImagePreview", ImagePreview);
Vue.component("RightToolbar", RightToolbar);
// 注册全局方法
Vue.prototype.getDicts = getDicts;
Vue.prototype.getConfigKey = getConfigKey;
Vue.prototype.parseTime = parseTime;
@@ -57,20 +62,78 @@
Vue.prototype.handleTree = handleTree;
Vue.prototype.$echarts = echarts;
// 保留 $showNotification,支持手动触发通知
Vue.prototype.$showNotification = function (type, title, message, onClick) {
  console.log('触发通知:', { type, title, message }, new Date().toLocaleString());
  Vue.prototype.$notify({
    title,
    message,
// 通知管理:跟踪当前通知和偏移量
const notificationManager = {
  notifications: [], // 存储当前显示的通知实例
  baseOffset: 50, // 基础偏移量
  notificationHeight: 80, // 每个通知的估计高度(包括间距)
  maxNotifications: 5, // 最大同时显示的通知数量
  addNotification(notification) {
    if (this.notifications.length >= this.maxNotifications) {
      // 关闭最早的通知
      const oldest = this.notifications.shift();
      oldest.close();
    }
    this.notifications.push(notification);
    // 设置动态 offset 和 z-index
    notification.offset = this.baseOffset + this.notifications.length * this.notificationHeight;
    notification.customClass += ` notification-${this.notifications.length}`; // 为 z-index 添加唯一类
    // 监听通知关闭
    notification.onClose = () => {
      const index = this.notifications.indexOf(notification);
      if (index > -1) {
        this.notifications.splice(index, 1);
        // 更新后续通知的 offset
        this.updateOffsets();
      }
    };
  },
  updateOffsets() {
    this.notifications.forEach((notification, index) => {
      notification.offset = this.baseOffset + (index + 1) * this.notificationHeight;
      notification.customClass = notification.customClass.replace(/notification-\d+/, `notification-${index + 1}`);
    });
  }
};
// 全局通知方法,添加“已读”按钮
Vue.prototype.$showNotification = function (type, title, message, onClick, noticeId) {
  console.log('触发通知:', { type, title, message, noticeId, noticeIdType: typeof noticeId }); // 调试:记录 noticeId 和类型
  const h = this.$createElement;
  const notification = this.$notify({
    title: title,
    message: h('div', { class: 'notification-content' }, [
      h('div', { class: 'notification-message' }, message),
      noticeId ? h('el-button', {
        class: 'read-button',
        style: { marginLeft: '10px', float: 'right', cursor: 'pointer' },
        props: { type: 'primary', size: 'mini' },
        on: {
          click: async () => {
            console.log('点击“已读”按钮,noticeId:', noticeId, 'type:', typeof noticeId); // 调试:记录点击时的 noticeId
            try {
              await yidu({ noticeId: String(noticeId) });
              console.log(`通知 ${noticeId} 已标记为已读`);
              this.$message.success('标记为已读成功');
              notification.close();
            } catch (error) {
              console.error('标记已读失败:', error, 'noticeId:', noticeId);
              this.$message.error('标记已读失败');
            }
          }
        }
      }, '已读') : null
    ]),
    type,
    duration: 5000,
    position: 'top-right',
    offset: 50,
    onClick,
    offset: notificationManager.baseOffset, // 初始 offset
    onClick: null, // 不跳转
    customClass: 'global-notification',
    dangerouslyUseHTMLString: false,
    appendTo: document.body
  });
  notificationManager.addNotification(notification); // 添加到通知管理
};
// 监听路由变化
@@ -78,13 +141,76 @@
  console.log('路由切换完成,当前路径:', router.currentRoute.path);
});
// 定义 WebSocket 初始化标志,防止重复连接
let isWebSocketInitialized = false;
const app = new Vue({
  el: "#app",
  router,
  store,
  render: (h) => h(App)
  render: (h) => h(App),
  mounted() {
    const token = store.state.user.token || Cookies.get('token') || '';
    if (token && !isWebSocketInitialized) {
      console.log('初始化 WebSocket,Token:', token);
      isWebSocketInitialized = true;
      initWebSocket(token, (type, data) => {
        console.log('WebSocket 收到消息:', { type, data }); // 调试:记录原始数据
        if (type === 'error') {
          Vue.prototype.$showNotification.call(this, 'error', '错误', data);
          return;
        }
        try {
          if (typeof data === 'string' && data.trim().startsWith('{')) {
            // 替换大整数字段,防止精度丢失
            const normalizedData = data.replace(/"(noticeId|notice_id|id)":\s*(\d+)/g, '"$1":"$2"');
            const message = JSON.parse(normalizedData);
            console.log('WebSocket 解析后消息:', message); // 调试:记录解析后的消息
            if (message.noticeId || message.notice_id || message.id) {
              const noticeTypeLabel = message.noticeType === '1' ? '通知' : '公告';
              const noticeTitle = message.noticeTitle ? message.noticeTitle.replace(/<[^>]+>/g, '') : '无标题';
              const contentPreview = message.noticeContent
                ? message.noticeContent.replace(/<[^>]+>/g, '').substring(0, 20) + '...'
                : '无内容';
              const noticeId = String(message.noticeId || message.notice_id || message.id);
              console.log('准备触发通知,noticeId:', noticeId, 'type:', typeof noticeId); // 调试:记录传递的 noticeId
              Vue.prototype.$showNotification.call(
                this,
                'success',
                `新${noticeTypeLabel}`,
                `${noticeTitle} - ${contentPreview}`,
                null,
                noticeId
              );
            } else {
              console.log('未知消息类型:', message);
              Vue.prototype.$showNotification.call(this, 'info', '消息', '收到未知格式的消息');
            }
          } else {
            console.log('WebSocket 非 JSON 消息:', data);
          }
        } catch (error) {
          console.error('消息解析失败:', error, '原始数据:', data);
          Vue.prototype.$showNotification.call(this, 'error', '消息解析失败', `服务器回应字符串: ${data}`);
        }
      });
    } else if (!token) {
      console.error('未找到 token,无法初始化 WebSocket');
    } else {
      console.log('WebSocket 已初始化,跳过重复连接');
    }
  },
  beforeDestroy() {
    if (isWebSocketInitialized) {
      closeWebSocket();
      isWebSocketInitialized = false;
      console.log('Vue 实例销毁,WebSocket 已清理');
    }
  }
});
// 注册插件
Vue.use(directive);
Vue.use(plugins);
Vue.use(VueMeta);
src/utils/websocket.js
@@ -1,6 +1,7 @@
// src/utils/websocket.js
let ws = null;
let reconnectAttempts = 0;
let pingTimer = null; // 用于管理心跳定时器
const maxReconnectAttempts = 5;
const reconnectInterval = 5000; // 5秒重连间隔
const pingInterval = 5000; // 5秒发送ping
@@ -11,21 +12,33 @@
    return;
  }
  const wsUrl = `ws://192.168.1.2:5011/ws?token=${token}`;
  // 如果 ws 已存在且连接活跃,则跳过初始化
  if (ws && ws.readyState === WebSocket.OPEN) {
    console.log('WebSocket 已连接,跳过重复初始化');
    return;
  }
  // 如果 ws 存在但未关闭,先关闭旧连接
  if (ws) {
    console.warn('发现旧 WebSocket 连接,正在关闭...');
    closeWebSocket();
  }
  const wsUrl = `ws://192.168.1.244:5011/ws?token=${token}`;
  ws = new WebSocket(wsUrl);
  ws.onopen = () => {
    console.log('WebSocket 连接成功');
    reconnectAttempts = 0;
    reconnectAttempts = 0; // 重置重连计数
    // 启动心跳机制
    const pingTimer = setInterval(() => {
      if (ws.readyState === WebSocket.OPEN) {
        console.log('发送 ping 消息');
    if (pingTimer) clearInterval(pingTimer); // 清理旧定时器
    pingTimer = setInterval(() => {
      if (ws && ws.readyState === WebSocket.OPEN) {
        ws.send('ping');
      } else {
        console.warn('WebSocket 未连接,停止 ping');
        clearInterval(pingTimer);
        pingTimer = null;
      }
    }, pingInterval);
  };
@@ -35,6 +48,7 @@
    console.log('WebSocket 收到原始消息:', data);
    if (data === 'pong') {
      console.log('收到 pong 响应,连接活跃');
      reconnectAttempts = 0; // 重置重连计数,确保活跃连接不触发重连
      return;
    }
    onMessage('message', data);
@@ -42,11 +56,14 @@
  ws.onerror = (error) => {
    console.error('WebSocket 错误:', error);
    onMessage('error', 'WebSocket 连接错误');
  };
  ws.onclose = () => {
    console.warn('WebSocket 连接关闭');
    console.warn('WebSocket 连接关闭,时间:', new Date());
    if (pingTimer) {
      clearInterval(pingTimer);
      pingTimer = null;
    }
    if (reconnectAttempts < maxReconnectAttempts) {
      reconnectAttempts++;
      console.log(`尝试重连 (${reconnectAttempts}/${maxReconnectAttempts})...`);
@@ -61,18 +78,18 @@
  // 清理 WebSocket
  window.addEventListener('beforeunload', () => {
    if (ws) {
      ws.close();
      ws = null;
      console.log('WebSocket 已清理');
    }
  });
    closeWebSocket();
  }, { once: true }); // 确保事件监听只添加一次
}
export function closeWebSocket() {
  if (ws) {
    ws.close();
    ws = null;
    if (pingTimer) {
      clearInterval(pingTimer);
      pingTimer = null;
    }
    console.log('WebSocket 已关闭');
  }
}
src/views/doctor/inspectCheck/index.vue
@@ -1,35 +1,77 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px" @submit.native.prevent>
    <el-form
      :model="queryParams"
      ref="queryForm"
      size="small"
      :inline="true"
      label-width="68px"
      @submit.native.prevent
    >
      <el-form-item label="体检号" prop="reportDoctorCode">
        <el-input ref="inputName" v-model="queryParams.tjNum" placeholder="请输入体检号" clearable
          @keyup.enter.native="handleQuery" style="width: 170px" />
        <el-input
          ref="inputName"
          v-model="queryParams.tjNum"
          placeholder="请输入体检号"
          clearable
          @keyup.enter.native="handleQuery"
          style="width: 170px"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery" style="margin-right: 15px">
        <el-button
          type="primary"
          icon="el-icon-search"
          size="mini"
          @click="handleQuery"
          style="margin-right: 15px"
        >
          查询
        </el-button>
        <el-button icon="el-icon-refresh" type="primary" size="mini" @click="resetQuery">
        <el-button
          icon="el-icon-refresh"
          type="primary"
          size="mini"
          @click="resetQuery"
        >
          重置
        </el-button>
        <el-button icon="el-icon-check" type="primary" size="mini" style="margin-right: 15px" @click="tongbu">
        <el-button
          icon="el-icon-check"
          type="primary"
          size="mini"
          style="margin-right: 15px"
          @click="tongbu"
        >
          同步
        </el-button>
      </el-form-item>
    </el-form>
    <div class="box">
      <div class="table-header">
        检验记录
      </div>
      <div class="table-header">检验记录</div>
      <div>
        <el-table :data="exaLists" border style="width: 100%" @selection-change="handleSelectionChange"
          :header-cell-style="{ background: '#aad8df', fontSize: '14px', color: '#333' }" height="350" ref="firstTable">
        <el-table
          :data="exaLists"
          border
          style="width: 100%"
          @selection-change="handleSelectionChange"
          :header-cell-style="{ background: '#aad8df', fontSize: '14px', color: '#333' }"
          height="350"
          ref="firstTable"
        >
          <el-table-column fixed type="selection" align="center" label="选择" width="50" />
          <el-table-column label="姓名" align="center" prop="name" width="80" />
          <el-table-column label="性别" align="center" prop="gender" width="80" />
          <el-table-column label="年龄" align="center" prop="patientAge" width="80" />
          <el-table-column label="送检科室" align="center" prop="deptName" width="100" />
          <el-table-column label="检验项目" align="center" prop="checkParts" width="350" :show-overflow-tooltip="true" />
          <el-table-column
            label="检验项目"
            align="center"
            prop="checkParts"
            width="350"
            :show-overflow-tooltip="true"
          />
          <el-table-column label="项目编号" align="center" prop="jcxmid" width="150" />
          <el-table-column label="审核医师" align="center" prop="shys" width="150" />
          <el-table-column label="报告时间" align="center" prop="examinationDate" width="150" />
@@ -49,20 +91,44 @@
        </el-table>
      </div>
    </div>
    <div class="table-title">
  <h3>
    体检记录:
    <span class="highlight">姓名:{{ infoList.tjCustomerName || "暂无" }}</span>
    <span class="highlight">性别:{{ infoList.tjCustomerSex == 0 ? "男" : infoList.tjCustomerSex == 1 ? "女" : "暂无" }}</span>
    <span class="highlight">年龄:{{ infoList.tjCustomerAge || "暂无" }}</span>
    <span class="highlight">门诊号:{{ infoList.cardId || "暂无" }}</span>
  </h3>
</div>
    <el-table border height="350" ref="tab1" :data="checkList" v-loading="loading" style="width: 100%"
      <h3>
        体检记录:
        <span class="highlight">姓名:{{ infoList.tjCustomerName || "暂无" }}</span>
        <span class="highlight">
          性别:
          {{
            infoList.tjCustomerSex == 0
              ? "男"
              : infoList.tjCustomerSex == 1
              ? "女"
              : "暂无"
          }}
        </span>
        <span class="highlight">年龄:{{ infoList.tjCustomerAge || "暂无" }}</span>
        <span class="highlight">门诊号:{{ infoList.cardId || "暂无" }}</span>
      </h3>
    </div>
    <el-table
      border
      height="350"
      ref="tab1"
      :data="checkList"
      v-loading="loading"
      style="width: 100%"
      @selection-change="handleSelectionChangeSecond"
      :header-cell-style="{ background: '#aad8df', fontSize: '14px', color: '#333' }">
      :header-cell-style="{ background: '#aad8df', fontSize: '14px', color: '#333' }"
    >
      <el-table-column type="selection" width="60" />
      <el-table-column label="状态" align="center" prop="type" :show-overflow-tooltip="true" min-width="60">
      <el-table-column
        label="状态"
        align="center"
        prop="type"
        :show-overflow-tooltip="true"
        min-width="60"
      >
        <template slot-scope="scope">
          <span v-if="scope.row.type == '0'">未检</span>
          <span v-if="scope.row.type == '1'">已检</span>
@@ -79,347 +145,152 @@
    </el-table>
  </div>
</template>
<script>
import { getlisList, getJyTjList, asyncPacs } from "@/api/doctor/pacsCheck";
import { getOrderList } from "@/api/hosp/order";
import moment from "moment";
export default {
  dicts: ["dict_tj_status"],
  data() {
    return {
        isProcessing: false, // 防止死循环
    isDeselection: false, // 标记是否为取消选中
      infoList: {},
      dis: false,
      code: null,
      createTimeList: "",
      total: 0,
      loading: false,
      isSyncing: false,
      isFetchingRightTableData: false,
      queryParams: {
        name: null,
        start: null,
        end: null,
        tjNum: null,
      },
      checkStatus: "0",
      exaLists: [],
      selectedFirstTable: null,
      selectedSecondTable: [],
      form: {},
      clearTimeSet: null,
      tjNumbers: "",
      multipleSelection: "",
      tjnum: "",
      xiangmuList: [],
      checkList: [],
      tg: true,
      bh: true,
      pickerOptions: {
        shortcuts: [
          {
            text: "最近一周",
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
              picker.$emit("pick", [start, end]);
            },
          },
          {
            text: "最近一个月",
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
              picker.$emit("pick", [start, end]);
            },
          },
          {
            text: "最近三个月",
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
              picker.$emit("pick", [start, end]);
            },
          },
        ],
      },
      infoList: {},
      selectedFirstTable: [],
      selectedSecondTable: [],
      loading: false,
      isProcessingSelection: false,
    };
  },
  mounted() {
    this.$nextTick(() => {
      this.$refs.inputName.focus();
    });
  },
  methods: {
    handleDateChange(val) {
      if (val && val.length === 2) {
        this.queryParams.start = val[0];
        this.queryParams.end = val[1];
      } else {
        this.queryParams.start = null;
        this.queryParams.end = null;
      }
      console.log("Query Params:", this.queryParams);
    },
    tableRowClassName({ row }) {
      return !row.brid ? "row-disabled" : "";
    },
handleSelectionChange(val) {
  // 防止重复触发
  if (this.isProcessingSelection) {
    console.log('正在处理选择,跳过重复触发');
    return;
  }
  this.isProcessingSelection = true;
  // 如果没有选中行,清空所有选择并跳过接口调用
  if (val.length === 0) {
    console.log('检测到取消选中,清空所有选择');
    this.$refs.firstTable.clearSelection();
    this.selectedFirstTable = null;
    this.checkList = [];
    this.isDeselection = true;
    this.$nextTick(() => {
      this.isProcessingSelection = false;
      this.$refs.firstTable.$forceUpdate(); // 强制刷新表格
    });
    return;
  }
  // 检查是否点击了全选按钮(val 长度等于表格总行数)
  if (val.length === this.exaLists.length) {
    console.log('检测到全选,选中所有行');
    this.selectedFirstTable = [...this.exaLists]; // 选中所有行
    this.$refs.firstTable.clearSelection();
    this.exaLists.forEach(row => {
      if (!row.brid) {
        console.warn(`名称为 ${row.name} 的行没有申请单号,跳过选择`);
        return;
      }
      this.$refs.firstTable.toggleRowSelection(row, true, false);
    });
  } else {
    // 获取最新选中的行
    const latestSelectedRow = val[val.length - 1];
    // 检查是否点击了已选中的行(取消所有选中)
    const isTogglingSelectedRow = this.selectedFirstTable && this.selectedFirstTable.some(row => row.tempId === latestSelectedRow.tempId);
    if (isTogglingSelectedRow) {
      console.log('点击已选中行,取消所有选中');
      this.$refs.firstTable.clearSelection();
      this.selectedFirstTable = null;
      this.checkList = [];
      this.isDeselection = true;
      this.$nextTick(() => {
        this.isProcessingSelection = false;
        this.$refs.firstTable.$forceUpdate(); // 强制刷新表格
      });
      return;
    }
    // 正常选中逻辑:根据 brid 筛选
    const filterKey = 'brid';
    const filterValue = latestSelectedRow.brid;
    console.log(`按 ${filterKey} 筛选: ${filterValue}`);
    // 清空所有选中状态
    this.$refs.firstTable.clearSelection();
    // 选中与 brid 匹配的行
    const rowsToSelect = this.exaLists.filter(row => row.brid === filterValue);
    rowsToSelect.forEach(row => {
      if (!row.brid) {
        console.warn(`名称为 ${row.name} 的行没有申请单号,跳过选择`);
        return;
      }
      this.$refs.firstTable.toggleRowSelection(row, true, false);
    });
    // 更新 selectedFirstTable 为 brid 匹配的行
    this.selectedFirstTable = rowsToSelect;
  }
  // 调用右侧表格数据
  if (!this.isFetchingRightTableData && this.selectedFirstTable.length > 0) {
    this.isFetchingRightTableData = true;
    this.fetchRightTableData().finally(() => {
      this.isFetchingRightTableData = false;
      this.$nextTick(() => {
        // 仅更新样式,不触发新事件
        this.$refs.firstTable.$forceUpdate();
        this.isProcessingSelection = false;
      });
    });
  } else {
    this.isProcessingSelection = false;
  }
  // 重置 isDeselection 状态
  this.isDeselection = false;
},
    fetchRightTableData() {
      const code = this.queryParams.tjNum;
      if (!code) {
        console.warn('未提供体检号,跳过 fetchRightTableData');
        this.checkList = [];
        this.loading = false;
        return Promise.resolve();
      }
      this.loading = true;
      return getJyTjList(code)
        .then((response) => {
          this.checkList = response.data || [];
          this.loading = false;
        })
        .catch((error) => {
          console.error('获取 checkList 失败:', error);
          this.checkList = [];
          this.loading = false;
          throw error;
        });
    },
    handleSelectionChangeSecond(selectedRows) {
      this.selectedSecondTable = selectedRows;
      if (selectedRows.length > 1) {
        let del_row = selectedRows.shift();
        this.$refs.tab1.toggleRowSelection(del_row, false);
      }
      console.log("当前选中的行数据:", this.selectedSecondTable);
    },
    async handleQuery() {
      if (!this.queryParams.tjNum) {
        this.$message.error("体检号不能为空");
        return;
      }
      this.loading = true;
      try {
        // 并行执行两个查询
        const [orderResponse, lisResponse] = await Promise.all([
          getOrderList(this.queryParams).catch((error) => {
            console.error('获取 orderList 失败:', error);
            return { data: { list: [] } }; // 返回空数据以继续执行
          }),
          getOrderList(this.queryParams).catch(() => ({ data: { list: [] } })),
          getlisList(this.queryParams),
        ]);
        // 处理 getOrderList 结果
        if (orderResponse.data?.list?.length > 0) {
          this.infoList = orderResponse.data.list[0];
        } else {
          this.infoList = {};
          console.warn('getOrderList 返回空列表');
        }
        // 处理 getlisList 结果
        if (lisResponse.code === 200) {
          this.exaLists = lisResponse.data.map((item, index) => ({
            ...item,
            brid: item.brid?.trim(), // 清理空格
            mzh: item.mzh?.trim(),
            tempId: index,
          }));
          console.log('Loaded exaLists:', this.exaLists);
          // 获取右侧表格数据
          await this.fetchRightTableData();
        } else {
          this.exaLists = [];
          this.$message.error(lisResponse.msg || "查询检验记录失败");
        }
      } catch (error) {
        console.error('查询失败:', error);
        this.$message.error(error?.msg || "查询失败,请稍后重试");
        this.exaLists = [];
        this.checkList = [];
        this.infoList = {};
        this.infoList = orderResponse.data.list?.[0] || {};
        this.exaLists = lisResponse.data.map((item, index) => ({
          ...item,
          brid: item.brid?.trim(),
          tempId: index,
        }));
        await this.fetchRightTableData();
      } catch (e) {
        console.error(e);
      } finally {
        this.loading = false;
      }
      // 处理时间范围
      if (this.createTimeList) {
        this.queryParams.start = this.createTimeList[0];
        this.queryParams.end = this.createTimeList[1];
      } else {
        this.queryParams.start = null;
        this.queryParams.end = null;
      }
    },
    resetQuery() {
      this.createTimeList = [];
      this.resetForm("queryForm");
      this.queryParams = {
        name: null,
        start: null,
        end: null,
        tjNum: null,
      };
      this.checkList = [];
      this.queryParams.tjNum = null;
      this.exaLists = [];
      this.checkList = [];
      this.infoList = {};
    },
handleSelectionChange(selected) {
  if (this.isProcessingSelection) return;
  this.isProcessingSelection = true;
    handleCurrentChange(row) {
      this.currentRow = row;
  // 找到用户刚操作的那一条(对比之前和现在的差异)
  const old = this.selectedFirstTable;
  let changedRow = null;
  if (selected.length > old.length) {
    // 新增:找出新增的
    changedRow = selected.find(row => !old.includes(row));
  } else {
    // 删除:找出删除的
    changedRow = old.find(row => !selected.includes(row));
  }
  if (changedRow) {
    const group = this.exaLists.filter(r => r.brid === changedRow.brid);
    const isAdding = selected.length > old.length;
    this.$refs.firstTable.clearSelection();
    if (isAdding) {
      // 添加,整个组选上 + 原有选的 brid 也选上
      const allBrids = new Set();
      selected.forEach(row => allBrids.add(row.brid));
      allBrids.add(changedRow.brid);
      const newSelection = this.exaLists.filter(row => allBrids.has(row.brid));
      newSelection.forEach(row => {
        this.$refs.firstTable.toggleRowSelection(row, true);
      });
      this.selectedFirstTable = newSelection;
    } else {
      // 删除,整个组取消,再把剩下 brid 分组补回去
      const remaining = old.filter(row => row.brid !== changedRow.brid);
      const remainBrids = [...new Set(remaining.map(r => r.brid))];
      const newSelection = this.exaLists.filter(row => remainBrids.includes(row.brid));
      newSelection.forEach(row => {
        this.$refs.firstTable.toggleRowSelection(row, true);
      });
      this.selectedFirstTable = newSelection;
    }
  } else {
    this.selectedFirstTable = selected;
  }
  this.fetchRightTableData();
  this.isProcessingSelection = false;
},
    handleSelectionChangeSecond(selected) {
      this.selectedSecondTable = selected.slice(0, 1);
    },
    setTime() {
      this.clearTimeSet = setInterval(() => {
        this.$modal.closeLoading();
      }, 300000);
    fetchRightTableData() {
      if (!this.queryParams.tjNum) return;
      this.loading = true;
      return getJyTjList(this.queryParams.tjNum)
        .then(res => {
          this.checkList = res.data || [];
        })
        .finally(() => {
          this.loading = false;
        });
    },
    tongbu() {
      this.$modal.loading("正在同步,请稍候...");
      this.setTime();
      const requestData = {
        lis: this.selectedFirstTable ? this.selectedFirstTable.map((item) => ({
          ...item,
          tjNum: this.queryParams.tjNum,
        })) : [],
        jcxmid: this.selectedFirstTable && this.selectedFirstTable.length > 0 ? this.selectedFirstTable[0].jcxmid : null,
        shys: this.selectedFirstTable && this.selectedFirstTable.length > 0 ? this.selectedFirstTable[0].shys : null,
        lis: this.selectedFirstTable,
        tj: this.selectedSecondTable[0],
      };
      asyncPacs(requestData)
        .then((res) => {
          if (res.code === 200) {
            this.fetchRightTableData();
            clearInterval(this.clearTimeSet);
            this.clearTimeSet = null;
            this.$modal.closeLoading();
            this.$modal.msgSuccess("同步成功!");
          } else {
            this.$message.error(res.message || "同步失败,请稍后重试");
          }
        })
        .catch((error) => {
          clearInterval(this.clearTimeSet);
          this.clearTimeSet = null;
          this.$modal.closeLoading();
        });
      asyncPacs(requestData).then(res => {
        if (res.code === 200) {
          this.$message.success("同步成功!");
          this.fetchRightTableData();
        } else {
          this.$message.error(res.message || "同步失败");
        }
      });
    },
  },
};</script>
};
</script>
<style lang="scss" scoped>
.app-container {
  padding: 20px;
  background: #f5f7fa;
}
.table-header {
  text-align: center;
  background-color: #aad8df;
@@ -430,13 +301,11 @@
  margin-top: 10px;
  border-radius: 4px 4px 0 0;
}
.table-title {
  text-align: left;
  margin: 20px 0;
  padding: 10px 0;
}
.table-title h3 {
  font-size: 16px;
  color: #333;
@@ -447,27 +316,12 @@
  flex-wrap: wrap;
  gap: 20px;
}
.table-title .highlight {
  font-weight: bold;
  color: #2c3e50;
}
.el-table {
  border-radius: 4px;
  font-size: 14px;
}
.el-table .warning-row {
  background: #e5f3ff !important;
}
::v-deep .el-table__body tr.current-row > td {
  background: #edf2fa !important;
}
.row-disabled {
  color: #999;
  background-color: #f5f5f5;
}
</style>
</style>
src/views/hosp/project/index.vue
@@ -1,5 +1,5 @@
<template>
  <div class="app-container">
  <div class="app-container" v-loading="pageLoading">
    <el-row :gutter="20">
      <el-col :span="4" :xs="24">
        <div class="head-container">
@@ -12,7 +12,7 @@
            style="margin-bottom: 15px"
          />
        </div>
        <div class="scrollable-container">
        <div class="scrollable-container" v-loading="loadings">
          <div class="content">
            <el-tree
              :data="deptOptions"
@@ -25,7 +25,6 @@
              highlight-current
              @node-click="handleNodeClick"
              :render-content="renderContent"
              v-loading="loadings"
            />
          </div>
        </div>
@@ -87,6 +86,7 @@
              icon="el-icon-plus"
              size="mini"
              @click="handleUpdate1"
              :loading="updateLoading"
              v-hasPermi="['hosp:project:add']"
              >修改</el-button
            >
@@ -128,6 +128,7 @@
        <el-table
          v-if="refreshTable"
          v-loading="loading"
          style="width: 100%"
          :data="projectList"
          ref="tableRef"
          height="580"
@@ -308,6 +309,7 @@
      width="1200px"
      append-to-body
      :before-close="handleClose"
      v-loading="loading"
      :close-on-click-modal="false"
    >
      <el-form
@@ -636,10 +638,18 @@
            ></el-option>
          </el-select>
        </el-form-item>
         <el-form-item label="是否外送" prop="proDelivery">
          <el-select v-model="form.proDelivery" placeholder="请选择是否外送" style="width: 200px">
            <el-option v-for="dict in wsTypesy" :key="dict.value" :label="dict.label"
              :value="dict.value"></el-option>
        <el-form-item label="是否外送" prop="proDelivery">
          <el-select
            v-model="form.proDelivery"
            placeholder="请选择是否外送"
            style="width: 200px"
          >
            <el-option
              v-for="dict in wsTypesy"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            ></el-option>
          </el-select>
        </el-form-item>
      </el-form>
@@ -1012,6 +1022,7 @@
      deptOptions: [],
      dialogTableVisible: false,
      isPriceDisabled: false,
      pageLoading: true,
      sfxmList: [],
      deptOptionstree: [],
      defaultPropstree: {
@@ -1028,15 +1039,15 @@
          label: "否",
        },
      ],
         wsTypesy: [
      wsTypesy: [
        {
          value:"1",
          label:"是"
          value: "1",
          label: "是",
        },
        {
          value:"0",
          label:"否"
        }
          value: "0",
          label: "否",
        },
      ],
      xmmc: "",
      chargeId: [],
@@ -1046,6 +1057,8 @@
      ChangeList: [],
      loading: true,
      loadings: false,
      addLoading: false,
      updateLoading: false,
      key: "",
      ids: [],
      single: true,
@@ -1149,6 +1162,8 @@
        this.$nextTick(() => {
          const lastId = newVal[newVal.length - 1] || "532";
          const node = this.findNodeById(this.deptOptions, lastId);
          this.queryParams.proId = lastId
          this.getList()
          if (node) {
            this.$refs.tree1.setCurrentKey(lastId);
            const nodeElement = document.querySelector(
@@ -1177,16 +1192,18 @@
    },
  },
  created() {
    this.getConsumables();
    this.getDeptList();
    // this.getConsumables();
    // this.getDeptList();
    // this.getDeptTree().then(() => {
    //   this.precomputePinyin();
    // });
  },
  mounted() {
    this.getDeptTree().then(() => {
      this.precomputePinyin();
    });
    // this.getDeptTree().then(() => {
    //   this.precomputePinyin();
    // });
    // this.precomputePinyin();
    this.loadPage();
  },
  methods: {
    debounceFilter: debounce(function (val) {
@@ -1209,6 +1226,59 @@
      };
      traverse(this.deptOptions);
    },
    async loadPage() {
      this.pageLoading = true;
      try {
        await Promise.all([
          this.getDeptTree(), // 获取左侧树
          //this.getList(),  获取右侧表格
        ]);
      } catch (error) {
        console.error("加载页面出错", error);
      } finally {
        this.pageLoading = false; // 两个都加载完再关闭 loading
      }
    },
    async getDeptTree() {
      this.loadings = true;
      try {
        const response = await deptTree111();
        this.deptOptions = response.data;
        // 保证 treeId 是数组格式
        this.treeId = [];
        if (this.treeDate?.id) {
          this.treeId.push(this.treeDate.id);
        } else {
          this.treeId.push("532");
        }
      } catch (error) {
        console.error("加载部门树失败:", error);
      } finally {
        this.loadings = false; // loading 状态结束
      }
    },
    async getList() {
      this.loading = true;
      let data = {
        proName: this.queryParams.proName,
        checkType: this.queryParams.checkType,
        deptId: this.queryParams.deptId,
        proId: this.queryParams.proId,
      };
      try {
        const res = await getAllChildListById(data); // 替换成你实际的接口
        this.projectList = res.data.list;
      } finally {
        this.loading = false;
      }
    },
    filterNode(value, data) {
      if (!value) return true;
      const cached = this.pinyinCache.get(data.id);
@@ -1478,15 +1548,23 @@
      });
    },
    getDeptTree() {
      return deptTree111().then((response) => {
        this.deptOptions = response.data;
        this.treeId = [];
        if (this.treeDate.id) {
          this.treeId.push(this.treeDate.id);
        } else {
          this.treeId.push("532");
        }
      });
      this.loadings = true;
      return deptTree111()
        .then((response) => {
          this.deptOptions = response.data;
          this.treeId = [];
          if (this.treeDate.id) {
            this.treeId.push(this.treeDate.id);
          } else {
            this.treeId.push("532");
          }
        })
        .catch((err) => {
          console.error("加载部门树失败:", err);
        })
        .finally(() => {
          this.loadings = false; // 无论成功或失败,结束加载
        });
    },
    handleNodeClick(date) {
      this.treeDate = date;
@@ -1645,22 +1723,27 @@
      }
    },
    handleUpdate1() {
      this.updateLoading = true;
      this.form = {};
      this.form = this.xiugaiList;
      this.form.proStatus = this.form.proStatus.toString();
      this.form.sfcyyc = this.form.sfcyyc.toString();
      this.proParent = true;
      this.isPriceDisabled = true;
      getlist().then((response) => {
        if (response.code == 200) {
          this.loading = false;
          this.projectOptions = [];
          const project = { proId: 0, proName: "主类目", children: [] };
          project.children = this.handleTree(response.data.list, "proId");
          this.key = response.data.key;
          this.projectOptions.push(project);
        }
      });
      getlist()
        .then((response) => {
          if (response.code == 200) {
            this.loading = false;
            this.projectOptions = [];
            const project = { proId: 0, proName: "主类目", children: [] };
            project.children = this.handleTree(response.data.list, "proId");
            this.key = response.data.key;
            this.projectOptions.push(project);
          }
        })
        .finally(() => {
          this.updateLoading = false;
        });
      this.open = true;
    },
    handleUpdate(row) {
@@ -1697,9 +1780,9 @@
              }[item.tjType] || item.tjType;
          });
        }
        getlist().then((response) => {
         getlist().then((response) => {
          if (response.code == 200) {
            this.loading = false;
            // this.loading = false;
            this.projectOptions = [];
            const project = { proId: 0, proName: "主类目", children: [] };
            project.children = this.handleTree(response.data.list, "proId");
@@ -1707,6 +1790,7 @@
            this.projectOptions.push(project);
          }
        });
        // this.loadPage();
        this.open = true;
        this.title = "体检项目信息维护";
      });
@@ -1726,6 +1810,7 @@
    },
    submitForm() {
      this.noclick = true;
      this.pageLoading = true;
      this.$refs["form"].validate((valid) => {
        if (valid) {
          const isUpdate = this.form.proId != null;
@@ -1752,9 +1837,13 @@
          this.form.lisXmbm = this.form.lisXmbm;
          if (this.key === "N") {
            this.processSubmission(isUpdate, false);
            this.open = false;
            // this.pageLoading = false;
          } else if (this.key === "Y") {
            this.form.sfxmId = this.sfxmId;
            this.processSubmission(isUpdate, true);
            this.open = false;
            // this.pageLoading = false;
          }
        }
      });
@@ -1787,16 +1876,30 @@
        }
      }
    },
    handleSuccess(isY) {
    /* handleSuccess(isY) {
      this.cancel();
      this.getList();
      // this.getList();
      if (this.proParent || isY) {
        this.getDeptTree().then(() => {
          this.precomputePinyin();
        });
      }
      console.log(this.treeId);
    },
    }, */
    async handleSuccess(isY) {
  this.cancel();
  if (this.proParent || isY) {
    // 等待左侧树加载和拼音处理
    await this.getDeptTree();
    this.precomputePinyin();
  }
  // 等待右侧表格刷新
  await this.getList();
  this.pageLoading = false; // ✅ 统一加载状态控制
},
    submit() {
      this.ChangeList.forEach((item) => {
        this.form.proPrice = item.ckdj;
src/views/reservation/reservations/index.vue
@@ -1389,10 +1389,10 @@
                  style="width: 160px"
                />
              </el-form-item>
              <el-form-item label="单位名称" prop="company">
              <el-form-item label="单位名称" prop="companyId">
                <el-select
                  :remote-method="getRemoteData"
                  v-model="formIn.company"
                  v-model="formIn.companyId"
                  remote
                  default-first-option
                  allow-create
@@ -2121,7 +2121,7 @@
        cusSex: null,
        cusBrithday: null,
        cusAddr: null,
        cusPhone: null,
        phone: null,
        yykssj: "", // 预约开始时间
        yyjssj: "", // 预约结束时间
        cusPostcode: null,
@@ -2235,7 +2235,7 @@
        // cusAddr: [
        //   { required: true, message: "现居住地址不能为空", trigger: "blur" },
        // ],
        cusPhone: [
        phone: [
          { required: true, validator: checkPhoneNum, trigger: "blur" },
        ],
        reservationTime: [
@@ -2575,7 +2575,7 @@
        cusSex: null,
        cusBrithday: null,
        cusAddr: null,
        cusPhone: null,
        phone: null,
        yykssj: "", // 预约开始时间
        yyjssj: "", // 预约结束时间
        cusPostcode: null,
@@ -3169,7 +3169,7 @@
          name: this.formIn.name,
          nation: this.formIn.nation,
          pacId,
          phoe: this.formIn.cusPhone,
          phoe: this.formIn.phoe,
          reservationTime: this.formIn.reservationTime,
          sex: this.formIn.sex,
          timeRegion: this.formIn.timeRegion,
@@ -3210,8 +3210,10 @@
        };
      }
      tjReappoint(data).then((res) => {
        console.log("接口响应", res);
        if (res.code === 200) {
          this.$modal.msgSuccess("预约成功");
          Object.assign(this.formIn, data);
          this.getList();
          this.$tab.refreshPage();
          _this.drawer = false;
vue.config.js
@@ -54,8 +54,8 @@
      [process.env.VUE_APP_BASE_API]: {
        // target: `https://ltpeis.xaltjdkj.cn:5801/`,
        // target: `http://192.168.1.99:5012`,
        target: `http://192.168.1.113:5011`,
        // target: `http://192.168.1.244:5011`,
        // target: `http://192.168.1.113:5011`,
        target: `http://192.168.1.244:5011`,
        // // target: `http://192.168.0.99:8080/ltkj-admin`,
        // target: `https://ltpeis.xaltjdkj.cn:5011/ltkj-admin`,
        // target: `http://10.168.0.9:5011`,