批量更新功能

This commit is contained in:
hero920103 committed 2026-09-14 16:15:44 +08:00
1 parent 6f106e43af
commit a26c98b92b
79 files changed
+4529 -724

No files matched your search

@@ -12,10 +12,27 @@
<div class="toolbar-status">
<el-tag :type="projectStatusTag(project.status)" size="small">{{ projectStatusText(project.status) }}</el-tag>
<el-tag v-if="projectOverdue(project).overdue" type="danger" size="small" effect="dark">
{{ overdueText(projectOverdue(project).days) }}
{{ projectOverdue(project).text }}
</el-tag>
</div>
<div class="toolbar-actions">
<!-- 状态流转:与合同详情一致,右上角下拉切换项目状态 -->
<el-dropdown trigger="click" @command="(cmd) => handleChangeStatus(cmd)">
<el-button link type="warning" size="small">
状态流转<el-icon style="margin-left: 2px"><ArrowDown /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="i in statusOptionsExcept(project.status)"
:key="i.value"
:command="i.value"
>
{{ i.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button type="primary" size="small" :icon="Edit" @click="handleEdit">编辑</el-button>
<el-button type="danger" size="small" :icon="Delete" @click="handleDelete">删除</el-button>
</div>
@@ -26,14 +43,9 @@
<DetailBasic :project="project" />
</el-tab-pane>
<!-- 联系人(正式客户通讯录) -->
<!-- 联系人(按项目类型分组:从对应企业通讯录中挑选,支持多选) -->
<el-tab-pane :label="`联系人 (${counts.contact})`" name="contact">
<DetailContact
ref="contactRef"
:company-id="project.customer_id"
company-type="customer"
@count="counts.contact = $event"
/>
<DetailContact ref="contactRef" :project="project" @count="counts.contact = $event" />
</el-tab-pane>
<!-- 跟进记录(链路累积:线索 + 商机 + 项目) -->
@@ -55,6 +67,7 @@
ref="contractRef"
:project-id="project.id"
:project-name="project.project_name || ''"
:project-type="project.project_type"
@count="counts.contract = $event"
/>
</el-tab-pane>
@@ -93,8 +106,8 @@
<script setup>
import { ref, reactive, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Edit, Delete } from "@element-plus/icons-vue";
import { deleteProject } from "@/api/crmPipeline";
import { Edit, Delete, ArrowDown } from "@element-plus/icons-vue";
import { deleteProject, changeProjectStatus, getProjectDetail } from "@/api/crmPipeline";
import DetailBasic from "./detail_basic.vue";
import DetailContact from "./detail_contact.vue";
import DetailFollow from "./detail_follow.vue";
@@ -102,7 +115,12 @@ import DetailContract from "./detail_contract.vue";
import DetailAttach from "./detail_attach.vue";
import DetailDocs from "./detail_docs.vue";
import DetailLog from "./detail_log.vue";
import { projectStatusText, projectStatusTag, projectOverdue, overdueText } from "../../dict";
import {
PROJECT_STATUS_OPTIONS,
projectStatusText,
projectStatusTag,
projectOverdue,
} from "../../dict";
const props = defineProps({
visible: { type: Boolean, default: false },
@@ -145,6 +163,24 @@ function handleEdit() {
emit("edit", props.project);
}
/** 状态流转候选:排除当前状态 */
function statusOptionsExcept(cur) {
return PROJECT_STATUS_OPTIONS.filter((i) => String(i.value) !== String(cur));
}
/** 项目状态流转:成功后更新本地展示并通知父组件刷新列表 */
async function handleChangeStatus(status) {
if (!props.project?.id) return;
try {
await changeProjectStatus(props.project.id, Number(status));
ElMessage.success(`状态已更新为「${projectStatusText(status)}」`);
props.project.status = Number(status);
emit("refresh");
} catch (e) {
ElMessage.error(e.message || "状态更新失败");
}
}
async function handleDelete() {
try {
await ElMessageBox.confirm(`确定删除项目「${props.project.project_name}」吗?删除后不可恢复。`, "删除确认", {
@@ -160,8 +196,19 @@ async function handleDelete() {
}
/** 抽屉打开后刷新各 Tab 数据 */
function loadAll() {
async function loadAll() {
if (!props.project?.id) return;
// 列表行数据是打开页面时的快照,联系人选中集合等字段可能已过期(如刚在其他项目/本次会话保存过),
// 先拉取最新详情刷新快照,各 Tab(含联系人)再按最新数据加载
try {
const res = await getProjectDetail(props.project.id);
const data = res?.data;
if (data && typeof data === "object" && props.project) {
Object.assign(props.project, data);
}
} catch (e) {
// 详情接口失败时保持快照展示,不阻断其他 Tab
}
contactRef.value?.reload();
followRef.value?.reload();
contractRef.value?.reload();