liding
2026-06-24 ec226b03fbd26bc178137ad20d7bd030ef2c5c8b
src/views/productionManagement/productStructure/DetailNew/MaterialCard.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,236 @@
<template>
  <div class="material-node">
    <!-- å½“前节点卡片 -->
    <div :class="['node-card', isRoot ? 'root-card' : (row.nodeType === 'semiFinished' ? 'semi-finished-card' : 'child-card')]">
      <div class="node-header">
        <div class="node-label">
          <el-tag :type="isRoot ? '' : (row.nodeType === 'semiFinished' ? 'warning' : 'success')" size="small" effect="dark">
            {{ isRoot ? '成品' : (row.nodeType === 'semiFinished' ? '半成品' : '原料') }}
          </el-tag>
          <span class="node-title">{{ row.productName || '未选择产品' }}</span>
          <span v-if="row.model" class="node-sub">规格: {{ row.model }}</span>
          <span v-if="row.unit" class="node-sub">单位: {{ row.unit }}</span>
        </div>
        <div class="node-actions">
          <template v-if="editable && (isRoot || row.nodeType === 'semiFinished')">
            <el-button type="primary"
                       text
                       size="small"
                       @click="handleAdd('semiFinished')">
              + æ·»åŠ åŠæˆå“
            </el-button>
            <el-button type="primary"
                       text
                       size="small"
                       @click="handleAdd('rawMaterial')">
              + æ·»åŠ åŽŸæ–™
            </el-button>
          </template>
          <el-button v-if="editable"
                     type="danger"
                     text
                     size="small"
                     @click="$emit('remove', row.tempId)">
            åˆ é™¤
          </el-button>
        </div>
      </div>
      <!-- ç¼–辑模式下的表单 -->
      <div v-if="editable" class="node-body">
        <el-row :gutter="12">
          <el-col :span="7">
            <el-form-item label="产品" :rules="[{ required: true, message: '请选择产品' }]" style="margin:0">
              <el-input :model-value="row.productName || ''"
                        readonly
                        placeholder="点击选择产品"
                        @click="openSelect"
                        style="width:100%">
                <template #suffix>
                  <el-icon><component :is="SearchIcon" /></el-icon>
                </template>
              </el-input>
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <el-form-item label="规格" style="margin:0">
              <el-select v-model="row.model"
                         placeholder="请选择规格"
                         clearable
                         style="width:100%"
                         @visible-change="(v:boolean) => { if (v) openSelect() }">
                <el-option v-if="row.model" :label="row.model" :value="row.model" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col v-if="!isRoot" :span="5">
            <el-form-item label="工序" :rules="[{ required: true, message: '请选择工序' }]" style="margin:0">
              <el-select v-model="row.processId"
                         placeholder="请选择"
                         filterable
                         clearable
                         style="width:100%"
                         @change="(v:any) => $emit('processChange', row, v)">
                <el-option v-for="item in processOptions"
                           :key="item.id"
                           :label="item.name"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="数量" :rules="[{ required: true, message: '请填写数量' }]" style="margin:0">
              <el-input-number v-model="row.unitQuantity"
                               :min="0"
                               :precision="2"
                               :step="1"
                               controls-position="right"
                               style="width:100%"
                               @change="$emit('quantityChange')" />
            </el-form-item>
          </el-col>
          <el-col :span="3">
            <el-form-item label="单位" style="margin:0">
              <el-input v-model="row.unit" placeholder="单位" clearable style="width:100%" />
            </el-form-item>
          </el-col>
        </el-row>
      </div>
      <!-- éžç¼–辑模式:简洁显示 -->
      <div v-else class="node-view">
        <span v-if="!isRoot && row.processName">工序: {{ row.processName }} | </span>
        <span>数量: {{ row.unitQuantity || '-' }}</span>
      </div>
    </div>
    <!-- é€’归渲染子节点 -->
    <div v-if="row.children && row.children.length > 0" class="node-children">
      <MaterialCard
        v-for="child in row.children"
        :key="child.tempId"
        :row="child"
        :depth="depth + 1"
        :editable="editable"
        :process-options="processOptions"
        @remove="(id:string) => $emit('remove', id)"
        @add="(id:string, nodeType:string) => $emit('add', id, nodeType)"
        @select-product="(tempId: string, data: any) => $emit('selectProduct', tempId, data)"
        @process-change="(row: any, v: any) => $emit('processChange', row, v)"
        @quantity-change="$emit('quantityChange')"
      />
    </div>
  </div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Search } from '@element-plus/icons-vue'
const SearchIcon = Search
const props = defineProps<{
  row: any
  depth: number
  editable: boolean
  processOptions: any[]
}>()
const emit = defineEmits<{
  remove: [tempId: string]
  add: [tempId: string, nodeType: string]
  selectProduct: [tempId: string, data: any]
  processChange: [row: any, value: any]
  quantityChange: []
}>()
const isRoot = computed(() => props.depth === 0)
const openSelect = () => {
  emit('selectProduct', props.row.tempId, null)
}
const handleAdd = (nodeType: string) => {
  emit('add', props.row.tempId, nodeType)
}
</script>
<script lang="ts">
export default { name: 'MaterialCard' }
</script>
<style scoped>
.material-node {
  margin: 4px 0;
}
.node-card {
  border: 1px solid #e4e7ed;
  border-radius: 8px;
  overflow: hidden;
}
.root-card {
  border-color: #409eff;
  border-left: 4px solid #409eff;
  background-color: #f0f5ff;
}
.child-card {
  border-left: 4px solid #67c23a;
  background-color: #f0f9eb;
}
.semi-finished-card {
  border-left: 4px solid #e6a23c;
  background-color: #fdf6ec;
}
.node-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  background-color: rgba(0,0,0,0.03);
  flex-wrap: wrap;
  gap: 4px;
}
.node-label {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.node-title {
  font-weight: 600;
  color: #303133;
}
.node-sub {
  font-size: 12px;
  color: #909399;
}
.node-actions {
  display: flex;
  gap: 4px;
}
.node-body {
  padding: 10px 12px;
}
.node-view {
  padding: 6px 12px;
  font-size: 13px;
  color: #606266;
}
.node-children {
  margin-left: 36px;
  padding-left: 16px;
  border-left: 2px dashed #dcdfe6;
}
</style>