Commit d8833b65 by suyuchen

feat(crawler): 添加爬虫网站管理功能

- 实现爬虫网站列表展示,包含网站名称、URL、请求部门等信息 - 添加网站新增、编辑、删除功能,支持表单验证和数据保存 - 实现爬取状态管理,包含正常、失败、暂停、待审核等状态 - 添加负责人字段管理,支持爬虫任务分配和跟踪 - 实现爬取频率设置和下次爬取时间计算功能 - 添加防爬机制配置和反爬策略管理 - 实现数据量统计和成功率监控功能 - 优化界面样式,提升用户体验和操作便捷性
parent 01256ea8
# 客户信息页面字段扩展需求
# 客户信息页面字段扩展需求
## 需求概述
为提升客户信息管理功能,需在客户信息页面增加VIP等级和负责人字段,以支持更精细化的客户分类和责任分配。
## 具体需求
### 列表页面
- 新增 [VIP等级](file:///D:/jimai/奥智项目/1,2,3/客户信息/.md#L9-L9)
- 新增 [负责人](file:///D:/jimai/奥智项目/1,2,3/客户信息/.md#L10-L10)
### 表单页面
- 新增 [VIP等级](file:///D:/jimai/奥智项目/1,2,3/客户信息/.md#L9-L9) 字段
- 新增 [负责人](file:///D:/jimai/奥智项目/1,2,3/客户信息/.md#L10-L10) 字段
## 预期效果
通过新增字段,实现对VIP客户的精准识别和专属负责人分配,提升客户服务水平和管理效率。
\ No newline at end of file
# 工单列表字段扩展需求
# 工单列表字段扩展需求
## 需求概述
为完善工单信息展示和处理流程,需在工单列表及表单中增加多个字段,并实现SLA计时功能,以提升工单处理效率和客户服务质量。
## 具体需求
### 列表页面
新增以下列:
- SLA计时
- 客户名称
- 服务订单号
- 设备信息
- 设备序列号
- 国家/地区
### 表单页面
新增以下字段:
- 客户名称
- 服务订单号
- 设备信息
- 设备序列号
- 国家/地区
### 特殊逻辑需求
1. 在表单原有字段"问题类型"中新增"紧急/停车"类别
2. "紧急/停车"类别需根据登录用户权限控制显示
3. 选择"紧急/停车"类型时自动启用SLA计时功能
4. 默认设置为T1级别,该级别包含以下字段:
- 首次响应时间
- 处理超时时间
- 通知对象
- 升级路径
5. SLA计时规则在"基本设置模块"的"升级规则配置"中维护
## 预期效果
通过新增字段和SLA计时功能,实现工单信息的全面展示和及时处理,确保紧急问题得到优先处理和有效跟踪。
\ No newline at end of file
<!DOCTYPE html> <!DOCTYPE html>
...@@ -785,6 +785,7 @@ ...@@ -785,6 +785,7 @@
display: flex; display: flex;
gap: 15px; gap: 15px;
margin-bottom: 15px; margin-bottom: 15px;
/*margin-right: 20px;*/
} }
.add-modal-form .form-row .form-group { .add-modal-form .form-row .form-group {
...@@ -1313,7 +1314,7 @@ ...@@ -1313,7 +1314,7 @@
} }
.container { .container {
max-width: 1000px; /*max-width: 1000px;*/
/*margin: 0 auto;*/ /*margin: 0 auto;*/
} }
...@@ -1382,7 +1383,7 @@ ...@@ -1382,7 +1383,7 @@
.form-row { .form-row {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 20px; gap: 100px;
} }
.form-actions { .form-actions {
...@@ -1464,7 +1465,11 @@ ...@@ -1464,7 +1465,11 @@
<div class="container"> <div class="container">
<div class="card" style="padding: 20px;"> <div class="card" style="padding: 20px;">
<div class="page-header">
<div>
<h1 class="page-title" data-i18n="title">{{ isEdit ? '编辑' : '新增' }}人工费用</h1>
</div>
</div>
<form @submit.prevent="handleSubmit"> <form @submit.prevent="handleSubmit">
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
......
<!DOCTYPE html> <!DOCTYPE html>
...@@ -480,7 +480,7 @@ ...@@ -480,7 +480,7 @@
/* 按钮样式 */ /* 按钮样式 */
.btn { .btn {
padding: 10px 20px; padding: 4px 14px;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
...@@ -489,7 +489,7 @@ ...@@ -489,7 +489,7 @@
transition: all 0.2s; transition: all 0.2s;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; margin-right: 10px;
} }
.btn-primary { .btn-primary {
...@@ -1331,11 +1331,15 @@ ...@@ -1331,11 +1331,15 @@
<div class="container"> <div class="container">
<div class="card" style="padding: 20px;"> <div class="card" style="padding: 20px;">
<div class="page-header">
<div>
<h1 class="page-title" data-i18n="title">人工费用</h1>
</div>
<div class="toolbar" style="display:flex;justify-content: end;margin-bottom: 20px;"> <div class="toolbar" style="display:flex;justify-content: end;margin-bottom: 20px;">
<button class="btn btn-primary" style="text-align: right;" @click="handleAdd">+ 新增人工费用 <button class="btn btn-primary" style="text-align: right;padding: 8px 28px;" @click="handleAdd">+ 新增人工费用
</button> </button>
</div> </div>
</div>
<table class="data-table"> <table class="data-table">
<thead> <thead>
<tr> <tr>
...@@ -1364,8 +1368,8 @@ ...@@ -1364,8 +1368,8 @@
</td> </td>
<td> <td>
<div class="action-btns"> <div class="action-btns">
<!-- <button class="btn btn-primary btn-small" @click="handleEdit(item)">编辑</button>--> <button class="btn btn-primary btn-small" @click="handleEdit(item)">编辑</button>
<svg t="1767688375858" class="icon" viewBox="0 0 1024 1024" version="1.1" <!-- <svg t="1767688375858" class="icon" viewBox="0 0 1024 1024" version="1.1"
@click="handleEdit(item)" style="cursor: pointer;margin-right: 5px;" @click="handleEdit(item)" style="cursor: pointer;margin-right: 5px;"
xmlns="http://www.w3.org/2000/svg" p-id="4934" width="20" height="20"> xmlns="http://www.w3.org/2000/svg" p-id="4934" width="20" height="20">
<path d="M800.8 956.7H232.7c-89.3 0-162-72.7-162-162V226.6c0-89.3 72.7-162 162-162h218.5c17.7 0 32 14.3 32 32s-14.3 32-32 32H232.7c-54 0-98 44-98 98v568.1c0 54 44 98 98 98h568.1c54 0 98-44 98-98V586.6c0-17.7 14.3-32 32-32s32 14.3 32 32v208.1c0 89.3-72.7 162-162 162z" <path d="M800.8 956.7H232.7c-89.3 0-162-72.7-162-162V226.6c0-89.3 72.7-162 162-162h218.5c17.7 0 32 14.3 32 32s-14.3 32-32 32H232.7c-54 0-98 44-98 98v568.1c0 54 44 98 98 98h568.1c54 0 98-44 98-98V586.6c0-17.7 14.3-32 32-32s32 14.3 32 32v208.1c0 89.3-72.7 162-162 162z"
...@@ -1374,33 +1378,33 @@ ...@@ -1374,33 +1378,33 @@
p-id="4936"></path> p-id="4936"></path>
<path d="M350.8 709.2c-8.4 0-16.5-3.3-22.6-9.4-8.1-8.1-11.2-19.9-8.3-30.9L366.2 496c3-11 11.6-19.7 22.6-22.6 11-3 22.8 0.2 30.9 8.3l126.6 126.6c8.1 8.1 11.2 19.9 8.3 30.9-3 11-11.6 19.7-22.6 22.6l-172.9 46.3c-2.8 0.7-5.5 1.1-8.3 1.1z m62.9-143.1L396 632l65.8-17.6-48.1-48.3zM848 316c-8.3 0-16.6-3.2-22.8-9.6L707 186.1c-12.4-12.6-12.2-32.9 0.4-45.3 12.6-12.4 32.9-12.2 45.3 0.4l118.2 120.3c12.4 12.6 12.2 32.9-0.4 45.3-6.3 6.1-14.4 9.2-22.5 9.2z" <path d="M350.8 709.2c-8.4 0-16.5-3.3-22.6-9.4-8.1-8.1-11.2-19.9-8.3-30.9L366.2 496c3-11 11.6-19.7 22.6-22.6 11-3 22.8 0.2 30.9 8.3l126.6 126.6c8.1 8.1 11.2 19.9 8.3 30.9-3 11-11.6 19.7-22.6 22.6l-172.9 46.3c-2.8 0.7-5.5 1.1-8.3 1.1z m62.9-143.1L396 632l65.8-17.6-48.1-48.3zM848 316c-8.3 0-16.6-3.2-22.8-9.6L707 186.1c-12.4-12.6-12.2-32.9 0.4-45.3 12.6-12.4 32.9-12.2 45.3 0.4l118.2 120.3c12.4 12.6 12.2 32.9-0.4 45.3-6.3 6.1-14.4 9.2-22.5 9.2z"
p-id="4937"></path> p-id="4937"></path>
</svg> </svg>-->
<!--<button <button
v-if="item.status === '启用'" v-if="item.status === '启用'"
class="btn btn-warning btn-small" class="btn btn-warning btn-small"
@click="handleToggleStatus(item)"> @click="handleToggleStatus(item)">
停用 停用
</button>--> </button>
<svg t="1767688450917" class="icon" viewBox="0 0 1024 1024" version="1.1" <!-- <svg t="1767688450917" class="icon" viewBox="0 0 1024 1024" version="1.1"
v-if="item.status === '启用'" @click="handleToggleStatus(item)" style="cursor: pointer;" v-if="item.status === '启用'" @click="handleToggleStatus(item)" style="cursor: pointer;"
xmlns="http://www.w3.org/2000/svg" p-id="5940" width="20" height="20"> xmlns="http://www.w3.org/2000/svg" p-id="5940" width="20" height="20">
<path d="M512 938.688A426.688 426.688 0 1 1 512 85.376a426.688 426.688 0 0 1 0 853.312z m0-85.376A341.312 341.312 0 1 0 512 170.688a341.312 341.312 0 0 0 0 682.624z m208.64-489.664l-356.992 357.056a257.728 257.728 0 0 1-60.352-60.352l357.056-357.056c23.296 16.64 43.712 37.056 60.352 60.352z" <path d="M512 938.688A426.688 426.688 0 1 1 512 85.376a426.688 426.688 0 0 1 0 853.312z m0-85.376A341.312 341.312 0 1 0 512 170.688a341.312 341.312 0 0 0 0 682.624z m208.64-489.664l-356.992 357.056a257.728 257.728 0 0 1-60.352-60.352l357.056-357.056c23.296 16.64 43.712 37.056 60.352 60.352z"
fill="#909399" p-id="5941"></path> fill="#909399" p-id="5941"></path>
</svg> </svg>-->
<!--<button <button
v-else v-else
class="btn btn-success btn-small" class="btn btn-success btn-small"
@click="handleToggleStatus(item)"> @click="handleToggleStatus(item)">
启用 启用
</button>--> </button>
<svg t="1767688500016" class="icon" viewBox="0 0 1024 1024" version="1.1" v-else @click="handleToggleStatus(item)" style="cursor: pointer;" <!--<svg t="1767688500016" class="icon" viewBox="0 0 1024 1024" version="1.1" v-else @click="handleToggleStatus(item)" style="cursor: pointer;"
xmlns="http://www.w3.org/2000/svg" p-id="7110" width="20" height="20"> xmlns="http://www.w3.org/2000/svg" p-id="7110" width="20" height="20">
<path d="M806.3 150.2c-5.4-5.4-14.3-8.9-25-7.1-19.6 0-35.7 16.1-35.7 35.7 0 8.9 3.6 17.8 10.7 25 89.2 73.1 146.3 183.7 146.3 306.8 0 217.6-174.8 392.4-392.4 392.4S117.8 728.1 117.8 510.5c0-123.1 55.3-231.9 142.7-303.2 8.9-7.1 16.1-17.8 16.1-30.3 0-19.6-16.1-35.7-35.7-35.7-8.9 0-16.1 3.6-23.2 8.9-103.5 85.6-169.5 214-169.5 358.5 0 256.9 206.9 463.8 463.8 463.8s463.8-206.9 463.8-463.8c0-144.4-66-272.9-169.5-358.5z" <path d="M806.3 150.2c-5.4-5.4-14.3-8.9-25-7.1-19.6 0-35.7 16.1-35.7 35.7 0 8.9 3.6 17.8 10.7 25 89.2 73.1 146.3 183.7 146.3 306.8 0 217.6-174.8 392.4-392.4 392.4S117.8 728.1 117.8 510.5c0-123.1 55.3-231.9 142.7-303.2 8.9-7.1 16.1-17.8 16.1-30.3 0-19.6-16.1-35.7-35.7-35.7-8.9 0-16.1 3.6-23.2 8.9-103.5 85.6-169.5 214-169.5 358.5 0 256.9 206.9 463.8 463.8 463.8s463.8-206.9 463.8-463.8c0-144.4-66-272.9-169.5-358.5z"
p-id="7111"></path> p-id="7111"></path>
<path d="M512 622.9c19.6 0 35.7-16.1 35.7-35.7V87.8c0-19.6-16.1-35.7-35.7-35.7s-35.7 16.1-35.7 35.7v499.4c0 19.6 16.1 35.7 35.7 35.7z" <path d="M512 622.9c19.6 0 35.7-16.1 35.7-35.7V87.8c0-19.6-16.1-35.7-35.7-35.7s-35.7 16.1-35.7 35.7v499.4c0 19.6 16.1 35.7 35.7 35.7z"
p-id="7112"></path> p-id="7112"></path>
</svg> </svg>-->
</div> </div>
</td> </td>
</tr> </tr>
......
<!DOCTYPE html> <!DOCTYPE html>
...@@ -1316,7 +1316,7 @@ ...@@ -1316,7 +1316,7 @@
} }
.container { .container {
max-width: 1400px; /*max-width: 1400px;*/
margin: 0 auto; margin: 0 auto;
} }
...@@ -1537,35 +1537,83 @@ ...@@ -1537,35 +1537,83 @@
.stats-bar { .stats-bar {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px; gap: 20px;
margin-bottom: 20px; margin-bottom: 25px;
} }
.stat-card { .stat-card {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%); background: white;
color: white; color: var(--text-primary);
padding: 20px; padding: 24px;
border-radius: 8px; border-radius: 12px;
text-align: center; text-align: center;
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-color);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-md);
} }
.stat-card.success { .stat-card.success {
background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%); border-left: 4px solid var(--success-color);
} }
.stat-card.warning { .stat-card.warning {
background: linear-gradient(135deg, #e6a23c 0%, #ebb563 100%); border-left: 4px solid var(--warning-color);
}
.stat-card.primary {
border-left: 4px solid var(--primary-color);
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
font-size: 20px;
}
.stat-card.primary .stat-icon {
background: rgba(27, 100, 243, 0.1);
color: var(--primary-color);
}
.stat-card.success .stat-icon {
background: rgba(34, 197, 94, 0.1);
color: var(--success-color);
}
.stat-card.warning .stat-icon {
background: rgba(234, 179, 8, 0.1);
color: var(--warning-color);
}
.stat-card .stat-icon {
background: rgba(148, 163, 184, 0.1);
color: var(--text-tertiary);
} }
.stat-value { .stat-value {
font-size: 32px; font-size: 28px;
font-weight: bold; font-weight: 700;
margin: 10px 0; margin: 8px 0;
color: var(--text-primary);
} }
.stat-label { .stat-label {
font-size: 14px; font-size: 14px;
opacity: 0.9; color: var(--text-secondary);
font-weight: 500;
} }
</style> </style>
...@@ -1599,27 +1647,40 @@ ...@@ -1599,27 +1647,40 @@
<div class="main-content" style="position: relative;"> <div class="main-content" style="position: relative;">
<div id="app"> <div id="app">
<div class="container card" style="margin: 0;">
<!-- 统计卡片 --> <!-- 统计卡片 -->
<div class="stats-bar"> <div class="stats-bar">
<div class="stat-card"> <div class="stat-card primary">
<div class="stat-icon">
<i class="fas fa-file-alt"></i>
</div>
<div class="stat-label">总报价单数</div> <div class="stat-label">总报价单数</div>
<div class="stat-value">{{ stats.total }}</div> <div class="stat-value">{{ stats.total }}</div>
</div> </div>
<div class="stat-card success"> <div class="stat-card success">
<div class="stat-icon">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-label">有效报价单</div> <div class="stat-label">有效报价单</div>
<div class="stat-value">{{ stats.active }}</div> <div class="stat-value">{{ stats.active }}</div>
</div> </div>
<div class="stat-card warning"> <div class="stat-card warning">
<div class="stat-icon">
<i class="fas fa-clock"></i>
</div>
<div class="stat-label">已过期</div> <div class="stat-label">已过期</div>
<div class="stat-value">{{ stats.expired }}</div> <div class="stat-value">{{ stats.expired }}</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-icon">
<i class="fas fa-yen-sign"></i>
</div>
<div class="stat-label">总金额</div> <div class="stat-label">总金额</div>
<div class="stat-value">¥{{ stats.totalAmount.toLocaleString() }}</div> <div class="stat-value">¥{{ stats.totalAmount.toLocaleString() }}</div>
</div> </div>
</div> </div>
<div class="container card" style="margin: 0;flex: 1;">
<div class=""> <div class="">
<div class="card-title"> <div class="card-title">
......
<!DOCTYPE html> <!DOCTYPE html>
...@@ -1312,8 +1312,8 @@ ...@@ -1312,8 +1312,8 @@
} }
.container { .container {
max-width: 1200px; /*max-width: 1200px;*/
margin: 0 auto; /*margin: 0 auto;*/
} }
.header { .header {
...@@ -1551,7 +1551,7 @@ ...@@ -1551,7 +1551,7 @@
<div class="breadcrumb"> <div class="breadcrumb">
<span>首页</span> <span>首页</span>
<span class="breadcrumb-separator">/</span> <span class="breadcrumb-separator">/</span>
<span class="breadcrumb-current">设备档案</span> <span class="breadcrumb-current">报价单详情</span>
</div> </div>
</div> </div>
<div class="header-right"> <div class="header-right">
...@@ -1574,6 +1574,11 @@ ...@@ -1574,6 +1574,11 @@
<div class="container" style="margin: 0;"> <div class="container" style="margin: 0;">
<div class="card"> <div class="card">
<div class="page-header">
<div>
<h1 class="page-title" data-i18n="title">报价单详情</h1>
</div>
</div>
<!-- 报价单头部信息 --> <!-- 报价单头部信息 -->
<div class="quote-header"> <div class="quote-header">
<div> <div>
...@@ -1586,6 +1591,14 @@ ...@@ -1586,6 +1591,14 @@
<div class="quote-info-value">{{ quoteInfo.customerName }}</div> <div class="quote-info-value">{{ quoteInfo.customerName }}</div>
</div> </div>
<div class="quote-info-item"> <div class="quote-info-item">
<div class="quote-info-label">客户所在地区</div>
<div class="quote-info-value">{{ quoteInfo.customerRegion }}</div>
</div>
<div class="quote-info-item">
<div class="quote-info-label">币种</div>
<div class="quote-info-value">{{ quoteInfo.currency }}</div>
</div>
<div class="quote-info-item">
<div class="quote-info-label">服务内容</div> <div class="quote-info-label">服务内容</div>
<div class="quote-info-value">{{ quoteInfo.serviceDescription }}</div> <div class="quote-info-value">{{ quoteInfo.serviceDescription }}</div>
</div> </div>
...@@ -1608,6 +1621,33 @@ ...@@ -1608,6 +1621,33 @@
</div> </div>
</div> </div>
<!-- 工厂信息 -->
<div class="detail-section">
<div class="detail-section-title">工厂信息</div>
<div class="quote-header">
<div>
<div class="quote-info-item">
<div class="quote-info-label">工厂</div>
<div class="quote-info-value">{{ quoteInfo.factory }}</div>
</div>
<div class="quote-info-item">
<div class="quote-info-label">工厂地区</div>
<div class="quote-info-value">{{ quoteInfo.factoryRegion }}</div>
</div>
</div>
<div>
<div class="quote-info-item">
<div class="quote-info-label">工厂地址</div>
<div class="quote-info-value">{{ quoteInfo.factoryAddress }}</div>
</div>
<div class="quote-info-item">
<div class="quote-info-label">联系人</div>
<div class="quote-info-value">{{ quoteInfo.contactPerson }}</div>
</div>
</div>
</div>
</div>
<!-- 人工费用明细 --> <!-- 人工费用明细 -->
<div class="detail-section" v-if="laborDetails.length > 0"> <div class="detail-section" v-if="laborDetails.length > 0">
<div class="detail-section-title">人工费用明细</div> <div class="detail-section-title">人工费用明细</div>
...@@ -1722,6 +1762,8 @@ ...@@ -1722,6 +1762,8 @@
quoteInfo: { quoteInfo: {
quoteNo: quoteNo, quoteNo: quoteNo,
customerName: '', customerName: '',
customerRegion: '',
currency: '',
serviceDescription: '', serviceDescription: '',
quoteDate: new Date().toLocaleDateString('zh-CN'), quoteDate: new Date().toLocaleDateString('zh-CN'),
validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toLocaleDateString('zh-CN'), validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toLocaleDateString('zh-CN'),
...@@ -1729,7 +1771,11 @@ ...@@ -1729,7 +1771,11 @@
whitebookVersion: 'V1.0', whitebookVersion: 'V1.0',
ruleName: '', ruleName: '',
scenario: '', scenario: '',
remark: '' remark: '',
factory: '',
factoryRegion: '',
factoryAddress: '',
contactPerson: ''
}, },
laborDetails: [], laborDetails: [],
partDetails: [] partDetails: []
...@@ -1760,6 +1806,8 @@ ...@@ -1760,6 +1806,8 @@
this.quoteInfo = { this.quoteInfo = {
quoteNo: quote.quoteNo, quoteNo: quote.quoteNo,
customerName: quote.customerName, customerName: quote.customerName,
customerRegion: quote.customerRegion || '',
currency: quote.currency || '',
serviceDescription: quote.serviceDescription, serviceDescription: quote.serviceDescription,
quoteDate: quote.quoteDate, quoteDate: quote.quoteDate,
validUntil: quote.validUntil, validUntil: quote.validUntil,
...@@ -1767,7 +1815,11 @@ ...@@ -1767,7 +1815,11 @@
whitebookVersion: quote.whitebookVersion || 'V1.0', whitebookVersion: quote.whitebookVersion || 'V1.0',
ruleName: quote.ruleName || '', ruleName: quote.ruleName || '',
scenario: quote.scenario || '', scenario: quote.scenario || '',
remark: quote.remark || '本报价单有效期30天,最终价格以实际服务为准。' remark: quote.remark || '本报价单有效期30天,最终价格以实际服务为准。',
factory: quote.factory || '',
factoryRegion: quote.factoryRegion || '',
factoryAddress: quote.factoryAddress || '',
contactPerson: quote.contactPerson || ''
}; };
this.laborDetails = quote.laborDetails || []; this.laborDetails = quote.laborDetails || [];
this.partDetails = quote.partDetails || []; this.partDetails = quote.partDetails || [];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment