feat: 台账品名/材质/规格手工录入与产品编号选填、审批模板启用锁定编辑
已修改5个文件
274 ■■■■■ 文件已修改
src/views/basicData/product/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/useApproveTemplate.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue
@@ -9,8 +9,7 @@
                  @clear="searchFilter"
                  clearable
                  prefix-icon="Search" />
        <el-button v-if="false"
                   type="primary"
        <el-button type="primary"
                   @click="openProDia('addOne')"
                   style="margin-left: 10px">新增产品大类</el-button>
      </div>
@@ -55,7 +54,6 @@
                           style="margin-left: 4px"
                           type="danger"
                           link
                           :disabled="isTopLevelNode(data, node)"
                           @click="remove(node, data)">
                  删除
                </el-button>
@@ -134,7 +132,7 @@
        <el-row>
          <el-row>
            <el-col :span="24">
              <el-form-item label="产品编号:"
              <el-form-item label="产品编号(选填):"
                            prop="productCode">
                <el-input v-model="modelForm.productCode"
                          placeholder="请输入产品编号"
@@ -346,7 +344,6 @@
    modelRules: {
      model: [{ required: true, message: "请输入", trigger: "blur" }],
      unit: [{ required: true, message: "请输入", trigger: "blur" }],
      productCode: [{ required: true, message: "请输入", trigger: "blur" }],
    },
  });
  const { form, rules, modelForm, modelRules } = toRefs(data);
