|
|
@@ -1,128 +1,232 @@
|
|
|
<template>
|
|
|
- <div class="operation-history-container">
|
|
|
- <vab-query-form>
|
|
|
- <vab-query-form-top-panel>
|
|
|
- <el-form ref="queryForm" :inline="true" :model="queryForm" @submit.native.prevent>
|
|
|
- <el-form-item prop="scopeType">
|
|
|
- <el-radio-group v-model="queryForm.scopeType" size="small" @change="queryData">
|
|
|
- <el-radio-button label="my">个人</el-radio-button>
|
|
|
- <el-radio-button label="all">全部</el-radio-button>
|
|
|
- </el-radio-group>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-checkbox v-model="queryForm.includeClosed" size="small" @change="queryData">未关闭</el-checkbox>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item prop="eventTitle">
|
|
|
- <el-input
|
|
|
- v-model="queryForm.eventTitle"
|
|
|
- clearable
|
|
|
- placeholder="事件标题"
|
|
|
- size="small"
|
|
|
- @keyup.enter.native="queryData" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item prop="custName">
|
|
|
- <el-input
|
|
|
- v-model="queryForm.custName"
|
|
|
- clearable
|
|
|
- placeholder="客户名称"
|
|
|
- size="small"
|
|
|
- @keyup.enter.native="queryData" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item prop="feedbackReporter">
|
|
|
- <el-input
|
|
|
- v-model="queryForm.feedbackReporter"
|
|
|
- clearable
|
|
|
- placeholder="反馈人"
|
|
|
- size="small"
|
|
|
- @keyup.enter.native="queryData" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item prop="opsUserName">
|
|
|
- <el-input
|
|
|
- v-model="queryForm.opsUserName"
|
|
|
- clearable
|
|
|
- placeholder="处理人"
|
|
|
- size="small"
|
|
|
- @keyup.enter.native="queryData" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item prop="dateRange">
|
|
|
- <el-date-picker
|
|
|
- v-model="queryForm.dateRange"
|
|
|
- end-placeholder="结束日期"
|
|
|
- range-separator="至"
|
|
|
- size="small"
|
|
|
- start-placeholder="反馈开始日期"
|
|
|
- type="daterange"
|
|
|
- value-format="yyyy-MM-dd" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button icon="el-icon-search" size="small" type="primary" @click="queryData">查询</el-button>
|
|
|
- <el-button icon="el-icon-refresh-right" size="small" @click="reset">重置</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- </vab-query-form-top-panel>
|
|
|
- </vab-query-form>
|
|
|
-
|
|
|
- <div class="export-btn-fixed">
|
|
|
- <el-button icon="el-icon-download" size="small" type="success" @click="handleExport">导出</el-button>
|
|
|
+ <div class="operation-history-page">
|
|
|
+ <div class="query-form-container">
|
|
|
+ <vab-query-form>
|
|
|
+ <vab-query-form-top-panel>
|
|
|
+ <el-form ref="queryForm" :inline="true" :model="queryForm" @submit.native.prevent>
|
|
|
+ <el-form-item prop="scopeType">
|
|
|
+ <el-radio-group v-model="queryForm.scopeType" size="small" @change="queryData">
|
|
|
+ <el-radio-button label="my">个人</el-radio-button>
|
|
|
+ <el-radio-button label="all">全部</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-checkbox v-model="queryForm.includeClosed" size="small" @change="queryData">未关闭</el-checkbox>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="eventTitle">
|
|
|
+ <el-input
|
|
|
+ v-model="queryForm.eventTitle"
|
|
|
+ clearable
|
|
|
+ placeholder="事件标题"
|
|
|
+ size="small"
|
|
|
+ @keyup.enter.native="queryData" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="custName">
|
|
|
+ <el-input
|
|
|
+ v-model="queryForm.custName"
|
|
|
+ clearable
|
|
|
+ placeholder="客户名称"
|
|
|
+ size="small"
|
|
|
+ @keyup.enter.native="queryData" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="feedbackReporter">
|
|
|
+ <el-input
|
|
|
+ v-model="queryForm.feedbackReporter"
|
|
|
+ clearable
|
|
|
+ placeholder="反馈人"
|
|
|
+ size="small"
|
|
|
+ @keyup.enter.native="queryData" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="opsUserName">
|
|
|
+ <el-input
|
|
|
+ v-model="queryForm.opsUserName"
|
|
|
+ clearable
|
|
|
+ placeholder="处理人"
|
|
|
+ size="small"
|
|
|
+ @keyup.enter.native="queryData" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="dateRange">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="queryForm.dateRange"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ range-separator="至"
|
|
|
+ size="small"
|
|
|
+ start-placeholder="反馈开始日期"
|
|
|
+ type="daterange"
|
|
|
+ value-format="yyyy-MM-dd" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button icon="el-icon-search" size="small" type="primary" @click="queryData">查询</el-button>
|
|
|
+ <el-button icon="el-icon-refresh-right" size="small" @click="reset">重置</el-button>
|
|
|
+ <el-button icon="el-icon-download" size="small" type="success" @click="handleExport">导出</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </vab-query-form-top-panel>
|
|
|
+ </vab-query-form>
|
|
|
</div>
|
|
|
|
|
|
- <div ref="tableWrapper" class="table-wrapper">
|
|
|
- <el-table ref="table" v-loading="listLoading" border :data="list" :height="height">
|
|
|
- <el-table-column align="center" label="序号" show-overflow-tooltip width="60">
|
|
|
- <template #default="{ $index }">
|
|
|
- {{ (queryForm.pageNum - 1) * queryForm.pageSize + $index + 1 }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column align="center" label="事件编号" min-width="130" prop="eventNo" show-overflow-tooltip />
|
|
|
- <el-table-column align="center" label="事件标题" min-width="150" prop="eventTitle" show-overflow-tooltip />
|
|
|
- <el-table-column align="center" label="客户名称" min-width="120" prop="custName" show-overflow-tooltip />
|
|
|
- <el-table-column align="center" label="反馈人" min-width="100" prop="feedbackReporter" show-overflow-tooltip />
|
|
|
- <el-table-column align="center" label="反馈时间" min-width="110" prop="feedbackDate" show-overflow-tooltip>
|
|
|
- <template #default="{ row }">
|
|
|
- {{ parseTime(row.feedbackDate, '{y}-{m}-{d}') }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column align="center" label="处理人" min-width="100" prop="opsUserName" show-overflow-tooltip />
|
|
|
- <el-table-column align="center" label="事件类型" min-width="90" prop="eventType" show-overflow-tooltip>
|
|
|
- <template #default="{ row }">
|
|
|
- {{ selectDictLabel(eventTypeOptions, row.eventType) }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column align="center" label="优先级" min-width="80" prop="priorityLevel" show-overflow-tooltip>
|
|
|
- <template #default="{ row }">
|
|
|
- {{ selectDictLabel(priorityLevelOptions, row.priorityLevel) }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column align="center" label="事件状态" min-width="100" prop="eventStatus" show-overflow-tooltip>
|
|
|
- <template #default="{ row }">
|
|
|
- {{ selectDictLabel(eventStatusOptions, row.eventStatus) }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column align="center" label="关闭时间" min-width="110" prop="completeTime" show-overflow-tooltip>
|
|
|
- <template #default="{ row }">
|
|
|
- {{ parseTime(row.completeTime, '{y}-{m}-{d}') }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column align="center" fixed="right" label="操作" width="80">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-button type="text" @click="handleView(row)">详情</el-button>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <template #empty>
|
|
|
- <el-image class="vab-data-empty" :src="require('@/assets/empty_images/data_empty.png')" />
|
|
|
+ <div class="main-content">
|
|
|
+ <div :class="['project-sidebar', { collapsed: sidebarCollapsed }]">
|
|
|
+ <div class="sidebar-header">
|
|
|
+ <div v-if="!sidebarCollapsed" class="sidebar-title-group">
|
|
|
+ <span class="sidebar-title">项目列表</span>
|
|
|
+ <button
|
|
|
+ :class="['sidebar-action-btn', { active: showSidebarFilters }]"
|
|
|
+ :title="showSidebarFilters ? '隐藏项目筛选' : '显示项目筛选'"
|
|
|
+ type="button"
|
|
|
+ @click="toggleSidebarFilters">
|
|
|
+ <i class="el-icon-search" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="sidebar-actions">
|
|
|
+ <button
|
|
|
+ class="collapse-trigger"
|
|
|
+ :title="sidebarCollapsed ? '展开项目列表' : '折叠项目列表'"
|
|
|
+ type="button"
|
|
|
+ @click="toggleSidebar">
|
|
|
+ <i :class="sidebarCollapsed ? 'el-icon-d-arrow-right' : 'el-icon-d-arrow-left'" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="sidebarCollapsed" class="sidebar-collapsed-label">项目列表</div>
|
|
|
+ <template v-else>
|
|
|
+ <div v-if="showSidebarFilters" class="project-search-wrapper">
|
|
|
+ <el-input
|
|
|
+ v-model="projectSearch"
|
|
|
+ class="project-search"
|
|
|
+ clearable
|
|
|
+ placeholder="搜索项目名称/合同编号"
|
|
|
+ prefix-icon="el-icon-search"
|
|
|
+ size="small" />
|
|
|
+ </div>
|
|
|
+ <div class="project-list">
|
|
|
+ <div
|
|
|
+ :class="['project-item', 'project-item--all', { active: selectedContractId === '' }]"
|
|
|
+ @click="selectProject('')">
|
|
|
+ <div>
|
|
|
+ <div class="project-overview-label">全部</div>
|
|
|
+ <div class="project-overview-desc">查看全部运维历史</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-for="project in filteredProjects"
|
|
|
+ :key="project.contractId || project.id"
|
|
|
+ :class="['project-card', { active: selectedContractId === project.contractId }]"
|
|
|
+ @click="selectProject(project.contractId)">
|
|
|
+ <div class="project-card-top">
|
|
|
+ <div class="project-card-tags">
|
|
|
+ <span class="project-contract">{{ project.contractNo || '-' }}</span>
|
|
|
+ <span class="project-line-tag">{{ getProductLineLabel(project.productLine) }}</span>
|
|
|
+ </div>
|
|
|
+ <span :class="['project-status-tag', 'project-status-tag--' + project.status]">
|
|
|
+ {{ selectDictLabel(projectStatusOptions, project.status) || '-' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="project-card-title" :title="project.name">{{ project.name || '-' }}</div>
|
|
|
+ <div class="project-card-meta">
|
|
|
+ <div class="project-card-meta-item">
|
|
|
+ <i class="el-icon-user project-card-meta-icon" title="销售负责人" />
|
|
|
+ <span class="project-card-meta-value" :title="project.salesOwner || '-'">
|
|
|
+ {{ project.salesOwner || '-' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="project-card-meta-item">
|
|
|
+ <i
|
|
|
+ class="el-icon-s-custom project-card-meta-icon project-card-meta-icon--delivery"
|
|
|
+ title="交付负责人" />
|
|
|
+ <span class="project-card-meta-value" :title="project.deliveryOwner || '-'">
|
|
|
+ {{ project.deliveryOwner || '-' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <i v-if="selectedContractId === project.contractId" class="el-icon-check project-card-check" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
- </el-table>
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
|
|
|
- <el-pagination
|
|
|
- ref="paginationRef"
|
|
|
- background
|
|
|
- :current-page="queryForm.pageNum"
|
|
|
- :layout="layout"
|
|
|
- :page-size="queryForm.pageSize"
|
|
|
- :total="total"
|
|
|
- @current-change="handleCurrentChange"
|
|
|
- @size-change="handleSizeChange" />
|
|
|
+ <div class="content-panel">
|
|
|
+ <div ref="tableWrapper" class="table-container">
|
|
|
+ <div class="table-scroll-wrapper">
|
|
|
+ <el-table ref="table" v-loading="listLoading" border :data="list" :height="height">
|
|
|
+ <el-table-column align="center" label="序号" show-overflow-tooltip width="60">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ {{ (queryForm.pageNum - 1) * queryForm.pageSize + $index + 1 }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column align="center" label="事件编号" min-width="130" prop="eventNo" show-overflow-tooltip />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="事件标题"
|
|
|
+ min-width="150"
|
|
|
+ prop="eventTitle"
|
|
|
+ show-overflow-tooltip />
|
|
|
+ <el-table-column align="center" label="客户名称" min-width="120" prop="custName" show-overflow-tooltip />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="反馈人"
|
|
|
+ min-width="100"
|
|
|
+ prop="feedbackReporter"
|
|
|
+ show-overflow-tooltip />
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="反馈时间"
|
|
|
+ min-width="110"
|
|
|
+ prop="feedbackDate"
|
|
|
+ show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ parseTime(row.feedbackDate, '{y}-{m}-{d}') }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column align="center" label="处理人" min-width="100" prop="opsUserName" show-overflow-tooltip />
|
|
|
+ <el-table-column align="center" label="事件类型" min-width="90" prop="eventType" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ selectDictLabel(eventTypeOptions, row.eventType) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column align="center" label="优先级" min-width="80" prop="priorityLevel" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ selectDictLabel(priorityLevelOptions, row.priorityLevel) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column align="center" label="事件状态" min-width="100" prop="eventStatus" show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ selectDictLabel(eventStatusOptions, row.eventStatus) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column
|
|
|
+ align="center"
|
|
|
+ label="关闭时间"
|
|
|
+ min-width="110"
|
|
|
+ prop="completeTime"
|
|
|
+ show-overflow-tooltip>
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ parseTime(row.completeTime, '{y}-{m}-{d}') }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column align="center" fixed="right" label="操作" width="80">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button type="text" @click="handleView(row)">详情</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <template #empty>
|
|
|
+ <el-image class="vab-data-empty" :src="require('@/assets/empty_images/data_empty.png')" />
|
|
|
+ </template>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-pagination
|
|
|
+ ref="paginationRef"
|
|
|
+ background
|
|
|
+ :current-page="queryForm.pageNum"
|
|
|
+ :layout="layout"
|
|
|
+ :page-size="queryForm.pageSize"
|
|
|
+ :total="total"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ @size-change="handleSizeChange" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<operation-detail
|
|
|
v-if="detailVisible"
|
|
|
@@ -135,7 +239,10 @@
|
|
|
|
|
|
<script>
|
|
|
import to from 'await-to-js'
|
|
|
+ import { mapGetters } from 'vuex'
|
|
|
+ import deliveryProjectApi from '@/api/devops/deliveryProject'
|
|
|
import operationEventApi from '@/api/operation/operationEvent'
|
|
|
+ import dictApi from '@/api/system/dict'
|
|
|
import OperationDetail from '@/views/devops/operation/components/OperationDetail'
|
|
|
|
|
|
export default {
|
|
|
@@ -156,6 +263,13 @@
|
|
|
eventTypeOptions: [],
|
|
|
eventStatusOptions: [],
|
|
|
priorityLevelOptions: [],
|
|
|
+ projectStatusOptions: [],
|
|
|
+ productLineDict: [],
|
|
|
+ projects: [],
|
|
|
+ selectedContractId: '',
|
|
|
+ projectSearch: '',
|
|
|
+ sidebarCollapsed: false,
|
|
|
+ showSidebarFilters: true,
|
|
|
queryForm: {
|
|
|
scopeType: 'my',
|
|
|
includeClosed: false,
|
|
|
@@ -164,11 +278,33 @@
|
|
|
feedbackReporter: '',
|
|
|
opsUserName: '',
|
|
|
dateRange: [],
|
|
|
+ contractId: '',
|
|
|
pageNum: 1,
|
|
|
pageSize: 10,
|
|
|
},
|
|
|
}
|
|
|
},
|
|
|
+ computed: {
|
|
|
+ ...mapGetters({
|
|
|
+ userId: 'user/id',
|
|
|
+ }),
|
|
|
+ currentUserId() {
|
|
|
+ return this.userId || (this.$store.state.user && this.$store.state.user.id) || ''
|
|
|
+ },
|
|
|
+ filteredProjects() {
|
|
|
+ const keyword = this.projectSearch.trim().toLowerCase()
|
|
|
+
|
|
|
+ return this.projects.filter((project) => {
|
|
|
+ if (!keyword) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ return [project.name, project.contractNo]
|
|
|
+ .filter(Boolean)
|
|
|
+ .some((field) => String(field).toLowerCase().includes(keyword))
|
|
|
+ })
|
|
|
+ },
|
|
|
+ },
|
|
|
activated() {
|
|
|
if (this.hasLoaded) {
|
|
|
this.scheduleTableLayout()
|
|
|
@@ -179,9 +315,8 @@
|
|
|
this.clearTableLayoutTasks()
|
|
|
window.removeEventListener('resize', this.scheduleTableLayout)
|
|
|
},
|
|
|
- mounted() {
|
|
|
- this.getOptions()
|
|
|
- this.fetchData()
|
|
|
+ async mounted() {
|
|
|
+ await this.initializePage()
|
|
|
this.scheduleTableLayout()
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
@@ -189,18 +324,29 @@
|
|
|
window.removeEventListener('resize', this.scheduleTableLayout)
|
|
|
},
|
|
|
methods: {
|
|
|
- getOptions() {
|
|
|
- Promise.all([
|
|
|
- this.getDicts('ops_event_type'),
|
|
|
- this.getDicts('ops_event_status'),
|
|
|
- this.getDicts('ops_priority_level'),
|
|
|
- ])
|
|
|
- .then(([eventType, eventStatus, priorityLevel]) => {
|
|
|
- this.eventTypeOptions = eventType.data.values || []
|
|
|
- this.eventStatusOptions = eventStatus.data.values || []
|
|
|
- this.priorityLevelOptions = priorityLevel.data.values || []
|
|
|
- })
|
|
|
- .catch((err) => console.log(err))
|
|
|
+ async initializePage() {
|
|
|
+ await this.getOptions()
|
|
|
+ await this.fetchProjects()
|
|
|
+ await this.fetchData()
|
|
|
+ },
|
|
|
+ async getOptions() {
|
|
|
+ try {
|
|
|
+ const res = await dictApi.getDictDataByTypes([
|
|
|
+ 'ops_event_type',
|
|
|
+ 'ops_event_status',
|
|
|
+ 'ops_priority_level',
|
|
|
+ 'delivery_project_status',
|
|
|
+ 'sys_product_line',
|
|
|
+ ])
|
|
|
+ const dicts = res.data || {}
|
|
|
+ this.eventTypeOptions = (dicts.ops_event_type && dicts.ops_event_type.values) || []
|
|
|
+ this.eventStatusOptions = (dicts.ops_event_status && dicts.ops_event_status.values) || []
|
|
|
+ this.priorityLevelOptions = (dicts.ops_priority_level && dicts.ops_priority_level.values) || []
|
|
|
+ this.projectStatusOptions = (dicts.delivery_project_status && dicts.delivery_project_status.values) || []
|
|
|
+ this.productLineDict = (dicts.sys_product_line && dicts.sys_product_line.values) || []
|
|
|
+ } catch (err) {
|
|
|
+ console.log(err)
|
|
|
+ }
|
|
|
},
|
|
|
clearTableLayoutTasks() {
|
|
|
this.tableLayoutTimers.forEach((timer) => clearTimeout(timer))
|
|
|
@@ -234,16 +380,66 @@
|
|
|
const tableEl = this.$refs.table && this.$refs.table.$el
|
|
|
const paginationEl = this.$refs.paginationRef && this.$refs.paginationRef.$el
|
|
|
|
|
|
- if (!tableEl || !paginationEl) return
|
|
|
+ if (!wrapperEl || !tableEl || !paginationEl) return
|
|
|
|
|
|
- const footerHeight = 60
|
|
|
- const bufferSpace = 10
|
|
|
- const tableTop = (wrapperEl || tableEl).getBoundingClientRect().top
|
|
|
+ const wrapperRect = wrapperEl.getBoundingClientRect()
|
|
|
const paginationHeight = paginationEl.getBoundingClientRect().height
|
|
|
- const availableHeight = window.innerHeight - tableTop - footerHeight - paginationHeight - bufferSpace
|
|
|
+ const availableHeight = wrapperRect.height - paginationHeight - 12
|
|
|
|
|
|
this.height = Math.max(availableHeight, 240)
|
|
|
},
|
|
|
+ normalizeProject(item) {
|
|
|
+ return {
|
|
|
+ id: String(item.id || item.projectId || item.project_id || item.contractId || item.contract_id || ''),
|
|
|
+ contractId: String(item.contractId || item.contract_id || ''),
|
|
|
+ contractNo: item.contractNo || item.contract_no || '-',
|
|
|
+ productLine: item.productLine || item.product_line || '',
|
|
|
+ status: String(item.projectStatus || item.project_status || ''),
|
|
|
+ name: item.projectName || item.project_name || item.name || '-',
|
|
|
+ salesOwner: item.salesUserName || item.sales_user_name || '-',
|
|
|
+ deliveryOwner: item.deliveryUserName || item.delivery_user_name || '-',
|
|
|
+ attribute4: item.attribute4 || item.attribute_4 || '',
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async fetchProjects() {
|
|
|
+ try {
|
|
|
+ const params = {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 999,
|
|
|
+ productLine: '10,20,30,40,50,60',
|
|
|
+ sortField: 'contract_no',
|
|
|
+ sortOrder: 'desc',
|
|
|
+ attribute9: '10',
|
|
|
+ attribute4: this.currentUserId,
|
|
|
+ projectStatus: '50',
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await deliveryProjectApi.getList(params)
|
|
|
+ const projectList = (res.data && res.data.list ? res.data.list : [])
|
|
|
+ .map(this.normalizeProject)
|
|
|
+ .filter((project) => {
|
|
|
+ if (!project.contractId) return false
|
|
|
+ if (!this.currentUserId) return true
|
|
|
+ return String(project.attribute4 || '') === String(this.currentUserId)
|
|
|
+ })
|
|
|
+
|
|
|
+ projectList.sort((a, b) => String(b.contractNo || '').localeCompare(String(a.contractNo || '')))
|
|
|
+ this.projects = projectList
|
|
|
+
|
|
|
+ if (
|
|
|
+ this.selectedContractId &&
|
|
|
+ !this.projects.some((project) => project.contractId === this.selectedContractId)
|
|
|
+ ) {
|
|
|
+ this.selectedContractId = ''
|
|
|
+ this.queryForm.contractId = ''
|
|
|
+ this.queryForm.pageNum = 1
|
|
|
+ await this.fetchData()
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取项目列表失败:', error)
|
|
|
+ this.projects = []
|
|
|
+ }
|
|
|
+ },
|
|
|
async fetchData() {
|
|
|
this.listLoading = true
|
|
|
const params = {
|
|
|
@@ -259,6 +455,10 @@
|
|
|
pageNum: this.queryForm.pageNum,
|
|
|
pageSize: this.queryForm.pageSize,
|
|
|
}
|
|
|
+ if (this.queryForm.contractId) {
|
|
|
+ params.contractId = this.queryForm.contractId
|
|
|
+ }
|
|
|
+
|
|
|
const [err, res] = await to(operationEventApi.getHistoryList(params))
|
|
|
if (err) {
|
|
|
this.listLoading = false
|
|
|
@@ -270,6 +470,12 @@
|
|
|
this.hasLoaded = true
|
|
|
this.scheduleTableLayout()
|
|
|
},
|
|
|
+ async selectProject(contractId) {
|
|
|
+ this.selectedContractId = contractId || ''
|
|
|
+ this.queryForm.contractId = contractId || ''
|
|
|
+ this.queryForm.pageNum = 1
|
|
|
+ await this.fetchData()
|
|
|
+ },
|
|
|
queryData() {
|
|
|
this.queryForm.pageNum = 1
|
|
|
this.fetchData()
|
|
|
@@ -283,11 +489,20 @@
|
|
|
feedbackReporter: '',
|
|
|
opsUserName: '',
|
|
|
dateRange: [],
|
|
|
+ contractId: this.selectedContractId,
|
|
|
pageNum: 1,
|
|
|
pageSize: 10,
|
|
|
}
|
|
|
this.fetchData()
|
|
|
},
|
|
|
+ toggleSidebar() {
|
|
|
+ this.sidebarCollapsed = !this.sidebarCollapsed
|
|
|
+ this.scheduleTableLayout()
|
|
|
+ },
|
|
|
+ toggleSidebarFilters() {
|
|
|
+ this.showSidebarFilters = !this.showSidebarFilters
|
|
|
+ this.scheduleTableLayout()
|
|
|
+ },
|
|
|
handleSizeChange(val) {
|
|
|
this.queryForm.pageSize = val
|
|
|
this.fetchData()
|
|
|
@@ -300,6 +515,9 @@
|
|
|
this.currentRow = row
|
|
|
this.detailVisible = true
|
|
|
},
|
|
|
+ getProductLineLabel(productLine) {
|
|
|
+ return this.selectDictLabel(this.productLineDict, productLine) || productLine || '-'
|
|
|
+ },
|
|
|
async handleExport() {
|
|
|
const params = {
|
|
|
scopeType: this.queryForm.scopeType,
|
|
|
@@ -312,6 +530,10 @@
|
|
|
this.queryForm.dateRange && this.queryForm.dateRange.length === 2 ? this.queryForm.dateRange[0] : '',
|
|
|
endTime: this.queryForm.dateRange && this.queryForm.dateRange.length === 2 ? this.queryForm.dateRange[1] : '',
|
|
|
}
|
|
|
+ if (this.queryForm.contractId) {
|
|
|
+ params.contractId = this.queryForm.contractId
|
|
|
+ }
|
|
|
+
|
|
|
const [err, res] = await to(operationEventApi.export(params))
|
|
|
if (err) {
|
|
|
return
|
|
|
@@ -344,25 +566,396 @@
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
-<style scoped>
|
|
|
- .operation-history-container {
|
|
|
+<style lang="scss" scoped>
|
|
|
+ .operation-history-page {
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-height: 0;
|
|
|
+ height: calc(100vh - 122px);
|
|
|
+ padding: 4px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .query-form-container {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding: 8px 12px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 6px;
|
|
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
|
|
+ }
|
|
|
+
|
|
|
+ .main-content {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ gap: 4px;
|
|
|
+ min-height: 0;
|
|
|
+ margin-top: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-sidebar {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ width: 280px;
|
|
|
+ min-height: 0;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 8px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
|
|
+ transition: width 0.2s ease, padding 0.2s ease;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ &.collapsed {
|
|
|
+ width: 44px;
|
|
|
+ padding: 8px 4px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .sidebar-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sidebar-title-group {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sidebar-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-sidebar.collapsed .sidebar-header {
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 0;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sidebar-title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sidebar-collapsed-label {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-start;
|
|
|
+ padding-top: 12px;
|
|
|
+ writing-mode: vertical-rl;
|
|
|
+ text-orientation: upright;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #606266;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ user-select: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .collapse-trigger {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ padding: 0;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #909399;
|
|
|
+ transition: all 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #ecf5ff;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .sidebar-action-btn {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
padding: 0;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #909399;
|
|
|
+ transition: all 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #ecf5ff;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background: #ecf5ff;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ i {
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-search-wrapper {
|
|
|
+ margin-bottom: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-search {
|
|
|
+ margin-bottom: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-list {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding-right: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 10px 12px;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 12px;
|
|
|
+ transition: all 0.2s;
|
|
|
+ border: 1px solid transparent;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #f8fbff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background: linear-gradient(180deg, #eef6ff 0%, #e6f0ff 100%);
|
|
|
+ border-color: #bfd9ff;
|
|
|
+ box-shadow: 0 8px 18px rgba(64, 158, 255, 0.12);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-item--all {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ background: linear-gradient(135deg, #f8fbff 0%, #f3f7fd 100%);
|
|
|
+ border-color: #e4edf7;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-overview-label {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-overview-desc {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ margin-top: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-card {
|
|
|
position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ min-height: 98px;
|
|
|
+ padding: 8px 10px;
|
|
|
+ border-radius: 14px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ background: linear-gradient(180deg, #ffffff 0%, #fbfcfe 100%);
|
|
|
+ box-shadow: 0 8px 18px rgba(15, 23, 42, 0.05);
|
|
|
+ cursor: pointer;
|
|
|
+ transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
|
|
+
|
|
|
+ &:not(:last-child) {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ border-color: #d5e7ff;
|
|
|
+ box-shadow: 0 12px 22px rgba(64, 158, 255, 0.12);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ border-color: #8bb8ff;
|
|
|
+ background: linear-gradient(180deg, #eff6ff 0%, #f7fbff 100%);
|
|
|
+ box-shadow: 0 14px 26px rgba(64, 158, 255, 0.16);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- .table-wrapper {
|
|
|
- min-height: 240px;
|
|
|
+ .project-card-top {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
}
|
|
|
|
|
|
- .operation-history-container :deep(.el-pagination) {
|
|
|
- margin-top: 8px;
|
|
|
- margin-bottom: 0;
|
|
|
+ .project-card-tags {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-contract {
|
|
|
+ max-width: 108px;
|
|
|
+ padding: 2px 8px;
|
|
|
+ font-size: 11px;
|
|
|
+ color: #5f6b7a;
|
|
|
+ background: #f2f6fc;
|
|
|
+ border-radius: 999px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
- .export-btn-fixed {
|
|
|
+ .project-line-tag {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding: 2px 8px;
|
|
|
+ font-size: 11px;
|
|
|
+ color: #409eff;
|
|
|
+ background: #ecf5ff;
|
|
|
+ border-radius: 999px;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-status-tag {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding: 2px 8px;
|
|
|
+ font-size: 11px;
|
|
|
+ color: #fff;
|
|
|
+ background: #909399;
|
|
|
+ border-radius: 999px;
|
|
|
+ white-space: nowrap;
|
|
|
+
|
|
|
+ &--10 {
|
|
|
+ background: #909399;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--20 {
|
|
|
+ background: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--30 {
|
|
|
+ background: #e6a23c;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--40 {
|
|
|
+ background: #67c23a;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--50 {
|
|
|
+ background: #1f9d55;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--90 {
|
|
|
+ background: #f56c6c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-card-title {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1.3;
|
|
|
+ color: #303133;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-card-meta {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-card-meta-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex: 1;
|
|
|
+ gap: 6px;
|
|
|
+ min-width: 0;
|
|
|
+ padding: 4px 8px;
|
|
|
+ background: #f7f9fc;
|
|
|
+ border-radius: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-card-meta-icon {
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-card-meta-icon--delivery {
|
|
|
+ color: #67c23a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-card-meta-value {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .project-card-check {
|
|
|
position: absolute;
|
|
|
top: 12px;
|
|
|
- right: 10px;
|
|
|
- z-index: 100;
|
|
|
+ right: 12px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-panel {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ flex-direction: column;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-container {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ flex-direction: column;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ padding: 8px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 4px;
|
|
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .table-scroll-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .operation-history-page :deep(.el-pagination) {
|
|
|
+ margin-top: 8px;
|
|
|
+ margin-bottom: 0;
|
|
|
}
|
|
|
</style>
|