批量优化产品和产品分类

This commit is contained in:
2026-09-12 00:44:13 +08:00
parent 184c604bee
commit 2b1874943f
33 changed files with 2595 additions and 59 deletions
@@ -2,7 +2,7 @@
<el-dialog
:model-value="visible"
:title="isEdit ? '编辑项目' : '新增项目'"
width="760px"
width="960px"
:close-on-click-modal="false"
@update:model-value="handleClose"
@opened="handleOpened"
@@ -98,6 +98,13 @@
</el-col>
</el-row>
</el-form>
<!-- <el-divider content-position="left">
产品清单
<span class="tip">保存项目时,产品参数将自动写入「产品管理」</span>
</el-divider>
<ProductList :products="form.products" /> -->
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
@@ -109,10 +116,12 @@
import { ref, reactive, watch } from "vue";
import { ElMessage } from "element-plus";
import { createProject, updateProject } from "@/api/crmPipeline";
import { getProductList } from "@/api/crmProduct";
import { getCrmCustomerList } from "@/api/crm";
import { getAllUsers } from "@/api/user";
import { useAuthStore } from "@/stores/auth";
import { PROJECT_STATUS_OPTIONS } from "../../dict";
import ProductList from "../../contract/components/ProductList.vue";
const props = defineProps({
visible: { type: Boolean, default: false },
@@ -146,10 +155,58 @@ const defaultForm = () => ({
contact_phone: "",
address: "",
remark: "",
products: [],
});
const form = reactive(defaultForm());
/** 解析项目产品清单:兼容 JSON 字符串 / 数组 / 空,并补全行内 __key(el-table 的 row-key) */
function parseProducts(val) {
let arr = [];
if (Array.isArray(val)) {
arr = val;
} else if (typeof val === "string" && val.trim()) {
try {
const parsed = JSON.parse(val);
if (Array.isArray(parsed)) arr = parsed;
} catch {
arr = [];
}
}
// 缺失 __key 时多行会被当作同一行,导致回显不出来
return arr.map((r, i) => ({
...r,
__key: r && r.__key ? r.__key : `row_${i}_${Date.now()}`,
}));
}
/**
* 兜底回显:存量项目 / 商机转化项目的 products 可能从未写入(null 或空串),
* 此时从「产品管理」回查归属本项目的产品,保证编辑时能看到产品清单。
*/
async function loadProjectProducts(projectId) {
try {
// 后端 pageSize 上限为 100
const res = await getProductList({ project_id: projectId, page: 1, pageSize: 100 });
const list = res?.data?.list || [];
form.products = list.map((p, i) => ({
__key: `row_p${p.id}_${i}`,
name: p.product_name || "",
product_id: p.id,
category: p.category || "",
spec: p.spec || "",
unit: p.unit || "",
quantity: 1,
price: Number(p.price) || 0,
cost_price: Number(p.cost_price) || 0,
tax_rate: Number(p.tax_rate) || 0,
remark: p.remark || "",
}));
} catch (e) {
form.products = [];
}
}
const rules = {
project_name: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
};
@@ -163,6 +220,15 @@ watch(
internalId.value = props.editData.id;
Object.assign(form, defaultForm(), props.editData);
form.status = Number(props.editData.status) || 1;
// 解析项目已关联的产品清单(后端返回 JSON 字符串)
const rawProducts = props.editData.products;
form.products = parseProducts(rawProducts);
// 从未写入过产品清单(null / 空串,而非 "[]")时,从产品管理回查兜底回显
const neverSet =
rawProducts === null || rawProducts === undefined || String(rawProducts).trim() === "";
if (neverSet && form.products.length === 0 && props.editData.id) {
loadProjectProducts(props.editData.id);
}
} else {
isEdit.value = false;
internalId.value = null;
@@ -220,7 +286,7 @@ async function handleSubmit() {
if (owner) form.owner_user_name = owner.name;
submitting.value = true;
try {
const payload = { ...form, amount: Number(form.amount) || 0 };
const payload = { ...form, amount: Number(form.amount) || 0, products: JSON.stringify(form.products || []) };
if (internalId.value) {
await updateProject(internalId.value, payload);
ElMessage.success("更新成功");
@@ -237,3 +303,11 @@ async function handleSubmit() {
}
}
</script>
<style lang="less" scoped>
.tip {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-left: 8px;
}
</style>