diff --git a/sql/hospital.sql b/sql/hospital.sql new file mode 100644 index 0000000..b6f73b3 --- /dev/null +++ b/sql/hospital.sql @@ -0,0 +1,11 @@ +-- 医院列表 +CREATE TABLE `hospital` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL 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`) +) COMMENT='医院列表'; diff --git a/src/config/router.js b/src/config/router.js index 639a691..15d18e5 100644 --- a/src/config/router.js +++ b/src/config/router.js @@ -56,6 +56,7 @@ module.exports = [ ['/common/ocr/idcard', 'common/ocrIdcard', 'post'], ['/common/ocr/hmt', 'common/ocrHmt', 'post'], ['/common/sampleTypes', 'common/sampleTypes'], + ['/common/hospitals', 'common/hospitals'], ['/api/content', 'common/content'], // 小程序接口 @@ -97,6 +98,16 @@ module.exports = [ ['/admin/sample_type/sort', 'admin/sample_type/sort', 'post'], ['/admin/sample_type/setRequired', 'admin/sample_type/setRequired', 'post'], + // 医院管理 + ['/admin/hospital', 'admin/hospital/index'], + ['/admin/hospital/list', 'admin/hospital/list'], + ['/admin/hospital/add', 'admin/hospital/add', 'post'], + ['/admin/hospital/edit', 'admin/hospital/edit', 'post'], + ['/admin/hospital/delete', 'admin/hospital/delete', 'post'], + ['/admin/hospital/toggleShow', 'admin/hospital/toggleShow', 'post'], + ['/admin/hospital/sort', 'admin/hospital/sort', 'post'], + ['/admin/hospital/import', 'admin/hospital/import', 'post'], + // 内容管理 ['/admin/content', 'admin/content/index'], ['/admin/content/list', 'admin/content/list'], diff --git a/src/controller/admin/hospital.js b/src/controller/admin/hospital.js new file mode 100644 index 0000000..abdacf0 --- /dev/null +++ b/src/controller/admin/hospital.js @@ -0,0 +1,110 @@ +const Base = require('../base'); + +module.exports = class extends Base { + async indexAction() { + this.assign('currentPage', 'hospital'); + this.assign('pageTitle', '医院管理'); + this.assign('breadcrumb', [{ name: '医院管理' }]); + return this.display(); + } + + // 列表 + async listAction() { + const keyword = this.get('keyword') || ''; + const page = this.get('page') || 1; + const pageSize = this.get('pageSize') || 10; + const where = { is_deleted: 0 }; + if (keyword) where.name = ['like', `%${keyword}%`]; + const data = await this.model('hospital') + .where(where) + .order('sort DESC, id ASC') + .page(page, pageSize) + .countSelect(); + return this.json({ code: 0, data }); + } + + // 新增 + async addAction() { + const { name, 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('该医院已存在'); + await this.model('hospital').add({ + name: name.trim(), + sort: sort || 0, + is_show: is_show === undefined ? 1 : (is_show ? 1 : 0) + }); + await this.log('add', '医院管理', `新增医院「${name.trim()}」`); + return this.success(); + } + + // 编辑 + async editAction() { + const { id, name, 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('该医院已存在'); + await this.model('hospital').where({ id }).update({ + name: name.trim(), + sort: sort || 0, + is_show: is_show ? 1 : 0 + }); + await this.log('edit', '医院管理', `编辑医院(ID:${id})「${name.trim()}」`); + return this.success(); + } + + // 删除 + async deleteAction() { + const { id } = this.post(); + if (!id) return this.fail('参数错误'); + await this.model('hospital').where({ id }).update({ is_deleted: 1 }); + await this.log('delete', '医院管理', `删除医院(ID:${id})`); + return this.success(); + } + + // 切换展示状态 + async toggleShowAction() { + 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.log('edit', '医院管理', `${is_show ? '展示' : '隐藏'}医院(ID:${id})`); + return this.success(); + } + + // 排序 + async sortAction() { + const { ids } = this.post(); + if (!ids || !ids.length) return this.fail('参数错误'); + for (let i = 0; i < ids.length; i++) { + await this.model('hospital').where({ id: ids[i] }).update({ sort: ids.length - i }); + } + return this.success(); + } + + // 批量导入(粘贴文本,每行一个) + 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 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 }); + } + }); + + 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 }); + } +}; diff --git a/src/controller/common.js b/src/controller/common.js index 8fc3d9c..8c7d93e 100644 --- a/src/controller/common.js +++ b/src/controller/common.js @@ -164,6 +164,21 @@ module.exports = class extends think.Controller { } } + /** + * 获取医院列表(展示中的) + * GET /common/hospitals + */ + async hospitalsAction() { + try { + const keyword = this.get('keyword') || ''; + const list = await this.model('hospital').getShowList(keyword); + return this.json({ code: 0, data: list }); + } catch (error) { + think.logger.error('获取医院列表失败:', error); + return this.json({ code: 0, data: [] }); + } + } + /** * 获取省市区树形数据(一次返回) * GET /common/regions diff --git a/src/controller/mp.js b/src/controller/mp.js index e400354..5effa80 100644 --- a/src/controller/mp.js +++ b/src/controller/mp.js @@ -715,6 +715,18 @@ module.exports = class extends Base { if (patient.status !== 1) return this.json({ code: 1, msg: '请先通过审核' }); const hasSample = sample_types && sample_types.length > 0; + + // 邮箱格式与重复校验 + if (hasSample && report_email) { + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(report_email)) { + return this.json({ code: 1, msg: '邮箱格式不正确' }); + } + const dup = await this.model('patient') + .where({ report_email, is_deleted: 0, id: ['!=', user.patient_id] }) + .find(); + if (!think.isEmpty(dup)) return this.json({ code: 1, msg: '该邮箱已被其他患者使用' }); + } + await this.model('patient').where({ id: user.patient_id }).update({ sample_types: JSON.stringify(sample_types || []), wax_return: (hasSample && wax_return) ? 1 : 0, diff --git a/src/model/hospital.js b/src/model/hospital.js new file mode 100644 index 0000000..3e41380 --- /dev/null +++ b/src/model/hospital.js @@ -0,0 +1,14 @@ +module.exports = class extends think.Model { + get tableName() { + return 'hospital'; + } + + /** + * 获取展示中的医院列表(小程序用) + */ + async getShowList(keyword = '') { + const where = { is_show: 1, is_deleted: 0 }; + if (keyword) where.name = ['like', `%${keyword}%`]; + return this.where(where).order('sort DESC, id ASC').select(); + } +}; diff --git a/view/admin/common/_sidebar.html b/view/admin/common/_sidebar.html index c8cf03f..9151781 100644 --- a/view/admin/common/_sidebar.html +++ b/view/admin/common/_sidebar.html @@ -42,6 +42,16 @@ {% endif %} + {# 医院管理 #} + {% if isSuperAdmin or (userPermissions and 'tag' in userPermissions) %} + + {% endif %} + {# 内容管理 #} {% if isSuperAdmin or (userPermissions and 'content' in userPermissions) %}