feat(m1): add project stakeholder CRUD

This commit is contained in:
2026-05-25 14:46:30 +08:00
parent 4bbf1f552f
commit e96383433d
9 changed files with 553 additions and 6 deletions
@@ -437,3 +437,16 @@ export function updateJsonTemplate(id, body) {
export function deleteJsonTemplate(id) {
return axios.delete(`/api/v1/integration/json-templates/${id}`);
}
export function listStakeholders(projectId) {
return axios.get(`/api/v1/projects/${projectId}/stakeholders`);
}
export function addStakeholder(projectId, body) {
return axios.post(`/api/v1/projects/${projectId}/stakeholders`, body);
}
export function updateStakeholder(projectId, id, body) {
return axios.put(`/api/v1/projects/${projectId}/stakeholders/${id}`, body);
}
export function deleteStakeholder(projectId, id) {
return axios.delete(`/api/v1/projects/${projectId}/stakeholders/${id}`);
}
@@ -21,7 +21,7 @@
/>
</el-select>
<el-button type="primary" :loading="loading" @click="load">查询</el-button>
<el-button type="success" @click="openCreate">新建项目</el-button>
<el-button v-permission="'project:rw'" type="success" @click="openCreate">新建项目</el-button>
</div>
</div>
</template>
@@ -38,10 +38,11 @@
{{ phaseLabel(row.phase) }}
</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right">
<el-table-column label="操作" width="220" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="openEdit(row)">编辑</el-button>
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
<el-button type="primary" link @click="openStakeholderDialog(row)">干系人</el-button>
<el-button v-permission="'project:rw'" type="primary" link @click="openEdit(row)">编辑</el-button>
<el-button v-permission="'project:delete'" type="danger" link @click="onDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -100,6 +101,50 @@
<el-button type="primary" :loading="saving" @click="submit">保存</el-button>
</template>
</el-dialog>
<el-dialog v-model="stakeholderVisible" :title="stakeholderTitle" width="700px" destroy-on-close>
<el-button type="primary" size="small" style="margin-bottom: 12px" @click="openStakeholderAdd">添加干系人</el-button>
<el-table :data="stakeholderRows" stripe style="width: 100%">
<el-table-column prop="contactName" label="姓名" min-width="100" show-overflow-tooltip />
<el-table-column prop="contactRole" label="角色" min-width="100" show-overflow-tooltip />
<el-table-column prop="phone" label="电话" min-width="130" />
<el-table-column prop="email" label="邮箱" min-width="160" show-overflow-tooltip />
<el-table-column label="内部人员" width="90">
<template #default="{ row }">
{{ row.isInternal ? '是' : '否' }}
</template>
</el-table-column>
<el-table-column label="操作" width="140" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="openStakeholderEdit(row)">编辑</el-button>
<el-button type="danger" link @click="onStakeholderDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog v-model="stakeholderFormVisible" :title="stakeholderFormTitle" width="500px" destroy-on-close @closed="resetStakeholderForm">
<el-form ref="stakeholderFormRef" :model="stakeholderForm" :rules="stakeholderRules" label-width="100px">
<el-form-item label="姓名" prop="contactName">
<el-input v-model="stakeholderForm.contactName" maxlength="128" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="角色" prop="contactRole">
<el-input v-model="stakeholderForm.contactRole" maxlength="64" placeholder="请输入角色" />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="stakeholderForm.phone" maxlength="32" placeholder="请输入电话" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="stakeholderForm.email" maxlength="128" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="内部人员">
<el-switch v-model="stakeholderForm.isInternal" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="stakeholderFormVisible = false">取消</el-button>
<el-button type="primary" :loading="stakeholderSaving" @click="submitStakeholder">保存</el-button>
</template>
</el-dialog>
</el-card>
</template>
@@ -114,6 +159,10 @@ import {
updateProject,
deleteProject,
getProjectPhaseDictionary,
listStakeholders,
addStakeholder,
updateStakeholder,
deleteStakeholder,
} from "../api/platform";
import { apiErrorMessage } from "../utils/apiErrorMessage";
@@ -151,6 +200,128 @@ const rules = {
const dialogTitle = computed(() => (editingId.value ? "编辑项目" : "新建项目"));
// —— 干系人 ——————————————————————————————————————————
const stakeholderVisible = ref(false);
const stakeholderFormVisible = ref(false);
const stakeholderSaving = ref(false);
const stakeholderRows = ref([]);
const stakeholderProjectId = ref(null);
const stakeholderEditingId = ref(null);
const stakeholderFormRef = ref(null);
const stakeholderForm = reactive({
contactName: "",
contactRole: "",
phone: "",
email: "",
isInternal: false,
});
const stakeholderRules = {
contactName: [{ required: true, message: "请输入姓名", trigger: "blur" }],
};
const stakeholderTitle = computed(() => {
const p = rows.value.find((r) => r.id === stakeholderProjectId.value);
return `干系人 - ${p?.name ?? stakeholderProjectId.value}`;
});
const stakeholderFormTitle = computed(() =>
stakeholderEditingId.value ? "编辑干系人" : "添加干系人"
);
function openStakeholderDialog(row) {
stakeholderProjectId.value = row.id;
stakeholderVisible.value = true;
loadStakeholders();
}
async function loadStakeholders() {
try {
const { data } = await listStakeholders(stakeholderProjectId.value);
stakeholderRows.value = Array.isArray(data) ? data : [];
} catch (e) {
ElMessage.error(apiErrorMessage(e, "加载干系人列表失败"));
stakeholderRows.value = [];
}
}
function openStakeholderAdd() {
stakeholderEditingId.value = null;
resetStakeholderForm();
stakeholderFormVisible.value = true;
}
function openStakeholderEdit(row) {
stakeholderEditingId.value = row.id;
stakeholderForm.contactName = row.contactName ?? "";
stakeholderForm.contactRole = row.contactRole ?? "";
stakeholderForm.phone = row.phone ?? "";
stakeholderForm.email = row.email ?? "";
stakeholderForm.isInternal = row.isInternal ?? false;
stakeholderFormVisible.value = true;
}
function resetStakeholderForm() {
stakeholderForm.contactName = "";
stakeholderForm.contactRole = "";
stakeholderForm.phone = "";
stakeholderForm.email = "";
stakeholderForm.isInternal = false;
stakeholderFormRef.value?.resetFields?.();
}
async function submitStakeholder() {
const f = stakeholderFormRef.value;
if (!f) return;
try {
await f.validate();
} catch {
return;
}
stakeholderSaving.value = true;
const payload = {
contactName: stakeholderForm.contactName.trim(),
contactRole: stakeholderForm.contactRole || null,
phone: stakeholderForm.phone || null,
email: stakeholderForm.email || null,
isInternal: stakeholderForm.isInternal,
};
try {
const pid = stakeholderProjectId.value;
if (stakeholderEditingId.value != null) {
await updateStakeholder(pid, stakeholderEditingId.value, payload);
ElMessage.success("已保存");
} else {
await addStakeholder(pid, payload);
ElMessage.success("已添加");
}
stakeholderFormVisible.value = false;
await loadStakeholders();
} catch (e) {
ElMessage.error(apiErrorMessage(e, "保存失败"));
} finally {
stakeholderSaving.value = false;
}
}
function onStakeholderDelete(row) {
ElMessageBox.confirm(`确定删除干系人「${row.contactName || row.id}」吗?`, "提示", {
type: "warning",
confirmButtonText: "删除",
cancelButtonText: "取消",
})
.then(async () => {
try {
await deleteStakeholder(stakeholderProjectId.value, row.id);
ElMessage.success("已删除");
await loadStakeholders();
} catch (e) {
ElMessage.error(apiErrorMessage(e, "删除失败"));
}
})
.catch(() => {});
}
const customerMap = computed(() => {
const m = new Map();
for (const c of customerOptions.value) {