@@ -458,10 +455,6 @@
  // 删除产品
  const remove = (node, data) => {
    if (isTopLevelNode(data, node)) {
      proxy.$modal.msgWarning("一级节点不能编辑或删除");
      return;
    }
    let ids = [];
    ids.push(data.id);
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue
@@ -8,6 +8,7 @@
        placeholder="如:请填写报销事由、金额等"
        maxlength="200"
        show-word-limit
        :disabled="readonly"
        @input="emitOut"
      />
    </div>
@@ -23,11 +24,11 @@
        <div class="fce-toolbar-actions">
          <el-dropdown
            trigger="click"
            :disabled="disableImport"
            :disabled="disableImport || readonly"
            @visible-change="onImportDropdownVisible"
            @command="importFromTemplate"
          >
            <el-button size="small" :loading="templateImportLoading" :disabled="disableImport">
            <el-button size="small" :loading="templateImportLoading" :disabled="disableImport || readonly">
              从已有模板导入
            </el-button>
            <template #dropdown>
@@ -46,7 +47,7 @@
              </el-dropdown-menu>
            </template>
          </el-dropdown>
          <el-button type="primary" size="small" :icon="Plus" @click="addField">添加填报项</el-button>
          <el-button v-if="!readonly" type="primary" size="small" :icon="Plus" @click="addField">添加填报项</el-button>
        </div>
      </div>
@@ -76,7 +77,7 @@
              <el-tag v-if="field.required" size="small" type="danger" effect="plain">必填</el-tag>
              <el-tag v-if="isFieldLocked(field)" size="small" type="info" effect="plain">内置项</el-tag>
            </div>
            <div v-if="!isFieldLocked(field)" class="fce-card-btns">
            <div v-if="!isFieldDisabled(field)" class="fce-card-btns">
              <el-tooltip content="上移" placement="top">
                <el-button circle size="small" :disabled="index === 0" @click="moveField(index, -1)">
                  <el-icon><Top /></el-icon>
@@ -109,7 +110,7 @@
                    v-model="field.label"
                    placeholder="如:报销说明"
                    maxlength="50"
                    :disabled="isFieldLocked(field)"
                    :disabled="isFieldDisabled(field)"
                    @input="emitOut"
                  />
                </el-form-item>
@@ -120,7 +121,7 @@
                    v-model="field.key"
                    placeholder="如:summary"
                    maxlength="50"
                    :disabled="isFieldLocked(field)"
                    :disabled="isFieldDisabled(field)"
                    @input="emitOut"
                  />
                </el-form-item>
@@ -130,7 +131,7 @@
                  <el-select
                    v-model="field.type"
                    style="width: 100%"
                    :disabled="isFieldLocked(field)"
                    :disabled="isFieldDisabled(field)"
                    @change="onTypeChange(field)"
                  >
                    <el-option
@@ -155,7 +156,7 @@
                    inline-prompt
                    active-text="必填"
                    inactive-text="选填"
                    :disabled="isFieldLocked(field)"
                    :disabled="isFieldDisabled(field)"
                    @change="emitOut"
                  />
                </el-form-item>
@@ -168,7 +169,7 @@
                    :max="10"
                    controls-position="right"
                    style="width: 100%"
                    :disabled="isFieldLocked(field)"
                    :disabled="isFieldDisabled(field)"
                    @change="emitOut"
                  />
                </el-form-item>
@@ -180,7 +181,7 @@
                      v-model="field.min"
                      controls-position="right"
                      style="width: 100%"
                      :disabled="isFieldLocked(field)"
                      :disabled="isFieldDisabled(field)"
                      @change="emitOut"
                    />
                  </el-form-item>
@@ -193,7 +194,7 @@
                      :max="4"
                      controls-position="right"
                      style="width: 100%"
                      :disabled="isFieldLocked(field)"
                      :disabled="isFieldDisabled(field)"
                      @change="emitOut"
                    />
                  </el-form-item>
@@ -211,7 +212,7 @@
              :type="field.type === 'textarea' ? 'textarea' : 'text'"
              :rows="field.type === 'textarea' ? 2 : undefined"
              :placeholder="defaultPlaceholder(field)"
              :disabled="isFieldLocked(field)"
              :disabled="isFieldDisabled(field)"
              clearable
              @input="emitOut"
            />
@@ -223,7 +224,7 @@
              controls-position="right"
              placeholder="选填"
              style="width: 100%"
              :disabled="isFieldLocked(field)"
              :disabled="isFieldDisabled(field)"
              @change="emitOut"
            />
            <el-date-picker
@@ -234,7 +235,7 @@
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              style="width: 100%"
              :disabled="isFieldLocked(field)"
              :disabled="isFieldDisabled(field)"
              clearable
              @change="emitOut"
            />
@@ -248,7 +249,7 @@
              format="YYYY-MM-DD HH:mm:ss"
              value-format="YYYY-MM-DD HH:mm:ss"
              style="width: 100%"
              :disabled="isFieldLocked(field)"
              :disabled="isFieldDisabled(field)"
              clearable
              @change="emitOut"
            />
@@ -260,7 +261,7 @@
              clearable
              filterable
              :loading="optionSourceLoading"
              :disabled="isFieldLocked(field)"
              :disabled="isFieldDisabled(field)"
              @change="emitOut"
            >
              <el-option
@@ -280,7 +281,7 @@
                  <el-select
                    v-model="field.optionSource"
                    style="width: 100%"
                    :disabled="isFieldLocked(field)"
                    :disabled="isFieldDisabled(field)"
                    @change="onOptionSourceChange(field)"
                  >
                    <el-option
@@ -304,7 +305,7 @@
                  link
                  size="small"
                  :icon="Plus"
                  :disabled="isFieldLocked(field)"
                  :disabled="isFieldDisabled(field)"
                  @click="addOption(field)"
                >
                  添加选项
@@ -319,21 +320,21 @@
                <el-input
                  v-model="opt.label"
                  placeholder="显示文本"
                  :disabled="isFieldLocked(field)"
                  :disabled="isFieldDisabled(field)"
                  @input="emitOut"
                />
                <el-input
                  v-model="opt.value"
                  placeholder="选项值"
                  class="fce-option-value"
                  :disabled="isFieldLocked(field)"
                  :disabled="isFieldDisabled(field)"
                  @input="emitOut"
                />
                <el-button
                  type="danger"
                  link
                  :icon="Delete"
                  :disabled="isFieldLocked(field) || field.options.length <= 1"
                  :disabled="isFieldDisabled(field) || field.options.length <= 1"
                  @click="removeOption(field, oi)"
                />
              </div>
@@ -382,6 +383,8 @@
  disableImport: { type: Boolean, default: false },
  /** 系统内置模板编辑时,打开弹窗即存在的填报项 _uid,不可改删 */
  lockedFieldUids: { type: Array, default: () => [] },
  /** 只读:整体不可编辑 */
  readonly: { type: Boolean, default: false },
});
const emit = defineEmits(["update:modelValue"]);
@@ -401,6 +404,10 @@
  return field?._uid != null && lockedUidSet.value.has(field._uid);
}
function isFieldDisabled(field) {
  return props.readonly || isFieldLocked(field);
}
function typeLabel(type) {
  return formFieldTypeLabel(type);
}
src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue
@@ -119,7 +119,7 @@
                placeholder="如:项目立项审批"
                maxlength="50"
                show-word-limit
                :disabled="isEditingBuiltin"
                :disabled="isEditFormLocked"
              />
            </el-form-item>
