mirror of
https://gitee.com/san-bing/JChargePointProtocol
synced 2026-05-03 17:39:55 +08:00
充电枪编辑功能
This commit is contained in:
@@ -16,6 +16,7 @@ import sanbing.jcpp.app.adapter.request.StationUpdateRequest;
|
||||
import sanbing.jcpp.app.adapter.response.ApiResponse;
|
||||
import sanbing.jcpp.app.adapter.response.PageResponse;
|
||||
import sanbing.jcpp.app.adapter.response.StationOption;
|
||||
import sanbing.jcpp.app.adapter.response.StationPileCascaderOption;
|
||||
import sanbing.jcpp.app.dal.entity.Station;
|
||||
import sanbing.jcpp.app.exception.JCPPException;
|
||||
import sanbing.jcpp.app.service.StationService;
|
||||
@@ -104,4 +105,14 @@ public class StationController extends BaseController {
|
||||
List<StationOption> options = stationService.searchStationOptions(keyword, page, size);
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", options));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取充电站-充电桩级联选择器数据(用于级联选择组件)
|
||||
*/
|
||||
@GetMapping("/pile-cascader")
|
||||
public ResponseEntity<ApiResponse<List<StationPileCascaderOption>>> getStationPileCascaderOptions(
|
||||
@RequestParam(required = false) String keyword) {
|
||||
List<StationPileCascaderOption> options = stationService.getStationPileCascaderOptions(keyword);
|
||||
return ResponseEntity.ok(ApiResponse.success("查询成功", options));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,18 @@ public class GunUpdateRequest {
|
||||
@NoXss
|
||||
private String gunName;
|
||||
|
||||
@NotBlank(message = "枪号不能为空")
|
||||
private String gunNo;
|
||||
|
||||
@NotBlank(message = "充电枪编码不能为空")
|
||||
@NoXss
|
||||
private String gunCode;
|
||||
|
||||
@NotBlank(message = "所属充电站不能为空")
|
||||
private String stationId;
|
||||
|
||||
@NotBlank(message = "所属充电桩不能为空")
|
||||
private String pileId;
|
||||
|
||||
private GunRunStatusEnum runStatus;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 开源代码,仅供学习和交流研究使用,商用请联系三丙
|
||||
* 微信:mohan_88888
|
||||
* 抖音:程序员三丙
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
package sanbing.jcpp.app.adapter.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 充电站-充电桩级联选择器选项响应
|
||||
* 用于Ant Design Cascader组件
|
||||
*
|
||||
* @author 九筒
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class StationPileCascaderOption {
|
||||
|
||||
private String value; // 选项的值(充电站ID或充电桩ID)
|
||||
private String label; // 显示的标签
|
||||
private boolean isLeaf; // 是否为叶子节点
|
||||
private List<StationPileCascaderOption> children; // 子选项(充电站下的充电桩)
|
||||
|
||||
// 额外信息
|
||||
private String stationId; // 充电站ID(当是充电桩选项时)
|
||||
private String stationName; // 充电站名称
|
||||
private String stationCode; // 充电站编码
|
||||
private String pileId; // 充电桩ID(当是充电桩选项时)
|
||||
private String pileName; // 充电桩名称(当是充电桩选项时)
|
||||
private String pileCode; // 充电桩编码(当是充电桩选项时)
|
||||
|
||||
/**
|
||||
* 创建充电站选项
|
||||
*/
|
||||
public static StationPileCascaderOption createStationOption(UUID stationId, String stationName, String stationCode, List<StationPileCascaderOption> piles) {
|
||||
StationPileCascaderOption option = new StationPileCascaderOption();
|
||||
option.setValue(stationId.toString());
|
||||
option.setLabel(stationName + " (" + stationCode + ")");
|
||||
option.setLeaf(false);
|
||||
option.setChildren(piles);
|
||||
option.setStationId(stationId.toString());
|
||||
option.setStationName(stationName);
|
||||
option.setStationCode(stationCode);
|
||||
return option;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建充电桩选项
|
||||
*/
|
||||
public static StationPileCascaderOption createPileOption(UUID stationId, String stationName, String stationCode,
|
||||
UUID pileId, String pileName, String pileCode) {
|
||||
StationPileCascaderOption option = new StationPileCascaderOption();
|
||||
option.setValue(pileId.toString());
|
||||
option.setLabel(pileName + " (" + pileCode + ")");
|
||||
option.setLeaf(true);
|
||||
option.setChildren(null);
|
||||
option.setStationId(stationId.toString());
|
||||
option.setStationName(stationName);
|
||||
option.setStationCode(stationCode);
|
||||
option.setPileId(pileId.toString());
|
||||
option.setPileName(pileName);
|
||||
option.setPileCode(pileCode);
|
||||
return option;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import sanbing.jcpp.app.adapter.request.StationQueryRequest;
|
||||
import sanbing.jcpp.app.adapter.request.StationUpdateRequest;
|
||||
import sanbing.jcpp.app.adapter.response.PageResponse;
|
||||
import sanbing.jcpp.app.adapter.response.StationOption;
|
||||
import sanbing.jcpp.app.adapter.response.StationPileCascaderOption;
|
||||
import sanbing.jcpp.app.dal.entity.Station;
|
||||
import sanbing.jcpp.app.exception.JCPPException;
|
||||
|
||||
@@ -58,4 +59,9 @@ public interface StationService {
|
||||
* 搜索充电站选项列表(支持关键字搜索和分页)
|
||||
*/
|
||||
List<StationOption> searchStationOptions(String keyword, int page, int size);
|
||||
|
||||
/**
|
||||
* 获取充电站-充电桩级联选择器数据(用于级联选择组件)
|
||||
*/
|
||||
List<StationPileCascaderOption> getStationPileCascaderOptions(String keyword);
|
||||
}
|
||||
|
||||
@@ -86,10 +86,10 @@ public class DefaultGunService implements GunService {
|
||||
.createdTime(existingGun.getCreatedTime())
|
||||
.updatedTime(LocalDateTime.now()) // 更新时设置更新时间
|
||||
.gunName(request.getGunName())
|
||||
.gunNo(existingGun.getGunNo()) // 编号不允许修改
|
||||
.gunCode(existingGun.getGunCode()) // 编码不允许修改
|
||||
.stationId(existingGun.getStationId()) // 所属充电站不允许修改
|
||||
.pileId(existingGun.getPileId()) // 所属充电桩不允许修改
|
||||
.gunNo(request.getGunNo()) // 允许修改枪号
|
||||
.gunCode(request.getGunCode()) // 允许修改编码
|
||||
.stationId(UUID.fromString(request.getStationId())) // 允许修改所属充电站
|
||||
.pileId(UUID.fromString(request.getPileId())) // 允许修改所属充电桩
|
||||
.additionalInfo(existingGun.getAdditionalInfo())
|
||||
.version(existingGun.getVersion())
|
||||
.build();
|
||||
|
||||
@@ -17,6 +17,8 @@ import sanbing.jcpp.app.adapter.request.StationQueryRequest;
|
||||
import sanbing.jcpp.app.adapter.request.StationUpdateRequest;
|
||||
import sanbing.jcpp.app.adapter.response.PageResponse;
|
||||
import sanbing.jcpp.app.adapter.response.StationOption;
|
||||
import sanbing.jcpp.app.adapter.response.StationPileCascaderOption;
|
||||
import sanbing.jcpp.app.dal.entity.Pile;
|
||||
import sanbing.jcpp.app.dal.entity.Station;
|
||||
import sanbing.jcpp.app.dal.mapper.PileMapper;
|
||||
import sanbing.jcpp.app.dal.mapper.StationMapper;
|
||||
@@ -27,6 +29,7 @@ import sanbing.jcpp.infrastructure.util.jackson.JacksonUtil;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -190,4 +193,55 @@ public class DefaultStationService implements StationService {
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StationPileCascaderOption> getStationPileCascaderOptions(String keyword) {
|
||||
// 查询充电站
|
||||
QueryWrapper<Station> stationWrapper = new QueryWrapper<>();
|
||||
stationWrapper.select("id", "station_name", "station_code");
|
||||
|
||||
// 如果有关键字,按站名或编码模糊搜索
|
||||
if (StringUtils.hasText(keyword)) {
|
||||
stationWrapper.and(w -> w.like("station_name", keyword)
|
||||
.or()
|
||||
.like("station_code", keyword));
|
||||
}
|
||||
|
||||
stationWrapper.orderByAsc("station_name");
|
||||
List<Station> stations = stationMapper.selectList(stationWrapper);
|
||||
|
||||
if (stations.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// 查询所有充电桩
|
||||
QueryWrapper<Pile> pileWrapper = new QueryWrapper<>();
|
||||
pileWrapper.select("id", "pile_name", "pile_code", "station_id")
|
||||
.in("station_id", stations.stream().map(Station::getId).collect(Collectors.toList()))
|
||||
.orderByAsc("pile_name");
|
||||
|
||||
List<Pile> piles = pileMapper.selectList(pileWrapper);
|
||||
|
||||
// 按充电站ID分组充电桩
|
||||
Map<UUID, List<Pile>> pilesByStation = piles.stream()
|
||||
.collect(Collectors.groupingBy(Pile::getStationId));
|
||||
|
||||
// 构建级联选择器数据
|
||||
return stations.stream()
|
||||
.map(station -> {
|
||||
List<Pile> stationPiles = pilesByStation.getOrDefault(station.getId(), List.of());
|
||||
|
||||
List<StationPileCascaderOption> pileOptions = stationPiles.stream()
|
||||
.map(pile -> StationPileCascaderOption.createPileOption(
|
||||
station.getId(), station.getStationName(), station.getStationCode(),
|
||||
pile.getId(), pile.getPileName(), pile.getPileCode()
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return StationPileCascaderOption.createStationOption(
|
||||
station.getId(), station.getStationName(), station.getStationCode(), pileOptions
|
||||
);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import React, {useEffect, useMemo, useState} from 'react';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
Col,
|
||||
Dropdown,
|
||||
@@ -29,7 +30,7 @@ import {getErrorMessage} from '../services/api';
|
||||
import * as gunService from '../services/gunService';
|
||||
import * as stationService from '../services/stationService';
|
||||
import {pileService} from '../services/pileService';
|
||||
import type {Gun, GunCreateRequest, PileOption, StationOption} from '../types';
|
||||
import type {Gun, GunCreateRequest, GunUpdateRequest, PileOption, StationOption} from '../types';
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
@@ -40,6 +41,7 @@ const GunManagement: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [stationOptions, setStationOptions] = useState<StationOption[]>([]);
|
||||
const [pileOptions, setPileOptions] = useState<PileOption[]>([]);
|
||||
const [cascaderOptions, setCascaderOptions] = useState<any[]>([]);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [modalLoading, setModalLoading] = useState(false);
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
@@ -236,9 +238,6 @@ const GunManagement: React.FC = () => {
|
||||
<Button type="link" size="small" onClick={() => handleEdit(record)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Button type="link" size="small" onClick={() => handleView(record)}>
|
||||
查看
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确认删除充电枪"
|
||||
description={
|
||||
@@ -429,6 +428,16 @@ const GunManagement: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 加载级联选择器数据
|
||||
const loadCascaderOptions = async () => {
|
||||
try {
|
||||
const response = await stationService.getStationPileCascaderOptions();
|
||||
setCascaderOptions(Array.isArray(response) ? response : []);
|
||||
} catch (error: any) {
|
||||
console.error('加载级联选择器数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 监听搜索参数变化
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
@@ -439,6 +448,7 @@ const GunManagement: React.FC = () => {
|
||||
useEffect(() => {
|
||||
loadStationOptions();
|
||||
loadPileOptions();
|
||||
loadCascaderOptions();
|
||||
}, []);
|
||||
|
||||
// 处理表格变化
|
||||
@@ -496,25 +506,23 @@ const GunManagement: React.FC = () => {
|
||||
setModalVisible(true);
|
||||
form.setFieldsValue({
|
||||
...record,
|
||||
gunNo: record.gunNo.toString()
|
||||
gunNo: record.gunNo,
|
||||
stationPile: [record.stationId, record.pileId] // 设置级联选择器的值
|
||||
});
|
||||
};
|
||||
|
||||
// 处理查看
|
||||
const handleView = (record: Gun) => {
|
||||
showMessage.info('查看功能待实现');
|
||||
};
|
||||
|
||||
// 生成充电枪编码
|
||||
const handleGenerateGunCode = () => {
|
||||
const pileId = form.getFieldValue('pileId');
|
||||
const stationPile = form.getFieldValue('stationPile');
|
||||
const gunNo = form.getFieldValue('gunNo');
|
||||
|
||||
if (!pileId || !gunNo) {
|
||||
showMessage.warning('请先选择充电桩和填写枪号');
|
||||
if (!stationPile || stationPile.length !== 2 || !gunNo) {
|
||||
showMessage.warning('请先选择充电站和充电桩,并填写枪号');
|
||||
return;
|
||||
}
|
||||
|
||||
const pileId = stationPile[1];
|
||||
const selectedPile = pileOptions.find(p => p.id === pileId);
|
||||
if (selectedPile) {
|
||||
const code = generateGunCode(selectedPile.pileCode, gunNo);
|
||||
@@ -529,16 +537,38 @@ const GunManagement: React.FC = () => {
|
||||
setModalLoading(true);
|
||||
|
||||
if (isEdit && currentRecord) {
|
||||
// 编辑功能待实现
|
||||
showMessage.info('编辑功能待实现');
|
||||
// 编辑充电枪
|
||||
// 从级联选择器值中获取充电站ID和充电桩ID
|
||||
const cascaderValue = values.stationPile;
|
||||
if (!cascaderValue || cascaderValue.length !== 2) {
|
||||
showMessage.error('请选择充电站和充电桩');
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData: GunUpdateRequest = {
|
||||
gunName: values.gunName,
|
||||
gunNo: values.gunNo,
|
||||
gunCode: values.gunCode,
|
||||
stationId: cascaderValue[0], // 充电站ID
|
||||
pileId: cascaderValue[1] // 充电桩ID
|
||||
};
|
||||
await gunService.updateGun(currentRecord.id, updateData);
|
||||
showMessage.success('充电枪更新成功');
|
||||
} else {
|
||||
// 新建充电枪
|
||||
// 从级联选择器值中获取充电站ID和充电桩ID
|
||||
const cascaderValue = values.stationPile;
|
||||
if (!cascaderValue || cascaderValue.length !== 2) {
|
||||
showMessage.error('请选择充电站和充电桩');
|
||||
return;
|
||||
}
|
||||
|
||||
const createData: GunCreateRequest = {
|
||||
gunName: values.gunName,
|
||||
gunNo: values.gunNo,
|
||||
gunCode: values.gunCode,
|
||||
stationId: values.stationId,
|
||||
pileId: values.pileId
|
||||
stationId: cascaderValue[0], // 充电站ID
|
||||
pileId: cascaderValue[1] // 充电桩ID
|
||||
};
|
||||
await gunService.createGun(createData);
|
||||
showMessage.success('充电枪创建成功');
|
||||
@@ -870,52 +900,25 @@ const GunManagement: React.FC = () => {
|
||||
<Input placeholder="请输入充电枪名称" />
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label="所属充电站"
|
||||
name="stationId"
|
||||
rules={[{ required: true, message: '请选择充电站' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择充电站"
|
||||
showSearch
|
||||
allowClear
|
||||
filterOption={(input, option) =>
|
||||
(option?.children as unknown as string)?.toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
>
|
||||
{stationOptions.map(station => (
|
||||
<Select.Option key={station.id} value={station.id}>
|
||||
{station.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label="所属充电桩"
|
||||
name="pileId"
|
||||
rules={[{ required: true, message: '请选择充电桩' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择充电桩"
|
||||
showSearch
|
||||
allowClear
|
||||
filterOption={(input, option) =>
|
||||
(option?.children as unknown as string)?.toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
>
|
||||
{pileOptions.map(pile => (
|
||||
<Select.Option key={pile.id} value={pile.id}>
|
||||
{pile.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item
|
||||
label="所属充电站/充电桩"
|
||||
name="stationPile"
|
||||
rules={[{ required: true, message: '请选择充电站和充电桩' }]}
|
||||
>
|
||||
<Cascader
|
||||
options={cascaderOptions}
|
||||
placeholder="请选择充电站和充电桩"
|
||||
showSearch={{
|
||||
filter: (inputValue, path) =>
|
||||
path.some(option =>
|
||||
option.label.toLowerCase().includes(inputValue.toLowerCase())
|
||||
)
|
||||
}}
|
||||
disabled={false}
|
||||
changeOnSelect={false}
|
||||
expandTrigger="hover"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
@@ -935,13 +938,13 @@ const GunManagement: React.FC = () => {
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入充电枪编码"
|
||||
disabled={isEdit}
|
||||
disabled={false}
|
||||
suffix={
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={handleGenerateGunCode}
|
||||
disabled={isEdit}
|
||||
disabled={false}
|
||||
style={{
|
||||
height: '24px',
|
||||
lineHeight: '24px',
|
||||
|
||||
@@ -299,9 +299,6 @@ const PileManagement: React.FC = () => {
|
||||
<Button type="link" size="small" onClick={() => handleEdit(record)} style={{ padding: '0 4px' }}>
|
||||
编辑
|
||||
</Button>
|
||||
<Button type="link" size="small" onClick={() => handleView(record)} style={{ padding: '0 4px' }}>
|
||||
查看
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确认删除充电桩"
|
||||
description={
|
||||
@@ -597,11 +594,6 @@ const PileManagement: React.FC = () => {
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// 查看充电桩详情
|
||||
const handleView = (record: Pile) => {
|
||||
console.log('查看充电桩详情:', record);
|
||||
showMessage.info('查看功能暂未实现,请查看控制台日志');
|
||||
};
|
||||
|
||||
// 生成充电桩编码
|
||||
const handleGeneratePileCode = () => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 付费课程知识星球:https://t.zsxq.com/aKtXo
|
||||
*/
|
||||
import {api} from './api';
|
||||
import type {Gun, GunCreateRequest, PageResponse, QueryParams} from '../types';
|
||||
import type {Gun, GunCreateRequest, GunUpdateRequest, PageResponse, QueryParams} from '../types';
|
||||
|
||||
export const getGuns = async (params: QueryParams): Promise<PageResponse<Gun>> => {
|
||||
const response = await api.get('/api/guns', { params });
|
||||
@@ -17,7 +17,7 @@ export const createGun = async (data: GunCreateRequest): Promise<Gun> => {
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateGun = async (id: string, data: Partial<Gun>): Promise<Gun> => {
|
||||
export const updateGun = async (id: string, data: GunUpdateRequest): Promise<Gun> => {
|
||||
const response = await api.put(`/api/guns/${id}`, data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
@@ -39,4 +39,11 @@ export const searchStationOptions = async (params: StationSearchRequest): Promis
|
||||
export const getStationOptions = async (): Promise<StationOption[]> => {
|
||||
const response = await api.get('/api/stations/options');
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
// 获取充电站-充电桩级联选择器数据
|
||||
export const getStationPileCascaderOptions = async (keyword?: string): Promise<any[]> => {
|
||||
const params = keyword ? { keyword } : {};
|
||||
const response = await api.get('/api/stations/pile-cascader', { params });
|
||||
return response.data.data;
|
||||
};
|
||||
@@ -120,7 +120,7 @@ export interface Gun {
|
||||
id: string;
|
||||
gunName: string;
|
||||
gunCode: string;
|
||||
gunNo: number;
|
||||
gunNo: string;
|
||||
stationId: string;
|
||||
stationName?: string; // 所属充电站名称
|
||||
pileId: string;
|
||||
@@ -144,6 +144,7 @@ export interface GunCreateRequest {
|
||||
export interface GunUpdateRequest {
|
||||
gunName: string;
|
||||
gunNo: string;
|
||||
gunCode: string;
|
||||
stationId: string;
|
||||
pileId: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user