| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813 |
- <template>
- <div class="page-container">
- <div class="page-header">
- <span class="breadcrumb-label">当前位置:</span>
- <span class="breadcrumb-path">院区信息管理</span>
- <span class="breadcrumb-separator">></span>
- <span class="breadcrumb-current">病房管理</span>
- </div>
- <!-- 查询栏 -->
- <div class="search-bar">
- <div class="search-item">
- <label>病房代码:</label>
- <input type="text" v-model="searchForm.code" placeholder="请输入病房代码" />
- </div>
- <div class="search-item">
- <label>所属科室:</label>
- <select v-model="searchForm.belongDept" @change="onDeptChange">
- <option value="">全部</option>
- <option v-for="(dept, index) in deptList" :key="index" :value="dept">{{ dept }}</option>
- </select>
- </div>
- <div class="search-item">
- <label>所属病区:</label>
- <select v-model="searchForm.belongWard">
- <option value="">全部</option>
- <option v-for="(ward, index) in wardList" :key="index" :value="ward">{{ ward }}</option>
- </select>
- </div>
- <div class="search-item">
- <label>是否启用:</label>
- <select v-model="searchForm.enabled">
- <option value="">全部</option>
- <option value="1">是</option>
- <option value="0">否</option>
- </select>
- </div>
- <button class="search-btn" @click="handleSearch">查询</button>
- <button class="reset-btn" @click="handleReset">重置</button>
- </div>
- <!-- 操作按钮 -->
- <div class="action-bar" v-if="isAdmin">
- <button class="code-set-btn" @click="openCodeSetting" :disabled="selectedRows.length === 0">
- 病房代码设置
- </button>
- </div>
- <!-- 数据列表 -->
- <div class="table-container">
- <div v-if="loading" class="loading-mask">
- <div class="loading-spinner"></div>
- <span>加载中...</span>
- </div>
- <table class="data-table" v-show="!loading">
- <thead>
- <tr>
- <th><input type="checkbox" :checked="selectAll" @change="handleSelectAll" /></th>
- <th>病房代码</th>
- <th>病房外部代码</th>
- <th>病房名称</th>
- <th>额定床位</th>
- <th>所属科室</th>
- <th>所属病区</th>
- <th>是否启用</th>
- <th>操作</th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="item in tableData" :key="item.id">
- <td><input type="checkbox" :checked="isSelected(item)" @change="toggleSelect(item)" /></td>
- <td>{{ item.code }}</td>
- <td>{{ item.outCode }}</td>
- <td>{{ item.name }}</td>
- <td>{{ item.bedCount }}</td>
- <td>{{ item.belongDept }}</td>
- <td>{{ item.belongWard }}</td>
- <td>{{ item.enabled === 1 ? '是' : '否' }}</td>
- <td>
- <button class="detail-btn" @click="showDetail(item)">详情</button>
- </td>
- </tr>
- <tr v-if="tableData.length === 0">
- <td colspan="9" class="empty-tip">暂无数据</td>
- </tr>
- </tbody>
- </table>
- </div>
- <!-- 分页 -->
- <div class="pagination" v-if="total > 0">
- <span class="pagination-info">共 {{ total }} 条记录</span>
- <div class="pagination-controls">
- <button class="page-btn" :disabled="currentPage === 1" @click="changePage(1)">首页</button>
- <button class="page-btn" :disabled="currentPage === 1" @click="changePage(currentPage - 1)">上一页</button>
- <span class="page-numbers">
- <button
- v-for="page in displayPages"
- :key="page"
- class="page-num"
- :class="{ active: page === currentPage }"
- @click="changePage(page)"
- >{{ page }}</button>
- </span>
- <button class="page-btn" :disabled="currentPage === totalPages" @click="changePage(currentPage + 1)">下一页</button>
- <button class="page-btn" :disabled="currentPage === totalPages" @click="changePage(totalPages)">末页</button>
- <span class="page-size-selector">
- <select v-model="pageSize" @change="handlePageSizeChange">
- <option :value="10">10条/页</option>
- <option :value="20">20条/页</option>
- <option :value="50">50条/页</option>
- <option :value="100">100条/页</option>
- </select>
- </span>
- <span class="page-jump">
- 跳至 <input type="number" v-model.number="jumpPage" min="1" :max="totalPages" @keyup.enter="handleJumpPage" /> 页
- </span>
- </div>
- </div>
- <!-- 详情弹窗 -->
- <div class="modal-overlay" v-if="detailVisible" @click.self="detailVisible = false">
- <div class="modal-content">
- <div class="modal-header">
- <span>病房详情</span>
- <button class="close-btn" @click="detailVisible = false">×</button>
- </div>
- <div class="modal-body">
- <div class="detail-row">
- <span class="label">病房代码:</span>
- <span class="value">{{ currentRoom.code }}</span>
- </div>
- <div class="detail-row">
- <span class="label">病房外部代码:</span>
- <span class="value">{{ currentRoom.outCode }}</span>
- </div>
- <div class="detail-row">
- <span class="label">病房名称:</span>
- <span class="value">{{ currentRoom.name }}</span>
- </div>
- <div class="detail-row">
- <span class="label">额定床位:</span>
- <span class="value">{{ currentRoom.bedCount }}</span>
- </div>
- <div class="detail-row">
- <span class="label">所属科室:</span>
- <span class="value">{{ currentRoom.belongDept }}</span>
- </div>
- <div class="detail-row">
- <span class="label">所属病区:</span>
- <span class="value">{{ currentRoom.belongWard }}</span>
- </div>
- <div class="detail-row">
- <span class="label">排序号:</span>
- <span class="value">{{ currentRoom.sort }}</span>
- </div>
- <div class="detail-row">
- <span class="label">是否启用:</span>
- <span class="value">{{ currentRoom.enabled === 1 ? '是' : '否' }}</span>
- </div>
- <div class="detail-row">
- <span class="label">备注:</span>
- <span class="value">{{ currentRoom.remark }}</span>
- </div>
- </div>
- </div>
- </div>
- <!-- 病房代码设置弹窗 -->
- <div class="modal-overlay" v-if="codeSettingVisible" @click.self="codeSettingVisible = false">
- <div class="modal-content code-setting-modal">
- <div class="modal-header">
- <span>病房代码设置</span>
- <button class="close-btn" @click="codeSettingVisible = false">×</button>
- </div>
- <div class="modal-body">
- <table class="code-setting-table">
- <thead>
- <tr>
- <th>病房名称</th>
- <th>所属病区</th>
- <th>当前代码</th>
- <th>新代码</th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="(item, index) in codeSettingList" :key="item.id">
- <td>{{ item.name }}</td>
- <td>{{ item.belongWard }}</td>
- <td>{{ item.code }}</td>
- <td>
- <input type="text" v-model="item.newCode" maxlength="4"
- placeholder="0001~9999" @blur="formatCodeInput(index)" />
- </td>
- </tr>
- </tbody>
- </table>
- <div class="code-tips">
- 提示:病房代码为0001~9999四位数字,输入1或101将自动补0变为0001与0101,同病区下代码不可重复
- </div>
- </div>
- <div class="modal-footer">
- <button class="cancel-btn" @click="codeSettingVisible = false">取消</button>
- <button class="confirm-btn" @click="saveCodeSetting">保存</button>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, computed, onMounted, watch } from 'vue'
- import request from '@/utils/request'
- // 判断是否为管理员
- const isAdmin = computed(() => {
- const userType = localStorage.getItem('userType')
- return userType === 'admin'
- })
- // 查询条件
- const searchForm = reactive({
- code: '',
- belongDept: '',
- belongWard: '',
- enabled: ''
- })
- // 科室列表和病区列表
- const deptList = ref([])
- const wardList = ref([])
- // 表格数据
- const tableData = ref([])
- // 加载状态
- const loading = ref(false)
- // 分页相关
- const currentPage = ref(1)
- const pageSize = ref(10)
- const total = ref(0)
- const jumpPage = ref(1)
- // 计算总页数
- const totalPages = computed(() => Math.ceil(total.value / pageSize.value) || 1)
- // 计算显示的页码
- const displayPages = computed(() => {
- const pages = []
- const maxShow = 5
- let start = Math.max(1, currentPage.value - Math.floor(maxShow / 2))
- let end = Math.min(totalPages.value, start + maxShow - 1)
- if (end - start + 1 < maxShow) {
- start = Math.max(1, end - maxShow + 1)
- }
- for (let i = start; i <= end; i++) {
- pages.push(i)
- }
- return pages
- })
- // 选中的行
- const selectedRows = ref([])
- const selectAll = computed({
- get: () => tableData.value.length > 0 && selectedRows.value.length === tableData.value.length,
- set: () => {}
- })
- // 详情弹窗
- const detailVisible = ref(false)
- const currentRoom = ref({})
- // 代码设置弹窗
- const codeSettingVisible = ref(false)
- const codeSettingList = ref([])
- onMounted(() => {
- loadDeptList()
- loadWardList()
- loadData()
- })
- // 加载所属科室列表
- const loadDeptList = async () => {
- try {
- const res = await request.get('/api/room/dept-list')
- if (res.code === 200) {
- deptList.value = res.data || []
- }
- } catch (error) {
- console.error('加载科室列表失败:', error)
- }
- }
- // 根据科室加载病区列表
- const loadWardList = async () => {
- try {
- const params = {}
- if (searchForm.belongDept) {
- params.belongDept = searchForm.belongDept
- }
- const res = await request.get('/api/room/ward-list', { params })
- if (res.code === 200) {
- wardList.value = res.data || []
- }
- } catch (error) {
- console.error('加载病区列表失败:', error)
- }
- }
- // 所属科室变化时,重新加载病区列表
- const onDeptChange = () => {
- searchForm.belongWard = ''
- loadWardList()
- }
- // 加载数据
- const loadData = async () => {
- loading.value = true
- try {
- const params = {
- page: currentPage.value,
- pageSize: pageSize.value
- }
- if (searchForm.code) params.code = searchForm.code
- if (searchForm.belongDept) params.belongDept = searchForm.belongDept
- if (searchForm.belongWard) params.belongWard = searchForm.belongWard
- if (searchForm.enabled !== '') params.enabled = searchForm.enabled
-
- const res = await request.get('/api/room/list', { params })
- if (res.code === 200) {
- tableData.value = res.data?.records || res.data || []
- total.value = res.data?.total || tableData.value.length
- selectedRows.value = []
- }
- } catch (error) {
- console.error('加载数据失败:', error)
- } finally {
- loading.value = false
- }
- }
- // 切换页码
- const changePage = (page) => {
- if (page >= 1 && page <= totalPages.value) {
- currentPage.value = page
- jumpPage.value = page
- loadData()
- }
- }
- // 修改每页条数
- const handlePageSizeChange = () => {
- currentPage.value = 1
- loadData()
- }
- // 跳转到指定页
- const handleJumpPage = () => {
- const page = Math.min(Math.max(1, jumpPage.value), totalPages.value)
- changePage(page)
- }
- // 判断是否选中
- const isSelected = (item) => {
- return selectedRows.value.some(row => row.id === item.id)
- }
- // 切换选中状态
- const toggleSelect = (item) => {
- const index = selectedRows.value.findIndex(row => row.id === item.id)
- if (index > -1) {
- selectedRows.value.splice(index, 1)
- } else {
- selectedRows.value.push(item)
- }
- }
- // 全选/取消全选
- const handleSelectAll = (e) => {
- if (e.target.checked) {
- selectedRows.value = [...tableData.value]
- } else {
- selectedRows.value = []
- }
- }
- // 查询
- const handleSearch = () => {
- currentPage.value = 1
- loadData()
- }
- // 重置
- const handleReset = () => {
- searchForm.code = ''
- searchForm.belongDept = ''
- searchForm.belongWard = ''
- searchForm.enabled = ''
- currentPage.value = 1
- loadWardList()
- loadData()
- }
- // 查看详情
- const showDetail = async (item) => {
- try {
- const res = await request.get(`/api/room/detail/${item.id}`)
- if (res.code === 200) {
- currentRoom.value = res.data
- detailVisible.value = true
- }
- } catch (error) {
- console.error('获取详情失败:', error)
- }
- }
- // 打开代码设置弹窗
- const openCodeSetting = () => {
- codeSettingList.value = selectedRows.value.map(item => ({
- id: item.id,
- name: item.name,
- belongWard: item.belongWard,
- code: item.code,
- newCode: item.code || ''
- }))
- codeSettingVisible.value = true
- }
- // 格式化代码输入
- const formatCodeInput = (index) => {
- let code = codeSettingList.value[index].newCode
- if (code) {
- code = code.replace(/\D/g, '')
- if (code) {
- let num = parseInt(code)
- if (num > 9999) num = 9999
- if (num < 0) num = 0
- codeSettingList.value[index].newCode = String(num).padStart(4, '0')
- }
- }
- }
- // 保存代码设置
- const saveCodeSetting = async () => {
- const ids = codeSettingList.value.map(item => item.id)
- const codes = codeSettingList.value.map(item => item.newCode)
-
- try {
- const res = await request.post('/api/room/batch-set-code', { ids, codes })
- if (res.code === 200) {
- if (res.errors && res.errors.length > 0) {
- alert('部分设置成功\n' + res.errors.join('\n'))
- } else {
- alert('设置成功')
- }
- codeSettingVisible.value = false
- loadData()
- } else {
- alert(res.message || '设置失败')
- }
- } catch (error) {
- console.error('设置失败:', error)
- alert('设置失败')
- }
- }
- </script>
- <style scoped>
- .page-container {
- background: #fff;
- border-radius: 8px;
- padding: 20px;
- }
- .page-header {
- border-bottom: 1px solid #eee;
- padding-bottom: 15px;
- margin-bottom: 20px;
- font-size: 14px;
- }
- .breadcrumb-label { color: #666; }
- .breadcrumb-path { color: #666; }
- .breadcrumb-separator { margin: 0 5px; color: #666; }
- .breadcrumb-current { color: #409eff; }
- /* 查询栏 */
- .search-bar {
- display: flex;
- align-items: center;
- gap: 15px;
- margin-bottom: 15px;
- padding: 15px;
- background: #f5f7fa;
- border-radius: 4px;
- }
- .search-item {
- display: flex;
- align-items: center;
- }
- .search-item label {
- margin-right: 8px;
- color: #666;
- font-size: 14px;
- white-space: nowrap;
- }
- .search-item input,
- .search-item select {
- padding: 8px 10px;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- font-size: 14px;
- }
- .search-item input { width: 120px; }
- .search-item select { width: 120px; }
- .search-btn {
- padding: 8px 20px;
- background: #409eff;
- color: #fff;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- white-space: nowrap;
- }
- .search-btn:hover { background: #66b1ff; }
- .reset-btn {
- padding: 8px 20px;
- background: #fff;
- color: #666;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- cursor: pointer;
- white-space: nowrap;
- }
- .reset-btn:hover { border-color: #409eff; color: #409eff; }
- /* 操作按钮栏 */
- .action-bar {
- margin-bottom: 15px;
- }
- .code-set-btn {
- padding: 8px 20px;
- background: #67c23a;
- color: #fff;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- }
- .code-set-btn:hover { background: #85ce61; }
- .code-set-btn:disabled {
- background: #c0c4cc;
- cursor: not-allowed;
- }
- /* 数据表格 */
- .table-container {
- overflow-x: auto;
- }
- .data-table {
- width: 100%;
- border-collapse: collapse;
- font-size: 14px;
- }
- .data-table th,
- .data-table td {
- padding: 12px;
- text-align: left;
- border-bottom: 1px solid #eee;
- }
- .data-table th {
- background: #f5f7fa;
- font-weight: 500;
- color: #333;
- }
- .data-table tbody tr:hover {
- background: #f5f7fa;
- }
- .empty-tip {
- text-align: center;
- color: #999;
- padding: 40px 0;
- }
- .detail-btn {
- padding: 4px 12px;
- background: #409eff;
- color: #fff;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 12px;
- }
- .detail-btn:hover { background: #66b1ff; }
- /* 加载状态 */
- .loading-mask {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 60px 0;
- color: #909399;
- }
- .loading-spinner {
- width: 32px;
- height: 32px;
- border: 3px solid #f3f3f3;
- border-top: 3px solid #409eff;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- margin-bottom: 12px;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- /* 弹窗样式 */
- .modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- }
- .modal-content {
- background: #fff;
- border-radius: 8px;
- min-width: 500px;
- max-width: 90%;
- max-height: 90%;
- overflow: hidden;
- }
- .code-setting-modal {
- min-width: 700px;
- }
- .modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 15px 20px;
- border-bottom: 1px solid #eee;
- font-size: 16px;
- font-weight: 500;
- }
- .close-btn {
- background: none;
- border: none;
- font-size: 20px;
- cursor: pointer;
- color: #999;
- }
- .close-btn:hover { color: #333; }
- .modal-body {
- padding: 20px;
- max-height: 60vh;
- overflow-y: auto;
- }
- .modal-footer {
- padding: 15px 20px;
- border-top: 1px solid #eee;
- display: flex;
- justify-content: flex-end;
- gap: 10px;
- }
- /* 详情行 */
- .detail-row {
- display: flex;
- padding: 10px 0;
- border-bottom: 1px solid #f0f0f0;
- }
- .detail-row .label {
- width: 120px;
- color: #666;
- flex-shrink: 0;
- }
- .detail-row .value {
- flex: 1;
- color: #333;
- }
- /* 代码设置表格 */
- .code-setting-table {
- width: 100%;
- border-collapse: collapse;
- font-size: 14px;
- }
- .code-setting-table th,
- .code-setting-table td {
- padding: 10px;
- text-align: left;
- border: 1px solid #eee;
- }
- .code-setting-table th {
- background: #f5f7fa;
- }
- .code-setting-table input {
- width: 100px;
- padding: 6px 10px;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- }
- .code-tips {
- margin-top: 15px;
- color: #999;
- font-size: 12px;
- }
- /* 按钮 */
- .cancel-btn {
- padding: 8px 20px;
- background: #fff;
- color: #666;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- cursor: pointer;
- }
- .cancel-btn:hover { border-color: #409eff; color: #409eff; }
- .confirm-btn {
- padding: 8px 20px;
- background: #409eff;
- color: #fff;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- }
- .confirm-btn:hover { background: #66b1ff; }
- /* 分页样式 */
- .pagination {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 20px;
- padding: 15px 0;
- border-top: 1px solid #eee;
- }
- .pagination-info {
- color: #666;
- font-size: 14px;
- }
- .pagination-controls {
- display: flex;
- align-items: center;
- gap: 5px;
- }
- .page-btn {
- padding: 6px 12px;
- background: #fff;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- cursor: pointer;
- font-size: 13px;
- color: #606266;
- }
- .page-btn:hover:not(:disabled) {
- color: #409eff;
- border-color: #c6e2ff;
- }
- .page-btn:disabled {
- color: #c0c4cc;
- cursor: not-allowed;
- }
- .page-numbers {
- display: flex;
- gap: 5px;
- }
- .page-num {
- min-width: 32px;
- height: 28px;
- padding: 0 6px;
- background: #fff;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- cursor: pointer;
- font-size: 13px;
- color: #606266;
- }
- .page-num:hover {
- color: #409eff;
- border-color: #c6e2ff;
- }
- .page-num.active {
- background: #409eff;
- border-color: #409eff;
- color: #fff;
- }
- .page-size-selector select {
- padding: 5px 10px;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- font-size: 13px;
- margin-left: 10px;
- }
- .page-jump {
- font-size: 13px;
- color: #606266;
- margin-left: 10px;
- }
- .page-jump input {
- width: 50px;
- padding: 5px;
- border: 1px solid #dcdfe6;
- border-radius: 4px;
- text-align: center;
- margin: 0 5px;
- }
- </style>
|