1083 lines
28 KiB
Vue
1083 lines
28 KiB
Vue
<template>
|
||
<!-- 调试信息:如果看到这个注释,说明模板已加载 -->
|
||
<div class="package-management" @vue:mounted="console.log('PackageManagement 模板已挂载')">
|
||
<!-- 左侧导航栏 -->
|
||
<nav class="sidebar" :class="{ active: sidebarActive }">
|
||
<div class="sidebar-item" @click="navigateToTab(0)">检验类型</div>
|
||
<div class="sidebar-item" @click="navigateToTab(1)">检验项目</div>
|
||
<div class="sidebar-item active" @click="navigateToTab(2)">套餐设置</div>
|
||
</nav>
|
||
|
||
<!-- 主内容区域 -->
|
||
<main class="content">
|
||
<!-- 导航切换按钮(响应式) -->
|
||
<div class="menu-toggle" @click="toggleSidebar">
|
||
<i class="fas fa-bars"></i>
|
||
</div>
|
||
|
||
<!-- 查询过滤区域 -->
|
||
<section class="filter-bar">
|
||
<div class="filter-item">
|
||
<label>日期:</label>
|
||
<input type="date" v-model="searchParams.startDate" placeholder="开始日期">
|
||
<span>至</span>
|
||
<input type="date" v-model="searchParams.endDate" placeholder="结束日期">
|
||
</div>
|
||
<div class="filter-item">
|
||
<label>卫生机构:</label>
|
||
<el-select
|
||
v-model="selectedTenantId"
|
||
placeholder="请选择机构"
|
||
style="width: 150px;"
|
||
clearable
|
||
@change="handleSearch"
|
||
>
|
||
<el-option
|
||
v-for="item in tenantOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</div>
|
||
<div class="filter-item">
|
||
<label>套餐名称:</label>
|
||
<input type="text" v-model="searchParams.packageName" placeholder="套餐名称">
|
||
</div>
|
||
<div class="filter-item">
|
||
<label>套餐级别:</label>
|
||
<select v-model="searchParams.packageLevel">
|
||
<option value="">请选择套餐级别</option>
|
||
<option value="全院套餐">全院套餐</option>
|
||
<option value="科室套餐">科室套餐</option>
|
||
<option value="个人套餐">个人套餐</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-item">
|
||
<label>套餐类别:</label>
|
||
<select>
|
||
<option>检验套餐</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-item filter-item-department">
|
||
<label>科室:</label>
|
||
<el-tree-select
|
||
v-model="searchParams.department"
|
||
placeholder="请选择科室"
|
||
:data="departments"
|
||
:props="{
|
||
value: 'name',
|
||
label: 'name',
|
||
children: 'children'
|
||
}"
|
||
value-key="name"
|
||
check-strictly
|
||
:expand-on-click-node="false"
|
||
clearable
|
||
style="width: 200px;"
|
||
/>
|
||
</div>
|
||
<div class="filter-item">
|
||
<label>用户:</label>
|
||
<select></select>
|
||
</div>
|
||
|
||
<!-- 操作按钮组 -->
|
||
<div class="button-group">
|
||
<button class="btn btn-search" @click="handleSearch"><i class="fas fa-search" style="margin-right: 6px;"></i>查询</button>
|
||
<button class="btn btn-reset" @click="handleReset"><i class="fas fa-redo" style="margin-right: 6px;"></i>重置</button>
|
||
<button class="btn btn-primary" @click="handleAdd"><i class="fas fa-plus" style="margin-right: 6px;"></i>新增</button>
|
||
<button class="btn btn-export" @click="handleExport"><i class="fas fa-download" style="margin-right: 6px;"></i>导出</button>
|
||
<button class="btn btn-copy" @click="returnToPackageSetup"><i class="fas fa-arrow-left" style="margin-right: 6px;"></i>返回</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 表格区域 -->
|
||
<section class="table-container">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>卫生机构</th>
|
||
<th>日期</th>
|
||
<th>套餐名称</th>
|
||
<th>套餐类别</th>
|
||
<th>套餐级别</th>
|
||
<th>科室</th>
|
||
<th>用户</th>
|
||
<th class="text-right">金额</th>
|
||
<th class="text-right">服务费</th>
|
||
<th class="text-right">总金额</th>
|
||
<th>组合套餐</th>
|
||
<th>显示套餐名</th>
|
||
<th>启用标志</th>
|
||
<th>操作人</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="item in filteredData" :key="item.id">
|
||
<td>{{ item.id }}</td>
|
||
<td>{{ item.hospital }}</td>
|
||
<td>{{ item.date }}</td>
|
||
<td>{{ item.name }}</td>
|
||
<td>{{ item.type }}</td>
|
||
<td>{{ item.level }}</td>
|
||
<td>{{ item.dept || '-' }}</td>
|
||
<td>{{ item.user || '-' }}</td>
|
||
<td class="text-right">{{ item.amount.toFixed(2) }}</td>
|
||
<td class="text-right">{{ item.fee.toFixed(2) }}</td>
|
||
<td class="text-right">{{ item.total.toFixed(2) }}</td>
|
||
<td><span class="status-tag" :class="item.combined === '是' ? 'status-no' : 'status-yes'">{{ item.combined }}</span></td>
|
||
<td><span class="status-tag" :class="item.display === '是' ? 'status-yes' : 'status-no'">{{ item.display }}</span></td>
|
||
<td><span class="status-tag" :class="item.enabled === '是' ? 'status-yes' : 'status-no'">{{ item.enabled }}</span></td>
|
||
<td>{{ item.operator }}</td>
|
||
<td class="action-cell">
|
||
<div class="action-btns">
|
||
<button class="action-btn edit-btn" @click="handleEdit(item)">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
||
</svg>
|
||
</button>
|
||
<button class="action-btn view-btn" @click="handleView(item)">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||
<circle cx="12" cy="12" r="3"></circle>
|
||
</svg>
|
||
</button>
|
||
<button class="action-btn delete-btn" @click="handleDelete(item)">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="3 6 5 6 21 6"></polyline>
|
||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
|
||
<!-- 分页组件 -->
|
||
<div class="pagination">
|
||
<button
|
||
class="page-btn"
|
||
:disabled="currentPage === 1"
|
||
@click="handlePrevPage"
|
||
><</button>
|
||
<button
|
||
v-for="page in pageButtons"
|
||
:key="page"
|
||
class="page-btn"
|
||
:class="{ active: page === currentPage }"
|
||
:disabled="page === '...'"
|
||
@click="handlePageChange(page)"
|
||
>{{ page }}</button>
|
||
<button
|
||
class="page-btn"
|
||
:disabled="currentPage >= totalPages || totalPages <= 1"
|
||
@click="handleNextPage"
|
||
>></button>
|
||
<div class="total-count">总数:{{ total }}</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {computed, onMounted, ref} from 'vue';
|
||
import {useRouter} from 'vue-router';
|
||
import {ElMessage} from 'element-plus';
|
||
import {getLocationTree} from '@/views/charge/outpatientregistration/components/outpatientregistration';
|
||
import {listInspectionPackage} from '@/api/system/inspectionPackage';
|
||
import { getTenantPage } from '@/api/system/tenant';
|
||
|
||
|
||
// 创建路由实例
|
||
const router = useRouter();
|
||
|
||
// 侧边栏状态
|
||
const sidebarActive = ref(false);
|
||
|
||
// 科室数据
|
||
const departments = ref([]);
|
||
|
||
// 获取科室数据 - 与门诊挂号页面保持一致
|
||
function getDepartmentList() {
|
||
console.log('调用getLocationTree API...');
|
||
getLocationTree().then((response) => {
|
||
|
||
|
||
// 检查数据结构并转换为适合el-tree-select的格式
|
||
if (Array.isArray(response.data)) {
|
||
// 直接使用数组数据
|
||
departments.value = response.data;
|
||
} else if (response.data && response.data.records) {
|
||
// 处理分页格式数据
|
||
departments.value = response.data.records;
|
||
} else if (response.data && response.data.rows) {
|
||
// 处理另一种分页格式数据
|
||
departments.value = response.data.rows;
|
||
} else {
|
||
|
||
departments.value = [];
|
||
}
|
||
|
||
|
||
}).catch((error) => {
|
||
departments.value = [];
|
||
});
|
||
}
|
||
|
||
// 获取科室数据 - 与门诊挂号页面保持一致,在组件初始化时直接调用
|
||
getDepartmentList();
|
||
|
||
// 表格数据 - 从API获取
|
||
const tableData = ref([])
|
||
const loading = ref(false)
|
||
const total = ref(0)
|
||
|
||
// 分页参数
|
||
const currentPage = ref(1)
|
||
const pageSize = ref(10)
|
||
|
||
// 获取当前日期的函数,格式为YYYY-MM-DD
|
||
function getCurrentDate() {
|
||
const today = new Date();
|
||
const year = today.getFullYear();
|
||
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||
const day = String(today.getDate()).padStart(2, '0');
|
||
return `${year}-${month}-${day}`;
|
||
}
|
||
|
||
// 搜索参数
|
||
const searchParams = ref({
|
||
startDate: getCurrentDate(),
|
||
endDate: getCurrentDate(),
|
||
packageName: '',
|
||
packageLevel: '',
|
||
department: ''
|
||
});
|
||
|
||
// 从API加载数据
|
||
async function loadData() {
|
||
try {
|
||
|
||
loading.value = true
|
||
|
||
// 构建查询参数(匹配后端 InspectionPackage 实体字段)
|
||
const params = {
|
||
pageNum: currentPage.value,
|
||
pageSize: pageSize.value,
|
||
packageCategory: '检验套餐' // InspectionPackage 使用 packageCategory 而不是 packageType
|
||
}
|
||
|
||
// 处理日期范围(后端可能不支持日期范围查询,先保留)
|
||
// if (searchParams.value.startDate) {
|
||
// params.startDate = searchParams.value.startDate
|
||
// }
|
||
// if (searchParams.value.endDate) {
|
||
// params.endDate = searchParams.value.endDate
|
||
// }
|
||
if (selectedTenantId.value) {
|
||
params.tenantId = selectedTenantId.value;
|
||
}
|
||
|
||
if (searchParams.value.packageName) {
|
||
params.packageName = searchParams.value.packageName
|
||
}
|
||
if (searchParams.value.packageLevel) {
|
||
params.packageLevel = searchParams.value.packageLevel
|
||
}
|
||
if (searchParams.value.department) {
|
||
params.department = searchParams.value.department
|
||
}
|
||
|
||
|
||
const response = await listInspectionPackage(params)
|
||
|
||
|
||
if (response) {
|
||
// 处理不同的响应格式(优先按若依风格:response.rows / response.total)
|
||
let dataList = []
|
||
let totalCount = 0
|
||
|
||
// 优先检查 response.data(axios 包装的响应)
|
||
if (response.data) {
|
||
const data = response.data
|
||
|
||
if (Array.isArray(data.rows)) {
|
||
// TableDataInfo 格式:{ rows: [], total: 0 }
|
||
dataList = data.rows
|
||
totalCount = data.total || 0
|
||
|
||
} else if (Array.isArray(data.records)) {
|
||
// MyBatis Plus 分页格式
|
||
dataList = data.records
|
||
totalCount = data.total || 0
|
||
|
||
} else if (Array.isArray(data)) {
|
||
// 直接是数组
|
||
dataList = data
|
||
totalCount = data.length
|
||
|
||
}
|
||
} else if (Array.isArray(response.rows)) {
|
||
// 直接是 TableDataInfo 格式
|
||
dataList = response.rows
|
||
totalCount = response.total || 0
|
||
|
||
} else if (Array.isArray(response)) {
|
||
// 直接返回数组
|
||
dataList = response
|
||
totalCount = response.length
|
||
|
||
}
|
||
|
||
total.value = totalCount
|
||
|
||
|
||
// 转换数据格式以匹配前端显示(InspectionPackage 字段映射)
|
||
|
||
tableData.value = (dataList || []).map(item => {
|
||
const mappedItem = {
|
||
id: item.basicInformationId || item.id, // 优先使用 basicInformationId
|
||
packageId: item.packageId || item.basicInformationId || item.id, // 保存packageId用于跳转
|
||
basicInformationId: item.basicInformationId, // 保存原始basicInformationId
|
||
// 兼容旧逻辑:有些地方把主键当成 departmentId 使用(注意:真正的科室ID仍在后端字段 departmentId 上)
|
||
departmentId: item.basicInformationId || item.id || item.packageId,
|
||
hospital: item.orgName || '演示医院',
|
||
date: item.createTime ? (item.createTime.split('T')[0] || item.createTime.substring(0, 10)) : '',
|
||
name: item.packageName || '',
|
||
type: item.packageCategory || '检验套餐',
|
||
level: item.packageLevel || '',
|
||
dept: item.department || '',
|
||
user: item.userId || '',
|
||
amount: parseFloat(item.packageAmount || 0),
|
||
fee: parseFloat(item.serviceFee || 0),
|
||
total: parseFloat(item.packageAmount || 0) + parseFloat(item.serviceFee || 0),
|
||
combined: item.enablePackagePrice === true ? '是' : '否',
|
||
display: item.showPackageName === true ? '是' : '否',
|
||
enabled: item.isDisabled === true ? '否' : '是',
|
||
operator: item.createBy || ''
|
||
}
|
||
|
||
return mappedItem
|
||
})
|
||
|
||
|
||
} else {
|
||
tableData.value = []
|
||
total.value = 0
|
||
|
||
}
|
||
} catch (error) {
|
||
|
||
ElMessage.error('加载数据失败: ' + (error.message || '未知错误'))
|
||
tableData.value = []
|
||
total.value = 0
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const selectedTenantId = ref(null); // 绑定下拉框的值
|
||
const tenantOptions = ref([]); // 存储机构选项
|
||
const loadingTenant = ref(false);
|
||
|
||
// 获取机构列表函数
|
||
const fetchTenantList = async () => {
|
||
if (loadingTenant.value) return;
|
||
loadingTenant.value = true;
|
||
|
||
try {
|
||
const response = await getTenantPage({ pageNum: 1, pageSize: 100 });
|
||
|
||
if (response.code !== 200) throw new Error(response.msg || '获取机构列表失败');
|
||
|
||
let tenantData = [];
|
||
const data = response.data;
|
||
if (Array.isArray(data)) {
|
||
tenantData = data;
|
||
} else if (data && typeof data === 'object') {
|
||
tenantData = data.records || data.rows || data.list || [];
|
||
}
|
||
|
||
// 过滤启用的机构
|
||
const activeTenants = (Array.isArray(tenantData) ? tenantData : []).filter(item => item && item.status === "0");
|
||
|
||
// 生成下拉选项
|
||
tenantOptions.value = activeTenants.map(item => ({
|
||
value: item.id,
|
||
label: item.tenantName || item.name || item.orgName || String(item.id)
|
||
}));
|
||
|
||
// 默认选中 ID=1 或第一个
|
||
if (activeTenants.length > 0) {
|
||
const zhonglianHospital = activeTenants.find(item => item.id === 1);
|
||
selectedTenantId.value = zhonglianHospital ? 1 : activeTenants[0].id;
|
||
}
|
||
} catch (error) {
|
||
console.error('获取机构列表失败:', error);
|
||
tenantOptions.value = [];
|
||
} finally {
|
||
loadingTenant.value = false;
|
||
}
|
||
};
|
||
// 初始化数据
|
||
// 整合后的 onMounted 钩子
|
||
onMounted(async () => {
|
||
// 1. 加载科室数据
|
||
getDepartmentList();
|
||
|
||
// 2. 加载机构列表 (包含默认选中逻辑)
|
||
await fetchTenantList();
|
||
|
||
// 3. 等待 DOM 更新
|
||
await nextTick();
|
||
|
||
// 4. 防御性检查
|
||
if (tenantOptions.value.length > 0 && !selectedTenantId.value) {
|
||
selectedTenantId.value = tenantOptions.value[0].value;
|
||
}
|
||
|
||
// 5. 加载表格数据
|
||
loadData();
|
||
|
||
});
|
||
|
||
// 过滤后的数据 - 现在直接从API获取,这里保留前端过滤作为补充
|
||
const filteredData = computed(() => {
|
||
return tableData.value
|
||
});
|
||
|
||
// 返回套餐设置主界面
|
||
function returnToPackageSetup() {
|
||
router.push('/maintainSystem/Inspection?tab=2');
|
||
}
|
||
|
||
// 导航到指定标签页
|
||
function navigateToTab(tabIndex) {
|
||
router.push(`/maintainSystem/Inspection?tab=${tabIndex}`);
|
||
}
|
||
|
||
// 切换侧边栏
|
||
function toggleSidebar() {
|
||
sidebarActive.value = !sidebarActive.value;
|
||
}
|
||
|
||
// 处理查询
|
||
function handleSearch() {
|
||
currentPage.value = 1; // 搜索时重置到第一页
|
||
loadData()
|
||
}
|
||
|
||
// 处理重置
|
||
function handleReset() {
|
||
searchParams.value = {
|
||
startDate: getCurrentDate(),
|
||
endDate: getCurrentDate(),
|
||
packageName: '',
|
||
packageLevel: '',
|
||
department: ''
|
||
};
|
||
currentPage.value = 1; // 重置到第一页
|
||
loadData()
|
||
}
|
||
|
||
// 计算总页数
|
||
const totalPages = computed(() => {
|
||
const pages = Math.ceil(total.value / pageSize.value)
|
||
|
||
return pages > 0 ? pages : 1 // 至少返回1页
|
||
})
|
||
|
||
// 处理分页 - 上一页
|
||
function handlePrevPage() {
|
||
if (currentPage.value > 1) {
|
||
currentPage.value--
|
||
loadData()
|
||
}
|
||
}
|
||
|
||
// 处理分页 - 下一页
|
||
function handleNextPage() {
|
||
|
||
if (currentPage.value < totalPages.value) {
|
||
currentPage.value++
|
||
|
||
loadData()
|
||
} else {
|
||
|
||
}
|
||
}
|
||
|
||
// 处理分页 - 跳转到指定页
|
||
function handlePageChange(page) {
|
||
|
||
if (page === '...') {
|
||
return // 省略号不可点击
|
||
}
|
||
if (page >= 1 && page <= totalPages.value && page !== currentPage.value) {
|
||
currentPage.value = page
|
||
|
||
loadData()
|
||
} else {
|
||
|
||
}
|
||
}
|
||
|
||
// 生成分页按钮数组
|
||
const pageButtons = computed(() => {
|
||
const buttons = []
|
||
const total = totalPages.value
|
||
const current = currentPage.value
|
||
|
||
|
||
|
||
if (total <= 0) {
|
||
// 如果没有数据,至少显示第1页
|
||
buttons.push(1)
|
||
return buttons
|
||
}
|
||
|
||
if (total <= 7) {
|
||
// 如果总页数小于等于7,显示所有页码
|
||
for (let i = 1; i <= total; i++) {
|
||
buttons.push(i)
|
||
}
|
||
} else {
|
||
// 如果总页数大于7,显示省略号
|
||
if (current <= 4) {
|
||
// 当前页在前4页
|
||
for (let i = 1; i <= 5; i++) {
|
||
buttons.push(i)
|
||
}
|
||
buttons.push('...')
|
||
buttons.push(total)
|
||
} else if (current >= total - 3) {
|
||
// 当前页在后4页
|
||
buttons.push(1)
|
||
buttons.push('...')
|
||
for (let i = total - 4; i <= total; i++) {
|
||
buttons.push(i)
|
||
}
|
||
} else {
|
||
// 当前页在中间
|
||
buttons.push(1)
|
||
buttons.push('...')
|
||
for (let i = current - 1; i <= current + 1; i++) {
|
||
buttons.push(i)
|
||
}
|
||
buttons.push('...')
|
||
buttons.push(total)
|
||
}
|
||
}
|
||
|
||
|
||
return buttons
|
||
})
|
||
|
||
// 处理新增
|
||
function handleAdd() {
|
||
router.push('/maintainSystem/Inspection?tab=2');
|
||
}
|
||
|
||
// 处理编辑
|
||
function handleEdit(item) {
|
||
// 跳转到套餐设置主界面,并传递套餐ID用于加载数据
|
||
// 后端接口使用 basicInformationId 作为路径参数
|
||
const packageId = item.basicInformationId || item.id;
|
||
if (!packageId) {
|
||
ElMessage.error('无法获取套餐ID,请刷新页面后重试');
|
||
return;
|
||
}
|
||
router.push({
|
||
path: '/maintainSystem/Inspection',
|
||
query: {
|
||
tab: '2',
|
||
packageId: packageId,
|
||
mode: 'edit'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 处理查看
|
||
function handleView(item) {
|
||
// 跳转到套餐设置主界面,并传递套餐ID用于加载数据
|
||
// 后端接口使用 basicInformationId 作为路径参数
|
||
const packageId = item.basicInformationId || item.id;
|
||
if (!packageId) {
|
||
ElMessage.error('无法获取套餐ID,请刷新页面后重试');
|
||
return;
|
||
}
|
||
router.push({
|
||
path: '/maintainSystem/Inspection',
|
||
query: {
|
||
tab: '2',
|
||
packageId: packageId,
|
||
mode: 'view'
|
||
}
|
||
});
|
||
}
|
||
|
||
// 处理删除
|
||
function handleDelete(item) {
|
||
if (confirm(`确定要删除套餐 "${item.name}" 吗?`)) {
|
||
const index = tableData.value.findIndex(i => i.id === item.id);
|
||
if (index !== -1) {
|
||
tableData.value.splice(index, 1);
|
||
alert(`套餐 "${item.name}" 已删除`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理导出
|
||
function handleExport() {
|
||
// 获取当前筛选后的数据
|
||
const dataToExport = filteredData.value;
|
||
|
||
// 转换为CSV格式
|
||
const csvContent = convertToCSV(dataToExport);
|
||
|
||
// 创建下载链接
|
||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||
const url = URL.createObjectURL(blob);
|
||
const link = document.createElement('a');
|
||
link.setAttribute('href', url);
|
||
link.setAttribute('download', `套餐数据_${new Date().toISOString().slice(0,10)}.csv`);
|
||
link.style.visibility = 'hidden';
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
}
|
||
|
||
// 将数据转换为CSV格式的函数
|
||
function convertToCSV(data) {
|
||
if (data.length === 0) return '';
|
||
|
||
// 获取表头
|
||
const headers = Object.keys(data[0]);
|
||
|
||
// 构建CSV内容
|
||
let csv = headers.join(',') + '\n';
|
||
|
||
// 添加数据行
|
||
data.forEach(row => {
|
||
const values = headers.map(header => {
|
||
// 处理可能包含逗号的值
|
||
const value = row[header] === null || row[header] === undefined ? '' : row[header];
|
||
return `"${value.toString().replace(/"/g, '""')}"`;
|
||
});
|
||
csv += values.join(',') + '\n';
|
||
});
|
||
|
||
return csv;
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
:root {
|
||
--background: #FFFFFF;
|
||
--primary: #5C8DFF;
|
||
--primary-hover: #7DA3FF;
|
||
--text-primary: #333333;
|
||
--text-secondary: #666666;
|
||
--text-disabled: #CCCCCC;
|
||
--danger: #FF6B6B;
|
||
--border: #E0E0E0;
|
||
--header-bg: #F5F5F5;
|
||
--row-hover: #FAFAFA;
|
||
--table-border: #E8E8E8;
|
||
--sidebar-width: 200px;
|
||
--content-padding: 24px;
|
||
--secondary: #8E8E93;
|
||
--secondary-hover: #A7A7AB;
|
||
--success: #4CAF50;
|
||
--info: #2196F3;
|
||
--btn-search: #6c757d;
|
||
--btn-reset: #17a2b8;
|
||
--btn-add: #28a745;
|
||
--btn-export: #17a2b8;
|
||
--btn-copy: #6c757d;
|
||
}
|
||
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||
}
|
||
|
||
/* 移除body样式,避免影响其他页面 */
|
||
|
||
.package-management {
|
||
display: flex;
|
||
height: 100vh;
|
||
background-color: var(--background);
|
||
color: var(--text-primary);
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 左侧导航栏 */
|
||
.sidebar {
|
||
width: var(--sidebar-width);
|
||
background-color: var(--background);
|
||
border-right: 1px solid var(--border);
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.sidebar-item {
|
||
padding: 16px 24px;
|
||
font-size: 14px;
|
||
color: var(--text-primary);
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.sidebar-item:hover {
|
||
background-color: #f0f8ff;
|
||
}
|
||
|
||
.sidebar-item.active {
|
||
background-color: var(--primary);
|
||
color: white;
|
||
}
|
||
|
||
/* 主内容区域 */
|
||
.content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 查询过滤栏 */
|
||
.filter-bar {
|
||
padding: 16px var(--content-padding);
|
||
border-bottom: 1px solid var(--border);
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.filter-item-department {
|
||
min-width: 280px;
|
||
}
|
||
|
||
.filter-item-department .el-tree-select {
|
||
width: 100%;
|
||
}
|
||
|
||
.filter-item label {
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
input, select {
|
||
height: 32px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
padding: 0 8px;
|
||
font-size: 14px;
|
||
min-width: 120px;
|
||
}
|
||
|
||
input:focus, select:focus {
|
||
outline: none;
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
/* 按钮样式 */
|
||
.btn {
|
||
padding: 6px 16px;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
height: 36px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
border: none;
|
||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||
min-width: 80px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: var(--primary);
|
||
color: white;
|
||
border: 1px solid var(--primary);
|
||
box-shadow: 0 2px 4px rgba(92, 141, 255, 0.2);
|
||
}
|
||
|
||
.btn-text {
|
||
background-color: #F5F7FA;
|
||
color: var(--secondary);
|
||
border: 1px solid var(--border);
|
||
}
|
||
|
||
.btn-search {
|
||
background-color: var(--btn-search);
|
||
color: white;
|
||
}
|
||
|
||
.btn-reset {
|
||
background-color: var(--btn-reset);
|
||
color: white;
|
||
}
|
||
|
||
.btn-export {
|
||
background-color: var(--btn-export);
|
||
color: white;
|
||
}
|
||
|
||
.btn-copy {
|
||
background-color: var(--btn-copy);
|
||
color: white;
|
||
}
|
||
|
||
.btn-text:hover {
|
||
background-color: #EBEEF2;
|
||
color: var(--text-primary);
|
||
border-color: var(--secondary-hover);
|
||
}
|
||
|
||
.btn-danger {
|
||
background-color: var(--danger);
|
||
color: white;
|
||
}
|
||
|
||
.btn:hover {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background-color: var(--primary-hover);
|
||
border-color: var(--primary-hover);
|
||
}
|
||
|
||
.button-group {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-left: auto;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
/* 表格区域 */
|
||
.table-container {
|
||
flex: 1;
|
||
overflow: auto;
|
||
padding: 0 var(--content-padding) var(--content-padding);
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 14px;
|
||
}
|
||
|
||
th, td {
|
||
padding: 8px;
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--table-border);
|
||
}
|
||
|
||
th {
|
||
background-color: var(--header-bg);
|
||
font-weight: 500;
|
||
position: sticky;
|
||
top: 0;
|
||
}
|
||
|
||
tr:hover {
|
||
background-color: var(--row-hover);
|
||
}
|
||
|
||
.text-right {
|
||
text-align: right;
|
||
}
|
||
|
||
/* 状态标签 */
|
||
.status-tag {
|
||
padding: 0 8px;
|
||
border-radius: 2px;
|
||
font-size: 12px;
|
||
height: 20px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.status-yes {
|
||
background-color: #f6ffed;
|
||
border: 1px solid #b7eb8f;
|
||
color: #389e0d;
|
||
}
|
||
|
||
.status-no {
|
||
background-color: #fff2f0;
|
||
border: 1px solid #ffccc7;
|
||
color: var(--danger);
|
||
}
|
||
|
||
/* 操作列 */
|
||
.action-cell {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
position: relative;
|
||
z-index: 10;
|
||
}
|
||
|
||
.action-btns {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.action-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
border: none;
|
||
transition: all 0.2s ease;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
position: relative;
|
||
z-index: 10;
|
||
background-color: #66b1ff;
|
||
color: white;
|
||
}
|
||
|
||
.edit-btn {
|
||
background-color: #FFC107;
|
||
color: white;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.view-btn {
|
||
background-color: #1890FF;
|
||
color: white;
|
||
}
|
||
|
||
.delete-btn {
|
||
background-color: #FF4D4F;
|
||
color: white;
|
||
font-size: 14px;
|
||
z-index: 20;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
/* 分页样式 */
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 16px 0;
|
||
gap: 8px;
|
||
}
|
||
|
||
.page-btn {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border);
|
||
background-color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.page-btn:hover:not(:disabled) {
|
||
background-color: #f0f8ff;
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.page-btn:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.page-btn:not(:disabled) {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.page-btn.active {
|
||
background-color: var(--primary);
|
||
color: white;
|
||
border: none;
|
||
}
|
||
|
||
.total-count {
|
||
margin-left: 16px;
|
||
font-size: 14px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 1200px) {
|
||
.sidebar {
|
||
transform: translateX(calc(-1 * var(--sidebar-width)));
|
||
position: absolute;
|
||
z-index: 100;
|
||
transition: transform 0.3s;
|
||
}
|
||
|
||
.sidebar.active {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.menu-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 40px;
|
||
width: 40px;
|
||
position: absolute;
|
||
top: 16px;
|
||
left: 16px;
|
||
z-index: 101;
|
||
cursor: pointer;
|
||
background: white;
|
||
border: 1px solid var(--border);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.filter-bar {
|
||
padding-top: 64px;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<style scoped>
|
||
/* 移除未使用的图标样式 */
|
||
</style>
|
||
|
||
<style scoped>
|
||
/* 移除自动生成的未使用样式 */
|
||
</style> |