更新项目管理模块中合同管理、项目安装、项目验收、回访记录等功能

This commit is contained in:
wk committed 2026-09-30 17:04:55 +08:00
1 parent 307e9cf1b9
commit b3cef22a54
32 files changed
+6094 -2600

No files matched your search

+2
View File
@@ -18,6 +18,8 @@ export interface AcceptanceQuery {
pageSize?: number;
keyword?: string;
customerId?: number;
acceptStartDate?: string;
acceptEndDate?: string;
}
export interface AcceptanceListResult {
+4
View File
@@ -2,11 +2,13 @@ import request from '@/utils/request';
export interface ContractInfo {
id: number;
contractNo: string;
customerId: number;
xtContractNo?: string;
contractAmount?: number;
payCycle?: string;
salesmanId?: number;
salesmanName?: string;
signDate?: string;
contractDesc?: string;
startDate?: string;
@@ -24,6 +26,8 @@ export interface ContractQuery {
customerId?: number;
salesmanId?: number;
status?: number;
signStartDate?: string;
signEndDate?: string;
}
export interface ContractListResult {
+112
View File
@@ -0,0 +1,112 @@
import request from '@/utils/request';
// 安装派单状态:0已派发 1已完成
export const INSTALL_STATUS_OPTIONS = [
{ value: 0, label: '已派发' },
{ value: 1, label: '已完成' }
];
export const INSTALL_STATUS = {
DISPATCHED: 0,
COMPLETED: 1
};
// 关联客户精简字段(列表 decorate 附带)
export interface InstallOrderCustomer {
id: number;
customerNo?: string;
customerName?: string;
contactPerson?: string;
contactPhone?: string;
address?: string;
}
// 共享电工精简字段
export interface InstallOrderWorker {
id: number;
workerName?: string;
phone?: string;
workerTitle?: string;
}
// 关联方案精简字段
export interface InstallOrderPlan {
id: number;
planNo?: string;
planDesc?: string;
status?: number;
}
export interface InstallOrderInfo {
id: number;
installNo: string;
customerId: number;
installer?: string;
workerId?: number;
dispatchDate?: string;
startDate?: string;
endDate?: string;
planId: number;
remark?: string;
status: number;
created_at?: string;
updated_at?: string;
// 关联信息(列表/详情 decorate 附带)
customer?: InstallOrderCustomer;
worker?: InstallOrderWorker;
plan?: InstallOrderPlan;
customerName?: string;
workerName?: string;
planNo?: string;
}
export interface InstallOrderQuery {
page?: number;
pageSize?: number;
keyword?: string;
customerId?: number;
status?: number;
// 派单日期范围,逗号分隔字符串 [startDate,endDate],后端 split 解析
dateRange?: string;
}
export interface InstallOrderListResult {
list: InstallOrderInfo[];
total: number;
page: number;
pageSize: number;
}
export interface InstallOrderCreateParams {
customerId: number;
installer?: string;
workerId?: number;
dispatchDate?: string;
startDate?: string;
endDate?: string;
planId: number;
remark?: string;
status?: number;
}
export type InstallOrderUpdateParams = Partial<InstallOrderCreateParams>;
export const installOrderApi = {
getList: (params: InstallOrderQuery) =>
request.get<InstallOrderListResult>('/install-orders', { params }),
getDetail: (id: number) =>
request.get<InstallOrderInfo>(`/install-orders/${id}`),
create: (data: InstallOrderCreateParams) =>
request.post<InstallOrderInfo>('/install-orders', data),
update: (id: number, data: InstallOrderUpdateParams) =>
request.put<InstallOrderInfo>(`/install-orders/${id}`, data),
// 标记完成:0已派发 → 1已完成
complete: (id: number) =>
request.put<InstallOrderInfo>(`/install-orders/${id}/complete`),
delete: (id: number) => request.delete(`/install-orders/${id}`)
};
+2
View File
@@ -29,6 +29,8 @@ export interface ProjectPlanQuery {
keyword?: string;
customerId?: number;
status?: number;
registerStartDate?: string;
registerEndDate?: string;
}
export interface ProjectPlanListResult {
+17
View File
@@ -9,6 +9,13 @@ export interface UploadImageResult {
size: number;
}
export interface UploadFileResult {
key: string;
url: string;
name: string;
size: number;
}
// 上传现场照片到对象存储(RustFS),返回对象 key 与预签名 URL
export const uploadApi = {
uploadImage: (file: File) => {
@@ -19,5 +26,15 @@ export const uploadApi = {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 60000
});
},
// 上传方案/合同/验收等业务文件到对象存储,返回对象 key 与预签名 URL
uploadFile: (file: File) => {
const formData = new FormData();
formData.append('files', file);
return request.post<UploadFileResult>('/upload/file', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 60000
});
}
};
+3
View File
@@ -2,6 +2,7 @@ import request from '@/utils/request';
export interface VisitRecordInfo {
id: number;
visitNo?: string;
customerId: number;
contactPerson?: string;
contactPhone?: string;
@@ -17,6 +18,8 @@ export interface VisitRecordQuery {
pageSize?: number;
keyword?: string;
customerId?: number;
visitStartDate?: string;
visitEndDate?: string;
}
export interface VisitRecordListResult {
+6
View File
@@ -145,6 +145,12 @@ const routes: RouteRecordRaw[] = [
component: () => import("@/views/project/visit/index.vue"),
meta: { requiresAuth: true, title: "回访记录", permission: "project:visit" }
},
{
path: "project/install",
name: "ProjectInstall",
component: () => import("@/views/project/install/index.vue"),
meta: { requiresAuth: true, title: "安装派单", permission: "project:install" }
},
{
path: "dispatch",
name: "Dispatch",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+873
View File
@@ -0,0 +1,873 @@
<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>
File diff suppressed because it is too large. Load diff
+12
View File
@@ -208,6 +208,18 @@
</button>
</div>
</div>
<div class="field span-2">
<label class="field-label"><span>备注</span></label>
<a-textarea
v-model:value="formState.remark"
:rows="3"
:maxlength="500"
show-count
placeholder="派单备注说明,如现场特殊要求、安全交底等"
:disabled="isReadonly"
/>
</div>
</div>
</section>
File diff suppressed because it is too large. Load diff
+564 -201
View File
@@ -1,75 +1,168 @@
<template>
<div class="page-management">
<a-card title="回访记录管理">
<template #extra>
<a-space>
<div class="visit-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">VISIT RECORD LEDGER</span>
</div>
<p class="desc">登记项目交付后的客户回访记录,跟踪回访人、回访时间与客户意见反馈全流程。</p>
</div>
<div class="header-actions">
<a-button type="primary" @click="handleAdd">
<template #icon><PlusOutlined /></template>
新增回访
</a-button>
</a-space>
</template>
</div>
</div>
</div>
<div class="search-bar">
<a-input
v-model:value="searchForm.keyword"
placeholder="联系人/回访人"
allow-clear
style="width: 220px"
@pressEnter="handleSearch"
>
<template #prefix><SearchOutlined /></template>
</a-input>
<a-button type="primary" @click="handleSearch">
<template #icon><SearchOutlined /></template>
查询
</a-button>
<a-button @click="handleReset">
<template #icon><ReloadOutlined /></template>
重置
</a-button>
<!-- 主体卡片 -->
<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 filter-item-date">
<label class="filter-label">回访日期范围</label>
<a-range-picker
v-model:value="visitRange"
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="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 === 'customerName'">
{{ getCustomerName(record.customerId) }}
</template>
<template v-if="column.key === 'visitTime'">
{{ record.visitTime || '-' }}
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="handleEdit(record)">
<template #icon><EditOutlined /></template>
编辑
</a-button>
<a-popconfirm title="确定要删除该回访记录吗?" ok-text="确定" cancel-text="取消" @confirm="handleDelete(record.id)">
<a-button type="link" size="small" danger>
<template #icon><DeleteOutlined /></template>
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
<!-- 列表区 -->
<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>
<template #emptyText>
<a-empty description="暂无回访记录" />
</template>
</a-table>
<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 === 'customerName'">
<div class="cust-name">{{ getCustomerName(record.customerId) }}</div>
<div class="sub-line">{{ record.contactPerson || '—' }}{{ record.contactPhone ? ' · ' + record.contactPhone : '' }}</div>
</template>
<template v-else-if="column.key === 'visitor'">
{{ record.visitor || '—' }}
</template>
<template v-else-if="column.key === 'visitTime'">
<div class="plan-time">{{ record.visitTime || '—' }}</div>
</template>
<template v-else-if="column.key === 'customerOpinion'">
<div v-if="record.customerOpinion" class="sub-line">{{ record.customerOpinion }}</div>
<div v-else>—</div>
</template>
<template v-else-if="column.key === 'action'">
<div class="row-actions">
<a-button type="primary" size="small" @click="handleEdit(record)">编辑</a-button>
<a-dropdown trigger="click">
<a-button type="link" size="small">更多 <DownOutlined /></a-button>
<template #overlay>
<a-menu @click="({ key }) => handleMore(key as string, record)">
<a-menu-item key="delete" danger>删除</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</template>
</template>
<template #emptyText>
<a-empty description="暂无回访记录" />
</template>
</a-table>
</div>
<!-- 表尾分页 -->
<div class="list-footer">
<span class="range-text">
共 <b class="strong">{{ total }}</b> 条
</span>
<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>
</a-card>
</div>
<!-- 新建/编辑弹窗 -->
<a-modal
v-model:open="modalVisible"
:title="modalTitle"
@@ -107,7 +200,7 @@
<a-date-picker v-model:value="formData.visitTime" style="width: 100%" placeholder="请选择回访时间" />
</a-form-item>
<a-form-item label="客户意见" name="customerOpinion">
<a-textarea v-model:value="formData.customerOpinion" :rows="3" placeholder="请输入客户意见" maxlength="2000" />
<a-textarea v-model:value="formData.customerOpinion" :rows="3" placeholder="请输入客户意见反馈" maxlength="2000" />
</a-form-item>
</a-form>
</a-modal>
@@ -116,26 +209,119 @@
<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 { message, Modal } from 'ant-design-vue';
import { SearchOutlined, ReloadOutlined, PlusOutlined, DownOutlined } from '@ant-design/icons-vue';
import dayjs from 'dayjs';
import { visitRecordApi, type VisitRecordInfo, type VisitRecordQuery } from '@/api/visitRecord';
import { customerApi } from '@/api/customer';
import { customerApi, type CustomerInfo } from '@/api/customer';
import { useTableScroll } from '@/composables/useTableScroll';
const { wrapperRef, tableHeight } = useTableScroll();
// ==================== 列表数据 ====================
const loading = ref(false);
const dataSource = ref<VisitRecordInfo[]>([]);
const total = ref(0);
const searchForm = reactive<VisitRecordQuery>({ page: 1, pageSize: 10, keyword: '' });
const searchForm = reactive<VisitRecordQuery>({
page: 1,
pageSize: 10,
keyword: '',
customerId: undefined as number | undefined,
});
const visitRange = ref<any>(undefined);
const columns = [
{ title: '客户', key: 'customerName', width: 220 },
{ title: '回访人', key: 'visitor', width: 120 },
{ title: '回访时间', key: 'visitTime', width: 160 },
{ title: '回访内容摘要', key: 'customerOpinion', ellipsis: true },
{ title: '操作', key: 'action', align: 'center' as const, width: 170, 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 }[]>([]);
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 = [];
}
}
function getCustomerName(customerId?: number): string {
if (!customerId) return '—';
const found = customerOptions.value.find((c) => c.value === customerId);
return found ? found.label : `#${customerId}`;
}
// ==================== 数据请求 ====================
function buildQuery(): VisitRecordQuery {
const range = visitRange.value as any[] | undefined;
return {
page: searchForm.page,
pageSize: searchForm.pageSize,
keyword: searchForm.keyword || undefined,
customerId: searchForm.customerId,
visitStartDate: range?.[0] ? range[0].format('YYYY-MM-DD') : undefined,
visitEndDate: range?.[1] ? range[1].format('YYYY-MM-DD') : undefined,
};
}
async function fetchList() {
loading.value = true;
try {
const res = await visitRecordApi.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;
visitRange.value = undefined;
searchForm.page = 1;
fetchList();
}
function handlePageChange(page: number, pageSize: number) {
searchForm.page = page;
searchForm.pageSize = pageSize;
fetchList();
}
// ==================== 弹窗表单 ====================
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 customerOptions = ref<{ label: string; value: number }[]>([]);
const formData = reactive({
customerId: undefined as number | undefined,
@@ -151,78 +337,18 @@ const formRules = {
contactPhone: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }],
};
const columns = [
{ title: '所属客户', key: 'customerName', ellipsis: true },
{ title: '联系人', dataIndex: 'contactPerson', key: 'contactPerson', ellipsis: true },
{ title: '联系电话', dataIndex: 'contactPhone', key: 'contactPhone', ellipsis: true },
{ title: '回访人', dataIndex: 'visitor', key: 'visitor', ellipsis: true },
{ title: '回访时间', key: 'visitTime', width: 110 },
{ title: '客户意见', dataIndex: 'customerOpinion', key: 'customerOpinion', ellipsis: true },
{ title: '操作', key: 'action', width: 140, fixed: 'right' as const },
];
const pagination = computed(() => ({
current: searchForm.page,
pageSize: searchForm.pageSize,
total: total.value,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t: number) => `共 ${t} 条数据`,
}));
onMounted(() => {
fetchList();
fetchCustomerOptions();
});
async function fetchList() {
loading.value = true;
try {
const res: any = await visitRecordApi.getList(searchForm);
if (res.code === 200) {
dataSource.value = res.data.list;
total.value = res.data.total;
}
} catch {
message.error('获取回访记录失败');
} finally {
loading.value = false;
function resetForm() {
formData.customerId = undefined;
formData.contactPerson = '';
formData.contactPhone = '';
formData.visitor = '';
formData.visitTime = undefined;
formData.customerOpinion = '';
if (formRef.value) {
formRef.value.resetFields();
}
}
async function fetchCustomerOptions() {
try {
const res: any = await customerApi.getCustomerOptions();
if (res.code === 200) {
customerOptions.value = res.data.map((item: any) => ({ label: item.customerName, value: item.id }));
}
} catch {
console.error('获取客户选项失败');
}
}
function getCustomerName(customerId: number): string {
const found = customerOptions.value.find((c) => c.value === customerId);
return found ? found.label : `#${customerId}`;
}
function handleSearch() {
searchForm.page = 1;
fetchList();
}
function handleReset() {
searchForm.keyword = '';
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;
@@ -237,26 +363,15 @@ function handleEdit(record: VisitRecordInfo) {
modalTitle.value = '编辑回访';
resetForm();
formData.customerId = record.customerId;
formData.contactPerson = record.contactPerson;
formData.contactPhone = record.contactPhone;
formData.visitor = record.visitor;
formData.contactPerson = record.contactPerson || '';
formData.contactPhone = record.contactPhone || '';
formData.visitor = record.visitor || '';
formData.visitTime = record.visitTime ? dayjs(record.visitTime) : undefined;
formData.customerOpinion = record.customerOpinion;
formData.customerOpinion = record.customerOpinion || '';
modalVisible.value = true;
}
function resetForm() {
formData.customerId = undefined;
formData.contactPerson = '';
formData.contactPhone = '';
formData.visitor = '';
formData.visitTime = undefined;
formData.customerOpinion = '';
if (formRef.value) {
formRef.value.resetFields();
}
}
// ==================== 提交 ====================
async function handleSubmit() {
try {
await formRef.value.validate();
@@ -266,31 +381,26 @@ async function handleSubmit() {
modalLoading.value = true;
try {
const submitData: any = {
const submitData = {
customerId: formData.customerId,
contactPerson: formData.contactPerson,
contactPhone: formData.contactPhone,
visitor: formData.visitor,
contactPerson: formData.contactPerson || undefined,
contactPhone: formData.contactPhone || undefined,
visitor: formData.visitor || undefined,
visitTime: formData.visitTime ? formData.visitTime.format('YYYY-MM-DD') : undefined,
customerOpinion: formData.customerOpinion,
customerOpinion: formData.customerOpinion || undefined,
};
let res: any;
if (isEdit.value && editId.value) {
res = await visitRecordApi.update(editId.value, submitData);
await visitRecordApi.update(editId.value, submitData);
message.success('更新成功');
} else {
res = await visitRecordApi.create(submitData);
}
if (res.code === 200) {
message.success(isEdit.value ? '更新成功' : '创建成功');
modalVisible.value = false;
fetchList();
} else {
message.error(res.message || '操作失败');
await visitRecordApi.create(submitData);
message.success('创建成功');
}
modalVisible.value = false;
fetchList();
} catch {
message.error('操作失败');
// 拦截器已提示
} finally {
modalLoading.value = false;
}
@@ -301,61 +411,314 @@ function handleCancel() {
resetForm();
}
async function handleDelete(id: number) {
try {
const res: any = await visitRecordApi.delete(id);
if (res.code === 200) {
message.success('删除成功');
fetchList();
} else {
message.error(res.message || '删除失败');
}
} catch {
message.error('删除失败');
}
// ==================== 行内操作 ====================
function handleMore(key: string, record: VisitRecordInfo) {
if (key === 'delete') return confirmDelete(record);
}
function confirmDelete(record: VisitRecordInfo) {
Modal.confirm({
title: '确认删除该回访记录?',
content: `客户「${getCustomerName(record.customerId)}」的回访记录将被删除,此操作不可恢复。`,
okText: '确认删除',
okType: 'danger',
cancelText: '取消',
async onOk() {
try {
await visitRecordApi.delete(record.id);
message.success('删除成功');
fetchList();
} catch {
// 拦截器已提示
}
},
});
}
</script>
<style scoped>
.page-management {
.visit-page {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 20px;
padding: 20px 24px;
box-sizing: border-box;
background: linear-gradient(180deg, #eef3f8 0%, #e8eefb 100%);
}
:deep(.ant-card) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
:deep(.ant-card-body) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.search-bar {
.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;
justify-content: flex-end;
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 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: space-between;
gap: 12px;
flex-wrap: wrap;
padding-top: 12px;
margin-top: 8px;
border-top: 1px solid #eef1f6;
}
.cust-name {
font-weight: 600;
color: #0d1c2c;
line-height: 1.4;
}
.plan-time {
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.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.row-actions {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
flex-wrap: nowrap;
}
:deep(.ant-table) {
font-size: 14px;
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;
}
</style>
: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>
+266 -43
View File
@@ -41,7 +41,7 @@
resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz"
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.23.0":
"@babel/core@^7.23.0":
version "7.29.7"
resolved "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz"
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
@@ -275,14 +275,6 @@
"@babel/types" "^7.29.7"
debug "^4.3.1"
"@babel/types@^7.28.2", "@babel/types@^7.29.7":
version "7.29.7"
resolved "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz"
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
dependencies:
"@babel/helper-string-parser" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/types@7.23.0":
version "7.23.0"
resolved "https://registry.npmmirror.com/@babel/types/-/types-7.23.0.tgz"
@@ -292,6 +284,14 @@
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@babel/types@^7.28.2", "@babel/types@^7.29.7":
version "7.29.7"
resolved "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz"
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
dependencies:
"@babel/helper-string-parser" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@ctrl/tinycolor@^3.4.0", "@ctrl/tinycolor@^3.5.0":
version "3.6.1"
resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz"
@@ -307,6 +307,116 @@
resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz"
integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==
"@esbuild/aix-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
"@esbuild/android-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
"@esbuild/android-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
"@esbuild/android-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
"@esbuild/darwin-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
"@esbuild/darwin-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
"@esbuild/freebsd-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
"@esbuild/freebsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
"@esbuild/linux-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
"@esbuild/linux-arm@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
"@esbuild/linux-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
"@esbuild/linux-loong64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
"@esbuild/linux-mips64el@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
"@esbuild/linux-ppc64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
"@esbuild/linux-riscv64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
"@esbuild/linux-s390x@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
"@esbuild/linux-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
"@esbuild/netbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
"@esbuild/openbsd-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
"@esbuild/sunos-x64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
"@esbuild/win32-arm64@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
"@esbuild/win32-ia32@0.21.5":
version "0.21.5"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
"@esbuild/win32-x64@0.21.5":
version "0.21.5"
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz"
@@ -405,7 +515,7 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -418,6 +528,121 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@rollup/rollup-android-arm-eabi@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz#3a04f01e9f01392bbef5920b94aa3b88794be7ab"
integrity sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==
"@rollup/rollup-android-arm64@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz#e371b653ceabc900790ae73f5548a0fd7cd63a70"
integrity sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==
"@rollup/rollup-darwin-arm64@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz#2a5aa70432e39816d666d79287a7324cfc3b4e72"
integrity sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==
"@rollup/rollup-darwin-x64@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz#c3b5b49629379cd9cdc5d841bf00ed44ebf393dd"
integrity sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==
"@rollup/rollup-freebsd-arm64@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz#f929d8e0462fae6602fc960beeabd7287d859283"
integrity sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==
"@rollup/rollup-freebsd-x64@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz#c01cb58031226f95d0900b1ec847f4fb32c6e809"
integrity sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==
"@rollup/rollup-linux-arm-gnueabihf@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz#f29d890c4858c8e0d3be01677eef4f6a359eed9d"
integrity sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==
"@rollup/rollup-linux-arm-musleabihf@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz#1ebfc8eb9f66136ed2faae5f44995add5ca3c964"
integrity sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==
"@rollup/rollup-linux-arm64-gnu@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz#c1fa823c2c4ce46ba7f61de1a4c3fdadd4fb4e7b"
integrity sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==
"@rollup/rollup-linux-arm64-musl@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz#a7f18854d0471b78bda8ea38f0891a4e059b571d"
integrity sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==
"@rollup/rollup-linux-loong64-gnu@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz#83658a9a4576bcce8cef85b2c78b9b649d2200c4"
integrity sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==
"@rollup/rollup-linux-loong64-musl@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz#fd2af677ae3417bb58d57ae37dd0d84686e40244"
integrity sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==
"@rollup/rollup-linux-ppc64-gnu@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz#6481647181c4cf8f1ddbd99f62c84cfc56c1a94a"
integrity sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==
"@rollup/rollup-linux-ppc64-musl@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz#18610a1a1550e28a5042ca916f898419540f17f4"
integrity sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==
"@rollup/rollup-linux-riscv64-gnu@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz#597bb80465a2621dbe0de0a41c66394a8a7e9a6e"
integrity sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==
"@rollup/rollup-linux-riscv64-musl@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz#a2a919a9f927ef7f24a60af77e3cb55f1ad59e4d"
integrity sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==
"@rollup/rollup-linux-s390x-gnu@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz#3166f6ceae7df9bbfddf9f36be1937231e13e3c6"
integrity sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==
"@rollup/rollup-linux-x64-gnu@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz#23c9bf79771d804fb87415eb0767569f273261e5"
integrity sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==
"@rollup/rollup-linux-x64-musl@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz#97941c6b94d67fe25cde0f027c10a19f2d1fdd39"
integrity sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==
"@rollup/rollup-openbsd-x64@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz#7aeb7d92e2cd1d399f56daf75c39040b777b6c77"
integrity sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==
"@rollup/rollup-openharmony-arm64@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz#925de61ae83bf99aa636e8acea87432e8c0ffaab"
integrity sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==
"@rollup/rollup-win32-arm64-msvc@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz#888ab83842721491044c46a7407e1f38f3235bb4"
integrity sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==
"@rollup/rollup-win32-ia32-msvc@4.60.4":
version "4.60.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz#fa30ac24e3f0232139d2a47500560a28695764d4"
integrity sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==
"@rollup/rollup-win32-x64-gnu@4.60.4":
version "4.60.4"
resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz"
@@ -441,7 +666,7 @@
resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
"@types/node@^18.0.0 || >=20.0.0", "@types/node@^22.15.30":
"@types/node@^22.15.30":
version "22.19.19"
resolved "https://registry.npmmirror.com/@types/node/-/node-22.19.19.tgz"
integrity sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==
@@ -463,7 +688,7 @@
natural-compare "^1.4.0"
ts-api-utils "^1.3.0"
"@typescript-eslint/parser@^7.0.0", "@typescript-eslint/parser@^7.0.1":
"@typescript-eslint/parser@^7.0.1":
version "7.18.0"
resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-7.18.0.tgz"
integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==
@@ -539,14 +764,14 @@
resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz"
integrity sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==
"@volar/language-core@~1.11.1", "@volar/language-core@1.11.1":
"@volar/language-core@1.11.1", "@volar/language-core@~1.11.1":
version "1.11.1"
resolved "https://registry.npmmirror.com/@volar/language-core/-/language-core-1.11.1.tgz"
integrity sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==
dependencies:
"@volar/source-map" "1.11.1"
"@volar/source-map@~1.11.1", "@volar/source-map@1.11.1":
"@volar/source-map@1.11.1", "@volar/source-map@~1.11.1":
version "1.11.1"
resolved "https://registry.npmmirror.com/@volar/source-map/-/source-map-1.11.1.tgz"
integrity sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==
@@ -603,7 +828,7 @@
estree-walker "^2.0.2"
source-map-js "^1.2.1"
"@vue/compiler-dom@^3.3.0", "@vue/compiler-dom@^3.3.4", "@vue/compiler-dom@3.5.34":
"@vue/compiler-dom@3.5.34", "@vue/compiler-dom@^3.3.0", "@vue/compiler-dom@^3.3.4":
version "3.5.34"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz"
integrity sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==
@@ -611,7 +836,7 @@
"@vue/compiler-core" "3.5.34"
"@vue/shared" "3.5.34"
"@vue/compiler-sfc@^3.5.18", "@vue/compiler-sfc@3.5.34":
"@vue/compiler-sfc@3.5.34", "@vue/compiler-sfc@^3.5.18":
version "3.5.34"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz"
integrity sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==
@@ -696,7 +921,7 @@
"@vue/reactivity" "3.5.34"
"@vue/shared" "3.5.34"
"@vue/runtime-dom@^3.4.15", "@vue/runtime-dom@3.5.34":
"@vue/runtime-dom@3.5.34", "@vue/runtime-dom@^3.4.15":
version "3.5.34"
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz"
integrity sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==
@@ -714,7 +939,7 @@
"@vue/compiler-ssr" "3.5.34"
"@vue/shared" "3.5.34"
"@vue/shared@^3.3.0", "@vue/shared@^3.5.18", "@vue/shared@3.5.34":
"@vue/shared@3.5.34", "@vue/shared@^3.3.0", "@vue/shared@^3.5.18":
version "3.5.34"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.34.tgz"
integrity sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==
@@ -729,7 +954,7 @@ acorn-jsx@^5.3.2:
resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.14.0, acorn@^8.9.0:
acorn@^8.14.0, acorn@^8.9.0:
version "8.16.0"
resolved "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz"
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
@@ -902,7 +1127,7 @@ braces@^3.0.3, braces@~3.0.2:
dependencies:
fill-range "^7.1.1"
browserslist@^4.24.0, browserslist@^4.28.2, "browserslist@>= 4.21.0":
browserslist@^4.24.0, browserslist@^4.28.2:
version "4.28.2"
resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz"
integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==
@@ -1056,7 +1281,7 @@ de-indent@^1.0.2:
resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz"
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4:
debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
version "4.4.3"
resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
@@ -1116,7 +1341,7 @@ dunder-proto@^1.0.1:
es-errors "^1.3.0"
gopd "^1.2.0"
echarts@^6.0.0, echarts@^6.1.0:
echarts@^6.1.0:
version "6.1.0"
resolved "https://registry.npmmirror.com/echarts/-/echarts-6.1.0.tgz"
integrity sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==
@@ -1234,7 +1459,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", eslint@^8.56.0, eslint@>=6.0.0:
eslint@^8.56.0:
version "8.57.1"
resolved "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz"
integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
@@ -1416,6 +1641,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz"
@@ -1651,7 +1881,7 @@ isexe@^2.0.0:
resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
jiti@^1.21.7, jiti@>=1.21.0:
jiti@^1.21.7:
version "1.21.7"
resolved "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz"
integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
@@ -1705,7 +1935,7 @@ kolorist@^1.8.0:
resolved "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz"
integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
less@*, less@^4.6.4:
less@^4.6.4:
version "4.6.4"
resolved "https://registry.npmmirror.com/less/-/less-4.6.4.tgz"
integrity sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==
@@ -1837,14 +2067,7 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimatch@^9.0.3:
version "9.0.9"
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz"
integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==
dependencies:
brace-expansion "^2.0.2"
minimatch@^9.0.4:
minimatch@^9.0.3, minimatch@^9.0.4:
version "9.0.9"
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz"
integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==
@@ -2015,7 +2238,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz"
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
"picomatch@^3 || ^4", picomatch@^4.0.4:
picomatch@^4.0.4:
version "4.0.4"
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz"
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
@@ -2085,7 +2308,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.35, postcss@^8.4.43, postcss@^8.4.47, postcss@^8.5.14, postcss@>=8.0.9:
postcss@^8.4.35, postcss@^8.4.43, postcss@^8.4.47, postcss@^8.5.14:
version "8.5.15"
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz"
integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==
@@ -2416,16 +2639,16 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
tslib@^2.8.1:
version "2.8.1"
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
tslib@2.3.0:
version "2.3.0"
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
tslib@^2.8.1:
version "2.8.1"
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz"
@@ -2438,7 +2661,7 @@ type-fest@^0.20.2:
resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
typescript@*, typescript@>=4.2.0, typescript@>=4.5.0, typescript@~5.3.3, typescript@5.x:
typescript@~5.3.3:
version "5.3.3"
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.3.3.tgz"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
@@ -2508,7 +2731,7 @@ vite-plugin-vue-dev-locator@1.0.3:
kolorist "^1.8.0"
magic-string "^0.30.4"
"vite@^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0", "vite@^5.0.0 || ^6.0.0", vite@^5.0.12, vite@>=4.0.0:
vite@^5.0.12:
version "5.4.21"
resolved "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz"
integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==
@@ -2568,7 +2791,7 @@ vue-types@^3.0.0:
dependencies:
is-plain-object "3.0.1"
vue@^3.0.0, vue@^3.2.25, vue@^3.3.0, vue@^3.4.0, vue@^3.4.15, vue@^3.5.0, vue@^3.5.11, vue@>=3.0.1, vue@>=3.0.3, vue@>=3.2.0, vue@3.5.34:
vue@^3.4.15:
version "3.5.34"
resolved "https://registry.npmmirror.com/vue/-/vue-3.5.34.tgz"
integrity sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==