|
@@ -1,8 +1,9 @@
|
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
|
|
|
+import type { CSSProperties } from 'vue'
|
|
|
import type { DeptOption, DetailItem, OperationMeeting } from './types'
|
|
import type { DeptOption, DetailItem, OperationMeeting } from './types'
|
|
|
import MeetingDetailDrawer from './components/meeting-detail-drawer.vue'
|
|
import MeetingDetailDrawer from './components/meeting-detail-drawer.vue'
|
|
|
-import OperationMeetingContent from './components/operation-meeting-content.vue'
|
|
|
|
|
import { OperationMeetingApi } from '@/api/pms/meeting'
|
|
import { OperationMeetingApi } from '@/api/pms/meeting'
|
|
|
|
|
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
|
|
|
import { useWindowSize } from '@vueuse/core'
|
|
import { useWindowSize } from '@vueuse/core'
|
|
|
|
|
|
|
|
interface Props {
|
|
interface Props {
|
|
@@ -24,6 +25,32 @@ interface SummaryMeetingItem {
|
|
|
details: DetailItem[]
|
|
details: DetailItem[]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+interface SummaryDetailItem extends DetailItem {
|
|
|
|
|
+ summaryDetailKey: string
|
|
|
|
|
+ meetingKey: string
|
|
|
|
|
+ companyName: string
|
|
|
|
|
+ companyFilterValue: string
|
|
|
|
|
+ deptId?: number
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface CompanyFilterOption {
|
|
|
|
|
+ label: string
|
|
|
|
|
+ value: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface DetailCardField {
|
|
|
|
|
+ label: string
|
|
|
|
|
+ prop: keyof DetailItem
|
|
|
|
|
+ unit?: string
|
|
|
|
|
+ numeric?: boolean
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface MeetingTableCellStyleProps {
|
|
|
|
|
+ column: {
|
|
|
|
|
+ property?: string
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
meetingSeries: '',
|
|
meetingSeries: '',
|
|
|
year: '',
|
|
year: '',
|
|
@@ -34,6 +61,7 @@ const emits = defineEmits<{
|
|
|
'update:visible': [visible: boolean]
|
|
'update:visible': [visible: boolean]
|
|
|
}>()
|
|
}>()
|
|
|
|
|
|
|
|
|
|
+const { ZmTable, ZmTableColumn } = useTableComponents<SummaryDetailItem>()
|
|
|
const { width } = useWindowSize()
|
|
const { width } = useWindowSize()
|
|
|
const drawerSize = computed(() => (width.value <= 768 ? '100%' : '100%'))
|
|
const drawerSize = computed(() => (width.value <= 768 ? '100%' : '100%'))
|
|
|
|
|
|
|
@@ -41,6 +69,90 @@ const loading = ref(false)
|
|
|
const summaryMeetings = ref<SummaryMeetingItem[]>([])
|
|
const summaryMeetings = ref<SummaryMeetingItem[]>([])
|
|
|
const detailDrawerVisible = ref(false)
|
|
const detailDrawerVisible = ref(false)
|
|
|
const detailForm = ref<DetailItem>(createDetailItem())
|
|
const detailForm = ref<DetailItem>(createDetailItem())
|
|
|
|
|
+const filterForm = reactive({
|
|
|
|
|
+ companyFilterValue: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const detailSummaryFields = [
|
|
|
|
|
+ 'currentRevenue',
|
|
|
|
|
+ 'cumulativeRevenue',
|
|
|
|
|
+ 'currentOnAccount',
|
|
|
|
|
+ 'cumulativeOnAccount',
|
|
|
|
|
+ 'currentPayment',
|
|
|
|
|
+ 'cumulativePayment'
|
|
|
|
|
+] as const
|
|
|
|
|
+
|
|
|
|
|
+type DetailSummaryField = (typeof detailSummaryFields)[number]
|
|
|
|
|
+
|
|
|
|
|
+const detailSummaryLabelMap: Record<DetailSummaryField, string> = {
|
|
|
|
|
+ currentRevenue: '收入-本期',
|
|
|
|
|
+ cumulativeRevenue: '收入-累计',
|
|
|
|
|
+ currentOnAccount: '挂帐-本期',
|
|
|
|
|
+ cumulativeOnAccount: '挂帐-累计',
|
|
|
|
|
+ currentPayment: '回款-本期',
|
|
|
|
|
+ cumulativePayment: '回款-累计'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const detailSummaryToneMap: Record<DetailSummaryField, 'revenue' | 'account' | 'payment'> = {
|
|
|
|
|
+ currentRevenue: 'revenue',
|
|
|
|
|
+ cumulativeRevenue: 'revenue',
|
|
|
|
|
+ currentOnAccount: 'account',
|
|
|
|
|
+ cumulativeOnAccount: 'account',
|
|
|
|
|
+ currentPayment: 'payment',
|
|
|
|
|
+ cumulativePayment: 'payment'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const detailSummaryIconMap: Record<DetailSummaryField, string> = {
|
|
|
|
|
+ currentRevenue: 'i-lucide:badge-japanese-yen',
|
|
|
|
|
+ cumulativeRevenue: 'i-lucide:badge-japanese-yen',
|
|
|
|
|
+ currentOnAccount: 'i-lucide:badge-alert',
|
|
|
|
|
+ cumulativeOnAccount: 'i-lucide:badge-alert',
|
|
|
|
|
+ currentPayment: 'i-lucide:badge-check',
|
|
|
|
|
+ cumulativePayment: 'i-lucide:badge-check'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const detailCardGroups: { title: string; fields: DetailCardField[] }[] = [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '经营情况',
|
|
|
|
|
+ fields: [
|
|
|
|
|
+ { label: '收入-本期', prop: 'currentRevenue', unit: '万元', numeric: true },
|
|
|
|
|
+ { label: '收入-累计', prop: 'cumulativeRevenue', unit: '万元', numeric: true },
|
|
|
|
|
+ { label: '挂帐-本期', prop: 'currentOnAccount', unit: '万元', numeric: true },
|
|
|
|
|
+ { label: '挂帐-累计', prop: 'cumulativeOnAccount', unit: '万元', numeric: true },
|
|
|
|
|
+ { label: '回款-本期', prop: 'currentPayment', unit: '万元', numeric: true },
|
|
|
|
|
+ { label: '回款-累计', prop: 'cumulativePayment', unit: '万元', numeric: true }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '本期生产运行情况',
|
|
|
|
|
+ fields: [
|
|
|
|
|
+ { label: '计划工作量', prop: 'plannedWorkload' },
|
|
|
|
|
+ { label: '实际完成', prop: 'actualCompletion' },
|
|
|
|
|
+ { label: '设备利用率', prop: 'equipmentUtilizationRate', unit: '%', numeric: true }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '生产管理情况及重点工作',
|
|
|
|
|
+ fields: [
|
|
|
|
|
+ { label: '重点工作及完成情况', prop: 'keyWorkCompletion' },
|
|
|
|
|
+ { label: '存在问题及分析', prop: 'problemsAnalysis' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '下期工作计划',
|
|
|
|
|
+ fields: [
|
|
|
|
|
+ { label: '计划工作量', prop: 'nextPlannedWorkload' },
|
|
|
|
|
+ { label: '重点工作事项', prop: 'priorityTasks' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const meetingTableBlueColumns = new Set<keyof DetailItem>([
|
|
|
|
|
+ 'projectName',
|
|
|
|
|
+ 'actualCompletion',
|
|
|
|
|
+ 'keyWorkCompletion',
|
|
|
|
|
+ 'problemsAnalysis'
|
|
|
|
|
+])
|
|
|
|
|
|
|
|
function createDetailItem(): DetailItem {
|
|
function createDetailItem(): DetailItem {
|
|
|
return {
|
|
return {
|
|
@@ -133,8 +245,136 @@ const normalizeSummaryMeeting = (
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const getCompanyDisplayName = (meeting: OperationMeetingForm) => {
|
|
|
|
|
+ if (meeting.companyName) return meeting.companyName
|
|
|
|
|
+
|
|
|
|
|
+ if (meeting.deptId) {
|
|
|
|
|
+ return props.deptOptions.find((item) => item.value === meeting.deptId)?.label || ''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const getCompanyFilterValue = (meeting: OperationMeetingForm, index: number) => {
|
|
|
|
|
+ if (meeting.deptId !== undefined) return `dept:${meeting.deptId}`
|
|
|
|
|
+
|
|
|
|
|
+ const companyName = getCompanyDisplayName(meeting).trim()
|
|
|
|
|
+
|
|
|
|
|
+ return companyName ? `company:${companyName}` : `unknown:${index}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const summaryMeetingMeta = computed<OperationMeetingForm>(
|
|
|
|
|
+ () => summaryMeetings.value[0]?.meeting || {}
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const companyOptions = computed<CompanyFilterOption[]>(() => {
|
|
|
|
|
+ const optionMap = new Map<string, CompanyFilterOption>()
|
|
|
|
|
+
|
|
|
|
|
+ summaryMeetings.value.forEach((item, index) => {
|
|
|
|
|
+ const value = getCompanyFilterValue(item.meeting, index)
|
|
|
|
|
+
|
|
|
|
|
+ if (optionMap.has(value)) return
|
|
|
|
|
+
|
|
|
|
|
+ optionMap.set(value, {
|
|
|
|
|
+ value,
|
|
|
|
|
+ label: getCompanyDisplayName(item.meeting) || '未知公司'
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ return Array.from(optionMap.values())
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const summaryDetailRows = computed<SummaryDetailItem[]>(() =>
|
|
|
|
|
+ summaryMeetings.value.flatMap((item, meetingIndex) => {
|
|
|
|
|
+ const companyFilterValue = getCompanyFilterValue(item.meeting, meetingIndex)
|
|
|
|
|
+ const companyName = getCompanyDisplayName(item.meeting) || '未知公司'
|
|
|
|
|
+
|
|
|
|
|
+ return item.details.map((detail, detailIndex) => ({
|
|
|
|
|
+ ...detail,
|
|
|
|
|
+ summaryDetailKey: `${item.key}-${detailIndex}`,
|
|
|
|
|
+ meetingKey: item.key,
|
|
|
|
|
+ companyName,
|
|
|
|
|
+ companyFilterValue,
|
|
|
|
|
+ deptId: item.meeting.deptId
|
|
|
|
|
+ }))
|
|
|
|
|
+ })
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const filteredDetailRows = computed(() => {
|
|
|
|
|
+ if (!filterForm.companyFilterValue) return summaryDetailRows.value
|
|
|
|
|
+
|
|
|
|
|
+ return summaryDetailRows.value.filter(
|
|
|
|
|
+ (item) => item.companyFilterValue === filterForm.companyFilterValue
|
|
|
|
|
+ )
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const currentSummaryScopeName = computed(
|
|
|
|
|
+ () =>
|
|
|
|
|
+ companyOptions.value.find((item) => item.value === filterForm.companyFilterValue)?.label ||
|
|
|
|
|
+ '全部公司'
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const formatSummaryNumber = (value: number) =>
|
|
|
|
|
+ value.toLocaleString('zh-CN', {
|
|
|
|
|
+ maximumFractionDigits: 2,
|
|
|
|
|
+ minimumFractionDigits: Number.isInteger(value) ? 0 : 2
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+const getDetailSummaryTotal = (field: DetailSummaryField) =>
|
|
|
|
|
+ filteredDetailRows.value.reduce((sum, item) => sum + Number(item[field] || 0), 0)
|
|
|
|
|
+
|
|
|
|
|
+const detailSummaryCards = computed(() =>
|
|
|
|
|
+ detailSummaryFields.map((field) => ({
|
|
|
|
|
+ label: detailSummaryLabelMap[field],
|
|
|
|
|
+ value: formatSummaryNumber(getDetailSummaryTotal(field)),
|
|
|
|
|
+ tone: detailSummaryToneMap[field],
|
|
|
|
|
+ icon: detailSummaryIconMap[field]
|
|
|
|
|
+ }))
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const formatDetailCardValue = (item: SummaryDetailItem, field: DetailCardField) => {
|
|
|
|
|
+ const value = item[field.prop]
|
|
|
|
|
+
|
|
|
|
|
+ if (value === undefined || value === null || value === '') {
|
|
|
|
|
+ return '-'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!field.numeric) {
|
|
|
|
|
+ return String(value)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const numericValue = Number(value)
|
|
|
|
|
+
|
|
|
|
|
+ if (Number.isNaN(numericValue)) {
|
|
|
|
|
+ return '-'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return `${formatSummaryNumber(numericValue)}${field.unit || ''}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const formatEquipmentUtilizationRate = (row: SummaryDetailItem) => {
|
|
|
|
|
+ if (row.equipmentUtilizationRate === undefined || row.equipmentUtilizationRate === null) {
|
|
|
|
|
+ return '-'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return `${formatSummaryNumber(Number(row.equipmentUtilizationRate))}%`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const getMeetingTableCellStyle: any = ({
|
|
|
|
|
+ column
|
|
|
|
|
+}: MeetingTableCellStyleProps): CSSProperties | undefined => {
|
|
|
|
|
+ const property = column.property as keyof DetailItem | undefined
|
|
|
|
|
+
|
|
|
|
|
+ if (property && meetingTableBlueColumns.has(property)) {
|
|
|
|
|
+ return { color: '#1b71f6' }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return undefined
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
const resetForm = () => {
|
|
const resetForm = () => {
|
|
|
summaryMeetings.value = []
|
|
summaryMeetings.value = []
|
|
|
|
|
+ filterForm.companyFilterValue = ''
|
|
|
loading.value = false
|
|
loading.value = false
|
|
|
detailDrawerVisible.value = false
|
|
detailDrawerVisible.value = false
|
|
|
detailForm.value = createDetailItem()
|
|
detailForm.value = createDetailItem()
|
|
@@ -169,12 +409,13 @@ const loadSummaryDetails = async () => {
|
|
|
summaryMeetings.value = meetings.map((item, index) =>
|
|
summaryMeetings.value = meetings.map((item, index) =>
|
|
|
normalizeSummaryMeeting(item as Record<string, unknown>, index)
|
|
normalizeSummaryMeeting(item as Record<string, unknown>, index)
|
|
|
)
|
|
)
|
|
|
|
|
+ filterForm.companyFilterValue = ''
|
|
|
} finally {
|
|
} finally {
|
|
|
loading.value = false
|
|
loading.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const handleViewDetail = (row: DetailItem) => {
|
|
|
|
|
|
|
+const handleViewDetail = (row: SummaryDetailItem) => {
|
|
|
detailForm.value = cloneDetailItem(row)
|
|
detailForm.value = cloneDetailItem(row)
|
|
|
detailDrawerVisible.value = true
|
|
detailDrawerVisible.value = true
|
|
|
}
|
|
}
|
|
@@ -208,23 +449,192 @@ watch(
|
|
|
<div v-loading="loading" class="summary-form">
|
|
<div v-loading="loading" class="summary-form">
|
|
|
<template v-if="summaryMeetings.length">
|
|
<template v-if="summaryMeetings.length">
|
|
|
<el-form
|
|
<el-form
|
|
|
- v-for="(item, index) in summaryMeetings"
|
|
|
|
|
- :key="item.key"
|
|
|
|
|
label-width="auto"
|
|
label-width="auto"
|
|
|
label-position="top"
|
|
label-position="top"
|
|
|
size="default"
|
|
size="default"
|
|
|
- :model="item.meeting"
|
|
|
|
|
- class="summary-form__meeting"
|
|
|
|
|
|
|
+ :model="filterForm"
|
|
|
|
|
+ class="summary-form__section"
|
|
|
>
|
|
>
|
|
|
- <OperationMeetingContent
|
|
|
|
|
- :meeting="item.meeting"
|
|
|
|
|
- :details="item.details"
|
|
|
|
|
- type="view"
|
|
|
|
|
- :dept-options="deptOptions"
|
|
|
|
|
- :loading="loading"
|
|
|
|
|
- :show-meeting-meta="index === 0"
|
|
|
|
|
- @edit-detail="handleViewDetail"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <div class="meeting-section__top">
|
|
|
|
|
+ <h2 class="meeting-section__title">生产运营双周例会汇报</h2>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="meeting-section__grid">
|
|
|
|
|
+ <el-form-item label="会议期次" label-position="left" class="mb-0! min-w-0">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ :model-value="summaryMeetingMeta.meetingSeries"
|
|
|
|
|
+ class="w-full!"
|
|
|
|
|
+ placeholder="暂无会议期次"
|
|
|
|
|
+ disabled
|
|
|
|
|
+ :controls="false"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item
|
|
|
|
|
+ label="所属公司"
|
|
|
|
|
+ label-position="left"
|
|
|
|
|
+ prop="companyFilterValue"
|
|
|
|
|
+ class="mb-0! min-w-0"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="filterForm.companyFilterValue"
|
|
|
|
|
+ class="w-full!"
|
|
|
|
|
+ placeholder="全部公司"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ filterable
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in companyOptions"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :label="item.label"
|
|
|
|
|
+ :value="item.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="会议日期" label-position="left" class="mb-0! min-w-0">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ :model-value="summaryMeetingMeta.meetingDate"
|
|
|
|
|
+ type="date"
|
|
|
|
|
+ placeholder="暂无会议日期"
|
|
|
|
|
+ disabled
|
|
|
|
|
+ class="w-full!"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <section class="meeting-summary-strip">
|
|
|
|
|
+ <div class="meeting-summary-strip__title">
|
|
|
|
|
+ <span>{{ currentSummaryScopeName }}</span>
|
|
|
|
|
+ <small>经营数据汇总</small>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="meeting-summary-strip__grid">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="item in detailSummaryCards"
|
|
|
|
|
+ :key="item.label"
|
|
|
|
|
+ :class="[
|
|
|
|
|
+ 'meeting-summary-strip__item',
|
|
|
|
|
+ `meeting-summary-strip__item--${item.tone}`
|
|
|
|
|
+ ]"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div :class="item.icon + ' size-5 icon'"></div>
|
|
|
|
|
+ <span>{{ item.label }}</span>
|
|
|
|
|
+ <strong>{{ item.value }}<em>万元</em></strong>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <section class="meeting-detail-panel">
|
|
|
|
|
+ <div class="meeting-detail-panel__header">
|
|
|
|
|
+ <h3 class="text-lg font-bold m-0">会议明细</h3>
|
|
|
|
|
+ <el-tag size="small" effect="plain">共 {{ filteredDetailRows.length }} 项</el-tag>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="meeting-detail-table-view">
|
|
|
|
|
+ <ZmTable
|
|
|
|
|
+ class="meeting-table"
|
|
|
|
|
+ :data="filteredDetailRows"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ :max-height="650"
|
|
|
|
|
+ align="left"
|
|
|
|
|
+ :show-overflow-tooltip="false"
|
|
|
|
|
+ :cell-style="getMeetingTableCellStyle"
|
|
|
|
|
+ >
|
|
|
|
|
+ <zm-table-column
|
|
|
|
|
+ min-width="6%"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ label="所属公司"
|
|
|
|
|
+ prop="companyName"
|
|
|
|
|
+ fixed="left"
|
|
|
|
|
+ />
|
|
|
|
|
+ <zm-table-column
|
|
|
|
|
+ min-width="6%"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ label="项目名称"
|
|
|
|
|
+ prop="projectName"
|
|
|
|
|
+ />
|
|
|
|
|
+ <zm-table-column min-width="24%" label="本期生产运行情况">
|
|
|
|
|
+ <zm-table-column min-width="9%" label="计划工作量" prop="plannedWorkload" />
|
|
|
|
|
+ <zm-table-column min-width="9%" label="实际完成" prop="actualCompletion" />
|
|
|
|
|
+ <zm-table-column
|
|
|
|
|
+ min-width="6%"
|
|
|
|
|
+ label="设备利用率"
|
|
|
|
|
+ prop="equipmentUtilizationRate"
|
|
|
|
|
+ :formatter="formatEquipmentUtilizationRate"
|
|
|
|
|
+ />
|
|
|
|
|
+ </zm-table-column>
|
|
|
|
|
+ <zm-table-column min-width="28%" label="生产管理情况及重点工作">
|
|
|
|
|
+ <zm-table-column
|
|
|
|
|
+ min-width="18%"
|
|
|
|
|
+ label="重点工作及完成情况"
|
|
|
|
|
+ prop="keyWorkCompletion"
|
|
|
|
|
+ />
|
|
|
|
|
+ <zm-table-column min-width="10%" label="存在问题及分析" prop="problemsAnalysis" />
|
|
|
|
|
+ </zm-table-column>
|
|
|
|
|
+ <zm-table-column min-width="27%" label="下期工作计划">
|
|
|
|
|
+ <zm-table-column min-width="9%" label="计划工作量" prop="nextPlannedWorkload" />
|
|
|
|
|
+ <zm-table-column min-width="18%" label="重点工作事项" prop="priorityTasks" />
|
|
|
|
|
+ </zm-table-column>
|
|
|
|
|
+ <zm-table-column label="操作" min-width="5%" align="center" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="meeting-table__actions">
|
|
|
|
|
+ <el-button link size="default" type="primary" @click="handleViewDetail(row)">
|
|
|
|
|
+ 查看
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </zm-table-column>
|
|
|
|
|
+ </ZmTable>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-loading="loading" class="meeting-detail-card-view">
|
|
|
|
|
+ <template v-if="filteredDetailRows.length">
|
|
|
|
|
+ <article
|
|
|
|
|
+ v-for="(item, index) in filteredDetailRows"
|
|
|
|
|
+ :key="item.summaryDetailKey"
|
|
|
|
|
+ class="meeting-detail-card"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="meeting-detail-card__header">
|
|
|
|
|
+ <div class="meeting-detail-card__title">
|
|
|
|
|
+ <span>项目名称</span>
|
|
|
|
|
+ <strong>{{ item.projectName || '-' }}</strong>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-tag size="small" effect="plain">第 {{ index + 1 }} 项</el-tag>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="meeting-detail-card__company">
|
|
|
|
|
+ <span>所属公司</span>
|
|
|
|
|
+ <strong>{{ item.companyName || '-' }}</strong>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <section
|
|
|
|
|
+ v-for="group in detailCardGroups"
|
|
|
|
|
+ :key="group.title"
|
|
|
|
|
+ class="meeting-detail-card__group"
|
|
|
|
|
+ >
|
|
|
|
|
+ <h4>{{ group.title }}</h4>
|
|
|
|
|
+ <div class="meeting-detail-card__fields">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="field in group.fields"
|
|
|
|
|
+ :key="field.prop"
|
|
|
|
|
+ class="meeting-detail-card__field"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span>{{ field.label }}</span>
|
|
|
|
|
+ <strong>{{ formatDetailCardValue(item, field) }}</strong>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="meeting-detail-card__actions">
|
|
|
|
|
+ <el-button link size="small" type="primary" @click="handleViewDetail(item)">
|
|
|
|
|
+ 查看
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </article>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <el-empty v-else description="暂无会议明细" :image-size="80" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -250,11 +660,370 @@ watch(
|
|
|
min-height: 240px;
|
|
min-height: 240px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.summary-form__meeting {
|
|
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
|
|
+.summary-form__section {
|
|
|
|
|
+ padding: 8px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid rgb(229 231 235 / 90%);
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-section__grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
+ gap: 18px 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-section__top {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: max-content minmax(0, 1fr);
|
|
|
|
|
+ gap: 240px;
|
|
|
|
|
+ align-items: start;
|
|
|
|
|
+ margin-bottom: 18px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-section__title {
|
|
|
|
|
+ padding-top: 4px;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ line-height: 32px;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ transform: translateY(-4px);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-form-item__label) {
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-summary-strip {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 112px minmax(0, 1fr);
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ align-items: stretch;
|
|
|
|
|
+ padding: 6px 8px;
|
|
|
|
|
+ background: linear-gradient(180deg, #f8fbff 0%, #fff 100%);
|
|
|
|
|
+ border: 1px solid #dbeafe;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.summary-form__meeting:last-child {
|
|
|
|
|
- margin-bottom: 0;
|
|
|
|
|
|
|
+.meeting-summary-strip__title {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ padding-right: 8px;
|
|
|
|
|
+ border-right: 1px solid #dbeafe;
|
|
|
|
|
+
|
|
|
|
|
+ span {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ line-height: 20px;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ small {
|
|
|
|
|
+ margin-top: 0;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ line-height: 16px;
|
|
|
|
|
+ color: #64748b;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-summary-strip__grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-summary-strip__item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+ padding: 4px 6px;
|
|
|
|
|
+ background: rgb(255 255 255 / 72%);
|
|
|
|
|
+ border: 1px solid rgb(219 234 254 / 70%);
|
|
|
|
|
+ border-radius: 7px;
|
|
|
|
|
+
|
|
|
|
|
+ span {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ line-height: 16px;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ strong {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ align-items: baseline;
|
|
|
|
|
+ gap: 3px;
|
|
|
|
|
+ margin-top: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ line-height: 18px;
|
|
|
|
|
+ color: #1b71f6;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ em {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-style: normal;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ color: #64748b;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-summary-strip__item--revenue strong,
|
|
|
|
|
+.meeting-summary-strip__item--revenue .icon {
|
|
|
|
|
+ color: #1b71f6;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-summary-strip__item--revenue {
|
|
|
|
|
+ background: linear-gradient(180deg, #eff6ff 0%, #fff 100%);
|
|
|
|
|
+ border-color: #bfdbfe;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-summary-strip__item--account strong,
|
|
|
|
|
+.meeting-summary-strip__item--account .icon {
|
|
|
|
|
+ color: #f59e0b;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-summary-strip__item--account {
|
|
|
|
|
+ background: linear-gradient(180deg, #fffbeb 0%, #fff 100%);
|
|
|
|
|
+ border-color: #fde68a;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-summary-strip__item--payment strong,
|
|
|
|
|
+.meeting-summary-strip__item--payment .icon {
|
|
|
|
|
+ color: #10b981;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-summary-strip__item--payment {
|
|
|
|
|
+ background: linear-gradient(180deg, #ecfdf5 0%, #fff 100%);
|
|
|
|
|
+ border-color: #bbf7d0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-panel {
|
|
|
|
|
+ padding-top: 12px;
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+ border-top: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-panel__header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-table {
|
|
|
|
|
+ --zm-table-font-size: 18px;
|
|
|
|
|
+ --zm-table-header-font-size: 18px;
|
|
|
|
|
+ --zm-table-header-font-weight: 800;
|
|
|
|
|
+ --zm-table-row-font-weight: 800;
|
|
|
|
|
+ --zm-table-header-text-color: #333;
|
|
|
|
|
+ --zm-table-border-color: #cbd5e1;
|
|
|
|
|
+ --zm-table-header-border-color: #c2ccda;
|
|
|
|
|
+ --zm-table-row-border-color: #d4dce8;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.header-wrapper) {
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ justify-content: center !important;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+
|
|
|
|
|
+ .truncate {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-table__actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-button) {
|
|
|
|
|
+ margin-left: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-table-view {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card-view {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ padding-bottom: 4px;
|
|
|
|
|
+ background: var(--el-bg-color);
|
|
|
|
|
+ border: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ box-shadow: 0 2px 8px rgb(15 23 42 / 6%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__header,
|
|
|
|
|
+.meeting-detail-card__actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__title {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__title span,
|
|
|
|
|
+.meeting-detail-card__company span,
|
|
|
|
|
+.meeting-detail-card__field span {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__title strong {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ line-height: 1.35;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__company {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__company strong {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+ overflow-wrap: anywhere;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__group {
|
|
|
|
|
+ padding-top: 12px;
|
|
|
|
|
+ border-top: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__group h4 {
|
|
|
|
|
+ margin: 0 0 10px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__fields {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
+ gap: 10px 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__field {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__field strong {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+ overflow-wrap: anywhere;
|
|
|
|
|
+ white-space: pre-wrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.meeting-detail-card__actions {
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ padding-top: 2px;
|
|
|
|
|
+ border-top: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (width <= 960px) {
|
|
|
|
|
+ .meeting-section__top {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .meeting-section__title {
|
|
|
|
|
+ padding-top: 0;
|
|
|
|
|
+ line-height: 24px;
|
|
|
|
|
+ transform: translateY(0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .meeting-section__grid {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .meeting-summary-strip {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .meeting-summary-strip__title {
|
|
|
|
|
+ padding-right: 0;
|
|
|
|
|
+ padding-bottom: 12px;
|
|
|
|
|
+ border-right: 0;
|
|
|
|
|
+ border-bottom: 1px solid #dbeafe;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .meeting-summary-strip__grid {
|
|
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (width <= 768px) {
|
|
|
|
|
+ .meeting-detail-table-view {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .meeting-detail-card-view {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ min-height: 160px;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .meeting-summary-strip__grid,
|
|
|
|
|
+ .meeting-detail-card__fields {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|