mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(m1): add customer detail aggregation view
This commit is contained in:
+8
@@ -8,6 +8,7 @@ import jakarta.validation.Valid;
|
|||||||
import jakarta.validation.constraints.Max;
|
import jakarta.validation.constraints.Max;
|
||||||
import jakarta.validation.constraints.Min;
|
import jakarta.validation.constraints.Min;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -20,6 +21,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;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户 API。{@code DELETE /{id}} 为<strong>软删除</strong>:将 {@code status} 置为 {@code INACTIVE}(可重复调用)。
|
* 客户 API。{@code DELETE /{id}} 为<strong>软删除</strong>:将 {@code status} 置为 {@code INACTIVE}(可重复调用)。
|
||||||
*/
|
*/
|
||||||
@@ -53,6 +56,11 @@ public class CustomerController {
|
|||||||
return customerService.getById(id);
|
return customerService.getById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/summary")
|
||||||
|
public ResponseEntity<Map<String, Object>> getSummary(@PathVariable Long id) {
|
||||||
|
return ResponseEntity.ok(customerService.getCustomerSummary(id));
|
||||||
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
public CustomerResponse update(
|
public CustomerResponse update(
|
||||||
@PathVariable("id") long id, @Valid @RequestBody CustomerRequest request) {
|
@PathVariable("id") long id, @Valid @RequestBody CustomerRequest request) {
|
||||||
|
|||||||
+18
-1
@@ -3,6 +3,8 @@ package cn.craftlabs.platform.api.service;
|
|||||||
import cn.craftlabs.platform.api.domain.CustomerStatus;
|
import cn.craftlabs.platform.api.domain.CustomerStatus;
|
||||||
import cn.craftlabs.platform.api.persistence.customer.PlatformCustomer;
|
import cn.craftlabs.platform.api.persistence.customer.PlatformCustomer;
|
||||||
import cn.craftlabs.platform.api.persistence.customer.PlatformCustomerMapper;
|
import cn.craftlabs.platform.api.persistence.customer.PlatformCustomerMapper;
|
||||||
|
import cn.craftlabs.platform.api.persistence.project.PlatformProject;
|
||||||
|
import cn.craftlabs.platform.api.persistence.project.PlatformProjectMapper;
|
||||||
import cn.craftlabs.platform.api.web.dto.CustomerRequest;
|
import cn.craftlabs.platform.api.web.dto.CustomerRequest;
|
||||||
import cn.craftlabs.platform.api.web.dto.CustomerResponse;
|
import cn.craftlabs.platform.api.web.dto.CustomerResponse;
|
||||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||||
@@ -18,15 +20,18 @@ import org.springframework.web.server.ResponseStatusException;
|
|||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CustomerService {
|
public class CustomerService {
|
||||||
|
|
||||||
private final PlatformCustomerMapper customerMapper;
|
private final PlatformCustomerMapper customerMapper;
|
||||||
|
private final PlatformProjectMapper projectMapper;
|
||||||
|
|
||||||
public CustomerService(PlatformCustomerMapper customerMapper) {
|
public CustomerService(PlatformCustomerMapper customerMapper, PlatformProjectMapper projectMapper) {
|
||||||
this.customerMapper = customerMapper;
|
this.customerMapper = customerMapper;
|
||||||
|
this.projectMapper = projectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@@ -113,6 +118,18 @@ public class CustomerService {
|
|||||||
customerMapper.updateById(c);
|
customerMapper.updateById(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Map<String, Object> getCustomerSummary(Long customerId) {
|
||||||
|
Map<String, Object> result = new java.util.LinkedHashMap<>();
|
||||||
|
var projectQuery = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PlatformProject>();
|
||||||
|
projectQuery.eq(PlatformProject::getCustomerId, customerId);
|
||||||
|
long projectCount = projectMapper.selectCount(projectQuery);
|
||||||
|
result.put("projectCount", projectCount);
|
||||||
|
result.put("contractCount", 0);
|
||||||
|
result.put("snCount", 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public void requireExists(long id) {
|
public void requireExists(long id) {
|
||||||
if (customerMapper.selectById(id) == null) {
|
if (customerMapper.selectById(id) == null) {
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ export function deleteProject(id) {
|
|||||||
return axios.delete(`/api/v1/projects/${id}`);
|
return axios.delete(`/api/v1/projects/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCustomerSummary(id) {
|
||||||
|
return axios.get(`/api/v1/customers/${id}/summary`);
|
||||||
|
}
|
||||||
|
|
||||||
export function getProjectPhaseDictionary() {
|
export function getProjectPhaseDictionary() {
|
||||||
return axios.get("/api/v1/dictionaries/PROJECT_PHASE");
|
return axios.get("/api/v1/dictionaries/PROJECT_PHASE");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ const routes = [
|
|||||||
component: () => import("../views/HomeView.vue"),
|
component: () => import("../views/HomeView.vue"),
|
||||||
meta: { roles: ["SYS_ADMIN", "DEVELOPER", "OPS"] },
|
meta: { roles: ["SYS_ADMIN", "DEVELOPER", "OPS"] },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "customers/:id",
|
||||||
|
name: "customer-detail",
|
||||||
|
component: () => import("../views/CustomerDetailView.vue"),
|
||||||
|
meta: { roles: ["SYS_ADMIN", "DEVELOPER"], title: "客户详情" },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "customers",
|
path: "customers",
|
||||||
name: "customers",
|
name: "customers",
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { useAuthStore } from '../stores/auth'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { apiErrorMessage } from '../utils/apiErrorMessage'
|
||||||
|
import { getCustomerSummary } from '../api/platform'
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
const summary = ref(null)
|
||||||
|
const customerId = route.params.id
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
auth.restoreAxiosAuth()
|
||||||
|
await loadSummary()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadSummary() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const { data } = await getCustomerSummary(customerId)
|
||||||
|
summary.value = data
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error(apiErrorMessage(e, '加载客户详情失败'))
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-button link type="primary" @click="router.push('/customers')">← 客户列表</el-button>
|
||||||
|
<h2>客户详情 #{{ customerId }}</h2>
|
||||||
|
|
||||||
|
<el-row :gutter="16" v-loading="loading" style="margin-top:16px">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header>关联项目</template>
|
||||||
|
<div style="font-size:2em;font-weight:bold;text-align:center">{{ summary?.projectCount ?? '—' }}</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header>在履约合同</template>
|
||||||
|
<div style="font-size:2em;font-weight:bold;text-align:center">{{ summary?.contractCount ?? '—' }}</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header>在途 SN</template>
|
||||||
|
<div style="font-size:2em;font-weight:bold;text-align:center">{{ summary?.snCount ?? '—' }}</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -20,12 +20,13 @@
|
|||||||
<el-table v-loading="loading" :data="rows" stripe style="width: 100%">
|
<el-table v-loading="loading" :data="rows" stripe style="width: 100%">
|
||||||
<el-table-column prop="name" label="客户名称" min-width="160" show-overflow-tooltip />
|
<el-table-column prop="name" label="客户名称" min-width="160" show-overflow-tooltip />
|
||||||
<el-table-column prop="creditCode" label="统一社会信用代码" min-width="200" show-overflow-tooltip />
|
<el-table-column prop="creditCode" label="统一社会信用代码" min-width="200" show-overflow-tooltip />
|
||||||
<el-table-column label="操作" width="160" fixed="right">
|
<el-table-column label="操作" width="200" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link @click="openEdit(row)">编辑</el-button>
|
<el-button type="primary" link @click="goDetail(row.id)">详情</el-button>
|
||||||
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
|
<el-button type="primary" link @click="openEdit(row)">编辑</el-button>
|
||||||
</template>
|
<el-button type="danger" link @click="onDelete(row)">删除</el-button>
|
||||||
</el-table-column>
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<div class="pager">
|
<div class="pager">
|
||||||
@@ -73,11 +74,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted } from "vue";
|
import { ref, reactive, computed, onMounted } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
import { useAuthStore } from "../stores/auth";
|
import { useAuthStore } from "../stores/auth";
|
||||||
import { listCustomers, createCustomer, updateCustomer, deleteCustomer } from "../api/platform";
|
import { listCustomers, createCustomer, updateCustomer, deleteCustomer } from "../api/platform";
|
||||||
import { apiErrorMessage } from "../utils/apiErrorMessage";
|
import { apiErrorMessage } from "../utils/apiErrorMessage";
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
@@ -115,6 +118,10 @@ function onSizeChange() {
|
|||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goDetail(id) {
|
||||||
|
router.push(`/customers/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user