mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(m1): add project stakeholder CRUD
This commit is contained in:
+98
@@ -0,0 +1,98 @@
|
|||||||
|
package cn.craftlabs.platform.api.persistence.project;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
|
||||||
|
@TableName("platform_project_stakeholder")
|
||||||
|
public class PlatformProjectStakeholder {
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@TableField("project_id")
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
@TableField("contact_name")
|
||||||
|
private String contactName;
|
||||||
|
|
||||||
|
@TableField("contact_role")
|
||||||
|
private String contactRole;
|
||||||
|
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@TableField("is_internal")
|
||||||
|
private Boolean isInternal;
|
||||||
|
|
||||||
|
@TableField("created_at")
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProjectId() {
|
||||||
|
return projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProjectId(Long projectId) {
|
||||||
|
this.projectId = projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactName() {
|
||||||
|
return contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactName(String contactName) {
|
||||||
|
this.contactName = contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactRole() {
|
||||||
|
return contactRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactRole(String contactRole) {
|
||||||
|
this.contactRole = contactRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsInternal() {
|
||||||
|
return isInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsInternal(Boolean isInternal) {
|
||||||
|
this.isInternal = isInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package cn.craftlabs.platform.api.persistence.project;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PlatformProjectStakeholderMapper extends BaseMapper<PlatformProjectStakeholder> {}
|
||||||
+33
-1
@@ -4,6 +4,8 @@ import cn.craftlabs.platform.api.service.ProjectService;
|
|||||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||||
import cn.craftlabs.platform.api.web.dto.ProjectRequest;
|
import cn.craftlabs.platform.api.web.dto.ProjectRequest;
|
||||||
import cn.craftlabs.platform.api.web.dto.ProjectResponse;
|
import cn.craftlabs.platform.api.web.dto.ProjectResponse;
|
||||||
|
import cn.craftlabs.platform.api.web.dto.StakeholderRequest;
|
||||||
|
import cn.craftlabs.platform.api.web.dto.StakeholderResponse;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.Max;
|
import jakarta.validation.constraints.Max;
|
||||||
import jakarta.validation.constraints.Min;
|
import jakarta.validation.constraints.Min;
|
||||||
@@ -20,7 +22,8 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/** 项目 API。{@code DELETE /{id}} 为<strong>物理删除</strong>。 */
|
import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/projects")
|
@RequestMapping("/api/v1/projects")
|
||||||
@Validated
|
@Validated
|
||||||
@@ -62,4 +65,33 @@ public class ProjectController {
|
|||||||
public void delete(@PathVariable("id") long id) {
|
public void delete(@PathVariable("id") long id) {
|
||||||
projectService.delete(id);
|
projectService.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{projectId}/stakeholders")
|
||||||
|
public List<StakeholderResponse> listStakeholders(@PathVariable("projectId") long projectId) {
|
||||||
|
return projectService.listStakeholders(projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{projectId}/stakeholders")
|
||||||
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
|
public StakeholderResponse addStakeholder(
|
||||||
|
@PathVariable("projectId") long projectId,
|
||||||
|
@Valid @RequestBody StakeholderRequest request) {
|
||||||
|
return projectService.addStakeholder(projectId, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{projectId}/stakeholders/{id}")
|
||||||
|
public StakeholderResponse updateStakeholder(
|
||||||
|
@PathVariable("projectId") long projectId,
|
||||||
|
@PathVariable("id") long id,
|
||||||
|
@Valid @RequestBody StakeholderRequest request) {
|
||||||
|
return projectService.updateStakeholder(id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{projectId}/stakeholders/{id}")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
public void deleteStakeholder(
|
||||||
|
@PathVariable("projectId") long projectId,
|
||||||
|
@PathVariable("id") long id) {
|
||||||
|
projectService.deleteStakeholder(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-1
@@ -2,9 +2,13 @@ package cn.craftlabs.platform.api.service;
|
|||||||
|
|
||||||
import cn.craftlabs.platform.api.persistence.project.PlatformProject;
|
import cn.craftlabs.platform.api.persistence.project.PlatformProject;
|
||||||
import cn.craftlabs.platform.api.persistence.project.PlatformProjectMapper;
|
import cn.craftlabs.platform.api.persistence.project.PlatformProjectMapper;
|
||||||
|
import cn.craftlabs.platform.api.persistence.project.PlatformProjectStakeholder;
|
||||||
|
import cn.craftlabs.platform.api.persistence.project.PlatformProjectStakeholderMapper;
|
||||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||||
import cn.craftlabs.platform.api.web.dto.ProjectRequest;
|
import cn.craftlabs.platform.api.web.dto.ProjectRequest;
|
||||||
import cn.craftlabs.platform.api.web.dto.ProjectResponse;
|
import cn.craftlabs.platform.api.web.dto.ProjectResponse;
|
||||||
|
import cn.craftlabs.platform.api.web.dto.StakeholderRequest;
|
||||||
|
import cn.craftlabs.platform.api.web.dto.StakeholderResponse;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
@@ -26,10 +30,12 @@ public class ProjectService {
|
|||||||
private static final String DEFAULT_PHASE = "PLANNING";
|
private static final String DEFAULT_PHASE = "PLANNING";
|
||||||
|
|
||||||
private final PlatformProjectMapper projectMapper;
|
private final PlatformProjectMapper projectMapper;
|
||||||
|
private final PlatformProjectStakeholderMapper stakeholderMapper;
|
||||||
private final CustomerService customerService;
|
private final CustomerService customerService;
|
||||||
|
|
||||||
public ProjectService(PlatformProjectMapper projectMapper, CustomerService customerService) {
|
public ProjectService(PlatformProjectMapper projectMapper, PlatformProjectStakeholderMapper stakeholderMapper, CustomerService customerService) {
|
||||||
this.projectMapper = projectMapper;
|
this.projectMapper = projectMapper;
|
||||||
|
this.stakeholderMapper = stakeholderMapper;
|
||||||
this.customerService = customerService;
|
this.customerService = customerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +107,74 @@ public class ProjectService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<StakeholderResponse> listStakeholders(long projectId) {
|
||||||
|
LambdaQueryWrapper<PlatformProjectStakeholder> q =
|
||||||
|
Wrappers.lambdaQuery(PlatformProjectStakeholder.class)
|
||||||
|
.eq(PlatformProjectStakeholder::getProjectId, projectId)
|
||||||
|
.orderByAsc(PlatformProjectStakeholder::getId);
|
||||||
|
return stakeholderMapper.selectList(q).stream()
|
||||||
|
.map(this::toStakeholderResponse)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public StakeholderResponse addStakeholder(long projectId, StakeholderRequest request) {
|
||||||
|
requireProjectExists(projectId);
|
||||||
|
PlatformProjectStakeholder s = new PlatformProjectStakeholder();
|
||||||
|
s.setProjectId(projectId);
|
||||||
|
s.setContactName(request.getContactName().trim());
|
||||||
|
s.setContactRole(request.getContactRole());
|
||||||
|
s.setPhone(request.getPhone());
|
||||||
|
s.setEmail(request.getEmail());
|
||||||
|
s.setIsInternal(request.getIsInternal() != null && request.getIsInternal());
|
||||||
|
s.setCreatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||||
|
stakeholderMapper.insert(s);
|
||||||
|
return toStakeholderResponse(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public StakeholderResponse updateStakeholder(long stakeholderId, StakeholderRequest request) {
|
||||||
|
PlatformProjectStakeholder s = stakeholderMapper.selectById(stakeholderId);
|
||||||
|
if (s == null) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "stakeholder not found");
|
||||||
|
}
|
||||||
|
s.setContactName(request.getContactName().trim());
|
||||||
|
s.setContactRole(request.getContactRole());
|
||||||
|
s.setPhone(request.getPhone());
|
||||||
|
s.setEmail(request.getEmail());
|
||||||
|
s.setIsInternal(request.getIsInternal() != null && request.getIsInternal());
|
||||||
|
stakeholderMapper.updateById(s);
|
||||||
|
return toStakeholderResponse(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteStakeholder(long stakeholderId) {
|
||||||
|
int rows = stakeholderMapper.deleteById(stakeholderId);
|
||||||
|
if (rows == 0) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "stakeholder not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireProjectExists(long projectId) {
|
||||||
|
if (projectMapper.selectById(projectId) == null) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "project not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StakeholderResponse toStakeholderResponse(PlatformProjectStakeholder s) {
|
||||||
|
StakeholderResponse r = new StakeholderResponse();
|
||||||
|
r.setId(s.getId());
|
||||||
|
r.setProjectId(s.getProjectId());
|
||||||
|
r.setContactName(s.getContactName());
|
||||||
|
r.setContactRole(s.getContactRole());
|
||||||
|
r.setPhone(s.getPhone());
|
||||||
|
r.setEmail(s.getEmail());
|
||||||
|
r.setIsInternal(s.getIsInternal());
|
||||||
|
r.setCreatedAt(s.getCreatedAt());
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
private String resolvePhase(String phase) {
|
private String resolvePhase(String phase) {
|
||||||
return StringUtils.hasText(phase) ? phase.trim() : DEFAULT_PHASE;
|
return StringUtils.hasText(phase) ? phase.trim() : DEFAULT_PHASE;
|
||||||
}
|
}
|
||||||
|
|||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package cn.craftlabs.platform.api.web.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public class StakeholderRequest {
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 128)
|
||||||
|
private String contactName;
|
||||||
|
|
||||||
|
@Size(max = 64)
|
||||||
|
private String contactRole;
|
||||||
|
|
||||||
|
@Size(max = 32)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Size(max = 128)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
private Boolean isInternal;
|
||||||
|
|
||||||
|
public String getContactName() {
|
||||||
|
return contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactName(String contactName) {
|
||||||
|
this.contactName = contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactRole() {
|
||||||
|
return contactRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactRole(String contactRole) {
|
||||||
|
this.contactRole = contactRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsInternal() {
|
||||||
|
return isInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsInternal(Boolean isInternal) {
|
||||||
|
this.isInternal = isInternal;
|
||||||
|
}
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
package cn.craftlabs.platform.api.web.dto;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
|
||||||
|
public class StakeholderResponse {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long projectId;
|
||||||
|
private String contactName;
|
||||||
|
private String contactRole;
|
||||||
|
private String phone;
|
||||||
|
private String email;
|
||||||
|
private Boolean isInternal;
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProjectId() {
|
||||||
|
return projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProjectId(Long projectId) {
|
||||||
|
this.projectId = projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactName() {
|
||||||
|
return contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactName(String contactName) {
|
||||||
|
this.contactName = contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContactRole() {
|
||||||
|
return contactRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContactRole(String contactRole) {
|
||||||
|
this.contactRole = contactRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsInternal() {
|
||||||
|
return isInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsInternal(Boolean isInternal) {
|
||||||
|
this.isInternal = isInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE platform_project_stakeholder (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
project_id BIGINT NOT NULL REFERENCES platform_project(id),
|
||||||
|
contact_name VARCHAR(128) NOT NULL,
|
||||||
|
contact_role VARCHAR(64),
|
||||||
|
phone VARCHAR(32),
|
||||||
|
email VARCHAR(128),
|
||||||
|
is_internal BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_stakeholder_project ON platform_project_stakeholder(project_id);
|
||||||
@@ -437,3 +437,16 @@ export function updateJsonTemplate(id, body) {
|
|||||||
export function deleteJsonTemplate(id) {
|
export function deleteJsonTemplate(id) {
|
||||||
return axios.delete(`/api/v1/integration/json-templates/${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-select>
|
||||||
<el-button type="primary" :loading="loading" @click="load">查询</el-button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -38,10 +38,11 @@
|
|||||||
{{ phaseLabel(row.phase) }}
|
{{ phaseLabel(row.phase) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="160" fixed="right">
|
<el-table-column label="操作" width="220" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link @click="openEdit(row)">编辑</el-button>
|
<el-button type="primary" link @click="openStakeholderDialog(row)">干系人</el-button>
|
||||||
<el-button type="danger" link @click="onDelete(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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -100,6 +101,50 @@
|
|||||||
<el-button type="primary" :loading="saving" @click="submit">保存</el-button>
|
<el-button type="primary" :loading="saving" @click="submit">保存</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</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>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -114,6 +159,10 @@ import {
|
|||||||
updateProject,
|
updateProject,
|
||||||
deleteProject,
|
deleteProject,
|
||||||
getProjectPhaseDictionary,
|
getProjectPhaseDictionary,
|
||||||
|
listStakeholders,
|
||||||
|
addStakeholder,
|
||||||
|
updateStakeholder,
|
||||||
|
deleteStakeholder,
|
||||||
} from "../api/platform";
|
} from "../api/platform";
|
||||||
import { apiErrorMessage } from "../utils/apiErrorMessage";
|
import { apiErrorMessage } from "../utils/apiErrorMessage";
|
||||||
|
|
||||||
@@ -151,6 +200,128 @@ const rules = {
|
|||||||
|
|
||||||
const dialogTitle = computed(() => (editingId.value ? "编辑项目" : "新建项目"));
|
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 customerMap = computed(() => {
|
||||||
const m = new Map();
|
const m = new Map();
|
||||||
for (const c of customerOptions.value) {
|
for (const c of customerOptions.value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user