feat: add maintenance management frontend pages
This commit is contained in:
parent
7956379f71
commit
913d6400e4
|
|
@ -0,0 +1,156 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// ==================== 维保计划相关类型 ====================
|
||||||
|
|
||||||
|
export interface MaintenancePlan {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
projectId: string
|
||||||
|
projectName?: string
|
||||||
|
triggerType: 'MANUAL' | 'SCHEDULED' | 'AUTOMATIC'
|
||||||
|
equipmentId?: string
|
||||||
|
equipmentName?: string
|
||||||
|
spaceNodeId?: string
|
||||||
|
spaceNodeName?: string
|
||||||
|
description?: string
|
||||||
|
enabled: boolean
|
||||||
|
cronExpression?: string
|
||||||
|
nextTriggerTime?: string
|
||||||
|
createdAt?: string
|
||||||
|
updatedAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MaintenancePlanForm {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
projectId: string
|
||||||
|
triggerType: 'MANUAL' | 'SCHEDULED' | 'AUTOMATIC'
|
||||||
|
equipmentId?: string
|
||||||
|
spaceNodeId?: string
|
||||||
|
description?: string
|
||||||
|
enabled?: boolean
|
||||||
|
cronExpression?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 维保任务相关类型 ====================
|
||||||
|
|
||||||
|
export type TaskStatus = 'PENDING' | 'ACCEPTED' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED'
|
||||||
|
|
||||||
|
export interface MaintenanceTask {
|
||||||
|
id: string
|
||||||
|
planId?: string
|
||||||
|
planName?: string
|
||||||
|
projectId: string
|
||||||
|
projectName?: string
|
||||||
|
equipmentId?: string
|
||||||
|
equipmentName?: string
|
||||||
|
spaceNodeId?: string
|
||||||
|
spaceNodeName?: string
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
status: TaskStatus
|
||||||
|
assigneeId?: string
|
||||||
|
assigneeName?: string
|
||||||
|
scheduledDate?: string
|
||||||
|
startTime?: string
|
||||||
|
completedTime?: string
|
||||||
|
cancellationReason?: string
|
||||||
|
completionNotes?: string
|
||||||
|
createdAt?: string
|
||||||
|
updatedAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskQueryParams {
|
||||||
|
projectId?: string
|
||||||
|
status?: TaskStatus
|
||||||
|
assigneeId?: string
|
||||||
|
page?: number
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 维保计划 API ====================
|
||||||
|
|
||||||
|
// 获取维保计划列表
|
||||||
|
export function getMaintenancePlans(projectId: string, triggerType?: string) {
|
||||||
|
return request.get<MaintenancePlan[]>({
|
||||||
|
url: '/api/v1/ops/maintenance-plans',
|
||||||
|
params: { projectId, triggerType }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取维保计划详情
|
||||||
|
export function getMaintenancePlan(id: string) {
|
||||||
|
return request.get<MaintenancePlan>({
|
||||||
|
url: `/api/v1/ops/maintenance-plans/${id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建维保计划
|
||||||
|
export function createMaintenancePlan(data: MaintenancePlanForm) {
|
||||||
|
return request.post({
|
||||||
|
url: '/api/v1/ops/maintenance-plans',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新维保计划
|
||||||
|
export function updateMaintenancePlan(id: string, data: MaintenancePlanForm) {
|
||||||
|
return request.put({
|
||||||
|
url: `/api/v1/ops/maintenance-plans/${id}`,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除/停用维保计划
|
||||||
|
export function deleteMaintenancePlan(id: string) {
|
||||||
|
return request.delete({
|
||||||
|
url: `/api/v1/ops/maintenance-plans/${id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 维保任务 API ====================
|
||||||
|
|
||||||
|
// 获取维保任务列表
|
||||||
|
export function getMaintenanceTasks(params: TaskQueryParams) {
|
||||||
|
return request.get({
|
||||||
|
url: '/api/v1/ops/maintenance-tasks',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取维保任务详情
|
||||||
|
export function getMaintenanceTask(id: string) {
|
||||||
|
return request.get<MaintenanceTask>({
|
||||||
|
url: `/api/v1/ops/maintenance-tasks/${id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接受任务
|
||||||
|
export function acceptMaintenanceTask(id: string, userId: string) {
|
||||||
|
return request.post({
|
||||||
|
url: `/api/v1/ops/maintenance-tasks/${id}/accept`,
|
||||||
|
params: { userId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始执行任务
|
||||||
|
export function startMaintenanceTask(id: string) {
|
||||||
|
return request.post({
|
||||||
|
url: `/api/v1/ops/maintenance-tasks/${id}/start`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成维保
|
||||||
|
export function completeMaintenanceTask(id: string, data: { completionNotes?: string }) {
|
||||||
|
return request.post({
|
||||||
|
url: `/api/v1/ops/maintenance-tasks/${id}/complete`,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消任务
|
||||||
|
export function cancelMaintenanceTask(id: string) {
|
||||||
|
return request.post({
|
||||||
|
url: `/api/v1/ops/maintenance-tasks/${id}/cancel`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,18 @@
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string
|
title?: string
|
||||||
showBack?: boolean
|
showBack?: boolean
|
||||||
actions?: { icon: string; text: string; onClick?: () => void }[]
|
actions?: { icon: string; text: string; onClick?: () => void }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>()
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
title: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'back'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
|
@ -24,7 +30,8 @@ const handleBack = () => {
|
||||||
<path d="M12.5 15L7.5 10L12.5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M12.5 15L7.5 10L12.5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<h2 class="page-title">{{ title }}</h2>
|
<h2 v-if="title" class="page-title">{{ title }}</h2>
|
||||||
|
<slot name="title"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="actions && actions.length > 0" class="page-header-actions">
|
<div v-if="actions && actions.length > 0" class="page-header-actions">
|
||||||
<button
|
<button
|
||||||
|
|
@ -36,6 +43,7 @@ const handleBack = () => {
|
||||||
{{ action.text }}
|
{{ action.text }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<slot name="actions"></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,18 @@ const router = createRouter({
|
||||||
name: 'EquipmentDetail',
|
name: 'EquipmentDetail',
|
||||||
component: () => import('@/views/equipment/EquipmentDetail.vue'),
|
component: () => import('@/views/equipment/EquipmentDetail.vue'),
|
||||||
meta: { title: '设备详情' }
|
meta: { title: '设备详情' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'maintenance/plans',
|
||||||
|
name: 'MaintenancePlans',
|
||||||
|
component: () => import('@/views/maintenance/PlanList.vue'),
|
||||||
|
meta: { title: '维保计划' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'maintenance/tasks',
|
||||||
|
name: 'MaintenanceTasks',
|
||||||
|
component: () => import('@/views/maintenance/TaskList.vue'),
|
||||||
|
meta: { title: '维保任务' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,18 @@ const menuItems: MenuProps['items'] = [
|
||||||
key: 'operation',
|
key: 'operation',
|
||||||
label: '运营管理',
|
label: '运营管理',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
children: []
|
children: [
|
||||||
|
{
|
||||||
|
key: '/maintenance/plans',
|
||||||
|
icon: () => h(ToolOutlined),
|
||||||
|
label: '维保计划'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '/maintenance/tasks',
|
||||||
|
icon: () => h(ToolOutlined),
|
||||||
|
label: '维保任务'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system',
|
key: 'system',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,397 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { Button, Select, Space, message, Tag, Modal, Form, Input, InputNumber, Popconfirm } from 'ant-design-vue'
|
||||||
|
import type { ColumnsType } from 'ant-design-vue/es/table'
|
||||||
|
import {
|
||||||
|
SearchOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
import {
|
||||||
|
getMaintenancePlans,
|
||||||
|
createMaintenancePlan,
|
||||||
|
updateMaintenancePlan,
|
||||||
|
deleteMaintenancePlan,
|
||||||
|
type MaintenancePlan,
|
||||||
|
type MaintenancePlanForm
|
||||||
|
} from '@/api/maintenance'
|
||||||
|
import { getProjectSelectorList } from '@/api/project'
|
||||||
|
import { TableActions, Pagination } from '@/components'
|
||||||
|
|
||||||
|
// 触发类型映射
|
||||||
|
const triggerTypeMap: Record<string, { text: string; color: string }> = {
|
||||||
|
MANUAL: { text: '手动', color: 'default' },
|
||||||
|
SCHEDULED: { text: '定时', color: 'blue' },
|
||||||
|
AUTOMATIC: { text: '自动', color: 'green' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns: ColumnsType = [
|
||||||
|
{ title: '计划名称', dataIndex: 'name', key: 'name', width: 180 },
|
||||||
|
{ title: '所属项目', dataIndex: 'projectName', key: 'projectName', width: 150 },
|
||||||
|
{ title: '触发类型', dataIndex: 'triggerType', key: 'triggerType', width: 100 },
|
||||||
|
{ title: '关联设备', dataIndex: 'equipmentName', key: 'equipmentName', width: 150 },
|
||||||
|
{ title: 'Cron表达式', dataIndex: 'cronExpression', key: 'cronExpression', width: 120 },
|
||||||
|
{ title: '下次触发时间', dataIndex: 'nextTriggerTime', key: 'nextTriggerTime', width: 160 },
|
||||||
|
{ title: '状态', dataIndex: 'enabled', key: 'enabled', width: 80 },
|
||||||
|
{ title: '操作', key: 'action', width: 120, fixed: 'right' as const }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 项目选择选项
|
||||||
|
const projectOptions = ref<{ value: string; label: string }[]>([])
|
||||||
|
|
||||||
|
// 触发类型选项
|
||||||
|
const triggerTypeOptions = [
|
||||||
|
{ value: 'MANUAL', label: '手动' },
|
||||||
|
{ value: 'SCHEDULED', label: '定时' },
|
||||||
|
{ value: 'AUTOMATIC', label: '自动' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive({
|
||||||
|
projectId: '',
|
||||||
|
triggerType: undefined as string | undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 数据状态
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref<MaintenancePlan[]>([])
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 模态框状态
|
||||||
|
const modalVisible = ref(false)
|
||||||
|
const modalTitle = ref('新建维保计划')
|
||||||
|
const formLoading = ref(false)
|
||||||
|
const editingPlan = ref<MaintenancePlan | null>(null)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formState = reactive<MaintenancePlanForm>({
|
||||||
|
name: '',
|
||||||
|
projectId: '',
|
||||||
|
triggerType: 'MANUAL',
|
||||||
|
equipmentId: undefined,
|
||||||
|
spaceNodeId: undefined,
|
||||||
|
description: '',
|
||||||
|
enabled: true,
|
||||||
|
cronExpression: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取项目列表
|
||||||
|
const fetchProjects = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getProjectSelectorList()
|
||||||
|
projectOptions.value = (res.data.data || []).map((item: any) => ({
|
||||||
|
value: item.id,
|
||||||
|
label: item.name
|
||||||
|
}))
|
||||||
|
} catch {
|
||||||
|
message.error('获取项目列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取维保计划列表
|
||||||
|
const fetchPlanList = async () => {
|
||||||
|
if (!queryParams.projectId) {
|
||||||
|
message.warning('请先选择项目')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getMaintenancePlans(queryParams.projectId, queryParams.triggerType)
|
||||||
|
const data = res.data.data || []
|
||||||
|
tableData.value = data
|
||||||
|
pagination.total = data.length
|
||||||
|
} catch {
|
||||||
|
message.error('获取维保计划列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.current = 1
|
||||||
|
fetchPlanList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
queryParams.projectId = ''
|
||||||
|
queryParams.triggerType = undefined
|
||||||
|
pagination.current = 1
|
||||||
|
tableData.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页变化
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
pagination.current = page
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
fetchPlanList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开新建模态框
|
||||||
|
const handleAdd = () => {
|
||||||
|
editingPlan.value = null
|
||||||
|
modalTitle.value = '新建维保计划'
|
||||||
|
formState.name = ''
|
||||||
|
formState.projectId = queryParams.projectId
|
||||||
|
formState.triggerType = 'MANUAL'
|
||||||
|
formState.equipmentId = undefined
|
||||||
|
formState.spaceNodeId = undefined
|
||||||
|
formState.description = ''
|
||||||
|
formState.enabled = true
|
||||||
|
formState.cronExpression = ''
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开编辑模态框
|
||||||
|
const handleEdit = (record: MaintenancePlan) => {
|
||||||
|
editingPlan.value = record
|
||||||
|
modalTitle.value = '编辑维保计划'
|
||||||
|
formState.id = record.id
|
||||||
|
formState.name = record.name
|
||||||
|
formState.projectId = record.projectId
|
||||||
|
formState.triggerType = record.triggerType
|
||||||
|
formState.equipmentId = record.equipmentId
|
||||||
|
formState.spaceNodeId = record.spaceNodeId
|
||||||
|
formState.description = record.description || ''
|
||||||
|
formState.enabled = record.enabled
|
||||||
|
formState.cronExpression = record.cronExpression || ''
|
||||||
|
modalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
if (editingPlan.value) {
|
||||||
|
await updateMaintenancePlan(editingPlan.value.id, formState)
|
||||||
|
message.success('更新成功')
|
||||||
|
} else {
|
||||||
|
await createMaintenancePlan(formState)
|
||||||
|
message.success('创建成功')
|
||||||
|
}
|
||||||
|
modalVisible.value = false
|
||||||
|
fetchPlanList()
|
||||||
|
} catch {
|
||||||
|
message.error(editingPlan.value ? '更新失败' : '创建失败')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除计划
|
||||||
|
const handleDelete = async (id: string) => {
|
||||||
|
try {
|
||||||
|
await deleteMaintenancePlan(id)
|
||||||
|
message.success('停用成功')
|
||||||
|
fetchPlanList()
|
||||||
|
} catch {
|
||||||
|
message.error('停用失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (date: string | Date | undefined) => {
|
||||||
|
if (!date) return '-'
|
||||||
|
const d = new Date(date)
|
||||||
|
const year = d.getFullYear()
|
||||||
|
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(d.getDate()).padStart(2, '0')
|
||||||
|
const hour = String(d.getHours()).padStart(2, '0')
|
||||||
|
const minute = String(d.getMinutes()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day} ${hour}:${minute}`
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchProjects()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<h2 class="page-title">维保计划</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选区 -->
|
||||||
|
<div class="filter-bar">
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
v-model:value="queryParams.projectId"
|
||||||
|
placeholder="请选择项目"
|
||||||
|
style="width: 240px"
|
||||||
|
allow-clear
|
||||||
|
:options="projectOptions"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-model:value="queryParams.triggerType"
|
||||||
|
placeholder="触发类型"
|
||||||
|
style="width: 120px"
|
||||||
|
allow-clear
|
||||||
|
:options="triggerTypeOptions"
|
||||||
|
/>
|
||||||
|
<Button type="primary" @click="handleSearch">
|
||||||
|
<SearchOutlined /> 查询
|
||||||
|
</Button>
|
||||||
|
<Button @click="handleReset">
|
||||||
|
<ReloadOutlined /> 重置
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" @click="handleAdd">
|
||||||
|
<PlusOutlined /> 新建
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区 -->
|
||||||
|
<div class="table-card">
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
:row-key="(record: MaintenancePlan) => record.id"
|
||||||
|
:pagination="{
|
||||||
|
current: pagination.current,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
total: pagination.total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total: number) => `共 ${total} 条`
|
||||||
|
}"
|
||||||
|
@change="(pag: any) => handlePageChange(pag.current, pag.pageSize)"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'triggerType'">
|
||||||
|
<Tag :color="triggerTypeMap[record.triggerType]?.color">
|
||||||
|
{{ triggerTypeMap[record.triggerType]?.text }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'nextTriggerTime'">
|
||||||
|
{{ formatDate(record.nextTriggerTime) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'enabled'">
|
||||||
|
<Tag :color="record.enabled ? 'green' : 'red'">
|
||||||
|
{{ record.enabled ? '启用' : '停用' }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<Space>
|
||||||
|
<Button type="link" size="small" @click="handleEdit(record)">
|
||||||
|
<EditOutlined /> 编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定要停用该计划吗?"
|
||||||
|
@confirm="handleDelete(record.id)"
|
||||||
|
>
|
||||||
|
<Button type="link" size="small" danger>
|
||||||
|
<DeleteOutlined /> 停用
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
<!-- 未选择项目提示 -->
|
||||||
|
<a-empty v-if="!queryParams.projectId && tableData.length === 0" description="请先选择项目" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 新建/编辑模态框 -->
|
||||||
|
<Modal
|
||||||
|
v-model:open="modalVisible"
|
||||||
|
:title="modalTitle"
|
||||||
|
:footer="null"
|
||||||
|
width="600px"
|
||||||
|
@cancel="modalVisible = false"
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
:model="formState"
|
||||||
|
layout="vertical"
|
||||||
|
@finish="handleSubmit"
|
||||||
|
>
|
||||||
|
<Form.Item label="计划名称" name="name" :rules="[{ required: true, message: '请输入计划名称' }]">
|
||||||
|
<Input v-model:value="formState.name" placeholder="请输入计划名称" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="所属项目" name="projectId" :rules="[{ required: true, message: '请选择项目' }]">
|
||||||
|
<Select
|
||||||
|
v-model:value="formState.projectId"
|
||||||
|
placeholder="请选择项目"
|
||||||
|
:options="projectOptions"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="触发类型" name="triggerType" :rules="[{ required: true, message: '请选择触发类型' }]">
|
||||||
|
<Select
|
||||||
|
v-model:value="formState.triggerType"
|
||||||
|
placeholder="请选择触发类型"
|
||||||
|
:options="triggerTypeOptions"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="Cron表达式" name="cronExpression">
|
||||||
|
<Input v-model:value="formState.cronExpression" placeholder="如: 0 0 2 * * ?" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="描述" name="description">
|
||||||
|
<Input.TextArea v-model:value="formState.description" placeholder="请输入描述" :rows="3" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="启用状态" name="enabled">
|
||||||
|
<Select
|
||||||
|
v-model:value="formState.enabled"
|
||||||
|
:options="[
|
||||||
|
{ value: true, label: '启用' },
|
||||||
|
{ value: false, label: '停用' }
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item style="margin-bottom: 0; text-align: right">
|
||||||
|
<Space>
|
||||||
|
<Button @click="modalVisible = false">取消</Button>
|
||||||
|
<Button type="primary" html-type="submit" :loading="formLoading">
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #262626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-bar {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,401 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { Button, Select, Space, message, Badge, Modal, Input, Form, Popconfirm } from 'ant-design-vue'
|
||||||
|
import type { ColumnsType } from 'ant-design-vue/es/table'
|
||||||
|
import {
|
||||||
|
SearchOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
PlayCircleOutlined,
|
||||||
|
CloseCircleOutlined,
|
||||||
|
UserOutlined
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
import {
|
||||||
|
getMaintenanceTasks,
|
||||||
|
acceptMaintenanceTask,
|
||||||
|
startMaintenanceTask,
|
||||||
|
completeMaintenanceTask,
|
||||||
|
cancelMaintenanceTask,
|
||||||
|
type MaintenanceTask,
|
||||||
|
type TaskStatus
|
||||||
|
} from '@/api/maintenance'
|
||||||
|
import { getProjectSelectorList } from '@/api/project'
|
||||||
|
import { getUserList } from '@/api/user'
|
||||||
|
|
||||||
|
// 任务状态映射
|
||||||
|
const statusMap: Record<TaskStatus, { text: string; color: string; status: 'default' | 'processing' | 'success' | 'error' | 'warning' | 'default' }> = {
|
||||||
|
PENDING: { text: '待接受', color: 'default', status: 'default' },
|
||||||
|
ACCEPTED: { text: '已接受', color: 'blue', status: 'processing' },
|
||||||
|
IN_PROGRESS: { text: '进行中', color: 'processing', status: 'processing' },
|
||||||
|
COMPLETED: { text: '已完成', color: 'success', status: 'success' },
|
||||||
|
CANCELLED: { text: '已取消', color: 'error', status: 'error' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns: ColumnsType = [
|
||||||
|
{ title: '任务标题', dataIndex: 'title', key: 'title', width: 180 },
|
||||||
|
{ title: '所属项目', dataIndex: 'projectName', key: 'projectName', width: 150 },
|
||||||
|
{ title: '关联设备', dataIndex: 'equipmentName', key: 'equipmentName', width: 150 },
|
||||||
|
{ title: '负责人', dataIndex: 'assigneeName', key: 'assigneeName', width: 100 },
|
||||||
|
{ title: '计划日期', dataIndex: 'scheduledDate', key: 'scheduledDate', width: 120 },
|
||||||
|
{ title: '开始时间', dataIndex: 'startTime', key: 'startTime', width: 160 },
|
||||||
|
{ title: '完成时间', dataIndex: 'completedTime', key: 'completedTime', width: 160 },
|
||||||
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 100 },
|
||||||
|
{ title: '操作', key: 'action', width: 200, fixed: 'right' as const }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 项目选择选项
|
||||||
|
const projectOptions = ref<{ value: string; label: string }[]>([])
|
||||||
|
|
||||||
|
// 状态选项
|
||||||
|
const statusOptions = [
|
||||||
|
{ value: 'PENDING', label: '待接受' },
|
||||||
|
{ value: 'ACCEPTED', label: '已接受' },
|
||||||
|
{ value: 'IN_PROGRESS', label: '进行中' },
|
||||||
|
{ value: 'COMPLETED', label: '已完成' },
|
||||||
|
{ value: 'CANCELLED', label: '取消' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive({
|
||||||
|
projectId: '',
|
||||||
|
status: undefined as TaskStatus | undefined,
|
||||||
|
assigneeId: undefined as string | undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 数据状态
|
||||||
|
const loading = ref(false)
|
||||||
|
const tableData = ref<MaintenanceTask[]>([])
|
||||||
|
const pagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 取消模态框
|
||||||
|
const cancelModalVisible = ref(false)
|
||||||
|
const cancelReason = ref('')
|
||||||
|
const cancellingTaskId = ref<string | null>(null)
|
||||||
|
|
||||||
|
// 完成模态框
|
||||||
|
const completeModalVisible = ref(false)
|
||||||
|
const completionNotes = ref('')
|
||||||
|
const completingTaskId = ref<string | null>(null)
|
||||||
|
|
||||||
|
// 获取项目列表
|
||||||
|
const fetchProjects = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getProjectSelectorList()
|
||||||
|
projectOptions.value = (res.data.data || []).map((item: any) => ({
|
||||||
|
value: item.id,
|
||||||
|
label: item.name
|
||||||
|
}))
|
||||||
|
} catch {
|
||||||
|
message.error('获取项目列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取维保任务列表
|
||||||
|
const fetchTaskList = async () => {
|
||||||
|
if (!queryParams.projectId) {
|
||||||
|
message.warning('请先选择项目')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getMaintenanceTasks({
|
||||||
|
projectId: queryParams.projectId,
|
||||||
|
status: queryParams.status,
|
||||||
|
assigneeId: queryParams.assigneeId
|
||||||
|
})
|
||||||
|
const data = res.data.data || []
|
||||||
|
tableData.value = data
|
||||||
|
pagination.total = data.length
|
||||||
|
} catch {
|
||||||
|
message.error('获取维保任务列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
pagination.current = 1
|
||||||
|
fetchTaskList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
queryParams.projectId = ''
|
||||||
|
queryParams.status = undefined
|
||||||
|
queryParams.assigneeId = undefined
|
||||||
|
pagination.current = 1
|
||||||
|
tableData.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页变化
|
||||||
|
const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
pagination.current = page
|
||||||
|
pagination.pageSize = pageSize
|
||||||
|
fetchTaskList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接受任务
|
||||||
|
const handleAccept = async (id: string) => {
|
||||||
|
try {
|
||||||
|
await acceptMaintenanceTask(id, 'current-user-id')
|
||||||
|
message.success('任务已接受')
|
||||||
|
fetchTaskList()
|
||||||
|
} catch {
|
||||||
|
message.error('接受任务失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始任务
|
||||||
|
const handleStart = async (id: string) => {
|
||||||
|
try {
|
||||||
|
await startMaintenanceTask(id)
|
||||||
|
message.success('任务已开始')
|
||||||
|
fetchTaskList()
|
||||||
|
} catch {
|
||||||
|
message.error('开始任务失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开完成模态框
|
||||||
|
const handleOpenComplete = (id: string) => {
|
||||||
|
completingTaskId.value = id
|
||||||
|
completionNotes.value = ''
|
||||||
|
completeModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成任务
|
||||||
|
const handleComplete = async () => {
|
||||||
|
if (!completingTaskId.value) return
|
||||||
|
try {
|
||||||
|
await completeMaintenanceTask(completingTaskId.value, { completionNotes: completionNotes.value })
|
||||||
|
message.success('任务已完成')
|
||||||
|
completeModalVisible.value = false
|
||||||
|
completingTaskId.value = null
|
||||||
|
completionNotes.value = ''
|
||||||
|
fetchTaskList()
|
||||||
|
} catch {
|
||||||
|
message.error('完成任务失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开取消模态框
|
||||||
|
const handleOpenCancel = (id: string) => {
|
||||||
|
cancellingTaskId.value = id
|
||||||
|
cancelReason.value = ''
|
||||||
|
cancelModalVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消任务
|
||||||
|
const handleCancel = async () => {
|
||||||
|
if (!cancellingTaskId.value) return
|
||||||
|
try {
|
||||||
|
await cancelMaintenanceTask(cancellingTaskId.value)
|
||||||
|
message.success('任务已取消')
|
||||||
|
cancelModalVisible.value = false
|
||||||
|
cancellingTaskId.value = null
|
||||||
|
cancelReason.value = ''
|
||||||
|
fetchTaskList()
|
||||||
|
} catch {
|
||||||
|
message.error('取消任务失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期时间
|
||||||
|
const formatDateTime = (date: string | Date | undefined) => {
|
||||||
|
if (!date) return '-'
|
||||||
|
const d = new Date(date)
|
||||||
|
const year = d.getFullYear()
|
||||||
|
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(d.getDate()).padStart(2, '0')
|
||||||
|
const hour = String(d.getHours()).padStart(2, '0')
|
||||||
|
const minute = String(d.getMinutes()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day} ${hour}:${minute}`
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchProjects()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="page-container">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<h2 class="page-title">维保任务</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选区 -->
|
||||||
|
<div class="filter-bar">
|
||||||
|
<Space>
|
||||||
|
<Select
|
||||||
|
v-model:value="queryParams.projectId"
|
||||||
|
placeholder="请选择项目"
|
||||||
|
style="width: 240px"
|
||||||
|
allow-clear
|
||||||
|
:options="projectOptions"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-model:value="queryParams.status"
|
||||||
|
placeholder="任务状态"
|
||||||
|
style="width: 120px"
|
||||||
|
allow-clear
|
||||||
|
:options="statusOptions"
|
||||||
|
/>
|
||||||
|
<Button type="primary" @click="handleSearch">
|
||||||
|
<SearchOutlined /> 查询
|
||||||
|
</Button>
|
||||||
|
<Button @click="handleReset">
|
||||||
|
<ReloadOutlined /> 重置
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区 -->
|
||||||
|
<div class="table-card">
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
:row-key="(record: MaintenanceTask) => record.id"
|
||||||
|
:pagination="{
|
||||||
|
current: pagination.current,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
total: pagination.total,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total: number) => `共 ${total} 条`
|
||||||
|
}"
|
||||||
|
@change="(pag: any) => handlePageChange(pag.current, pag.pageSize)"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'assigneeName'">
|
||||||
|
<Space>
|
||||||
|
<UserOutlined />
|
||||||
|
{{ record.assigneeName || '-' }}
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'scheduledDate'">
|
||||||
|
{{ record.scheduledDate || '-' }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'startTime'">
|
||||||
|
{{ formatDateTime(record.startTime) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'completedTime'">
|
||||||
|
{{ formatDateTime(record.completedTime) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'status'">
|
||||||
|
<Badge
|
||||||
|
:status="statusMap[record.status]?.status"
|
||||||
|
:text="statusMap[record.status]?.text"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<Space>
|
||||||
|
<!-- 待接受状态显示接受按钮 -->
|
||||||
|
<Button
|
||||||
|
v-if="record.status === 'PENDING'"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="handleAccept(record.id)"
|
||||||
|
>
|
||||||
|
<CheckCircleOutlined /> 接受
|
||||||
|
</Button>
|
||||||
|
<!-- 已接受状态显示开始按钮 -->
|
||||||
|
<Button
|
||||||
|
v-if="record.status === 'ACCEPTED'"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="handleStart(record.id)"
|
||||||
|
>
|
||||||
|
<PlayCircleOutlined /> 开始
|
||||||
|
</Button>
|
||||||
|
<!-- 进行中状态显示完成按钮 -->
|
||||||
|
<Button
|
||||||
|
v-if="record.status === 'IN_PROGRESS'"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="handleOpenComplete(record.id)"
|
||||||
|
>
|
||||||
|
<CheckCircleOutlined /> 完成
|
||||||
|
</Button>
|
||||||
|
<!-- 待接受、已接受、进行中状态显示取消按钮 -->
|
||||||
|
<Button
|
||||||
|
v-if="['PENDING', 'ACCEPTED', 'IN_PROGRESS'].includes(record.status)"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
danger
|
||||||
|
@click="handleOpenCancel(record.id)"
|
||||||
|
>
|
||||||
|
<CloseCircleOutlined /> 取消
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
|
<!-- 未选择项目提示 -->
|
||||||
|
<a-empty v-if="!queryParams.projectId && tableData.length === 0" description="请先选择项目" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 取消任务模态框 -->
|
||||||
|
<Modal
|
||||||
|
v-model:open="cancelModalVisible"
|
||||||
|
title="取消任务"
|
||||||
|
@ok="handleCancel"
|
||||||
|
>
|
||||||
|
<p>确定要取消该维保任务吗?</p>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<!-- 完成任务模态框 -->
|
||||||
|
<Modal
|
||||||
|
v-model:open="completeModalVisible"
|
||||||
|
title="完成任务"
|
||||||
|
@ok="handleComplete"
|
||||||
|
>
|
||||||
|
<Form layout="vertical">
|
||||||
|
<Form.Item label="完成备注">
|
||||||
|
<Input.TextArea
|
||||||
|
v-model:value="completionNotes"
|
||||||
|
placeholder="请输入完成备注"
|
||||||
|
:rows="4"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #262626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-bar {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted, computed } from 'vue'
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Button, Drawer, Form, Input, Select, Space, message, Tag, Descriptions, DescriptionsItem, Divider, Card, Statistic, Row, Col } from 'ant-design-vue'
|
import { Button, Drawer, Form, Input, Select, Space, message, Tag, Descriptions, DescriptionsItem, Divider, Card, Statistic, Row, Col, Tabs, TabPane, Table, Empty } from 'ant-design-vue'
|
||||||
import type { ColumnsType } from 'ant-design-vue/es/table'
|
import type { ColumnsType } from 'ant-design-vue/es/table'
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
ReloadOutlined
|
ReloadOutlined,
|
||||||
|
UserAddOutlined
|
||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue'
|
||||||
import {
|
import {
|
||||||
queryProjects,
|
queryProjects,
|
||||||
|
|
@ -14,7 +15,8 @@ import {
|
||||||
updateProject,
|
updateProject,
|
||||||
deleteProject,
|
deleteProject,
|
||||||
enableProject,
|
enableProject,
|
||||||
disableProject
|
disableProject,
|
||||||
|
getProjectStatistics
|
||||||
} from '@/api/project'
|
} from '@/api/project'
|
||||||
import { TableActions, Pagination, StatusTag } from '@/components'
|
import { TableActions, Pagination, StatusTag } from '@/components'
|
||||||
import type { Project } from '@/types'
|
import type { Project } from '@/types'
|
||||||
|
|
@ -61,6 +63,14 @@ const typeOptions = Object.entries(ProjectTypeMap).map(([value, { label }]) => (
|
||||||
label
|
label
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// 成员表格列
|
||||||
|
const memberColumns: ColumnsType = [
|
||||||
|
{ title: '姓名', dataIndex: 'realName', key: 'realName' },
|
||||||
|
{ title: '用户名', dataIndex: 'username', key: 'username' },
|
||||||
|
{ title: '角色', dataIndex: 'roleInProject', key: 'roleInProject' },
|
||||||
|
{ title: '加入时间', dataIndex: 'createdAt', key: 'createdAt' }
|
||||||
|
]
|
||||||
|
|
||||||
// 查询参数
|
// 查询参数
|
||||||
const queryParams = reactive<ProjectQuery>({
|
const queryParams = reactive<ProjectQuery>({
|
||||||
keyword: '',
|
keyword: '',
|
||||||
|
|
@ -99,6 +109,35 @@ const viewStatistics = ref<any>(null)
|
||||||
const viewMembers = ref<any[]>([])
|
const viewMembers = ref<any[]>([])
|
||||||
const viewActiveTab = ref('info')
|
const viewActiveTab = ref('info')
|
||||||
|
|
||||||
|
// 编辑抽屉状态
|
||||||
|
const editDrawerVisible = ref(false)
|
||||||
|
const editProject = ref<Project | null>(null)
|
||||||
|
const editLoading = ref(false)
|
||||||
|
const editActiveTab = ref('info')
|
||||||
|
const editFormState = ref({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
address: '',
|
||||||
|
projectType: 'RESIDENTIAL' as ProjectType,
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
status: 'ACTIVE'
|
||||||
|
})
|
||||||
|
const editSubmitting = ref(false)
|
||||||
|
|
||||||
|
// 新增表单状态
|
||||||
|
const createFormState = ref({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
address: '',
|
||||||
|
projectType: 'RESIDENTIAL' as ProjectType,
|
||||||
|
province: '',
|
||||||
|
city: '',
|
||||||
|
district: '',
|
||||||
|
status: 'ACTIVE'
|
||||||
|
})
|
||||||
|
|
||||||
// 点击项目名称查看详情
|
// 点击项目名称查看详情
|
||||||
const handleNameClick = async (record: Project) => {
|
const handleNameClick = async (record: Project) => {
|
||||||
viewProject.value = record
|
viewProject.value = record
|
||||||
|
|
@ -118,17 +157,13 @@ const fetchViewData = async (id: string) => {
|
||||||
viewLoading.value = false
|
viewLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const formState = ref<ProjectFormData>({
|
|
||||||
id: '',
|
// 编辑项目
|
||||||
name: '',
|
const handleEdit = async (record: Project) => {
|
||||||
description: '',
|
editProject.value = record
|
||||||
address: '',
|
editActiveTab.value = 'info'
|
||||||
projectType: 'RESIDENTIAL',
|
editDrawerVisible.value = true
|
||||||
province: '',
|
}
|
||||||
city: '',
|
|
||||||
district: '',
|
|
||||||
status: 'ACTIVE'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取项目列表
|
// 获取项目列表
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
|
|
@ -173,9 +208,7 @@ const handlePageChange = (page: number, pageSize: number) => {
|
||||||
|
|
||||||
// 新增
|
// 新增
|
||||||
const handleAdd = async () => {
|
const handleAdd = async () => {
|
||||||
drawerTitle.value = '新增项目'
|
createFormState.value = {
|
||||||
formState.value = {
|
|
||||||
id: '',
|
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
address: '',
|
address: '',
|
||||||
|
|
@ -188,26 +221,29 @@ const handleAdd = async () => {
|
||||||
drawerVisible.value = true
|
drawerVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑
|
// 快速编辑项目
|
||||||
const handleEdit = (record: Project) => {
|
const handleQuickEdit = async (record: Project) => {
|
||||||
drawerTitle.value = '编辑项目'
|
editProject.value = record
|
||||||
formState.value = {
|
editActiveTab.value = 'info'
|
||||||
id: record.id,
|
editFormState.value = {
|
||||||
name: record.name,
|
name: record.name,
|
||||||
description: record.description || '',
|
description: record.description || '',
|
||||||
address: record.address || '',
|
address: record.address || '',
|
||||||
projectType: record.projectType,
|
projectType: record.projectType || 'RESIDENTIAL',
|
||||||
province: record.province || '',
|
province: record.province || '',
|
||||||
city: record.city || '',
|
city: record.city || '',
|
||||||
district: record.district || '',
|
district: record.district || '',
|
||||||
status: record.status
|
status: record.status || 'ACTIVE'
|
||||||
}
|
}
|
||||||
drawerVisible.value = true
|
editDrawerVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看详情
|
// 查看详情
|
||||||
const handleView = (record: Project) => {
|
const handleViewProject = async (record: Project) => {
|
||||||
router.push(`/project/detail/${record.id}`)
|
viewProject.value = record
|
||||||
|
viewActiveTab.value = 'info'
|
||||||
|
viewDrawerVisible.value = true
|
||||||
|
await fetchViewData(record.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 成员管理
|
// 成员管理
|
||||||
|
|
@ -248,32 +284,32 @@ const handleDelete = async (id: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const handleSubmit = async () => {
|
const handleCreateSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
await formRef.value.validate()
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
|
await createProject(createFormState.value)
|
||||||
if (formState.value.id) {
|
|
||||||
await updateProject(formState.value.id, formState.value)
|
|
||||||
message.success('更新成功')
|
|
||||||
} else {
|
|
||||||
await createProject(formState.value)
|
|
||||||
message.success('创建成功')
|
message.success('创建成功')
|
||||||
}
|
|
||||||
drawerVisible.value = false
|
drawerVisible.value = false
|
||||||
fetchProjects()
|
fetchProjects()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.errorFields) return
|
message.error('创建失败')
|
||||||
message.error('操作失败')
|
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭抽屉
|
const handleEditSubmit = async () => {
|
||||||
const handleClose = () => {
|
try {
|
||||||
formRef.value?.resetFields()
|
editSubmitting.value = true
|
||||||
drawerVisible.value = false
|
await updateProject(editProject.value!.id, editFormState.value)
|
||||||
|
message.success('更新成功')
|
||||||
|
editDrawerVisible.value = false
|
||||||
|
fetchProjects()
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error('更新失败')
|
||||||
|
} finally {
|
||||||
|
editSubmitting.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 状态标签配置
|
// 状态标签配置
|
||||||
|
|
@ -401,51 +437,132 @@ onMounted(fetchProjects)
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 抽屉 -->
|
<!-- 新增抽屉 -->
|
||||||
<Drawer
|
<Drawer
|
||||||
v-model:open="drawerVisible"
|
v-model:open="drawerVisible"
|
||||||
:title="drawerTitle"
|
title="新增项目"
|
||||||
width="560px"
|
width="560px"
|
||||||
:footer-style="{ textAlign: 'right' }"
|
:footer-style="{ textAlign: 'right' }"
|
||||||
@close="handleClose"
|
@close="drawerVisible = false"
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
ref="formRef"
|
:model="createFormState"
|
||||||
:model="formState"
|
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
:rules="{
|
:rules="{
|
||||||
name: [{ required: true, message: '请输入项目名称' }]
|
name: [{ required: true, message: '请输入项目名称' }]
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<Form.Item label="项目名称" name="name">
|
<Form.Item label="项目名称" name="name">
|
||||||
<Input v-model:value="formState.name" placeholder="请输入项目名称" />
|
<Input v-model:value="createFormState.name" placeholder="请输入项目名称" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="项目类型" name="projectType">
|
<Form.Item label="项目类型" name="projectType">
|
||||||
<Select v-model:value="formState.projectType" placeholder="请选择项目类型" :options="typeOptions" />
|
<Select v-model:value="createFormState.projectType" placeholder="请选择项目类型" :options="typeOptions" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="描述" name="description">
|
<Form.Item label="描述" name="description">
|
||||||
<Input.TextArea v-model:value="formState.description" placeholder="请输入描述" :rows="2" />
|
<Input.TextArea v-model:value="createFormState.description" placeholder="请输入描述" :rows="2" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="省份" name="province">
|
<Form.Item label="省份" name="province">
|
||||||
<Input v-model:value="formState.province" placeholder="请输入省份" />
|
<Input v-model:value="createFormState.province" placeholder="请输入省份" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="城市" name="city">
|
<Form.Item label="城市" name="city">
|
||||||
<Input v-model:value="formState.city" placeholder="请输入城市" />
|
<Input v-model:value="createFormState.city" placeholder="请输入城市" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="区县" name="district">
|
<Form.Item label="区县" name="district">
|
||||||
<Input v-model:value="formState.district" placeholder="请输入区县" />
|
<Input v-model:value="createFormState.district" placeholder="请输入区县" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="详细地址" name="address">
|
<Form.Item label="详细地址" name="address">
|
||||||
<Input v-model:value="formState.address" placeholder="请输入详细地址" />
|
<Input v-model:value="createFormState.address" placeholder="请输入详细地址" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="状态" name="status">
|
<Form.Item label="状态" name="status">
|
||||||
<Select v-model:value="formState.status" :options="statusOptions" />
|
<Select v-model:value="createFormState.status" :options="statusOptions" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<Space>
|
<Space>
|
||||||
<Button @click="handleClose">取消</Button>
|
<Button @click="drawerVisible = false">取消</Button>
|
||||||
<Button type="primary" :loading="submitting" @click="handleSubmit">确定</Button>
|
<Button type="primary" :loading="submitting" @click="handleCreateSubmit">确定</Button>
|
||||||
|
</Space>
|
||||||
|
</template>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
|
<!-- 编辑抽屉 -->
|
||||||
|
<Drawer
|
||||||
|
v-model:open="editDrawerVisible"
|
||||||
|
title="编辑项目"
|
||||||
|
width="900px"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
>
|
||||||
|
<template v-if="editProject">
|
||||||
|
<Tabs v-model:activeKey="editActiveTab">
|
||||||
|
<TabPane key="info" tab="基本信息">
|
||||||
|
<Form
|
||||||
|
:model="editFormState"
|
||||||
|
layout="vertical"
|
||||||
|
:rules="{
|
||||||
|
name: [{ required: true, message: '请输入项目名称' }]
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<Row :gutter="16">
|
||||||
|
<Col :span="12">
|
||||||
|
<Form.Item label="项目名称" name="name">
|
||||||
|
<Input v-model:value="editFormState.name" placeholder="请输入项目名称" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :span="12">
|
||||||
|
<Form.Item label="项目类型" name="projectType">
|
||||||
|
<Select v-model:value="editFormState.projectType" :options="typeOptions" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :span="12">
|
||||||
|
<Form.Item label="省份" name="province">
|
||||||
|
<Input v-model:value="editFormState.province" placeholder="请输入省份" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :span="12">
|
||||||
|
<Form.Item label="城市" name="city">
|
||||||
|
<Input v-model:value="editFormState.city" placeholder="请输入城市" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :span="12">
|
||||||
|
<Form.Item label="区县" name="district">
|
||||||
|
<Input v-model:value="editFormState.district" placeholder="请输入区县" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :span="12">
|
||||||
|
<Form.Item label="状态" name="status">
|
||||||
|
<Select v-model:value="editFormState.status" :options="statusOptions" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :span="24">
|
||||||
|
<Form.Item label="详细地址" name="address">
|
||||||
|
<Input.TextArea v-model:value="editFormState.address" placeholder="请输入详细地址" :rows="2" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col :span="24">
|
||||||
|
<Form.Item label="描述" name="description">
|
||||||
|
<Input.TextArea v-model:value="editFormState.description" placeholder="请输入描述" :rows="3" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane key="members" tab="成员管理">
|
||||||
|
<div style="text-align: right; margin-bottom: 8px">
|
||||||
|
<Button type="primary" size="small">
|
||||||
|
<UserAddOutlined /> 添加成员
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Table :columns="memberColumns" :dataSource="[]" :pagination="false" size="small" />
|
||||||
|
</TabPane>
|
||||||
|
<TabPane key="config" tab="功能配置">
|
||||||
|
<Empty description="暂无配置数据" />
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<Space>
|
||||||
|
<Button @click="editDrawerVisible = false">取消</Button>
|
||||||
|
<Button type="primary" :loading="editSubmitting" @click="handleEditSubmit">确定</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</template>
|
</template>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
@ -453,9 +570,45 @@ onMounted(fetchProjects)
|
||||||
<Drawer
|
<Drawer
|
||||||
v-model:open="viewDrawerVisible"
|
v-model:open="viewDrawerVisible"
|
||||||
title="项目详情"
|
title="项目详情"
|
||||||
width="600px"
|
width="900px"
|
||||||
|
:destroyOnClose="true"
|
||||||
>
|
>
|
||||||
<template v-if="viewProject">
|
<template v-if="viewProject">
|
||||||
|
<Row :gutter="16" class="statistics-row">
|
||||||
|
<Col :span="4">
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic title="成员数" :value="viewStatistics?.memberCount || 0" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col :span="4">
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic title="楼栋数" :value="viewStatistics?.buildingCount || 0" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col :span="4">
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic title="房间数" :value="viewStatistics?.roomCount || 0" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col :span="4">
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic title="业主数" :value="viewStatistics?.ownerCount || 0" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col :span="4">
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic title="租户数" :value="viewStatistics?.tenantCount || 0" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col :span="4">
|
||||||
|
<Card size="small">
|
||||||
|
<Statistic title="进行中任务" :value="viewStatistics?.activeTaskCount || 0" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Tabs v-model:activeKey="viewActiveTab" style="margin-top: 16px">
|
||||||
|
<TabPane key="info" tab="基本信息">
|
||||||
<Descriptions :column="2" bordered size="small">
|
<Descriptions :column="2" bordered size="small">
|
||||||
<DescriptionsItem label="项目名称">{{ viewProject.name }}</DescriptionsItem>
|
<DescriptionsItem label="项目名称">{{ viewProject.name }}</DescriptionsItem>
|
||||||
<DescriptionsItem label="项目类型">
|
<DescriptionsItem label="项目类型">
|
||||||
|
|
@ -473,25 +626,27 @@ onMounted(fetchProjects)
|
||||||
<DescriptionsItem label="城市">{{ viewProject.city || '-' }}</DescriptionsItem>
|
<DescriptionsItem label="城市">{{ viewProject.city || '-' }}</DescriptionsItem>
|
||||||
<DescriptionsItem label="区县">{{ viewProject.district || '-' }}</DescriptionsItem>
|
<DescriptionsItem label="区县">{{ viewProject.district || '-' }}</DescriptionsItem>
|
||||||
<DescriptionsItem label="详细地址" :span="2">{{ viewProject.address || '-' }}</DescriptionsItem>
|
<DescriptionsItem label="详细地址" :span="2">{{ viewProject.address || '-' }}</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="创建时间">{{ viewProject.createdAt || '-' }}</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="更新时间">{{ viewProject.updatedAt || '-' }}</DescriptionsItem>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
<Divider />
|
</TabPane>
|
||||||
<Row :gutter="16">
|
<TabPane key="members" tab="成员管理">
|
||||||
<Col :span="8">
|
<div style="text-align: right; margin-bottom: 8px">
|
||||||
<Card size="small">
|
<Button type="primary" size="small">
|
||||||
<Statistic title="楼栋数" :value="viewProject.buildingCount || 0" />
|
<UserAddOutlined /> 添加成员
|
||||||
</Card>
|
</Button>
|
||||||
</Col>
|
</div>
|
||||||
<Col :span="8">
|
<Table
|
||||||
<Card size="small">
|
:columns="memberColumns"
|
||||||
<Statistic title="单元数" :value="viewProject.unitCount || 0" />
|
:dataSource="viewMembers"
|
||||||
</Card>
|
:pagination="false"
|
||||||
</Col>
|
size="small"
|
||||||
<Col :span="8">
|
/>
|
||||||
<Card size="small">
|
</TabPane>
|
||||||
<Statistic title="房间数" :value="viewProject.roomCount || 0" />
|
<TabPane key="config" tab="功能配置">
|
||||||
</Card>
|
<Empty description="暂无配置数据" />
|
||||||
</Col>
|
</TabPane>
|
||||||
</Row>
|
</Tabs>
|
||||||
</template>
|
</template>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,16 @@ import 'dayjs/locale/zh-cn'
|
||||||
import type { Dayjs } from 'dayjs'
|
import type { Dayjs } from 'dayjs'
|
||||||
import { getAuditLogs, getAuditModules, getAuditActions, getAuditStats } from '@/api/audit'
|
import { getAuditLogs, getAuditModules, getAuditActions, getAuditStats } from '@/api/audit'
|
||||||
import type { AuditLog } from '@/api/audit'
|
import type { AuditLog } from '@/api/audit'
|
||||||
|
import {
|
||||||
|
PageHeader,
|
||||||
|
FilterBar,
|
||||||
|
TableCard,
|
||||||
|
TableToolbar,
|
||||||
|
Pagination
|
||||||
|
} from '@/components'
|
||||||
|
|
||||||
dayjs.locale('zh-cn')
|
dayjs.locale('zh-cn')
|
||||||
|
|
||||||
// 表格列定义
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 170 },
|
{ title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 170 },
|
||||||
{ title: '操作用户', dataIndex: 'username', key: 'username', width: 100 },
|
{ title: '操作用户', dataIndex: 'username', key: 'username', width: 100 },
|
||||||
|
|
@ -23,7 +29,6 @@ const columns = [
|
||||||
{ title: '耗时', dataIndex: 'executionTimeMs', key: 'executionTimeMs', width: 80 }
|
{ title: '耗时', dataIndex: 'executionTimeMs', key: 'executionTimeMs', width: 80 }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 数据
|
|
||||||
const logs = ref<AuditLog[]>([])
|
const logs = ref<AuditLog[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
|
|
@ -32,17 +37,14 @@ const pagination = ref({
|
||||||
total: 0
|
total: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// 统计数据
|
|
||||||
const stats = ref({
|
const stats = ref({
|
||||||
total: 0,
|
total: 0,
|
||||||
retentionDays: 30
|
retentionDays: 30
|
||||||
})
|
})
|
||||||
|
|
||||||
// 筛选选项
|
|
||||||
const moduleOptions = ref<{ value: string; label: string }[]>([])
|
const moduleOptions = ref<{ value: string; label: string }[]>([])
|
||||||
const actionOptions = ref<{ value: string; label: string }[]>([])
|
const actionOptions = ref<{ value: string; label: string }[]>([])
|
||||||
|
|
||||||
// 筛选条件
|
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
module: undefined as string | undefined,
|
module: undefined as string | undefined,
|
||||||
action: undefined as string | undefined,
|
action: undefined as string | undefined,
|
||||||
|
|
@ -50,13 +52,11 @@ const filters = ref({
|
||||||
dateRange: null as [Dayjs, Dayjs] | null
|
dateRange: null as [Dayjs, Dayjs] | null
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载模块选项
|
|
||||||
const loadModules = async () => {
|
const loadModules = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getAuditModules()
|
const res = await getAuditModules()
|
||||||
moduleOptions.value = res.data.data || []
|
moduleOptions.value = res.data.data || []
|
||||||
} catch {
|
} catch {
|
||||||
// 使用默认值
|
|
||||||
moduleOptions.value = [
|
moduleOptions.value = [
|
||||||
{ value: 'USER', label: '用户管理' },
|
{ value: 'USER', label: '用户管理' },
|
||||||
{ value: 'ROLE', label: '角色管理' },
|
{ value: 'ROLE', label: '角色管理' },
|
||||||
|
|
@ -66,13 +66,11 @@ const loadModules = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载操作类型选项
|
|
||||||
const loadActions = async () => {
|
const loadActions = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getAuditActions()
|
const res = await getAuditActions()
|
||||||
actionOptions.value = res.data.data || []
|
actionOptions.value = res.data.data || []
|
||||||
} catch {
|
} catch {
|
||||||
// 使用默认值
|
|
||||||
actionOptions.value = [
|
actionOptions.value = [
|
||||||
{ value: 'CREATE', label: '创建' },
|
{ value: 'CREATE', label: '创建' },
|
||||||
{ value: 'UPDATE', label: '修改' },
|
{ value: 'UPDATE', label: '修改' },
|
||||||
|
|
@ -84,17 +82,15 @@ const loadActions = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载统计数据
|
|
||||||
const loadStats = async () => {
|
const loadStats = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getAuditStats()
|
const res = await getAuditStats()
|
||||||
stats.value = res.data.data || { total: 0, retentionDays: 30 }
|
stats.value = res.data.data || { total: 0, retentionDays: 30 }
|
||||||
} catch {
|
} catch {
|
||||||
// 忽略错误
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载数据
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
|
|
@ -130,20 +126,17 @@ const loadData = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分页变化
|
|
||||||
const handleTableChange = (pag: any) => {
|
const handleTableChange = (pag: any) => {
|
||||||
pagination.value.current = pag.current
|
pagination.value.current = pag.current
|
||||||
pagination.value.pageSize = pag.pageSize
|
pagination.value.pageSize = pag.pageSize
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
pagination.value.current = 1
|
pagination.value.current = 1
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
filters.value = {
|
filters.value = {
|
||||||
module: undefined,
|
module: undefined,
|
||||||
|
|
@ -155,7 +148,6 @@ const handleReset = () => {
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取模块标签
|
|
||||||
const getModuleLabel = (module: string) => {
|
const getModuleLabel = (module: string) => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
USER: '用户管理',
|
USER: '用户管理',
|
||||||
|
|
@ -167,13 +159,13 @@ const getModuleLabel = (module: string) => {
|
||||||
return map[module] || module
|
return map[module] || module
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取操作类型标签
|
|
||||||
const getActionLabel = (action: string) => {
|
const getActionLabel = (action: string) => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
CREATE: '创建',
|
CREATE: '创建',
|
||||||
UPDATE: '修改',
|
UPDATE: '修改',
|
||||||
DELETE: '删除',
|
DELETE: '删除',
|
||||||
QUERY: '查询',
|
QUERY: '查询',
|
||||||
|
VIEW: '查看',
|
||||||
LOGIN: '登录',
|
LOGIN: '登录',
|
||||||
LOGOUT: '登出',
|
LOGOUT: '登出',
|
||||||
EXPORT: '导出',
|
EXPORT: '导出',
|
||||||
|
|
@ -184,13 +176,13 @@ const getActionLabel = (action: string) => {
|
||||||
return map[action] || action
|
return map[action] || action
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取操作类型颜色
|
|
||||||
const getActionColor = (action: string) => {
|
const getActionColor = (action: string) => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
CREATE: 'green',
|
CREATE: 'green',
|
||||||
UPDATE: 'blue',
|
UPDATE: 'blue',
|
||||||
DELETE: 'red',
|
DELETE: 'red',
|
||||||
QUERY: 'default',
|
QUERY: 'default',
|
||||||
|
VIEW: 'cyan',
|
||||||
LOGIN: 'cyan',
|
LOGIN: 'cyan',
|
||||||
LOGOUT: 'default',
|
LOGOUT: 'default',
|
||||||
EXPORT: 'purple',
|
EXPORT: 'purple',
|
||||||
|
|
@ -201,24 +193,20 @@ const getActionColor = (action: string) => {
|
||||||
return map[action] || 'default'
|
return map[action] || 'default'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态标签
|
|
||||||
const getStatusLabel = (status: string) => {
|
const getStatusLabel = (status: string) => {
|
||||||
return status === 'SUCCESS' ? '成功' : '失败'
|
return status === 'SUCCESS' ? '成功' : '失败'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态颜色
|
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
return status === 'SUCCESS' ? 'success' : 'error'
|
return status === 'SUCCESS' ? 'success' : 'error'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化耗时
|
|
||||||
const formatDuration = (ms?: number) => {
|
const formatDuration = (ms?: number) => {
|
||||||
if (ms === undefined || ms === null) return '-'
|
if (ms === undefined || ms === null) return '-'
|
||||||
if (ms < 1000) return `${ms}ms`
|
if (ms < 1000) return `${ms}ms`
|
||||||
return `${(ms / 1000).toFixed(2)}s`
|
return `${(ms / 1000).toFixed(2)}s`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 禁用30天前的日期
|
|
||||||
const disabledDate = (current: Dayjs) => {
|
const disabledDate = (current: Dayjs) => {
|
||||||
const thirtyDaysAgo = dayjs().subtract(30, 'day').startOf('day')
|
const thirtyDaysAgo = dayjs().subtract(30, 'day').startOf('day')
|
||||||
return current && (current < thirtyDaysAgo || current > dayjs().endOf('day'))
|
return current && (current < thirtyDaysAgo || current > dayjs().endOf('day'))
|
||||||
|
|
@ -235,16 +223,16 @@ onMounted(() => {
|
||||||
<template>
|
<template>
|
||||||
<ConfigProvider :locale="zhCN">
|
<ConfigProvider :locale="zhCN">
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<!-- 页面标题 -->
|
<PageHeader>
|
||||||
<div class="page-header">
|
<template #title>
|
||||||
<h2 class="page-title">操作审计日志</h2>
|
<span>操作审计日志</span>
|
||||||
<div class="page-subtitle">
|
</template>
|
||||||
保留最近 {{ stats.retentionDays }} 天的操作记录,共 {{ stats.total }} 条
|
<template #subtitle>
|
||||||
</div>
|
<span>保留最近 {{ stats.retentionDays }} 天的操作记录,共 {{ stats.total }} 条</span>
|
||||||
</div>
|
</template>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
<!-- 筛选区 -->
|
<FilterBar>
|
||||||
<div class="filter-bar">
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Select
|
<Select
|
||||||
v-model:value="filters.module"
|
v-model:value="filters.module"
|
||||||
|
|
@ -281,11 +269,12 @@ onMounted(() => {
|
||||||
<ReloadOutlined /> 重置
|
<ReloadOutlined /> 重置
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</FilterBar>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<TableCard>
|
||||||
<div class="table-card">
|
<TableToolbar @refresh="loadData" />
|
||||||
<Table
|
|
||||||
|
<a-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="logs"
|
:data-source="logs"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
|
@ -320,35 +309,15 @@ onMounted(() => {
|
||||||
{{ dayjs(record.createdAt).format('YYYY-MM-DD HH:mm:ss') }}
|
{{ dayjs(record.createdAt).format('YYYY-MM-DD HH:mm:ss') }}
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</Table>
|
</a-table>
|
||||||
</div>
|
|
||||||
|
<Pagination
|
||||||
|
v-model:current="pagination.current"
|
||||||
|
v-model:pageSize="pagination.pageSize"
|
||||||
|
:total="pagination.total"
|
||||||
|
@change="handleTableChange"
|
||||||
|
/>
|
||||||
|
</TableCard>
|
||||||
</div>
|
</div>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-subtitle {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-bar {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-card {
|
|
||||||
background: #fff;
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,24 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { Card, Form, FormItem, Input, Button, message, Breadcrumb, BreadcrumbItem } from 'ant-design-vue'
|
import { Card, Form, FormItem, Input, Button, message, Divider } from 'ant-design-vue'
|
||||||
import { HomeOutlined } from '@ant-design/icons-vue'
|
import { SaveOutlined } from '@ant-design/icons-vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { PageHeader } from '@/components'
|
||||||
import { getConfig, updateConfig } from '@/api/system'
|
import { getConfig, updateConfig } from '@/api/system'
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
|
const formRef = ref()
|
||||||
|
|
||||||
const formState = ref({
|
const formState = ref({
|
||||||
propertyCompanyName: '',
|
propertyCompanyName: '',
|
||||||
propertyCompanyAddress: '',
|
propertyCompanyAddress: '',
|
||||||
propertyCompanyPhone: ''
|
propertyCompanyPhone: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
propertyCompanyName: [{ required: true, message: '请输入物业企业名称' }]
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
|
|
@ -30,6 +35,12 @@ onMounted(async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value.validate()
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
await updateConfig({
|
await updateConfig({
|
||||||
|
|
@ -48,44 +59,55 @@ const handleSubmit = async () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<Breadcrumb>
|
<PageHeader>
|
||||||
<BreadcrumbItem>
|
<template #title>
|
||||||
<HomeOutlined />
|
<span>系统设置</span>
|
||||||
</BreadcrumbItem>
|
</template>
|
||||||
<BreadcrumbItem>系统管理</BreadcrumbItem>
|
</PageHeader>
|
||||||
<BreadcrumbItem>系统设置</BreadcrumbItem>
|
|
||||||
</Breadcrumb>
|
|
||||||
|
|
||||||
<div class="page-header">
|
|
||||||
<h2 class="page-title">系统设置</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card :loading="loading">
|
<Card :loading="loading">
|
||||||
<Form layout="vertical">
|
<Form
|
||||||
<FormItem label="物业企业名称">
|
ref="formRef"
|
||||||
|
:model="formState"
|
||||||
|
layout="vertical"
|
||||||
|
:rules="rules"
|
||||||
|
>
|
||||||
|
<Divider orientation="left">基本信息</Divider>
|
||||||
|
|
||||||
|
<FormItem label="物业企业名称" name="propertyCompanyName">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="formState.propertyCompanyName"
|
v-model:value="formState.propertyCompanyName"
|
||||||
placeholder="请输入物业企业名称"
|
placeholder="请输入物业企业名称"
|
||||||
:maxlength="100"
|
:maxlength="100"
|
||||||
|
style="width: 400px"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="物业企业地址">
|
|
||||||
|
<FormItem label="物业企业地址" name="propertyCompanyAddress">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="formState.propertyCompanyAddress"
|
v-model:value="formState.propertyCompanyAddress"
|
||||||
placeholder="请输入物业企业地址"
|
placeholder="请输入物业企业地址"
|
||||||
:maxlength="200"
|
:maxlength="200"
|
||||||
|
style="width: 600px"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
<FormItem label="物业企业电话">
|
|
||||||
|
<FormItem label="物业企业电话" name="propertyCompanyPhone">
|
||||||
<Input
|
<Input
|
||||||
v-model:value="formState.propertyCompanyPhone"
|
v-model:value="formState.propertyCompanyPhone"
|
||||||
placeholder="请输入物业企业电话"
|
placeholder="请输入物业企业电话"
|
||||||
:maxlength="20"
|
:maxlength="20"
|
||||||
|
style="width: 200px"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<Button type="primary" :loading="submitting" @click="handleSubmit">
|
<Button
|
||||||
保存设置
|
type="primary"
|
||||||
|
:loading="submitting"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
<SaveOutlined /> 保存设置
|
||||||
</Button>
|
</Button>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue