| @@ -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`; | |||
| @@ -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`; | |||
| @@ -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'], | |||
| // 小程序接口 | |||
| @@ -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] || '' | |||
| }; | |||
| } | |||
| }; | |||
| @@ -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 || '', | |||
| @@ -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() { | |||
| @@ -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 || []), | |||
| @@ -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 }; | |||
| @@ -18,6 +18,9 @@ | |||
| <div class="flex items-center justify-between"> | |||
| <div class="flex items-center gap-3"> | |||
| <el-input v-model="keyword" placeholder="搜索医院名称" style="width:240px;" clearable @keyup.enter="onSearch"></el-input> | |||
| <el-cascader v-model="regionFilter" :options="regionTree" | |||
| :props="{ value: 'code', label: 'name', children: 'children', checkStrictly: true }" | |||
| placeholder="按地区筛选" clearable filterable :filter-method="filterRegionNode" style="width:240px;"></el-cascader> | |||
| <el-button type="primary" @click="onSearch">搜索</el-button> | |||
| <el-button @click="resetQuery">重置</el-button> | |||
| </div> | |||
| @@ -37,13 +40,19 @@ | |||
| <span class="drag-handle">⠿</span> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column prop="name" label="医院名称"></el-table-column> | |||
| <el-table-column label="是否展示" width="120" align="center"> | |||
| <el-table-column prop="name" label="医院名称" min-width="200"></el-table-column> | |||
| <el-table-column label="所在地区" min-width="180"> | |||
| <template #default="{ row }"> | |||
| <span v-if="row.province_name">${ row.province_name } ${ row.city_name } ${ row.district_name }</span> | |||
| <span v-else style="color:#c0c4cc;">未设置</span> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column label="是否展示" width="100" align="center"> | |||
| <template #default="{ row }"> | |||
| <el-switch :model-value="row.is_show === 1" @change="(v) => handleToggle(row, v)" /> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column label="操作" width="180" align="center"> | |||
| <el-table-column label="操作" width="160" align="center"> | |||
| <template #default="{ row }"> | |||
| <el-button type="primary" link @click="showEdit(row)">编辑</el-button> | |||
| <el-button type="danger" link @click="handleDelete(row)">删除</el-button> | |||
| @@ -61,13 +70,18 @@ | |||
| <el-dialog v-model="dialogVisible" :title="dialogTitle" width="460px" destroy-on-close draggable :close-on-click-modal="false"> | |||
| <el-form :model="form" label-width="90px"> | |||
| <el-form-item label="医院名称" required> | |||
| <el-input v-model="form.name" placeholder="请输入医院名称" maxlength="100" /> | |||
| <el-input v-model="form.name" placeholder="请输入医院名称" maxlength="100"></el-input> | |||
| </el-form-item> | |||
| <el-form-item label="所在地区"> | |||
| <el-cascader v-model="form.regionCodes" :options="regionTree" | |||
| :props="{ value: 'code', label: 'name', children: 'children' }" | |||
| placeholder="请选择省/市/区" clearable filterable :filter-method="filterRegionNode" style="width:100%;"></el-cascader> | |||
| </el-form-item> | |||
| <el-form-item label="排序"> | |||
| <el-input-number v-model="form.sort" :min="0" :max="9999" /> | |||
| <el-input-number v-model="form.sort" :min="0" :max="9999"></el-input-number> | |||
| </el-form-item> | |||
| <el-form-item label="是否展示"> | |||
| <el-switch v-model="form.is_show" :active-value="1" :inactive-value="0" /> | |||
| <el-switch v-model="form.is_show" :active-value="1" :inactive-value="0"></el-switch> | |||
| </el-form-item> | |||
| </el-form> | |||
| <template #footer> | |||
| @@ -76,10 +90,16 @@ | |||
| </template> | |||
| </el-dialog> | |||
| <!-- 批量导入弹窗 --> | |||
| <el-dialog v-model="showImport" title="批量导入医院" width="500px" destroy-on-close :close-on-click-modal="false"> | |||
| <p style="font-size:13px;color:var(--el-color-primary);margin-bottom:12px;">每行输入一个医院名称,重复的将自动跳过。</p> | |||
| <el-input v-model="importText" type="textarea" :rows="10" :placeholder="importPlaceholder"></el-input> | |||
| <!-- Excel 导入弹窗 --> | |||
| <el-dialog v-model="showImport" title="导入医院" width="500px" destroy-on-close :close-on-click-modal="false"> | |||
| <p style="font-size:13px;color:var(--el-color-primary);margin-bottom:12px;"> | |||
| 请上传 Excel 文件(.xlsx),表头依次为:关联机构名称、省份、城市、区县。重复医院将自动跳过。 | |||
| </p> | |||
| <el-upload drag :auto-upload="false" :limit="1" :on-change="onFileChange" :on-remove="onFileRemove" | |||
| accept=".xlsx" :file-list="importFileList"> | |||
| <el-icon class="el-icon--upload"><Upload /></el-icon> | |||
| <div class="el-upload__text">将 Excel 文件拖到此处,或<em>点击上传</em></div> | |||
| </el-upload> | |||
| <template #footer> | |||
| <div style="text-align:right;"> | |||
| <el-button @click="showImport = false">取消</el-button> | |||
| @@ -105,22 +125,40 @@ const app = createApp({ | |||
| const total = ref(0); | |||
| const page = ref(1); | |||
| const pageSize = ref(10); | |||
| const regionTree = ref([]); | |||
| const regionFilter = ref([]); | |||
| const dialogVisible = ref(false); | |||
| const dialogTitle = ref('新增医院'); | |||
| const saving = ref(false); | |||
| const form = reactive({ id: null, name: '', sort: 0, is_show: 1 }); | |||
| const form = reactive({ id: null, name: '', regionCodes: [], sort: 0, is_show: 1 }); | |||
| const showImport = ref(false); | |||
| const importText = ref(''); | |||
| const importing = ref(false); | |||
| const importPlaceholder = '北京协和医院\n复旦大学附属肿瘤医院\n中山大学肿瘤防治中心'; | |||
| const importFileList = ref([]); | |||
| let importFile = null; | |||
| let sortableInstance = null; | |||
| async function loadRegionTree() { | |||
| const res = await fetch('/common/regions').then(r => r.json()); | |||
| if (res.code === 0) regionTree.value = res.data || []; | |||
| } | |||
| function filterRegionNode(node, keyword) { | |||
| const kw = (keyword || '').trim().toLowerCase(); | |||
| if (!kw) return true; | |||
| const text = String(node.text || '').toLowerCase(); | |||
| const pathText = (node.pathLabels || []).join('').toLowerCase(); | |||
| return text.includes(kw) || pathText.includes(kw); | |||
| } | |||
| async function loadList() { | |||
| loading.value = true; | |||
| try { | |||
| const params = new URLSearchParams({ | |||
| keyword: keyword.value, page: page.value, pageSize: pageSize.value | |||
| }); | |||
| if (regionFilter.value && regionFilter.value.length >= 1) params.set('province_code', regionFilter.value[0]); | |||
| if (regionFilter.value && regionFilter.value.length >= 2) params.set('city_code', regionFilter.value[1]); | |||
| if (regionFilter.value && regionFilter.value.length >= 3) params.set('district_code', regionFilter.value[2]); | |||
| const res = await fetch('/admin/hospital/list?' + params).then(r => r.json()); | |||
| if (res.code === 0) { | |||
| list.value = res.data.data || []; | |||
| @@ -158,7 +196,7 @@ const app = createApp({ | |||
| function showAdd() { | |||
| dialogTitle.value = '新增医院'; | |||
| form.id = null; form.name = ''; form.sort = 0; form.is_show = 1; | |||
| form.id = null; form.name = ''; form.regionCodes = []; form.sort = 0; form.is_show = 1; | |||
| dialogVisible.value = true; | |||
| } | |||
| @@ -169,6 +207,7 @@ const app = createApp({ | |||
| function resetQuery() { | |||
| keyword.value = ''; | |||
| regionFilter.value = []; | |||
| page.value = 1; | |||
| loadList(); | |||
| } | |||
| @@ -180,6 +219,7 @@ const app = createApp({ | |||
| function showEdit(row) { | |||
| dialogTitle.value = '编辑医院'; | |||
| form.id = row.id; form.name = row.name; form.sort = row.sort; form.is_show = row.is_show; | |||
| form.regionCodes = [row.province_code, row.city_code, row.district_code].filter(Boolean); | |||
| dialogVisible.value = true; | |||
| } | |||
| @@ -188,10 +228,15 @@ const app = createApp({ | |||
| saving.value = true; | |||
| try { | |||
| const url = form.id ? '/admin/hospital/edit' : '/admin/hospital/add'; | |||
| const codes = form.regionCodes || []; | |||
| const payload = { | |||
| id: form.id, name: form.name, sort: form.sort, is_show: form.is_show, | |||
| province_code: codes[0] || '', city_code: codes[1] || '', district_code: codes[2] || '' | |||
| }; | |||
| const res = await fetch(url, { | |||
| method: 'POST', | |||
| headers: { 'Content-Type': 'application/json' }, | |||
| body: JSON.stringify(form) | |||
| body: JSON.stringify(payload) | |||
| }).then(r => r.json()); | |||
| if (res.code === 0) { | |||
| ElementPlus.ElMessage.success('保存成功'); | |||
| @@ -226,19 +271,30 @@ const app = createApp({ | |||
| else { ElementPlus.ElMessage.error(res.msg || '操作失败'); } | |||
| } | |||
| function onFileChange(file) { | |||
| importFile = file.raw; | |||
| importFileList.value = [file]; | |||
| } | |||
| function onFileRemove() { | |||
| importFile = null; | |||
| importFileList.value = []; | |||
| } | |||
| async function handleImport() { | |||
| if (!importText.value.trim()) { ElementPlus.ElMessage.warning('请输入医院名称'); return; } | |||
| if (!importFile) { ElementPlus.ElMessage.warning('请先选择 Excel 文件'); return; } | |||
| importing.value = true; | |||
| try { | |||
| const res = await fetch('/admin/hospital/import', { | |||
| method: 'POST', | |||
| headers: { 'Content-Type': 'application/json' }, | |||
| body: JSON.stringify({ text: importText.value }) | |||
| }).then(r => r.json()); | |||
| const fd = new FormData(); | |||
| fd.append('file', importFile); | |||
| const res = await fetch('/admin/hospital/import', { method: 'POST', body: fd }).then(r => r.json()); | |||
| if (res.code === 0) { | |||
| ElementPlus.ElMessage.success(`导入成功 ${res.data.count} 条,跳过 ${res.data.skipped} 条`); | |||
| let msg = `导入成功 ${res.data.count} 条,跳过 ${res.data.skipped} 条`; | |||
| if (res.data.unmatchedCount > 0) msg += `,${res.data.unmatchedCount} 条地区未匹配`; | |||
| ElementPlus.ElMessage.success(msg); | |||
| showImport.value = false; | |||
| importText.value = ''; | |||
| importFile = null; | |||
| importFileList.value = []; | |||
| loadList(); | |||
| } else { | |||
| ElementPlus.ElMessage.error(res.msg || '导入失败'); | |||
| @@ -246,9 +302,9 @@ const app = createApp({ | |||
| } finally { importing.value = false; } | |||
| } | |||
| onMounted(() => loadList()); | |||
| onMounted(() => { loadList(); loadRegionTree(); }); | |||
| return { loading, list, keyword, total, page, pageSize, dialogVisible, dialogTitle, saving, form, showImport, importText, importing, importPlaceholder, loadList, onSearch, resetQuery, onSizeChange, showAdd, showEdit, handleSave, handleDelete, handleToggle, handleImport, Plus, Upload }; | |||
| return { loading, list, keyword, total, page, pageSize, regionTree, regionFilter, dialogVisible, dialogTitle, saving, form, showImport, importing, importFileList, loadList, onSearch, resetQuery, onSizeChange, showAdd, showEdit, handleSave, handleDelete, handleToggle, handleImport, onFileChange, onFileRemove, filterRegionNode, Plus, Upload }; | |||
| } | |||
| }); | |||
| @@ -63,6 +63,10 @@ | |||
| </div> | |||
| <div class="info-item"><span class="label">提交时间:</span><span class="value">${ patient.create_time }</span></div> | |||
| <div class="info-item"><span class="label">医院名称:</span><span class="value">${ patient.hospital || '—' }</span></div> | |||
| <div class="info-item"> | |||
| <span class="label">医院所在地:</span> | |||
| <span class="value">${ hospitalRegionText || '—' }</span> | |||
| </div> | |||
| <div class="info-item"><span class="label">紧急联系人:</span><span class="value">${ patient.emergency_contact || '—' }</span></div> | |||
| <div class="info-item"><span class="label">紧急联系电话:</span><span class="value">${ patient.emergency_phone || '—' }</span></div> | |||
| <div class="info-item" v-if="patient.income_amount"><span class="label">年可支配收入:</span><span class="value">${ patient.income_amount } 元</span></div> | |||
| @@ -226,7 +230,10 @@ var app = createApp({ | |||
| id: '', patient_no: '', name: '', phone: '', id_card: '', gender: '', birth_date: '', | |||
| province_code: '', city_code: '', district_code: '', | |||
| province_name: '', city_name: '', district_name: '', | |||
| address: '', hospital: '', tag: '', documents: [], | |||
| address: '', hospital: '', | |||
| hospital_province_code: '', hospital_city_code: '', hospital_district_code: '', | |||
| hospital_province_name: '', hospital_city_name: '', hospital_district_name: '', | |||
| tag: '', documents: [], | |||
| sample_types: [], wax_return: 0, | |||
| return_name: '', return_phone: '', | |||
| return_province_code: '', return_city_code: '', return_district_code: '', | |||
| @@ -282,6 +289,14 @@ var app = createApp({ | |||
| return /\.(png|jpg|jpeg|gif|bmp|webp|svg)$/.test(lower); | |||
| } | |||
| var hospitalRegionText = Vue.computed(function() { | |||
| return [ | |||
| patient.hospital_province_name, | |||
| patient.hospital_city_name, | |||
| patient.hospital_district_name | |||
| ].filter(Boolean).join(' '); | |||
| }); | |||
| var signDocs = Vue.computed(function() { | |||
| var docs = [ | |||
| { key: 'income', label: '个人可支配收入声明', url: patient.sign_income }, | |||
| @@ -362,7 +377,8 @@ var app = createApp({ | |||
| return { | |||
| loading, patient, audits, canAudit, | |||
| rejectVisible, rejectSaving, rejectReason, selectedReasons, commonReasons, | |||
| goBack, downloadSign, isImageUrl, signDocs, signImageList, authImageList, handleApprove, showRejectDialog, toggleReason, doReject | |||
| hospitalRegionText, goBack, downloadSign, isImageUrl, signDocs, signImageList, | |||
| authImageList, handleApprove, showRejectDialog, toggleReason, doReject | |||
| }; | |||
| } | |||
| }); | |||
| @@ -164,6 +164,18 @@ | |||
| <el-input v-model="addForm.address" placeholder="请输入详细地址(街道门牌号等)" /> | |||
| </el-form-item> | |||
| </el-col> | |||
| <el-col :span="12"> | |||
| <el-form-item label="医院名称"> | |||
| <el-input v-model="addForm.hospital" placeholder="请输入就诊医院名称" /> | |||
| </el-form-item> | |||
| </el-col> | |||
| <el-col :span="12"> | |||
| <el-form-item label="医院地区"> | |||
| <el-cascader v-model="addForm.hospitalRegionCodes" :options="regionTree" | |||
| :props="{ value: 'code', label: 'name', children: 'children' }" | |||
| placeholder="请选择医院省/市/区" clearable style="width:100%;" /> | |||
| </el-form-item> | |||
| </el-col> | |||
| <el-col :span="12"> | |||
| <el-form-item label="紧急联系人"> | |||
| <el-input v-model="addForm.emergency_contact" placeholder="联系人姓名" /> | |||
| @@ -322,7 +334,8 @@ const app = createApp({ | |||
| const editingId = ref(null); | |||
| const addForm = reactive({ | |||
| name: '', phone: '', id_card: '', gender: '', birth_date: '', | |||
| regionCodes: [], address: '', emergency_contact: '', emergency_phone: '', | |||
| regionCodes: [], address: '', hospital: '', hospitalRegionCodes: [], | |||
| emergency_contact: '', emergency_phone: '', | |||
| tag: '', documents: [], sign_income: '', sign_privacy: '', sign_promise: '', | |||
| sign_privacy_jhr: '', income_amount: '', guardian_name: '', guardian_id_card: '', guardian_relation: '' | |||
| }); | |||
| @@ -480,7 +493,8 @@ const app = createApp({ | |||
| editingId.value = null; | |||
| Object.assign(addForm, { | |||
| name: '', phone: '', id_card: '', gender: '', birth_date: '', | |||
| regionCodes: [], address: '', emergency_contact: '', emergency_phone: '', | |||
| regionCodes: [], address: '', hospital: '', hospitalRegionCodes: [], | |||
| emergency_contact: '', emergency_phone: '', | |||
| tag: '', documents: [], sign_income: '', sign_privacy: '', sign_promise: '', | |||
| sign_privacy_jhr: '', income_amount: '', guardian_name: '', guardian_id_card: '', guardian_relation: '' | |||
| }); | |||
| @@ -516,7 +530,8 @@ const app = createApp({ | |||
| // 先重置表单 | |||
| Object.assign(addForm, { | |||
| name: '', phone: '', id_card: '', gender: '', birth_date: '', | |||
| regionCodes: [], address: '', emergency_contact: '', emergency_phone: '', | |||
| regionCodes: [], address: '', hospital: '', hospitalRegionCodes: [], | |||
| emergency_contact: '', emergency_phone: '', | |||
| tag: '', documents: [], sign_income: '', sign_privacy: '', sign_promise: '', | |||
| sign_privacy_jhr: '', income_amount: '', guardian_name: '', guardian_id_card: '', guardian_relation: '' | |||
| }); | |||
| @@ -531,6 +546,8 @@ const app = createApp({ | |||
| gender: p.gender || '', | |||
| birth_date: p.birth_date || '', | |||
| address: p.address || '', | |||
| hospital: p.hospital || '', | |||
| hospitalRegionCodes: [p.hospital_province_code, p.hospital_city_code, p.hospital_district_code].filter(Boolean), | |||
| emergency_contact: p.emergency_contact || '', | |||
| emergency_phone: p.emergency_phone || '', | |||
| tag: p.tag || '', | |||
| @@ -566,6 +583,7 @@ const app = createApp({ | |||
| addSaving.value = true; | |||
| try { | |||
| var url = editingId.value ? '/admin/patient/edit' : '/admin/patient/add'; | |||
| var hospitalRegionCodes = addForm.hospitalRegionCodes || []; | |||
| var body = { | |||
| name: addForm.name.trim(), | |||
| phone: addForm.phone, | |||
| @@ -576,6 +594,10 @@ const app = createApp({ | |||
| city_code: addForm.regionCodes[1], | |||
| district_code: addForm.regionCodes[2], | |||
| address: addForm.address.trim(), | |||
| hospital: addForm.hospital || '', | |||
| hospital_province_code: hospitalRegionCodes[0] || '', | |||
| hospital_city_code: hospitalRegionCodes[1] || '', | |||
| hospital_district_code: hospitalRegionCodes[2] || '', | |||
| emergency_contact: addForm.emergency_contact || '', | |||
| emergency_phone: addForm.emergency_phone || '', | |||
| tag: addForm.tag || '', | |||