@@ -134,7 +134,7 @@
                v-model="form.businessType"
                placeholder="请选择"
                style="width: 100%"
                :disabled="isEditingBuiltin"
                :disabled="isEditFormLocked"
              >
                <el-option
@@ -183,6 +183,8 @@
            show-word-limit
            :disabled="isEditFormLocked"
          />
        </el-form-item>
@@ -192,7 +194,8 @@
          <FormConfigEditor
            v-model="form.formConfigData"
            :exclude-template-id="form.id"
            :disable-import="isEditingBuiltin"
            :disable-import="isEditFormLocked"
            :readonly="isEditFormLocked"
            :locked-field-uids="isEditingBuiltin ? form.lockedFormFieldUids : []"
          />
@@ -202,7 +205,7 @@
        <el-form-item label="审批流程" required>
          <TemplateFlowEditor v-model="form.flowNodes" :user-options="flowUserOptions" />
          <TemplateFlowEditor v-model="form.flowNodes" :user-options="flowUserOptions" :readonly="isEditFormLocked" />
          <p class="flow-tip">
@@ -216,7 +219,7 @@
          <div class="upload-block">
            <FileUpload v-model:file-list="form.storageBlobDTOs" :limit="10" button-text="点击选择文件" />
            <FileUpload v-model:file-list="form.storageBlobDTOs" :limit="10" button-text="点击选择文件" :disabled="isEditFormLocked" />
          </div>
@@ -472,6 +475,8 @@
  isEditingBuiltin,
  isEditFormLocked,
  detailDialog,
  detailRow,
src/views/officeProcessAutomation/ApproveManage/approve-template/useApproveTemplate.js
@@ -56,13 +56,20 @@
  const page = reactive({ current: 1, size: 10, total: 0 });
  const tableData = ref([]);
  const formDialog = reactive({ visible: false, title: "", mode: "add" });
  const formDialog = reactive({ visible: false, title: "", mode: "add", originEnabled: false });
  const form = reactive(createEmptyTemplateForm());
  const formRef = ref();
  const isEditingBuiltin = computed(
    () => formDialog.mode === "edit" && Number(form.templateType) === TEMPLATE_TYPE_BUILTIN
  );
  // 编辑启用中的模板时只能改启用状态,其他项只读(需先停用再编辑)
  const isEditingEnabled = computed(
    () => formDialog.mode === "edit" && formDialog.originEnabled === true
  );
  const isEditFormLocked = computed(() => isEditingBuiltin.value || isEditingEnabled.value);
  async function loadTemplateTypeOptions() {
    try {
@@ -229,6 +236,7 @@
    } else {
      resetForm(mode === "edit" ? row : null);
    }
    formDialog.originEnabled = mode === "edit" && form.enabled !== false;
    formDialog.visible = true;
  }
