refactor(menu): 菜单模块归属调整 - 勘察费用/运维费定价迁至基础数据(/base/*),项目勘察/项目安装迁至运营派单(/dispatch/*)并按序排列(电工→勘察→安装→报修→维保);同步前端路由/页面git mv/通知跳转与db_seed,生产库已执行迁移
This commit is contained in:
1 parent
77891bf4f2
commit
850cd92584
11 files changed
+121
-74
No files matched your search
@@ -0,0 +1,864 @@
|
||||
<template>
|
||||
<div class="cost-page">
|
||||
<!-- 顶部标题 + 操作 -->
|
||||
<div class="page-header">
|
||||
<div class="breadcrumb">
|
||||
<span>运营管控体系</span>
|
||||
<span class="sep">/</span>
|
||||
<span>项目管理</span>
|
||||
<span class="sep">/</span>
|
||||
<span class="cur">勘察费用</span>
|
||||
</div>
|
||||
<div class="header-main">
|
||||
<div class="header-left">
|
||||
<div class="header-row">
|
||||
<span class="title-bar"></span>
|
||||
<span class="main-title">勘察费用</span>
|
||||
<span class="sub-title">SURVEY FEE</span>
|
||||
</div>
|
||||
<p class="desc">维护勘察派单相关费用标准(基础出场费、回路测点补贴等),支持按次、按工时两种计价方式。</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<a-button v-if="canCreate" type="primary" @click="handleAdd">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增勘察费用
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体卡片 -->
|
||||
<div class="content-card">
|
||||
<!-- 筛选区 -->
|
||||
<div class="filter-card">
|
||||
<div class="filter-grid">
|
||||
<div class="filter-item">
|
||||
<label class="filter-label">关键词检索</label>
|
||||
<a-input
|
||||
v-model:value="searchForm.keyword"
|
||||
placeholder="价格编码"
|
||||
allow-clear
|
||||
maxlength="64"
|
||||
@pressEnter="handleSearch"
|
||||
>
|
||||
<template #prefix><SearchOutlined /></template>
|
||||
</a-input>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label class="filter-label">派单类型</label>
|
||||
<a-select v-model:value="searchForm.dispatchType" placeholder="全部类型" allow-clear>
|
||||
<a-select-option value="勘察">勘察</a-select-option>
|
||||
<a-select-option value="安装">安装</a-select-option>
|
||||
<a-select-option value="预警">预警</a-select-option>
|
||||
<a-select-option value="维修">维修</a-select-option>
|
||||
<a-select-option value="维保">维保</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label class="filter-label">计价方式</label>
|
||||
<a-select v-model:value="searchForm.priceMode" placeholder="全部方式" allow-clear>
|
||||
<a-select-option :value="1">按次</a-select-option>
|
||||
<a-select-option :value="2">按工时</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label class="filter-label">状态</label>
|
||||
<a-select v-model:value="searchForm.status" placeholder="全部状态" allow-clear>
|
||||
<a-select-option :value="1">启用</a-select-option>
|
||||
<a-select-option :value="0">停用</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-bottom">
|
||||
<div class="filter-actions">
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
查询
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列表区 -->
|
||||
<div class="list-section">
|
||||
<div class="list-header">
|
||||
<div class="list-title">
|
||||
<span class="dot"></span>
|
||||
<span class="t">勘察费用清单</span>
|
||||
<span class="count">{{ total }}</span>
|
||||
</div>
|
||||
<a-button type="text" size="small" title="刷新列表" @click="fetchList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper" ref="wrapperRef">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
row-key="id"
|
||||
:scroll="{ x: 'max-content', y: tableHeight }"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'priceCode'">
|
||||
<span class="mono">{{ record.priceCode || '-' }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'dispatchType'">
|
||||
{{ record.dispatchType || '-' }}
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'priceType'">
|
||||
{{ record.priceType || '-' }}
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'priceMode'">
|
||||
<a-tag :color="getPriceModeColor(record.priceMode)">{{ getPriceModeText(record.priceMode) }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'unitPrice'">
|
||||
<span class="mono">{{ formatPrice(record.unitPrice) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'unit'">
|
||||
{{ record.unit || '-' }}
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-switch
|
||||
:checked="record.status === 1"
|
||||
checked-children="启用"
|
||||
un-checked-children="停用"
|
||||
@change="(checked: boolean) => handleStatusChange(record, checked)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'remark'">
|
||||
{{ record.remark || '-' }}
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button v-if="canUpdate" type="link" size="small" @click="handleEdit(record)">
|
||||
<template #icon><EditOutlined /></template>
|
||||
编辑
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
v-if="canDelete"
|
||||
title="确定要删除该勘察费用吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
ok-button-props="{ danger: true }"
|
||||
@confirm="handleDelete(record.id)"
|
||||
>
|
||||
<a-button type="link" size="small" danger>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #emptyText>
|
||||
<a-empty description="暂无勘察费用数据" />
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="modalVisible"
|
||||
:title="modalTitle"
|
||||
:confirm-loading="modalLoading"
|
||||
width="640px"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<!-- 价格编码信息条(编辑态只读展示) -->
|
||||
<div v-if="isEdit && editId" class="no-bar">
|
||||
<div class="no-item">
|
||||
<span class="no-label">价格编码</span>
|
||||
<span class="no-value mono">{{ editCode || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<div class="form-section">
|
||||
<div class="section-head">
|
||||
<span class="section-no">01</span>
|
||||
<div class="section-text">
|
||||
<span class="section-name">勘察费用信息</span>
|
||||
<span class="section-sub">Cost Price Information</span>
|
||||
</div>
|
||||
</div>
|
||||
<a-form-item label="派单类型" name="dispatchType">
|
||||
<a-select v-model:value="formData.dispatchType" placeholder="请选择派单类型" style="width: 100%">
|
||||
<a-select-option value="勘察">勘察</a-select-option>
|
||||
<a-select-option value="安装">安装</a-select-option>
|
||||
<a-select-option value="预警">预警</a-select-option>
|
||||
<a-select-option value="维修">维修</a-select-option>
|
||||
<a-select-option value="维保">维保</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="价格类型" name="priceType">
|
||||
<a-select v-model:value="formData.priceType" placeholder="请选择价格类型" allow-clear style="width: 100%">
|
||||
<a-select-option value="单价">单价</a-select-option>
|
||||
<a-select-option value="总价提成">总价提成</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="计价方式" name="priceMode">
|
||||
<a-select
|
||||
v-model:value="formData.priceMode"
|
||||
placeholder="请选择计价方式"
|
||||
style="width: 100%"
|
||||
@change="handlePriceModeChange"
|
||||
>
|
||||
<a-select-option :value="1">按次</a-select-option>
|
||||
<a-select-option :value="2">按工时</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="单价" name="unitPrice">
|
||||
<a-input-number
|
||||
v-model:value="formData.unitPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="请输入单价"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="单位" name="unit">
|
||||
<a-input
|
||||
v-model:value="formData.unit"
|
||||
placeholder="如:次 / 小时"
|
||||
maxlength="16"
|
||||
@change="handleUnitManualChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="remark">
|
||||
<a-textarea v-model:value="formData.remark" :rows="2" placeholder="请输入备注" maxlength="255" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-radio-group v-model:value="formData.status">
|
||||
<a-radio :value="1">启用</a-radio>
|
||||
<a-radio :value="0">停用</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { SearchOutlined, ReloadOutlined, PlusOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
|
||||
import {
|
||||
dispatchPriceApi,
|
||||
type DispatchPriceInfo,
|
||||
type DispatchPriceQuery,
|
||||
} from '@/api/dispatchPrice';
|
||||
import { useTableScroll } from '@/composables/useTableScroll';
|
||||
import { usePermissionStore } from '@/stores/permission';
|
||||
|
||||
const { wrapperRef, tableHeight } = useTableScroll();
|
||||
const permissionStore = usePermissionStore();
|
||||
|
||||
// 按钮权限
|
||||
const canCreate = computed(() => permissionStore.hasPermission('cost:create'));
|
||||
const canUpdate = computed(() => permissionStore.hasPermission('cost:update'));
|
||||
const canDelete = computed(() => permissionStore.hasPermission('cost:delete'));
|
||||
|
||||
// 计价方式 → 默认单位
|
||||
const PRICE_MODE_UNIT: Record<number, string> = { 1: '次', 2: '小时' };
|
||||
|
||||
// ==================== 列表数据 ====================
|
||||
const loading = ref(false);
|
||||
const dataSource = ref<DispatchPriceInfo[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
const searchForm = reactive<DispatchPriceQuery>({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
keyword: '',
|
||||
dispatchType: undefined as any,
|
||||
priceMode: undefined as any,
|
||||
status: undefined as any,
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ title: '价格编码', dataIndex: 'priceCode', key: 'priceCode', ellipsis: true, width: 150 },
|
||||
{ title: '派单类型', dataIndex: 'dispatchType', key: 'dispatchType', ellipsis: true, width: 110 },
|
||||
{ title: '价格类型', dataIndex: 'priceType', key: 'priceType', ellipsis: true, width: 110 },
|
||||
{ title: '计价方式', key: 'priceMode', align: 'center' as const, width: 110 },
|
||||
{ title: '单价', key: 'unitPrice', align: 'right' as const, width: 110 },
|
||||
{ title: '单位', dataIndex: 'unit', key: 'unit', align: 'center' as const, width: 80 },
|
||||
{ title: '状态', key: 'status', align: 'center' as const, width: 100 },
|
||||
{ title: '备注', dataIndex: 'remark', key: 'remark', ellipsis: true },
|
||||
{ title: '操作', key: 'action', width: 150, align: 'center' as const, fixed: 'right' as const },
|
||||
];
|
||||
|
||||
const pagination = computed(() => ({
|
||||
current: searchForm.page,
|
||||
pageSize: searchForm.pageSize,
|
||||
total: total.value,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (t: number) => `共 ${t} 条数据`,
|
||||
}));
|
||||
|
||||
// ==================== 弹窗表单 ====================
|
||||
const modalVisible = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalLoading = ref(false);
|
||||
const formRef = ref();
|
||||
const isEdit = ref(false);
|
||||
const editId = ref<number | null>(null);
|
||||
const editCode = ref('');
|
||||
// 用户是否手动修改过单位(手动修改后不再随计价方式联动覆盖)
|
||||
const unitManuallyEdited = ref(false);
|
||||
|
||||
const formData = reactive({
|
||||
dispatchType: undefined as string | undefined,
|
||||
priceType: undefined as string | undefined,
|
||||
priceMode: undefined as number | undefined,
|
||||
unitPrice: undefined as number | undefined,
|
||||
unit: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
});
|
||||
|
||||
const formRules = {
|
||||
dispatchType: [{ required: true, message: '请选择派单类型', trigger: 'change' }],
|
||||
priceMode: [{ required: true, message: '请选择计价方式', trigger: 'change' }],
|
||||
unitPrice: [{ required: true, message: '请输入单价', trigger: 'change', type: 'number' as const }],
|
||||
unit: [{ pattern: /^[^<>"']*$/, message: '不能包含 < > " \' 特殊字符', trigger: 'blur' }],
|
||||
remark: [{ pattern: /^[^<>"']*$/, message: '不能包含 < > " \' 特殊字符', trigger: 'blur' }],
|
||||
};
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
onMounted(() => {
|
||||
fetchList();
|
||||
});
|
||||
|
||||
// ==================== 数据请求 ====================
|
||||
async function fetchList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res: any = await dispatchPriceApi.getDispatchPriceList(searchForm);
|
||||
if (res.code === 200) {
|
||||
dataSource.value = res.data.list;
|
||||
total.value = res.data.total;
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('获取勘察费用列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 搜索重置 ====================
|
||||
function handleSearch() {
|
||||
searchForm.page = 1;
|
||||
fetchList();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.keyword = '';
|
||||
searchForm.dispatchType = undefined as any;
|
||||
searchForm.priceMode = undefined as any;
|
||||
searchForm.status = undefined as any;
|
||||
searchForm.page = 1;
|
||||
fetchList();
|
||||
}
|
||||
|
||||
function handleTableChange(pag: any) {
|
||||
searchForm.page = pag.current;
|
||||
searchForm.pageSize = pag.pageSize;
|
||||
fetchList();
|
||||
}
|
||||
|
||||
// ==================== 新增/编辑 ====================
|
||||
function handleAdd() {
|
||||
isEdit.value = false;
|
||||
editId.value = null;
|
||||
editCode.value = '';
|
||||
modalTitle.value = '新增勘察费用';
|
||||
resetForm();
|
||||
modalVisible.value = true;
|
||||
}
|
||||
|
||||
function handleEdit(record: DispatchPriceInfo) {
|
||||
isEdit.value = true;
|
||||
editId.value = record.id;
|
||||
editCode.value = record.priceCode;
|
||||
modalTitle.value = '编辑勘察费用';
|
||||
resetForm();
|
||||
|
||||
formData.dispatchType = record.dispatchType || undefined;
|
||||
formData.priceType = record.priceType || undefined;
|
||||
formData.priceMode = record.priceMode;
|
||||
formData.unitPrice = Number(record.unitPrice ?? 0);
|
||||
formData.unit = record.unit || '';
|
||||
formData.remark = record.remark || '';
|
||||
formData.status = record.status;
|
||||
// 编辑已有记录视为已手动设定单位,避免切换计价方式覆盖既有单位
|
||||
unitManuallyEdited.value = true;
|
||||
|
||||
modalVisible.value = true;
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
formData.dispatchType = undefined;
|
||||
formData.priceType = undefined;
|
||||
formData.priceMode = undefined;
|
||||
formData.unitPrice = undefined;
|
||||
formData.unit = '';
|
||||
formData.remark = '';
|
||||
formData.status = 1;
|
||||
unitManuallyEdited.value = false;
|
||||
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
}
|
||||
|
||||
// 计价方式变化:未手动改过单位时按计价方式联动单位
|
||||
function handlePriceModeChange(v: any) {
|
||||
if (!unitManuallyEdited.value) {
|
||||
formData.unit = PRICE_MODE_UNIT[Number(v)] || '';
|
||||
}
|
||||
}
|
||||
|
||||
// 用户手动修改单位后置标记
|
||||
function handleUnitManualChange() {
|
||||
unitManuallyEdited.value = true;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
modalLoading.value = true;
|
||||
try {
|
||||
const submitData = {
|
||||
dispatchType: formData.dispatchType,
|
||||
priceType: formData.priceType || undefined,
|
||||
priceMode: formData.priceMode as number,
|
||||
unitPrice: formData.unitPrice as number,
|
||||
unit: formData.unit || undefined,
|
||||
status: formData.status,
|
||||
remark: formData.remark || undefined,
|
||||
};
|
||||
|
||||
let res: any;
|
||||
if (isEdit.value && editId.value) {
|
||||
res = await dispatchPriceApi.updateDispatchPrice(editId.value, submitData);
|
||||
} else {
|
||||
res = await dispatchPriceApi.createDispatchPrice(submitData);
|
||||
}
|
||||
|
||||
if (res.code === 200) {
|
||||
message.success(isEdit.value ? '更新成功' : '创建成功');
|
||||
modalVisible.value = false;
|
||||
searchForm.page = 1;
|
||||
fetchList();
|
||||
} else {
|
||||
message.error(res.message || '操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('操作失败');
|
||||
} finally {
|
||||
modalLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
modalVisible.value = false;
|
||||
resetForm();
|
||||
}
|
||||
|
||||
async function handleDelete(id: number) {
|
||||
try {
|
||||
const res: any = await dispatchPriceApi.deleteDispatchPrice(id);
|
||||
if (res.code === 200) {
|
||||
message.success('删除成功');
|
||||
searchForm.page = 1;
|
||||
fetchList();
|
||||
} else {
|
||||
message.error(res.message || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 行内状态切换
|
||||
async function handleStatusChange(record: DispatchPriceInfo, checked: boolean) {
|
||||
const status = checked ? 1 : 0;
|
||||
try {
|
||||
const res: any = await dispatchPriceApi.updateDispatchPriceStatus(record.id, status);
|
||||
if (res.code === 200) {
|
||||
message.success(status === 1 ? '已启用' : '已停用');
|
||||
fetchList();
|
||||
} else {
|
||||
message.error(res.message || '状态更新失败');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('状态更新失败');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 辅助 ====================
|
||||
function getPriceModeText(mode?: number): string {
|
||||
const textMap: Record<number, string> = { 1: '按次', 2: '按工时' };
|
||||
return textMap[mode ?? 0] || '未知';
|
||||
}
|
||||
|
||||
function getPriceModeColor(mode?: number): string {
|
||||
const colorMap: Record<number, string> = { 1: 'blue', 2: 'orange' };
|
||||
return colorMap[mode ?? 0] || 'default';
|
||||
}
|
||||
|
||||
function formatPrice(value?: number | string | null): string {
|
||||
return Number(value ?? 0).toFixed(2);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cost-page {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 20px 24px;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, #eef3f8 0%, #e8eefb 100%);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: #5b6b7e;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.breadcrumb .sep {
|
||||
color: #c2c6d8;
|
||||
}
|
||||
|
||||
.breadcrumb .cur {
|
||||
color: #0054cd;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.header-main {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
width: 5px;
|
||||
height: 22px;
|
||||
background: #0054cd;
|
||||
border-radius: 3px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #0d1c2c;
|
||||
line-height: 1.4;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #0054cd;
|
||||
letter-spacing: 2px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin: 6px 0 0;
|
||||
font-size: 13px;
|
||||
color: #5b6b7e;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
background: #fff;
|
||||
border: 1px solid #dbe4ee;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.07);
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
flex-shrink: 0;
|
||||
background: #f8faff;
|
||||
border: 1px solid #dbe4ee;
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.filter-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.filter-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.filter-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #5b6b7e;
|
||||
}
|
||||
|
||||
.filter-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #dbe4ee;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.list-section {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 2px 4px 12px;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.list-title .dot {
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background: #0054cd;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.list-title .t {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0d1c2c;
|
||||
}
|
||||
|
||||
.list-title .count {
|
||||
padding: 1px 8px;
|
||||
border-radius: 6px;
|
||||
background: #eef4ff;
|
||||
color: #0054cd;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-family: Consolas, Menlo, monospace;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: Consolas, Menlo, monospace;
|
||||
font-size: 13px;
|
||||
color: #0d1c2c;
|
||||
}
|
||||
|
||||
/* 弹窗 */
|
||||
.no-bar {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
background: #f4f7ff;
|
||||
border: 1px solid #dbe4ee;
|
||||
}
|
||||
|
||||
.no-item {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.no-label {
|
||||
font-size: 12px;
|
||||
color: #5b6b7e;
|
||||
}
|
||||
|
||||
.no-value {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #0054cd;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 4px 0 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #eef1f6;
|
||||
}
|
||||
|
||||
.section-no {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 7px;
|
||||
background: #0054cd;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: Consolas, Menlo, monospace;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.section-name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0d1c2c;
|
||||
}
|
||||
|
||||
.section-sub {
|
||||
font-size: 11px;
|
||||
color: #8a97a8;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
:deep(.ant-table) {
|
||||
font-size: 14px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background: #f4f7ff;
|
||||
color: #5b6b7e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.ant-btn-primary) {
|
||||
background: #0054cd;
|
||||
border-color: #0054cd;
|
||||
}
|
||||
|
||||
:deep(.ant-btn-primary:not(:disabled):not(.ant-btn-disabled):hover) {
|
||||
background: #0947ab;
|
||||
border-color: #0947ab;
|
||||
}
|
||||
|
||||
:deep(.ant-modal-title) {
|
||||
color: #0d1c2c;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user