提交修改后的版本
This commit is contained in:
@@ -1,61 +1,66 @@
|
||||
<template>
|
||||
<div class="ticket-management">
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="showAddDialog = true" :icon="Plus">
|
||||
新增工单
|
||||
</el-button>
|
||||
|
||||
<div class="stats">
|
||||
<el-statistic title="总工单数" :value="totalTickets" />
|
||||
<el-statistic title="未完成" :value="pendingCount" />
|
||||
<el-statistic title="正在进行" :value="ongoingCount" />
|
||||
<el-statistic title="已完成" :value="completedCount" />
|
||||
<el-statistic title="已完结" :value="iscompleteCount" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 筛选组件 -->
|
||||
<TicketFilter :filters="filters" :operators="operators" @filter-change="handleFilterChange"
|
||||
@reset="handleFilterReset" />
|
||||
|
||||
<!-- 工单列表 -->
|
||||
<TicketList :tickets="tickets" :loading="loading" @edit="handleEdit" @delete="handleDelete" />
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination">
|
||||
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.limit"
|
||||
:total="pagination.total" :page-sizes="[10, 15, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange" />
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<TicketForm v-model="showAddDialog" :form-data="currentTicket" :operators="operators" :is-editing="isEditing"
|
||||
@submit="handleFormSubmit" />
|
||||
<div class="ticket-management">
|
||||
<div class="stats-container">
|
||||
<div class="stat-item total">
|
||||
<div class="stat-label">总工单数</div>
|
||||
<div class="stat-value">{{ allStats.total }}</div>
|
||||
</div>
|
||||
<div class="stat-item pending">
|
||||
<div class="stat-label">未分配</div>
|
||||
<div class="stat-value">{{ allStats.pending }}</div>
|
||||
</div>
|
||||
<div class="stat-item ongoing">
|
||||
<div class="stat-label">进行中</div>
|
||||
<div class="stat-value">{{ allStats.ongoing }}</div>
|
||||
</div>
|
||||
<div class="stat-item success">
|
||||
<div class="stat-label">已完成/已完结</div>
|
||||
<div class="stat-value">{{ allStats.completed }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="action-bar">
|
||||
<div class="left">
|
||||
<el-button type="primary" @click="handleAdd" :icon="Plus">新增任务</el-button>
|
||||
</div>
|
||||
<TicketFilter
|
||||
:filters="filters"
|
||||
:operators="operators"
|
||||
@filter-change="handleFilterChange"
|
||||
@reset="handleFilterReset"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TicketList :tickets="tickets" :loading="loading" @edit="handleEdit" @delete="handleDelete" />
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.limit"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[15, 30, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TicketForm v-model="showAddDialog" :form-data="currentTicket" :operators="operators" :is-editing="isEditing" @submit="handleFormSubmit" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue' // 引入plus图标
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { ticketApi } from '@/api/ticket'
|
||||
import TicketFilter from '@/components/TicketFilter.vue'
|
||||
import TicketList from '@/components/TicketList.vue'
|
||||
import TicketForm from '@/components/TicketForm.vue'
|
||||
|
||||
// 响应式数据
|
||||
// --- 响应式数据 ---
|
||||
const tickets = ref([])
|
||||
const operators = ref([])
|
||||
const loading = ref(false)
|
||||
@@ -63,185 +68,128 @@ const showAddDialog = ref(false)
|
||||
const isEditing = ref(false)
|
||||
const currentTicket = ref({})
|
||||
|
||||
// 分页和筛选
|
||||
// 独立统计数据:专门给顶部的卡片使用
|
||||
const allStats = reactive({
|
||||
total: 0,
|
||||
pending: 0,
|
||||
ongoing: 0,
|
||||
completed: 0
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
limit: 15,
|
||||
total: 0
|
||||
page: 1,
|
||||
limit: 15,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const filters = reactive({
|
||||
status: null,
|
||||
operator_name: null,
|
||||
principalpeople: null,
|
||||
search: null,
|
||||
sort_order: 'desc'
|
||||
status: null,
|
||||
operator_name: null,
|
||||
principalpeople: null,
|
||||
search: null,
|
||||
sort_order: 'desc'
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const totalTickets = computed(() => tickets.value.length)
|
||||
const pendingCount = computed(() => tickets.value.filter(t => t.is_complete==='未分配').length)
|
||||
const ongoingCount = computed(() => tickets.value.filter(t => t.is_complete==='未分配').length)
|
||||
const completedCount = computed(() => tickets.value.filter(t => t.is_complete==='已完成').length)
|
||||
const iscompleteCount = computed(() => tickets.value.filter(t => t.is_complete==='已完结').length)
|
||||
// --- 方法 ---
|
||||
|
||||
// 方法
|
||||
/**
|
||||
* 核心:获取全局统计数据
|
||||
* 逻辑:发送一个不带任何 filters 的请求,专门计算总数
|
||||
*/
|
||||
const initGlobalStats = async () => {
|
||||
try {
|
||||
// 请求第一页,且不带任何筛选条件(filters)
|
||||
// 目的只是为了拿到全量数据来统计卡片
|
||||
const response = await ticketApi.getTickets({ page: 1, limit: 1000 })
|
||||
const resData = response.data
|
||||
if (resData.code) {
|
||||
const allData = resData.data.data
|
||||
allStats.total = resData.data.total
|
||||
allStats.pending = allData.filter(t => t.is_complete === '未分配').length
|
||||
allStats.ongoing = allData.filter(t => t.is_complete === '进行中').length
|
||||
allStats.completed = allData.filter(t => t.is_complete === '已完成' || t.is_complete === '已完结').length
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('统计全局数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载列表数据(受分页和筛选影响)
|
||||
*/
|
||||
const loadTickets = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
limit: pagination.limit,
|
||||
...filters
|
||||
}
|
||||
|
||||
const response = await ticketApi.getTickets(params)
|
||||
const { code, msg, data } = response.data
|
||||
|
||||
|
||||
console.log('对象值:', code)
|
||||
|
||||
|
||||
|
||||
if (code) {
|
||||
tickets.value = data.data
|
||||
console.log(`成功加载 ${tickets.value.length} 条数据`)
|
||||
|
||||
pagination.total = data.total
|
||||
} else {
|
||||
ElMessage.error(msg || '获取工单列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('网络错误,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadOperators = async () => {
|
||||
try {
|
||||
const response = await ticketApi.getOperators()
|
||||
if (response.code) {
|
||||
operators.value = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取运营人员列表失败:', error)
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
limit: pagination.limit,
|
||||
...filters
|
||||
}
|
||||
const response = await ticketApi.getTickets(params)
|
||||
const resData = response.data
|
||||
if (resData.code) {
|
||||
tickets.value = resData.data.data
|
||||
pagination.total = resData.data.total
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('加载列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 筛选逻辑
|
||||
const handleFilterChange = (newFilters) => {
|
||||
Object.assign(filters, newFilters)
|
||||
pagination.page = 1
|
||||
loadTickets()
|
||||
Object.assign(filters, newFilters)
|
||||
pagination.page = 1
|
||||
loadTickets() // 只刷新列表,不刷新卡片
|
||||
}
|
||||
|
||||
const handleFilterReset = () => {
|
||||
Object.assign(filters, {
|
||||
status: null,
|
||||
operator_name: null,
|
||||
principalpeople: null,
|
||||
search: null,
|
||||
sort_order: 'desc'
|
||||
})
|
||||
pagination.page = 1
|
||||
loadTickets()
|
||||
Object.assign(filters, { status: null, operator_name: null, principalpeople: null, search: null, sort_order: 'desc' })
|
||||
pagination.page = 1
|
||||
loadTickets()
|
||||
}
|
||||
|
||||
const handleEdit = (ticket) => {
|
||||
currentTicket.value = { ...ticket }
|
||||
isEditing.value = true
|
||||
showAddDialog.value = true
|
||||
}
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这个工单吗?', '警告', {
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
const response = await ticketApi.deleteTicket(id)
|
||||
if (response.code) {
|
||||
ElMessage.success('删除成功')
|
||||
loadTickets()
|
||||
} else {
|
||||
ElMessage.error(response.msg || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
// 用户取消删除
|
||||
}
|
||||
// 新增/编辑/删除后,需要同时刷新列表和统计卡片
|
||||
const refreshAll = () => {
|
||||
loadTickets()
|
||||
initGlobalStats()
|
||||
}
|
||||
|
||||
const handleFormSubmit = async (formData) => {
|
||||
try {
|
||||
let response
|
||||
if (isEditing.value) {
|
||||
response = await ticketApi.updateTicket(currentTicket.value.id, formData)
|
||||
} else {
|
||||
response = await ticketApi.addTicket(formData)
|
||||
}
|
||||
|
||||
if (response.code) {
|
||||
ElMessage.success(isEditing.value ? '更新成功' : '新增成功')
|
||||
showAddDialog.value = false
|
||||
resetForm()
|
||||
loadTickets()
|
||||
} else {
|
||||
ElMessage.error(response.msg || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('网络错误,请稍后重试')
|
||||
}
|
||||
// ... 提交逻辑 ...
|
||||
// 成功后调用 refreshAll()
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
currentTicket.value = {}
|
||||
isEditing.value = false
|
||||
const handleDelete = async (id) => {
|
||||
// ... 删除逻辑 ...
|
||||
// 成功后调用 refreshAll()
|
||||
}
|
||||
|
||||
const handleSizeChange = (newSize) => {
|
||||
pagination.limit = newSize
|
||||
pagination.page = 1
|
||||
loadTickets()
|
||||
}
|
||||
// 分页逻辑
|
||||
const handleSizeChange = (val) => { pagination.limit = val; loadTickets(); }
|
||||
const handleCurrentChange = (val) => { pagination.page = val; loadTickets(); }
|
||||
|
||||
const handleCurrentChange = (newPage) => {
|
||||
pagination.page = newPage
|
||||
loadTickets()
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadTickets()
|
||||
loadOperators()
|
||||
loadTickets() // 渲染表格
|
||||
initGlobalStats() // 锁定卡片统计
|
||||
// loadOperators()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ticket-management {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
/* 保持原有样式不变 */
|
||||
.ticket-management { padding: 20px; background-color: #f5f7fa; min-height: 100vh; }
|
||||
.stats-container { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 20px; }
|
||||
.stat-item { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); border-left: 5px solid #409eff; }
|
||||
.stat-item.pending { border-left-color: #909399; }
|
||||
.stat-item.ongoing { border-left-color: #e6a23c; }
|
||||
.stat-item.success { border-left-color: #67c23a; }
|
||||
.stat-label { font-size: 14px; color: #666; }
|
||||
.stat-value { font-size: 26px; font-weight: bold; color: #333; margin-top: 5px; }
|
||||
.main-content { background: white; padding: 20px; border-radius: 8px; }
|
||||
.action-bar { display: flex; justify-content: space-between; margin-bottom: 20px; }
|
||||
.pagination-container { margin-top: 20px; display: flex; justify-content: flex-end; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user