@@ -332,6 +340,7 @@
    formRef,
    formRules,
    isEditingBuiltin,
    isEditFormLocked,
    detailDialog,
    detailRow,
    detailLoading,
src/views/procurementManagement/procurementLedger/index.vue
@@ -105,6 +105,8 @@
                               width="60" />
              <el-table-column label="产品大类"
                               prop="productCategory" />
              <el-table-column label="材质"
                               prop="material" />
              <el-table-column label="规格型号"
                               prop="specificationModel" />
              <el-table-column label="单位"
@@ -412,6 +414,8 @@
                           width="60" />
          <el-table-column label="产品大类"
                           prop="productCategory" />
          <el-table-column label="材质"
                           prop="material" />
          <el-table-column label="规格型号"
                           prop="specificationModel" />
          <el-table-column label="单位"
@@ -540,34 +544,31 @@
               ref="productFormRef">
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品大类:"
                          prop="productId">
              <el-tree-select v-model="productForm.productId"
                              placeholder="请选择"
                              clearable
                              filterable
                              check-strictly
                              @change="getModels"
                              :data="productOptions"
                              :render-after-expand="false"
                              style="width: 100%" />
            <el-form-item label="品名:"
                          prop="productCategory">
              <el-input v-model="productForm.productCategory"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="材质:"
                          prop="material">
              <el-input v-model="productForm.material"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="规格型号:"
                          prop="productModelId">
              <el-select v-model="productForm.productModelId"
                         placeholder="请选择"
                         filterable
                         clearable
                         @change="getProductModel">
                <el-option v-for="item in modelOptions"
                           :key="item.id"
                           :label="item.model"
                           :value="item.id" />
              </el-select>
                          prop="specificationModel">
              <el-input v-model="productForm.specificationModel"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
@@ -744,9 +745,7 @@
  const productData = ref([]);
  const selectedRows = ref([]);
  const productSelectedRows = ref([]);
  const modelOptions = ref([]);
  const userList = ref([]);
  const productOptions = ref([]);
  const salesContractList = ref([]);
  const supplierList = ref([]);
  const tableLoading = ref(false);
@@ -759,7 +758,6 @@
  const total = ref(0);
  const fileList = ref([]);
  import useUserStore from "@/store/modules/user";
  import { modelList, productTreeList } from "@/api/basicData/product.js";
  import dayjs from "dayjs";
  import FileUpload from "@/components/AttachmentUpload/file/index.vue";
