Enhance patrol records and tasks management with comprehensive updates
- Add detailed view and statistics for patrol records - Implement advanced filtering and export functionality - Update task management with improved UI and data handling - Add Amap API key to development environment - Fix import paths for SCSS variables in monitor views - Improve route configuration for patrol-related pages
This commit is contained in:
parent
f22ce91e1a
commit
032d404101
@ -11,4 +11,7 @@ VITE_BASE_URL=/
|
||||
VITE_MOCK_API=true
|
||||
|
||||
# 是否开启调试工具
|
||||
VITE_DEV_TOOLS=true
|
||||
VITE_DEV_TOOLS=true
|
||||
|
||||
# 高德地图API密钥
|
||||
VITE_AMAP_KEY=your_amap_key_here
|
116
src/api/patrol/plan.js
Normal file
116
src/api/patrol/plan.js
Normal file
@ -0,0 +1,116 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取巡护计划列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.page=1] - 页码
|
||||
* @param {number} [params.pageSize=10] - 每页条数
|
||||
* @param {string} [params.keyword] - 搜索关键词
|
||||
* @param {string} [params.planType] - 计划类型:daily-日常巡护,special-专项巡护
|
||||
* @param {number} [params.status] - 状态:0-禁用,1-启用
|
||||
* @param {string} [params.startDate] - 开始日期
|
||||
* @param {string} [params.endDate] - 结束日期
|
||||
* @returns {Promise} 返回计划列表数据
|
||||
*/
|
||||
export function getPlanList(params = {}) {
|
||||
return request.get('/api/admin/patrol/plans', {
|
||||
params: {
|
||||
page: params.page || 1,
|
||||
page_size: params.pageSize || 10,
|
||||
keyword: params.keyword || undefined,
|
||||
plan_type: params.planType || undefined,
|
||||
status: params.status === undefined ? undefined : Number(params.status),
|
||||
start_date: params.startDate || undefined,
|
||||
end_date: params.endDate || undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建巡护计划
|
||||
* @param {Object} data - 计划数据
|
||||
* @param {string} data.plan_name - 计划名称,最大100字符
|
||||
* @param {string} data.plan_type - 计划类型:daily-日常巡护,special-专项巡护
|
||||
* @param {string} data.start_date - 开始日期,ISO日期格式
|
||||
* @param {string} data.end_date - 结束日期,必须大于start_date
|
||||
* @param {Array<Object>} data.area_scope - 巡护区域范围点位列表,至少3个点位
|
||||
* @param {string} [data.task_frequency] - 任务频次:daily、weekly、monthly
|
||||
* @param {string} [data.description] - 计划描述,最大1000字符
|
||||
* @param {number} [data.status] - 状态:0-禁用,1-启用,默认1
|
||||
* @returns {Promise} 返回创建结果
|
||||
*/
|
||||
export function createPlan(data) {
|
||||
return request.post('/api/admin/patrol/plans', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取巡护计划详情
|
||||
* @param {string|number} id - 计划ID
|
||||
* @returns {Promise} 返回计划详情数据
|
||||
*/
|
||||
export function getPlanDetail(id) {
|
||||
return request.get(`/api/admin/patrol/plans/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新巡护计划
|
||||
* @param {string|number} id - 计划ID
|
||||
* @param {Object} data - 更新数据,参数同创建计划
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updatePlan(id, data) {
|
||||
return request.put(`/api/admin/patrol/plans/${id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新巡护计划状态
|
||||
* @param {string|number} id - 计划ID
|
||||
* @param {number} status - 状态值:0-禁用,1-启用
|
||||
* @returns {Promise} 返回状态更新结果
|
||||
*/
|
||||
export function updatePlanStatus(id, status) {
|
||||
return request.put(`/api/admin/patrol/plans/${id}/status`, {
|
||||
status: status === 1 ? 1 : 0
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查计划名称是否可用
|
||||
* @param {string} name - 计划名称
|
||||
* @param {string|number} [excludeId] - 排除的计划ID(用于编辑时检查)
|
||||
* @returns {Promise} 返回检查结果
|
||||
*/
|
||||
export function checkPlanName(name, excludeId) {
|
||||
return request.get('/api/admin/patrol/plans/check-name', {
|
||||
params: {
|
||||
plan_name: name,
|
||||
exclude_id: excludeId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除巡护计划
|
||||
* @param {string|number} id - 计划ID
|
||||
* @returns {Promise} 返回删除结果
|
||||
*/
|
||||
export function deletePlan(id) {
|
||||
return request.delete(`/api/admin/patrol/plans/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除巡护计划
|
||||
* @param {Array<string|number>} ids - 计划ID列表
|
||||
* @returns {Promise} 返回批量删除结果
|
||||
*/
|
||||
export function batchDeletePlans(ids) {
|
||||
return request.post('/api/admin/patrol/plans/batch/delete', { ids })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取巡护计划统计数据
|
||||
* @returns {Promise} 返回统计数据
|
||||
*/
|
||||
export function getPlanStatistics() {
|
||||
return request.get('/api/admin/patrol/plans/statistics')
|
||||
}
|
163
src/api/patrol/record.js
Normal file
163
src/api/patrol/record.js
Normal file
@ -0,0 +1,163 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 创建巡护记录
|
||||
* @param {Object} data - 记录数据
|
||||
* @param {number} data.task_id - 关联的任务ID
|
||||
* @param {string} data.record_time - 记录时间
|
||||
* @param {Array<Object>} data.patrol_points - 巡护点位列表
|
||||
* @param {string} data.description - 巡护描述
|
||||
* @param {Array<Object>} [data.images] - 图片列表
|
||||
* @param {Array<Object>} [data.files] - 附件列表
|
||||
* @param {Object} [data.weather] - 天气信息
|
||||
* @returns {Promise} 返回创建结果
|
||||
*/
|
||||
export function createRecord(data) {
|
||||
return request.post('/api/admin/patrol/records', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取巡护记录列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.page=1] - 页码
|
||||
* @param {number} [params.pageSize=10] - 每页条数
|
||||
* @param {string} [params.keyword] - 搜索关键词
|
||||
* @param {string} [params.startDate] - 开始日期
|
||||
* @param {string} [params.endDate] - 结束日期
|
||||
* @param {number} [params.taskId] - 任务ID
|
||||
* @param {number} [params.patrolerId] - 巡护员ID
|
||||
* @returns {Promise} 返回记录列表数据
|
||||
*/
|
||||
export function getRecordList(params = {}) {
|
||||
return request.get('/api/admin/patrol/records', {
|
||||
params: {
|
||||
page: params.page || 1,
|
||||
page_size: params.pageSize || 10,
|
||||
keyword: params.keyword || undefined,
|
||||
start_date: params.startDate || undefined,
|
||||
end_date: params.endDate || undefined,
|
||||
task_id: params.taskId || undefined,
|
||||
patroler_id: params.patrolerId || undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取巡护记录详情
|
||||
* @param {string|number} id - 记录ID
|
||||
* @returns {Promise} 返回记录详情数据
|
||||
*/
|
||||
export function getRecordDetail(id) {
|
||||
return request.get(`/api/admin/patrol/records/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出巡护记录
|
||||
* @param {Object} params - 导出参数
|
||||
* @param {string} [params.startDate] - 开始日期
|
||||
* @param {string} [params.endDate] - 结束日期
|
||||
* @param {number} [params.taskId] - 任务ID
|
||||
* @param {number} [params.patrolerId] - 巡护员ID
|
||||
* @param {string} [params.format='excel'] - 导出格式
|
||||
* @returns {Promise} 返回二进制文件流
|
||||
*/
|
||||
export function exportRecords(params = {}) {
|
||||
return request.get('/api/admin/patrol/records/export/list', {
|
||||
params: {
|
||||
start_date: params.startDate || undefined,
|
||||
end_date: params.endDate || undefined,
|
||||
task_id: params.taskId || undefined,
|
||||
patroler_id: params.patrolerId || undefined,
|
||||
format: params.format || 'excel'
|
||||
},
|
||||
responseType: 'blob',
|
||||
headers: {
|
||||
'Accept': 'application/vnd.ms-excel'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取巡护统计信息
|
||||
* @returns {Promise} 返回统计数据
|
||||
*/
|
||||
export function getRecordStatistics() {
|
||||
return request.get('/api/admin/patrol/records/statistics/overview')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务相关的巡护记录
|
||||
* @param {string|number} taskId - 任务ID
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.page=1] - 页码
|
||||
* @param {number} [params.pageSize=10] - 每页条数
|
||||
* @returns {Promise} 返回任务相关的记录列表
|
||||
*/
|
||||
export function getTaskRecords(taskId, params = {}) {
|
||||
return request.get(`/api/admin/patrol/records/task/${taskId}`, {
|
||||
params: {
|
||||
page: params.page || 1,
|
||||
page_size: params.pageSize || 10
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新巡护记录
|
||||
* @param {string|number} id - 记录ID
|
||||
* @param {Object} data - 更新数据
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updateRecord(id, data) {
|
||||
return request.put(`/api/admin/patrol/records/${id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除巡护记录
|
||||
* @param {string|number} id - 记录ID
|
||||
* @returns {Promise} 返回删除结果
|
||||
*/
|
||||
export function deleteRecord(id) {
|
||||
return request.delete(`/api/admin/patrol/records/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除巡护记录
|
||||
* @param {Array<string|number>} ids - 记录ID列表
|
||||
* @returns {Promise} 返回批量删除结果
|
||||
*/
|
||||
export function batchDeleteRecords(ids) {
|
||||
return request.post('/api/admin/patrol/records/batch/delete', { ids })
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传巡护记录图片
|
||||
* @param {File} file - 图片文件
|
||||
* @returns {Promise} 返回上传结果
|
||||
*/
|
||||
export function uploadRecordImage(file) {
|
||||
const formData = new FormData()
|
||||
formData.append('image', file)
|
||||
|
||||
return request.post('/api/admin/patrol/records/upload/image', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传巡护记录附件
|
||||
* @param {File} file - 附件文件
|
||||
* @returns {Promise} 返回上传结果
|
||||
*/
|
||||
export function uploadRecordFile(file) {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
return request.post('/api/admin/patrol/records/upload/file', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
93
src/api/patrol/task.js
Normal file
93
src/api/patrol/task.js
Normal file
@ -0,0 +1,93 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 创建巡护任务
|
||||
* @param {Object} data - 任务数据
|
||||
* @param {string} data.title - 任务标题
|
||||
* @param {string} data.description - 任务描述
|
||||
* @param {string} data.start_time - 开始时间
|
||||
* @param {string} data.end_time - 结束时间
|
||||
* @param {number} data.executor_id - 执行人ID
|
||||
* @param {Array} data.patrol_points - 巡护点位列表
|
||||
* @param {string} data.patrol_area - 巡护区域
|
||||
* @param {number} data.priority - 优先级:0-普通,1-重要,2-紧急
|
||||
* @returns {Promise} 返回创建结果
|
||||
*/
|
||||
export function createTask(data) {
|
||||
return request.post('/api/admin/patrol/tasks', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取巡护任务列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.page=1] - 页码
|
||||
* @param {number} [params.pageSize=10] - 每页条数
|
||||
* @param {string} [params.keyword] - 搜索关键词
|
||||
* @param {string} [params.status] - 任务状态
|
||||
* @param {string} [params.startDate] - 开始日期
|
||||
* @param {string} [params.endDate] - 结束日期
|
||||
* @param {number} [params.executorId] - 执行人ID
|
||||
* @returns {Promise} 返回任务列表数据
|
||||
*/
|
||||
export function getTaskList(params = {}) {
|
||||
return request.get('/api/admin/patrol/tasks', {
|
||||
params: {
|
||||
page: params.page || 1,
|
||||
pageSize: params.pageSize || 10,
|
||||
keyword: params.keyword || undefined,
|
||||
status: params.status || undefined,
|
||||
start_date: params.startDate || undefined,
|
||||
end_date: params.endDate || undefined,
|
||||
executor_id: params.executorId || undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取巡护任务详情
|
||||
* @param {string|number} id - 任务ID
|
||||
* @returns {Promise} 返回任务详情数据
|
||||
*/
|
||||
export function getTaskDetail(id) {
|
||||
return request.get(`/api/admin/patrol/tasks/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新巡护任务
|
||||
* @param {string|number} id - 任务ID
|
||||
* @param {Object} data - 更新数据
|
||||
* @returns {Promise} 返回更新结果
|
||||
*/
|
||||
export function updateTask(id, data) {
|
||||
return request.put(`/api/admin/patrol/plans/${id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取执行人的任务列表
|
||||
* @param {string|number} executorId - 执行人ID
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.page=1] - 页码
|
||||
* @param {number} [params.pageSize=10] - 每页条数
|
||||
* @param {string} [params.status] - 任务状态
|
||||
* @returns {Promise} 返回执行人的任务列表
|
||||
*/
|
||||
export function getExecutorTasks(executorId, params = {}) {
|
||||
return request.get(`/api/admin/patrol/tasks/executor/${executorId}`, {
|
||||
params: {
|
||||
page: params.page || 1,
|
||||
pageSize: params.pageSize || 10,
|
||||
status: params.status || undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消巡护任务
|
||||
* @param {string|number} id - 任务ID
|
||||
* @param {Object} data - 取消原因等数据
|
||||
* @param {string} data.reason - 取消原因
|
||||
* @returns {Promise} 返回取消结果
|
||||
*/
|
||||
export function cancelTask(id, data = {}) {
|
||||
return request.post(`/api/admin/patrol/tasks/${id}/cancel`, data)
|
||||
}
|
@ -91,6 +91,7 @@ const handleLogout = () => {
|
||||
:default-active="activeMenu"
|
||||
class="el-menu-vertical"
|
||||
@select="handleSelect"
|
||||
unique-opened
|
||||
>
|
||||
<el-menu-item index="/dashboard">
|
||||
<el-icon><component :is="icons.DataBoard" /></el-icon>
|
||||
@ -130,6 +131,7 @@ const handleLogout = () => {
|
||||
<el-icon><component :is="icons.Location" /></el-icon>
|
||||
<span>巡护管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/patrol/plans">巡护计划</el-menu-item>
|
||||
<el-menu-item index="/patrol/tasks">巡护任务</el-menu-item>
|
||||
<el-menu-item index="/patrol/records">巡护记录</el-menu-item>
|
||||
<el-menu-item index="/patrol/events">安防事件</el-menu-item>
|
||||
|
@ -69,6 +69,11 @@ const router = createRouter({
|
||||
name: 'PatrolEvents',
|
||||
component: () => import('../views/patrol/events/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'patrol/plans',
|
||||
name: 'PatrolPlans',
|
||||
component: () => import('../views/patrol/plans/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'patrol/records',
|
||||
name: 'PatrolRecords',
|
||||
@ -112,7 +117,8 @@ const router = createRouter({
|
||||
{
|
||||
path: 'system/data',
|
||||
name: 'DataManagement',
|
||||
component: () => import('../views/system/data/index.vue')
|
||||
component: () => import('@/views/system/data/index.vue'),
|
||||
meta: { title: '数据管理', icon: 'data' }
|
||||
},
|
||||
{
|
||||
path: 'system/carousel',
|
||||
|
54
src/utils/map.js
Normal file
54
src/utils/map.js
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 将经纬度转换为地址
|
||||
* @param {number} longitude - 经度
|
||||
* @param {number} latitude - 纬度
|
||||
* @returns {Promise<string>} 返回地址字符串
|
||||
*/
|
||||
export async function getAddressFromLocation(longitude, latitude) {
|
||||
try {
|
||||
// 使用高德地图API进行地理编码
|
||||
const response = await fetch(
|
||||
`https://restapi.amap.com/v3/geocode/regeo?key=${import.meta.env.VITE_AMAP_KEY}&location=${longitude},${latitude}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === '1' && data.regeocode) {
|
||||
return data.regeocode.formatted_address;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('地理编码错误:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理巡护路线点位信息,添加地址信息
|
||||
* @param {Array} points - 路线点位数组
|
||||
* @returns {Promise<Array>} 返回带有地址信息的点位数组
|
||||
*/
|
||||
export async function getRoutePointsWithAddress(points) {
|
||||
if (!points || !Array.isArray(points)) return [];
|
||||
|
||||
try {
|
||||
const pointsWithAddress = await Promise.all(
|
||||
points.map(async (point, index) => {
|
||||
const address = await getAddressFromLocation(point.longitude, point.latitude);
|
||||
return {
|
||||
...point,
|
||||
index: index + 1,
|
||||
address: address || '未能获取地址'
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return pointsWithAddress;
|
||||
} catch (error) {
|
||||
console.error('处理路线点位错误:', error);
|
||||
return points.map((point, index) => ({
|
||||
...point,
|
||||
index: index + 1,
|
||||
address: '未能获取地址'
|
||||
}));
|
||||
}
|
||||
}
|
@ -468,7 +468,7 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "./styles/variables" as v;
|
||||
@use "../../../styles/variables.scss" as *;
|
||||
|
||||
.env-container {
|
||||
.env-card {
|
||||
|
@ -297,7 +297,7 @@ const handleExportConfirm = () => {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "./styles/variables" as v;
|
||||
@use "../../../styles/variables.scss" as *;
|
||||
|
||||
.species-container {
|
||||
padding: 20px;
|
||||
|
921
src/views/patrol/plans/index.vue
Normal file
921
src/views/patrol/plans/index.vue
Normal file
@ -0,0 +1,921 @@
|
||||
<template>
|
||||
<div class="patrol-plans">
|
||||
<el-card class="main-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="header-title">巡护计划管理</span>
|
||||
<div class="header-btns">
|
||||
<el-button type="primary" :icon="Plus" @click="newPlanDialogVisible = true">新建计划</el-button>
|
||||
<el-button :icon="Refresh" @click="handleRefresh">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 搜索工具栏 -->
|
||||
<div class="toolbar">
|
||||
<div class="search-area">
|
||||
<el-input
|
||||
v-model="searchText"
|
||||
placeholder="搜索计划名称"
|
||||
clearable
|
||||
class="search-input"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-select
|
||||
v-model="typeFilter"
|
||||
placeholder="计划类型"
|
||||
clearable
|
||||
class="filter-select"
|
||||
>
|
||||
<el-option value="daily" label="日常巡护" />
|
||||
<el-option value="special" label="专项巡护" />
|
||||
</el-select>
|
||||
|
||||
<el-select
|
||||
v-model="statusFilter"
|
||||
placeholder="计划状态"
|
||||
clearable
|
||||
class="filter-select"
|
||||
>
|
||||
<el-option :value="0" label="禁用" />
|
||||
<el-option :value="1" label="启用" />
|
||||
</el-select>
|
||||
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
class="date-picker"
|
||||
/>
|
||||
|
||||
<div class="search-buttons">
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetFilters">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-area">
|
||||
<el-button
|
||||
type="danger"
|
||||
:disabled="selectedPlans.length === 0"
|
||||
@click="handleBatchDelete"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 计划列表 -->
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
border
|
||||
stripe
|
||||
:header-cell-style="{ background: '#f5f7fa' }"
|
||||
@selection-change="selectedPlans = $event"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
|
||||
<el-table-column prop="plan_name" label="计划名称" min-width="200" show-overflow-tooltip align="center"/>
|
||||
|
||||
<el-table-column prop="plan_type" label="类型" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.plan_type === 'daily' ? 'info' : 'warning'">
|
||||
{{ row.plan_type === 'daily' ? '日常巡护' : '专项巡护' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="执行时间" width="240">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.start_date, 'YYYY-MM-DD') }} 至
|
||||
{{ formatDateTime(row.end_date, 'YYYY-MM-DD') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="task_frequency" label="任务频次" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ {
|
||||
'daily': '每日',
|
||||
'weekly': '每周',
|
||||
'monthly': '每月'
|
||||
}[row.task_frequency] || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'info'">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="handleView(row)">查看</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleStatusChange(row)"
|
||||
>
|
||||
{{ row.status === 1 ? '禁用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页器 - 只在总数大于页面大小时显示 -->
|
||||
<div v-if="total > pageSize" class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
background
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 新建计划弹窗 -->
|
||||
<el-dialog
|
||||
v-model="newPlanDialogVisible"
|
||||
title="新建巡护计划"
|
||||
width="600px"
|
||||
@close="handleDialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="newPlanFormRef"
|
||||
:model="newPlanForm"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="计划名称" prop="plan_name">
|
||||
<el-input v-model="newPlanForm.plan_name" placeholder="请输入计划名称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="计划类型" prop="plan_type">
|
||||
<el-radio-group v-model="newPlanForm.plan_type">
|
||||
<el-radio value="daily">日常巡护</el-radio>
|
||||
<el-radio value="special">专项巡护</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="执行时间" required>
|
||||
<el-col :span="11">
|
||||
<el-form-item prop="start_date">
|
||||
<el-date-picker
|
||||
v-model="newPlanForm.start_date"
|
||||
type="date"
|
||||
placeholder="开始日期"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="2" class="text-center">
|
||||
<span class="text-gray-500">至</span>
|
||||
</el-col>
|
||||
<el-col :span="11">
|
||||
<el-form-item prop="end_date">
|
||||
<el-date-picker
|
||||
v-model="newPlanForm.end_date"
|
||||
type="date"
|
||||
placeholder="结束日期"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="任务频次" prop="task_frequency">
|
||||
<el-radio-group v-model="newPlanForm.task_frequency">
|
||||
<el-radio value="daily">每日</el-radio>
|
||||
<el-radio value="weekly">每周</el-radio>
|
||||
<el-radio value="monthly">每月</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="巡护区域" prop="area_scope">
|
||||
<div class="address-input-container">
|
||||
<div class="address-input">
|
||||
<el-input
|
||||
v-model="addressInput"
|
||||
placeholder="请输入巡护点位地址"
|
||||
class="address-input-field"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="handleAddAddress">添加</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div v-if="addressList.length > 0" class="address-list">
|
||||
<el-table :data="addressList" size="small" border>
|
||||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||||
<el-table-column label="地址" prop="address" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="经度" prop="longitude" width="100" align="center" />
|
||||
<el-table-column label="纬度" prop="latitude" width="100" align="center" />
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleRemoveAddress($index)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="address-tip">
|
||||
<el-alert
|
||||
title="请至少添加3个巡护点位,系统将自动连接各点形成巡护区域"
|
||||
type="info"
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="计划描述" prop="description">
|
||||
<el-input
|
||||
v-model="newPlanForm.description"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入计划描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="计划状态" prop="status">
|
||||
<el-radio-group v-model="newPlanForm.status">
|
||||
<el-radio :value="1">启用</el-radio>
|
||||
<el-radio :value="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="newPlanDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleNewPlanSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 计划详情弹窗 -->
|
||||
<el-dialog
|
||||
v-model="detailDialogVisible"
|
||||
title="巡护计划详情"
|
||||
width="600px"
|
||||
>
|
||||
<el-descriptions v-if="currentPlan" :column="1" border>
|
||||
<el-descriptions-item label="计划名称">
|
||||
{{ currentPlan.plan_name }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="计划类型">
|
||||
<el-tag :type="currentPlan.plan_type === 'daily' ? 'info' : 'warning'">
|
||||
{{ currentPlan.plan_type === 'daily' ? '日常巡护' : '专项巡护' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="执行时间">
|
||||
{{ formatDateTime(currentPlan.start_date, 'YYYY-MM-DD') }} 至
|
||||
{{ formatDateTime(currentPlan.end_date, 'YYYY-MM-DD') }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="任务频次">
|
||||
{{ {
|
||||
'daily': '每日',
|
||||
'weekly': '每周',
|
||||
'monthly': '每月'
|
||||
}[currentPlan.task_frequency] || '-' }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="巡护区域">
|
||||
<div v-if="currentPlan.area_scope?.length">
|
||||
<div v-for="(point, index) in currentPlan.area_scope" :key="index">
|
||||
经度: {{ point.longitude }}, 纬度: {{ point.latitude }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="计划状态">
|
||||
<el-tag :type="currentPlan.status === 1 ? 'success' : 'info'">
|
||||
{{ currentPlan.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="计划描述">
|
||||
{{ currentPlan.description || '-' }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDateTime(currentPlan.created_at) }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDateTime(currentPlan.updated_at) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Plus, Delete, Refresh } from '@element-plus/icons-vue'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import {
|
||||
getPlanList,
|
||||
createPlan,
|
||||
updatePlan,
|
||||
getPlanDetail,
|
||||
deletePlan,
|
||||
updatePlanStatus,
|
||||
batchDeletePlans,
|
||||
checkPlanName
|
||||
} from '@/api/patrol/plan'
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([])
|
||||
const loading = ref(false)
|
||||
const selectedPlans = ref([])
|
||||
|
||||
// 分页相关
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
|
||||
// 搜索和筛选条件
|
||||
const searchText = ref('')
|
||||
const typeFilter = ref('')
|
||||
const statusFilter = ref(null)
|
||||
const dateRange = ref(['', ''])
|
||||
|
||||
// 新建计划相关
|
||||
const newPlanDialogVisible = ref(false)
|
||||
const newPlanForm = reactive({
|
||||
plan_name: '',
|
||||
plan_type: 'daily',
|
||||
description: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
area_scope: [],
|
||||
task_frequency: 'daily',
|
||||
status: 1
|
||||
})
|
||||
|
||||
const newPlanFormRef = ref()
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
plan_name: [
|
||||
{ required: true, message: '请输入计划名称', trigger: 'blur' },
|
||||
{ max: 100, message: '计划名称最大100字符', trigger: 'blur' },
|
||||
{
|
||||
validator: async (rule, value, callback) => {
|
||||
if (!value) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await checkPlanName(value, newPlanForm.id)
|
||||
if (res.success && !res.data.available) {
|
||||
callback(new Error('计划名称已存在'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查计划名称错误:', error)
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
plan_type: [{ required: true, message: '请选择计划类型', trigger: 'change' }],
|
||||
start_date: [{ required: true, message: '请选择开始日期', trigger: 'change' }],
|
||||
end_date: [
|
||||
{ required: true, message: '请选择结束日期', trigger: 'change' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value && newPlanForm.start_date && value <= newPlanForm.start_date) {
|
||||
callback(new Error('结束日期必须大于开始日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
area_scope: [{
|
||||
type: 'array',
|
||||
required: true,
|
||||
min: 3,
|
||||
message: '请至少选择3个巡护点位',
|
||||
trigger: 'change'
|
||||
}],
|
||||
task_frequency: [{ required: false }],
|
||||
description: [{ max: 1000, message: '描述最大1000字符', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// 获取计划列表
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
keyword: searchText.value,
|
||||
planType: typeFilter.value || undefined,
|
||||
status: statusFilter.value === null ? undefined : Number(statusFilter.value),
|
||||
startDate: dateRange.value[0] ? formatDateTime(dateRange.value[0], 'YYYY-MM-DD') : '',
|
||||
endDate: dateRange.value[1] ? formatDateTime(dateRange.value[1], 'YYYY-MM-DD') : '',
|
||||
page: Number(currentPage.value),
|
||||
pageSize: Number(pageSize.value)
|
||||
}
|
||||
|
||||
const res = await getPlanList(params)
|
||||
if (res.success) {
|
||||
tableData.value = res.data.list
|
||||
total.value = Number(res.data.pagination.total)
|
||||
currentPage.value = Number(res.data.pagination.page)
|
||||
pageSize.value = Number(res.data.pagination.page_size)
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取计划列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取计划列表错误:', error)
|
||||
ElMessage.error('获取计划列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(newPlanForm, {
|
||||
plan_name: '',
|
||||
plan_type: 'daily',
|
||||
description: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
area_scope: [],
|
||||
task_frequency: 'daily',
|
||||
status: 1
|
||||
})
|
||||
addressList.value = [] // 清空地址列表
|
||||
if (newPlanFormRef.value) {
|
||||
newPlanFormRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
// 处理新建计划提交
|
||||
const handleNewPlanSubmit = async () => {
|
||||
if (!newPlanFormRef.value) return
|
||||
|
||||
try {
|
||||
await newPlanFormRef.value.validate()
|
||||
|
||||
const submitData = {
|
||||
plan_name: newPlanForm.plan_name.trim(),
|
||||
plan_type: newPlanForm.plan_type,
|
||||
description: newPlanForm.description?.trim(),
|
||||
start_date: newPlanForm.start_date,
|
||||
end_date: newPlanForm.end_date,
|
||||
area_scope: newPlanForm.area_scope,
|
||||
task_frequency: newPlanForm.task_frequency,
|
||||
status: newPlanForm.status
|
||||
}
|
||||
|
||||
const res = await createPlan(submitData)
|
||||
if (res.success) {
|
||||
ElMessage.success('创建成功')
|
||||
newPlanDialogVisible.value = false
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '创建失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建计划错误:', error)
|
||||
ElMessage.error('创建失败,请检查输入是否正确')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理关闭弹窗
|
||||
const handleDialogClose = () => {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置筛选条件
|
||||
const resetFilters = () => {
|
||||
searchText.value = ''
|
||||
typeFilter.value = ''
|
||||
statusFilter.value = null
|
||||
dateRange.value = ['', '']
|
||||
currentPage.value = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 处理分页变化
|
||||
const handlePageChange = (page) => {
|
||||
currentPage.value = Number(page)
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleSizeChange = (size) => {
|
||||
pageSize.value = Number(size)
|
||||
currentPage.value = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 计划详情相关
|
||||
const detailDialogVisible = ref(false)
|
||||
const currentPlan = ref(null)
|
||||
|
||||
// 查看计划详情
|
||||
const handleView = async (row) => {
|
||||
try {
|
||||
const res = await getPlanDetail(row.id)
|
||||
if (res.success) {
|
||||
currentPlan.value = res.data
|
||||
detailDialogVisible.value = true
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取计划详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取计划详情错误:', error)
|
||||
ElMessage.error('获取计划详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 更新计划状态
|
||||
const handleStatusChange = async (row) => {
|
||||
try {
|
||||
const newStatus = row.status === 1 ? 0 : 1
|
||||
const res = await updatePlanStatus(row.id, newStatus)
|
||||
if (res.success) {
|
||||
ElMessage.success('状态更新成功')
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '状态更新失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新计划状态错误:', error)
|
||||
ElMessage.error('状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除计划
|
||||
const handleDelete = (row) => {
|
||||
ElMessageBox.confirm('确定要删除该计划吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const res = await deletePlan(row.id)
|
||||
if (res.success) {
|
||||
ElMessage.success('删除成功')
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除计划错误:', error)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 批量删除计划
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedPlans.value.length === 0) {
|
||||
ElMessage.warning('请选择要删除的计划')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(`确定要删除选中的 ${selectedPlans.value.length} 个计划吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const ids = selectedPlans.value.map(plan => plan.id)
|
||||
const res = await batchDeletePlans(ids)
|
||||
if (res.success) {
|
||||
ElMessage.success('批量删除成功')
|
||||
selectedPlans.value = []
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '批量删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量删除计划错误:', error)
|
||||
ElMessage.error('批量删除失败')
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 添加刷新方法
|
||||
const handleRefresh = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
// 在 script setup 部分添加新的响应式变量和方法
|
||||
const addressList = ref([]) // 用于存储地址列表
|
||||
const addressInput = ref('') // 用于存储当前输入的地址
|
||||
|
||||
// 添加地址转换方法
|
||||
const convertAddressToCoords = async (address) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://restapi.amap.com/v3/geocode/geo?key=${import.meta.env.VITE_AMAP_KEY}&address=${encodeURIComponent(address)}`
|
||||
)
|
||||
const data = await response.json()
|
||||
|
||||
if (data.status === '1' && data.geocodes && data.geocodes.length > 0) {
|
||||
const [longitude, latitude] = data.geocodes[0].location.split(',')
|
||||
return {
|
||||
address,
|
||||
longitude: Number(longitude),
|
||||
latitude: Number(latitude)
|
||||
}
|
||||
}
|
||||
throw new Error('地址解析失败')
|
||||
} catch (error) {
|
||||
console.error('地址转换错误:', error)
|
||||
ElMessage.error('地址转换失败')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 添加地址
|
||||
const handleAddAddress = async () => {
|
||||
if (!addressInput.value.trim()) {
|
||||
ElMessage.warning('请输入地址')
|
||||
return
|
||||
}
|
||||
|
||||
const result = await convertAddressToCoords(addressInput.value.trim())
|
||||
if (result) {
|
||||
addressList.value.push(result)
|
||||
newPlanForm.area_scope = addressList.value.map(item => ({
|
||||
longitude: item.longitude,
|
||||
latitude: item.latitude
|
||||
}))
|
||||
addressInput.value = '' // 清空输入
|
||||
}
|
||||
}
|
||||
|
||||
// 删除地址
|
||||
const handleRemoveAddress = (index) => {
|
||||
addressList.value.splice(index, 1)
|
||||
newPlanForm.area_scope = addressList.value.map(item => ({
|
||||
longitude: item.longitude,
|
||||
latitude: item.latitude
|
||||
}))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.patrol-plans {
|
||||
padding: 16px;
|
||||
background-color: #f0f2f5;
|
||||
min-height: calc(100vh - 120px);
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.main-card {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
height: 100%;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2f3d;
|
||||
}
|
||||
|
||||
.header-btns {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
height: calc(100% - 60px);
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
padding: 16px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
|
||||
.search-area {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
align-items: flex-start;
|
||||
|
||||
.search-input {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.search-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-area {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: calc(100% - 140px);
|
||||
padding: 0;
|
||||
position: relative;
|
||||
|
||||
:deep(.el-table) {
|
||||
border-radius: 8px 8px 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
th {
|
||||
background-color: #f5f7fa !important;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
margin-top: 0;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 对话框样式优化
|
||||
:deep(.el-dialog) {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
.el-dialog__header {
|
||||
margin: 0;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
background-color: #f8f9fa;
|
||||
|
||||
.el-dialog__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2f3d;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid #dcdfe6;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
}
|
||||
|
||||
// 表单项样式优化
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 22px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
.el-input__wrapper,
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-textarea__inner {
|
||||
min-height: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标签样式优化
|
||||
:deep(.el-tag) {
|
||||
border-radius: 4px;
|
||||
padding: 0 8px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
// 按钮样式优化
|
||||
:deep(.el-button) {
|
||||
&.is-link {
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.address-input-container {
|
||||
.address-input {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.address-input-field {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.address-list {
|
||||
margin-bottom: 12px;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.address-tip {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user