diff --git a/sql/hospital.sql b/sql/hospital.sql index b6f73b3..138fbb7 100644 --- a/sql/hospital.sql +++ b/sql/hospital.sql @@ -2,10 +2,23 @@ CREATE TABLE `hospital` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '医院名称', + `province_code` varchar(10) DEFAULT '' COMMENT '省份编码', + `city_code` varchar(10) DEFAULT '' COMMENT '城市编码', + `district_code` varchar(10) DEFAULT '' COMMENT '区县编码', + `province_name` varchar(50) DEFAULT '' COMMENT '省份名称', + `city_name` varchar(50) DEFAULT '' COMMENT '城市名称', + `district_name` varchar(50) DEFAULT '' COMMENT '区县名称', `sort` int(11) DEFAULT 0 COMMENT '排序(越大越前)', `is_show` tinyint(1) DEFAULT 1 COMMENT '是否展示: 1是 0否', `is_deleted` tinyint(1) DEFAULT 0 COMMENT '软删除: 0正常 1已删除', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `idx_region` (`province_code`, `city_code`, `district_code`) ) COMMENT='医院列表'; + +-- patient 表新增就诊医院省市区编码 +ALTER TABLE `patient` + ADD COLUMN `hospital_province_code` varchar(10) DEFAULT '' COMMENT '就诊医院省份编码' AFTER `hospital`, + ADD COLUMN `hospital_city_code` varchar(10) DEFAULT '' COMMENT '就诊医院城市编码' AFTER `hospital_province_code`, + ADD COLUMN `hospital_district_code` varchar(10) DEFAULT '' COMMENT '就诊医院区县编码' AFTER `hospital_city_code`; diff --git a/sql/hospital_region_update.sql b/sql/hospital_region_update.sql new file mode 100644 index 0000000..18d41d4 --- /dev/null +++ b/sql/hospital_region_update.sql @@ -0,0 +1,16 @@ +-- 存量库升级脚本:医院表增加省市区字段,患者表记录就诊医院省市区编码 +-- 如果是全新建库,请使用 hospital.sql;如果已执行过对应字段,请不要重复执行本脚本。 + +ALTER TABLE `hospital` + ADD COLUMN `province_code` varchar(10) DEFAULT '' COMMENT '省份编码' AFTER `name`, + ADD COLUMN `city_code` varchar(10) DEFAULT '' COMMENT '城市编码' AFTER `province_code`, + ADD COLUMN `district_code` varchar(10) DEFAULT '' COMMENT '区县编码' AFTER `city_code`, + ADD COLUMN `province_name` varchar(50) DEFAULT '' COMMENT '省份名称' AFTER `district_code`, + ADD COLUMN `city_name` varchar(50) DEFAULT '' COMMENT '城市名称' AFTER `province_name`, + ADD COLUMN `district_name` varchar(50) DEFAULT '' COMMENT '区县名称' AFTER `city_name`, + ADD KEY `idx_region` (`province_code`, `city_code`, `district_code`); + +ALTER TABLE `patient` + ADD COLUMN `hospital_province_code` varchar(10) DEFAULT '' COMMENT '就诊医院省份编码' AFTER `hospital`, + ADD COLUMN `hospital_city_code` varchar(10) DEFAULT '' COMMENT '就诊医院城市编码' AFTER `hospital_province_code`, + ADD COLUMN `hospital_district_code` varchar(10) DEFAULT '' COMMENT '就诊医院区县编码' AFTER `hospital_city_code`; diff --git a/src/config/router.js b/src/config/router.js index 15d18e5..2569b5d 100644 --- a/src/config/router.js +++ b/src/config/router.js @@ -57,6 +57,7 @@ module.exports = [ ['/common/ocr/hmt', 'common/ocrHmt', 'post'], ['/common/sampleTypes', 'common/sampleTypes'], ['/common/hospitals', 'common/hospitals'], + ['/common/hospitalTree', 'common/hospitalTree'], ['/api/content', 'common/content'], // 小程序接口 diff --git a/src/controller/admin/hospital.js b/src/controller/admin/hospital.js index abdacf0..a597a2d 100644 --- a/src/controller/admin/hospital.js +++ b/src/controller/admin/hospital.js @@ -8,13 +8,19 @@ module.exports = class extends Base { return this.display(); } - // 列表 + // 列表(分页) async listAction() { const keyword = this.get('keyword') || ''; + const provinceCode = this.get('province_code') || ''; + const cityCode = this.get('city_code') || ''; + const districtCode = this.get('district_code') || ''; const page = this.get('page') || 1; const pageSize = this.get('pageSize') || 10; const where = { is_deleted: 0 }; if (keyword) where.name = ['like', `%${keyword}%`]; + if (provinceCode) where.province_code = provinceCode; + if (cityCode) where.city_code = cityCode; + if (districtCode) where.district_code = districtCode; const data = await this.model('hospital') .where(where) .order('sort DESC, id ASC') @@ -25,31 +31,47 @@ module.exports = class extends Base { // 新增 async addAction() { - const { name, sort, is_show } = this.post(); + const { name, province_code, city_code, district_code, sort, is_show } = this.post(); if (!name || !name.trim()) return this.fail('请输入医院名称'); const exists = await this.model('hospital').where({ name: name.trim(), is_deleted: 0 }).find(); if (!think.isEmpty(exists)) return this.fail('该医院已存在'); + const regionNames = await this._getRegionNames(province_code, city_code, district_code); await this.model('hospital').add({ name: name.trim(), + province_code: province_code || '', + city_code: city_code || '', + district_code: district_code || '', + province_name: regionNames.province, + city_name: regionNames.city, + district_name: regionNames.district, sort: sort || 0, is_show: is_show === undefined ? 1 : (is_show ? 1 : 0) }); + await this.model('hospital').clearTreeCache(); await this.log('add', '医院管理', `新增医院「${name.trim()}」`); return this.success(); } // 编辑 async editAction() { - const { id, name, sort, is_show } = this.post(); + const { id, name, province_code, city_code, district_code, sort, is_show } = this.post(); if (!id) return this.fail('参数错误'); if (!name || !name.trim()) return this.fail('请输入医院名称'); const exists = await this.model('hospital').where({ name: name.trim(), id: ['!=', id], is_deleted: 0 }).find(); if (!think.isEmpty(exists)) return this.fail('该医院已存在'); + const regionNames = await this._getRegionNames(province_code, city_code, district_code); await this.model('hospital').where({ id }).update({ name: name.trim(), + province_code: province_code || '', + city_code: city_code || '', + district_code: district_code || '', + province_name: regionNames.province, + city_name: regionNames.city, + district_name: regionNames.district, sort: sort || 0, is_show: is_show ? 1 : 0 }); + await this.model('hospital').clearTreeCache(); await this.log('edit', '医院管理', `编辑医院(ID:${id})「${name.trim()}」`); return this.success(); } @@ -59,6 +81,7 @@ module.exports = class extends Base { const { id } = this.post(); if (!id) return this.fail('参数错误'); await this.model('hospital').where({ id }).update({ is_deleted: 1 }); + await this.model('hospital').clearTreeCache(); await this.log('delete', '医院管理', `删除医院(ID:${id})`); return this.success(); } @@ -68,6 +91,7 @@ module.exports = class extends Base { const { id, is_show } = this.post(); if (!id) return this.fail('参数错误'); await this.model('hospital').where({ id }).update({ is_show: is_show ? 1 : 0 }); + await this.model('hospital').clearTreeCache(); await this.log('edit', '医院管理', `${is_show ? '展示' : '隐藏'}医院(ID:${id})`); return this.success(); } @@ -79,32 +103,222 @@ module.exports = class extends Base { for (let i = 0; i < ids.length; i++) { await this.model('hospital').where({ id: ids[i] }).update({ sort: ids.length - i }); } + await this.model('hospital').clearTreeCache(); return this.success(); } - // 批量导入(粘贴文本,每行一个) + // Excel 导入(表头:关联机构名称/省份/城市/区县) async importAction() { - const { text } = this.post(); - if (!text || !text.trim()) return this.fail('请输入医院名称'); - const names = text.split('\n').map(s => s.trim()).filter(Boolean); - if (!names.length) return this.fail('没有有效的医院名称'); + const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + let file = this.file('file'); + if (!file || !file.path) { + await sleep(1000); + file = this.file('file'); + } + if (!file || !file.path) return this.fail('请上传 Excel 文件'); + + const ExcelJS = require('exceljs'); + const workbook = new ExcelJS.Workbook(); + try { + await workbook.xlsx.readFile(file.path); + } catch (e) { + return this.fail('文件解析失败,请上传有效的 Excel 文件'); + } + const sheet = workbook.worksheets[0]; + if (!sheet) return this.fail('Excel 内容为空'); + + // 读取数据行(跳过表头) + const rows = []; + sheet.eachRow((row, rowNumber) => { + if (rowNumber === 1) return; // 表头 + const name = this._cellText(row.getCell(1)); + const province = this._cellText(row.getCell(2)); + const city = this._cellText(row.getCell(3)); + const district = this._cellText(row.getCell(4)); + if (name) rows.push({ name, province, city, district }); + }); + + if (!rows.length) return this.fail('没有有效的数据行'); - // 查已存在的,去重 + // 预加载全部地区,构建名称索引 + const regions = await this.model('sys_region').where({ status: 1 }).select(); + const regionIndex = this._buildRegionIndex(regions); + + // 已存在的医院名称 const existRows = await this.model('hospital').where({ is_deleted: 0 }).field('name').select(); const existNames = new Set(existRows.map(r => r.name)); const toInsert = []; const seen = new Set(); - names.forEach(name => { - if (!existNames.has(name) && !seen.has(name)) { - seen.add(name); - toInsert.push({ name, sort: 0, is_show: 1 }); + const unmatched = []; // 地区未匹配的医院名 + let skipped = 0; + + rows.forEach(r => { + if (existNames.has(r.name) || seen.has(r.name)) { skipped++; return; } + seen.add(r.name); + const codes = this._matchRegion(regionIndex, r.province, r.city, r.district); + if ((r.province && !codes.province_code) || (r.city && !codes.city_code) || (r.district && !codes.district_code)) { + unmatched.push(r.name); } + toInsert.push({ + name: r.name, + province_code: codes.province_code, + city_code: codes.city_code, + district_code: codes.district_code, + province_name: r.province || '', + city_name: r.city || '', + district_name: r.district || '', + sort: 0, + is_show: 1 + }); }); if (!toInsert.length) return this.fail('没有新增医院(均已存在)'); - await this.model('hospital').addMany(toInsert); - await this.log('import', '医院管理', `批量导入医院 ${toInsert.length} 条`); - return this.success({ count: toInsert.length, skipped: names.length - toInsert.length }); + + // 分批插入 + const batchSize = 200; + for (let i = 0; i < toInsert.length; i += batchSize) { + await this.model('hospital').addMany(toInsert.slice(i, i + batchSize)); + } + await this.model('hospital').clearTreeCache(); + await this.log('import', '医院管理', `导入医院 ${toInsert.length} 条`); + + return this.success({ + count: toInsert.length, + skipped, + unmatchedCount: unmatched.length, + unmatched: unmatched.slice(0, 50) + }); + } + + // @private 读取单元格文本 + _cellText(cell) { + if (!cell || cell.value === null || cell.value === undefined) return ''; + const v = cell.value; + if (typeof v === 'object') { + if (v.text) return String(v.text).trim(); + if (v.result !== undefined) return String(v.result).trim(); + if (v.richText) return v.richText.map(t => t.text).join('').trim(); + return ''; + } + return String(v).trim(); + } + + // @private 构建地区名称索引:{ provinceName: {code, cities: { cityName: {code, districts: { distName: code }}}}} + _buildRegionIndex(regions) { + const provinces = {}; + const cityByParent = {}; + const distByParent = {}; + regions.forEach(r => { + const code = r.code; + if (code.endsWith('0000')) { + provinces[r.name] = { code, cities: {} }; + } else if (code.endsWith('00')) { + const pCode = code.substring(0, 2) + '0000'; + if (!cityByParent[pCode]) cityByParent[pCode] = []; + cityByParent[pCode].push({ code, name: r.name }); + } else { + const cCode = code.substring(0, 4) + '00'; + if (!distByParent[cCode]) distByParent[cCode] = []; + distByParent[cCode].push({ code, name: r.name }); + } + }); + // 组装 + Object.values(provinces).forEach(prov => { + const cities = cityByParent[prov.code] || []; + cities.forEach(c => { + prov.cities[c.name] = { code: c.code, districts: {} }; + const dists = distByParent[c.code] || []; + dists.forEach(d => { prov.cities[c.name].districts[d.name] = d.code; }); + }); + }); + return provinces; + } + + // @private 名称匹配编码(容错:直辖市省市同名、模糊包含) + _matchRegion(index, provinceName, cityName, districtName) { + const result = { province_code: '', city_code: '', district_code: '' }; + if (!provinceName) return result; + + // 匹配省(容错:去掉"省/市/自治区"等后缀的包含匹配) + let prov = index[provinceName]; + if (!prov) { + const pKey = Object.keys(index).find(k => k.includes(provinceName) || provinceName.includes(k)); + if (pKey) prov = index[pKey]; + } + if (!prov) return result; + result.province_code = prov.code; + + // 匹配市 + let city = prov.cities[cityName]; + if (!city && cityName) { + const cKey = Object.keys(prov.cities).find(k => k.includes(cityName) || cityName.includes(k)); + if (cKey) city = prov.cities[cKey]; + } + // 直辖市:省名=市名,市层可能只有一个 + if (!city) { + const cityKeys = Object.keys(prov.cities); + if (cityKeys.length === 1) city = prov.cities[cityKeys[0]]; + } + + // 省直辖县级行政区、部分直辖市导入数据可能出现「城市=区县」: + // 此时城市名匹配不到标准市级节点,用「省 + 区县」在该省下横向查一次。 + if (!city && districtName) { + const matched = this._findDistrictInProvince(prov, districtName); + if (matched) { + result.city_code = matched.city.code; + result.district_code = matched.districtCode; + } + return result; + } + if (!city) return result; + result.city_code = city.code; + + // 匹配区县 + if (districtName) { + let dCode = city.districts[districtName]; + if (!dCode) { + const dKey = Object.keys(city.districts).find(k => k.includes(districtName) || districtName.includes(k)); + if (dKey) dCode = city.districts[dKey]; + } + if (!dCode) { + const matched = this._findDistrictInProvince(prov, districtName); + if (matched) { + result.city_code = matched.city.code; + dCode = matched.districtCode; + } + } + if (dCode) result.district_code = dCode; + } + return result; + } + + // @private 在指定省份下按区县名称横向匹配,并返回其所属城市 + _findDistrictInProvince(prov, districtName) { + if (!prov || !districtName) return null; + for (const city of Object.values(prov.cities)) { + let dCode = city.districts[districtName]; + if (!dCode) { + const dKey = Object.keys(city.districts).find(k => k.includes(districtName) || districtName.includes(k)); + if (dKey) dCode = city.districts[dKey]; + } + if (dCode) return { city, districtCode: dCode }; + } + return null; + } + + // @private 根据编码查地区名称 + async _getRegionNames(provinceCode, cityCode, districtCode) { + const codes = [provinceCode, cityCode, districtCode].filter(Boolean); + const map = {}; + if (codes.length) { + const regions = await this.model('sys_region').where({ code: ['in', codes] }).select(); + regions.forEach(r => { map[r.code] = r.name; }); + } + return { + province: map[provinceCode] || '', + city: map[cityCode] || '', + district: map[districtCode] || '' + }; } }; diff --git a/src/controller/admin/patient.js b/src/controller/admin/patient.js index 7f5e844..e451ad7 100644 --- a/src/controller/admin/patient.js +++ b/src/controller/admin/patient.js @@ -98,7 +98,8 @@ module.exports = class extends Base { // 查询省市区名称(包含寄回地址) const allCodes = [ patient.province_code, patient.city_code, patient.district_code, - patient.return_province_code, patient.return_city_code, patient.return_district_code + patient.return_province_code, patient.return_city_code, patient.return_district_code, + patient.hospital_province_code, patient.hospital_city_code, patient.hospital_district_code ].filter(Boolean); if (allCodes.length) { const regions = await this.model('sys_region') @@ -112,6 +113,9 @@ module.exports = class extends Base { patient.return_province_name = regionMap[patient.return_province_code] || ''; patient.return_city_name = regionMap[patient.return_city_code] || ''; patient.return_district_name = regionMap[patient.return_district_code] || ''; + patient.hospital_province_name = regionMap[patient.hospital_province_code] || ''; + patient.hospital_city_name = regionMap[patient.hospital_city_code] || ''; + patient.hospital_district_name = regionMap[patient.hospital_district_code] || ''; } // 获取审核记录 @@ -126,7 +130,7 @@ module.exports = class extends Base { // 新增患者 async addAction() { const data = this.post(); - const { name, phone, id_card, gender, birth_date, province_code, city_code, district_code, address, hospital, emergency_contact, emergency_phone, tag, documents, sample_types, wax_return, return_name, return_phone, return_province_code, return_city_code, return_district_code, return_address, report_email, sample_tracking_no, sample_photos, sign_income, sign_privacy, sign_promise, sign_privacy_jhr, income_amount, guardian_name, guardian_id_card, guardian_relation } = data; + const { name, phone, id_card, gender, birth_date, province_code, city_code, district_code, address, hospital, hospital_province_code, hospital_city_code, hospital_district_code, emergency_contact, emergency_phone, tag, documents, sample_types, wax_return, return_name, return_phone, return_province_code, return_city_code, return_district_code, return_address, report_email, sample_tracking_no, sample_photos, sign_income, sign_privacy, sign_promise, sign_privacy_jhr, income_amount, guardian_name, guardian_id_card, guardian_relation } = data; if (!name || !phone || !id_card || !gender || !birth_date) { return this.fail('请填写完整信息'); @@ -178,6 +182,9 @@ module.exports = class extends Base { district_code: district_code || '', address: address || '', hospital: hospital || '', + hospital_province_code: hospital_province_code || '', + hospital_city_code: hospital_city_code || '', + hospital_district_code: hospital_district_code || '', emergency_contact: emergency_contact || '', emergency_phone: emergency_phone || '', tag: tag || '', @@ -332,7 +339,7 @@ module.exports = class extends Base { // 编辑患者 async editAction() { const data = this.post(); - const { id, name, phone, id_card, gender, birth_date, province_code, city_code, district_code, address, hospital, emergency_contact, emergency_phone, tag, documents, sample_types, wax_return, return_name, return_phone, return_province_code, return_city_code, return_district_code, return_address, report_email, sample_tracking_no, sample_photos, sign_income, sign_privacy, sign_promise, sign_privacy_jhr, income_amount, guardian_name, guardian_id_card, guardian_relation } = data; + const { id, name, phone, id_card, gender, birth_date, province_code, city_code, district_code, address, hospital, hospital_province_code, hospital_city_code, hospital_district_code, emergency_contact, emergency_phone, tag, documents, sample_types, wax_return, return_name, return_phone, return_province_code, return_city_code, return_district_code, return_address, report_email, sample_tracking_no, sample_photos, sign_income, sign_privacy, sign_promise, sign_privacy_jhr, income_amount, guardian_name, guardian_id_card, guardian_relation } = data; if (!id) return this.fail('参数错误'); if (!name || !phone || !id_card || !gender || !birth_date) return this.fail('请填写完整信息'); @@ -355,6 +362,9 @@ module.exports = class extends Base { province_code, city_code, district_code, address: address || '', hospital: hospital || '', + hospital_province_code: hospital_province_code || '', + hospital_city_code: hospital_city_code || '', + hospital_district_code: hospital_district_code || '', emergency_contact: emergency_contact || '', emergency_phone: emergency_phone || '', tag: tag || '', @@ -400,6 +410,9 @@ module.exports = class extends Base { if (item.return_province_code) allCodes.add(item.return_province_code); if (item.return_city_code) allCodes.add(item.return_city_code); if (item.return_district_code) allCodes.add(item.return_district_code); + if (item.hospital_province_code) allCodes.add(item.hospital_province_code); + if (item.hospital_city_code) allCodes.add(item.hospital_city_code); + if (item.hospital_district_code) allCodes.add(item.hospital_district_code); }); const regionMap = {}; if (allCodes.size) { @@ -425,7 +438,7 @@ module.exports = class extends Base { const statusMap = { '-1': '待提交', 0: '待审核', 1: '审核通过', 2: '已驳回' }; const header = [ 'ID', '姓名', '性别', '身份证', '手机号', '省份', '城市', '详细地址', - '医院名称', '紧急联系人', '紧急联系电话', '瘤种', + '医院名称', '医院省份', '医院城市', '医院区县', '紧急联系人', '紧急联系电话', '瘤种', '送检样本类型', '是否需寄回', '收件人', '收件电话', '收件地址', '报告接收邮箱', '送检物流单号', '提交时间', '审核状态', '审核日期', '审核驳回原因' @@ -479,6 +492,9 @@ module.exports = class extends Base { regionMap[item.city_code] || '', item.address || '', item.hospital || '', + regionMap[item.hospital_province_code] || '', + regionMap[item.hospital_city_code] || '', + regionMap[item.hospital_district_code] || '', item.emergency_contact || '', item.emergency_phone || '', item.tag || '', diff --git a/src/controller/common.js b/src/controller/common.js index 8c7d93e..04af260 100644 --- a/src/controller/common.js +++ b/src/controller/common.js @@ -165,7 +165,21 @@ module.exports = class extends think.Controller { } /** - * 获取医院列表(展示中的) + * 获取医院树(省→市→区→医院),用于小程序逐级选择 + * GET /common/hospitalTree + */ + async hospitalTreeAction() { + try { + const tree = await this.model('hospital').getTree(); + return this.json({ code: 0, data: tree }); + } catch (error) { + think.logger.error('获取医院树失败:', error); + return this.json({ code: 0, data: [] }); + } + } + + /** + * 获取医院列表(兼容旧版小程序/H5) * GET /common/hospitals */ async hospitalsAction() { diff --git a/src/controller/mp.js b/src/controller/mp.js index 5effa80..b544565 100644 --- a/src/controller/mp.js +++ b/src/controller/mp.js @@ -363,7 +363,10 @@ module.exports = class extends Base { const patient = await this.model('patient').where({ id: user.patient_id, is_deleted: 0 }).find(); if (think.isEmpty(patient)) return this.json({ code: 0, data: null }); - const codes = [patient.province_code, patient.city_code, patient.district_code].filter(Boolean); + const codes = [ + patient.province_code, patient.city_code, patient.district_code, + patient.hospital_province_code, patient.hospital_city_code, patient.hospital_district_code + ].filter(Boolean); const regionMap = {}; if (codes.length) { const regions = await this.model('sys_region').where({ code: ['in', codes] }).select(); @@ -396,6 +399,12 @@ module.exports = class extends Base { district_name: regionMap[patient.district_code] || '', address: patient.address || '', hospital: patient.hospital || '', + hospital_province_code: patient.hospital_province_code || '', + hospital_city_code: patient.hospital_city_code || '', + hospital_district_code: patient.hospital_district_code || '', + hospital_province_name: regionMap[patient.hospital_province_code] || '', + hospital_city_name: regionMap[patient.hospital_city_code] || '', + hospital_district_name: regionMap[patient.hospital_district_code] || '', emergency_contact: patient.emergency_contact || '', emergency_phone: patient.emergency_phone || '', tag: patient.tag || '', documents, @@ -431,7 +440,7 @@ module.exports = class extends Base { async saveMyInfoAction() { const mpUser = this.mpUser; if (!mpUser) return this.json({ code: 1009, msg: '请先登录' }); - const { gender, province_code, city_code, district_code, address, hospital, emergency_contact, emergency_phone, documents, tag, sign_income, sign_privacy, sign_privacy_jhr, sign_promise, income_amount, guardian_name, guardian_id_card, guardian_relation, mp_env_version } = this.post(); + const { gender, province_code, city_code, district_code, address, hospital, hospital_province_code, hospital_city_code, hospital_district_code, emergency_contact, emergency_phone, documents, tag, sign_income, sign_privacy, sign_privacy_jhr, sign_promise, income_amount, guardian_name, guardian_id_card, guardian_relation, mp_env_version } = this.post(); const user = await this.model('wechat_user').where({ id: mpUser.id, status: 1 }).find(); if (think.isEmpty(user) || !user.patient_id) return this.json({ code: 1, msg: '请先完成实名认证' }); if (!province_code || !city_code || !district_code) return this.json({ code: 1, msg: '请选择省市区' }); @@ -444,10 +453,27 @@ module.exports = class extends Base { if (mp_env_version) { await this.model('wechat_user').where({ id: mpUser.id }).update({ mp_env_version }); } + let finalHospitalProvinceCode = hospital_province_code || ''; + let finalHospitalCityCode = hospital_city_code || ''; + let finalHospitalDistrictCode = hospital_district_code || ''; + if (hospital && (!finalHospitalProvinceCode || !finalHospitalCityCode || !finalHospitalDistrictCode)) { + const hospitalRow = await this.model('hospital') + .where({ name: hospital, is_deleted: 0 }) + .find(); + if (!think.isEmpty(hospitalRow)) { + finalHospitalProvinceCode = finalHospitalProvinceCode || hospitalRow.province_code || ''; + finalHospitalCityCode = finalHospitalCityCode || hospitalRow.city_code || ''; + finalHospitalDistrictCode = finalHospitalDistrictCode || hospitalRow.district_code || ''; + } + } + await this.model('patient').where({ id: user.patient_id }).update({ gender: gender || '', province_code: province_code || '', city_code: city_code || '', district_code: district_code || '', address: address || '', hospital: hospital || '', + hospital_province_code: finalHospitalProvinceCode, + hospital_city_code: finalHospitalCityCode, + hospital_district_code: finalHospitalDistrictCode, emergency_contact: emergency_contact || '', emergency_phone: emergency_phone || '', tag: tag || '', documents: JSON.stringify(documents || []), diff --git a/src/model/hospital.js b/src/model/hospital.js index 3e41380..1a6775f 100644 --- a/src/model/hospital.js +++ b/src/model/hospital.js @@ -1,10 +1,76 @@ +const HOSPITAL_TREE_CACHE_KEY = 'hospital_tree'; + module.exports = class extends think.Model { get tableName() { return 'hospital'; } /** - * 获取展示中的医院列表(小程序用) + * 获取展示中的医院树(省→市→区→医院),带缓存 + */ + async getTree() { + const cached = await think.cache(HOSPITAL_TREE_CACHE_KEY); + if (cached) return cached; + + const list = await this.where({ is_show: 1, is_deleted: 0 }) + .order('sort DESC, id ASC') + .select(); + + // 组装 省→市→区→医院 树 + const provinceMap = {}; + list.forEach(h => { + const pCode = h.province_code || ''; + const pName = h.province_name || '其他'; + const pKey = pCode || `unknown:${pName}`; + const cCode = h.city_code || ''; + const cName = h.city_name || '其他'; + const cKey = cCode || `unknown:${cName}`; + const dCode = h.district_code || ''; + const dName = h.district_name || '其他'; + const dKey = dCode || `unknown:${dName}`; + + if (!provinceMap[pKey]) { + provinceMap[pKey] = { provinceCode: pCode, provinceName: pName, _cityMap: {} }; + } + const prov = provinceMap[pKey]; + if (!prov._cityMap[cKey]) { + prov._cityMap[cKey] = { cityCode: cCode, cityName: cName, _distMap: {} }; + } + const city = prov._cityMap[cKey]; + if (!city._distMap[dKey]) { + city._distMap[dKey] = { districtCode: dCode, districtName: dName, hospitals: [] }; + } + city._distMap[dKey].hospitals.push({ + id: h.id, + name: h.name, + province_code: h.province_code, + city_code: h.city_code, + district_code: h.district_code + }); + }); + + // map 转 array + const tree = Object.values(provinceMap).map(prov => { + const cities = Object.values(prov._cityMap).map(city => { + const districts = Object.values(city._distMap); + return { cityCode: city.cityCode, cityName: city.cityName, districts }; + }); + return { provinceCode: prov.provinceCode, provinceName: prov.provinceName, cities }; + }); + + await think.cache(HOSPITAL_TREE_CACHE_KEY, tree, { timeout: 24 * 60 * 60 * 1000 }); + return tree; + } + + /** + * 清除医院树缓存(增删改后调用) + */ + async clearTreeCache() { + await think.cache(HOSPITAL_TREE_CACHE_KEY, null); + } + + /** + * 获取展示中的医院列表(兼容旧版小程序/H5) */ async getShowList(keyword = '') { const where = { is_show: 1, is_deleted: 0 }; diff --git a/view/admin/hospital_index.html b/view/admin/hospital_index.html index 4c25e73..9327ee3 100644 --- a/view/admin/hospital_index.html +++ b/view/admin/hospital_index.html @@ -18,6 +18,9 @@
+ 搜索 重置
@@ -37,13 +40,19 @@ - - + + + + + - +