@@ -954,9 +952,8 @@
  const currentId = ref("");
  const productFormData = reactive({
    productForm: {
      productId: "",
      productCategory: "",
      productModelId: "",
      material: "",
      specificationModel: "",
      unit: "",
      quantity: "",
@@ -969,8 +966,11 @@
      isChecked: false,
    },
    productRules: {
      productId: [{ required: true, message: "请选择", trigger: "change" }],
      productModelId: [{ required: true, message: "请选择", trigger: "change" }],
      productCategory: [{ required: true, message: "请输入", trigger: "blur" }],
      material: [{ required: true, message: "请输入", trigger: "blur" }],
      specificationModel: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      unit: [{ required: true, message: "请输入", trigger: "blur" }],
      quantity: [{ required: true, message: "请输入", trigger: "blur" }],
      taxInclusiveUnitPrice: [
@@ -1457,7 +1457,7 @@
    dialogFormVisible.value = false;
  };
  // 打开产品弹框
  const openProductForm = async (type, row, index) => {
  const openProductForm = (type, row, index) => {
    // 编辑时检查产品入库审核状态,完全入库时不能编辑
    if (type === "edit" && row && row.stockInApprovalStatus === "完全入库") {
      proxy.$modal.msgWarning("完全入库状态的产品不能编辑");
@@ -1470,12 +1470,6 @@
    proxy.resetForm("productFormRef");
    productFormVisible.value = true;
    // 先获取产品选项,确保数据加载完成
    await getProductOptions();
    // 等待 DOM 更新
    await nextTick();
    if (type === "add") {
      productForm.value.isChecked = false;
    }
@@ -1483,130 +1477,8 @@
    if (type === "edit") {
      // 复制行数据
      productForm.value = { ...row };
      // 如果是从模板加载的数据,可能没有 productId 和 productModelId
      // 需要根据 productCategory 和 specificationModel 来查找对应的 ID
      if (!productForm.value.productId && productForm.value.productCategory) {
        // 根据 productCategory 查找 productId
        const findProductIdByCategory = (nodes, categoryName) => {
          for (let i = 0; i < nodes.length; i++) {
            if (nodes[i].label === categoryName) {
              return nodes[i].value;
            }
            if (nodes[i].children && nodes[i].children.length > 0) {
              const found = findProductIdByCategory(
                nodes[i].children,
                categoryName
              );
              if (found) return found;
            }
          }
          return null;
        };
        const productId = findProductIdByCategory(
          productOptions.value,
          productForm.value.productCategory
        );
        if (productId) {
          productForm.value.productId = productId;
          // 获取型号列表并等待完成
          const modelRes = await modelList({ id: productId });
          modelOptions.value = modelRes;
          // 等待 DOM 更新
          await nextTick();
          // 根据 specificationModel 查找 productModelId
          if (
            productForm.value.specificationModel &&
            modelOptions.value.length > 0
          ) {
            const modelItem = modelOptions.value.find(
              item => item.model === productForm.value.specificationModel
            );
            if (modelItem) {
              productForm.value.productModelId = modelItem.id;
              // 设置规格型号和单位
              getProductModel(modelItem.id);
            }
          }
        }
      } else if (productForm.value.productId) {
        // 如果有 productId,正常加载型号列表
        await getModels(productForm.value.productId);
        // 等待 DOM 更新
        await nextTick();
        if (productForm.value.productModelId) {
          getProductModel(productForm.value.productModelId);
        }
      }
      // 最后再等待一次 DOM 更新,确保所有数据都已设置
      await nextTick();
    }
  };
  const getProductOptions = () => {
    return productTreeList().then(res => {
      productOptions.value = convertIdToValue(res);
      return res;
    });
  };
  const getModels = value => {
    if (value) {
      productForm.value.productCategory =
        findNodeById(productOptions.value, value) || "";
      return modelList({ id: value }).then(res => {
        modelOptions.value = res;
        return res;
      });
    } else {
      productForm.value.productCategory = "";
      modelOptions.value = [];
      return Promise.resolve([]);
    }
  };
  const getProductModel = value => {
    const index = modelOptions.value.findIndex(item => item.id === value);
    if (index !== -1) {
      productForm.value.specificationModel = modelOptions.value[index].model;
      productForm.value.unit = modelOptions.value[index].unit;
    } else {
      productForm.value.specificationModel = null;
      productForm.value.unit = null;
    }
  };
  const findNodeById = (nodes, productId) => {
    for (let i = 0; i < nodes.length; i++) {
      if (nodes[i].value === productId) {
        return nodes[i].label; // 找到节点,返回该节点的label
      }
      if (nodes[i].children && nodes[i].children.length > 0) {
        const foundNode = findNodeById(nodes[i].children, productId);
        if (foundNode) {
          return foundNode; // 在子节点中找到,直接返回(已经是label字符串)
        }
      }
    }
    return null; // 没有找到节点,返回null
  };
  function convertIdToValue(data) {
    return data.map(item => {
      const { id, children, ...rest } = item;
      const newItem = {
        ...rest,
        value: id, // 将 id 改为 value
      };
      if (children && children.length > 0) {
        newItem.children = convertIdToValue(children);
      }
      return newItem;
    });
  }
  // 提交产品表单
  const submitProduct = () => {