wlzboy
2025-11-09 13a31edf7f569cdcf15d3c43a476a2c947f47fbf
feat: 增加hospdata表,同步sqlserver过来
17个文件已修改
30个文件已添加
3507 ■■■■■ 已修改文件
app/api/hospital.js 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/components/AttachmentUpload.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/create-emergency.vue 129 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/detail.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/index.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/HospDataController.java 85 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptRegionController.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TbHospDataController.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-prod.yml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/HospDataSyncTask.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/HospDataSyncDTO.java 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysDeptRegion.java 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/TbHospData.java 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/HospDataMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/HospDataSyncMapper.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SQLHospDataMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptRegionMapper.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TbHospDataMapper.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/IHospDataSyncDataService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/IHospDataSyncService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISQLHospDataService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ITbHospDataService.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/HospDataSyncDataServiceImpl.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/HospDataSyncServiceImpl.java 218 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SQLHospDataServiceImpl.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TbHospDataServiceImpl.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/HospDataMapper.xml 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/HospDataSyncMapper.xml 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SQLHospDataMapper.xml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysDeptRegionMapper.xml 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TbHospDataMapper.xml 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/test/java/com/ruoyi/system/service/LegacySystemHttpsTest.java 260 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/api/system/deptRegion.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/api/system/hosp.js 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/dept/index.vue 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/hosp/index.vue 411 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/HospData.sql 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/hosp_data_menu.sql 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/sys_dept_region.sql 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/tb_hosp_data.sql 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/api/hospital.js
@@ -3,15 +3,15 @@
/**
 * 搜索医院
 * @param {string} keyword 搜索关键词(医院名称、地址、简称、省市区)
 * @param {string} region 地域关键词(用于过滤省市区)
 * @param {number} deptId 部门ID(用于根据部门区域配置过滤医院)
 */
export function searchHospitals(keyword, region) {
export function searchHospitals(keyword, deptId) {
  return request({
    url: '/system/hospital/search',
    method: 'get',
    params: {
      keyword: keyword,
      region: region
      deptId: deptId
    }
  })
}
@@ -61,3 +61,19 @@
    }
  })
}
/**
 * 根据部门区域配置搜索医院(支持多级区域)
 * @param {string} keyword 搜索关键词
 * @param {number} deptId 部门ID
 */
export function searchHospitalsByDeptRegion(keyword, deptId) {
  return request({
    url: '/system/hospital/search/by-dept-region',
    method: 'get',
    params: {
      keyword: keyword,
      deptId: deptId
    }
  })
}
app/components/AttachmentUpload.vue
@@ -3,7 +3,7 @@
    <view class="detail-section">
      <view class="section-title">
        {{ title }}
        <button class="upload-btn" @click="showUploadDialog">上传附件</button>
        <button class="upload-btn" @click="showUploadDialog" v-if="!readonly">上传附件</button>
      </view>
      <view v-if="attachmentList && attachmentList.length > 0">
        <view class="attachment-item" v-for="(item, index) in attachmentList" :key="item.attachmentId">
@@ -20,7 +20,7 @@
          </view>
          <view class="attachment-actions">
            <button class="action-btn view-btn" @click="viewAttachment(item)">查看</button>
            <button class="action-btn delete-btn" @click="deleteAttachment(item.attachmentId, index)">删除</button>
            <button class="action-btn delete-btn" @click="deleteAttachment(item.attachmentId, index)" v-if="!readonly">删除</button>
          </view>
        </view>
      </view>
