diff --git a/handler/resource.js b/handler/resource.js index 9c366fb..2debc81 100644 --- a/handler/resource.js +++ b/handler/resource.js @@ -1,6 +1,7 @@ import ResponseUtil from "../utils/responseUtil.js"; import services from "../resource/services.js"; import agreement from "../resource/agreement.js"; +import hospitalContact from "../resource/hospital_contact.js"; class HandlerResource { constructor() { @@ -23,6 +24,15 @@ class HandlerResource { return ResponseUtil.internalError(ctx, err.message); } } + + // 获取医院联系电话 + async getHospitalContact(ctx) { + try { + return ResponseUtil.success(ctx, { hospitalContact }, "查询成功"); + } catch (err) { + return ResponseUtil.internalError(ctx, err.message); + } + } } export { HandlerResource }; diff --git a/models/schema/employee.js b/models/schema/employee.js new file mode 100644 index 0000000..8d55130 --- /dev/null +++ b/models/schema/employee.js @@ -0,0 +1,190 @@ +"use strict"; + +import mongoose from "mongoose"; + +/** + * Employee Schema + * 员工/人员信息,支持陪诊员、护士、医生、管理员等角色 + */ +const EmployeeSchema = mongoose.Schema( + { + // 基础信息 + name: { type: String, required: true, comment: "姓名" }, + employeeNo: { type: String, default: "", unique: true, sparse: true, comment: "工号" }, + + // 登录账号(关联用户体系) + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: "user", + index: true, + comment: "关联用户ID", + }, + + // 所属机构 + orgId: { + type: mongoose.Schema.Types.ObjectId, + ref: "organization", + index: true, + comment: "所属机构ID", + }, + + // 角色/岗位 + role: { + type: String, + enum: ["attendant", "nurse", "doctor", "admin", "operator", "other"], + default: "attendant", + comment: "角色:陪诊员、护士、医生、管理员、运营、其他", + }, + + // 个人信息 + profile: { + sex: { type: String, enum: ["male", "female", "other", ""], default: "", comment: "性别" }, + birthday: { type: Date, comment: "出生日期" }, + idNumber: { type: String, default: "", comment: "身份证号" }, + phone: { type: String, default: "", comment: "手机号" }, + email: { type: String, default: "", comment: "邮箱" }, + avatar: { type: String, default: "", comment: "头像URL" }, + }, + + // 工作信息 + work: { + department: { type: String, default: "", comment: "所属科室/部门" }, + title: { type: String, default: "", comment: "职称/头衔" }, + entryDate: { type: Date, comment: "入职日期" }, + workYears: { type: Number, default: 0, comment: "工作年限" }, + qualification: { type: String, default: "", comment: "资质证书编号" }, + }, + + // 陪诊员特有信息 + attendantInfo: { + serviceAreas: { type: [String], default: [], comment: "服务区域/城市列表" }, + specialties: { type: [String], default: [], comment: "擅长科室/领域" }, + languages: { type: [String], default: ["普通话"], comment: "掌握语言" }, + rating: { type: Number, default: 5.0, min: 1, max: 5, comment: "评分(1-5)" }, + orderCount: { type: Number, default: 0, comment: "接单数量" }, + isOnline: { type: Boolean, default: false, comment: "是否在线" }, + }, + + // 紧急联系人 + emergencyContact: { + name: { type: String, default: "", comment: "紧急联系人姓名" }, + phone: { type: String, default: "", comment: "紧急联系人电话" }, + relation: { type: String, default: "", comment: "关系" }, + }, + + // 状态 + status: { + type: String, + enum: ["active", "inactive", "resigned", "suspended"], + default: "active", + comment: "状态:在职、停用、离职、暂停", + }, + + // 简介/备注 + introduction: { type: String, default: "", comment: "个人简介" }, + remark: { type: String, default: "", comment: "备注" }, + + // 元数据 + meta: { + createtime: { type: Date, default: Date.now, comment: "创建时间" }, + updatetime: { type: Date, default: Date.now, comment: "更新时间" }, + }, + }, + { + minimize: false, + strict: false, + collection: "employee", + timestamps: false, + } +); + +// ==================== 静态方法 ==================== + +/** + * 根据机构ID查询员工列表 + */ +EmployeeSchema.statics.findByOrg = async function (orgId, options = {}) { + const { page = 1, pageSize = 20, role, status } = options; + const filter = { orgId }; + if (role) filter.role = role; + if (status) filter.status = status; + const skip = (page - 1) * pageSize; + return await this.find(filter) + .skip(skip) + .limit(pageSize) + .exec(); +}; + +/** + * 根据角色查询员工 + */ +EmployeeSchema.statics.findByRole = async function (role, options = {}) { + const { page = 1, pageSize = 20, status = "active" } = options; + const filter = { role }; + if (status) filter.status = status; + const skip = (page - 1) * pageSize; + return await this.find(filter) + .skip(skip) + .limit(pageSize) + .exec(); +}; + +/** + * 根据用户ID查找员工 + */ +EmployeeSchema.statics.findByUserId = async function (userId) { + return await this.findOne({ userId }).exec(); +}; + +/** + * 根据ID查找员工 + */ +EmployeeSchema.statics.findByEmployeeId = async function (id) { + return await this.findById(id).exec(); +}; + +/** + * 创建员工 + */ +EmployeeSchema.statics.createEmployee = async function (data) { + data.meta = { createtime: Date.now(), updatetime: Date.now() }; + const doc = new this(data); + return await doc.save(); +}; + +/** + * 更新员工 + */ +EmployeeSchema.statics.updateEmployee = async function (id, update) { + update["meta.updatetime"] = Date.now(); + return await this.findByIdAndUpdate(id, { $set: update }, { new: true }).exec(); +}; + +/** + * 查询在线陪诊员列表 + */ +EmployeeSchema.statics.findOnlineAttendants = async function (options = {}) { + const { page = 1, pageSize = 20 } = options; + const skip = (page - 1) * pageSize; + return await this.find({ + role: "attendant", + status: "active", + "attendantInfo.isOnline": true, + }) + .sort({ "attendantInfo.rating": -1 }) + .skip(skip) + .limit(pageSize) + .exec(); +}; + +// ==================== 索引定义 ==================== + +EmployeeSchema.index({ name: 1 }); +EmployeeSchema.index({ employeeNo: 1 }); +EmployeeSchema.index({ userId: 1 }); +EmployeeSchema.index({ orgId: 1, role: 1, status: 1 }); +EmployeeSchema.index({ role: 1, status: 1 }); +EmployeeSchema.index({ "profile.phone": 1 }); +EmployeeSchema.index({ status: 1 }); + +export { EmployeeSchema }; diff --git a/models/schema/org.js b/models/schema/org.js index 745aa77..bfe29ad 100644 --- a/models/schema/org.js +++ b/models/schema/org.js @@ -3,88 +3,68 @@ import mongoose from "mongoose"; /** - * 医院Schema定义 - * - * 包含5个主要分类块:基本信息、位置信息、联系方式、服务信息和元数据。 - * 用于记录和管理医院信息,支持陪诊服务的医院选择功能。 + * Organization Schema + * 组织/机构基础信息,支持医院、诊所、体检中心等多种机构类型 */ -const HospitalSchema = mongoose.Schema( +const OrganizationSchema = mongoose.Schema( { - // 基本信息 - 医院的核心标识信息 - basic: { - name: { type: String, required: true, comment: "医院名称" }, - shortName: { type: String, default: "", comment: "医院简称" }, - pinyin: { type: String, default: "", comment: "医院名称拼音,用于搜索" }, - pinyinFL: { type: String, default: "", comment: "医院名称拼音首字母,用于搜索" }, - level: { - type: String, - enum: ["tertiary", "secondary", "primary", "other"], - default: "other", - comment: "医院等级:三级、二级、一级、其他", - }, - type: { - type: String, - enum: ["general", "specialized", "traditional_chinese", "integrated", "other"], - default: "general", - comment: "医院类型:综合医院、专科医院、中医医院、中西医结合医院、其他", - }, - description: { type: String, default: "", comment: "医院简介" }, - logo: { type: String, default: "", comment: "医院Logo地址" }, + // 基础信息 + name: { type: String, required: true, index: true, comment: "机构名称" }, + shortName: { type: String, default: "", comment: "机构简称" }, + code: { type: String, default: "", unique: true, sparse: true, comment: "机构编码" }, + + // 机构类型 + type: { + type: String, + enum: ["hospital", "clinic", "checkup_center", "other"], + default: "hospital", + comment: "机构类型:医院、诊所、体检中心、其他", }, - // 位置信息 - 医院的地理位置 - location: { + // 等级/性质 + level: { + type: String, + enum: ["三级甲等", "三级乙等", "二级甲等", "二级乙等", "一级", "未定级", ""], + default: "", + comment: "医院等级", + }, + nature: { + type: String, + enum: ["public", "private", "joint", "other", ""], + default: "", + comment: "机构性质:公立、私立、合资、其他", + }, + + // 联系方式 + contact: { + phone: { type: String, default: "", comment: "联系电话" }, + email: { type: String, default: "", comment: "联系邮箱" }, + website: { type: String, default: "", comment: "官方网站" }, + }, + + // 地址信息 + address: { province: { type: String, default: "", comment: "省份" }, city: { type: String, default: "", comment: "城市" }, district: { type: String, default: "", comment: "区县" }, - address: { type: String, default: "", comment: "详细地址" }, + detail: { type: String, default: "", comment: "详细地址" }, longitude: { type: Number, default: 0, comment: "经度" }, latitude: { type: Number, default: 0, comment: "纬度" }, }, - // 联系方式 - 医院的联系信息 - contact: { - phone: { type: String, default: "", comment: "联系电话" }, - emergencyPhone: { type: String, default: "", comment: "急诊电话" }, - website: { type: String, default: "", comment: "官方网站" }, - email: { type: String, default: "", comment: "电子邮箱" }, + // 状态 + status: { + type: String, + enum: ["active", "inactive", "suspended"], + default: "active", + comment: "机构状态:正常、停用、暂停", }, - // 服务信息 - 医院提供的服务 - service: { - features: [{ type: String, comment: "特色服务" }], - isEnabled: { type: Boolean, default: true, comment: "是否启用" }, - sortOrder: { type: Number, default: 0, comment: "排序优先级" }, - }, + // 简介/备注 + description: { type: String, default: "", comment: "机构简介" }, + remark: { type: String, default: "", comment: "备注" }, - // 科室信息 - 医院科室详细信息 - departments: [ - { - name: { type: String, required: true, comment: "科室名称" }, - pinyin: { type: String, default: "", comment: "科室名称拼音" }, - pinyinFL: { type: String, default: "", comment: "科室名称拼音首字母" }, - location: { type: String, default: "", comment: "科室位置(几号楼,几层)" }, - phone: { type: String, default: "", comment: "科室联系电话" }, - description: { type: String, default: "", comment: "科室简介" }, - doctors: [ - { - name: { type: String, required: true, comment: "医生姓名" }, - title: { - type: String, - enum: ["resident", "attending", "deputy_chief", "chief", "other"], - default: "attending", - comment: "医生职称:住院医师、主治医师、副主任医师、主任医师、其他", - }, - specialty: { type: String, default: "", comment: "专业擅长" }, - avatar: { type: String, default: "", comment: "医生头像" }, - }, - ], - isEnabled: { type: Boolean, default: true, comment: "是否启用" }, - sortOrder: { type: Number, default: 0, comment: "排序优先级" }, - }, - ], - - // 元数据 - 系统管理信息 + // 元数据 meta: { createtime: { type: Date, default: Date.now, comment: "创建时间" }, updatetime: { type: Date, default: Date.now, comment: "更新时间" }, @@ -93,154 +73,67 @@ const HospitalSchema = mongoose.Schema( { minimize: false, strict: false, - collection: "hospital", + collection: "organization", timestamps: false, } ); +// ==================== 静态方法 ==================== + /** - * 根据名称查找医院 - * - * @param {String} name - 医院名称(支持模糊搜索) - * @param {Object} options - 查询选项 { page, pageSize } - * @param {Function} cb - 可选的回调函数 - * @returns {Promise} 医院列表 + * 根据名称模糊查询机构 */ -HospitalSchema.statics.findByName = async function (name, options = {}, cb) { +OrganizationSchema.statics.findByName = async function (name, options = {}) { const { page = 1, pageSize = 20 } = options; - const filter = { - $or: [ - { "basic.name": new RegExp(name, "i") }, - { "basic.shortName": new RegExp(name, "i") }, - { "basic.pinyin": new RegExp(name.toLowerCase(), "i") }, - { "basic.pinyinFL": new RegExp(name.toUpperCase(), "i") }, - ], - "service.isEnabled": true, - }; const skip = (page - 1) * pageSize; - return await this.find(filter) - .sort({ "service.sortOrder": 1, "basic.name": 1 }) + return await this.find({ name: { $regex: name, $options: "i" } }) .skip(skip) .limit(pageSize) - .exec(cb); + .exec(); }; /** - * 根据城市查找医院 - * - * @param {String} city - 城市名称 - * @param {Object} options - 查询选项 { page, pageSize, level, type } - * @param {Function} cb - 可选的回调函数 - * @returns {Promise} 医院列表 + * 根据城市查询机构列表 */ -HospitalSchema.statics.findByCity = async function (city, options = {}, cb) { - const { page = 1, pageSize = 20, level, type } = options; - const filter = { - "location.city": city, - "service.isEnabled": true, - }; - if (level) { - filter["basic.level"] = level; - } - if (type) { - filter["basic.type"] = type; - } +OrganizationSchema.statics.findByCity = async function (city, options = {}) { + const { page = 1, pageSize = 20 } = options; const skip = (page - 1) * pageSize; - return await this.find(filter) - .sort({ "service.sortOrder": 1, "basic.name": 1 }) + return await this.find({ "address.city": city }) .skip(skip) .limit(pageSize) - .exec(cb); + .exec(); }; /** - * 获取所有启用的医院列表(用于选择器) - * - * @param {Object} options - 查询选项 { city, level, type } - * @param {Function} cb - 可选的回调函数 - * @returns {Promise} 医院列表(仅包含名称和ID) + * 根据ID查找机构 */ -HospitalSchema.statics.getHospitalSelector = async function (options = {}, cb) { - const { city, level, type } = options; - const filter = { "service.isEnabled": true }; - if (city) { - filter["location.city"] = city; - } - if (level) { - filter["basic.level"] = level; - } - if (type) { - filter["basic.type"] = type; - } - return await this.find(filter, { "basic.name": 1 }) - .sort({ "service.sortOrder": 1, "basic.name": 1 }) - .exec(cb); +OrganizationSchema.statics.findByOrgId = async function (id) { + return await this.findById(id).exec(); }; /** - * 创建医院 - * - * @param {Object} hospital - 医院对象 - * @param {Function} cb - 可选的回调函数 - * @returns {Promise} 创建的医院 + * 创建机构 */ -HospitalSchema.statics.createHospital = async function (hospital, cb) { - hospital.meta = { - createtime: Date.now(), - updatetime: Date.now(), - }; - const newHospital = new this(hospital); - return await newHospital.save(cb); +OrganizationSchema.statics.createOrg = async function (data) { + data.meta = { createtime: Date.now(), updatetime: Date.now() }; + const doc = new this(data); + return await doc.save(); }; /** - * 更新医院信息 - * - * @param {ObjectId} id - 医院ID - * @param {Object} update - 要更新的字段 - * @param {Function} cb - 可选的回调函数 - * @returns {Promise} 更新后的医院 + * 更新机构 */ -HospitalSchema.statics.updateHospital = async function (id, update, cb) { - try { - update["meta.updatetime"] = Date.now(); - return await this.findByIdAndUpdate(id, { $set: update }, { new: true }, cb); - } catch (error) { - return null; - } +OrganizationSchema.statics.updateOrg = async function (id, update) { + update["meta.updatetime"] = Date.now(); + return await this.findByIdAndUpdate(id, { $set: update }, { new: true }).exec(); }; -/** - * 启用/禁用医院 - * - * @param {ObjectId} id - 医院ID - * @param {Boolean} isEnabled - 是否启用 - * @param {Function} cb - 可选的回调函数 - * @returns {Promise} 更新后的医院 - */ -HospitalSchema.statics.setHospitalStatus = async function (id, isEnabled, cb) { - try { - return await this.findByIdAndUpdate( - id, - { - "service.isEnabled": isEnabled, - "meta.updatetime": Date.now(), - }, - { new: true }, - cb - ); - } catch (error) { - return null; - } -}; +// ==================== 索引定义 ==================== -// 医院名称索引 -HospitalSchema.index({ "basic.name": 1 }); +OrganizationSchema.index({ name: 1 }); +OrganizationSchema.index({ code: 1 }); +OrganizationSchema.index({ type: 1, status: 1 }); +OrganizationSchema.index({ "address.city": 1, "address.district": 1 }); +OrganizationSchema.index({ status: 1 }); -// 城市和等级索引 -HospitalSchema.index({ "location.city": 1, "basic.level": 1 }); - -// 启用状态索引 -HospitalSchema.index({ "service.isEnabled": 1 }); - -export { HospitalSchema }; \ No newline at end of file +export { OrganizationSchema }; diff --git a/resource/hospital_contact.js b/resource/hospital_contact.js new file mode 100644 index 0000000..606cec7 --- /dev/null +++ b/resource/hospital_contact.js @@ -0,0 +1,35 @@ +const hospitalContact = [ + { id: 1, name: '北京协和医院', phone: '69151188' }, + { id: 2, name: '北京安定医院', phone: '86430066' }, + { id: 3, name: '中国人民解放军总医院(301医院)', phone: '68182215' }, + { id: 4, name: '中国人民解放军总医院(301医院)总机', phone: '66939343' }, + { id: 5, name: '北京大学人民医院', phone: '88326666' }, + { id: 6, name: '中日友好医院', phone: '84205566' }, + { id: 7, name: '北京中医药大学东直门医院', phone: '84013276' }, + { id: 8, name: '首都医科大学附属北京友谊医院', phone: '63138585' }, + { id: 9, name: '首都医科大学附属北京妇产医院', phone: '52276699' }, + { id: 10, name: '北京积水潭医院', phone: '58516688' }, + { id: 11, name: '北京大学肿瘤医院(北京肿瘤)', phone: '59612345' }, + { id: 12, name: '北京大学第三医院(北医三院)', phone: '82266799' }, + { id: 13, name: '首都儿科研究所附属儿童医院', phone: '65612345' }, + { id: 14, name: '首都医科大学附属北京天坛医院', phone: '59978585' }, + { id: 15, name: '北京大学口腔医院', phone: '62179977' }, + { id: 16, name: '首都医科大学宣武医院', phone: '83922345' }, + { id: 17, name: '首都医科大学附属北京口腔医院', phone: '57099114' }, + { id: 18, name: '首都医科大学附属北京安贞医院', phone: '64412431' }, + { id: 19, name: '北京大学肿瘤医院(北肿)', phone: '88121122' }, + { id: 20, name: '中国医学科学院阜外医院', phone: '68314466' }, + { id: 21, name: '中国医学科学院肿瘤医院(东肿)', phone: '67781331' }, + { id: 22, name: '北京大学第一医院(北医一院)', phone: '83572211' }, + { id: 23, name: '中国中医科学院广安门医院', phone: '83123321' }, + { id: 24, name: '中国中医科学院广安门医院', phone: '83123311' }, + { id: 25, name: '首都医科大学附属北京同仁医院', phone: '58266699' }, + { id: 26, name: '中国中医科学院西苑医院', phone: '62835678' }, + { id: 27, name: '国家体育总局运动医学研究所(体育医院)', phone: '67116611-425' }, + { id: 28, name: '中国人民解放军空军特色医学中心(空军总医院)', phone: '68410099' }, + { id: 29, name: '北京大学第六医院', phone: '62723860' }, + { id: 30, name: '中国人民解放军海军特色医学中心', phone: '66958114' }, + { id: 31, name: '北京大学第三医院(北医三院)', phone: '82801936' }, +] + +export default hospitalContact diff --git a/routes/index.js b/routes/index.js index 0d43102..fa0127a 100644 --- a/routes/index.js +++ b/routes/index.js @@ -26,6 +26,7 @@ function registerRoutes(app) { router.get("/service", handlerResource.getServices.bind(handlerResource)); router.get("/agreement", handlerResource.getAgreement.bind(handlerResource)); + router.get("/hospital-contact", handlerResource.getHospitalContact.bind(handlerResource)); app.use(router.routes()); app.use(router.allowedMethods());