Zimo пре 21 часа
родитељ
комит
d06799c69c

+ 3 - 3
src/views/pms/operation-meeting/components/operation-meeting-content.vue

@@ -417,16 +417,16 @@ const visible = ref(false)
 
     <section class="meeting-support-panel">
       <el-form-item
-        label="需集团协调支持的事项"
+        label="其他重点事项及集团协调事项"
         prop="support"
-        class="meeting-form-item mb-0! min-w-0"
+        class="meeting-form-item min-w-0"
       >
         <el-input
           v-model="supportModel"
           type="textarea"
           :rows="4"
           :disabled="type === 'view'"
-          placeholder="请输入需集团协调支持的事项"
+          placeholder="请输入其他重点事项及集团协调事项"
         />
       </el-form-item>
     </section>

+ 1 - 1
src/views/pms/operation-meeting/meeting-form.vue

@@ -136,7 +136,7 @@ const operationMeetingRules = reactive<FormRules>({
       trigger: ['blur', 'change']
     }
   ],
-  support: requiredTextRule('请输入需集团协调支持的事项')
+  support: requiredTextRule('请输入其他重点事项及集团协调事项')
 })
 
 const handleAddDetailItem = () => {

+ 788 - 19
src/views/pms/operation-meeting/summary-form.vue

@@ -1,8 +1,9 @@
 <script lang="ts" setup>
+import type { CSSProperties } from 'vue'
 import type { DeptOption, DetailItem, OperationMeeting } from './types'
 import MeetingDetailDrawer from './components/meeting-detail-drawer.vue'
-import OperationMeetingContent from './components/operation-meeting-content.vue'
 import { OperationMeetingApi } from '@/api/pms/meeting'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 import { useWindowSize } from '@vueuse/core'
 
 interface Props {
@@ -24,6 +25,32 @@ interface SummaryMeetingItem {
   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>(), {
   meetingSeries: '',
   year: '',
@@ -34,6 +61,7 @@ const emits = defineEmits<{
   'update:visible': [visible: boolean]
 }>()
 
+const { ZmTable, ZmTableColumn } = useTableComponents<SummaryDetailItem>()
 const { width } = useWindowSize()
 const drawerSize = computed(() => (width.value <= 768 ? '100%' : '100%'))
 
@@ -41,6 +69,90 @@ const loading = ref(false)
 const summaryMeetings = ref<SummaryMeetingItem[]>([])
 const detailDrawerVisible = ref(false)
 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 {
   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 = () => {
   summaryMeetings.value = []
+  filterForm.companyFilterValue = ''
   loading.value = false
   detailDrawerVisible.value = false
   detailForm.value = createDetailItem()
@@ -169,12 +409,13 @@ const loadSummaryDetails = async () => {
     summaryMeetings.value = meetings.map((item, index) =>
       normalizeSummaryMeeting(item as Record<string, unknown>, index)
     )
+    filterForm.companyFilterValue = ''
   } finally {
     loading.value = false
   }
 }
 
-const handleViewDetail = (row: DetailItem) => {
+const handleViewDetail = (row: SummaryDetailItem) => {
   detailForm.value = cloneDetailItem(row)
   detailDrawerVisible.value = true
 }
@@ -208,23 +449,192 @@ watch(
     <div v-loading="loading" class="summary-form">
       <template v-if="summaryMeetings.length">
         <el-form
-          v-for="(item, index) in summaryMeetings"
-          :key="item.key"
           label-width="auto"
           label-position="top"
           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>
       </template>
 
@@ -250,11 +660,370 @@ watch(
   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>