Files
his/openhis-ui-vue3/src/views/maintainSystem/checkprojectSettings/index.vue

2179 lines
66 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="check-project-settings">
<!-- 左侧导航栏 -->
<div class="sidebar">
<div class="sidebar-header">
<h3>检查项目管理</h3>
</div>
<div class="sidebar-menu">
<!-- 明确列出所有导航项 -->
<button
class="menu-item active"
@click="handleMenuClick('检查类型')"
>
<span class="menu-icon">📋</span>
<span class="menu-text">检查类型</span>
</button>
<button
class="menu-item"
@click="handleMenuClick('检查方法')"
>
<span class="menu-icon">🔬</span>
<span class="menu-text">检查方法</span>
</button>
<button
class="menu-item"
@click="handleMenuClick('检查部位')"
>
<span class="menu-icon">🎯</span>
<span class="menu-text">检查部位</span>
</button>
<button
class="menu-item"
@click="handleMenuClick('套餐设置')"
>
<span class="menu-icon">📦</span>
<span class="menu-text">套餐设置</span>
</button>
</div>
</div>
<!-- 主内容区域 -->
<div class="content">
<!-- 套餐管理和套餐设置 -->
<template v-if="activeMenu === '套餐设置'">
<!-- 套餐设置界面默认显示 -->
<PackageSettings
v-if="packageView === 'settings'"
:mode="packageMode"
:package-data="currentPackageData"
@switch-to-management="handleSwitchToManagement"
@save-success="handleSaveSuccess"
/>
<!-- 套餐管理界面列表 -->
<PackageManagement
v-else
@switch-to-settings="handleSwitchToSettings"
/>
</template>
<!-- 检查类型的表格视图 -->
<template v-if="activeMenu === '检查类型'">
<div class="header">
<h1>{{ activeMenu }}管理</h1>
<div class="header-actions">
<button class="btn btn-add-new" @click="handleAddNewRow">+
</button>
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th style="width: 50px;"></th>
<th style="width: 100px;">*编码</th>
<th style="width: 150px;">*名称</th>
<th style="width: 150px;">*检查类型</th>
<th style="width: 120px;">选择部位</th>
<th style="width: 150px;">*执行科室</th>
<th style="width: 100px;">序号</th>
<th style="width: 150px;">备注</th>
<th style="width: 120px;">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in tableData"
:key="index"
:class="{ 'editing-row': item.editing, 'child-row': !!item.parentId }"
@click="handleRowClick(index)"
>
<td>{{ item.row }}</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入编码" v-model="item.code">
</template>
<template v-else>
{{ item.code }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入名称" v-model="item.name">
</template>
<template v-else>
{{ item.name }}
</template>
</td>
<td>
<template v-if="item.editing">
<select v-model="item.type" :class="{ 'placeholder-text': !item.type }">
<option value="">选择检查类型</option>
<option
v-for="dict in inspectionTypeDicts"
:key="dict.dictValue"
:value="dict.dictValue"
>
{{ dict.dictLabel }}
</option>
</select>
</template>
<template v-else>
<span v-if="item.type">{{ getInspectionTypeLabel(item.type) }}</span>
<span v-else class="placeholder-text">选择检查类型</span>
</template>
</td>
<td class="checkbox-container">
<input type="checkbox" v-model="item.selected" @click.stop>
</td>
<td>
<template v-if="item.editing">
<select v-model="item.department" :class="{ 'placeholder-text': !item.department }">
<option value="">选择执行科室</option>
<option
v-if="item.department"
:value="item.department"
>
{{ item.department }}
</option>
<option
v-for="dept in departments"
:key="dept.dictValue"
:value="dept.dictLabel"
>
{{ dept.dictLabel }}
</option>
</select>
</template>
<template v-else>
<span v-if="item.department">{{ item.department }}</span>
<span v-else class="placeholder-text">选择执行科室</span>
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" v-model="item.number">
</template>
<template v-else>
{{ item.number }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入备注" v-model="item.remark">
</template>
<template v-else>
{{ item.remark || '' }}
</template>
</td>
<td class="actions">
<template v-if="item.editing">
<button class="btn btn-confirm" @click.stop="handleConfirm(index)" title="保存">
</button>
<button class="btn btn-cancel" @click.stop="handleCancelEdit(index)" title="取消">
</button>
<button
v-if="!item.parentId"
class="btn btn-add"
@click.stop="handleAdd(index)"
title="添加子项"
>
+
</button>
</template>
<template v-else>
<button class="btn btn-edit" @click.stop="handleEdit(index)" title="修改">
</button>
<button
v-if="!item.parentId"
class="btn btn-add"
@click.stop="handleAdd(index)"
title="添加子项"
>
+
</button>
<button class="btn btn-delete" @click.stop="handleDelete(index)" title="删除">
</button>
</template>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<!-- 检查方法的表格视图 -->
<template v-else-if="activeMenu === '检查方法'">
<div class="header">
<h1>{{ activeMenu }}管理</h1>
</div>
<div class="search-bar search-bar-method">
<div class="search-filters">
<div class="search-item">
<label>检查类型</label>
<el-select v-model="searchParamsMethod.checkType" placeholder="选择检查类型" style="width: 150px">
<el-option
v-for="dict in inspectionTypeDicts"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
>
</el-option>
</el-select>
</div>
<div class="search-item">
<label>名称</label>
<el-input placeholder="名称/编码" v-model="searchParamsMethod.name" />
</div>
<div class="search-item">
<label>费用套餐</label>
<el-select v-model="searchParamsMethod.packageName" placeholder="选择使用套餐" style="width: 150px">
<el-option
v-for="pkg in checkPackages"
:key="pkg.id"
:label="pkg.name"
:value="pkg.name"
>
</el-option>
</el-select>
</div>
</div>
<div class="search-actions">
<el-button type="primary" @mouseenter="hoverAddButton = true" @mouseleave="hoverAddButton = false" @click="handleAddNewRow">新增</el-button>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button type="primary" @click="handleReset">重置</el-button>
<el-button type="success" @click="handleExport">导出表格</el-button>
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th style="width: 50px;"></th>
<th style="width: 100px;">代码</th>
<th style="width: 150px;">名称</th>
<th style="width: 150px;">检查类型</th>
<th style="width: 150px;">套餐名称</th>
<th style="width: 100px;">曝光次数</th>
<th style="width: 100px;">序号</th>
<th style="width: 150px;">备注</th>
<th style="width: 120px;">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in checkMethodData"
:key="index"
:class="{ 'editing-row': item.editing }"
@click="handleEdit(index)"
>
<td>{{ item.row }}</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入编码" v-model="item.code">
</template>
<template v-else>
{{ item.code }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入方法名称" v-model="item.name">
</template>
<template v-else>
{{ item.name }}
</template>
</td>
<td>
<template v-if="item.editing">
<select v-model="item.checkType">
<option value="">选择检查类型</option>
<option
v-for="dict in inspectionTypeDicts"
:key="dict.dictValue"
:value="dict.dictValue"
>
{{ dict.dictLabel }}
</option>
</select>
</template>
<template v-else>
{{ getInspectionTypeLabel(item.checkType) || '' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入套餐名称" v-model="item.packageName">
</template>
<template v-else>
{{ item.packageName || '' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" v-model="item.exposureNum">
</template>
<template v-else>
{{ item.exposureNum || 0}}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" v-model="item.orderNum">
</template>
<template v-else>
{{ item.orderNum || '0' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入备注" v-model="item.remark">
</template>
<template v-else>
{{ item.remark || '' }}
</template>
</td>
<td class="actions">
<template v-if="item.editing">
<button class="btn btn-confirm" @click.stop="handleConfirm(index)" title="保存">
</button>
<button class="btn btn-cancel" @click.stop="handleCancelEdit(index)" title="取消">
</button>
</template>
<template v-else>
<button class="btn btn-edit" @click.stop="handleEdit(index)" title="编辑">
</button>
<button class="btn btn-confirm" @click.stop="handleConfirm(index)" title="保存">
</button>
<button class="btn btn-delete" @click.stop="handleDelete(index)" title="删除">
</button>
</template>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<!-- 检查部位的表格视图 -->
<template v-else-if="activeMenu === '检查部位'">
<div class="header">
<h1>{{ activeMenu }}管理</h1>
</div>
<div class="search-bar search-bar-part">
<div class="search-item">
<label>检查类型</label>
<el-select v-model="searchParamsPart.checkType" placeholder="选择检查类型" style="width: 150px">
<el-option
v-for="dict in inspectionTypeDicts"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
>
</el-option>
</el-select>
</div>
<div class="search-item">
<label>名称</label>
<el-input placeholder="名称/编码" v-model="searchParamsPart.name" />
</div>
<div class="search-item">
<label>费用套餐</label>
<el-input placeholder="费用套餐" v-model="searchParamsPart.packageName" />
</div>
<div class="search-actions">
<el-button type="primary" @click="handleAddNewRow">新增</el-button>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button type="primary" @click="handleReset">重置</el-button>
<el-button type="success" @click="handleExport">导出表格</el-button>
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th style="width: 50px;"></th>
<th style="width: 100px;">*编码</th>
<th style="width: 100px;">*名称</th>
<th style="width: 120px;">检查类型</th>
<th style="width: 80px;">曝光次数</th>
<th style="width: 120px;">费用套餐</th>
<th style="width: 80px;">金额</th>
<th style="width: 50px;">序号</th>
<th style="width: 120px;">服务范围</th>
<th style="width: 120px;">下级医技类型</th>
<th style="width: 150px;">备注</th>
<th style="width: 120px;">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in checkPartData"
:key="index"
:class="{ 'editing-row': item.editing }"
@click="handleEdit(index)"
>
<td>{{ item.row }}</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入编码" v-model="item.code">
</template>
<template v-else>
{{ item.code }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入名称" v-model="item.name">
</template>
<template v-else>
{{ item.name }}
</template>
</td>
<td>
<template v-if="item.editing">
<select v-model="item.checkType">
<option value="">选择检查类型</option>
<option
v-for="dict in inspectionTypeDicts"
:key="dict.dictValue"
:value="dict.dictValue"
>
{{ dict.dictLabel }}
</option>
</select>
</template>
<template v-else>
{{ getInspectionTypeLabel(item.checkType) || '' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="number" min="0" placeholder="请输入曝光次数" v-model="item.exposureNum">
</template>
<template v-else>
{{ item.exposureNum || '0' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入费用套餐" v-model="item.packageName">
</template>
<template v-else>
{{ item.packageName || '' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="number" step="0.01" min="0" placeholder="请输入金额" v-model="item.price">
</template>
<template v-else>
{{ item.price || '0.00' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="number" min="0" placeholder="请输入序号" v-model="item.number">
</template>
<template v-else>
{{ item.number || '999999' }}
</template>
</td>
<td>
<template v-if="item.editing">
<select v-model="item.serviceScope">
<option value="">选择服务范围</option>
<option
v-for="dict in serviceScopeDicts"
:key="dict.dictValue"
:value="dict.dictValue"
>
{{ dict.dictLabel }}
</option>
</select>
</template>
<template v-else>
{{ getServiceScopeLabel(item.serviceScope) || '' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入下级医技类型" v-model="item.subType">
</template>
<template v-else>
{{ item.subType || '' }}
</template>
</td>
<td>
<template v-if="item.editing">
<input type="text" placeholder="请输入备注" v-model="item.remark">
</template>
<template v-else>
{{ item.remark || '' }}
</template>
</td>
<td class="actions">
<template v-if="item.editing">
<button class="btn btn-confirm" @click.stop="handleConfirm(index)" title="保存">
</button>
<button class="btn btn-cancel" @click.stop="handleCancelEdit(index)" title="取消">
</button>
</template>
<template v-else>
<button class="btn btn-edit" @click.stop="handleEdit(index)" title="编辑">
</button>
<button class="btn btn-confirm" @click.stop="handleConfirm(index)" title="保存">
</button>
<button class="btn btn-delete" @click.stop="handleDelete(index)" title="删除">
</button>
</template>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<!-- 分页区域 -->
<div class="pagination">
<button class="pagination-btn"></button>
<span>1</span>
<button class="pagination-btn"></button>
</div>
</div>
</div>
</template>
<script setup>
import {computed, onMounted, reactive, ref} from 'vue';
import {ElMessage, ElMessageBox} from 'element-plus';
import {getDicts} from '@/api/system/dict/data';
import {getList as getDeptList} from '@/views/basicmanage/organization/components/api';
import {
addCheckMethod,
addCheckPart,
addCheckType,
delCheckMethod,
delCheckPart,
delCheckType,
exportCheckMethod,
exportCheckPart,
listCheckMethod,
listCheckPackage,
listCheckPart,
listCheckType,
searchCheckMethod,
searchCheckPart,
updateCheckMethod,
updateCheckPart,
updateCheckType
} from '@/api/system/checkType';
import PackageSettings from './components/PackageSettings.vue';
import PackageManagement from './components/PackageManagement.vue';
// 菜单数据
const menus = ['检查类型', '检查方法', '检查部位', '套餐设置'];
const activeMenu = ref('检查类型');
// 套餐视图状态: management-套餐管理列表, settings-套餐设置
const packageView = ref('management')
// 套餐设置模式: add-新增, edit-编辑, view-查看
const packageMode = ref('add')
// 当前编辑的套餐数据
const currentPackageData = ref(null)
// 检查类型和科室选项
const checkTypes = ref([]);
// 完整的检查类型字典数据
const inspectionTypeDicts = ref([]);
// 完整的服务范围字典数据
const serviceScopeDicts = ref([]);
const checkMethods = ref([]);
// 根据字典值获取检查类型标签
const getInspectionTypeLabel = (value) => {
const dictItem = inspectionTypeDicts.value.find(item => item.dictValue === value);
return dictItem ? dictItem.dictLabel : value;
};
// 根据字典值获取服务范围标签
const getServiceScopeLabel = (value) => {
const dictItem = serviceScopeDicts.value.find(item => item.dictValue === value);
return dictItem ? dictItem.dictLabel : value;
};
const checkParts = ref([]);
const checkPackages = ref([]);
const departments = ref([]);
// 表格数据 - 为每个菜单创建独立的数据存储
const checkTypeData = reactive([]);
const checkMethodData = reactive([]);
const checkPartData = reactive([]);
const packageData = reactive([]);
// 当前显示的表格数据
const tableData = computed(() => {
switch(activeMenu.value) {
case '检查类型':
return checkTypeData;
case '检查方法':
return checkMethodData;
case '检查部位':
return checkPartData;
case '套餐设置':
return packageData;
default:
return [];
}
});
// 搜索条件 - 为每个菜单创建独立的搜索参数
const searchParamsType = reactive({});
const searchParamsMethod = reactive({
checkType: '',
name: '',
packageName: ''
});
const searchParamsPart = reactive({
checkType: '',
name: '',
packageName: ''
});
// 获取当前菜单对应的搜索参数
const currentSearchParams = computed(() => {
switch(activeMenu.value) {
case '检查类型':
return searchParamsType;
case '检查方法':
return searchParamsMethod;
case '检查部位':
return searchParamsPart;
case '套餐设置':
return searchParamsMethod; // 套餐设置可能共享检查方法的搜索参数
default:
return {};
}
});
// 按钮悬停状态
const hoverAddButton = ref(false);
// 排序函数:确保父行在前,子行在父行后面
function sortTableDataByParentChild(data) {
// 分离父行和子行
const parentRows = [];
const childRows = [];
data.forEach(item => {
// parentId 为 null/undefined/0 的都是父行
if (item.parentId) {
childRows.push(item);
} else {
parentRows.push(item);
}
});
// 按编码排序父行
parentRows.sort((a, b) => {
const codeA = a.code || '';
const codeB = b.code || '';
return codeA.localeCompare(codeB, undefined, { numeric: true });
});
// 构建最终排序结果
const sortedData = [];
let rowNum = 1;
parentRows.forEach(parent => {
// 添加父行
parent.row = String(rowNum);
sortedData.push(parent);
// 查找并添加该父行的所有子行
const children = childRows.filter(child => child.parentId === parent.id);
// 按子行编码排序
children.sort((a, b) => {
const codeA = a.code || '';
const codeB = b.code || '';
return codeA.localeCompare(codeB, undefined, { numeric: true });
});
// 添加子行,并设置子行行号
children.forEach((child, index) => {
child.row = parent.row + '.' + (index + 1);
sortedData.push(child);
});
rowNum++;
});
return sortedData;
}
// 从数据库获取所有检查相关数据
onMounted(async () => {
try {
// 1. 获取科室分类字典,找到“医学影像科”对应的 value
let imageClassValues = [];
try {
const dictRes = await getDicts('organization_class');
if (dictRes && dictRes.data) {
imageClassValues = dictRes.data
.filter(item => item.dictLabel.includes('医学影像科'))
.map(item => item.dictValue);
}
} catch (e) {
console.error('获取字典失败', e);
}
// 2. 获取科室列表
const deptResponse = await getDeptList({ pageNo: 1, pageSize: 1000 });
if (deptResponse && (deptResponse.data || deptResponse.records)) {
const records = deptResponse.data?.records || deptResponse.records || [];
// 3. 过滤
const filteredDepts = records.filter(item => {
// 匹配字典值
let matchClass = false;
if (item.classEnum && imageClassValues.length > 0) {
// classEnum 可能是 "1" 或 "1,2"
const itemClasses = String(item.classEnum).split(',');
matchClass = itemClasses.some(c => imageClassValues.includes(c));
}
// 匹配文本
const matchText = item.classEnum_dictText && item.classEnum_dictText.includes('医学影像科');
// 匹配名称 (保底) - 放宽匹配条件
const matchName = ['超声', '放射', '内镜', '心电'].some(k => item.name && item.name.includes(k));
return matchClass || matchText || matchName;
});
// 4. 赋值
departments.value = filteredDepts.map(item => ({
dictValue: item.name,
dictLabel: item.name
}));
}
// 获取检查类型数据(从数据字典获取)
const typeResponse = await getDicts('inspection_type');
if (typeResponse && typeResponse.data) {
// 保存完整的字典数据
inspectionTypeDicts.value = typeResponse.data;
// 从数据字典获取检查类型值
checkTypes.value = typeResponse.data.map(item => item.dictValue);
} else {
checkTypes.value = [];
inspectionTypeDicts.value = [];
}
// 获取服务范围数据(从数据字典获取)
const scopeResponse = await getDicts('scope_of_services');
if (scopeResponse && scopeResponse.data) {
// 保存完整的服务范围字典数据
serviceScopeDicts.value = scopeResponse.data;
} else {
serviceScopeDicts.value = [];
}
// 获取检查类型表格数据仍然从API获取
const typeTableResponse = await listCheckType();
if (typeTableResponse && typeTableResponse.data) {
// 构建检查类型表格数据
checkTypeData.splice(0, checkTypeData.length);
const tempData = typeTableResponse.data.map((item) => ({
id: item.id, // 保存id字段用于判断是新增还是修改
row: '', // 行号将在排序后重新分配
code: item.code,
name: item.name,
type: item.type,
selected: true,
department: item.department || '',
number: item.number || '999999',
remark: item.remark || '',
parentId: item.parentId || null, // 父行的 parentId 保持为 null
actions: true
}));
// 排序数据,确保父行在前,子行在父行后
const sortedData = sortTableDataByParentChild(tempData);
sortedData.forEach(item => {
checkTypeData.push(item);
});
}
// 获取检查方法数据
const methodResponse = await listCheckMethod();
if (methodResponse && methodResponse.data) {
// 确保data是数组类型
checkMethods.value = Array.isArray(methodResponse.data) ? methodResponse.data : [];
} else {
checkMethods.value = [];
}
// 获取检查部位数据
const partResponse = await listCheckPart();
if (partResponse && partResponse.data) {
// 确保data是数组类型
checkParts.value = Array.isArray(partResponse.data) ? partResponse.data : [];
} else {
checkParts.value = [];
}
// 获取检查套餐数据
const packageResponse = await listCheckPackage();
if (packageResponse && packageResponse.data) {
// 确保data是数组类型
checkPackages.value = Array.isArray(packageResponse.data) ? packageResponse.data : [];
} else {
checkPackages.value = [];
}
} catch (error) {
console.error('获取数据失败:', error);
// 如果API调用失败显示友好提示
alert('获取检查类型数据失败,请检查网络或服务状态');
}
});
// 处理菜单点击
function handleMenuClick(menu) {
console.log('点击菜单:', menu);
console.log('当前activeMenu:', activeMenu.value);
activeMenu.value = menu;
console.log('更新后activeMenu:', activeMenu.value);
// 更新菜单激活状态
const menuItems = document.querySelectorAll('.menu-item');
menuItems.forEach(item => {
item.classList.remove('active');
if (item.textContent.trim() === menu) {
item.classList.add('active');
}
});
// 如果点击套餐设置,默认显示套餐设置界面
if (menu === '套餐设置') {
packageView.value = 'settings'
packageMode.value = 'add'
currentPackageData.value = null
console.log('显示套餐设置界面')
} else {
loadMenuData(menu);
}
}
// 从套餐设置切换到套餐管理界面
function handleSwitchToManagement() {
console.log('切换到套餐管理界面')
packageView.value = 'management'
}
// 从套餐管理切换到套餐设置界面(新增/编辑/查看)
function handleSwitchToSettings(params) {
console.log('切换到套餐设置界面:', params)
packageView.value = 'settings'
packageMode.value = params.mode
currentPackageData.value = params.data
}
// 保存成功后保持在套餐设置界面
function handleSaveSuccess() {
console.log('保存成功')
// 保存成功后保持在套餐设置界面,可以继续编辑或返回管理界面
ElMessage.success('保存成功')
}
// 根据菜单加载对应数据
async function loadMenuData(menu) {
try {
switch(menu) {
case '检查类型':
// 清空检查类型数据
checkTypeData.splice(0, checkTypeData.length);
const typeResponse = await listCheckType();
if (typeResponse && typeResponse.data) {
// 确保data是数组类型
const typeData = Array.isArray(typeResponse.data) ? typeResponse.data : [];
// 获取所有不重复的检查类型值
checkTypes.value = [...new Set(typeData.map(item => item.type))];
// 构建临时数据
const tempData = typeData.map((item) => ({
id: item.id, // 保存id字段用于判断是新增还是修改
row: '', // 行号将在排序后重新分配
code: item.code,
name: item.name,
type: item.type,
selected: true,
department: item.department || '',
number: item.number || '999999',
remark: item.remark || '',
parentId: item.parentId || null, // 父行的 parentId 保持为 null
actions: true
}));
// 排序数据,确保父行在前,子行在父行后
const sortedData = sortTableDataByParentChild(tempData);
sortedData.forEach(item => {
checkTypeData.push(item);
});
}
break;
case '检查方法':
// 清空检查方法数据
checkMethodData.splice(0, checkMethodData.length);
// 构建检查方法的搜索参数
const methodParams = {
pageNo: 1,
pageSize: 100, // 默认获取100条数据可以根据需要调整
checkType: searchParamsMethod.checkType,
name: searchParamsMethod.name,
packageName: searchParamsMethod.packageName
};
try {
const methodResponse = await searchCheckMethod(methodParams);
// 确保data是数组适配不同的后端返回格式
let methodData = [];
if (methodResponse) {
if (Array.isArray(methodResponse)) {
methodData = methodResponse;
} else if (methodResponse.data && Array.isArray(methodResponse.data)) {
methodData = methodResponse.data;
} else if (methodResponse.data && methodResponse.data.data && Array.isArray(methodResponse.data.data)) {
methodData = methodResponse.data.data;
} else if (methodResponse.data && methodResponse.data.records) {
methodData = methodResponse.data.records;
}
}
// 处理数组数据
if (methodData.length > 0) {
methodData.forEach((item, index) => {
checkMethodData.push({
id: item.id, // 保存id字段用于判断是新增还是修改
row: (index + 1).toString(),
code: item.code,
name: item.name,
checkType: item.checkType || '',
packageName: item.packageName || '',
exposureNum: item.exposureNum || 0,
orderNum: item.orderNum || 0,
remark: item.remark || '',
actions: true
});
});
} else {
ElMessage.warning('未获取到检查方法数据');
}
} catch (error) {
ElMessage.error(`加载检查方法数据失败: ${error.message || '未知错误'}`);
}
break;
case '检查部位':
// 清空检查部位数据
checkPartData.splice(0, checkPartData.length);
// 构建检查部位的搜索参数
const partParams = {
pageNo: 1,
pageSize: 100, // 默认获取100条数据可以根据需要调整
checkType: searchParamsPart.checkType,
name: searchParamsPart.name,
packageName: searchParamsPart.packageName
};
try {
const partResponse = await searchCheckPart(partParams);
// 确保data是数组适配不同的后端返回格式
let partData = [];
if (partResponse) {
if (Array.isArray(partResponse)) {
partData = partResponse;
} else if (partResponse.data && Array.isArray(partResponse.data)) {
partData = partResponse.data;
} else if (partResponse.data && partResponse.data.data && Array.isArray(partResponse.data.data)) {
partData = partResponse.data.data;
} else if (partResponse.data && partResponse.data.records) {
partData = partResponse.data.records;
}
}
// 处理数组数据
if (partData.length > 0) {
partData.forEach((item, index) => {
checkPartData.push({
id: item.id, // 保存id字段用于判断是新增还是修改
row: (index + 1).toString(),
code: item.code,
name: item.name,
checkType: item.checkType || '',
exposureNum: item.exposureNum || 0,
packageName: item.packageName || '',
price: item.price || 0,
number: item.number || '999999',
serviceScope: item.serviceScope || '',
subType: item.subType || '',
remark: item.remark || '',
actions: true
});
});
} else {
ElMessage.warning('未获取到检查部位数据');
}
} catch (error) {
ElMessage.error(`加载检查部位数据失败: ${error.message || '未知错误'}`);
}
break;
case '套餐设置':
const packageResponse = await listCheckPackage();
if (packageResponse && packageResponse.data) {
}
break;
}
} catch (error) {
console.error('加载菜单数据失败:', error);
ElMessage.error(`加载${menu}数据失败,请检查网络或服务状态`);
}
}
// 处理行点击进入编辑状态
function handleRowClick(index) {
const item = tableData.value[index];
if (!item.editing) {
item.editing = true;
}
}
// 处理编辑按钮点击
function handleEdit(index) {
const item = tableData.value[index];
item.editing = true;
}
// 处理取消编辑按钮点击
function handleCancelEdit(index) {
const item = tableData.value[index];
if (item.isNew) {
tableData.value.splice(index, 1);
} else {
item.editing = false;
}
}
// 处理确认按钮点击
async function handleConfirm(index) {
const item = tableData.value[index];
// 必填字段验证
if (!item.code || item.code.trim() === '') {
ElMessage.error('编码不能为空');
return;
}
if (!item.name || item.name.trim() === '') {
ElMessage.error('名称不能为空');
return;
}
if (!item.type || item.type.trim() === '') {
ElMessage.error('检查类型不能为空');
return;
}
if (!item.department || item.department.trim() === '') {
ElMessage.error('执行科室不能为空');
return;
}
try {
// 根据当前激活的菜单调用不同的API
if (activeMenu.value === '检查方法') {
// 检查方法的保存逻辑
if (item.id) {
// 修改操作:只传递必要字段
const updateData = {
id: item.id,
code: item.code,
name: item.name,
checkType: item.checkType,
packageName: item.packageName,
exposureNum: item.exposureNum,
orderNum: item.orderNum,
remark: item.remark
};
await updateCheckMethod(updateData);
} else {
// 新增操作:只传递必要字段
const addData = {
code: item.code,
name: item.name,
checkType: item.checkType,
packageName: item.packageName,
exposureNum: item.exposureNum,
orderNum: item.orderNum,
remark: item.remark
};
const response = await addCheckMethod(addData);
// 将新增的id赋值给本地数据兼容不同的返回格式
let newId = null;
if (response) {
// 尝试多种可能的返回格式
newId = response.id ||
response.data?.id ||
response.data?.data?.id ||
(typeof response.data === 'number' ? response.data : null) ||
(typeof response.data === 'string' ? response.data : null);
}
if (newId) {
item.id = newId;
} else {
ElMessage.warning('数据已保存但未能获取ID删除功能可能受影响');
}
}
} else if (activeMenu.value === '检查部位') {
// 检查部位的保存逻辑
if (item.id) {
// 修改操作:只传递必要字段
const updateData = {
id: item.id,
code: item.code,
name: item.name,
checkType: item.checkType,
exposureNum: item.exposureNum,
packageName: item.packageName,
price: item.price,
number: item.number,
serviceScope: item.serviceScope,
subType: item.subType,
remark: item.remark
};
await updateCheckPart(updateData);
} else {
// 新增操作:只传递必要字段
const addData = {
code: item.code,
name: item.name,
checkType: item.checkType,
exposureNum: item.exposureNum,
packageName: item.packageName,
price: item.price,
number: item.number,
serviceScope: item.serviceScope,
subType: item.subType,
remark: item.remark
};
console.log('准备新增检查部位:', addData);
const newItem = await addCheckPart(addData);
console.log('新增检查部位返回结果:', newItem);
// 将新增的id赋值给本地数据
item.id = newItem.id;
}
} else {
// 检查类型的保存逻辑
if (item.id) {
// 修改操作:只传递必要字段
const updateData = {
id: item.id,
code: item.code,
name: item.name,
type: item.type,
selected: item.selected,
department: item.department,
number: item.number,
remark: item.remark,
parentId: item.parentId || null // 父行的 parentId 设为 null
};
await updateCheckType(updateData);
} else {
// 新增操作:只传递必要字段
const addData = {
code: item.code,
name: item.name,
type: item.type,
selected: item.selected,
department: item.department,
number: item.number,
remark: item.remark,
parentId: item.parentId || null // 父行的 parentId 设为 null
};
const response = await addCheckType(addData);
// 将新增的id赋值给本地数据兼容不同的返回格式
const newItem = response.data || response;
item.id = newItem.id || newItem;
console.log('保存的ID:', item.id);
}
}
// 退出编辑状态
tableData.value[index].editing = false;
if (item.isNew) {
item.isNew = false;
}
// 显示保存成功提示
ElMessage.success(`${item.row} 行数据已保存`);
// 保存成功后自动刷新列表
await loadMenuData(activeMenu.value);
} catch (error) {
console.error('保存失败,错误信息:', error);
console.error('保存失败,错误详情:', error.response ? error.response.data : error);
ElMessage.error('保存失败,请稍后重试');
}
}
// 处理删除按钮点击
async function handleDelete(index) {
const item = tableData.value[index];
// 如果是新增但未保存的行没有ID或标记为新增直接从列表中移除
// 注意:必须严格检查 ID 是否为有效值
const hasValidId = item.id &&
item.id !== null &&
item.id !== undefined &&
item.id !== '' &&
String(item.id).trim() !== '' &&
String(item.id) !== 'undefined' &&
String(item.id) !== 'null';
if (!hasValidId || item.isNew) {
ElMessageBox.confirm('该行数据尚未保存,确定要删除吗?', '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
tableData.value.splice(index, 1);
ElMessage.success('删除成功');
}).catch(() => {
console.log('用户取消删除操作');
});
return;
}
// 检查是否为父行
const isParentRow = activeMenu.value === '检查类型' && !item.parentId;
// 如果是父行,查找所有子行
let childRows = [];
if (isParentRow && item.id) {
childRows = tableData.value.filter(row => row.parentId === item.id);
}
// 构建确认消息
let confirmMessage = '确定要删除这一行吗?';
if (isParentRow && childRows.length > 0) {
confirmMessage = `确定要删除这一行吗?\n该操作将同时删除 ${childRows.length} 个子行数据。`;
}
ElMessageBox.confirm(confirmMessage, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
// 确保 ID 是有效的数字或字符串
const validId = String(item.id).trim();
if (!validId || validId === 'undefined' || validId === 'null') {
ElMessage.error('删除失败无效的ID');
return;
}
// 只需要删除当前行,数据库会通过外键级联自动删除子行
if (activeMenu.value === '检查方法') {
await delCheckMethod(validId);
} else if (activeMenu.value === '检查部位') {
await delCheckPart(validId);
} else {
await delCheckType(validId);
}
// 删除成功,刷新列表数据
await loadMenuData(activeMenu.value);
ElMessage.success(`删除成功!${isParentRow && childRows.length > 0 ? `已删除 1 个父行和 ${childRows.length} 个子行` : ''}`);
} catch (error) {
ElMessage.error('删除失败,请稍后重试');
}
}).catch(() => {
// 用户取消删除操作
});
}
// 处理添加新行按钮点击
function handleAddNewRow() {
// 获取当前最大行号,为新建行生成行号
const maxRowNum = Math.max(
0,
...tableData.value.map(item => {
// 处理子行编号,如"1.1"只取主行号"1"
const rowParts = item.row.split('.');
return parseInt(rowParts[0]) || 0;
})
);
let newRow;
// 根据当前激活的菜单生成不同的初始数据结构
if (activeMenu.value === '检查类型') {
newRow = {
row: String(maxRowNum + 1),
code: '',
name: '',
type: '',
selected: true,
department: '',
number: '999999',
remark: '',
parentId: null, // 父行的 parentId 设为 null避免外键约束错误
editing: true, // 新行默认进入编辑状态
isNew: true, // 标记为新增行
actions: true
};
} else if (activeMenu.value === '检查方法') {
newRow = {
row: String(maxRowNum + 1),
code: '',
name: '',
checkType: '',
packageName: '',
exposureNum: 0,
orderNum: 0,
remark: '',
editing: true, // 新行默认进入编辑状态
isNew: true, // 标记为新增行
actions: true
};
} else if (activeMenu.value === '检查部位') {
newRow = {
row: String(maxRowNum + 1),
code: '',
name: '',
checkType: '',
exposureNum: 0,
packageName: '',
price: 0,
number: '999999',
serviceScope: '',
subType: '',
remark: '',
editing: true, // 新行默认进入编辑状态
isNew: true, // 标记为新增行
actions: true
};
} else {
// 默认数据结构
newRow = {
row: String(maxRowNum + 1),
code: '',
name: '',
type: '',
selected: true,
department: '',
number: '999999',
remark: '',
editing: true, // 新行默认进入编辑状态
isNew: true, // 标记为新增行
actions: true
};
}
tableData.value.push(newRow);
}
// 处理添加按钮点击
function handleAdd(index) {
const parentRow = tableData.value[index];
if (!parentRow.id) {
ElMessage.warning('请先保存父行数据后再添加子行');
return;
}
// 查找该父行的所有现有子行,确定下一个子行号
const children = tableData.value.filter(item => item.parentId === parentRow.id);
const nextChildNum = children.length + 1;
// 生成默认子行编码
const childCode = parentRow.code ? `${parentRow.code}_${String(nextChildNum).padStart(2, '0')}` : '';
// 创建子行数据
const childRow = {
row: parentRow.row + '.' + nextChildNum, // 子行行号显示
code: childCode,
name: '',
type: parentRow.type,
selected: false,
department: parentRow.department,
number: '',
remark: '',
parentId: parentRow.id,
editing: true,
isNew: true,
actions: true
};
// 找到父行的最后一个子行位置,在其后插入新子行
let insertIndex = index + 1;
for (let i = index + 1; i < tableData.value.length; i++) {
const item = tableData.value[i];
// 如果是该父行的子行,继续向后查找
if (item.parentId === parentRow.id) {
insertIndex = i + 1;
} else {
// 遇到非子行,停止查找
break;
}
}
// 在找到的位置插入子行
tableData.value.splice(insertIndex, 0, childRow);
}
// 处理搜索功能
async function handleSearch() {
try {
console.log('搜索条件:', currentSearchParams.value);
// ElMessage.info(`正在搜索${activeMenu.value}数据...`);
switch(activeMenu.value) {
case '检查方法':
// 清空检查方法数据
checkMethodData.splice(0, checkMethodData.length);
// 构建检查方法的搜索参数
const methodParams = {
pageNo: 1,
pageSize: 100, // 默认获取100条数据可以根据需要调整
checkType: searchParamsMethod.checkType,
name: searchParamsMethod.name,
packageName: searchParamsMethod.packageName
};
const methodResponse = await searchCheckMethod(methodParams);
// 确保data是数组适配不同的后端返回格式
let methodData = [];
if (methodResponse) {
if (Array.isArray(methodResponse)) {
methodData = methodResponse;
} else if (methodResponse.data && Array.isArray(methodResponse.data)) {
methodData = methodResponse.data;
} else if (methodResponse.data && methodResponse.data.data && Array.isArray(methodResponse.data.data)) {
methodData = methodResponse.data.data;
} else if (methodResponse.data && methodResponse.data.records) {
methodData = methodResponse.data.records;
}
}
// 处理数组数据
if (methodData.length > 0) {
methodData.forEach((item, index) => {
checkMethodData.push({
id: item.id, // 保存id字段用于判断是新增还是修改
row: (index + 1).toString(),
code: item.code,
name: item.name,
checkType: item.checkType || '',
packageName: item.packageName || '',
exposureNum: item.exposureNum || 0,
orderNum: item.orderNum || 0,
remark: item.remark || '',
actions: true
});
});
ElMessage.success(`搜索到${methodData.length}条检查方法数据`);
} else {
ElMessage.warning('未搜索到检查方法数据');
}
break;
case '检查部位':
// 清空检查部位数据
checkPartData.splice(0, checkPartData.length);
// 构建检查部位的搜索参数
const partParams = {
pageNo: 1,
pageSize: 100, // 默认获取100条数据可以根据需要调整
checkType: searchParamsPart.checkType,
name: searchParamsPart.name,
packageName: searchParamsPart.packageName
};
const partResponse = await searchCheckPart(partParams);
// 确保data是数组适配不同的后端返回格式
let partData = [];
if (partResponse) {
if (Array.isArray(partResponse)) {
partData = partResponse;
} else if (partResponse.data && Array.isArray(partResponse.data)) {
partData = partResponse.data;
} else if (partResponse.data && partResponse.data.data && Array.isArray(partResponse.data.data)) {
partData = partResponse.data.data;
} else if (partResponse.data && partResponse.data.records) {
partData = partResponse.data.records;
}
}
// 处理数组数据
if (partData.length > 0) {
partData.forEach((item, index) => {
checkPartData.push({
id: item.id, // 保存id字段用于判断是新增还是修改
row: (index + 1).toString(),
code: item.code,
name: item.name,
checkType: item.checkType || '',
exposureNum: item.exposureNum || 0,
packageName: item.packageName || '',
price: item.price || 0,
number: item.number || '999999',
serviceScope: item.serviceScope || '',
subType: item.subType || '',
remark: item.remark || '',
actions: true
});
});
ElMessage.success(`搜索到${partData.length}条检查部位数据`);
} else {
ElMessage.warning('未搜索到检查部位数据');
}
break;
default:
// 其他菜单使用原有的加载逻辑
await loadMenuData(activeMenu.value);
break;
}
} catch (error) {
console.error('搜索失败:', error);
ElMessage.error(`搜索${activeMenu.value}数据失败: ${error.message || '未知错误'}`);
}
}
// 处理重置功能
function handleReset() {
// 根据当前活动菜单重置对应的搜索条件
switch(activeMenu.value) {
case '检查类型':
for (const key in searchParamsType) {
searchParamsType[key] = '';
}
break;
case '检查方法':
searchParamsMethod.checkType = '';
searchParamsMethod.name = '';
searchParamsMethod.packageName = '';
break;
case '检查部位':
searchParamsPart.checkType = '';
searchParamsPart.name = '';
searchParamsPart.packageName = '';
break;
default:
break;
}
ElMessage.info('搜索条件已重置');
}
// 处理导出表格功能
function handleExport() {
console.log('导出表格:', activeMenu.value);
if (activeMenu.value === '检查方法') {
// 调用检查方法导出API
exportCheckMethod(searchParamsMethod).then(blobData => {
// 直接使用blobData创建下载链接因为response拦截器已经返回了res.data
const blob = new Blob([blobData], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// 设置文件名
link.setAttribute('download', `检查方法数据_${new Date().toISOString().slice(0, 10)}.xlsx`);
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
ElMessage.success('检查方法数据导出成功');
}).catch(error => {
console.error('导出检查方法数据失败:', error);
ElMessage.error('导出检查方法数据失败');
});
} else if (activeMenu.value === '检查部位') {
// 调用检查部位导出API
exportCheckPart(searchParamsPart).then(blobData => {
// 直接使用blobData创建下载链接因为response拦截器已经返回了res.data
const blob = new Blob([blobData], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// 设置文件名
link.setAttribute('download', `检查部位数据_${new Date().toISOString().slice(0, 10)}.xlsx`);
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
ElMessage.success('检查部位数据导出成功');
}).catch(error => {
console.error('导出检查部位数据失败:', error);
ElMessage.error('导出检查部位数据失败');
});
} else {
// 其他菜单的导出逻辑可以在这里扩展
ElMessage.warning('该功能尚未实现');
}
}
</script>
<style scoped>
select {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 14px;
padding-right: 28px;
}
.check-project-settings {
display: flex;
min-height: 100vh;
background-color: #f5f7fa;
color: #000000;
}
/* 左侧导航栏样式 */
.sidebar {
width: 200px;
background: linear-gradient(180deg, #f8f9fa 0%, #ffffff 100%);
height: 100%;
position: relative;
padding: 24px 0;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.08);
overflow-y: auto;
flex-shrink: 0;
border-right: 1px solid #e8e8e8;
}
.sidebar-header {
padding: 0 20px 20px 20px;
margin-bottom: 12px;
border-bottom: 2px solid #1890FF;
}
.sidebar-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1890FF;
letter-spacing: 0.5px;
}
.sidebar-menu {
display: flex;
flex-direction: column;
gap: 4px;
padding: 0 12px;
}
.menu-item {
display: flex;
align-items: center;
width: 100%;
padding: 14px 16px;
margin-bottom: 0;
background-color: transparent;
color: #333333;
border-radius: 8px;
text-decoration: none;
font-size: 15px;
font-weight: 500;
line-height: 1;
border: 1px solid transparent;
cursor: pointer;
text-align: left;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.menu-item::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 0;
background: linear-gradient(180deg, #1890FF 0%, #40a9ff 100%);
border-radius: 0 4px 4px 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.menu-item:hover {
background-color: rgba(24, 144, 255, 0.08);
border-color: rgba(24, 144, 255, 0.2);
transform: translateX(4px);
}
.menu-item:hover::before {
height: 20px;
}
.menu-item.active {
background: linear-gradient(135deg, #1890FF 0%, #40a9ff 100%);
color: #FFFFFF;
border-color: #1890FF;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
.menu-item.active::before {
height: 24px;
}
.menu-icon {
font-size: 18px;
margin-right: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
transition: transform 0.3s ease;
}
.menu-item:hover .menu-icon {
transform: scale(1.1);
}
.menu-text {
flex: 1;
letter-spacing: 0.3px;
}
/* 主内容区样式 */
.content {
flex: 1;
padding: 32px 32px 32px 32px;
min-width: 0;
overflow-x: auto;
overflow-y: auto;
background-color: #f8f9fa;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 20px 24px;
background: #ffffff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
border: 1px solid #e8e8e8;
}
.header h1 {
font-size: 22px;
font-weight: 600;
color: #1890FF;
margin: 0;
letter-spacing: 0.5px;
}
/* 搜索栏样式 */
.search-bar {
background-color: #FFFFFF;
padding: 20px 24px;
border-radius: 12px;
margin-bottom: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
border: 1px solid #e8e8e8;
display: flex;
align-items: center;
gap: 16px;
}
/* 检查方法搜索栏特定样式 */
.search-bar-method {
display: flex;
flex-direction: column;
gap: 16px;
}
.search-bar-method .search-filters {
display: flex;
align-items: center;
gap: 16px;
width: 100%;
}
.search-bar-method .search-actions {
margin-left: auto;
display: flex;
gap: 8px;
}
.search-item {
display: flex;
align-items: center;
gap: 8px;
}
.search-item label {
font-size: 14px;
color: #000000;
white-space: nowrap;
}
.search-item select,
.search-item input {
width: 150px;
height: 32px;
padding: 0 8px;
border: 1px solid #D9D9D9;
border-radius: 4px;
font-size: 14px;
}
.search-actions {
margin-left: auto;
display: flex;
gap: 8px;
}
.search-actions .btn {
width: auto;
height: 32px;
padding: 0 16px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
border: none;
transition: all 0.2s ease;
}
.btn-purple {
background-color: #722ED1;
color: white;
}
.btn-blue {
background-color: #1890FF;
color: white;
}
.btn-default {
background-color: #FFFFFF;
color: #000000;
border: 1px solid #D9D9D9;
}
.btn-green {
background-color: #52C41A;
color: white;
}
.search-actions .btn:hover {
opacity: 0.85;
}
.header-actions {
display: flex;
gap: 10px;
}
.btn-add-new {
background-color: #409eff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.btn-add-new:hover {
background-color: #66b1ff;
}
/* 表格样式 */
.table-container {
width: 100%;
overflow-x: auto;
background: #FFFFFF;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
border: 1px solid #e8e8e8;
}
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
table-layout: fixed;
min-width: 1200px;
}
th {
background: linear-gradient(180deg, #f8f9fa 0%, #fafbfc 100%);
height: 48px;
padding: 12px 16px;
font-size: 14px;
font-weight: 600;
line-height: 24px;
text-align: center;
border-bottom: 2px solid #e8e8e8;
color: #1890FF;
letter-spacing: 0.3px;
}
td {
padding: 8px 16px;
height: 40px;
font-size: 14px;
font-weight: 400;
line-height: 24px;
border-bottom: 1px solid #e8e8e8;
text-align: center;
}
/* 操作按钮样式 */
.actions {
display: flex;
justify-content: center;
gap: 6px;
position: relative;
z-index: 10;
}
.btn {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 14px;
font-weight: bold;
position: relative;
z-index: 10;
color: white;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
}
.btn:hover {
transform: translateY(-2px) scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
}
.btn:active {
transform: translateY(0) scale(0.95);
}
.btn-confirm {
background: linear-gradient(135deg, #52C41A 0%, #73d13d 100%);
}
.btn-confirm:hover {
background: linear-gradient(135deg, #389E0D 0%, #52C41A 100%);
}
.btn-edit {
background: linear-gradient(135deg, #1890FF 0%, #40a9ff 100%);
}
.btn-edit:hover {
background: linear-gradient(135deg, #096DD9 0%, #1890FF 100%);
}
.btn-add {
background: linear-gradient(135deg, #1890FF 0%, #40a9ff 100%);
}
.btn-add:hover {
background: linear-gradient(135deg, #096DD9 0%, #1890FF 100%);
}
.btn-cancel {
background: linear-gradient(135deg, #FA8C16 0%, #ffa940 100%);
}
.btn-cancel:hover {
background: linear-gradient(135deg, #D46B08 0%, #FA8C16 100%);
}
.btn-delete {
background: linear-gradient(135deg, #FF4D4F 0%, #ff7875 100%);
z-index: 20;
pointer-events: auto;
}
.btn-delete:hover {
background: linear-gradient(135deg, #CF1322 0%, #FF4D4F 100%);
}
/* 特殊状态样式 */
.editing-row {
background-color: #E6F7FF;
cursor: text;
}
tr:not(.editing-row):hover {
background-color: rgba(24, 144, 255, 0.05);
cursor: pointer;
}
.child-row {
background-color: #f9f9f9;
}
.child-row td:first-child {
padding-left: 32px;
}
.checkbox-container {
display: flex;
justify-content: center;
}
input[type="checkbox"] {
width: 16px;
height: 16px;
}
input[type="text"], select {
height: 30px;
padding: 0 8px;
width: 100%;
border: 1px solid #D9D9D9;
border-radius: 4px;
font-size: 14px;
background-color: white;
color: #333;
}
input[type="text"]::placeholder {
color: #C0C4CC;
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 16px 0;
margin-top: 8px;
}
.pagination span {
font-size: 14px;
margin: 0 16px;
}
.pagination-btn {
background: none;
border: 1px solid #D9D9D9;
border-radius: 4px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.pagination-btn:hover {
border-color: #1890FF;
color: #1890FF;
}
/* 禁用状态样式 */
.placeholder-text {
color: #C0C4CC;
}
/* 响应式设计 */
@media (max-width: 768px) {
.sidebar {
width: 70px;
padding: 16px 8px;
}
.sidebar-header {
padding: 0 8px 16px 8px;
}
.sidebar-header h3 {
font-size: 14px;
text-align: center;
}
.menu-text {
display: none;
}
.menu-icon {
margin-right: 0;
}
.menu-item {
justify-content: center;
padding: 12px;
}
.content {
padding: 20px 16px;
}
}
/* 平板适配 */
@media (min-width: 769px) and (max-width: 1024px) {
.sidebar {
width: 160px;
}
.content {
padding: 24px 20px;
}
}
</style>