You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1242 lines
45 KiB
1242 lines
45 KiB
<template>
|
|
<div class="mod-config">
|
|
<el-form :inline="true" label-position="top" class="query-form">
|
|
<el-form-item label="项目号">
|
|
<el-input v-model="searchData.projectNo" clearable placeholder="请输入项目号" style="width: 140px"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="型号">
|
|
<el-input v-model="searchData.modelNo" clearable placeholder="请输入型号" style="width: 140px"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="颜色">
|
|
<el-input v-model="searchData.color" clearable placeholder="请输入颜色" style="width: 120px"></el-input>
|
|
</el-form-item>
|
|
<el-form-item label="状态">
|
|
<el-select v-model="searchData.status" clearable placeholder="全部" style="width: 120px">
|
|
<el-option label="已排产" value="已排产"></el-option>
|
|
<el-option label="进行中" value="进行中"></el-option>
|
|
<el-option label="已完成" value="已完成"></el-option>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="计划发货日期">
|
|
<el-date-picker v-model="searchData.deliveryStartDate" type="date" value-format="yyyy-MM-dd" placeholder="开始" style="width: 130px"></el-date-picker>
|
|
</el-form-item>
|
|
<el-form-item label="至">
|
|
<el-date-picker v-model="searchData.deliveryEndDate" type="date" value-format="yyyy-MM-dd" placeholder="结束" style="width: 130px"></el-date-picker>
|
|
</el-form-item>
|
|
<el-form-item label=" " style="margin-top: -11px">
|
|
<el-button @click="getDataList('Y')" plain class="search-btn">查询</el-button>
|
|
<el-button @click="resetQuery()" plain class="reset-btn">重置</el-button>
|
|
<el-button @click="openEditDialog()" plain class="add-btn">新增改造订单</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<el-table
|
|
ref="orderTable"
|
|
class="data-table"
|
|
:data="dataList"
|
|
:height="tableHeight"
|
|
border
|
|
highlight-current-row
|
|
v-loading="dataListLoading"
|
|
style="width: 100%"
|
|
@current-change="onCurrentRowChange">
|
|
<el-table-column label="操作" width="200" align="center" >
|
|
<template slot-scope="scope">
|
|
<a type="text" @click="openEditDialog(scope.row)" v-if="scope.row.status === '已排产'">修改</a>
|
|
<a type="text" @click="openAssignDialog(scope.row)" v-if="scope.row.status !== '已完成' && scope.row.currentNode!=='全部完成'">分配人员</a>
|
|
<a type="text" @click="finishOrder(scope.row)" v-if="scope.row.status !== '已完成'">完工</a>
|
|
<a type="text" style="color:#F56C6C" @click="deleteOrder(scope.row)" v-if="scope.row.status !== '已完成'">删除</a>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column type="index" label="#" width="50" align="center"></el-table-column>
|
|
<el-table-column prop="projectNo" label="项目号" width="120" align="center"></el-table-column>
|
|
<el-table-column prop="modelNo" label="型号" width="130" align="center"></el-table-column>
|
|
<el-table-column prop="color" label="颜色" width="90" align="center"></el-table-column>
|
|
<el-table-column prop="floorCount" label="层数" width="70" align="center"></el-table-column>
|
|
<el-table-column prop="specialRequirement" label="特殊要求" min-width="180" show-overflow-tooltip></el-table-column>
|
|
<el-table-column prop="planDeliveryDate" label="计划发货日期" width="120" align="center"></el-table-column>
|
|
<el-table-column prop="currentNode" label="当前节点" width="120" align="center"></el-table-column>
|
|
<el-table-column prop="assigneeSummary" label="节点负责人" min-width="160" show-overflow-tooltip></el-table-column>
|
|
<el-table-column label="节点进度" width="100" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag size="small" type="info">{{ scope.row.nodeDoneCount }}/{{ scope.row.nodeTotalCount }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="status" label="状态" width="90" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag :type="getStatusType(scope.row.status)" size="small">{{ scope.row.status }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="finishDate" label="完工日期" width="120" align="center">
|
|
<template slot-scope="scope">{{ scope.row.finishDate || '-' }}</template>
|
|
</el-table-column>
|
|
|
|
</el-table>
|
|
|
|
<el-pagination
|
|
@size-change="sizeChangeHandle"
|
|
@current-change="currentChangeHandle"
|
|
:current-page="pageIndex"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
:page-size="pageSize"
|
|
:total="totalPage"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
style="margin-top: 20px; text-align: right">
|
|
</el-pagination>
|
|
|
|
<div class="detail-tabs-wrap">
|
|
<el-tabs v-model="detailTabName" type="border-card">
|
|
<el-tab-pane label="节点状态和日志" name="statusLogs">
|
|
<div v-if="selectedOrder.orderNo" class="two-column-layout">
|
|
<div class="stages-column">
|
|
<div class="column-header">
|
|
<i class="el-icon-s-order"></i>
|
|
<span>节点流程</span>
|
|
<span class="progress-badge">{{ selectedOrderProgressPercent }}%</span>
|
|
</div>
|
|
<div class="stages-list">
|
|
<div
|
|
v-for="(stage, index) in selectedOrderNodeList"
|
|
:key="stage.nodeCode || index"
|
|
class="stage-item"
|
|
:class="'stage-' + getStageClass(stage.status)">
|
|
<div class="stage-icon">
|
|
<i :class="getStageIcon(stage.status)"></i>
|
|
</div>
|
|
<div class="stage-content">
|
|
<div class="stage-name">{{ stage.nodeName }}</div>
|
|
<div class="stage-meta">
|
|
<el-tag :type="getStageTagType(stage.status)" size="mini" effect="plain">{{ stage.status || '未开始' }}</el-tag>
|
|
<span class="stage-owner">负责人:{{ stage.assigneeUserName || '-' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="logs-column">
|
|
<div class="column-header">
|
|
<i class="el-icon-tickets"></i>
|
|
<span>操作日志</span>
|
|
<span class="logs-count">{{ selectedOrderLogList.length }}条</span>
|
|
</div>
|
|
<div class="logs-table-wrapper">
|
|
<el-table :data="selectedOrderLogList" border size="small" class="detail-table" v-loading="detailLogLoading" height="295px">
|
|
<el-table-column prop="logTime" label="时间" min-width="160" align="center"></el-table-column>
|
|
<el-table-column prop="action" label="操作" min-width="95" align="center"></el-table-column>
|
|
<el-table-column prop="nodeName" label="节点" min-width="130" align="center">
|
|
<template slot-scope="scope">{{ scope.row.nodeName || scope.row.nodeCode || '-' }}</template>
|
|
</el-table-column>
|
|
<el-table-column prop="operatorName" label="操作人" min-width="100" align="center"></el-table-column>
|
|
<!-- <el-table-column prop="comment" label="备注" min-width="220" show-overflow-tooltip>
|
|
<template slot-scope="scope">{{ scope.row.comment || '-' }}</template>
|
|
</el-table-column>-->
|
|
<el-table-column label="影像" min-width="95" align="center">
|
|
<template slot-scope="scope">
|
|
<a
|
|
v-if="isMediaNodeLog(scope.row)"
|
|
type="text"
|
|
size="mini"
|
|
@click="openMediaFileDialog(scope.row)">
|
|
查看文件
|
|
</a>
|
|
<span v-else>-</span>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<el-empty v-else description="请先选择一条订单记录"></el-empty>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</div>
|
|
|
|
<el-dialog :title="saveHeaderData.orderNo ? '修改改造项目订单' : '新增改造项目订单'" :visible.sync="setUp.reviewFlag" width="550px" :close-on-click-modal="false" v-drag>
|
|
<el-form
|
|
ref="editForm"
|
|
:model="saveHeaderData"
|
|
label-position="top"
|
|
class="edit-form">
|
|
<el-row :gutter="20">
|
|
<el-col :span="12"><el-form-item label="项目号" required><el-input v-model="saveHeaderData.projectNo"></el-input></el-form-item></el-col>
|
|
<el-col :span="12"><el-form-item label="型号" required><el-input v-model="saveHeaderData.modelNo"></el-input></el-form-item></el-col>
|
|
</el-row>
|
|
<el-row :gutter="20">
|
|
<el-col :span="12"><el-form-item label="颜色"><el-input v-model="saveHeaderData.color"></el-input></el-form-item></el-col>
|
|
<el-col :span="12"><el-form-item label="层数"><el-input v-model="saveHeaderData.floorCount" :min="1" :max="99" style="width: 100%"></el-input></el-form-item></el-col>
|
|
</el-row>
|
|
<el-row :gutter="20">
|
|
<el-col :span="12"><el-form-item label="计划发货日期"><el-date-picker v-model="saveHeaderData.planDeliveryDate" type="date" value-format="yyyy-MM-dd" style="width: 100%"></el-date-picker></el-form-item></el-col>
|
|
<el-col :span="12"><el-form-item label="状态"><el-select disabled v-model="saveHeaderData.status" style="width: 100%"><el-option label="已排产" value="已排产"></el-option><el-option label="进行中" value="进行中"></el-option><el-option label="已完成" value="已完成"></el-option></el-select></el-form-item></el-col>
|
|
</el-row>
|
|
<!-- <el-row :gutter="20">
|
|
<el-col :span="12">
|
|
<el-form-item>
|
|
<template slot="label">
|
|
人员分配策略
|
|
<el-tooltip effect="dark" placement="top">
|
|
<div slot="content">
|
|
默认分配:创建订单后,系统会按节点角色自动分配该角色下全部人员。<br>
|
|
手动分配:创建订单后不自动分配,需要在“分配人员”里手工选择负责人。
|
|
</div>
|
|
<i class="el-icon-question" style="margin-left:6px;color:#909399;cursor:pointer;"></i>
|
|
</el-tooltip>
|
|
</template>
|
|
<el-radio-group v-model="saveHeaderData.autoAssignAllUsers">
|
|
<el-radio :label="true">默认分配</el-radio>
|
|
<el-radio :label="false">手动分配</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item label="节点报工模式">
|
|
<el-radio-group v-model="saveHeaderData.nodeReportMode">
|
|
<el-radio label="PARALLEL">并行</el-radio>
|
|
<el-radio label="SEQUENTIAL">串行</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>-->
|
|
<el-form-item label="特殊要求"><el-input v-model="saveHeaderData.specialRequirement" type="textarea" :rows="3"></el-input></el-form-item>
|
|
</el-form>
|
|
<el-footer style="height: 40px; margin-top: 50px; text-align: center">
|
|
<el-button plain class="reset-btn" @click="setUp.reviewFlag = false">取消</el-button>
|
|
<el-button plain class="add-btn" :loading="setUp.saveButton" @click="saveOrder">保存</el-button>
|
|
</el-footer>
|
|
</el-dialog>
|
|
|
|
<el-dialog title="车间节点报工" :visible.sync="setUp.reportFlag" width="460px" :close-on-click-modal="false" v-drag>
|
|
<el-form :model="reportData" label-width="110px" label-position="top">
|
|
<el-form-item label="项目号"><el-input v-model="reportData.projectNo" disabled></el-input></el-form-item>
|
|
<el-form-item label="报工节点">
|
|
<el-select v-model="reportData.nodeCode" placeholder="请选择节点" style="width: 100%">
|
|
<el-option v-for="item in reportNodeOptions" :key="item.nodeCode" :label="item.nodeName" :value="item.nodeCode"></el-option>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="报工备注"><el-input v-model="reportData.remark" type="textarea" :rows="2"></el-input></el-form-item>
|
|
</el-form>
|
|
<el-footer style="height: 40px; margin-top: 50px; text-align: center">
|
|
<el-button plain class="reset-btn" @click="setUp.reportFlag = false">取消</el-button>
|
|
<el-button plain class="search-btn" :loading="setUp.reportButton" @click="submitNodeReport">提交报工</el-button>
|
|
</el-footer>
|
|
</el-dialog>
|
|
<el-dialog title="节点负责人分配" :visible.sync="setUp.assignFlag" width="550px" :close-on-click-modal="false" v-drag>
|
|
<el-table :data="assignNodeList" border class="data-table">
|
|
<el-table-column prop="nodeName" label="节点" width="150"></el-table-column>
|
|
<el-table-column label="负责人" width="370">
|
|
<template slot-scope="scope">
|
|
<el-select v-model="scope.row.assigneeUserIdList" filterable clearable multiple placeholder="请选择负责人" style="width: 100%">
|
|
<el-option v-for="user in scope.row.userOptions" :key="user.userId" :label="user.displayName || user.username" :value="user.userId"></el-option>
|
|
</el-select>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<el-footer style="height: 40px; margin-top: 20px; text-align: center">
|
|
<el-button plain class="reset-btn" @click="setUp.assignFlag = false">取消</el-button>
|
|
<el-button plain class="add-btn" :loading="setUp.assignButton" @click="saveNodeAssigneeAction">保存分配</el-button>
|
|
</el-footer>
|
|
</el-dialog>
|
|
|
|
<el-dialog
|
|
title="报工影像文件"
|
|
:visible.sync="mediaDialogVisible"
|
|
width="600px"
|
|
:close-on-click-modal="false"
|
|
@close="handleMediaDialogClose"
|
|
v-drag>
|
|
<div v-loading="mediaDialogLoading" class="media-dialog-body">
|
|
<div class="media-dialog-meta">
|
|
<span>节点:{{ mediaDialogLog.nodeName || mediaDialogLog.nodeCode || '-' }}</span>
|
|
<span>报工时间:{{ mediaDialogLog.logTime || '-' }}</span>
|
|
</div>
|
|
<el-table v-if="mediaFileList.length > 0" :data="mediaFileList" class="file-table" border size="small" height="360px">
|
|
<el-table-column type="index" label="#" width="55" align="center"></el-table-column>
|
|
<el-table-column label="缩略图" width="150" align="center">
|
|
<template slot-scope="scope">
|
|
<div class="media-thumb-cell">
|
|
<el-button v-if="resolveMediaKind(scope.row) === 'other'" type="text" size="mini" disabled>不支持</el-button>
|
|
<span v-else-if="scope.row.previewLoading" class="media-thumb-loading">加载中...</span>
|
|
<img
|
|
v-else-if="resolveMediaKind(scope.row) === 'image' && scope.row.previewUrl"
|
|
:src="scope.row.previewUrl"
|
|
class="media-thumb media-thumb-image"
|
|
alt="media-thumb"
|
|
@click="previewMediaFile(scope.row)">
|
|
<video
|
|
v-else-if="resolveMediaKind(scope.row) === 'video'"
|
|
:src="getVideoStreamUrl(scope.row)"
|
|
class="media-thumb media-thumb-video"
|
|
muted
|
|
playsinline
|
|
preload="metadata"
|
|
@click="previewMediaFile(scope.row)">
|
|
</video>
|
|
<el-button v-else type="text" size="mini" @click="previewMediaFile(scope.row)">加载预览</el-button>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="类型" width="90" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag size="mini" :type="resolveMediaKind(scope.row) === 'video' ? 'warning' : (resolveMediaKind(scope.row) === 'image' ? 'success' : 'info')">
|
|
{{ getMediaKindLabel(scope.row) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="上传时间" min-width="170" align="center">
|
|
<template slot-scope="scope">{{ scope.row.createDate || '-' }}</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<el-empty v-else description="当前日志暂无影像文件"></el-empty>
|
|
</div>
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button type="primary" @click="mediaDialogVisible = false">关闭</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
|
|
<div v-if="mediaPreviewVisible" class="media-preview-overlay" @click="closeMediaPreview">
|
|
<i class="el-icon-close media-preview-close" @click.stop="closeMediaPreview"></i>
|
|
<img
|
|
v-if="mediaPreviewType === 'image'"
|
|
:src="mediaPreviewUrl"
|
|
:alt="mediaPreviewName || 'media-preview'"
|
|
class="media-preview-image"
|
|
@click.stop="closeMediaPreview">
|
|
<video
|
|
v-else-if="mediaPreviewType === 'video'"
|
|
ref="mediaPreviewVideo"
|
|
:key="mediaPreviewUrl"
|
|
:src="mediaPreviewUrl"
|
|
class="media-preview-video"
|
|
controls
|
|
autoplay
|
|
playsinline
|
|
preload="auto"
|
|
@error="handleMediaPreviewError"
|
|
@click.stop>
|
|
</video>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { deleteRenovationOrder, finishRenovationOrder, getNodeAssigneeList, getNodeAssigneeUsers, getRenovationOrderList, getReportLogList, reportRenovationOrderNode, saveNodeAssignee, saveRenovationOrder } from '@/api/longchuang/productionPlan'
|
|
import { getOssVideoStreamUrl, previewOssFileById2, queryOssFilePlus } from '@/api/oss/oss'
|
|
|
|
export default {
|
|
name: 'ProductionPlanRenovationOrder',
|
|
data() {
|
|
return {
|
|
searchData: { projectNo: '', modelNo: '', color: '', status: '', deliveryStartDate: '', deliveryEndDate: '', page: 1, limit: 20 },
|
|
saveHeaderData: {},
|
|
reportData: { orderNo: '', projectNo: '', nodeCode: '', remark: '' },
|
|
reportNodeOptions: [],
|
|
setUp: { reviewFlag: false, reportFlag: false, assignFlag: false, saveButton: false, reportButton: false, assignButton: false },
|
|
dataList: [],
|
|
currentAssignOrder: { orderNo: '', orderType: 'RENOVATION' },
|
|
assignNodeList: [],
|
|
selectedOrder: {},
|
|
detailTabName: 'statusLogs',
|
|
selectedOrderLogList: [],
|
|
detailLogLoading: false,
|
|
mediaDialogVisible: false,
|
|
mediaDialogLoading: false,
|
|
mediaDialogLog: {},
|
|
mediaFileList: [],
|
|
mediaPreviewVisible: false,
|
|
mediaPreviewUrl: '',
|
|
mediaPreviewType: '',
|
|
mediaPreviewName: '',
|
|
pageIndex: 1,
|
|
pageSize: 20,
|
|
totalPage: 0,
|
|
dataListLoading: false,
|
|
tableHeight: (window.innerHeight - 320) / 2
|
|
}
|
|
},
|
|
activated() {
|
|
this.getDataList()
|
|
},
|
|
beforeDestroy() {
|
|
this.releaseMediaFileUrls()
|
|
},
|
|
methods: {
|
|
getDataList(flag) {
|
|
if (flag === 'Y') this.pageIndex = 1
|
|
this.searchData.page = this.pageIndex
|
|
this.searchData.limit = this.pageSize
|
|
this.dataListLoading = true
|
|
getRenovationOrderList(this.searchData).then(({data}) => {
|
|
this.dataListLoading = false
|
|
if (data && data.code === 0) {
|
|
this.dataList = (data.page.list || []).map(this.normalizeRow)
|
|
this.totalPage = data.page.totalCount || 0
|
|
this.syncSelectedOrder()
|
|
} else this.loadMockData()
|
|
}).catch(() => {
|
|
this.dataListLoading = false
|
|
this.loadMockData()
|
|
})
|
|
},
|
|
normalizeRow(row) {
|
|
const list = row.nodeList || []
|
|
const done = list.filter(item => item.status === '已完成').length
|
|
const currentNode = (list.find(item => item.status !== '已完成') || {}).nodeName || '全部完成'
|
|
const assigneeSummary = list.filter(item => item.assigneeUserName).map(item => `${item.nodeName}:${item.assigneeUserName}`).join(';')
|
|
return { ...row, autoAssignAllUsers: !!row.autoAssignAllUsers, nodeReportMode: row.nodeReportMode || 'PARALLEL', nodeList: list, nodeDoneCount: done, nodeTotalCount: list.length, currentNode: row.currentNode || currentNode, assigneeSummary: assigneeSummary || '-' }
|
|
},
|
|
loadMockData() {
|
|
this.dataList = [
|
|
{
|
|
orderNo: 'MOCK-RENOVATION-001',
|
|
projectNo: 'RNV-202604-001',
|
|
modelNo: 'LC-REN-630',
|
|
color: '钛金灰',
|
|
floorCount: 10,
|
|
autoAssignAllUsers: true,
|
|
nodeReportMode: 'PARALLEL',
|
|
specialRequirement: '井道尺寸受限,需优化导轨方案',
|
|
planDeliveryDate: '2026-04-28',
|
|
status: '进行中',
|
|
finishDate: '',
|
|
nodeList: [
|
|
{ nodeCode: 'stocking', nodeName: '仓库配料', status: '已完成' },
|
|
{ nodeCode: 'assy', nodeName: '组装', status: '进行中' },
|
|
{ nodeCode: 'inspect', nodeName: '检验', status: '未开始' },
|
|
{ nodeCode: 'pack', nodeName: '打包', status: '未开始' }
|
|
]
|
|
},
|
|
{
|
|
orderNo: 'MOCK-RENOVATION-002',
|
|
projectNo: 'RNV-202604-002',
|
|
modelNo: 'LC-REN-800',
|
|
color: '深空黑',
|
|
floorCount: 14,
|
|
autoAssignAllUsers: true,
|
|
nodeReportMode: 'PARALLEL',
|
|
specialRequirement: '兼容旧楼层召唤系统',
|
|
planDeliveryDate: '2026-05-06',
|
|
status: '已排产',
|
|
finishDate: '',
|
|
nodeList: [
|
|
{ nodeCode: 'stocking', nodeName: '仓库配料', status: '未开始' },
|
|
{ nodeCode: 'assy', nodeName: '组装', status: '未开始' },
|
|
{ nodeCode: 'inspect', nodeName: '检验', status: '未开始' },
|
|
{ nodeCode: 'pack', nodeName: '打包', status: '未开始' }
|
|
]
|
|
}
|
|
].map(this.normalizeRow)
|
|
this.totalPage = this.dataList.length
|
|
this.syncSelectedOrder()
|
|
},
|
|
syncSelectedOrder() {
|
|
if (!this.dataList.length) {
|
|
this.selectedOrder = {}
|
|
this.selectedOrderLogList = []
|
|
return
|
|
}
|
|
const selectedOrderNo = this.selectedOrder && this.selectedOrder.orderNo
|
|
const matched = selectedOrderNo ? this.dataList.find(item => item.orderNo === selectedOrderNo) : null
|
|
const current = matched || this.dataList[0]
|
|
this.selectedOrder = current
|
|
this.$nextTick(() => {
|
|
if (this.$refs.orderTable && current) {
|
|
this.$refs.orderTable.setCurrentRow(current)
|
|
}
|
|
})
|
|
this.loadSelectedOrderLogList(current)
|
|
},
|
|
onCurrentRowChange(row) {
|
|
if (!row || !row.orderNo) {
|
|
this.selectedOrder = {}
|
|
this.selectedOrderLogList = []
|
|
return
|
|
}
|
|
this.selectedOrder = row
|
|
this.loadSelectedOrderLogList(row)
|
|
},
|
|
loadSelectedOrderLogList(row) {
|
|
if (!row || !row.orderNo) {
|
|
this.selectedOrderLogList = []
|
|
return
|
|
}
|
|
this.detailLogLoading = true
|
|
getReportLogList({ orderNo: row.orderNo, orderType: 'RENOVATION' }).then(({ data }) => {
|
|
this.detailLogLoading = false
|
|
this.selectedOrderLogList = data && data.code === 0 ? (data.rows || []) : []
|
|
}).catch(() => {
|
|
this.detailLogLoading = false
|
|
this.selectedOrderLogList = []
|
|
})
|
|
},
|
|
isMediaNodeLog(row) {
|
|
if (!row || !row.logNo) {
|
|
return false
|
|
}
|
|
if (row.action && row.action !== '报工完成') {
|
|
return false
|
|
}
|
|
const codeSet = ['inspect', 'pack']
|
|
const nameSet = ['检验', '打包']
|
|
const nodeCode = String(row.nodeCode || '').trim()
|
|
const nodeName = String(row.nodeName || '').replace(/\s+/g, '')
|
|
return codeSet.includes(nodeCode) || nameSet.some(item => item.replace(/\s+/g, '') === nodeName)
|
|
},
|
|
async openMediaFileDialog(row) {
|
|
if (!row || !row.logNo || !row.nodeCode) {
|
|
this.$message.warning('当前日志未关联影像文件')
|
|
return
|
|
}
|
|
this.releaseMediaFileUrls()
|
|
this.closeMediaPreview()
|
|
this.mediaFileList = []
|
|
this.mediaDialogLog = { ...row }
|
|
this.mediaDialogVisible = true
|
|
this.mediaDialogLoading = true
|
|
try {
|
|
const { data } = await queryOssFilePlus({
|
|
orderRef1: this.selectedOrder.orderNo || row.orderNo,
|
|
orderRef2: row.nodeCode,
|
|
orderRef3: row.logNo
|
|
})
|
|
this.mediaFileList = (data && data.code === 0 ? (data.rows || []) : []).map(item => ({
|
|
...item,
|
|
previewUrl: '',
|
|
previewLoading: false
|
|
}))
|
|
this.mediaFileList.forEach(item => {
|
|
const kind = this.resolveMediaKind(item)
|
|
if (kind === 'image') {
|
|
this.loadMediaPreviewUrl(item, kind, false)
|
|
}
|
|
})
|
|
} catch (e) {
|
|
this.mediaFileList = []
|
|
this.$message.error('影像文件加载失败')
|
|
} finally {
|
|
this.mediaDialogLoading = false
|
|
}
|
|
},
|
|
handleMediaDialogClose() {
|
|
this.mediaDialogLoading = false
|
|
this.mediaDialogLog = {}
|
|
this.releaseMediaFileUrls()
|
|
this.mediaFileList = []
|
|
this.closeMediaPreview()
|
|
},
|
|
releaseMediaFileUrls() {
|
|
this.mediaFileList.forEach(item => {
|
|
if (item && item.previewUrl) {
|
|
URL.revokeObjectURL(item.previewUrl)
|
|
}
|
|
})
|
|
},
|
|
getMediaExt(fileRow) {
|
|
if (!fileRow) {
|
|
return ''
|
|
}
|
|
const ext = fileRow.fileType || fileRow.fileSuffix || this.getExtFromFileName(fileRow.fileName || fileRow.newFileName || '')
|
|
return String(ext).replace(/^\./, '').toLowerCase()
|
|
},
|
|
getExtFromFileName(fileName) {
|
|
const name = String(fileName || '')
|
|
const dotIndex = name.lastIndexOf('.')
|
|
if (dotIndex < 0 || dotIndex >= name.length - 1) {
|
|
return ''
|
|
}
|
|
return name.slice(dotIndex + 1)
|
|
},
|
|
resolveMediaKind(fileRow) {
|
|
const ext = this.getMediaExt(fileRow)
|
|
const imageExtList = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
|
|
const videoExtList = ['mp4', 'webm', 'mov', 'avi', 'm4v', '3gp']
|
|
if (imageExtList.includes(ext)) {
|
|
return 'image'
|
|
}
|
|
if (videoExtList.includes(ext)) {
|
|
return 'video'
|
|
}
|
|
return 'other'
|
|
},
|
|
getMediaKindLabel(fileRow) {
|
|
const kind = this.resolveMediaKind(fileRow)
|
|
if (kind === 'image') {
|
|
return '照片'
|
|
}
|
|
if (kind === 'video') {
|
|
return '视频'
|
|
}
|
|
return '其他'
|
|
},
|
|
buildPreviewMimeType(fileRow, kind) {
|
|
const ext = this.getMediaExt(fileRow)
|
|
if (kind === 'image') {
|
|
const imageMimeMap = {
|
|
jpg: 'image/jpeg',
|
|
jpeg: 'image/jpeg',
|
|
png: 'image/png',
|
|
gif: 'image/gif',
|
|
bmp: 'image/bmp',
|
|
webp: 'image/webp'
|
|
}
|
|
return imageMimeMap[ext] || 'image/jpeg'
|
|
}
|
|
if (kind === 'video') {
|
|
const videoMimeMap = {
|
|
mp4: 'video/mp4',
|
|
m4v: 'video/mp4',
|
|
webm: 'video/webm',
|
|
mov: 'video/quicktime',
|
|
avi: 'video/x-msvideo',
|
|
'3gp': 'video/3gpp'
|
|
}
|
|
return videoMimeMap[ext] || 'video/mp4'
|
|
}
|
|
return 'application/octet-stream'
|
|
},
|
|
async loadMediaPreviewUrl(fileRow, fixedKind = '', showError = true) {
|
|
if (!fileRow || !fileRow.id) {
|
|
return false
|
|
}
|
|
const kind = fixedKind || this.resolveMediaKind(fileRow)
|
|
if (kind === 'other') {
|
|
return false
|
|
}
|
|
if (fileRow.previewUrl) {
|
|
return true
|
|
}
|
|
if (fileRow.previewLoading) {
|
|
return false
|
|
}
|
|
this.$set(fileRow, 'previewLoading', true)
|
|
const mimeType = this.buildPreviewMimeType(fileRow, kind)
|
|
try {
|
|
const { data } = await previewOssFileById2({ id: fileRow.id, fileType: mimeType })
|
|
const sourceBlob = data instanceof Blob ? data : new Blob([data])
|
|
const blobType = mimeType || sourceBlob.type || 'application/octet-stream'
|
|
const blob = new Blob([sourceBlob], { type: blobType })
|
|
this.$set(fileRow, 'previewUrl', URL.createObjectURL(blob))
|
|
return true
|
|
} catch (e) {
|
|
if (showError) {
|
|
this.$message.error('文件预览加载失败')
|
|
}
|
|
return false
|
|
} finally {
|
|
this.$set(fileRow, 'previewLoading', false)
|
|
}
|
|
},
|
|
getVideoStreamUrl(fileRow) {
|
|
if (!fileRow || !fileRow.id) {
|
|
return ''
|
|
}
|
|
return getOssVideoStreamUrl(fileRow.id)
|
|
},
|
|
async previewMediaFile(fileRow) {
|
|
const kind = this.resolveMediaKind(fileRow)
|
|
if (kind === 'other') {
|
|
this.$message.warning('当前文件暂不支持预览')
|
|
return
|
|
}
|
|
if (!fileRow || !fileRow.id) {
|
|
this.$message.warning('文件信息不完整,无法预览')
|
|
return
|
|
}
|
|
if (kind === 'video') {
|
|
const videoUrl = this.getVideoStreamUrl(fileRow)
|
|
if (!videoUrl) {
|
|
this.$message.warning('视频地址无效,无法预览')
|
|
return
|
|
}
|
|
this.mediaPreviewType = 'video'
|
|
this.mediaPreviewName = fileRow.fileName || fileRow.newFileName || ''
|
|
this.mediaPreviewUrl = videoUrl
|
|
this.mediaPreviewVisible = true
|
|
this.$nextTick(() => {
|
|
const previewVideo = this.$refs.mediaPreviewVideo
|
|
if (previewVideo && typeof previewVideo.play === 'function') {
|
|
const playPromise = previewVideo.play()
|
|
if (playPromise && typeof playPromise.catch === 'function') {
|
|
playPromise.catch(() => {})
|
|
}
|
|
}
|
|
})
|
|
return
|
|
}
|
|
const loaded = await this.loadMediaPreviewUrl(fileRow, kind, true)
|
|
if (!loaded) {
|
|
return
|
|
}
|
|
this.mediaPreviewType = kind
|
|
this.mediaPreviewName = fileRow.fileName || fileRow.newFileName || ''
|
|
this.mediaPreviewUrl = fileRow.previewUrl
|
|
this.mediaPreviewVisible = true
|
|
},
|
|
closeMediaPreview() {
|
|
const previewVideo = this.$refs.mediaPreviewVideo
|
|
if (previewVideo && typeof previewVideo.pause === 'function') {
|
|
previewVideo.pause()
|
|
}
|
|
this.mediaPreviewVisible = false
|
|
this.mediaPreviewType = ''
|
|
this.mediaPreviewName = ''
|
|
this.mediaPreviewUrl = ''
|
|
},
|
|
handleMediaPreviewError() {
|
|
this.$message.warning('视频播放失败,请稍后重试')
|
|
},
|
|
resetQuery() {
|
|
this.searchData = { projectNo: '', modelNo: '', color: '', status: '', deliveryStartDate: '', deliveryEndDate: '', page: 1, limit: 20 }
|
|
this.getDataList('Y')
|
|
},
|
|
openEditDialog(row) {
|
|
this.saveHeaderData = row ? { ...row, autoAssignAllUsers: !!row.autoAssignAllUsers, nodeReportMode: row.nodeReportMode || 'PARALLEL' } : { orderNo: '', projectNo: '', modelNo: '', color: '', floorCount: 1, specialRequirement: '', planDeliveryDate: '', status: '已排产', autoAssignAllUsers: true, nodeReportMode: 'PARALLEL', nodeList: [] }
|
|
this.setUp.reviewFlag = true
|
|
},
|
|
isProjectNoDuplicate(projectNo, currentOrderNo) {
|
|
const normalizedProjectNo = String(projectNo || '').trim().toUpperCase()
|
|
if (!normalizedProjectNo) {
|
|
return false
|
|
}
|
|
return (this.dataList || []).some(item => {
|
|
if (!item || !item.orderNo) {
|
|
return false
|
|
}
|
|
if (currentOrderNo && item.orderNo === currentOrderNo) {
|
|
return false
|
|
}
|
|
const itemProjectNo = String(item.projectNo || '').trim().toUpperCase()
|
|
return itemProjectNo && itemProjectNo === normalizedProjectNo
|
|
})
|
|
},
|
|
saveOrder() {
|
|
const projectNo = String(this.saveHeaderData.projectNo || '').trim()
|
|
const modelNo = String(this.saveHeaderData.modelNo || '').trim()
|
|
if (!projectNo || !modelNo) return this.$message.warning('请先填写项目号和型号')
|
|
if (this.isProjectNoDuplicate(projectNo, this.saveHeaderData.orderNo)) {
|
|
return this.$message.warning(`项目号【${projectNo}】已存在,不能重复`)
|
|
}
|
|
this.saveHeaderData.projectNo = projectNo
|
|
this.saveHeaderData.modelNo = modelNo
|
|
this.setUp.saveButton = true
|
|
saveRenovationOrder(this.saveHeaderData).then(({data}) => {
|
|
this.setUp.saveButton = false
|
|
if (data && data.code === 0) {
|
|
this.$message.success(data.msg || '保存成功')
|
|
this.setUp.reviewFlag = false
|
|
this.getDataList()
|
|
} else this.$message.error(data.msg || '保存失败')
|
|
}).catch(() => {
|
|
this.setUp.saveButton = false
|
|
const orderNo = this.saveHeaderData.orderNo || String(Date.now())
|
|
const index = this.dataList.findIndex(item => item.orderNo === orderNo)
|
|
const saveData = this.normalizeRow({
|
|
...this.saveHeaderData,
|
|
orderNo: orderNo,
|
|
nodeList: this.saveHeaderData.nodeList && this.saveHeaderData.nodeList.length ? this.saveHeaderData.nodeList : [
|
|
{ nodeCode: 'stocking', nodeName: '仓库配料', status: '未开始' },
|
|
{ nodeCode: 'assy', nodeName: '组装', status: '未开始' },
|
|
{ nodeCode: 'inspect', nodeName: '检验', status: '未开始' },
|
|
{ nodeCode: 'pack', nodeName: '打包', status: '未开始' }
|
|
]
|
|
})
|
|
if (index > -1) this.$set(this.dataList, index, saveData)
|
|
else this.dataList.unshift(saveData)
|
|
this.totalPage = this.dataList.length
|
|
this.setUp.reviewFlag = false
|
|
this.$message.success('后端未完成,已在前端演示保存')
|
|
})
|
|
},
|
|
openReportDialog(row) {
|
|
this.reportNodeOptions = (row.nodeList || []).filter(item => item.status !== '已完成')
|
|
this.reportData = { orderNo: row.orderNo, projectNo: row.projectNo, nodeCode: '', remark: '' }
|
|
this.setUp.reportFlag = true
|
|
},
|
|
openAssignDialog(row) {
|
|
if (!row.orderNo) return this.$message.warning('请先保存订单后再分配人员')
|
|
this.currentAssignOrder = { orderNo: row.orderNo, orderType: 'RENOVATION' }
|
|
getNodeAssigneeList({ orderNo: row.orderNo, orderType: 'RENOVATION' }).then(({ data }) => {
|
|
const assignRows = data && data.code === 0 ? (data.rows || []) : []
|
|
this.assignNodeList = assignRows.map(item => ({ ...item, assigneeUserIdList: item.assigneeUserIdList || [], userOptions: [] }))
|
|
const requests = this.assignNodeList.map(item =>
|
|
getNodeAssigneeUsers({ orderType: 'RENOVATION', nodeCode: item.nodeCode }).then(({ data: userData }) => {
|
|
item.userOptions = userData && userData.code === 0 ? (userData.rows || []) : []
|
|
}).catch(() => { item.userOptions = [] })
|
|
)
|
|
Promise.all(requests).finally(() => { this.setUp.assignFlag = true })
|
|
}).catch(() => {
|
|
this.$message.error('加载节点分配信息失败')
|
|
})
|
|
},
|
|
saveNodeAssigneeAction() {
|
|
this.setUp.assignButton = true
|
|
const assigneeList = this.assignNodeList.map(item => ({
|
|
nodeCode: item.nodeCode,
|
|
nodeName: item.nodeName,
|
|
assigneeUserIdList: item.assigneeUserIdList || []
|
|
}))
|
|
saveNodeAssignee({
|
|
orderNo: this.currentAssignOrder.orderNo,
|
|
orderType: this.currentAssignOrder.orderType,
|
|
assigneeList: assigneeList
|
|
}).then(({ data }) => {
|
|
this.setUp.assignButton = false
|
|
if (data && data.code === 0) {
|
|
this.$message.success(data.msg || '分配成功')
|
|
this.setUp.assignFlag = false
|
|
this.getDataList()
|
|
} else this.$message.error(data.msg || '分配失败')
|
|
}).catch(() => {
|
|
this.setUp.assignButton = false
|
|
this.$message.error('分配失败')
|
|
})
|
|
},
|
|
submitNodeReport() {
|
|
if (!this.reportData.nodeCode) return this.$message.warning('请选择报工节点')
|
|
this.setUp.reportButton = true
|
|
reportRenovationOrderNode(this.reportData).then(({data}) => {
|
|
this.setUp.reportButton = false
|
|
if (data && data.code === 0) {
|
|
this.$message.success(data.msg || '报工成功')
|
|
this.setUp.reportFlag = false
|
|
this.getDataList()
|
|
} else this.$message.error(data.msg || '报工失败')
|
|
}).catch(() => {
|
|
this.setUp.reportButton = false
|
|
this.simulateNodeReport(this.reportData.orderNo, this.reportData.nodeCode)
|
|
this.setUp.reportFlag = false
|
|
this.$message.success('后端未完成,已在前端演示节点报工')
|
|
})
|
|
},
|
|
simulateNodeReport(orderNo, nodeCode) {
|
|
const row = this.dataList.find(item => item.orderNo === orderNo)
|
|
if (!row) return
|
|
const node = row.nodeList.find(item => item.nodeCode === nodeCode)
|
|
if (!node) return
|
|
node.status = '已完成'
|
|
row.status = '进行中'
|
|
const nextNode = row.nodeList.find(item => item.status !== '已完成')
|
|
row.currentNode = nextNode ? nextNode.nodeName : '全部完成'
|
|
row.nodeDoneCount = row.nodeList.filter(item => item.status === '已完成').length
|
|
row.finishDate = ''
|
|
},
|
|
finishOrder(row) {
|
|
finishRenovationOrder({ orderNo: row.orderNo }).then(({data}) => {
|
|
if (data && data.code === 0) {
|
|
this.$message.success(data.msg || '完工成功')
|
|
this.getDataList()
|
|
} else this.$message.error(data.msg || '完工失败')
|
|
}).catch(() => {
|
|
row.status = '已完成'
|
|
row.finishDate = this.dayjs().format('YYYY-MM-DD')
|
|
row.nodeList = row.nodeList.map(item => ({ ...item, status: '已完成' }))
|
|
row.currentNode = '全部完成'
|
|
row.nodeDoneCount = row.nodeTotalCount
|
|
this.$message.success('后端未完成,已在前端演示完工')
|
|
})
|
|
},
|
|
deleteOrder(row) {
|
|
this.$confirm('确定删除该改造订单吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
|
|
deleteRenovationOrder({ orderNo: row.orderNo }).then(({data}) => {
|
|
if (data && data.code === 0) {
|
|
this.$message.success(data.msg || '删除成功')
|
|
this.getDataList()
|
|
} else this.$message.error(data.msg || '删除失败')
|
|
}).catch(() => {
|
|
this.dataList = this.dataList.filter(item => item.orderNo !== row.orderNo)
|
|
this.totalPage = this.dataList.length
|
|
this.$message.success('后端未完成,已在前端演示删除')
|
|
})
|
|
}).catch(() => {})
|
|
},
|
|
getStatusType(status) {
|
|
const map = { 已排产: 'info', 进行中: 'warning', 已完成: 'success' }
|
|
return map[status] || 'info'
|
|
},
|
|
getStageClass(status) {
|
|
if (status === '已完成') return 'done'
|
|
if (status === '进行中') return 'processing'
|
|
return 'pending'
|
|
},
|
|
getStageIcon(status) {
|
|
if (status === '已完成') return 'el-icon-check'
|
|
if (status === '进行中') return 'el-icon-loading'
|
|
return 'el-icon-time'
|
|
},
|
|
getStageTagType(status) {
|
|
if (status === '已完成') return 'success'
|
|
if (status === '进行中') return 'warning'
|
|
return 'info'
|
|
},
|
|
sizeChangeHandle(val) {
|
|
this.pageSize = val
|
|
this.pageIndex = 1
|
|
this.getDataList()
|
|
},
|
|
currentChangeHandle(val) {
|
|
this.pageIndex = val
|
|
this.getDataList()
|
|
}
|
|
},
|
|
computed: {
|
|
selectedOrderNodeList() {
|
|
return (this.selectedOrder && this.selectedOrder.nodeList) ? this.selectedOrder.nodeList : []
|
|
},
|
|
selectedOrderProgressPercent() {
|
|
const total = this.selectedOrderNodeList.length
|
|
if (!total) {
|
|
return 0
|
|
}
|
|
const done = this.selectedOrderNodeList.filter(item => item.status === '已完成').length
|
|
return Math.round((done * 100) / total)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.data-table {
|
|
background-color: #fff;
|
|
border-radius: 4px;
|
|
}
|
|
.data-table >>> .cell {
|
|
line-height: 20px;
|
|
height: 20px;
|
|
}
|
|
.data-table >>> .el-table__header-wrapper th,
|
|
.data-table >>> .el-table__fixed-header-wrapper th {
|
|
background-color: #f5f7fa !important;
|
|
color: #333;
|
|
font-weight: 600;
|
|
border-color: #ebeef5;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.data-table >>> .el-table__header-wrapper .cell,
|
|
.data-table >>> .el-table__fixed-header-wrapper .cell,
|
|
.data-table >>> .el-table__body-wrapper .cell,
|
|
.data-table >>> .el-table__fixed-body-wrapper .cell {
|
|
padding: 0 10px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-size: 13px !important;
|
|
}
|
|
|
|
.data-table >>> .el-table__body tr:hover > td {
|
|
background-color: #f5f7fa !important;
|
|
}
|
|
|
|
.data-table >>> .el-table__body tr.current-row > td {
|
|
background-color: #ecf5ff !important;
|
|
}
|
|
|
|
.query-form {
|
|
background-color: #fff;
|
|
padding: 15px 15px 5px 15px;
|
|
border-radius: 4px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.query-form >>> .el-form-item__label {
|
|
color: #333;
|
|
font-size: 13px;
|
|
padding-bottom: 5px;
|
|
}
|
|
|
|
.query-form >>> .el-input__inner {
|
|
height: 32px;
|
|
line-height: 32px;
|
|
border-radius: 4px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.query-form >>> .el-button {
|
|
height: 32px;
|
|
padding: 0 15px;
|
|
font-size: 13px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.search-btn {
|
|
background-color: #ecf5ff;
|
|
border-color: #b3d8ff;
|
|
color: #409eff;
|
|
}
|
|
|
|
.search-btn:hover {
|
|
background-color: #409eff;
|
|
border-color: #409eff;
|
|
color: #fff;
|
|
}
|
|
|
|
.reset-btn {
|
|
background-color: #f5f7fa;
|
|
border-color: #d3d4d6;
|
|
color: #606266;
|
|
}
|
|
|
|
.reset-btn:hover {
|
|
background-color: #909399;
|
|
border-color: #909399;
|
|
color: #fff;
|
|
}
|
|
|
|
.add-btn {
|
|
background-color: #f0f9eb;
|
|
border-color: #c2e7b0;
|
|
color: #67c23a;
|
|
}
|
|
|
|
.add-btn:hover {
|
|
background-color: #67c23a;
|
|
border-color: #67c23a;
|
|
color: #fff;
|
|
}
|
|
|
|
.dialog-footer {
|
|
text-align: right;
|
|
}
|
|
|
|
.edit-form {
|
|
margin-left: 5px;
|
|
margin-top: -5px;
|
|
}
|
|
|
|
.detail-tabs-wrap {
|
|
margin-top: 12px;
|
|
background-color: #fff;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.detail-table {
|
|
width: 100%;
|
|
}
|
|
|
|
.detail-table >>> .el-table__header-wrapper th,
|
|
.detail-table >>> .el-table__fixed-header-wrapper th {
|
|
background-color: #f5f7fa !important;
|
|
color: #333;
|
|
font-weight: 600;
|
|
border-color: #ebeef5;
|
|
}
|
|
|
|
.two-column-layout {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.stages-column {
|
|
width: 38%;
|
|
min-width: 320px;
|
|
border: 1px solid #ebeef5;
|
|
border-radius: 4px;
|
|
background: #fff;
|
|
}
|
|
|
|
.logs-column {
|
|
flex: 1;
|
|
border: 1px solid #ebeef5;
|
|
border-radius: 4px;
|
|
background: #fff;
|
|
}
|
|
|
|
.column-header {
|
|
height: 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 0 12px;
|
|
border-bottom: 1px solid #ebeef5;
|
|
font-weight: 600;
|
|
color: #303133;
|
|
}
|
|
|
|
.progress-badge,
|
|
.logs-count {
|
|
margin-left: auto;
|
|
color: #409eff;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.stages-list {
|
|
max-height: 295px;
|
|
overflow-y: auto;
|
|
padding: 10px;
|
|
}
|
|
|
|
.stage-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
padding: 3px 8px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.stage-item + .stage-item {
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.stage-item.stage-done {
|
|
background: #f0f9eb;
|
|
}
|
|
|
|
.stage-item.stage-processing {
|
|
background: #fdf6ec;
|
|
}
|
|
|
|
.stage-item.stage-pending {
|
|
background: #f5f7fa;
|
|
}
|
|
|
|
.stage-icon {
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 13px;
|
|
background: #fff;
|
|
border: 1px solid #dcdfe6;
|
|
color: #606266;
|
|
}
|
|
|
|
.stage-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.stage-name {
|
|
font-size: 13px;
|
|
color: #303133;
|
|
line-height: 20px;
|
|
}
|
|
|
|
.stage-meta {
|
|
margin-top: 6px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.stage-owner {
|
|
color: #606266;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.logs-table-wrapper {
|
|
padding: 8px;
|
|
}
|
|
|
|
.media-dialog-body {
|
|
min-height: 360px;
|
|
}
|
|
|
|
.media-dialog-meta {
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
color: #606266;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.media-thumb-cell {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.media-thumb-loading {
|
|
color: #909399;
|
|
font-size: 12px;
|
|
}
|
|
|
|
|
|
.media-thumb {
|
|
width: 100px;
|
|
height: 55px;
|
|
border-radius: 4px;
|
|
border: 1px solid #ebeef5;
|
|
object-fit: cover;
|
|
background: #000;
|
|
cursor: zoom-in;
|
|
}
|
|
|
|
.media-thumb:hover {
|
|
border-color: #409eff;
|
|
}
|
|
|
|
.media-preview-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 3000;
|
|
background: rgba(0, 0, 0, 0.85);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 24px;
|
|
}
|
|
|
|
.media-preview-close {
|
|
position: absolute;
|
|
right: 24px;
|
|
top: 24px;
|
|
color: #fff;
|
|
font-size: 24px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.media-preview-image {
|
|
max-width: calc(100vw - 60px);
|
|
max-height: calc(100vh - 60px);
|
|
object-fit: contain;
|
|
cursor: zoom-out;
|
|
}
|
|
|
|
.media-preview-video {
|
|
max-width: calc(100vw - 60px);
|
|
max-height: calc(100vh - 60px);
|
|
background: #000;
|
|
}
|
|
|
|
.el-icon-check {
|
|
color: #67c23a;
|
|
font-weight: 1000;
|
|
}
|
|
|
|
.file-table {
|
|
background-color: #fff;
|
|
border-radius: 4px;
|
|
}
|
|
.file-table >>> .cell {
|
|
line-height: 55px;
|
|
height: 55px;
|
|
}
|
|
.file-table >>> .el-table__header-wrapper th,
|
|
.file-table >>> .el-table__fixed-header-wrapper th {
|
|
background-color: #f5f7fa !important;
|
|
color: #333;
|
|
font-weight: 600;
|
|
border-color: #ebeef5;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.file-table >>> .el-table__header-wrapper .cell,
|
|
.file-table >>> .el-table__fixed-header-wrapper .cell{
|
|
padding: 0 10px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-size: 13px !important;
|
|
line-height: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
.file-table >>> .el-table__body tr:hover > td {
|
|
background-color: #f5f7fa !important;
|
|
}
|
|
|
|
.file-table >>> .el-table__body tr.current-row > td {
|
|
background-color: #ecf5ff !important;
|
|
}
|
|
</style>
|