更新若干功能

This commit is contained in:
hero920103 committed 2026-10-21 17:45:50 +08:00
1 parent 892ba5886d
commit 79d5ae6b66
45 files changed
+6755 -38

No files matched your search

@@ -7,11 +7,37 @@
@update:model-value="handleClose"
@opened="loadAll"
>
<!-- 标题 + 右上角阶段流转(与合同 / 项目详情一致) -->
<!-- 标题 + 右上角状态 / 阶段流转(与合同 / 项目详情一致) -->
<template #header>
<div class="drawer-header">
<span class="drawer-title">{{ business?.business_name || "商机详情" }}</span>
<div v-if="business" class="drawer-actions">
<!-- 状态流转:跟不下去的商机可废弃,废弃后可恢复为跟进中 -->
<el-dropdown
v-if="statusOptionsExcept(business.status).length"
trigger="click"
@command="(cmd) => handleChangeStatus(cmd)"
>
<el-button
link
type="info"
size="small"
:disabled="Number(business.locked) === 1 || Number(business.status) === 2"
>
状态流转<el-icon style="margin-left: 2px"><ArrowDown /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="i in statusOptionsExcept(business.status)"
:key="i.value"
:command="i.value"
>
{{ i.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown
v-if="stageOptionsExcept(business.stage).length"
trigger="click"
@@ -21,7 +47,11 @@
link
type="warning"
size="small"
:disabled="Number(business.locked) === 1 || Number(business.status) === 2"
:disabled="
Number(business.locked) === 1 ||
Number(business.status) === 2 ||
Number(business.status) === 3
"
>
阶段流转<el-icon style="margin-left: 2px"><ArrowDown /></el-icon>
</el-button>
@@ -42,6 +72,14 @@
</template>
<div v-if="business" class="biz-detail">
<el-alert
v-if="Number(business.status) === 3"
type="info"
:closable="false"
show-icon
title="该商机已废弃,可点击右上角「状态流转」恢复为「跟进中」后继续推进"
style="margin-bottom: 12px"
/>
<el-tabs v-model="activeTab" tab-position="left" class="detail-tabs">
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="basic">
@@ -98,8 +136,13 @@
import { ref, reactive, watch } from "vue";
import { ArrowDown } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { changeBusinessStage } from "@/api/crmPipeline";
import { BUSINESS_STAGE_OPTIONS, businessStageText } from "../../dict";
import { changeBusinessStage, changeBusinessStatus } from "@/api/crmPipeline";
import {
BUSINESS_STAGE_OPTIONS,
BUSINESS_STATUS_OPTIONS,
businessStageText,
businessStatusText,
} from "../../dict";
import DetailBasic from "./detail_basic.vue";
import DetailContact from "./detail_contact.vue";
import DetailFollow from "./detail_follow.vue";
@@ -139,6 +182,29 @@ function handleClose() {
emit("update:visible", false);
}
/**
* 可流转的状态:排除当前状态与「已转化项目」。
* 已转化必须走「转项目」流程(会锁定商机),不能只改状态。
*/
function statusOptionsExcept(cur) {
return BUSINESS_STATUS_OPTIONS.filter(
(i) => String(i.value) !== String(cur) && String(i.value) !== "2"
);
}
/** 状态流转:仅更新状态字段,成功后同步本地展示并通知列表刷新 */
async function handleChangeStatus(status) {
if (!props.business?.id) return;
try {
await changeBusinessStatus(props.business.id, Number(status));
ElMessage.success(`状态已更新为「${businessStatusText(status)}」`);
props.business.status = Number(status);
emit("refresh");
} catch (e) {
ElMessage.error(e.message || "状态更新失败");
}
}
/** 可流转的阶段:排除当前阶段 */
function stageOptionsExcept(cur) {
return BUSINESS_STAGE_OPTIONS.filter((i) => String(i.value) !== String(cur));
+53 -3
View File
@@ -82,7 +82,7 @@
<el-table-column label="下次联系时间" width="160" align="center">
<template #default="{ row }">{{ formatDateTime(row.next_contact_time) }}</template>
</el-table-column>
<el-table-column label="操作" width="220" align="center" fixed="right">
<el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" :disabled="row.locked === 1" @click="openEdit(row)">
编辑
@@ -91,11 +91,31 @@
link
type="warning"
size="small"
:disabled="row.locked === 1 || row.status === 2"
:disabled="row.locked === 1 || row.status === 2 || row.status === 3"
@click="openConvert(row)"
>
转项目
</el-button>
<el-button
v-if="row.status === 3"
link
type="success"
size="small"
:disabled="row.locked === 1"
@click="handleRestore(row)"
>
恢复
</el-button>
<el-button
v-else
link
type="info"
size="small"
:disabled="row.locked === 1 || row.status === 2"
@click="handleDiscard(row)"
>
废弃
</el-button>
<el-button
link
type="danger"
@@ -138,7 +158,7 @@
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Search, Refresh } from "@element-plus/icons-vue";
import { getBusinessList, deleteBusiness } from "@/api/crmPipeline";
import { getBusinessList, deleteBusiness, changeBusinessStatus } from "@/api/crmPipeline";
import BusinessEdit from "./components/edit.vue";
import BusinessConvert from "./components/convert.vue";
import BusinessDetail from "./components/detail.vue";
@@ -228,6 +248,36 @@ function handleConvertSuccess() {
ElMessage.success("商机已转化为项目,可在项目管理中继续跟进");
}
/** 废弃商机:保留全部过程数据,仅把状态置为已废弃,可随时恢复 */
async function handleDiscard(row) {
try {
await ElMessageBox.confirm(
`确定废弃商机「${row.business_name}」吗?废弃后可随时恢复为「跟进中」。`,
"废弃确认",
{ type: "warning" }
);
await changeBusinessStatus(row.id, 3);
ElMessage.success("商机已废弃");
fetchList();
} catch (e) {
if (e !== "cancel") ElMessage.error(e.message || "废弃失败");
}
}
/** 恢复商机:把已废弃的商机恢复为跟进中 */
async function handleRestore(row) {
try {
await ElMessageBox.confirm(`确定将商机「${row.business_name}」恢复为「跟进中」吗?`, "恢复确认", {
type: "info",
});
await changeBusinessStatus(row.id, 1);
ElMessage.success("商机已恢复为跟进中");
fetchList();
} catch (e) {
if (e !== "cancel") ElMessage.error(e.message || "恢复失败");
}
}
async function handleDelete(row) {
try {
await ElMessageBox.confirm(`确定删除商机「${row.business_name}」吗?删除后不可恢复。`, "删除确认", {
@@ -63,6 +63,14 @@
title="该线索已转化为商机并锁定,后续请在商机管理中操作;商机详情会按阶段展示线索 + 商机的联系人、跟进记录与附件"
style="margin-bottom: 12px"
/>
<el-alert
v-if="Number(clue.status) === 3"
type="info"
:closable="false"
show-icon
title="该线索已废弃,可点击右上角「状态流转」恢复为「跟进中」后继续跟进"
style="margin-bottom: 12px"
/>
<el-tabs v-model="activeTab" tab-position="left" class="detail-tabs">
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="basic">
+54 -4
View File
@@ -84,18 +84,38 @@
<el-table-column label="创建时间" width="160" align="center">
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="{ row }">
<!-- 编辑 / 删除已收口到「线索详情」内,列表只保留转化 -->
<!-- 编辑 / 删除已收口到「线索详情」内,列表保留转化 / 废弃 -->
<el-button
link
type="warning"
size="small"
:disabled="row.locked === 1 || row.status === 2"
:disabled="row.locked === 1 || row.status === 2 || row.status === 3"
@click="openConvert(row)"
>
转化
</el-button>
<el-button
v-if="row.status === 3"
link
type="success"
size="small"
:disabled="row.locked === 1"
@click="handleRestore(row)"
>
恢复
</el-button>
<el-button
v-else
link
type="info"
size="small"
:disabled="row.locked === 1 || row.status === 2"
@click="handleDiscard(row)"
>
废弃
</el-button>
</template>
</el-table-column>
<template #empty><el-empty description="暂无线索" :image-size="80" /></template>
@@ -130,7 +150,7 @@
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Search, Refresh } from "@element-plus/icons-vue";
import { getClueList, deleteClue } from "@/api/crmPipeline";
import { getClueList, deleteClue, changeClueStatus } from "@/api/crmPipeline";
import ClueEdit from "./components/edit.vue";
import ClueConvert from "./components/convert.vue";
import ClueDetail from "./components/detail.vue";
@@ -218,6 +238,36 @@ function handleConvertSuccess() {
ElMessage.success("线索已转化,可在商机管理中继续跟进");
}
/** 废弃线索:保留全部过程数据,仅把状态置为已废弃,可随时恢复 */
async function handleDiscard(row) {
try {
await ElMessageBox.confirm(
`确定废弃线索「${row.clue_name}」吗?废弃后可随时恢复为「跟进中」。`,
"废弃确认",
{ type: "warning" }
);
await changeClueStatus(row.id, 3);
ElMessage.success("线索已废弃");
fetchList();
} catch (e) {
if (e !== "cancel") ElMessage.error(e.message || "废弃失败");
}
}
/** 恢复线索:把已废弃的线索恢复为跟进中 */
async function handleRestore(row) {
try {
await ElMessageBox.confirm(`确定将线索「${row.clue_name}」恢复为「跟进中」吗?`, "恢复确认", {
type: "info",
});
await changeClueStatus(row.id, 1);
ElMessage.success("线索已恢复为跟进中");
fetchList();
} catch (e) {
if (e !== "cancel") ElMessage.error(e.message || "恢复失败");
}
}
/** 详情内编辑:收起详情后打开编辑弹窗(详情数据即当前行) */
function handleDetailEdit() {
detailVisible.value = false;
+2 -2
View File
@@ -176,11 +176,11 @@ export const BUSINESS_STAGE_OPTIONS = [
{ label: "输单", value: "6" },
];
/** 商机状态 */
/** 商机状态:跳过一半无法继续推进的商机可废弃(可恢复为跟进中) */
export const BUSINESS_STATUS_OPTIONS = [
{ label: "跟进中", value: "1" },
{ label: "已转化项目", value: "2" },
{ label: "已关闭", value: "3" },
{ label: "已废弃", value: "3" },
];
/** 项目状态 */