<template>
|
<div class="app-container">
|
<!-- 页面标题 -->
|
<div class="page-header">
|
<div class="page-header-left">
|
<h1>称重盒子维护</h1>
|
</div>
|
<div class="header-actions">
|
|
<n-button type="primary" @click="handleAdd">
|
+ 新增盒子
|
</n-button>
|
</div>
|
</div>
|
|
<!-- 搜索栏 -->
|
<div class="search-bar">
|
<n-input v-model:value="searchParams.name" placeholder="搜索名称、编号、位置..." style="width: 250px;" />
|
|
<n-button type="primary" @click="handleSearch" style="width: 80px;">
|
搜索
|
</n-button>
|
<!-- 快捷操作按钮 -->
|
<div class="quick-actions">
|
<!-- <n-button @click="handleBatchCalibrate" >
|
<span class="action-icon">🔧</span> 批量校准 <n-tag class="action-tag" size="small" type="error">{{ statistics.overdue || 0 }}</n-tag>
|
</n-button> -->
|
<n-button @click="handleBatchConfig('all')" >
|
<span class="action-icon">📐</span> 批量配置校准周期
|
</n-button>
|
<!-- <n-button @click="handleBatchActivate" >
|
<span class="action-icon">✅</span> 批量启用
|
</n-button>
|
<n-button @click="handleBatchDeactivate" >
|
<span class="action-icon">❌</span> 批量停用
|
</n-button> -->
|
<n-button @click="filterByCalibStatus('overdue')" >
|
<span class="action-icon">🚨</span> 仅看逾期 <n-tag class="action-tag" size="small" type="error">{{ statistics.overdue || 0 }}</n-tag>
|
</n-button>
|
<n-button @click="filterByCalibStatus('warning')" >
|
<span class="action-icon">⚠️</span> 即将到期 <n-tag class="action-tag" size="small" type="warning">{{ statistics.warning || 0 }}</n-tag>
|
</n-button>
|
<!-- <n-button @click="handleCopy" >
|
<span class="action-icon">📋</span> 复制盒子
|
</n-button> -->
|
<n-button @click="resetSearch" >
|
<span class="action-icon">🔄</span> 重置筛选
|
</n-button>
|
</div>
|
</div>
|
|
<!-- 统计卡片 -->
|
<div class="stats-container">
|
<div class="stat-card total" @click="filterByType('total')">
|
<div class="stat-header">
|
<div class="stat-icon">📦</div>
|
<div class="stat-title">盒子总数</div>
|
</div>
|
<div class="stat-value">{{ statistics.total || 0 }}</div>
|
<div class="stat-sub">已启用 {{ statistics.active || 0 }} · 已停用 {{ statistics.inactive || 0 }}</div>
|
</div>
|
<div class="stat-card normal" @click="filterByCalibStatus('normal')">
|
<div class="stat-header">
|
<div class="stat-icon">✅</div>
|
<div class="stat-title">正常</div>
|
</div>
|
<div class="stat-value">{{ statistics.normal || 0 }}</div>
|
<div class="stat-sub">距离校准 > 7天</div>
|
</div>
|
<div class="stat-card warning" @click="filterByCalibStatus('warning')">
|
<div class="stat-header">
|
<div class="stat-icon">⚠️</div>
|
<div class="stat-title">即将到期</div>
|
</div>
|
<div class="stat-value">{{ statistics.warning || 0 }}</div>
|
<div class="stat-sub">7天内需校准</div>
|
</div>
|
<div class="stat-card overdue" @click="filterByCalibStatus('overdue')">
|
<div class="stat-header">
|
<div class="stat-icon">🚨</div>
|
<div class="stat-title">已逾期</div>
|
</div>
|
<div class="stat-value">{{ statistics.overdue || 0 }}</div>
|
<div class="stat-sub">校准已过期未完成</div>
|
</div>
|
<div class="stat-card inactive" @click="filterByActiveStatus(0)">
|
<div class="stat-header">
|
<div class="stat-icon">❌</div>
|
<div class="stat-title">已停用</div>
|
</div>
|
<div class="stat-value">{{ statistics.inactive || 0 }}</div>
|
<div class="stat-sub">暂不使用</div>
|
</div>
|
</div>
|
|
<!-- 批量操作栏 -->
|
<div class="batch-bar" v-if="selectedRows.length > 0">
|
<span class="batch-info">已选中 <strong>{{ selectedRows.length }}</strong> 项</span>
|
<div class="batch-actions">
|
<n-button type="primary" size="small" tertiary @click="handleBatchCalibrate">
|
<span class="action-icon">🔧</span> 批量校准
|
</n-button>
|
<n-button type="success" size="small" tertiary @click="handleBatchActivate">
|
<span class="action-icon">✅</span> 批量启用
|
</n-button>
|
<n-button type="tertiary" size="small" tertiary @click="handleBatchDeactivate">
|
<span class="action-icon">❌</span> 批量停用
|
</n-button>
|
<n-button type="warning" size="small" tertiary @click="handleBatchConfig('selected')">
|
<span class="action-icon">📐</span> 批量配置校准周期
|
</n-button>
|
<n-button type="error" size="small" tertiary @click="handleDelete(undefined)">
|
<span class="action-icon">🗑️</span> 批量删除
|
</n-button>
|
<n-button size="small" @click="clearSelection">
|
取消选择
|
</n-button>
|
</div>
|
</div>
|
|
<!-- 数据表格 -->
|
<div class="table-container">
|
<n-data-table
|
:columns="columns"
|
:data="data"
|
:loading="loading"
|
:pagination="mobilePagination"
|
:row-key="(row) => row.id"
|
:checked-row-keys="checkedRowKeys"
|
@update:checked-row-keys="handleChecked"
|
:bordered="true"
|
:single-line="true"
|
>
|
<template #body-cell="{ column, row }">
|
<template v-if="column.key === 'calibStatus'">
|
<span class="calib-badge" :class="row.calibStatus">
|
{{ getCalibIcon(row.calibStatus) }} {{ getCalibLabel(row.calibStatus) }}
|
<span v-if="row.calibStatus === 'warning' || row.calibStatus === 'overdue'" class="days-left">
|
{{ row.daysLeft ? `· 剩余${row.daysLeft}天` : '' }}
|
</span>
|
</span>
|
</template>
|
<template v-else-if="column.key === 'activeStatus'">
|
<span class="status-tag active">
|
启用
|
</span>
|
</template>
|
<template v-else-if="column.key === 'calibCycleDays'">
|
每 {{ row.calibCycleDays }} 天
|
</template>
|
<template v-else-if="column.key === 'actions'">
|
<div class="actions">
|
<n-button size="small" circle @click="handleView(row)">
|
<SvgIcon icon="lucide:eye" />
|
</n-button>
|
<n-button size="small" circle @click="handleCalibrate(row)">
|
<SvgIcon icon="lucide:refresh-ccw" />
|
</n-button>
|
<n-button size="small" circle @click="handleEdit(row)">
|
<SvgIcon icon="lucide:edit" />
|
</n-button>
|
<n-button size="small" circle type="error" @click="handleDelete([row.id])">
|
<SvgIcon icon="lucide:trash-2" />
|
</n-button>
|
</div>
|
</template>
|
</template>
|
</n-data-table>
|
</div>
|
|
<!-- 新增/编辑弹窗组件 -->
|
<WeighingBoxOperate
|
v-model:visible="drawerVisible"
|
:operateType="operateType"
|
:rowData="currentRow"
|
@submitted="handleSubmitted"
|
/>
|
|
<!-- 校准弹窗组件 -->
|
<WeighingBoxCalibrate
|
v-model:visible="calibrateVisible"
|
:rowData="currentRow"
|
@submitted="handleSubmitted"
|
/>
|
|
<!-- 批量校准弹窗组件 -->
|
<WeighingBoxBatchCalibrate
|
v-model:visible="batchCalibrateVisible"
|
:selectedRows="selectedRows"
|
@submitted="handleSubmitted"
|
/>
|
|
<!-- 统一配置弹窗组件 -->
|
<WeighingBoxBatchConfig
|
v-model:visible="batchConfigVisible"
|
:selectedRows="selectedRows"
|
:type="applyScope"
|
@submitted="handleSubmitted"
|
/>
|
|
<!-- 复制弹窗组件 -->
|
<WeighingBoxCopy
|
v-model:visible="copyVisible"
|
:rowData="currentRow"
|
@submitted="handleSubmitted"
|
/>
|
</div>
|
</template>
|
|
<script setup lang="tsx">
|
import { ref, reactive, computed, onMounted } from 'vue';
|
import ButtonIcon from '@/components/custom/button-icon.vue';
|
import { NDivider, useDialog } from 'naive-ui';
|
import { weighingBoxApi } from '@/service/api/md/weighing-box';
|
import { useMessage } from 'naive-ui';
|
import SvgIcon from '@/components/custom/svg-icon.vue';
|
import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
|
import WeighingBoxOperate from './modules/weighing-box-operate.vue';
|
import WeighingBoxCalibrate from './modules/weighing-box-calibrate.vue';
|
import WeighingBoxBatchCalibrate from './modules/weighing-box-batch-calibrate.vue';
|
import WeighingBoxBatchConfig from './modules/weighing-box-batch-config.vue';
|
import WeighingBoxCopy from './modules/weighing-box-copy.vue';
|
|
const message = useMessage();
|
const dialog = useDialog();
|
|
// 搜索参数
|
const searchParams = reactive({
|
pageNum: 1,
|
pageSize: 10,
|
name: '',
|
code: '',
|
activeStatus: undefined,
|
calibStatus: undefined,
|
location: undefined
|
});
|
|
// 统计数据
|
const statistics = ref({
|
total: 0,
|
active: 0,
|
inactive: 0,
|
normal: 0,
|
warning: 0,
|
overdue: 0,
|
unset: 0
|
});
|
|
// 选中的行
|
const checkedRowKeys = ref<number[]>([]);
|
const selectedRows = computed(() => {
|
return data.value.filter(row => checkedRowKeys.value.includes(row.id));
|
});
|
|
// 使用 useNaivePaginatedTable 管理表格数据
|
const { columns, data, getData, getDataByPage, loading, mobilePagination, scrollX } = useNaivePaginatedTable({
|
api: () => weighingBoxApi.getList(searchParams),
|
transform: response => defaultTransform(response),
|
onPaginationParamsChange: params => {
|
searchParams.pageNum = params.page;
|
searchParams.pageSize = params.pageSize;
|
},
|
columns: () => [
|
{
|
type: 'selection',
|
width: 40
|
},
|
{
|
title: '编号',
|
key: 'code',
|
width: 120,
|
sorter: true
|
},
|
{
|
title: '名称',
|
key: 'name',
|
width: 150,
|
sorter: true
|
},
|
{
|
title: '重量',
|
key: 'weight',
|
width: 100,
|
render: (row) => `${row.weight} ${row.unit || ''}`
|
},
|
{
|
title: '位置',
|
key: 'location',
|
width: 150
|
},
|
{
|
title: '校准周期',
|
key: 'calibCycleDays',
|
width: 120,
|
sorter: true
|
},
|
{
|
title: '下次校准',
|
key: 'nextCalibDate',
|
width: 120,
|
sorter: true
|
},
|
{
|
title: '校准状态',
|
key: 'calibStatus',
|
width: 150,
|
render: (row) => {
|
return (
|
<span class={`calib-badge ${row.calibStatus}`}>
|
{getCalibIcon(row.calibStatus)} {getCalibLabel(row.calibStatus)}
|
{row.calibStatus === 'warning' || row.calibStatus === 'overdue' ?
|
<span class="days-left">{row.daysLeft ? `· 剩余${row.daysLeft}天` : ''}</span> :
|
''
|
}
|
</span>
|
);
|
}
|
},
|
{
|
title: '启用',
|
key: 'activeStatus',
|
width: 80,
|
render: (row) => {
|
const isActive = row.activeStatus === 1;
|
return (
|
<span class={`status-tag ${isActive ? 'active' : 'inactive'}`}>
|
{isActive ? '启用' : '停用'}
|
</span>
|
);
|
}
|
},
|
{
|
title: '操作',
|
key: 'actions',
|
width: 130,
|
fixed: 'right',
|
render: (row) => {
|
const divider = () => {
|
return <NDivider vertical />;
|
};
|
|
const editBtn = () => {
|
return (
|
<ButtonIcon
|
text
|
type="primary"
|
icon="material-symbols:drive-file-rename-outline-outline"
|
tooltipContent="编辑"
|
onClick={() => handleEdit(row)}
|
/>
|
);
|
};
|
|
// 校准按钮
|
const calibrateBtn = () => {
|
return (
|
<ButtonIcon
|
text
|
type="primary"
|
icon="material-symbols:build-outline-rounded"
|
tooltipContent="校准"
|
onClick={() => handleCalibrate(row)}
|
/>
|
);
|
};
|
// 复制按钮
|
const copyBtn = () => {
|
return (
|
<ButtonIcon
|
text
|
type="primary"
|
icon="material-symbols:copy-all-outline"
|
tooltipContent="复制"
|
onClick={() => handleCopy(row)}
|
/>
|
);
|
};
|
|
// 删除按钮
|
const deleteBtn = () => {
|
return (
|
<ButtonIcon
|
text
|
type="error"
|
icon="material-symbols:delete-outline"
|
tooltipContent="删除"
|
popconfirmContent="确定删除吗?"
|
onPositiveClick={() => handleDelete([row.id])}
|
/>
|
);
|
};
|
|
return (
|
<div class="flex-center gap-8px">
|
{editBtn()}
|
{divider()}
|
{calibrateBtn()}
|
{divider()}
|
{copyBtn()}
|
{divider()}
|
{deleteBtn()}
|
</div>
|
);
|
}
|
}
|
]
|
});
|
|
// 弹窗相关状态
|
const drawerVisible = ref(false);
|
const calibrateVisible = ref(false);
|
const batchCalibrateVisible = ref(false);
|
const batchConfigVisible = ref(false);
|
const applyScope = ref('all');
|
const copyVisible = ref(false);
|
const operateType = ref('add');
|
const currentRow = ref({});
|
|
// 获取校准状态图标
|
const getCalibIcon = (status) => {
|
const icons = {
|
normal: '✅',
|
warning: '⚠️',
|
overdue: '🚨',
|
unset: 'ℹ️'
|
};
|
return icons[status] || 'ℹ️';
|
};
|
|
// 获取校准状态标签
|
const getCalibLabel = (status) => {
|
const labels = {
|
normal: '正常',
|
warning: '即将到期',
|
overdue: '已过期',
|
unset: '未设置'
|
};
|
return labels[status] || '未设置';
|
};
|
|
// 按校准状态筛选
|
const filterByCalibStatus = (status) => {
|
searchParams.calibStatus = status;
|
searchParams.activeStatus = undefined;
|
handleSearch();
|
};
|
|
// 按启用状态筛选
|
const filterByActiveStatus = (status) => {
|
searchParams.activeStatus = status;
|
searchParams.calibStatus = undefined;
|
handleSearch();
|
};
|
|
// 按类型筛选
|
const filterByType = (type) => {
|
if (type === 'total') {
|
// 显示所有盒子
|
searchParams.calibStatus = undefined;
|
searchParams.activeStatus = undefined;
|
}
|
handleSearch();
|
};
|
|
// 清除选择
|
const clearSelection = () => {
|
checkedRowKeys.value = [];
|
};
|
|
// 导出
|
const handleExport = () => {
|
message.info('导出功能开发中');
|
};
|
|
// 导入
|
const handleImport = () => {
|
message.info('导入功能开发中');
|
};
|
|
// 查看
|
const handleView = (row: any) => {
|
message.info('查看功能开发中');
|
};
|
|
// 加载统计数据
|
const loadStatistics = async () => {
|
try {
|
const response = await weighingBoxApi.getStatistics();
|
statistics.value = response.data || {};
|
} catch (error) {
|
console.error('加载统计数据失败', error);
|
}
|
};
|
|
// 搜索
|
const handleSearch = () => {
|
searchParams.pageNum = 1;
|
getDataByPage();
|
};
|
|
// 重置搜索
|
const resetSearch = () => {
|
Object.keys(searchParams).forEach(key => {
|
if (key !== 'pageNum' && key !== 'pageSize') {
|
searchParams[key] = undefined;
|
}
|
});
|
searchParams.pageNum = 1;
|
getDataByPage();
|
};
|
|
// 处理选中
|
const handleChecked = (keys: number[]) => {
|
checkedRowKeys.value = keys;
|
};
|
|
// 新增
|
const handleAdd = () => {
|
operateType.value = 'add';
|
currentRow.value = {};
|
drawerVisible.value = true;
|
};
|
|
// 编辑
|
const handleEdit = (row: any) => {
|
operateType.value = 'edit';
|
currentRow.value = row;
|
drawerVisible.value = true;
|
};
|
|
// 校准
|
const handleCalibrate = (row: any) => {
|
currentRow.value = row;
|
calibrateVisible.value = true;
|
};
|
|
// 批量校准
|
const handleBatchCalibrate = () => {
|
if (selectedRows.value.length === 0) {
|
message.warning('请选择要校准的盒子');
|
return;
|
}
|
batchCalibrateVisible.value = true;
|
};
|
|
// 批量配置
|
const handleBatchConfig = (type: string) => {
|
|
batchConfigVisible.value = true;
|
applyScope.value = type;
|
};
|
|
// 批量启用
|
const handleBatchActivate = async () => {
|
if (selectedRows.value.length === 0) {
|
message.warning('请选择要启用的盒子');
|
return;
|
}
|
|
dialog.warning({
|
title: '批量启用确认',
|
content: `确定要启用选中的 ${selectedRows.value.length} 个盒子吗?`,
|
positiveText: '确定',
|
negativeText: '取消',
|
onPositiveClick: async () => {
|
try {
|
const response = await weighingBoxApi.batchUpdateStatus({
|
boxIds: selectedRows.value.map(row => row.id),
|
activeStatus: 1
|
});
|
const res = response.response.data;
|
if (res.code === 200) {
|
message.success('批量启用成功');
|
getDataByPage();
|
loadStatistics();
|
} else {
|
message.error(response.msg || '批量启用失败');
|
}
|
} catch (error) {
|
message.error('批量启用失败');
|
}
|
}
|
});
|
};
|
|
// 批量停用
|
const handleBatchDeactivate = async () => {
|
if (selectedRows.value.length === 0) {
|
message.warning('请选择要停用的盒子');
|
return;
|
}
|
|
dialog.warning({
|
title: '批量停用确认',
|
content: `确定要停用选中的 ${selectedRows.value.length} 个盒子吗?`,
|
positiveText: '确定',
|
negativeText: '取消',
|
onPositiveClick: async () => {
|
try {
|
const response = await weighingBoxApi.batchUpdateStatus({
|
boxIds: selectedRows.value.map(row => row.id),
|
activeStatus: 0
|
});
|
const res = response.response.data;
|
|
if (res.code === 200) {
|
message.success('批量停用成功');
|
getDataByPage();
|
loadStatistics();
|
} else {
|
message.error(res.msg || '批量停用失败');
|
}
|
} catch (error) {
|
message.error('批量停用失败');
|
}
|
}
|
});
|
};
|
|
// 复制
|
const handleCopy = (row: any) => {
|
currentRow.value = row;
|
copyVisible.value = true;
|
};
|
|
// 删除
|
const handleDelete = async (ids?: number[]) => {
|
const deleteIds = ids || selectedRows.value.map(row => row.id);
|
|
console.log(deleteIds);
|
if (deleteIds.length === 0) {
|
message.warning('请选择要删除的盒子');
|
return;
|
}
|
|
dialog.error({
|
title: '批量删除确认',
|
content: `确定要删除选中的 ${deleteIds.length} 个盒子吗?此操作不可恢复。`,
|
positiveText: '确定删除',
|
negativeText: '取消',
|
onPositiveClick: async () => {
|
try {
|
const response = await weighingBoxApi.remove(deleteIds);
|
|
console.log("删除响应:", response.response.data);
|
const res = response.response.data;
|
if (res.code === 200) {
|
message.success('删除成功');
|
getDataByPage();
|
loadStatistics();
|
} else {
|
message.error(res.msg || '删除失败');
|
}
|
} catch (error) {
|
message.error('删除失败');
|
}
|
}
|
});
|
};
|
|
// 提交后处理
|
const handleSubmitted = () => {
|
getDataByPage();
|
loadStatistics();
|
};
|
|
// 初始化
|
onMounted(() => {
|
getData();
|
loadStatistics();
|
});
|
</script>
|
|
<style scoped>
|
.app-container {
|
padding: 20px;
|
}
|
|
/* 页面标题 */
|
.page-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 20px;
|
}
|
|
.page-header-left .breadcrumb {
|
font-size: 14px;
|
color: #666;
|
margin-bottom: 8px;
|
}
|
|
.page-header-left h1 {
|
font-size: 24px;
|
font-weight: 600;
|
color: #333;
|
margin: 0;
|
}
|
|
.header-actions {
|
display: flex;
|
gap: 10px;
|
align-items: center;
|
}
|
|
/* 搜索栏 */
|
.search-bar {
|
background: #fff;
|
border-radius: 8px;
|
padding: 16px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
margin-bottom: 20px;
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
flex-wrap: wrap;
|
}
|
|
/* 快捷操作按钮 */
|
.quick-actions {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
flex-wrap: wrap;
|
}
|
|
.action-icon {
|
margin-right: 4px;
|
}
|
|
/* 统计卡片 */
|
.stats-container {
|
display: grid;
|
grid-template-columns: repeat(5, 1fr);
|
gap: 16px;
|
margin-bottom: 20px;
|
}
|
|
.stat-card {
|
background: #fff;
|
border-radius: 8px;
|
padding: 16px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
position: relative;
|
overflow: hidden;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.stat-card::before {
|
content: '';
|
position: absolute;
|
top: 0;
|
left: 0;
|
right: 0;
|
height: 4px;
|
}
|
|
.stat-card.total::before {
|
background: #3b82f6;
|
}
|
|
.stat-card.normal::before {
|
background: #10b981;
|
}
|
|
.stat-card.warning::before {
|
background: #f59e0b;
|
}
|
|
.stat-card.overdue::before {
|
background: #ef4444;
|
}
|
|
.stat-card.inactive::before {
|
background: #6b7280;
|
}
|
|
.stat-header {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
margin-bottom: 8px;
|
}
|
|
.stat-icon {
|
font-size: 15px;
|
}
|
|
.stat-card .stat-title {
|
font-size: 14px;
|
color: #666;
|
margin: 0;
|
}
|
|
.stat-card .stat-value {
|
font-size: 28px;
|
font-weight: 700;
|
color: #333;
|
margin-bottom: 4px;
|
}
|
|
.stat-card .stat-sub {
|
font-size: 12px;
|
color: #999;
|
}
|
|
.search-input {
|
width: 250px;
|
}
|
|
/* 批量操作栏 */
|
.batch-bar {
|
background: #e6f7ff;
|
border: 1px solid #91d5ff;
|
border-radius: 8px;
|
padding: 12px 16px;
|
margin-bottom: 20px;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
.batch-info {
|
font-size: 14px;
|
color: #1890ff;
|
font-weight: 500;
|
}
|
|
.batch-actions {
|
display: flex;
|
gap: 8px;
|
}
|
|
/* 表格容器 */
|
.table-container {
|
background: #fff;
|
border-radius: 8px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
overflow: hidden;
|
}
|
|
:deep(.n-data-table) {
|
border-radius: 8px;
|
overflow: hidden;
|
}
|
|
:deep(.n-data-table th) {
|
background: #f8f9fa;
|
font-size: 14px;
|
font-weight: 600;
|
color: #666;
|
text-align: left;
|
padding: 12px 16px;
|
border-bottom: 1px solid #e5e7eb;
|
}
|
|
:deep(.n-data-table td) {
|
padding: 12px 16px;
|
font-size: 14px;
|
color: #333;
|
border-bottom: 1px solid #f0f0f0;
|
vertical-align: middle;
|
}
|
|
:deep(.n-data-table tr:hover) {
|
background: #fafbfc;
|
}
|
|
:deep(.n-data-table tr.n-data-table__row--selected) {
|
background: #e6f7ff;
|
}
|
|
:deep(.n-data-table .n-data-table__pagination) {
|
padding: 16px;
|
border-top: 1px solid #f0f0f0;
|
}
|
|
/* 状态标签 */
|
.status-tag {
|
display: inline-flex;
|
align-items: center;
|
justify-content: center;
|
padding: 2px 8px;
|
border-radius: 12px;
|
font-size: 12px;
|
font-weight: 500;
|
}
|
|
.status-tag.active {
|
background: #f6ffed;
|
color: #52c41a;
|
border: 1px solid #b7eb8f;
|
}
|
|
.status-tag.inactive {
|
background: #f5f5f5;
|
color: #999;
|
border: 1px solid #d9d9d9;
|
}
|
|
/* 校准状态标签 */
|
.calib-badge {
|
display: inline-flex;
|
align-items: center;
|
gap: 4px;
|
padding: 4px 12px;
|
border-radius: 12px;
|
font-size: 12px;
|
font-weight: 500;
|
}
|
|
.calib-badge.normal {
|
background: #f6ffed;
|
color: #52c41a;
|
border: 1px solid #b7eb8f;
|
}
|
|
.calib-badge.warning {
|
background: #fffbe6;
|
color: #d48806;
|
border: 1px solid #ffe58f;
|
}
|
|
.calib-badge.overdue {
|
background: #fff2f0;
|
color: #ff4d4f;
|
border: 1px solid #ffccc7;
|
}
|
|
.calib-badge.unset {
|
background: #f5f5f5;
|
color: #999;
|
border: 1px solid #d9d9d9;
|
}
|
|
.calib-badge .days-left {
|
font-size: 11px;
|
opacity: 0.8;
|
}
|
|
/* 操作按钮 */
|
.actions {
|
display: flex;
|
gap: 4px;
|
}
|
|
.action-btn {
|
width: 32px;
|
height: 32px;
|
border-radius: 50%;
|
border: 1px solid #e5e7eb;
|
background: #fff;
|
cursor: pointer;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
transition: all 0.2s ease;
|
}
|
|
.action-btn:hover {
|
background: #f5f5f5;
|
border-color: #1890ff;
|
}
|
|
.action-btn.delete:hover {
|
background: #fff2f0;
|
border-color: #ff4d4f;
|
}
|
|
.action-btn .icon {
|
font-size: 14px;
|
}
|
|
.flex-center {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.gap-8px {
|
gap: 8px;
|
}
|
|
/* 响应式调整 */
|
@media (max-width: 1200px) {
|
.stats-container {
|
grid-template-columns: repeat(3, 1fr);
|
}
|
}
|
|
@media (max-width: 768px) {
|
.stats-container {
|
grid-template-columns: repeat(2, 1fr);
|
}
|
|
.search-bar {
|
flex-direction: column;
|
align-items: stretch;
|
}
|
|
.quick-actions {
|
flex-direction: column;
|
align-items: stretch;
|
}
|
|
.page-header {
|
flex-direction: column;
|
align-items: stretch;
|
}
|
|
.header-actions {
|
margin-top: 12px;
|
}
|
}
|
|
/* 模态框样式 */
|
:deep(.n-modal-card) {
|
border-radius: 12px;
|
overflow: hidden;
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
}
|
|
:deep(.n-modal-card .n-modal-card__header) {
|
padding: 16px 20px;
|
border-bottom: 1px solid #e5e7eb;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
:deep(.n-modal-card .n-modal-card__header-title) {
|
font-size: 16px;
|
font-weight: 600;
|
color: #333;
|
}
|
|
:deep(.n-modal-card .n-modal-card__body) {
|
padding: 20px;
|
overflow-y: auto;
|
flex: 1;
|
}
|
|
:deep(.n-modal-card .n-modal-card__footer) {
|
padding: 16px 20px;
|
border-top: 1px solid #e5e7eb;
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
}
|
|
/* 表单样式 */
|
.form-grid {
|
display: grid;
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
.form-group {
|
display: flex;
|
flex-direction: column;
|
}
|
|
.form-group.full {
|
grid-column: 1 / -1;
|
}
|
|
.form-label {
|
font-size: 14px;
|
font-weight: 500;
|
color: #333;
|
margin-bottom: 8px;
|
}
|
|
.form-label .required {
|
color: #ff4d4f;
|
margin-left: 4px;
|
}
|
|
.form-row {
|
display: flex;
|
gap: 10px;
|
align-items: center;
|
}
|
|
.form-hint {
|
font-size: 12px;
|
color: #999;
|
margin-top: 4px;
|
}
|
|
.form-divider {
|
grid-column: 1 / -1;
|
border: none;
|
border-top: 1px solid #e5e7eb;
|
margin: 12px 0;
|
}
|
|
.form-section-title {
|
grid-column: 1 / -1;
|
font-size: 14px;
|
font-weight: 600;
|
color: #1890ff;
|
margin: 12px 0 8px;
|
}
|
|
/* 预设模板 */
|
.preset-bar {
|
grid-column: 1 / -1;
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
flex-wrap: wrap;
|
margin-bottom: 12px;
|
}
|
|
.preset-chip {
|
padding: 6px 16px;
|
border-radius: 20px;
|
border: 1px solid #e5e7eb;
|
font-size: 14px;
|
color: #666;
|
cursor: pointer;
|
transition: all 0.2s ease;
|
background: #fff;
|
}
|
|
.preset-chip:hover {
|
border-color: #1890ff;
|
color: #1890ff;
|
}
|
|
.preset-chip.active {
|
background: #1890ff;
|
color: #fff;
|
border-color: #1890ff;
|
}
|
|
/* 配置模板 */
|
.config-template-grid {
|
display: grid;
|
grid-template-columns: repeat(3, 1fr);
|
gap: 12px;
|
margin-bottom: 20px;
|
}
|
|
.template-card {
|
border: 2px solid #e5e7eb;
|
border-radius: 8px;
|
padding: 16px;
|
cursor: pointer;
|
transition: all 0.2s ease;
|
text-align: center;
|
background: #fff;
|
}
|
|
.template-card:hover {
|
border-color: #91d5ff;
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
|
}
|
|
.template-card.selected {
|
border-color: #1890ff;
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
|
background: #e6f7ff;
|
}
|
|
.template-card .t-icon {
|
font-size: 24px;
|
margin-bottom: 8px;
|
}
|
|
.template-card .t-name {
|
font-size: 14px;
|
font-weight: 600;
|
color: #333;
|
margin-bottom: 4px;
|
}
|
|
.template-card .t-desc {
|
font-size: 12px;
|
color: #666;
|
}
|
|
.template-card .t-custom-input {
|
width: 80px;
|
margin-top: 8px;
|
}
|
|
/* 应用范围 */
|
.apply-target {
|
margin-top: 16px;
|
}
|
|
.radio-group {
|
display: flex;
|
gap: 20px;
|
margin-top: 8px;
|
flex-wrap: wrap;
|
}
|
|
.radio-item {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
font-size: 14px;
|
color: #333;
|
cursor: pointer;
|
}
|
|
/* 批量校准列表 */
|
.batch-cali-list {
|
max-height: 300px;
|
overflow-y: auto;
|
border: 1px solid #e5e7eb;
|
border-radius: 8px;
|
margin: 12px 0;
|
}
|
|
.batch-cali-item {
|
display: flex;
|
align-items: center;
|
padding: 12px 16px;
|
border-bottom: 1px solid #f0f0f0;
|
font-size: 14px;
|
}
|
|
.batch-cali-item:last-child {
|
border-bottom: none;
|
}
|
|
.batch-cali-item .box-info {
|
flex: 1;
|
}
|
|
.batch-cali-item .box-name {
|
font-weight: 500;
|
color: #333;
|
margin-bottom: 4px;
|
}
|
|
.batch-cali-item .box-meta {
|
font-size: 12px;
|
color: #666;
|
}
|
|
.batch-cali-summary {
|
background: #f8f9fa;
|
border-radius: 8px;
|
padding: 12px 16px;
|
margin-top: 12px;
|
font-size: 14px;
|
color: #666;
|
}
|
|
/* 校准信息 */
|
.calib-info {
|
background: #f8f9fa;
|
border-radius: 8px;
|
padding: 16px;
|
margin-bottom: 20px;
|
border: 1px solid #e5e7eb;
|
}
|
|
.calib-info .info-row {
|
display: flex;
|
justify-content: space-between;
|
padding: 8px 0;
|
font-size: 14px;
|
border-bottom: 1px solid #e5e7eb;
|
}
|
|
.calib-info .info-row:last-child {
|
border-bottom: none;
|
}
|
|
.calib-info .info-label {
|
color: #666;
|
font-weight: 500;
|
}
|
|
.calib-info .info-value {
|
color: #333;
|
}
|
|
/* 模态框底部 */
|
.modal-footer {
|
width: 100%;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.modal-hint {
|
font-size: 12px;
|
color: #999;
|
}
|
|
.modal-footer-right {
|
display: flex;
|
gap: 10px;
|
}
|
|
/* 开关 */
|
.switch-wrap {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.switch-label {
|
font-size: 14px;
|
color: #666;
|
}
|
.action-tag {
|
font-size: 12px;
|
font-weight: 500;
|
margin-left: 8px;
|
padding: 2px 8px;
|
border-radius: 15px;
|
}
|
</style>
|