@@ -87,6 +87,11 @@
      autoLoad: {
        type: Boolean,
        default: true
      },
      // 是否只读模式(禁止上传和删除)
      readonly: {
        type: Boolean,
        default: false
      }
    },
    data() {
app/pages/task/create-emergency.vue
@@ -506,7 +506,7 @@
import { addTask } from "@/api/task"
import { listAvailableVehicles, getUserBoundVehicle } from "@/api/vehicle"
import { calculateDistance, baiduDistanceByAddress, baiduPlaceSuggestion } from "@/api/map"
import { searchHospitals, getFrequentOutHospitals, getFrequentInHospitals } from "@/api/hospital"
import { searchHospitals, getFrequentOutHospitals, getFrequentInHospitals, searchHospitalsByDeptRegion } from "@/api/hospital"
import { listUser } from "@/api/system/user"
import { searchIcd10 } from "@/api/icd10"
@@ -720,11 +720,14 @@
      this.selectedOrganizationServiceOrderClass = selected.serviceOrderClass || '' // 保存服务单编码
      // 从归属机构中提取地域关键词(去除“分公司”后缀)
      // 例如:“广州分公司” -> “广州”
      this.selectedRegion = selected.deptName.replace(/分公司$/g, '').trim()
      //如果出现广州总公司,也要去除“总公司”后缀
      this.selectedRegion = this.replaceRegion(selected.deptName);
      // 重新加载医院列表(带地域过滤)
      this.loadDefaultHospitals()
    },
    replaceRegion(region){
        return region.replace(/(分公司|总公司|总部)$/g, '').trim();
    },
    // 加载分公司数据(parent_id=100的部门)
    loadBranchCompanies() {
      listBranchCompany().then(response => {
@@ -744,7 +747,7 @@
            this.selectedOrganizationId = this.organizationOptions[index].deptId // 保存部门ID
            this.selectedOrganizationServiceOrderClass = this.organizationOptions[index].serviceOrderClass || '' // 保存服务单编码
            // 提取地域关键词
            this.selectedRegion = this.selectedOrganization.replace(/分公司$/g, '').trim()
            this.selectedRegion =this.replaceRegion(this.selectedOrganization);
            console.log('默认选中归属机构:', this.selectedOrganization, '部门ID:', this.selectedOrganizationId, '服务单编码:', this.selectedOrganizationServiceOrderClass, '地域:', this.selectedRegion)
            // 加载医院列表(带地域过滤)
            this.loadDefaultHospitals()
@@ -865,57 +868,17 @@
    
    // 加载默认医院列表(常用医院)
    loadDefaultHospitals() {
      // 检查是否有服务单编码
      if (!this.selectedOrganizationServiceOrderClass) {
        console.warn('未找到服务单编码,无法加载常用医院')
        // 如果没有服务单编码,降级为普通搜索(按地域过滤)
        this.loadDefaultHospitalsByRegion()
      // 检查是否有归属机构ID
      if (!this.selectedOrganizationId) {
        console.warn('未选择归属机构,无法加载医院列表')
        return
      }
      
      // 转出医院:加载当前分公司的常用转出医院
      getFrequentOutHospitals(this.selectedOrganizationServiceOrderClass, this.selectedRegion).then(response => {
        this.hospitalOutResults = response.data || []
        console.log('加载常用转出医院:', this.selectedOrganizationServiceOrderClass, '地域:', this.selectedRegion, '数量:', this.hospitalOutResults.length)
        // 如果没有常用医院,降级为普通搜索
        if (this.hospitalOutResults.length === 0) {
          console.log('未找到常用转出医院,降级为地域搜索')
          searchHospitals('', this.selectedRegion).then(res => {
            this.hospitalOutResults = res.data || []
          })
        }
      }).catch(error => {
        console.error('加载常用转出医院失败:', error)
        // 失败后降级为普通搜索
        searchHospitals('', this.selectedRegion).then(res => {
          this.hospitalOutResults = res.data || []
        })
      })
      // 转出医院:根据归属机构的区域配置加载
      this.loadHospitalsByDeptRegion('out')
      
      // 转入医院:加载当前分公司的常用转入医院(本地区域优先)
      getFrequentInHospitals(this.selectedOrganizationServiceOrderClass, '').then(response => {
        const allHospitals = response.data || []
        // 将医院按地域排序:本地区域优先
        this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
        console.log('加载常用转入医院:', this.selectedOrganizationServiceOrderClass, '数量:', this.hospitalInResults.length)
        // 如果没有常用医院,降级为普通搜索
        if (this.hospitalInResults.length === 0) {
          console.log('未找到常用转入医院,降级为全部医院')
          searchHospitals('', '').then(res => {
            const allHospitals = res.data || []
            this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
          })
        }
      }).catch(error => {
        console.error('加载常用转入医院失败:', error)
        // 失败后降级为普通搜索
        searchHospitals('', '').then(res => {
          const allHospitals = res.data || []
          this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
        })
      })
      // 转入医院:根据归属机构的区域配置加载
      this.loadHospitalsByDeptRegion('in')
    },
    
    // 降级加载医院(按地域过滤)
@@ -938,6 +901,33 @@
      }).catch(error => {
        console.error('加载转入医院列表失败:', error)
        this.hospitalInResults = []
      })
    },
    // 根据部门区域配置加载医院
    loadHospitalsByDeptRegion(type) {
      const deptId = this.selectedOrganizationId
      if (!deptId) {
        console.warn('部门ID不存在')
        return
      }
      // 调用后端接口,根据部门的区域配置查询医院
      searchHospitalsByDeptRegion('', deptId).then(response => {
        const hospitals = response.data || []
        if (type === 'out') {
          this.hospitalOutResults = hospitals
          console.log('加载转出医院(区域配置):部门', deptId, '数量:', this.hospitalOutResults.length)
        } else if (type === 'in') {
          // 转入医院按地域排序
          this.hospitalInResults = this.sortHospitalsByRegion(hospitals)
          console.log('加载转入医院(区域配置):部门', deptId, '数量:', this.hospitalInResults.length)
        }
      }).catch(error => {
        console.error('加载医院失败(区域配置):', error)
        // 失败后降级为普通搜索
        this.loadDefaultHospitalsByRegion()
      })
    },
    
@@ -995,21 +985,21 @@
            this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
            // 如果没有常用医院,降级为普通搜索
            if (this.hospitalOutResults.length === 0) {
              searchHospitals('', this.selectedRegion).then(res => {
              searchHospitals('', this.selectedOrganizationId).then(res => {
                const hospitals = res.data || []
                this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
              })
            }
          }).catch(error => {
            console.error('加载常用转出医院失败:', error)
            searchHospitals('', this.selectedRegion).then(res => {
            searchHospitals('', this.selectedOrganizationId).then(res => {
              const hospitals = res.data || []
              this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
            })
          })
        } else {
          // 没有服务单编码,使用普通搜索
          searchHospitals('', this.selectedRegion).then(response => {
          searchHospitals('', this.selectedOrganizationId).then(response => {
            const hospitals = response.data || []
            this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
          }).catch(error => {
@@ -1033,7 +1023,7 @@
      
      // 如果关键词为空,显示当前区域的医院
      if (!keyword || keyword.trim() === '') {
        searchHospitals('', this.selectedRegion).then(response => {
        searchHospitals('', this.selectedOrganizationId).then(response => {
          const hospitals = response.data || []
          // 确保"家中"在最前面
          this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
@@ -1053,13 +1043,13 @@
    
    // 搜索转出医院(仅限当前区域)
    searchHospitalOut(keyword) {
      // 传入关键词和地域过滤,只搜索当前区域的医院
      searchHospitals(keyword, this.selectedRegion).then(response => {
      // 传入关键词和部门ID,只搜索当前区域的医院
      searchHospitals(keyword, this.selectedOrganizationId).then(response => {
        const hospitals = response.data || []
        // 确保"家中"在最前面
        this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
        this.showHospitalOutResults = true
        console.log('搜索转出医院:', keyword, '区域:', this.selectedRegion, '结果数:', this.hospitalOutResults.length)
        console.log('搜索转出医院:', keyword, '部门ID:', this.selectedOrganizationId, '结果数:', this.hospitalOutResults.length)
      }).catch(error => {
        console.error('搜索转出医院失败:', error)
        this.hospitalOutResults = []
@@ -1123,21 +1113,21 @@
            this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
            // 如果没有常用医院,降级为普通搜索
            if (this.hospitalInResults.length === 0) {
              searchHospitals('', '').then(res => {
              searchHospitals('', null).then(res => {
                const allHospitals = res.data || []
                this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
              })
            }
          }).catch(error => {
            console.error('加载常用转入医院失败:', error)
            searchHospitals('', '').then(res => {
            searchHospitals('', null).then(res => {
              const allHospitals = res.data || []
              this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
            })
          })
        } else {
          // 没有服务单编码,使用普通搜索
          searchHospitals('', '').then(response => {
          searchHospitals('', null).then(response => {
            const allHospitals = response.data || []
            // 按地域排序:本地区域优先
            this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
@@ -1162,7 +1152,7 @@
      
      // 如果关键词为空,显示所有医院(本地区域优先)
      if (!keyword || keyword.trim() === '') {
        searchHospitals('', '').then(response => {
        searchHospitals('', null).then(response => {
          const allHospitals = response.data || []
          // 按地域排序:"家中"最前,本地区域优先
          this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
@@ -1182,8 +1172,8 @@
    
    // 搜索转入医院(不限区域,但本地区域优先)
    searchHospitalIn(keyword) {
      // 传入关键词,不传地域过滤(搜索所有区域)
      searchHospitals(keyword, '').then(response => {
      // 传入关键词,不传部门ID(搜索所有区域)
      searchHospitals(keyword, null).then(response => {
        const allHospitals = response.data || []
        // 按地域排序:"家中"最前,本地区域优先
        this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
@@ -1888,11 +1878,18 @@
        addTask(submitData).then(response => {
          this.loading = false
          this.$modal.showToast('任务创建成功')
          // 延迟跳转,让用户看到成功提示
          setTimeout(() => {
            // 跳转到任务列表并触发刷新
            uni.switchTab({
              url: '/pages/task/index'
              url: '/pages/task/index',
              success: () => {
                // 使用事件总线通知任务列表页面刷新
                uni.$emit('refreshTaskList')
              }
            })
          }, 1500)
          }, 1000)
        }).catch(error => {
          this.loading = false
          console.error('任务创建失败:', error)
app/pages/task/detail.vue
@@ -198,6 +198,7 @@
      <AttachmentUpload 
        :taskId="taskId" 
        title="任务附件"
        :readonly="isTaskFinished"
        @uploaded="onAttachmentUploaded"
        @deleted="onAttachmentDeleted"
      />
@@ -341,6 +342,13 @@
      }
    },
    computed: {
      // 判断任务是否已结束(已完成或已取消)
      isTaskFinished() {
        if (!this.taskDetail || !this.taskDetail.taskStatus) {
          return false
        }
        return ['COMPLETED', 'CANCELLED'].includes(this.taskDetail.taskStatus)
      },
      // 显示任务类型
      displayTaskType() {
        if (!this.taskDetail || !this.taskDetail.taskType) {
app/pages/task/index.vue
@@ -323,11 +323,28 @@
    },
    onLoad() {
      this.loadTaskList()
      // 监听任务列表刷新事件
      uni.$on('refreshTaskList', this.handleRefreshEvent)
    },
    onShow() {
      // 页面显示时刷新列表(从其他页面返回时)
      this.loadTaskList()
    },
    onUnload() {
      // 页面销毁时移除事件监听
      uni.$off('refreshTaskList', this.handleRefreshEvent)
    },
    onPullDownRefresh() {
      this.refreshList()
    },
    methods: {
      // 处理刷新事件
      handleRefreshEvent() {
        console.log('收到刷新任务列表事件')
        this.refreshList()
      },
      // 加载任务列表
      loadTaskList() {
        this.loading = true
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/HospDataController.java
@@ -4,6 +4,7 @@
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.HospData;
import com.ruoyi.system.mapper.HospDataMapper;
import com.ruoyi.system.service.ISQLHospDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -14,6 +15,8 @@
/**
 * 医院数据Controller
 * 从MySQL的tb_hosp_data表查询医院数据
 * hospId对应legacy_hosp_id字段(旧系统的医院ID)
 * 
 * @author ruoyi
 * @date 2024-01-16
@@ -24,28 +27,49 @@
    
    @Autowired
    private HospDataMapper hospDataMapper;
    @Autowired
    private ISQLHospDataService sqlHospDataService;
    
    /**
     * 搜索医院
     * 搜索医院(从MySQL tb_hosp_data表查询)
     * 支持根据医院名称、地址、地域进行模糊搜索
     * @param keyword 搜索关键词(医院名称、地址、简称、省市区)
     * @param region 地域关键词(用于过滤省市区)
     * @param deptId 部门ID(用于根据部门区域配置过滤医院)
     */
    @GetMapping("/search")
    public AjaxResult searchHospitals(
            @RequestParam(value = "keyword", required = false) String keyword,
            @RequestParam(value = "region", required = false) String region) {
        List<HospData> list = hospDataMapper.searchHospitals(keyword, region);
        Integer homeHospId=hospDataMapper.getHomeHospId();
        if(homeHospId>0 && list.stream().count()>0 && list.stream().filter(hospData -> hospData.getHospId().equals(homeHospId)).count()<=0) {
            HospData hospData=   hospDataMapper.selectHospDataById(homeHospId);
            list.add(0,hospData);
            @RequestParam(value = "deptId", required = false) Long deptId) {
        List<HospData> list;
        // 如果keyword为空,使用部门区域过滤查询
        if (keyword == null || keyword.trim().isEmpty()) {
            if (deptId != null) {
                list = hospDataMapper.searchHospitalsByDeptRegion(null, deptId);
            } else {
                list = hospDataMapper.searchHospitals(null, null);
            }
        } else {
                list = hospDataMapper.searchHospitals(keyword, null);
        }
        // 确保"家中"在结果中
        Integer homeHospId = hospDataMapper.getHomeHospId();
        if(homeHospId > 0 && !list.isEmpty() && list.stream().noneMatch(hospData -> hospData.getHospId().equals(homeHospId))) {
            HospData hospData = hospDataMapper.selectHospDataById(homeHospId);
            if (hospData != null) {
                list.add(0, hospData);
            }
        }
        return success(list);
    }
    
    /**
     * 根据ID获取医院详情
     * 根据ID获取医院详情(从MySQL tb_hosp_data表查询)
     * @param hospId 医院ID(对应legacy_hosp_id)
     */
    @GetMapping("/detail")
    public AjaxResult getHospitalDetail(@RequestParam("hospId") Integer hospId) {
@@ -54,7 +78,7 @@
    }
    
    /**
     * 获取常用转出医院列表
     * 获取常用转出医院列表(从MySQL tb_hosp_data表查询)
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @param region 地域关键词(可选)
     */
@@ -63,8 +87,10 @@
            @RequestParam("serviceOrdClass") String serviceOrdClass,
            @RequestParam(value = "region", required = false) String region) {
        // 查询常用转出医院ID列表
        List<Integer> hospIds = hospDataMapper.selectFrequentOutHospitalIds(serviceOrdClass);
        if (hospIds == null || hospIds.isEmpty()) {
        logger.info("getFrequentOutHospitals 传入的 serviceOrdClass :{}",serviceOrdClass);
        List<Integer> hospIds = sqlHospDataService.selectFrequentOutHospitalIds(serviceOrdClass);
        logger.info(" getFrequentOutHospitals 查询出来的 hospIds :{}",hospIds.toArray().length);
        if (hospIds.isEmpty()) {
            return success();
        }
@@ -75,7 +101,7 @@
    }
    
    /**
     * 获取常用转入医院列表
     * 获取常用转入医院列表(从MySQL tb_hosp_data表查询)
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @param region 地域关键词(可选)
     */
@@ -84,8 +110,10 @@
            @RequestParam("serviceOrdClass") String serviceOrdClass,
            @RequestParam(value = "region", required = false) String region) {
        // 查询常用转入医院ID列表
        List<Integer> hospIds = hospDataMapper.selectFrequentInHospitalIds(serviceOrdClass);
        if (hospIds == null || hospIds.isEmpty()) {
        logger.info("getFrequentInHospitals 传入的 serviceOrdClass {}",serviceOrdClass);
        List<Integer> hospIds = sqlHospDataService.selectFrequentInHospitalIds(serviceOrdClass);
        logger.info("getFrequentInHospitals 查询出来的 hospIds {}",hospIds.toArray().length);
        if (hospIds.isEmpty()) {
            return success();
        }
        Integer homeHospId=hospDataMapper.getHomeHospId();
@@ -99,4 +127,31 @@
        }
        return success(hospitals);
    }
    /**
     * 根据部门区域配置搜索医院(从MySQL tb_hosp_data表查询,支持省、市、县/区等多级区域)
     * @param keyword 搜索关键词
     * @param deptId 部门ID
     */
    @GetMapping("/search/by-dept-region")
    public AjaxResult searchHospitalsByDeptRegion(
            @RequestParam(value = "keyword", required = false) String keyword,
            @RequestParam("deptId") Long deptId) {
        logger.info("根据部门区域配置搜索医院:deptId={}, keyword={}", deptId, keyword);
        // 调用Mapper查询,自动根据部门的区域配置过滤医院
        List<HospData> hospitals = hospDataMapper.searchHospitalsByDeptRegion(keyword, deptId);
        logger.info("查询到医院数量:{}", hospitals.size());
        // 确保"家中"在结果中
        Integer homeHospId = hospDataMapper.getHomeHospId();
        if (homeHospId > 0 && hospitals.stream().noneMatch(h -> h.getHospId().equals(homeHospId))) {
            HospData homeHosp = hospDataMapper.selectHospDataById(homeHospId);
            if (homeHosp != null) {
                hospitals.add(0, homeHosp);
            }
        }
        return success(hospitals);
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptRegionController.java
New file
@@ -0,0 +1,68 @@
package com.ruoyi.web.controller.system;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.SysDeptRegion;
import com.ruoyi.system.mapper.SysDeptRegionMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * 部门区域管理Controller
 *
 * @author ruoyi
 */
@RestController
@RequestMapping("/system/dept/region")
public class SysDeptRegionController extends BaseController
{
    @Autowired
    private SysDeptRegionMapper deptRegionMapper;
    /**
     * 查询部门的区域列表
     */
    @PreAuthorize("@ss.hasPermi('system:dept:query')")
    @GetMapping("/list/{deptId}")
    public AjaxResult list(@PathVariable("deptId") Long deptId)
    {
        List<SysDeptRegion> list = deptRegionMapper.selectDeptRegionListByDeptId(deptId);
        return success(list);
    }
    /**
     * 新增部门区域
     */
    @PreAuthorize("@ss.hasPermi('system:dept:edit')")
    @PostMapping
    public AjaxResult add(@Validated @RequestBody SysDeptRegion deptRegion)
    {
        deptRegion.setCreateBy(getUsername());
        return toAjax(deptRegionMapper.insertDeptRegion(deptRegion));
    }
    /**
     * 修改部门区域
     */
    @PreAuthorize("@ss.hasPermi('system:dept:edit')")
    @PutMapping
    public AjaxResult edit(@Validated @RequestBody SysDeptRegion deptRegion)
    {
        deptRegion.setUpdateBy(getUsername());
        return toAjax(deptRegionMapper.updateDeptRegion(deptRegion));
    }
    /**
     * 删除部门区域
     */
    @PreAuthorize("@ss.hasPermi('system:dept:edit')")
    @DeleteMapping("/{regionId}")
    public AjaxResult remove(@PathVariable Long regionId)
    {
        return toAjax(deptRegionMapper.deleteDeptRegionById(regionId));
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/TbHospDataController.java
New file
@@ -0,0 +1,112 @@
package com.ruoyi.web.controller.system;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.TbHospData;
import com.ruoyi.system.service.IHospDataSyncService;
import com.ruoyi.system.service.ITbHospDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * 医院数据Controller
 *
 * @author ruoyi
 */
@RestController
@RequestMapping("/system/hosp")
public class TbHospDataController extends BaseController
{
    @Autowired
    private ITbHospDataService tbHospDataService;
    @Autowired
    private IHospDataSyncService hospDataSyncService;
    /**
     * 查询医院数据列表
     */
    @PreAuthorize("@ss.hasPermi('system:hosp:list')")
    @GetMapping("/list")
    public TableDataInfo list(TbHospData tbHospData)
    {
        startPage();
        List<TbHospData> list = tbHospDataService.selectTbHospDataList(tbHospData);
        return getDataTable(list);
    }
    /**
     * 导出医院数据列表
     */
    @PreAuthorize("@ss.hasPermi('system:hosp:export')")
    @Log(title = "医院数据", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, TbHospData tbHospData)
    {
        List<TbHospData> list = tbHospDataService.selectTbHospDataList(tbHospData);
        ExcelUtil<TbHospData> util = new ExcelUtil<TbHospData>(TbHospData.class);
        util.exportExcel(response, list, "医院数据");
    }
    /**
     * 获取医院数据详细信息
     */
    @PreAuthorize("@ss.hasPermi('system:hosp:query')")
    @GetMapping(value = "/{hospId}")
    public AjaxResult getInfo(@PathVariable("hospId") Long hospId)
    {
        return success(tbHospDataService.selectTbHospDataById(hospId));
    }
    /**
     * 新增医院数据
     */
    @PreAuthorize("@ss.hasPermi('system:hosp:add')")
    @Log(title = "医院数据", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody TbHospData tbHospData)
    {
        return toAjax(tbHospDataService.insertTbHospData(tbHospData));
    }
    /**
     * 修改医院数据
     */
    @PreAuthorize("@ss.hasPermi('system:hosp:edit')")
    @Log(title = "医院数据", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody TbHospData tbHospData)
    {
        return toAjax(tbHospDataService.updateTbHospData(tbHospData));
    }
    /**
     * 删除医院数据
     */
    @PreAuthorize("@ss.hasPermi('system:hosp:remove')")
    @Log(title = "医院数据", businessType = BusinessType.DELETE)
    @DeleteMapping("/{hospIds}")
    public AjaxResult remove(@PathVariable Long[] hospIds)
    {
        return toAjax(tbHospDataService.deleteTbHospDataByIds(hospIds));
    }
    /**
     * 从SQL Server同步医院数据
     */
    @PreAuthorize("@ss.hasPermi('system:hosp:sync')")
    @Log(title = "医院数据同步", businessType = BusinessType.OTHER)
    @PostMapping("/sync")
    public AjaxResult sync()
    {
        return hospDataSyncService.syncHospData();
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java
@@ -65,7 +65,6 @@
    /**
     * 查询任务附件列表
     */
    @PreAuthorize("@ss.hasPermi('task:general:query')")
    @GetMapping("/list/{taskId}")
    public AjaxResult list(@PathVariable("taskId") Long taskId) {
        SysTask task = sysTaskService.getTaskDetail(taskId);
@@ -75,7 +74,7 @@
    /**
     * 上传任务附件
     */
    @PreAuthorize("@ss.hasPermi('task:general:edit')")
    @Log(title = "任务附件", businessType = BusinessType.INSERT)
    @PostMapping("/upload/{taskId}")
    public AjaxResult upload(@PathVariable("taskId") Long taskId, 
@@ -119,7 +118,6 @@
    /**
     * 删除任务附件
     */
    @PreAuthorize("@ss.hasPermi('task:general:edit')")
    @Log(title = "任务附件", businessType = BusinessType.DELETE)
    @DeleteMapping("/{attachmentId}")
    public AjaxResult remove(@PathVariable("attachmentId") Long attachmentId) {
@@ -138,7 +136,6 @@
    /**
     * 从微信mediaId上传附件(微信小程序专用)
     */
    @PreAuthorize("@ss.hasPermi('task:general:edit')")
    @Log(title = "任务附件", businessType = BusinessType.INSERT)
    @PostMapping("/uploadFromWechat/{taskId}")
    public AjaxResult uploadFromWechat(@PathVariable("taskId") Long taskId,
ruoyi-admin/src/main/resources/application-prod.yml
@@ -96,7 +96,7 @@
evaluationWechat:
  appId: wx70f6a7346ee842c0
  appSecret: 2d6c59de85e876b7eadebeba62e5417a
  redirectUri: http://yourdomain.com/evaluation
  redirectUri: http://gzgj.966120.com.cn/evaluation
# 调度用的weixin配置
transferConfigWeixin:
  appId: wx40692cc44953a8cb
@@ -109,11 +109,16 @@
  map:
    ak: GX7G1RmAbTEQHor9NKpzRiB2jerqaY1E
# 旧系统配置
# 旧系统配置
legacy:
  system:
    # 旧系统基础URL (必须配置)
    # 示例: http://192.168.1.100:8080 或 http://legacy.yourdomain.com
    base-url: https://sys.966120.com.cn
    # 文件上传URL
    fileUploadUrl: https://sys.966120.com.cn/weixin/upload_file.php
    # 文件下载URL
    fileServerUrl: https://sys.966120.com.cn
    # 急救转运创建接口路径 (可选,默认值如下)
    emergency-create-path: /admin_save_19.gds
ruoyi-admin/src/main/resources/application.yml
@@ -9,7 +9,7 @@
  # 实例演示开关
  demoEnabled: true
  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
  profile: D:/ruoyi/uploadPath
  profile: D:/GZGJ/uploadPath
  # 获取ip地址开关
  addressEnabled: false
  # 验证码类型 math 数字计算 char 字符验证
ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java
@@ -344,6 +344,7 @@
    /**
     * 发送文件上传请求(支持自定义文件名)
     * 支持HTTP和HTTPS,HTTPS会自动忽略SSL证书验证
     *
     * @param url 请求URL
     * @param params 请求参数(包含文件流)
@@ -365,7 +366,18 @@
            // 创建连接
            URL requestUrl = new URL(url);
            connection = (HttpURLConnection) requestUrl.openConnection();
            // 如果是HTTPS请求,配置SSL信任所有证书
            if (connection instanceof HttpsURLConnection) {
                HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
                SSLContext sc = SSLContext.getInstance("TLS");
                sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
                httpsConnection.setSSLSocketFactory(sc.getSocketFactory());
                httpsConnection.setHostnameVerifier(new TrustAnyHostnameVerifier());
                log.info("配置HTTPS连接,忽略SSL证书验证");
            }
            // 设置请求属性
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java
@@ -51,7 +51,7 @@
            {
                DynamicDataSourceContextHolder.setDataSourceType(newDataSourceType);
                isNewDataSource = true;
                logger.debug("切换数据源: {} -> {}", oldDataSourceType, newDataSourceType);
                //logger.debug("切换数据源: {} -> {}", oldDataSourceType, newDataSourceType);
            }
        }
@@ -68,12 +68,12 @@
                if (StringUtils.isNotEmpty(oldDataSourceType))
                {
                    DynamicDataSourceContextHolder.setDataSourceType(oldDataSourceType);
                    logger.debug("恢复数据源: {}", oldDataSourceType);
                    //logger.debug("恢复数据源: {}", oldDataSourceType);
                }
                else
                {
                    DynamicDataSourceContextHolder.clearDataSourceType();
                    logger.debug("清除数据源,恢复到默认数据源");
                    //logger.debug("清除数据源,恢复到默认数据源");
                }
            }
        }
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/HospDataSyncTask.java
New file
@@ -0,0 +1,63 @@
package com.ruoyi.quartz.task;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.service.IHospDataSyncService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * 医院数据同步定时任务
 *
 * @author ruoyi
 */
@Component("hospDataSyncTask")
public class HospDataSyncTask
{
    private static final Logger log = LoggerFactory.getLogger(HospDataSyncTask.class);
    @Autowired
    private IHospDataSyncService hospDataSyncService;
    /**
     * 同步医院数据
     */
    public void syncHospData()
    {
        log.info("开始执行医院数据同步定时任务...");
        try
        {
            AjaxResult result = hospDataSyncService.syncHospData();
            if (result.isSuccess())
            {
                log.info("医院数据同步成功: {}", result.get(AjaxResult.MSG_TAG));
            }
            else if (result.isWarn())
            {
                log.warn("医院数据同步警告: {}", result.get(AjaxResult.MSG_TAG));
            }
            else
            {
                log.error("医院数据同步失败: {}", result.get(AjaxResult.MSG_TAG));
            }
        }
        catch (Exception e)
        {
            log.error("医院数据同步异常", e);
        }
    }
    /**
     * 带参数的同步方法(可以在系统管理-定时任务中配置参数)
     *
     * @param params 参数
     */
    public void syncHospDataWithParams(String params)
    {
        log.info("开始执行医院数据同步定时任务(参数: {})...", params);
        syncHospData();
    }
}
ruoyi-system/pom.xml
@@ -39,6 +39,11 @@
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
ruoyi-system/src/main/java/com/ruoyi/system/domain/HospDataSyncDTO.java
New file
@@ -0,0 +1,185 @@
package com.ruoyi.system.domain;
/**
 * 医院数据同步DTO
 * 用于从 SQL Server 查询医院数据
 *
 * @author ruoyi
 */
public class HospDataSyncDTO
{
    /** 医院ID */
    private Integer hospId;
    /** 医院名称 */
    private String hospName;
    /** 城市ID */
    private Integer hospCityId;
    /** 医院简称 */
    private String hospShort;
    /** 省份 */
    private String hopsProvince;
    /** 城市 */
    private String hopsCity;
    /** 区域 */
    private String hopsArea;
    /** 医院地址 */
    private String hospAddress;
    /** 医院电话 */
    private String hospTel;
    /** 单位ID */
    private Integer hospUnitId;
    /** 状态 */
    private Integer hospState;
    /** OA ID */
    private String hospOaId;
    /** 介绍人ID */
    private Integer hospIntroducerId;
    /** 介绍日期 */
    private String hospIntroducerDate;
    /** 医院级别 */
    private Integer hospLevel;
    public Integer getHospId() {
        return hospId;
    }
    public void setHospId(Integer hospId) {
        this.hospId = hospId;
    }
    public String getHospName() {
        return hospName;
    }
    public void setHospName(String hospName) {
        this.hospName = hospName;
    }
    public Integer getHospCityId() {
        return hospCityId;
    }
    public void setHospCityId(Integer hospCityId) {
        this.hospCityId = hospCityId;
    }
    public String getHospShort() {
        return hospShort;
    }
    public void setHospShort(String hospShort) {
        this.hospShort = hospShort;
    }
    public String getHopsProvince() {
        return hopsProvince;
    }
    public void setHopsProvince(String hopsProvince) {
        this.hopsProvince = hopsProvince;
    }
    public String getHopsCity() {
        return hopsCity;
    }
    public void setHopsCity(String hopsCity) {
        this.hopsCity = hopsCity;
    }
    public String getHopsArea() {
        return hopsArea;
    }
    public void setHopsArea(String hopsArea) {
        this.hopsArea = hopsArea;
    }
    public String getHospAddress() {
        return hospAddress;
    }
    public void setHospAddress(String hospAddress) {
        this.hospAddress = hospAddress;
    }
    public String getHospTel() {
        return hospTel;
    }
    public void setHospTel(String hospTel) {
        this.hospTel = hospTel;
    }
    public Integer getHospUnitId() {
        return hospUnitId;
    }
    public void setHospUnitId(Integer hospUnitId) {
        this.hospUnitId = hospUnitId;
    }
    public Integer getHospState() {
        return hospState;
    }
    public void setHospState(Integer hospState) {
        this.hospState = hospState;
    }
    public String getHospOaId() {
        return hospOaId;
    }
    public void setHospOaId(String hospOaId) {
        this.hospOaId = hospOaId;
    }
    public Integer getHospIntroducerId() {
        return hospIntroducerId;
    }
    public void setHospIntroducerId(Integer hospIntroducerId) {
        this.hospIntroducerId = hospIntroducerId;
    }
    public String getHospIntroducerDate() {
        return hospIntroducerDate;
    }
    public void setHospIntroducerDate(String hospIntroducerDate) {
        this.hospIntroducerDate = hospIntroducerDate;
    }
    public Integer getHospLevel() {
        return hospLevel;
    }
    public void setHospLevel(Integer hospLevel) {
        this.hospLevel = hospLevel;
    }
    @Override
    public String toString() {
        return "HospDataSyncDTO{" +
                "hospId=" + hospId +
                ", hospName='" + hospName + '\'' +
                ", hopsCity='" + hopsCity + '\'' +
                ", hopsArea='" + hopsArea + '\'' +
                '}';
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysDeptRegion.java
New file
@@ -0,0 +1,152 @@
package com.ruoyi.system.domain;
import com.ruoyi.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
 * 部门管理区域对象 sys_dept_region
 *
 * @author ruoyi
 */
public class SysDeptRegion extends BaseEntity
{
    private static final long serialVersionUID = 1L;
    /** 区域ID */
    private Long regionId;
    /** 部门ID */
    private Long deptId;
    /** 省份 */
    private String province;
    /** 城市 */
    private String city;
    /** 县/区 */
    private String area;
    /** 详细地址 */
    private String address;
    /** 指定医院名称 */
    private String hospitalName;
    /** 区域类型:AREA-地域范围,HOSPITAL-指定医院 */
    private String regionType;
    /** 状态(0正常 1停用) */
    private String status;
    public Long getRegionId()
    {
        return regionId;
    }
    public void setRegionId(Long regionId)
    {
        this.regionId = regionId;
    }
    public Long getDeptId()
    {
        return deptId;
    }
    public void setDeptId(Long deptId)
    {
        this.deptId = deptId;
    }
    public String getProvince()
    {
        return province;
    }
    public void setProvince(String province)
    {
        this.province = province;
    }
    public String getCity()
    {
        return city;
    }
    public void setCity(String city)
    {
        this.city = city;
    }
    public String getArea()
    {
        return area;
    }
    public void setArea(String area)
    {
        this.area = area;
    }
    public String getAddress()
    {
        return address;
    }
    public void setAddress(String address)
    {
        this.address = address;
    }
    public String getHospitalName()
    {
        return hospitalName;
    }
    public void setHospitalName(String hospitalName)
    {
        this.hospitalName = hospitalName;
    }
    public String getRegionType()
    {
        return regionType;
    }
    public void setRegionType(String regionType)
    {
        this.regionType = regionType;
    }
    public String getStatus()
    {
        return status;
    }
    public void setStatus(String status)
    {
        this.status = status;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
            .append("regionId", getRegionId())
            .append("deptId", getDeptId())
            .append("province", getProvince())
            .append("city", getCity())
            .append("area", getArea())
            .append("address", getAddress())
            .append("hospitalName", getHospitalName())
            .append("regionType", getRegionType())
            .append("status", getStatus())
            .append("remark", getRemark())
            .append("createBy", getCreateBy())
            .append("createTime", getCreateTime())
            .append("updateBy", getUpdateBy())
            .append("updateTime", getUpdateTime())
            .toString();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/TbHospData.java
New file
@@ -0,0 +1,234 @@
package com.ruoyi.system.domain;
import com.ruoyi.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
 * 医院数据对象 tb_hosp_data
 *
 * @author ruoyi
 */
public class TbHospData extends BaseEntity
{
    private static final long serialVersionUID = 1L;
    /** 医院ID(自增主键) */
    private Long hospId;
    /** 旧系统医院ID(对应SQL Server中的HospID) */
    private Integer legacyHospId;
    /** 医院名称 */
    private String hospName;
    /** 城市ID */
    private Integer hospCityId;
    /** 医院简称 */
    private String hospShort;
    /** 省份 */
    private String hopsProvince;
    /** 城市 */
    private String hopsCity;
    /** 区域 */
    private String hopsArea;
    /** 医院地址 */
    private String hospAddress;
    /** 医院电话 */
    private String hospTel;
    /** 单位ID */
    private Integer hospUnitId;
    /** 状态 */
    private Integer hospState;
    /** OA ID */
    private String hospOaId;
    /** 介绍人ID */
    private Integer hospIntroducerId;
    /** 介绍日期 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date hospIntroducerDate;
    /** 医院级别 */
    private Integer hospLevel;
    /** 数据状态(0正常 1停用) */
    private String status;
    public Long getHospId() {
        return hospId;
    }
    public void setHospId(Long hospId) {
        this.hospId = hospId;
    }
    public Integer getLegacyHospId() {
        return legacyHospId;
    }
    public void setLegacyHospId(Integer legacyHospId) {
        this.legacyHospId = legacyHospId;
    }
    public String getHospName() {
        return hospName;
    }
    public void setHospName(String hospName) {
        this.hospName = hospName;
    }
    public Integer getHospCityId() {
        return hospCityId;
    }
    public void setHospCityId(Integer hospCityId) {
        this.hospCityId = hospCityId;
    }
    public String getHospShort() {
        return hospShort;
    }
    public void setHospShort(String hospShort) {
        this.hospShort = hospShort;
    }
    public String getHopsProvince() {
        return hopsProvince;
    }
    public void setHopsProvince(String hopsProvince) {
        this.hopsProvince = hopsProvince;
    }
    public String getHopsCity() {
        return hopsCity;
    }
    public void setHopsCity(String hopsCity) {
        this.hopsCity = hopsCity;
    }
    public String getHopsArea() {
        return hopsArea;
    }
    public void setHopsArea(String hopsArea) {
        this.hopsArea = hopsArea;
    }
    public String getHospAddress() {
        return hospAddress;
    }
    public void setHospAddress(String hospAddress) {
        this.hospAddress = hospAddress;
    }
    public String getHospTel() {
        return hospTel;
    }
    public void setHospTel(String hospTel) {
        this.hospTel = hospTel;
    }
    public Integer getHospUnitId() {
        return hospUnitId;
    }
    public void setHospUnitId(Integer hospUnitId) {
        this.hospUnitId = hospUnitId;
    }
    public Integer getHospState() {
        return hospState;
    }
    public void setHospState(Integer hospState) {
        this.hospState = hospState;
    }
    public String getHospOaId() {
        return hospOaId;
    }
    public void setHospOaId(String hospOaId) {
        this.hospOaId = hospOaId;
    }
    public Integer getHospIntroducerId() {
        return hospIntroducerId;
    }
    public void setHospIntroducerId(Integer hospIntroducerId) {
        this.hospIntroducerId = hospIntroducerId;
    }
    public Date getHospIntroducerDate() {
        return hospIntroducerDate;
    }
    public void setHospIntroducerDate(Date hospIntroducerDate) {
        this.hospIntroducerDate = hospIntroducerDate;
    }
    public Integer getHospLevel() {
        return hospLevel;
    }
    public void setHospLevel(Integer hospLevel) {
        this.hospLevel = hospLevel;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
            .append("hospId", getHospId())
            .append("legacyHospId", getLegacyHospId())
            .append("hospName", getHospName())
            .append("hospCityId", getHospCityId())
            .append("hospShort", getHospShort())
            .append("hopsProvince", getHopsProvince())
            .append("hopsCity", getHopsCity())
            .append("hopsArea", getHopsArea())
            .append("hospAddress", getHospAddress())
            .append("hospTel", getHospTel())
            .append("hospUnitId", getHospUnitId())
            .append("hospState", getHospState())
            .append("hospOaId", getHospOaId())
            .append("hospIntroducerId", getHospIntroducerId())
            .append("hospIntroducerDate", getHospIntroducerDate())
            .append("hospLevel", getHospLevel())
            .append("status", getStatus())
            .append("remark", getRemark())
            .append("createBy", getCreateBy())
            .append("createTime", getCreateTime())
            .append("updateBy", getUpdateBy())
            .append("updateTime", getUpdateTime())
            .toString();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/HospDataMapper.java
@@ -13,7 +13,7 @@
 * @author ruoyi
 * @date 2024-01-16
 */
@DataSource(DataSourceType.SQLSERVER)
public interface HospDataMapper {
    
    /**
@@ -33,22 +33,9 @@
     */
    HospData selectHospDataById(@Param("hospId") Integer hospId);
    
    /**
     * 查询常用转出医院ID列表
     *
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @return 常用转出医院ID列表
     */
    List<Integer> selectFrequentOutHospitalIds(@Param("serviceOrdClass") String serviceOrdClass);
    Integer getHomeHospId();
    /**
     * 查询常用转入医院ID列表
     *
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @return 常用转入医院ID列表
     */
    List<Integer> selectFrequentInHospitalIds(@Param("serviceOrdClass") String serviceOrdClass);
    
    /**
     * 根据医院ID列表查询医院信息
@@ -58,4 +45,13 @@
     * @return 医院列表
     */
    List<HospData> selectHospDataByIds(@Param("hospIds") List<Integer> hospIds, @Param("region") String region);
    /**
     * 根据区域配置查询医院(支持省、市、县/区等多级区域)
     *
     * @param keyword 搜索关键词
     * @param deptId 部门ID(用于查询该部门的区域配置)
     * @return 医院列表
     */
    List<HospData> searchHospitalsByDeptRegion(@Param("keyword") String keyword, @Param("deptId") Long deptId);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/HospDataSyncMapper.java
New file
@@ -0,0 +1,23 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.system.domain.HospDataSyncDTO;
import java.util.List;
/**
 * 医院数据同步Mapper接口(从SQL Server查询数据)
 *
 * @author ruoyi
 */
@DataSource(DataSourceType.SQLSERVER)
public interface HospDataSyncMapper
{
    /**
     * 从SQL Server查询所有医院数据
     *
     * @return 医院数据列表
     */
    List<HospDataSyncDTO> selectAllHospDataFromSqlServer();
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SQLHospDataMapper.java
New file
@@ -0,0 +1,24 @@
package com.ruoyi.system.mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface SQLHospDataMapper {
    /**
     * 查询常用转入医院ID列表
     *
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @return 常用转入医院ID列表
     */
    List<Integer> selectFrequentInHospitalIds(@Param("serviceOrdClass") String serviceOrdClass);
    /**
     * 查询常用转出医院ID列表
     *
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @return 常用转出医院ID列表
     */
    List<Integer> selectFrequentOutHospitalIds(@Param("serviceOrdClass") String serviceOrdClass);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysDeptRegionMapper.java
New file
@@ -0,0 +1,62 @@
package com.ruoyi.system.mapper;
import com.ruoyi.system.domain.SysDeptRegion;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * 部门管理区域Mapper接口
 *
 * @author ruoyi
 */
public interface SysDeptRegionMapper
{
    /**
     * 查询部门管理区域列表
     *
     * @param deptId 部门ID
     * @return 部门管理区域集合
     */
    List<SysDeptRegion> selectDeptRegionListByDeptId(@Param("deptId") Long deptId);
    /**
     * 批量查询多个部门的管理区域
     *
     * @param deptIds 部门ID列表
     * @return 部门管理区域集合
     */
    List<SysDeptRegion> selectDeptRegionListByDeptIds(@Param("deptIds") List<Long> deptIds);
    /**
     * 新增部门管理区域
     *
     * @param deptRegion 部门管理区域
     * @return 结果
     */
    int insertDeptRegion(SysDeptRegion deptRegion);
    /**
     * 修改部门管理区域
     *
     * @param deptRegion 部门管理区域
     * @return 结果
     */
    int updateDeptRegion(SysDeptRegion deptRegion);
    /**
     * 删除部门管理区域
     *
     * @param regionId 区域ID
     * @return 结果
     */
    int deleteDeptRegionById(@Param("regionId") Long regionId);
    /**
     * 删除部门的所有管理区域
     *
     * @param deptId 部门ID
     * @return 结果
     */
    int deleteDeptRegionByDeptId(@Param("deptId") Long deptId);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TbHospDataMapper.java
New file
@@ -0,0 +1,70 @@
package com.ruoyi.system.mapper;
import com.ruoyi.system.domain.TbHospData;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * 医院数据Mapper接口
 *
 * @author ruoyi
 */
public interface TbHospDataMapper
{
    /**
     * 查询医院数据列表
     *
     * @param tbHospData 医院数据
     * @return 医院数据集合
     */
    List<TbHospData> selectTbHospDataList(TbHospData tbHospData);
    /**
     * 根据医院ID查询医院数据
     *
     * @param hospId 医院ID
     * @return 医院数据
     */
    TbHospData selectTbHospDataById(@Param("hospId") Long hospId);
    /**
     * 根据旧系统医院ID查询医院数据
     *
     * @param legacyHospId 旧系统医院ID
     * @return 医院数据
     */
    TbHospData selectTbHospDataByLegacyId(@Param("legacyHospId") Integer legacyHospId);
    /**
     * 新增医院数据
     *
     * @param tbHospData 医院数据
     * @return 结果
     */
    int insertTbHospData(TbHospData tbHospData);
    /**
     * 修改医院数据
     *
     * @param tbHospData 医院数据
     * @return 结果
     */
    int updateTbHospData(TbHospData tbHospData);
    /**
     * 删除医院数据
     *
     * @param hospId 医院ID
     * @return 结果
     */
    int deleteTbHospDataById(@Param("hospId") Long hospId);
    /**
     * 批量删除医院数据
     *
     * @param hospIds 需要删除的数据ID
     * @return 结果
     */
    int deleteTbHospDataByIds(@Param("hospIds") Long[] hospIds);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/IHospDataSyncDataService.java
New file
@@ -0,0 +1,20 @@
package com.ruoyi.system.service;
import com.ruoyi.system.domain.HospDataSyncDTO;
import java.util.List;
/**
 * 医院数据同步数据查询Service接口(从SQL Server查询)
 *
 * @author ruoyi
 */
public interface IHospDataSyncDataService
{
    /**
     * 从SQL Server查询所有医院数据
     *
     * @return 医院数据列表
     */
    List<HospDataSyncDTO> selectAllHospDataFromSqlServer();
}
ruoyi-system/src/main/java/com/ruoyi/system/service/IHospDataSyncService.java
New file
@@ -0,0 +1,18 @@
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.AjaxResult;
/**
 * 医院数据同步Service接口
 *
 * @author ruoyi
 */
public interface IHospDataSyncService
{
    /**
     * 从SQL Server同步医院数据到MySQL
     *
     * @return 同步结果
     */
    AjaxResult syncHospData();
}
ruoyi-system/src/main/java/com/ruoyi/system/service/ISQLHospDataService.java
New file
@@ -0,0 +1,25 @@
package com.ruoyi.system.service;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ISQLHospDataService {
    /**
     * 查询常用转入医院ID列表
     *
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @return 常用转入医院ID列表
     */
    List<Integer> selectFrequentInHospitalIds(@Param("serviceOrdClass") String serviceOrdClass);
    /**
     * 查询常用转出医院ID列表
     *
     * @param serviceOrdClass 分公司编码(service_order_class)
     * @return 常用转出医院ID列表
     */
    List<Integer> selectFrequentOutHospitalIds(@Param("serviceOrdClass") String serviceOrdClass);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/ITbHospDataService.java
New file
@@ -0,0 +1,69 @@
package com.ruoyi.system.service;
import com.ruoyi.system.domain.TbHospData;
import java.util.List;
/**
 * 医院数据Service接口
 *
 * @author ruoyi
 */
public interface ITbHospDataService
{
    /**
     * 查询医院数据列表
     *
     * @param tbHospData 医院数据
     * @return 医院数据集合
     */
    List<TbHospData> selectTbHospDataList(TbHospData tbHospData);
    /**
     * 根据医院ID查询医院数据
     *
     * @param hospId 医院ID
     * @return 医院数据
     */
    TbHospData selectTbHospDataById(Long hospId);
    /**
     * 根据旧系统医院ID查询医院数据
     *
     * @param legacyHospId 旧系统医院ID
     * @return 医院数据
     */
    TbHospData selectTbHospDataByLegacyId(Integer legacyHospId);
    /**
     * 新增医院数据
     *
     * @param tbHospData 医院数据
     * @return 结果
     */
    int insertTbHospData(TbHospData tbHospData);
    /**
     * 修改医院数据
     *
     * @param tbHospData 医院数据
     * @return 结果
     */
    int updateTbHospData(TbHospData tbHospData);
    /**
     * 批量删除医院数据
     *
     * @param hospIds 需要删除的医院数据ID
     * @return 结果
     */
    int deleteTbHospDataByIds(Long[] hospIds);
    /**
     * 删除医院数据信息
     *
     * @param hospId 医院数据ID
     * @return 结果
     */
    int deleteTbHospDataById(Long hospId);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/HospDataSyncDataServiceImpl.java
New file
@@ -0,0 +1,35 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.system.domain.HospDataSyncDTO;
import com.ruoyi.system.mapper.HospDataSyncMapper;
import com.ruoyi.system.service.IHospDataSyncDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * 医院数据同步数据查询Service业务层处理(从SQL Server查询)
 *
 * @author ruoyi
 */
@Service
@DataSource(DataSourceType.SQLSERVER)
public class HospDataSyncDataServiceImpl implements IHospDataSyncDataService
{
    @Autowired
    private HospDataSyncMapper hospDataSyncMapper;
    /**
     * 从SQL Server查询所有医院数据
     *
     * @return 医院数据列表
     */
    @Override
    public List<HospDataSyncDTO> selectAllHospDataFromSqlServer()
    {
        return hospDataSyncMapper.selectAllHospDataFromSqlServer();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/HospDataSyncServiceImpl.java
New file
@@ -0,0 +1,218 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.HospDataSyncDTO;
import com.ruoyi.system.domain.TbHospData;
import com.ruoyi.system.service.IHospDataSyncService;
import com.ruoyi.system.service.IHospDataSyncDataService;
import com.ruoyi.system.service.ITbHospDataService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * 医院数据同步Service业务层处理
 *
 * @author ruoyi
 */
@Service
public class HospDataSyncServiceImpl implements IHospDataSyncService
{
    private static final Logger log = LoggerFactory.getLogger(HospDataSyncServiceImpl.class);
    @Autowired
    private IHospDataSyncDataService hospDataSyncDataService;
    @Autowired
    private ITbHospDataService tbHospDataService;
    /**
     * 从SQL Server同步医院数据到MySQL
     *
     * @return 同步结果
     */
    @Override
    public AjaxResult syncHospData()
    {
        try
        {
            log.info("开始同步医院数据...");
            // 从SQL Server查询所有医院数据
            List<HospDataSyncDTO> sqlServerHospList = hospDataSyncDataService.selectAllHospDataFromSqlServer();
            if (sqlServerHospList == null || sqlServerHospList.isEmpty())
            {
                return AjaxResult.warn("SQL Server中没有医院数据");
            }
            log.info("从SQL Server查询到 {} 条医院数据", sqlServerHospList.size());
            int insertCount = 0;
            int updateCount = 0;
            int skipCount = 0;
            Map<String, String> errorMessages = new HashMap<>();
            // 遍历SQL Server数据,同步到MySQL
            for (HospDataSyncDTO dto : sqlServerHospList)
            {
                try
                {
                    // 检查必填字段
                    if (dto.getHospId() == null || StringUtils.isBlank(dto.getHospName()))
                    {
                        log.warn("医院数据不完整,跳过同步: HospID={}, HospName={}",
                                dto.getHospId(), dto.getHospName());
                        skipCount++;
                        continue;
                    }
                    // 根据旧系统ID查询MySQL中是否已存在
                    TbHospData existingHosp = tbHospDataService.selectTbHospDataByLegacyId(dto.getHospId());
                    if (existingHosp != null)
                    {
                        // 更新已有数据
                        updateHospData(existingHosp, dto);
                        tbHospDataService.updateTbHospData(existingHosp);
                        updateCount++;
                        log.debug("更新医院: {} (HospID={})", dto.getHospName(), dto.getHospId());
                    }
                    else
                    {
                        // 插入新数据
                        TbHospData newHosp = convertToTbHospData(dto);
                        tbHospDataService.insertTbHospData(newHosp);
                        insertCount++;
                        log.debug("新增医院: {} (HospID={})", dto.getHospName(), dto.getHospId());
                    }
                }
                catch (Exception e)
                {
                    String errorMsg = String.format("同步医院 HospID=%d 失败: %s",
                            dto.getHospId(), e.getMessage());
                    log.error(errorMsg, e);
                    errorMessages.put("HospID_" + dto.getHospId(), errorMsg);
                }
            }
            String resultMsg = String.format(
                    "医院数据同步完成 - 新增: %d, 更新: %d, 跳过: %d, 失败: %d",
                    insertCount, updateCount, skipCount, errorMessages.size());
            log.info(resultMsg);
            Map<String, Object> result = new HashMap<>();
            result.put("insertCount", insertCount);
            result.put("updateCount", updateCount);
            result.put("skipCount", skipCount);
            result.put("errorCount", errorMessages.size());
            result.put("errors", errorMessages);
            result.put("totalProcessed", sqlServerHospList.size());
            if (errorMessages.isEmpty())
            {
                return AjaxResult.success(resultMsg, result);
            }
            else
            {
                return AjaxResult.warn(resultMsg, result);
            }
        }
        catch (Exception e)
        {
            log.error("同步医院数据失败", e);
            return AjaxResult.error("同步失败: " + e.getMessage());
        }
    }
    /**
     * 将DTO转换为实体对象(新增)
     *
     * @param dto SQL Server数据DTO
     * @return MySQL实体对象
     */
    private TbHospData convertToTbHospData(HospDataSyncDTO dto)
    {
        TbHospData hosp = new TbHospData();
        hosp.setLegacyHospId(dto.getHospId());
        hosp.setHospName(dto.getHospName());
        hosp.setHospCityId(dto.getHospCityId());
        hosp.setHospShort(dto.getHospShort());
        hosp.setHopsProvince(dto.getHopsProvince());
        hosp.setHopsCity(dto.getHopsCity());
        hosp.setHopsArea(dto.getHopsArea());
        hosp.setHospAddress(dto.getHospAddress());
        hosp.setHospTel(dto.getHospTel());
        hosp.setHospUnitId(dto.getHospUnitId());
        hosp.setHospState(dto.getHospState());
        hosp.setHospOaId(dto.getHospOaId());
        hosp.setHospIntroducerId(dto.getHospIntroducerId());
        hosp.setHospIntroducerDate(parseDate(dto.getHospIntroducerDate()));
        hosp.setHospLevel(dto.getHospLevel());
        hosp.setStatus("0"); // 默认正常状态
        hosp.setCreateBy("sync");
        hosp.setRemark("从旧系统同步");
        return hosp;
    }
    /**
     * 更新现有数据
     *
     * @param target MySQL实体对象
     * @param source SQL Server数据DTO
     */
    private void updateHospData(TbHospData target, HospDataSyncDTO source)
    {
        target.setHospName(source.getHospName());
        target.setHospCityId(source.getHospCityId());
        target.setHospShort(source.getHospShort());
        target.setHopsProvince(source.getHopsProvince());
        target.setHopsCity(source.getHopsCity());
        target.setHopsArea(source.getHopsArea());
        target.setHospAddress(source.getHospAddress());
        target.setHospTel(source.getHospTel());
        target.setHospUnitId(source.getHospUnitId());
        target.setHospState(source.getHospState());
        target.setHospOaId(source.getHospOaId());
        target.setHospIntroducerId(source.getHospIntroducerId());
        target.setHospIntroducerDate(parseDate(source.getHospIntroducerDate()));
        target.setHospLevel(source.getHospLevel());
        target.setUpdateBy("sync");
    }
    /**
     * 解析日期字符串
     *
     * @param dateStr 日期字符串
     * @return Date对象
     */
    private Date parseDate(String dateStr)
    {
        if (StringUtils.isBlank(dateStr))
        {
            return null;
        }
        try
        {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return sdf.parse(dateStr);
        }
        catch (ParseException e)
        {
            log.warn("日期解析失败: {}", dateStr);
            return null;
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java
@@ -11,6 +11,11 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import com.ruoyi.system.domain.*;
import com.ruoyi.system.service.*;
@@ -977,11 +982,20 @@
    }
    
    /**
     * 发送HTTP POST请求
     * 发送HTTP/HTTPS POST请求
     * 支持HTTPS自签名证书
     */
    private String sendHttpPost(String urlString, Map<String, String> params) throws Exception {
        URL url = new URL(urlString);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        // 如果是HTTPS请求,配置SSL信任所有证书
        if (conn instanceof HttpsURLConnection) {
            HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
            httpsConn.setSSLSocketFactory(createTrustAllSSLContext().getSocketFactory());
            httpsConn.setHostnameVerifier((hostname, session) -> true); // 信任所有主机名
            log.debug("配置HTTPS连接,信任所有SSL证书,URL: {}", urlString);
        }
        
        try {
            // 设置连接属性
@@ -1027,8 +1041,7 @@
                }
            } else {
                log.error("请求失败,请求URL {},参数 {}",urlString,postData);
                throw new Exception("HTTP请求失败,响应码: " + responseCode);
                throw new Exception("HTTP/HTTPS请求失败,响应码: " + responseCode);
            }
            
        } finally {
@@ -1037,6 +1050,40 @@
    }
    
    /**
     * 创建信任所有SSL证书的SSLContext
     * 用于支持自签名证书的HTTPS请求
     *
     * 注意:此方法会信任所有SSL证书,包括自签名证书
     * 仅用于与旧系统的内部通信,生产环境建议使用正规CA证书
     */
    private SSLContext createTrustAllSSLContext() throws Exception {
        // 创建信任所有证书的TrustManager
        TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    // 信任所有客户端证书
                }
                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    // 信任所有服务器证书
                }
            }
        };
        // 安装信任所有证书的TrustManager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        return sslContext;
    }
    /**
     * 解析旧系统响应
     * 预期格式: "OK:ServiceOrdID" 或错误信息
     */
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SQLHospDataServiceImpl.java
New file
@@ -0,0 +1,27 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.system.mapper.SQLHospDataMapper;
import com.ruoyi.system.service.ISQLHospDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Service
@DataSource(DataSourceType.SQLSERVER)
public class SQLHospDataServiceImpl implements ISQLHospDataService {
    @Autowired
    private SQLHospDataMapper sqlHospDataMapper;
    @Override
    public List<Integer> selectFrequentInHospitalIds(String serviceOrdClass) {
        return sqlHospDataMapper.selectFrequentInHospitalIds(serviceOrdClass);
    }
    @Override
    public List<Integer> selectFrequentOutHospitalIds(String serviceOrdClass) {
        return sqlHospDataMapper.selectFrequentOutHospitalIds(serviceOrdClass);
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TbHospDataServiceImpl.java
New file
@@ -0,0 +1,105 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.system.domain.TbHospData;
import com.ruoyi.system.mapper.TbHospDataMapper;
import com.ruoyi.system.service.ITbHospDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * 医院数据Service业务层处理
 *
 * @author ruoyi
 */
@Service
public class TbHospDataServiceImpl implements ITbHospDataService
{
    @Autowired
    private TbHospDataMapper tbHospDataMapper;
    /**
     * 查询医院数据列表
     *
     * @param tbHospData 医院数据
     * @return 医院数据
     */
    @Override
    public List<TbHospData> selectTbHospDataList(TbHospData tbHospData)
    {
        return tbHospDataMapper.selectTbHospDataList(tbHospData);
    }
    /**
     * 根据医院ID查询医院数据
     *
     * @param hospId 医院ID
     * @return 医院数据
     */
    @Override
    public TbHospData selectTbHospDataById(Long hospId)
    {
        return tbHospDataMapper.selectTbHospDataById(hospId);
    }
    /**
     * 根据旧系统医院ID查询医院数据
     *
     * @param legacyHospId 旧系统医院ID
     * @return 医院数据
     */
    @Override
    public TbHospData selectTbHospDataByLegacyId(Integer legacyHospId)
    {
        return tbHospDataMapper.selectTbHospDataByLegacyId(legacyHospId);
    }
    /**
     * 新增医院数据
     *
     * @param tbHospData 医院数据
     * @return 结果
     */
    @Override
    public int insertTbHospData(TbHospData tbHospData)
    {
        return tbHospDataMapper.insertTbHospData(tbHospData);
    }
    /**
     * 修改医院数据
     *
     * @param tbHospData 医院数据
     * @return 结果
     */
    @Override
    public int updateTbHospData(TbHospData tbHospData)
    {
        return tbHospDataMapper.updateTbHospData(tbHospData);
    }
    /**
     * 批量删除医院数据
     *
     * @param hospIds 需要删除的医院数据ID
     * @return 结果
     */
    @Override
    public int deleteTbHospDataByIds(Long[] hospIds)
    {
        return tbHospDataMapper.deleteTbHospDataByIds(hospIds);
    }
    /**
     * 删除医院数据信息
     *
     * @param hospId 医院数据ID
     * @return 结果
     */
    @Override
    public int deleteTbHospDataById(Long hospId)
    {
        return tbHospDataMapper.deleteTbHospDataById(hospId);
    }
}
ruoyi-system/src/main/resources/mapper/system/HospDataMapper.xml
@@ -5,112 +5,146 @@
<mapper namespace="com.ruoyi.system.mapper.HospDataMapper">
    
    <resultMap type="HospData" id="HospDataResult">
        <result property="hospId" column="HospID" />
        <result property="hospName" column="HospName" />
        <result property="hospCityId" column="HospCityID" />
        <result property="hospShort" column="HospShort" />
        <result property="hopsProvince" column="HopsProvince" />
        <result property="hopsCity" column="HopsCity" />
        <result property="hopsArea" column="HopsArea" />
        <result property="hospAddress" column="HospAddress" />
        <result property="hospTel" column="HospTEL" />
        <result property="hospUnitId" column="HospUnitID" />
        <result property="hospState" column="HospState" />
        <result property="hospOaId" column="HospOAID" />
        <result property="hospIntroducerId" column="HospIntroducerID" />
        <result property="hospIntroducerDate" column="HospIntroducerDate" />
        <result property="hospLevel" column="HospLevel" />
        <result property="hospId" column="legacy_hosp_id" />
        <result property="hospName" column="hosp_name" />
        <result property="hospCityId" column="hosp_city_id" />
        <result property="hospShort" column="hosp_short" />
        <result property="hopsProvince" column="hops_province" />
        <result property="hopsCity" column="hops_city" />
        <result property="hopsArea" column="hops_area" />
        <result property="hospAddress" column="hosp_address" />
        <result property="hospTel" column="hosp_tel" />
        <result property="hospUnitId" column="hosp_unit_id" />
        <result property="hospState" column="hosp_state" />
        <result property="hospOaId" column="hosp_oa_id" />
        <result property="hospIntroducerId" column="hosp_introducer_id" />
        <result property="hospIntroducerDate" column="hosp_introducer_date" />
        <result property="hospLevel" column="hosp_level" />
    </resultMap>
    <select id="searchHospitals" resultMap="HospDataResult">
        SELECT TOP 1000
            HospID, HospName, HospCityID, HospShort,
            HopsProvince, HopsCity, HopsArea, HospAddress,
            HospTEL, HospUnitID, HospState, HospOAID,
            HospIntroducerID, HospIntroducerDate, HospLevel
        FROM HospData
        WHERE 1=1
        SELECT legacy_hosp_id, hosp_name, hosp_city_id, hosp_short,
            hops_province, hops_city, hops_area, hosp_address,
            hosp_tel, hosp_unit_id, hosp_state, hosp_oa_id,
            hosp_introducer_id, hosp_introducer_date, hosp_level
        FROM tb_hosp_data
        WHERE status = '0'
        <!-- 有 keyword 就只用 keyword,不用 region -->
        <if test="keyword != null and keyword != ''">
        AND (
            HopsProvince LIKE '%' + #{keyword} + '%'
                OR HopsCity LIKE '%' + #{keyword} + '%'
                OR HopsArea LIKE '%' + #{keyword} + '%'
                OR HospAddress LIKE '%' + #{keyword} + '%'
                OR HospName LIKE '%' + #{keyword} + '%'
                OR HospShort LIKE '%' + #{keyword} + '%'
            hops_province LIKE CONCAT('%', #{keyword}, '%')
                OR hops_city LIKE CONCAT('%', #{keyword}, '%')
                OR hops_area LIKE CONCAT('%', #{keyword}, '%')
                OR hosp_address LIKE CONCAT('%', #{keyword}, '%')
                OR hosp_name LIKE CONCAT('%', #{keyword}, '%')
                OR hosp_short LIKE CONCAT('%', #{keyword}, '%')
        )
        </if>
        <!-- 没有 keyword 时才用 region -->
        <if test="(keyword == null or keyword == '') and (region != null and region != '')">
        AND (
            HopsProvince LIKE '%' + #{region} + '%'
                OR HopsCity LIKE '%' + #{region} + '%'
                OR HopsArea LIKE '%' + #{region} + '%'
                OR HospAddress LIKE '%' + #{region} + '%'
                OR HospName LIKE '%' + #{region} + '%'
                OR HospShort LIKE '%' + #{region} + '%'
            hops_province LIKE CONCAT('%', #{region}, '%')
                OR hops_city LIKE CONCAT('%', #{region}, '%')
                OR hops_area LIKE CONCAT('%', #{region}, '%')
                OR hosp_address LIKE CONCAT('%', #{region}, '%')
                OR hosp_name LIKE CONCAT('%', #{region}, '%')
                OR hosp_short LIKE CONCAT('%', #{region}, '%')
        )
        </if>
        AND (HospState IS NULL OR HospState = 1)
        AND (hosp_state IS NULL OR hosp_state = 1)
        ORDER BY 
            CASE WHEN HospName = '家中' THEN 0 ELSE 1 END,
            HospName
            CASE WHEN hosp_name = '家中' THEN 0 ELSE 1 END,
            hosp_name
    </select>
    
    <select id="selectHospDataById" parameterType="Integer" resultMap="HospDataResult">
        SELECT 
            HospID, HospName, HospCityID, HospShort,
            HopsProvince, HopsCity, HopsArea, HospAddress,
            HospTEL, HospUnitID, HospState, HospOAID,
            HospIntroducerID, HospIntroducerDate, HospLevel
        FROM HospData
        WHERE HospID = #{hospId}
            legacy_hosp_id, hosp_name, hosp_city_id, hosp_short,
            hops_province, hops_city, hops_area, hosp_address,
            hosp_tel, hosp_unit_id, hosp_state, hosp_oa_id,
            hosp_introducer_id, hosp_introducer_date, hosp_level
        FROM tb_hosp_data
        WHERE legacy_hosp_id = #{hospId}
    </select>
    
    <!-- 查询常用转出医院ID列表 -->
    <select id="selectFrequentOutHospitalIds" resultType="java.lang.Integer">
        select ServiceOrdPtOutHospID from (
            select    ServiceOrdPtOutHospID,count(1) as InCount  from ServiceOrder
            where ServiceOrdClass=#{serviceOrdClass} and ServiceOrdPtOutHospID>0
            group by ServiceOrdPtOutHospID
        ) as t
        order by InCount desc
    </select>
    <select id="getHomeHospId" resultType="java.lang.Integer">
        select HospID from HospData where HospName='家中'
        select legacy_hosp_id from tb_hosp_data where hosp_name='家中'
    </select>
    
    <!-- 查询常用转入医院ID列表 -->
    <select id="selectFrequentInHospitalIds" resultType="java.lang.Integer">
        select ServiceOrdPtInHospID from (
            select    ServiceOrdPtInHospID,count(1) as InCount  from ServiceOrder
            where ServiceOrdClass=#{serviceOrdClass} and ServiceOrdPtInHospID>0
            group by ServiceOrdPtInHospID
        ) as t
        order by InCount desc
    </select>
    <!-- 根据医院ID列表查询医院信息 -->
    <select id="selectHospDataByIds" resultMap="HospDataResult">
        SELECT 
            HospID, HospName, HospCityID, HospShort,
            HopsProvince, HopsCity, HopsArea, HospAddress,
            HospTEL, HospUnitID, HospState, HospOAID,
            HospIntroducerID, HospIntroducerDate, HospLevel
        FROM HospData
        WHERE HospID IN
            legacy_hosp_id, hosp_name, hosp_city_id, hosp_short,
            hops_province, hops_city, hops_area, hosp_address,
            hosp_tel, hosp_unit_id, hosp_state, hosp_oa_id,
            hosp_introducer_id, hosp_introducer_date, hosp_level
        FROM tb_hosp_data
        WHERE legacy_hosp_id IN
        <foreach collection="hospIds" item="hospId" open="(" separator="," close=")">
            #{hospId}
        </foreach>
        <if test="region != null and region != ''">
        AND (
            HopsProvince LIKE '%' + #{region} + '%'
                OR HopsCity LIKE '%' + #{region} + '%'
                OR HopsArea LIKE '%' + #{region} + '%'
            hops_province LIKE CONCAT('%', #{region}, '%')
                OR hops_city LIKE CONCAT('%', #{region}, '%')
                OR hosp_short LIKE CONCAT('%', #{region}, '%')
                OR hops_area LIKE CONCAT('%', #{region}, '%')
                OR hosp_address LIKE CONCAT('%', #{region}, '%')
                OR hosp_name LIKE CONCAT('%', #{region}, '%')
        )
        </if>
        AND (HospState IS NULL OR HospState = 1)
        ORDER BY HospName
        AND (hosp_state IS NULL OR hosp_state = 1)
        ORDER BY hosp_name
    </select>
    <!-- 根据部门区域配置查询医院(支持多级区域过滤) -->
    <select id="searchHospitalsByDeptRegion" resultMap="HospDataResult">
        SELECT DISTINCT
            h.legacy_hosp_id, h.hosp_name, h.hosp_city_id, h.hosp_short,
            h.hops_province, h.hops_city, h.hops_area, h.hosp_address,
            h.hosp_tel, h.hosp_unit_id, h.hosp_state, h.hosp_oa_id,
            h.hosp_introducer_id, h.hosp_introducer_date, h.hosp_level
        FROM tb_hosp_data h
        WHERE (h.hosp_state IS NULL OR h.hosp_state = 1)
        AND h.status = '0'
        AND EXISTS (
            SELECT 1 FROM sys_dept_region r
            WHERE r.dept_id = #{deptId}
            AND r.status = '0'
            AND (
                -- 区域类型:地域范围匹配
                (r.region_type = 'AREA' AND (
                    -- 省份匹配(为空则跳过)
                    (r.province IS NULL OR r.province = '' OR h.hops_province LIKE CONCAT('%', r.province, '%'))
                    -- 城市匹配(为空则跳过)
                    AND (r.city IS NULL OR r.city = '' OR h.hops_city LIKE CONCAT('%', r.city, '%'))
                    -- 县/区匹配(为空则跳过)
                    AND (r.area IS NULL OR r.area = '' OR h.hops_area LIKE CONCAT('%', r.area, '%'))
                    -- 详细地址匹配(为空则跳过)
                    AND (r.address IS NULL OR r.address = '' OR h.hosp_address LIKE CONCAT('%', r.address, '%'))
                ))
                -- 区域类型:指定医院匹配
                OR (r.region_type = 'HOSPITAL' AND r.hospital_name IS NOT NULL AND r.hospital_name != '' AND (
                    h.hosp_name LIKE CONCAT('%', r.hospital_name, '%')
                    OR h.hosp_short LIKE CONCAT('%', r.hospital_name, '%')
                ))
            )
        )
        <!-- 关键词搜索 -->
        <if test="keyword != null and keyword != ''">
        AND (
            h.hops_province LIKE CONCAT('%', #{keyword}, '%')
            OR h.hops_city LIKE CONCAT('%', #{keyword}, '%')
            OR h.hops_area LIKE CONCAT('%', #{keyword}, '%')
            OR h.hosp_address LIKE CONCAT('%', #{keyword}, '%')
            OR h.hosp_name LIKE CONCAT('%', #{keyword}, '%')
            OR h.hosp_short LIKE CONCAT('%', #{keyword}, '%')
        )
        </if>
        ORDER BY
            CASE WHEN h.hosp_name = '家中' THEN 0 ELSE 1 END,
            h.hosp_name
    </select>
</mapper>
ruoyi-system/src/main/resources/mapper/system/HospDataSyncMapper.xml
New file
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.HospDataSyncMapper">
    <resultMap type="HospDataSyncDTO" id="HospDataSyncResult">
        <result property="hospId"              column="HospID"              />
        <result property="hospName"            column="HospName"            />
        <result property="hospCityId"          column="HospCityID"          />
        <result property="hospShort"           column="HospShort"           />
        <result property="hopsProvince"        column="HopsProvince"        />
        <result property="hopsCity"            column="HopsCity"            />
        <result property="hopsArea"            column="HopsArea"            />
        <result property="hospAddress"         column="HospAddress"         />
        <result property="hospTel"             column="HospTEL"             />
        <result property="hospUnitId"          column="HospUnitID"          />
        <result property="hospState"           column="HospState"           />
        <result property="hospOaId"            column="HospOAID"            />
        <result property="hospIntroducerId"    column="HospIntroducerID"    />
        <result property="hospIntroducerDate"  column="HospIntroducerDate"  />
        <result property="hospLevel"           column="HospLevel"           />
    </resultMap>
    <!-- 从SQL Server查询所有医院数据 -->
    <select id="selectAllHospDataFromSqlServer" resultMap="HospDataSyncResult">
        SELECT
            HospID, HospName, HospCityID, HospShort,
            HopsProvince, HopsCity, HopsArea, HospAddress,
            HospTEL, HospUnitID, HospState, HospOAID,
            HospIntroducerID, HospIntroducerDate, HospLevel
        FROM HospData
        WHERE 1=1
        ORDER BY HospID
    </select>
</mapper>
ruoyi-system/src/main/resources/mapper/system/SQLHospDataMapper.xml
New file
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SQLHospDataMapper">
    <!-- 查询常用转出医院ID列表 -->
    <select id="selectFrequentOutHospitalIds" resultType="java.lang.Integer">
        select ServiceOrdPtOutHospID from (
        select    ServiceOrdPtOutHospID,count(1) as InCount  from ServiceOrder
        where ServiceOrdClass=#{serviceOrdClass} and ServiceOrdPtOutHospID>0
        group by ServiceOrdPtOutHospID
        ) as t
        order by InCount desc
    </select>
    <!-- 查询常用转入医院ID列表 -->
    <select id="selectFrequentInHospitalIds" resultType="java.lang.Integer">
        select ServiceOrdPtInHospID from (
        select    ServiceOrdPtInHospID,count(1) as InCount  from ServiceOrder
        where ServiceOrdClass=#{serviceOrdClass} and ServiceOrdPtInHospID>0
        group by ServiceOrdPtInHospID
        ) as t
        order by InCount desc
    </select>
</mapper>
ruoyi-system/src/main/resources/mapper/system/SysDeptRegionMapper.xml
New file
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SysDeptRegionMapper">
    <resultMap type="SysDeptRegion" id="SysDeptRegionResult">
        <id     property="regionId"     column="region_id"     />
        <result property="deptId"       column="dept_id"       />
        <result property="province"     column="province"      />
        <result property="city"         column="city"          />
        <result property="area"         column="area"          />
        <result property="address"      column="address"       />
        <result property="hospitalName" column="hospital_name" />
        <result property="regionType"   column="region_type"   />
        <result property="status"       column="status"        />
        <result property="remark"       column="remark"        />
        <result property="createBy"     column="create_by"     />
        <result property="createTime"   column="create_time"   />
        <result property="updateBy"     column="update_by"     />
        <result property="updateTime"   column="update_time"   />
    </resultMap>
    <sql id="selectDeptRegionVo">
        select region_id, dept_id, province, city, area, address, hospital_name, region_type, status, remark, create_by, create_time, update_by, update_time
        from sys_dept_region
    </sql>
    <select id="selectDeptRegionListByDeptId" parameterType="Long" resultMap="SysDeptRegionResult">
        <include refid="selectDeptRegionVo"/>
        where dept_id = #{deptId}
        and status = '0'
        order by region_id
    </select>
    <select id="selectDeptRegionListByDeptIds" resultMap="SysDeptRegionResult">
        <include refid="selectDeptRegionVo"/>
        where dept_id in
        <foreach collection="deptIds" item="deptId" open="(" separator="," close=")">
            #{deptId}
        </foreach>
        and status = '0'
        order by dept_id, region_id
    </select>
    <insert id="insertDeptRegion" parameterType="SysDeptRegion">
        insert into sys_dept_region(
            <if test="deptId != null">dept_id,</if>
            <if test="province != null and province != ''">province,</if>
            <if test="city != null and city != ''">city,</if>
            <if test="area != null and area != ''">area,</if>
            <if test="address != null and address != ''">address,</if>
            <if test="hospitalName != null and hospitalName != ''">hospital_name,</if>
            <if test="regionType != null and regionType != ''">region_type,</if>
            <if test="status != null">status,</if>
            <if test="remark != null">remark,</if>
            <if test="createBy != null and createBy != ''">create_by,</if>
            create_time
        )values(
            <if test="deptId != null">#{deptId},</if>
            <if test="province != null and province != ''">#{province},</if>
            <if test="city != null and city != ''">#{city},</if>
            <if test="area != null and area != ''">#{area},</if>
            <if test="address != null and address != ''">#{address},</if>
            <if test="hospitalName != null and hospitalName != ''">#{hospitalName},</if>
            <if test="regionType != null and regionType != ''">#{regionType},</if>
            <if test="status != null">#{status},</if>
            <if test="remark != null">#{remark},</if>
            <if test="createBy != null and createBy != ''">#{createBy},</if>
            sysdate()
        )
    </insert>
    <update id="updateDeptRegion" parameterType="SysDeptRegion">
        update sys_dept_region
        <set>
            <if test="province != null">province = #{province},</if>
            <if test="city != null">city = #{city},</if>
            <if test="area != null">area = #{area},</if>
            <if test="address != null">address = #{address},</if>
            <if test="hospitalName != null">hospital_name = #{hospitalName},</if>
            <if test="regionType != null and regionType != ''">region_type = #{regionType},</if>
            <if test="status != null">status = #{status},</if>
            <if test="remark != null">remark = #{remark},</if>
            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
            update_time = sysdate()
        </set>
        where region_id = #{regionId}
    </update>
    <delete id="deleteDeptRegionById" parameterType="Long">
        delete from sys_dept_region where region_id = #{regionId}
    </delete>
    <delete id="deleteDeptRegionByDeptId" parameterType="Long">
        delete from sys_dept_region where dept_id = #{deptId}
    </delete>
</mapper>
ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml
@@ -124,7 +124,7 @@
    <select id="selectMaxTaskCodeByDatePrefix" parameterType="String" resultType="String">
        select max(task_code) 
        from sys_task 
        where task_code like concat(#{datePrefix}, '%') and del_flag = '0'
        where task_code like concat(#{datePrefix}, '%')
    </select>
    <select id="selectOverdueTasks" resultMap="SysTaskResult">
ruoyi-system/src/main/resources/mapper/system/TbHospDataMapper.xml
New file
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.TbHospDataMapper">
    <resultMap type="TbHospData" id="TbHospDataResult">
        <id     property="hospId"              column="hosp_id"              />
        <result property="legacyHospId"        column="legacy_hosp_id"       />
        <result property="hospName"            column="hosp_name"            />
        <result property="hospCityId"          column="hosp_city_id"         />
        <result property="hospShort"           column="hosp_short"           />
        <result property="hopsProvince"        column="hops_province"        />
        <result property="hopsCity"            column="hops_city"            />
        <result property="hopsArea"            column="hops_area"            />
        <result property="hospAddress"         column="hosp_address"         />
        <result property="hospTel"             column="hosp_tel"             />
        <result property="hospUnitId"          column="hosp_unit_id"         />
        <result property="hospState"           column="hosp_state"           />
        <result property="hospOaId"            column="hosp_oa_id"           />
        <result property="hospIntroducerId"    column="hosp_introducer_id"   />
        <result property="hospIntroducerDate"  column="hosp_introducer_date" />
        <result property="hospLevel"           column="hosp_level"           />
        <result property="status"              column="status"               />
        <result property="remark"              column="remark"               />
        <result property="createBy"            column="create_by"            />
        <result property="createTime"          column="create_time"          />
        <result property="updateBy"            column="update_by"            />
        <result property="updateTime"          column="update_time"          />
    </resultMap>
    <sql id="selectTbHospDataVo">
        select hosp_id, legacy_hosp_id, hosp_name, hosp_city_id, hosp_short,
               hops_province, hops_city, hops_area, hosp_address, hosp_tel,
               hosp_unit_id, hosp_state, hosp_oa_id, hosp_introducer_id,
               hosp_introducer_date, hosp_level, status, remark,
               create_by, create_time, update_by, update_time
        from tb_hosp_data
    </sql>
    <select id="selectTbHospDataList" parameterType="TbHospData" resultMap="TbHospDataResult">
        <include refid="selectTbHospDataVo"/>
        <where>
            <if test="legacyHospId != null">
                and legacy_hosp_id = #{legacyHospId}
            </if>
            <if test="hospName != null and hospName != ''">
                and hosp_name like concat('%', #{hospName}, '%')
            </if>
            <if test="hopsProvince != null and hopsProvince != ''">
                and hops_province like concat('%', #{hopsProvince}, '%')
            </if>
            <if test="hopsCity != null and hopsCity != ''">
                and hops_city like concat('%', #{hopsCity}, '%')
            </if>
            <if test="hopsArea != null and hopsArea != ''">
                and hops_area like concat('%', #{hopsArea}, '%')
            </if>
            <if test="status != null and status != ''">
                and status = #{status}
            </if>
        </where>
        order by hosp_id desc
    </select>
    <select id="selectTbHospDataById" parameterType="Long" resultMap="TbHospDataResult">
        <include refid="selectTbHospDataVo"/>
        where hosp_id = #{hospId}
    </select>
    <select id="selectTbHospDataByLegacyId" parameterType="Integer" resultMap="TbHospDataResult">
        <include refid="selectTbHospDataVo"/>
        where legacy_hosp_id = #{legacyHospId}
    </select>
    <insert id="insertTbHospData" parameterType="TbHospData" useGeneratedKeys="true" keyProperty="hospId">
        insert into tb_hosp_data(
            <if test="legacyHospId != null">legacy_hosp_id,</if>
            <if test="hospName != null and hospName != ''">hosp_name,</if>
            <if test="hospCityId != null">hosp_city_id,</if>
            <if test="hospShort != null">hosp_short,</if>
            <if test="hopsProvince != null">hops_province,</if>
            <if test="hopsCity != null">hops_city,</if>
            <if test="hopsArea != null">hops_area,</if>
            <if test="hospAddress != null">hosp_address,</if>
            <if test="hospTel != null">hosp_tel,</if>
            <if test="hospUnitId != null">hosp_unit_id,</if>
            <if test="hospState != null">hosp_state,</if>
            <if test="hospOaId != null">hosp_oa_id,</if>
            <if test="hospIntroducerId != null">hosp_introducer_id,</if>
            <if test="hospIntroducerDate != null">hosp_introducer_date,</if>
            <if test="hospLevel != null">hosp_level,</if>
            <if test="status != null">status,</if>
            <if test="remark != null">remark,</if>
            <if test="createBy != null and createBy != ''">create_by,</if>
            create_time
        )values(
            <if test="legacyHospId != null">#{legacyHospId},</if>
            <if test="hospName != null and hospName != ''">#{hospName},</if>
            <if test="hospCityId != null">#{hospCityId},</if>
            <if test="hospShort != null">#{hospShort},</if>
            <if test="hopsProvince != null">#{hopsProvince},</if>
            <if test="hopsCity != null">#{hopsCity},</if>
            <if test="hopsArea != null">#{hopsArea},</if>
            <if test="hospAddress != null">#{hospAddress},</if>
            <if test="hospTel != null">#{hospTel},</if>
            <if test="hospUnitId != null">#{hospUnitId},</if>
            <if test="hospState != null">#{hospState},</if>
            <if test="hospOaId != null">#{hospOaId},</if>
            <if test="hospIntroducerId != null">#{hospIntroducerId},</if>
            <if test="hospIntroducerDate != null">#{hospIntroducerDate},</if>
            <if test="hospLevel != null">#{hospLevel},</if>
            <if test="status != null">#{status},</if>
            <if test="remark != null">#{remark},</if>
            <if test="createBy != null and createBy != ''">#{createBy},</if>
            sysdate()
        )
    </insert>
    <update id="updateTbHospData" parameterType="TbHospData">
        update tb_hosp_data
        <set>
            <if test="legacyHospId != null">legacy_hosp_id = #{legacyHospId},</if>
            <if test="hospName != null and hospName != ''">hosp_name = #{hospName},</if>
            <if test="hospCityId != null">hosp_city_id = #{hospCityId},</if>
            <if test="hospShort != null">hosp_short = #{hospShort},</if>
            <if test="hopsProvince != null">hops_province = #{hopsProvince},</if>
            <if test="hopsCity != null">hops_city = #{hopsCity},</if>
            <if test="hopsArea != null">hops_area = #{hopsArea},</if>
            <if test="hospAddress != null">hosp_address = #{hospAddress},</if>
            <if test="hospTel != null">hosp_tel = #{hospTel},</if>
            <if test="hospUnitId != null">hosp_unit_id = #{hospUnitId},</if>
            <if test="hospState != null">hosp_state = #{hospState},</if>
            <if test="hospOaId != null">hosp_oa_id = #{hospOaId},</if>
            <if test="hospIntroducerId != null">hosp_introducer_id = #{hospIntroducerId},</if>
            <if test="hospIntroducerDate != null">hosp_introducer_date = #{hospIntroducerDate},</if>
            <if test="hospLevel != null">hosp_level = #{hospLevel},</if>
            <if test="status != null">status = #{status},</if>
            <if test="remark != null">remark = #{remark},</if>
            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
            update_time = sysdate()
        </set>
        where hosp_id = #{hospId}
    </update>
    <delete id="deleteTbHospDataById" parameterType="Long">
        delete from tb_hosp_data where hosp_id = #{hospId}
    </delete>
    <delete id="deleteTbHospDataByIds" parameterType="Long">
        delete from tb_hosp_data where hosp_id in
        <foreach collection="array" item="hospId" open="(" separator="," close=")">
            #{hospId}
        </foreach>
    </delete>
</mapper>
ruoyi-system/src/test/java/com/ruoyi/system/service/LegacySystemHttpsTest.java
New file
@@ -0,0 +1,260 @@
package com.ruoyi.system.service;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
/**
 * 旧系统HTTPS接口测试
 * 测试admin_save_19.gds接口调用
 *
 * @author ruoyi
 * @date 2025-11-09
 */
public class LegacySystemHttpsTest {
    private static final Logger log = LoggerFactory.getLogger(LegacySystemHttpsTest.class);
    /**
     * 测试调用admin_save_19.gds接口(创建服务单)
     */
    @Test
    public void testCreateServiceOrder() {
        try {
            String url = "https://sys.966120.com.cn/admin_save_19.gds";
            // 构建请求参数
            Map<String, String> params = buildTestParams();
            // 发送请求
            String response = sendHttpsPost(url, params);
            // 输出响应
            log.info("接口响应:{}", response);
            System.out.println("接口响应:" + response);
            // 解析响应
            if (response != null && response.startsWith("OK:")) {
                String serviceOrdId = response.substring(3).trim();
                log.info("服务单创建成功,ServiceOrdID: {}", serviceOrdId);
                System.out.println("✅ 服务单创建成功,ServiceOrdID: " + serviceOrdId);
            } else {
                log.error("服务单创建失败,响应: {}", response);
                System.out.println("❌ 服务单创建失败,响应: " + response);
            }
        } catch (Exception e) {
            log.error("测试异常", e);
            e.printStackTrace();
        }
    }
    /**
     * 构建测试参数
     */
    private Map<String, String> buildTestParams() {
        Map<String, String> params = new HashMap<>();
        // 基础信息
        params.put("adminID", "1312");
        params.put("ServiceOrdClass", "BF");
        params.put("ServiceOrdAreaType", "1");
        params.put("ServiceOrdType", "20");
        params.put("ServiceOrdState", "2");
        params.put("ServiceOrdStartDate", "2025-11-09");
        // 预约时间
        params.put("ServiceOrdApptDate", "2025-11-09 16:56:31");
        // 联系人信息
        params.put("ServiceOrdCoName", "李测试11");
        params.put("ServiceOrdCoPhone", "13602220102");
        params.put("ServiceOrdCoTies", "");
        // 患者信息
        params.put("ServiceOrdPtName", "李测试21");
        params.put("ServiceOrdPtAge", "");
        params.put("ServiceOrdPtKG", "");
        params.put("ServiceOrdPtSex", "");
        params.put("ServiceOrdPtNat", "");
        params.put("ServiceOrdPtIDCard", "");
        // 医院信息
        params.put("ServiceOrdPtOutHosp", "广东省第二人民医院天河医院(广东燕岭医院)");
        params.put("ServiceOrdPtOutHospID", "310");
        params.put("ServiceOrdPtInHosp", "家中");
        params.put("ServiceOrdPtInHospID", "153");
        // 科室信息
        params.put("ServiceOrdPtServices", "其他");
        params.put("ServiceOrdPtServicesID", "22");
        params.put("ServiceOrdPtInServices", "其他");
        params.put("ServiceOrdPtInServicesID", "0");
        // 病情信息
        params.put("ServiceOrdPtDiagnosis", "");
        params.put("ServiceOrdPtCondition", "");
        params.put("ServiceOrdTaskRemarks", "");
        params.put("ServiceOrdPtDoctor", "");
        params.put("ServiceOrdPtDoctorPhone", "");
        // 地址信息
        params.put("province", "");
        params.put("city", "");
        params.put("ServiceOrdTraStreet", "广东省广州市天河区棠安路533号");
        params.put("ServiceOrdTraStreetCoo", "");
        params.put("ServiceOrdTraEnd", "天河区广州市-天河区-柏德路101号");
        params.put("ServiceOrdTraEndCoo", "");
        params.put("ServiceOrdTraVia", "");
        // 距离和价格信息
        params.put("ServiceOrdViaDistance", "0");
        params.put("ServiceOrdTraDistance", "10.70");
        params.put("ServiceOrdTraDuration", "");
        params.put("ServiceOrdTraUnitPrice", "0");
        params.put("ServiceOrdTraOfferPrice", "154.00");
        params.put("ServiceOrdTraTxnPrice", "154.00");
        params.put("ServiceOrdTraPrePayment", "0");
        params.put("SettlementPrice", "0");
        params.put("ServiceOrdTraPriceReason", "");
        // 其他信息
        params.put("Phone", "13602220102");
        params.put("TEL_Time", "2025-11-09 16:58:21");
        params.put("TEL_Remarks", "新系统同步");
        params.put("TransferModeID", "");
        params.put("ServiceOrdVIP", "0");
        params.put("ServiceOrd_CC_ID", "");
        params.put("ServiceOrd_Sale_ID", "");
        params.put("ServiceOrdIntroducer", "");
        params.put("ServiceOrd_work_ID", "");
        params.put("ServiceOrd_work_IDs", "");
        params.put("ServiceOrd_work_is", "0");
        params.put("CommissionScenarioID", "0");
        params.put("ServiceOrdOperationRemarks", "新系统同步创建");
        params.put("ServiceOrdEstimatedOrderDate", "");
        params.put("ServiceOrdSource", "10");
        params.put("OrderLevel", "0");
        params.put("ServiceOrdDepartureType", "1");
        params.put("ConditionLevel", "0");
        params.put("DirectionType", "0");
        params.put("ServiceOrd_m", "1");
        params.put("FromHQ2_is", "0");
        params.put("OrderPrice_Auto", "0");
        return params;
    }
    /**
     * 发送HTTPS POST请求(忽略SSL证书验证)
     */
    private String sendHttpsPost(String urlString, Map<String, String> params) throws Exception {
        URL url = new URL(urlString);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        // 如果是HTTPS请求,配置SSL信任所有证书
        if (conn instanceof HttpsURLConnection) {
            HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
            httpsConn.setSSLSocketFactory(createTrustAllSSLContext().getSocketFactory());
            httpsConn.setHostnameVerifier((hostname, session) -> true);
            log.info("配置HTTPS连接,信任所有SSL证书");
        }
        try {
            // 设置连接属性
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(60000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=GBK");
            conn.setRequestProperty("Accept-Charset", "GBK");
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
            // 构建POST数据
            StringBuilder postData = new StringBuilder();
            for (Map.Entry<String, String> entry : params.entrySet()) {
                if (postData.length() > 0) {
                    postData.append("&");
                }
                postData.append(URLEncoder.encode(entry.getKey(), "GBK"));
                postData.append("=");
                postData.append(URLEncoder.encode(entry.getValue(), "GBK"));
            }
            log.info("请求URL: {}", urlString);
            log.info("请求参数长度: {}", postData.length());
            // 发送POST数据
            try (OutputStream os = conn.getOutputStream()) {
                os.write(postData.toString().getBytes("GBK"));
                os.flush();
            }
            // 读取响应
            int responseCode = conn.getResponseCode();
            log.info("响应码: {}", responseCode);
            if (responseCode == HttpURLConnection.HTTP_OK) {
                try (BufferedReader reader = new BufferedReader(
                        new InputStreamReader(conn.getInputStream(), "GBK"))) {
                    StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        response.append(line);
                    }
                    return response.toString().trim();
                }
            } else {
                log.error("请求失败,响应码: {}", responseCode);
                throw new Exception("HTTPS请求失败,响应码: " + responseCode);
            }
        } finally {
            conn.disconnect();
        }
    }
    /**
     * 创建信任所有SSL证书的SSLContext
     */
    private SSLContext createTrustAllSSLContext() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    // 信任所有客户端证书
                }
                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    // 信任所有服务器证书
                }
            }
        };
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        return sslContext;
    }
}
ruoyi-ui/src/api/system/deptRegion.js
New file
@@ -0,0 +1,35 @@
import request from '@/utils/request'
// 查询部门区域列表
export function listDeptRegion(deptId) {
  return request({
    url: '/system/dept/region/list/' + deptId,
    method: 'get'
  })
}
// 新增部门区域
export function addDeptRegion(data) {
  return request({
    url: '/system/dept/region',
    method: 'post',
    data: data
  })
}
// 修改部门区域
export function updateDeptRegion(data) {
  return request({
    url: '/system/dept/region',
    method: 'put',
    data: data
  })
}
// 删除部门区域
export function delDeptRegion(regionId) {
  return request({
    url: '/system/dept/region/' + regionId,
    method: 'delete'
  })
}
ruoyi-ui/src/api/system/hosp.js
New file
@@ -0,0 +1,52 @@
import request from '@/utils/request'
// 查询医院数据列表
export function listHosp(query) {
  return request({
    url: '/system/hosp/list',
    method: 'get',
    params: query
  })
}
// 查询医院数据详细
export function getHosp(hospId) {
  return request({
    url: '/system/hosp/' + hospId,
    method: 'get'
  })
}
// 新增医院数据
export function addHosp(data) {
  return request({
    url: '/system/hosp',
    method: 'post',
    data: data
  })
}
// 修改医院数据
export function updateHosp(data) {
  return request({
    url: '/system/hosp',
    method: 'put',
    data: data
  })
}
// 删除医院数据
export function delHosp(hospId) {
  return request({
    url: '/system/hosp/' + hospId,
    method: 'delete'
  })
}
// 同步医院数据
export function syncHosp() {
  return request({
    url: '/system/hosp/sync',
    method: 'post'
  })
}
ruoyi-ui/src/views/system/dept/index.vue
@@ -104,6 +104,13 @@
            v-hasPermi="['system:dept:add']"
          >新增</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-location"
            @click="handleManageRegion(scope.row)"
            v-hasPermi="['system:dept:edit']"
          >管理区域</el-button>
          <el-button
            v-if="scope.row.parentId != 0"
            size="mini"
            type="text"
@@ -114,6 +121,92 @@
        </template>
      </el-table-column>
    </el-table>
    <!-- 管理区域对话框 -->
    <el-dialog title="管理区域" :visible.sync="regionDialogVisible" width="900px" append-to-body>
      <div style="margin-bottom: 10px;">
        <el-button type="primary" icon="el-icon-plus" size="small" @click="handleAddRegion">新增区域</el-button>
      </div>
      <el-table :data="regionList" border>
        <el-table-column label="区域类型" align="center" width="120">
          <template slot-scope="scope">
            <el-tag v-if="scope.row.regionType === 'AREA'" type="success">地域范围</el-tag>
            <el-tag v-else-if="scope.row.regionType === 'HOSPITAL'" type="primary">指定医院</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="省份" prop="province" align="center" width="100" />
        <el-table-column label="城市" prop="city" align="center" width="100" />
        <el-table-column label="县/区" prop="area" align="center" width="100" />
        <el-table-column label="详细地址" prop="address" align="center" show-overflow-tooltip />
        <el-table-column label="指定医院" prop="hospitalName" align="center" show-overflow-tooltip />
        <el-table-column label="状态" align="center" width="80">
          <template slot-scope="scope">
            <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="150">
          <template slot-scope="scope">
            <el-button size="mini" type="text" icon="el-icon-edit" @click="handleEditRegion(scope.row)">编辑</el-button>
            <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDeleteRegion(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div slot="footer" class="dialog-footer">
        <el-button @click="regionDialogVisible = false">关 闭</el-button>
      </div>
    </el-dialog>
    <!-- 添加或修改区域对话框 -->
    <el-dialog :title="regionFormTitle" :visible.sync="regionFormVisible" width="600px" append-to-body>
      <el-form ref="regionForm" :model="regionForm" :rules="regionRules" label-width="100px">
        <el-form-item label="区域类型" prop="regionType">
          <el-radio-group v-model="regionForm.regionType" @change="handleRegionTypeChange">
            <el-radio label="AREA">地域范围</el-radio>
            <el-radio label="HOSPITAL">指定医院</el-radio>
          </el-radio-group>
        </el-form-item>
        <!-- 地域范围配置 -->
        <div v-if="regionForm.regionType === 'AREA'">
          <el-form-item label="省份" prop="province">
            <el-input v-model="regionForm.province" placeholder="请输入省份,如:广东省" />
          </el-form-item>
          <el-form-item label="城市" prop="city">
            <el-input v-model="regionForm.city" placeholder="请输入城市,如:深圳市" />
          </el-form-item>
          <el-form-item label="县/区" prop="area">
            <el-input v-model="regionForm.area" placeholder="请输入县/区,如:南山区" />
          </el-form-item>
          <el-form-item label="详细地址" prop="address">
            <el-input v-model="regionForm.address" type="textarea" placeholder="请输入详细地址或关键词" :rows="3" />
          </el-form-item>
        </div>
        <!-- 指定医院配置 -->
        <div v-if="regionForm.regionType === 'HOSPITAL'">
          <el-form-item label="医院名称" prop="hospitalName">
            <el-input v-model="regionForm.hospitalName" placeholder="请输入医院名称或关键词" />
          </el-form-item>
        </div>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="regionForm.status">
            <el-radio label="0">正常</el-radio>
            <el-radio label="1">停用</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="regionForm.remark" type="textarea" placeholder="请输入备注" :rows="2" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitRegionForm">确 定</el-button>
        <el-button @click="regionFormVisible = false">取 消</el-button>
      </div>
    </el-dialog>
    <!-- 添加或修改部门对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
@@ -197,6 +290,7 @@
<script>
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept";
import { listDeptRegion, addDeptRegion, updateDeptRegion, delDeptRegion } from "@/api/system/deptRegion";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
@@ -231,6 +325,21 @@
      },
      // 表单参数
      form: {},
      // 区域管理相关
      regionDialogVisible: false,
      currentDept: null,
      regionList: [],
      regionFormVisible: false,
      regionFormTitle: '',
      regionForm: {},
      regionRules: {
        regionType: [
          { required: true, message: "区域类型不能为空", trigger: "change" }
        ],
        status: [
          { required: true, message: "状态不能为空", trigger: "change" }
        ]
      },
      // 表单校验
      rules: {
        parentId: [
@@ -397,6 +506,87 @@
        this.getList();
        this.$modal.msgSuccess("删除成功");
      }).catch(() => {});
    },
    /** 管理区域按钮操作 */
    handleManageRegion(row) {
      this.currentDept = row;
      this.regionDialogVisible = true;
      this.loadRegionList();
    },
    /** 加载区域列表 */
    loadRegionList() {
      if (!this.currentDept) return;
      listDeptRegion(this.currentDept.deptId).then(response => {
        this.regionList = response.data;
      });
    },
    /** 新增区域 */
    handleAddRegion() {
      this.resetRegionForm();
      this.regionFormTitle = "新增区域";
      this.regionFormVisible = true;
    },
    /** 编辑区域 */
    handleEditRegion(row) {
      this.regionForm = { ...row };
      this.regionFormTitle = "编辑区域";
      this.regionFormVisible = true;
    },
    /** 删除区域 */
    handleDeleteRegion(row) {
      this.$modal.confirm('是否确认删除该区域配置?').then(() => {
        return delDeptRegion(row.regionId);
      }).then(() => {
        this.$modal.msgSuccess("删除成功");
        this.loadRegionList();
      }).catch(() => {});
    },
    /** 重置区域表单 */
    resetRegionForm() {
      this.regionForm = {
        regionId: undefined,
        deptId: this.currentDept.deptId,
        regionType: 'AREA',
        province: '',
        city: '',
        area: '',
        address: '',
        hospitalName: '',
        status: '0',
        remark: ''
      };
      this.resetForm("regionForm");
    },
    /** 区域类型切换 */
    handleRegionTypeChange(value) {
      if (value === 'AREA') {
        this.regionForm.hospitalName = '';
      } else if (value === 'HOSPITAL') {
        this.regionForm.province = '';
        this.regionForm.city = '';
        this.regionForm.area = '';
        this.regionForm.address = '';
      }
    },
    /** 提交区域表单 */
    submitRegionForm() {
      this.$refs["regionForm"].validate(valid => {
        if (valid) {
          if (this.regionForm.regionId) {
            updateDeptRegion(this.regionForm).then(response => {
              this.$modal.msgSuccess("修改成功");
              this.regionFormVisible = false;
              this.loadRegionList();
            });
          } else {
            addDeptRegion(this.regionForm).then(response => {
              this.$modal.msgSuccess("新增成功");
              this.regionFormVisible = false;
              this.loadRegionList();
            });
          }
        }
      });
    }
  }
};
ruoyi-ui/src/views/system/hosp/index.vue
New file
@@ -0,0 +1,411 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
      <el-form-item label="医院名称" prop="hospName">
        <el-input
          v-model="queryParams.hospName"
          placeholder="请输入医院名称"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="省份" prop="hopsProvince">
        <el-input
          v-model="queryParams.hopsProvince"
          placeholder="请输入省份"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="城市" prop="hopsCity">
        <el-input
          v-model="queryParams.hopsCity"
          placeholder="请输入城市"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="区域" prop="hopsArea">
        <el-input
          v-model="queryParams.hopsArea"
          placeholder="请输入区域"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <el-select v-model="queryParams.status" placeholder="医院状态" clearable>
          <el-option
            v-for="dict in dict.type.sys_normal_disable"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['system:hosp:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-edit"
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['system:hosp:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['system:hosp:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['system:hosp:export']"
        >导出</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="info"
          plain
          icon="el-icon-refresh"
          size="mini"
          @click="handleSync"
          v-hasPermi="['system:hosp:sync']"
          :loading="syncing"
        >同步数据</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
    <el-table v-loading="loading" :data="hospList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="医院ID" align="center" prop="hospId" width="80" />
      <el-table-column label="旧系统ID" align="center" prop="legacyHospId" width="100" />
      <el-table-column label="医院名称" align="center" prop="hospName" :show-overflow-tooltip="true" min-width="180" />
      <el-table-column label="医院简称" align="center" prop="hospShort" :show-overflow-tooltip="true" width="120" />
      <el-table-column label="省份" align="center" prop="hopsProvince" width="100" />
      <el-table-column label="城市" align="center" prop="hopsCity" width="100" />
      <el-table-column label="区域" align="center" prop="hopsArea" width="100" />
      <el-table-column label="医院地址" align="center" prop="hospAddress" :show-overflow-tooltip="true" min-width="200" />
      <el-table-column label="联系电话" align="center" prop="hospTel" width="120" />
      <el-table-column label="状态" align="center" prop="status" width="80">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['system:hosp:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['system:hosp:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
    <!-- 添加或修改医院数据对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
        <el-row>
          <el-col :span="12">
            <el-form-item label="医院名称" prop="hospName">
              <el-input v-model="form.hospName" placeholder="请输入医院名称" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="医院简称" prop="hospShort">
              <el-input v-model="form.hospShort" placeholder="请输入医院简称" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="8">
            <el-form-item label="省份" prop="hopsProvince">
              <el-input v-model="form.hopsProvince" placeholder="请输入省份" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="城市" prop="hopsCity">
              <el-input v-model="form.hopsCity" placeholder="请输入城市" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="区域" prop="hopsArea">
              <el-input v-model="form.hopsArea" placeholder="请输入区域" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="医院地址" prop="hospAddress">
              <el-input v-model="form.hospAddress" placeholder="请输入医院地址" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="联系电话" prop="hospTel">
              <el-input v-model="form.hospTel" placeholder="请输入联系电话" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="医院级别" prop="hospLevel">
              <el-input-number v-model="form.hospLevel" :min="0" :max="5" placeholder="请输入医院级别" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
            <el-form-item label="旧系统ID" prop="legacyHospId">
              <el-input-number v-model="form.legacyHospId" :disabled="form.hospId != undefined" placeholder="旧系统医院ID" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="状态" prop="status">
              <el-radio-group v-model="form.status">
                <el-radio
                  v-for="dict in dict.type.sys_normal_disable"
                  :key="dict.value"
                  :label="dict.value"
                >{{dict.label}}</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="备注" prop="remark">
              <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { listHosp, getHosp, delHosp, addHosp, updateHosp, syncHosp } from "@/api/system/hosp";
export default {
  name: "Hosp",
  dicts: ['sys_normal_disable'],
  data() {
    return {
      // 遮罩层
      loading: true,
      // 同步加载状态
      syncing: false,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // 医院数据表格数据
      hospList: [],
      // 弹出层标题
      title: "",
      // 是否显示弹出层
      open: false,
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        hospName: null,
        hopsProvince: null,
        hopsCity: null,
        hopsArea: null,
        status: null
      },
      // 表单参数
      form: {},
      // 表单校验
      rules: {
        hospName: [
          { required: true, message: "医院名称不能为空", trigger: "blur" }
        ]
      }
    };
  },
  created() {
    this.getList();
  },
  methods: {
    /** 查询医院数据列表 */
    getList() {
      this.loading = true;
      listHosp(this.queryParams).then(response => {
        this.hospList = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },
    // 取消按钮
    cancel() {
      this.open = false;
      this.reset();
    },
    // 表单重置
    reset() {
      this.form = {
        hospId: null,
        legacyHospId: null,
        hospName: null,
        hospCityId: null,
        hospShort: null,
        hopsProvince: null,
        hopsCity: null,
        hopsArea: null,
        hospAddress: null,
        hospTel: null,
        hospUnitId: null,
        hospState: null,
        hospOaId: null,
        hospIntroducerId: null,
        hospIntroducerDate: null,
        hospLevel: null,
        status: "0",
        remark: null
      };
      this.resetForm("form");
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.hospId)
      this.single = selection.length !== 1
      this.multiple = !selection.length
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset();
      this.open = true;
      this.title = "添加医院数据";
    },
    /** 修改按钮操作 */
    handleUpdate(row) {
      this.reset();
      const hospId = row.hospId || this.ids
      getHosp(hospId).then(response => {
        this.form = response.data;
        this.open = true;
        this.title = "修改医院数据";
      });
    },
    /** 提交按钮 */
    submitForm() {
      this.$refs["form"].validate(valid => {
        if (valid) {
          if (this.form.hospId != null) {
            updateHosp(this.form).then(response => {
              this.$modal.msgSuccess("修改成功");
              this.open = false;
              this.getList();
            });
          } else {
            addHosp(this.form).then(response => {
              this.$modal.msgSuccess("新增成功");
              this.open = false;
              this.getList();
            });
          }
        }
      });
    },
    /** 删除按钮操作 */
    handleDelete(row) {
      const hospIds = row.hospId || this.ids;
      this.$modal.confirm('是否确认删除医院数据编号为"' + hospIds + '"的数据项?').then(function() {
        return delHosp(hospIds);
      }).then(() => {
        this.getList();
        this.$modal.msgSuccess("删除成功");
      }).catch(() => {});
    },
    /** 导出按钮操作 */
    handleExport() {
      this.download('system/hosp/export', {
        ...this.queryParams
      }, `hosp_${new Date().getTime()}.xlsx`)
    },
    /** 同步数据按钮操作 */
    handleSync() {
      this.$modal.confirm('是否确认从SQL Server同步医院数据?此操作可能需要较长时间。').then(() => {
        this.syncing = true;
        return syncHosp();
      }).then(response => {
        this.syncing = false;
        this.$modal.msgSuccess("同步完成:" + response.msg);
        this.getList();
      }).catch(() => {
        this.syncing = false;
      });
    }
  }
};
</script>
sql/HospData.sql
New file
@@ -0,0 +1,17 @@
create table HospData(
HospID    int    no    4    10       0        no    (n/a)    (n/a)    NULL
HospName    nvarchar    no    200                      yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
HospCityID    int    no    4    10       0        yes    (n/a)    (n/a)    NULL
HospShort    nvarchar    no    200                      yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
HopsProvince    nvarchar    no    100                      yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
HopsCity    nvarchar    no    100                      yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
HopsArea    nvarchar    no    100                      yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
HospAddress    nvarchar    no    200                      yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
HospTEL    nvarchar    no    100                      yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
HospUnitID    int    no    4    10       0        yes    (n/a)    (n/a)    NULL
HospState    int    no    4    10       0        yes    (n/a)    (n/a)    NULL
HospOAID    nvarchar    no    100                      yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
HospIntroducerID    int    no    4    10       0        yes    (n/a)    (n/a)    NULL
HospIntroducerDate    datetime    no    8                      yes    (n/a)    (n/a)    NULL
HospLevel    int    no    4    10       0        yes    (n/a)    (n/a)    NULL
)
sql/hosp_data_menu.sql
New file
@@ -0,0 +1,38 @@
-- 医院数据管理菜单和权限SQL
-- 菜单 SQL
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES
('医院数据', 1, 8, 'hosp', 'system/hosp/index', 1, 0, 'C', '0', '0', 'system:hosp:list', 'hospital', 'admin', sysdate(), '', null, '医院数据菜单');
-- 获取刚插入的父菜单ID(需要手动执行下面的SQL,替换@parentId)
SET @parentId = LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES
('医院数据查询', @parentId, 1,  '#', '', 1, 0, 'F', '0', '0', 'system:hosp:query',        '#', 'admin', sysdate(), '', null, ''),
('医院数据新增', @parentId, 2,  '#', '', 1, 0, 'F', '0', '0', 'system:hosp:add',          '#', 'admin', sysdate(), '', null, ''),
('医院数据修改', @parentId, 3,  '#', '', 1, 0, 'F', '0', '0', 'system:hosp:edit',         '#', 'admin', sysdate(), '', null, ''),
('医院数据删除', @parentId, 4,  '#', '', 1, 0, 'F', '0', '0', 'system:hosp:remove',       '#', 'admin', sysdate(), '', null, ''),
('医院数据导出', @parentId, 5,  '#', '', 1, 0, 'F', '0', '0', 'system:hosp:export',       '#', 'admin', sysdate(), '', null, ''),
('医院数据同步', @parentId, 6,  '#', '', 1, 0, 'F', '0', '0', 'system:hosp:sync',         '#', 'admin', sysdate(), '', null, '');
-- 定时任务配置(可选,用于定时同步)
INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
VALUES
('医院数据同步', 'DEFAULT', 'hospDataSyncTask.syncHospData', '0 0 2 * * ?', '3', '1', '1', 'admin', sysdate(), '每天凌晨2点同步一次医院数据');
-- 说明:
-- 1. 菜单路径为 /system/hosp,对应前端页面需要创建
-- 2. 权限标识:
--    - system:hosp:list   - 查询医院列表
--    - system:hosp:query  - 查询医院详情
--    - system:hosp:add    - 新增医院
--    - system:hosp:edit   - 编辑医院
--    - system:hosp:remove - 删除医院
--    - system:hosp:export - 导出医院数据
--    - system:hosp:sync   - 同步医院数据(从SQL Server)
-- 3. 定时任务默认状态为暂停(status=1),需要手动启动
-- 4. 定时任务表达式 '0 0 2 * * ?' 表示每天凌晨2点执行
sql/sys_dept_region.sql
New file
@@ -0,0 +1,64 @@
-- 部门管理区域表
-- 用于配置每个分公司/部门可管理的地域范围,支持省、市、县/区、详细地址等多级区域
CREATE TABLE IF NOT EXISTS `sys_dept_region` (
  `region_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '区域ID',
  `dept_id` bigint(20) NOT NULL COMMENT '部门ID',
  `province` varchar(100) DEFAULT NULL COMMENT '省份',
  `city` varchar(100) DEFAULT NULL COMMENT '城市',
  `area` varchar(100) DEFAULT NULL COMMENT '县/区',
  `address` varchar(255) DEFAULT NULL COMMENT '详细地址(可选)',
  `hospital_name` varchar(255) DEFAULT NULL COMMENT '指定医院名称(可选,用于精确匹配特定医院)',
  `region_type` varchar(20) DEFAULT 'AREA' COMMENT '区域类型:AREA-地域范围,HOSPITAL-指定医院',
  `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`region_id`),
  KEY `idx_dept_id` (`dept_id`),
  KEY `idx_province` (`province`),
  KEY `idx_city` (`city`),
  KEY `idx_area` (`area`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门管理区域表';
-- 插入示例数据
-- 广州总部:管理整个广州市
INSERT INTO `sys_dept_region` (`dept_id`, `province`, `city`, `area`, `region_type`, `remark`, `create_time`)
SELECT dept_id, '广东省', '广州市', NULL, 'AREA', '广州总部管理整个广州市', NOW()
FROM sys_dept WHERE dept_name LIKE '%广州总%' AND parent_id = 100 LIMIT 1;
-- 黄埔分公司:管理黄埔区
INSERT INTO `sys_dept_region` (`dept_id`, `province`, `city`, `area`, `region_type`, `remark`, `create_time`)
SELECT dept_id, '广东省', '广州市', '黄埔区', 'AREA', '黄埔分公司管理黄埔区', NOW()
FROM sys_dept WHERE dept_name LIKE '%黄埔%' AND parent_id = 100 LIMIT 1;
-- 深圳分公司:管理整个深圳市
INSERT INTO `sys_dept_region` (`dept_id`, `province`, `city`, `area`, `region_type`, `remark`, `create_time`)
SELECT dept_id, '广东省', '深圳市', NULL, 'AREA', '深圳分公司管理整个深圳市', NOW()
FROM sys_dept WHERE dept_name LIKE '%深圳%' AND parent_id = 100 LIMIT 1;
-- 省二医分公司:仅管理指定医院
INSERT INTO `sys_dept_region` (`dept_id`, `province`, `city`, `hospital_name`, `region_type`, `remark`, `create_time`)
SELECT dept_id, '广东省', '广州市', '广东省第二人民医院', 'HOSPITAL', '省二医分公司仅管理省二医及相关医院', NOW()
FROM sys_dept WHERE dept_name LIKE '%省二医%' AND parent_id = 100 LIMIT 1;
INSERT INTO `sys_dept_region` (`dept_id`, `province`, `city`, `hospital_name`, `region_type`, `remark`, `create_time`)
SELECT dept_id, '广东省', '广州市', '省二医', 'HOSPITAL', '省二医分公司仅管理省二医及相关医院(简称匹配)', NOW()
FROM sys_dept WHERE dept_name LIKE '%省二医%' AND parent_id = 100 LIMIT 1;
-- 说明:
-- 1. region_type = 'AREA' 表示按地域范围管理
--    - 可以配置省、市、县/区,支持层级匹配
--    - province、city、area 都为 NULL 表示不限制
--    - 只配置 province 表示管理整个省
--    - 配置 province + city 表示管理该省的某个市
--    - 配置 province + city + area 表示管理该市的某个区
--
-- 2. region_type = 'HOSPITAL' 表示按指定医院管理
--    - hospital_name 字段用于精确或模糊匹配医院名称
--    - 可以为同一个部门配置多条医院记录
--
-- 3. 一个部门可以配置多条区域记录,表示管理多个区域或多个医院
sql/tb_hosp_data.sql
New file
@@ -0,0 +1,40 @@
-- 医院数据表(MySQL)
-- 用于存储从 SQL Server HospData 表同步过来的医院数据
CREATE TABLE IF NOT EXISTS `tb_hosp_data` (
  `hosp_id` int(11) NOT NULL  COMMENT '医院ID(自增主键)',
  `legacy_hosp_id` int(11) DEFAULT NULL COMMENT '旧系统医院ID(对应SQL Server中的HospID)',
  `hosp_name` varchar(200) DEFAULT NULL COMMENT '医院名称',
  `hosp_city_id` int(11) DEFAULT NULL COMMENT '城市ID',
  `hosp_short` varchar(200) DEFAULT NULL COMMENT '医院简称',
  `hops_province` varchar(100) DEFAULT NULL COMMENT '省份',
  `hops_city` varchar(100) DEFAULT NULL COMMENT '城市',
  `hops_area` varchar(100) DEFAULT NULL COMMENT '区域',
  `hosp_address` varchar(200) DEFAULT NULL COMMENT '医院地址',
  `hosp_tel` varchar(100) DEFAULT NULL COMMENT '医院电话',
  `hosp_unit_id` int(11) DEFAULT NULL COMMENT '单位ID',
  `hosp_state` int(11) DEFAULT NULL COMMENT '状态',
  `hosp_oa_id` varchar(100) DEFAULT NULL COMMENT 'OA ID',
  `hosp_introducer_id` int(11) DEFAULT NULL COMMENT '介绍人ID',
  `hosp_introducer_date` datetime DEFAULT NULL COMMENT '介绍日期',
  `hosp_level` int(11) DEFAULT NULL COMMENT '医院级别',
  `status` char(1) DEFAULT '0' COMMENT '数据状态(0正常 1停用)',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`hosp_id`),
  UNIQUE KEY `uk_legacy_hosp_id` (`legacy_hosp_id`),
  KEY `idx_hosp_name` (`hosp_name`),
  KEY `idx_province` (`hops_province`),
  KEY `idx_city` (`hops_city`),
  KEY `idx_area` (`hops_area`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='医院数据表';
-- 说明:
-- 1. hosp_id 为自增主键,用于内部关联
-- 2. legacy_hosp_id 对应 SQL Server 中的 HospID,设置唯一索引
-- 3. 其他字段与 SQL Server HospData 表保持一致
-- 4. 新增 status、remark、create_by 等标准字段
-- 5. 添加索引以提高查询性能