diff --git a/.codebuddy/rules/codegraph.mdc b/.codebuddy/rules/codegraph.mdc new file mode 100644 index 0000000..43358c7 --- /dev/null +++ b/.codebuddy/rules/codegraph.mdc @@ -0,0 +1,9 @@ +--- +description: +alwaysApply: true +enabled: true +updatedAt: 2026-09-11T14:51:38.120Z +provider: +--- + +调用前先执行codegraph的mcp \ No newline at end of file diff --git a/backend/components.d.ts b/backend/components.d.ts index dceff9a..2f3d0d6 100644 --- a/backend/components.d.ts +++ b/backend/components.d.ts @@ -46,7 +46,6 @@ declare module 'vue' { ElHeader: typeof import('element-plus/es')['ElHeader'] ElIcon: typeof import('element-plus/es')['ElIcon'] ElImage: typeof import('element-plus/es')['ElImage'] - ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] diff --git a/backend/package-lock.json b/backend/package-lock.json index f053a40..6d58ff6 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -24,7 +24,7 @@ "os": "^0.1.2", "pdfjs-dist": "^6.2.108", "pinia": "^3.0.3", - "pinyin-pro": "^3.29.3", + "pinyin-pro": "^3.29.4", "tesseract.js": "^7.0.0", "v-viewer": "^3.0.11", "vue": "^3.5.22", @@ -6557,9 +6557,9 @@ } }, "node_modules/pinyin-pro": { - "version": "3.29.3", - "resolved": "https://registry.npmmirror.com/pinyin-pro/-/pinyin-pro-3.29.3.tgz", - "integrity": "sha512-+UU9bx6vfDw8amOJGHm0TE0rdQl8VPylsDWviQ5OOQ3e+on1xRP4OqDbiDuMT5OISgvfl/Y6ez1BBRaIP80GLQ==", + "version": "3.29.4", + "resolved": "https://registry.npmmirror.com/pinyin-pro/-/pinyin-pro-3.29.4.tgz", + "integrity": "sha512-SPXpDT2cHEy+d26V1RXYMlVzXN42hotFAak1fzyWPi4o2dKXb61UqD4pzxDJHwk6gbv8vQ6EfErd+hYX0Qhzug==", "license": "MIT" }, "node_modules/pkg-types": { diff --git a/backend/package.json b/backend/package.json index 67d047e..f1aaffd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,7 +25,7 @@ "os": "^0.1.2", "pdfjs-dist": "^6.2.108", "pinia": "^3.0.3", - "pinyin-pro": "^3.29.3", + "pinyin-pro": "^3.29.4", "tesseract.js": "^7.0.0", "v-viewer": "^3.0.11", "vue": "^3.5.22", diff --git a/backend/src/api/crmProduct.js b/backend/src/api/crmProduct.js new file mode 100644 index 0000000..ec226ff --- /dev/null +++ b/backend/src/api/crmProduct.js @@ -0,0 +1,32 @@ +import request from "@/utils/request"; + +/** + * CRM 产品管理(产品台账/目录)接口。 + */ + +/* --------------------------------- 产品 --------------------------------- */ + +export function getProductList(params) { + return request({ url: "/backend/crm/product/list", method: "get", params }); +} + +export function getProductDetail(id) { + return request({ url: `/backend/crm/product/${id}`, method: "get" }); +} + +export function createProduct(data) { + return request({ url: "/backend/crm/product", method: "post", data }); +} + +export function updateProduct(id, data) { + return request({ url: `/backend/crm/product/${id}`, method: "put", data }); +} + +export function deleteProduct(id) { + return request({ url: `/backend/crm/product/${id}`, method: "delete" }); +} + +/** 项目生成/保存时,把项目产品清单写入产品管理 */ +export function syncProjectProducts(data) { + return request({ url: "/backend/crm/product/sync-from-project", method: "post", data }); +} diff --git a/backend/src/api/crmProductCategory.js b/backend/src/api/crmProductCategory.js new file mode 100644 index 0000000..5b3d6cb --- /dev/null +++ b/backend/src/api/crmProductCategory.js @@ -0,0 +1,25 @@ +import request from "@/utils/request"; + +/** + * CRM 产品分类(用户自定义产品归类)接口。 + */ + +export function getProductCategoryList(params) { + return request({ url: "/backend/crm/product/category/list", method: "get", params }); +} + +export function getProductCategoryDetail(id) { + return request({ url: `/backend/crm/product/category/${id}`, method: "get" }); +} + +export function createProductCategory(data) { + return request({ url: "/backend/crm/product/category", method: "post", data }); +} + +export function updateProductCategory(id, data) { + return request({ url: `/backend/crm/product/category/${id}`, method: "put", data }); +} + +export function deleteProductCategory(id) { + return request({ url: `/backend/crm/product/category/${id}`, method: "delete" }); +} diff --git a/backend/src/router/index.js b/backend/src/router/index.js index 7d26fb4..f3d3a8a 100644 --- a/backend/src/router/index.js +++ b/backend/src/router/index.js @@ -54,6 +54,13 @@ const staticMainChildren = [ component: () => import("@/views/apps/cms/solution/type/index.vue"), meta: { requiresAuth: true, title: "解决方案分类", modulePath: "/apps/cms" } }, + // CRM 产品管理(产品台账/目录):项目生成时自动写入产品参数 + { + path: "/apps/crm/product", + name: "CrmProduct", + component: () => import("@/views/apps/crm/product/index.vue"), + meta: { requiresAuth: true, title: "产品管理", modulePath: "/apps/crm" } + }, // 兼容旧路径:articles/* -> article/* { path: "/apps/cms/articles", @@ -63,6 +70,18 @@ const staticMainChildren = [ path: "/apps/cms/articles/category", redirect: "/apps/cms/article/type" }, + // CRM 产品分类(用户自定义产品归类):产品表单的分类下拉取自该表 + { + path: "/apps/crm/product/cate", + name: "CrmProductCategory", + component: () => import("@/views/apps/crm/product/cate.vue"), + meta: { requiresAuth: true, title: "产品分类", modulePath: "/apps/crm" } + }, + // 兼容带 .vue 后缀的菜单路径 + { + path: "/apps/crm/product/cate.vue", + redirect: "/apps/crm/product/cate" + }, { path: "/user/userProfile", name: "userProfile", diff --git a/backend/src/views/apps/crm/contract/components/PartySelect.vue b/backend/src/views/apps/crm/contract/components/PartySelect.vue index 7a49139..f3069dc 100644 --- a/backend/src/views/apps/crm/contract/components/PartySelect.vue +++ b/backend/src/views/apps/crm/contract/components/PartySelect.vue @@ -59,7 +59,7 @@ -
已关联:{{ party.ref_name }}
+ diff --git a/backend/src/views/apps/crm/contract/components/ProductList.vue b/backend/src/views/apps/crm/contract/components/ProductList.vue index 13d6b84..ef8c228 100644 --- a/backend/src/views/apps/crm/contract/components/ProductList.vue +++ b/backend/src/views/apps/crm/contract/components/ProductList.vue @@ -2,29 +2,61 @@
- + - - - + + + - + @@ -53,14 +85,29 @@ /> - + + + + @@ -84,7 +131,9 @@
添加产品 - 按类别自动归集:硬件 / 软件单列金额,服务、开发等计入其他 + + 先选产品类别,再在该分类下搜索/选择产品(自动带出规格、单位、成本、税率);分类内无对应名称可输入新增,提交时自动建档到产品管理 +
@@ -112,7 +161,10 @@
合同总利润 - + ¥{{ formatMoney(summary.total_profit) }}
@@ -122,10 +174,12 @@ diff --git a/backend/src/views/apps/crm/dict.js b/backend/src/views/apps/crm/dict.js index 6cb2c23..68b7dea 100644 --- a/backend/src/views/apps/crm/dict.js +++ b/backend/src/views/apps/crm/dict.js @@ -360,6 +360,45 @@ export const productCategoryText = (val) => PRODUCT_CATEGORY_MAP[normalize(val)] || normalize(val) || "-"; export const productCategoryTag = (val) => PRODUCT_CATEGORY_TAG[normalize(val)] || "info"; +/* ===================================================================== + * 产品管理(CRM 产品台账/目录) + * 分类复用合同产品分类:1硬件/2软件/3服务/4开发/5其他 + * 状态:1=启用 0=停用 + * ===================================================================== */ + +/** 产品状态:1=启用 0=停用 */ +export const PRODUCT_STATUS_OPTIONS = [ + { label: "启用", value: 1 }, + { label: "停用", value: 0 }, +]; + +/** 产品单位 */ +export const PRODUCT_UNIT_OPTIONS = [ + { label: "套", value: "套" }, + { label: "台", value: "台" }, + { label: "个", value: "个" }, + { label: "件", value: "件" }, + { label: "批", value: "批" }, + { label: "次", value: "次" }, + { label: "人月", value: "人月" }, + { label: "人天", value: "人天" }, + { label: "年", value: "年" }, +]; + +const PRODUCT_STATUS_MAP = { 1: "启用", 0: "停用" }; +const PRODUCT_STATUS_TAG = { 1: "success", 0: "info" }; + +export const productStatusText = (val) => PRODUCT_STATUS_MAP[normalize(val)] || "-"; +export const productStatusTag = (val) => PRODUCT_STATUS_TAG[normalize(val)] || "info"; + +/** 税率格式化:13 -> 13% */ +export function formatPercent(val) { + if (val === undefined || val === null || val === "") return "-"; + const n = Number(val); + if (isNaN(n)) return String(val); + return `${n}%`; +} + /** 合同分类选项(供 el-select 遍历) */ export const contractCategoryOptions = CONTRACT_CATEGORY_OPTIONS; export const contractStatusOptions = CONTRACT_STATUS_OPTIONS; diff --git a/backend/src/views/apps/crm/product/cate.vue b/backend/src/views/apps/crm/product/cate.vue new file mode 100644 index 0000000..ff4065c --- /dev/null +++ b/backend/src/views/apps/crm/product/cate.vue @@ -0,0 +1,245 @@ + + + + + diff --git a/backend/src/views/apps/crm/product/components/detail.vue b/backend/src/views/apps/crm/product/components/detail.vue new file mode 100644 index 0000000..707d21e --- /dev/null +++ b/backend/src/views/apps/crm/product/components/detail.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/backend/src/views/apps/crm/product/components/edit.vue b/backend/src/views/apps/crm/product/components/edit.vue new file mode 100644 index 0000000..73878f3 --- /dev/null +++ b/backend/src/views/apps/crm/product/components/edit.vue @@ -0,0 +1,189 @@ + + + diff --git a/backend/src/views/apps/crm/product/index.vue b/backend/src/views/apps/crm/product/index.vue new file mode 100644 index 0000000..42114bc --- /dev/null +++ b/backend/src/views/apps/crm/product/index.vue @@ -0,0 +1,392 @@ + + + + + + + diff --git a/backend/src/views/apps/crm/project/components/detail.vue b/backend/src/views/apps/crm/project/components/detail.vue index 5c6e083..35b6139 100644 --- a/backend/src/views/apps/crm/project/components/detail.vue +++ b/backend/src/views/apps/crm/project/components/detail.vue @@ -37,6 +37,16 @@ /> + + + + + +
+
+ 新增合同 + +
+ +
+ + + + + +
+
+
+ {{ c.contract_name }} + + {{ contractStatusText(c.status) }} + + + {{ ourRoleText(c.our_role) }} + +
+
+ 编辑 + 删除 +
+
+
+ {{ c.contract_no || "-" }} · {{ contractCategoryText(c.contract_category) }} +
+ + + {{ formatDateOnly(c.sign_date) }} + {{ c.owner_user_name || "-" }} + {{ formatDateOnly(c.effective_date) }} + {{ formatDateOnly(c.expire_date) }} + {{ c.remark || "-" }} + + + 各方签约主体 + + + + + + + + + + + + + + + + + + + 产品清单 + + + + + + + + + + + + + + + + + + + + + + + 金额汇总 +
+
+ 合同总金额 + ¥{{ formatMoney(summaryOf(c).total_amount) }} +
+
+ 产品总成本 + ¥{{ formatMoney(summaryOf(c).total_cost) }} +
+
+ 合同总利润 + ¥{{ formatMoney(summaryOf(c).total_profit) }} +
+
+ 硬件部分金额 + ¥{{ formatMoney(summaryOf(c).hardware_amount) }} +
+
+ 软件部分金额 + ¥{{ formatMoney(summaryOf(c).software_amount) }} +
+
+ 其他部分金额 + ¥{{ formatMoney(summaryOf(c).other_amount) }} +
+
+
+
+
+ + + 新增合同 + +
+ + + +
+ + + + + diff --git a/backend/src/views/apps/crm/project/components/edit.vue b/backend/src/views/apps/crm/project/components/edit.vue index 4e19c15..12416c7 100644 --- a/backend/src/views/apps/crm/project/components/edit.vue +++ b/backend/src/views/apps/crm/project/components/edit.vue @@ -2,7 +2,7 @@ + + +