874 lines
21 KiB
Vue
874 lines
21 KiB
Vue
<template>
|
||
<div class="install-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">INSTALL DISPATCH LEDGER</span>
|
||
</div>
|
||
<p class="desc">统筹项目方案落地施工派单,跟踪派单、施工起止与完工状态全流程。</p>
|
||
</div>
|
||
<div class="header-actions">
|
||
<a-button 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 filter-item-search">
|
||
<label class="filter-label">模糊搜索</label>
|
||
<a-input
|
||
v-model:value="searchForm.keyword"
|
||
placeholder="输入安装单号、安装人、备注..."
|
||
allow-clear
|
||
@pressEnter="handleSearch"
|
||
>
|
||
<template #prefix><SearchOutlined /></template>
|
||
</a-input>
|
||
</div>
|
||
<div class="filter-item">
|
||
<label class="filter-label">关联客户</label>
|
||
<a-select
|
||
v-model:value="searchForm.customerId"
|
||
placeholder="全部客户"
|
||
allow-clear
|
||
show-search
|
||
option-filter-prop="label"
|
||
:options="customerOptions"
|
||
/>
|
||
</div>
|
||
<div class="filter-item">
|
||
<label class="filter-label">当前状态</label>
|
||
<a-select
|
||
v-model:value="searchForm.status"
|
||
placeholder="全部状态"
|
||
allow-clear
|
||
:options="statusOptions"
|
||
/>
|
||
</div>
|
||
<div class="filter-item filter-item-date">
|
||
<label class="filter-label">派单日期范围</label>
|
||
<a-range-picker
|
||
v-model:value="searchForm.dateRange"
|
||
style="width: 100%"
|
||
:placeholder="['开始日期', '结束日期']"
|
||
/>
|
||
</div>
|
||
<div class="filter-item filter-item-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="divider">|</span>
|
||
<span class="range-text">
|
||
显示第 <b>{{ rangeStart }}</b> 到 <b>{{ rangeEnd }}</b> 笔,共
|
||
<b class="strong">{{ total }}</b> 笔派单记录
|
||
</span>
|
||
</div>
|
||
<div class="list-extra">
|
||
<a-button type="text" size="small" title="刷新列表" @click="fetchList">
|
||
<template #icon><ReloadOutlined /></template>
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-wrapper" ref="wrapperRef">
|
||
<a-table
|
||
:columns="columns"
|
||
:data-source="dataSource"
|
||
:loading="loading"
|
||
:pagination="false"
|
||
row-key="id"
|
||
:scroll="{ x: 'max-content', y: tableHeight }"
|
||
>
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.key === 'installNo'">
|
||
<span class="install-no">{{ record.installNo }}</span>
|
||
</template>
|
||
|
||
<template v-else-if="column.key === 'customer'">
|
||
<div class="cust-name">{{ record.customer?.customerName || '-' }}</div>
|
||
<div class="sub-line">{{ record.customer?.contactPhone || '—' }}</div>
|
||
</template>
|
||
|
||
<template v-else-if="column.key === 'plan'">
|
||
<div class="plan-no">{{ record.plan?.planNo || '-' }}</div>
|
||
<div class="sub-line">{{ record.plan?.planDesc || '—' }}</div>
|
||
</template>
|
||
|
||
<template v-else-if="column.key === 'installer'">
|
||
<div class="type-main">{{ record.installer || record.workerName || '-' }}</div>
|
||
</template>
|
||
|
||
<template v-else-if="column.key === 'dispatchDate'">
|
||
<div class="plan-time">{{ formatDate(record.dispatchDate) }}</div>
|
||
</template>
|
||
|
||
<template v-else-if="column.key === 'period'">
|
||
<div class="plan-time">
|
||
{{ formatDate(record.startDate) }} ~ {{ formatDate(record.endDate) }}
|
||
</div>
|
||
</template>
|
||
|
||
<template v-else-if="column.key === 'status'">
|
||
<span class="status-pill" :style="pillStyle(statusMeta(record.status))">
|
||
<i class="dot" :style="{ background: statusMeta(record.status).color }"></i>
|
||
{{ statusMeta(record.status).label }}
|
||
</span>
|
||
</template>
|
||
|
||
<template v-else-if="column.key === 'action'">
|
||
<div class="row-actions">
|
||
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
||
<a-button
|
||
v-if="record.status === INSTALL_STATUS.DISPATCHED"
|
||
type="link"
|
||
size="small"
|
||
@click="handleComplete(record)"
|
||
>标记完成</a-button>
|
||
<a-button type="link" size="small" danger @click="handleDelete(record)">删除</a-button>
|
||
</div>
|
||
</template>
|
||
</template>
|
||
|
||
<template #emptyText>
|
||
<a-empty description="暂无安装派单数据" />
|
||
</template>
|
||
</a-table>
|
||
</div>
|
||
|
||
<!-- 表尾:分页 -->
|
||
<div class="list-footer">
|
||
<a-pagination
|
||
v-model:current="searchForm.page"
|
||
v-model:page-size="searchForm.pageSize"
|
||
:total="total"
|
||
:show-size-changer="true"
|
||
:show-quick-jumper="true"
|
||
:page-size-options="['10', '20', '50']"
|
||
:show-total="(t: number) => `共 ${t} 条`"
|
||
@change="handlePageChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新建/编辑弹窗 -->
|
||
<a-modal
|
||
v-model:open="formVisible"
|
||
:title="formMode === 'create' ? '新增安装派单' : '编辑安装派单'"
|
||
:confirm-loading="formLoading"
|
||
width="640px"
|
||
@ok="handleSubmit"
|
||
>
|
||
<a-form ref="formRef" :model="form" :rules="formRules" :label-col="{ span: 6 }" :wrapper-col="{ span: 17 }">
|
||
<a-form-item label="安装单号" v-if="formMode === 'edit'">
|
||
<span class="mono">{{ form.installNo }}</span>
|
||
</a-form-item>
|
||
<a-form-item label="关联客户" name="customerId">
|
||
<a-select
|
||
v-model:value="form.customerId"
|
||
placeholder="请选择客户"
|
||
show-search
|
||
option-filter-prop="label"
|
||
:options="customerOptions"
|
||
:disabled="formMode === 'edit'"
|
||
@change="onCustomerChange"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item label="关联方案" name="planId">
|
||
<a-select
|
||
v-model:value="form.planId"
|
||
placeholder="请选择方案"
|
||
show-search
|
||
option-filter-prop="label"
|
||
:options="planOptions"
|
||
:not-found-content="form.customerId ? '该客户暂无方案' : '请先选择客户'"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item label="安装人" name="installer">
|
||
<a-input v-model:value="form.installer" placeholder="请输入安装施工负责人" maxlength="64" />
|
||
</a-form-item>
|
||
<a-form-item label="派单日期" name="dispatchDate">
|
||
<a-date-picker
|
||
v-model:value="form.dispatchDate"
|
||
style="width: 100%"
|
||
placeholder="请选择派单日期"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item label="施工起止" name="period">
|
||
<a-range-picker
|
||
v-model:value="form.period"
|
||
style="width: 100%"
|
||
:placeholder="['开始日期', '结束日期']"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item label="备注" name="remark">
|
||
<a-textarea v-model:value="form.remark" placeholder="选填,施工说明、注意事项等" :rows="3" maxlength="500" />
|
||
</a-form-item>
|
||
</a-form>
|
||
</a-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, computed, onMounted } from 'vue';
|
||
import { message, Modal } from 'ant-design-vue';
|
||
import {
|
||
SearchOutlined,
|
||
ReloadOutlined,
|
||
PlusOutlined,
|
||
} from '@ant-design/icons-vue';
|
||
import dayjs from 'dayjs';
|
||
import {
|
||
installOrderApi,
|
||
INSTALL_STATUS_OPTIONS,
|
||
INSTALL_STATUS,
|
||
type InstallOrderInfo,
|
||
type InstallOrderQuery,
|
||
} from '@/api/installOrder';
|
||
import { customerApi, type CustomerInfo } from '@/api/customer';
|
||
import { projectPlanApi, type ProjectPlanInfo } from '@/api/projectPlan';
|
||
import { useTableScroll } from '@/composables/useTableScroll';
|
||
|
||
const { wrapperRef, tableHeight } = useTableScroll();
|
||
|
||
// ==================== 元数据 ====================
|
||
const statusOptions = INSTALL_STATUS_OPTIONS;
|
||
|
||
// 状态配色:0已派发(琥珀) 1已完成(绿)
|
||
const STATUS_META: Record<number, { label: string; color: string; bg: string; border: string }> = {
|
||
0: { label: '已派发', color: '#d97706', bg: '#fef3c7', border: '#fde68a' },
|
||
1: { label: '已完成', color: '#059669', bg: '#d1fae5', border: '#a7f3d0' },
|
||
};
|
||
|
||
const statusMeta = (v: number) => STATUS_META[v] || STATUS_META[0];
|
||
const pillStyle = (m: { color: string; bg: string; border: string }) => ({
|
||
background: m.bg,
|
||
color: m.color,
|
||
borderColor: m.border,
|
||
});
|
||
|
||
// ==================== 列表数据 ====================
|
||
const loading = ref(false);
|
||
const dataSource = ref<InstallOrderInfo[]>([]);
|
||
const total = ref(0);
|
||
|
||
const searchForm = reactive({
|
||
page: 1,
|
||
pageSize: 10,
|
||
keyword: '',
|
||
customerId: undefined as number | undefined,
|
||
status: undefined as number | undefined,
|
||
dateRange: undefined as any,
|
||
});
|
||
|
||
const columns = [
|
||
{ title: '安装单号', key: 'installNo', width: 170 },
|
||
{ title: '关联客户', key: 'customer', width: 200 },
|
||
{ title: '关联方案', key: 'plan', width: 180 },
|
||
{ title: '安装人', key: 'installer', width: 130 },
|
||
{ title: '派单日期', key: 'dispatchDate', width: 130, align: 'center' as const },
|
||
{ title: '施工起止', key: 'period', width: 220 },
|
||
{ title: '状态', key: 'status', width: 120, align: 'center' as const },
|
||
{ title: '操作', key: 'action', width: 200, align: 'center' as const, fixed: 'right' as const },
|
||
];
|
||
|
||
const rangeStart = computed(() => (total.value === 0 ? 0 : (searchForm.page - 1) * searchForm.pageSize + 1));
|
||
const rangeEnd = computed(() => Math.min(searchForm.page * searchForm.pageSize, total.value));
|
||
|
||
// ==================== 下拉选项 ====================
|
||
const customerOptions = ref<{ label: string; value: number }[]>([]);
|
||
const planOptions = ref<{ label: string; value: number }[]>([]);
|
||
|
||
async function fetchCustomerOptions() {
|
||
try {
|
||
const res = await customerApi.getCustomerOptions();
|
||
customerOptions.value = (res.data || []).map((item: CustomerInfo) => ({
|
||
label: item.customerName,
|
||
value: item.id,
|
||
}));
|
||
} catch {
|
||
customerOptions.value = [];
|
||
}
|
||
}
|
||
|
||
// 按客户拉取方案下拉(方案与客户一致性校验在后端)
|
||
async function fetchPlanOptions(customerId?: number) {
|
||
if (!customerId) {
|
||
planOptions.value = [];
|
||
return;
|
||
}
|
||
try {
|
||
const res = await projectPlanApi.getList({ page: 1, pageSize: 999, customerId });
|
||
planOptions.value = (res.data?.list || []).map((item: ProjectPlanInfo) => ({
|
||
label: item.planDesc ? `${item.planNo} · ${item.planDesc}` : item.planNo,
|
||
value: item.id,
|
||
}));
|
||
} catch {
|
||
planOptions.value = [];
|
||
}
|
||
}
|
||
|
||
// ==================== 数据请求 ====================
|
||
function buildQuery(): InstallOrderQuery {
|
||
const range = searchForm.dateRange as any[] | undefined;
|
||
let dateRange: string | undefined;
|
||
if (range && range.length === 2 && (range[0] || range[1])) {
|
||
const s = range[0] ? dayjs(range[0]).format('YYYY-MM-DD') : '';
|
||
const e = range[1] ? dayjs(range[1]).format('YYYY-MM-DD') : '';
|
||
if (s || e) dateRange = `${s},${e}`;
|
||
}
|
||
return {
|
||
page: searchForm.page,
|
||
pageSize: searchForm.pageSize,
|
||
keyword: searchForm.keyword || undefined,
|
||
customerId: searchForm.customerId,
|
||
status: searchForm.status,
|
||
dateRange,
|
||
};
|
||
}
|
||
|
||
async function fetchList() {
|
||
loading.value = true;
|
||
try {
|
||
const res = await installOrderApi.getList(buildQuery());
|
||
dataSource.value = res.data.list || [];
|
||
total.value = res.data.total || 0;
|
||
} catch {
|
||
dataSource.value = [];
|
||
total.value = 0;
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchList();
|
||
fetchCustomerOptions();
|
||
});
|
||
|
||
// ==================== 筛选与分页 ====================
|
||
function handleSearch() {
|
||
searchForm.page = 1;
|
||
fetchList();
|
||
}
|
||
|
||
function handleReset() {
|
||
searchForm.keyword = '';
|
||
searchForm.customerId = undefined;
|
||
searchForm.status = undefined;
|
||
searchForm.dateRange = undefined;
|
||
searchForm.page = 1;
|
||
fetchList();
|
||
}
|
||
|
||
function handlePageChange(page: number, pageSize: number) {
|
||
searchForm.page = page;
|
||
searchForm.pageSize = pageSize;
|
||
fetchList();
|
||
}
|
||
|
||
// ==================== 新建/编辑弹窗 ====================
|
||
const formVisible = ref(false);
|
||
const formLoading = ref(false);
|
||
const formMode = ref<'create' | 'edit'>('create');
|
||
const formRef = ref();
|
||
const editingId = ref<number | null>(null);
|
||
|
||
const form = reactive({
|
||
installNo: '',
|
||
customerId: undefined as number | undefined,
|
||
planId: undefined as number | undefined,
|
||
installer: '',
|
||
dispatchDate: undefined as any,
|
||
period: undefined as any,
|
||
remark: '',
|
||
});
|
||
|
||
const formRules = {
|
||
customerId: [{ required: true, message: '请选择关联客户', trigger: 'change' }],
|
||
planId: [{ required: true, message: '请选择关联方案', trigger: 'change' }],
|
||
};
|
||
|
||
function resetForm() {
|
||
form.installNo = '';
|
||
form.customerId = undefined;
|
||
form.planId = undefined;
|
||
form.installer = '';
|
||
form.dispatchDate = undefined;
|
||
form.period = undefined;
|
||
form.remark = '';
|
||
planOptions.value = [];
|
||
}
|
||
|
||
function handleAdd() {
|
||
formMode.value = 'create';
|
||
editingId.value = null;
|
||
resetForm();
|
||
formVisible.value = true;
|
||
}
|
||
|
||
function handleEdit(record: InstallOrderInfo) {
|
||
formMode.value = 'edit';
|
||
editingId.value = record.id;
|
||
resetForm();
|
||
form.installNo = record.installNo;
|
||
form.customerId = record.customerId;
|
||
form.planId = record.planId;
|
||
form.installer = record.installer || '';
|
||
form.dispatchDate = record.dispatchDate ? dayjs(record.dispatchDate) : undefined;
|
||
form.period = (record.startDate || record.endDate)
|
||
? [record.startDate ? dayjs(record.startDate) : undefined, record.endDate ? dayjs(record.endDate) : undefined]
|
||
: undefined;
|
||
form.remark = record.remark || '';
|
||
fetchPlanOptions(record.customerId);
|
||
formVisible.value = true;
|
||
}
|
||
|
||
function onCustomerChange() {
|
||
form.planId = undefined;
|
||
fetchPlanOptions(form.customerId);
|
||
}
|
||
|
||
async function handleSubmit() {
|
||
try {
|
||
await formRef.value.validate();
|
||
} catch {
|
||
return;
|
||
}
|
||
const period = form.period as any[] | undefined;
|
||
const payload = {
|
||
customerId: form.customerId!,
|
||
planId: form.planId!,
|
||
installer: form.installer || undefined,
|
||
dispatchDate: form.dispatchDate ? dayjs(form.dispatchDate).format('YYYY-MM-DD') : undefined,
|
||
startDate: period && period[0] ? dayjs(period[0]).format('YYYY-MM-DD') : undefined,
|
||
endDate: period && period[1] ? dayjs(period[1]).format('YYYY-MM-DD') : undefined,
|
||
remark: form.remark || undefined,
|
||
};
|
||
formLoading.value = true;
|
||
try {
|
||
if (formMode.value === 'create') {
|
||
const res = await installOrderApi.create(payload);
|
||
message.success(res.message || '创建成功');
|
||
} else if (editingId.value !== null) {
|
||
const res = await installOrderApi.update(editingId.value, payload);
|
||
message.success(res.message || '更新成功');
|
||
}
|
||
formVisible.value = false;
|
||
fetchList();
|
||
} catch {
|
||
// 拦截器已提示
|
||
} finally {
|
||
formLoading.value = false;
|
||
}
|
||
}
|
||
|
||
// ==================== 行内操作 ====================
|
||
function handleComplete(record: InstallOrderInfo) {
|
||
Modal.confirm({
|
||
title: '确认标记完成?',
|
||
content: `安装单 ${record.installNo} 将变更为「已完成」状态,完成后不可再修改。`,
|
||
okText: '确认完成',
|
||
cancelText: '取消',
|
||
async onOk() {
|
||
try {
|
||
const res = await installOrderApi.complete(record.id);
|
||
message.success(res.message || '已标记完成');
|
||
fetchList();
|
||
} catch {
|
||
// 拦截器已提示
|
||
}
|
||
},
|
||
});
|
||
}
|
||
|
||
function handleDelete(record: InstallOrderInfo) {
|
||
Modal.confirm({
|
||
title: '确认删除该安装派单?',
|
||
content: `安装单 ${record.installNo} 删除后不可恢复,请谨慎操作。`,
|
||
okText: '确认删除',
|
||
okType: 'danger',
|
||
cancelText: '返回',
|
||
async onOk() {
|
||
try {
|
||
const res = await installOrderApi.delete(record.id);
|
||
message.success(res.message || '删除成功');
|
||
fetchList();
|
||
} catch {
|
||
// 拦截器已提示
|
||
}
|
||
},
|
||
});
|
||
}
|
||
|
||
// ==================== 展示辅助 ====================
|
||
function formatDate(value?: string) {
|
||
if (!value) return '—';
|
||
return dayjs(value).format('YYYY-MM-DD');
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.install-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;
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.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: minmax(220px, 2fr) 1fr 1fr 1.6fr auto;
|
||
gap: 12px 16px;
|
||
align-items: end;
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.filter-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
|
||
.filter-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.filter-item-actions {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.filter-label {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #5b6b7e;
|
||
}
|
||
|
||
.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;
|
||
gap: 12px;
|
||
padding: 2px 4px 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.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 .divider {
|
||
color: #cbd5e1;
|
||
}
|
||
|
||
.range-text {
|
||
font-size: 12px;
|
||
color: #5b6b7e;
|
||
}
|
||
|
||
.range-text b {
|
||
font-family: Consolas, Menlo, monospace;
|
||
color: #0d1c2c;
|
||
}
|
||
|
||
.range-text .strong {
|
||
color: #0054cd;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.list-extra {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
font-size: 12px;
|
||
color: #5b6b7e;
|
||
}
|
||
|
||
.table-wrapper {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.list-footer {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
padding-top: 12px;
|
||
margin-top: 8px;
|
||
border-top: 1px solid #eef1f6;
|
||
}
|
||
|
||
.install-no {
|
||
font-family: Consolas, Menlo, monospace;
|
||
font-weight: 700;
|
||
color: #0054cd;
|
||
}
|
||
|
||
.cust-name {
|
||
font-weight: 600;
|
||
color: #0d1c2c;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.type-main {
|
||
font-weight: 600;
|
||
color: #1e293b;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.plan-no {
|
||
font-family: Consolas, Menlo, monospace;
|
||
font-size: 12.5px;
|
||
color: #0d1c2c;
|
||
}
|
||
|
||
.sub-line {
|
||
margin-top: 3px;
|
||
font-size: 11.5px;
|
||
color: #8a97a8;
|
||
line-height: 1.45;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.plan-time {
|
||
font-family: Consolas, Menlo, monospace;
|
||
font-size: 12.5px;
|
||
color: #0d1c2c;
|
||
}
|
||
|
||
.status-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 2px 10px;
|
||
border-radius: 9999px;
|
||
border: 1px solid transparent;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.status-pill .dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.row-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 2px;
|
||
flex-wrap: nowrap;
|
||
}
|
||
|
||
.mono {
|
||
font-family: Consolas, Menlo, monospace;
|
||
color: #0054cd;
|
||
font-weight: 600;
|
||
}
|
||
|
||
:deep(.ant-table) {
|
||
font-size: 13px;
|
||
height: 100%;
|
||
}
|
||
|
||
:deep(.ant-table-thead > tr > th) {
|
||
background: #f4f7ff;
|
||
color: #5b6b7e;
|
||
font-weight: 600;
|
||
font-size: 12px;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
: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;
|
||
}
|
||
</style>
|