hejianhao
2026-08-18 042646b38859ee174dc21aa85efbd1034c843075
feat0806: 保存省代理
7个文件已修改
489 ■■■■■ 已修改文件
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/controller/ProvincialAgentController.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/dto/ProvincialAgentSaveRequest.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/service/ProvincialAgentAppService.java 218 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/vo/ProvincialAgentDetailVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/vo/ProvincialAgentListItemVO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/live/entity/Clinic.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin/src/pages/member/agent/index.tsx 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/controller/ProvincialAgentController.java
@@ -1,4 +1,5 @@
package com.tcm.training.admin.agent.controller;
import com.tcm.training.admin.agent.dto.ProvincialAgentSaveRequest;
import com.tcm.training.admin.agent.service.ProvincialAgentAppService;
import com.tcm.training.admin.agent.vo.ProvincialAgentDetailVO;
@@ -7,13 +8,42 @@
import com.tcm.training.admin.common.PageResponse;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Validated @RestController @RequestMapping({"/api/v1","/api"})
@Validated
@RestController
@RequestMapping({"/api/v1", "/api"})
public class ProvincialAgentController {
 private final ProvincialAgentAppService service; public ProvincialAgentController(ProvincialAgentAppService service){this.service=service;}
 @GetMapping("/provincial-agents") public ApiResponse<PageResponse<ProvincialAgentListItemVO>> page(@RequestParam(defaultValue="1") int page,@RequestParam(defaultValue="10") int pageSize,@RequestParam(required=false) String agentName,@RequestParam(required=false) String province){return ApiResponse.ok(service.page(page,pageSize,agentName,province));}
 @GetMapping("/provincial-agents/{id}") public ApiResponse<ProvincialAgentDetailVO> detail(@PathVariable Long id){return ApiResponse.ok(service.detail(id));}
 @PostMapping("/provincial-agents") public ApiResponse<ProvincialAgentDetailVO> create(@Valid @RequestBody ProvincialAgentSaveRequest r){return ApiResponse.ok(service.create(r));}
 @PutMapping("/provincial-agents/{id}") public ApiResponse<ProvincialAgentDetailVO> update(@PathVariable Long id,@Valid @RequestBody ProvincialAgentSaveRequest r){return ApiResponse.ok(service.update(id,r));}
 @DeleteMapping("/provincial-agents/{id}") public ApiResponse<Void> delete(@PathVariable Long id){service.delete(id);return ApiResponse.ok(null);}
    private final ProvincialAgentAppService service;
    public ProvincialAgentController(ProvincialAgentAppService service) {
        this.service = service;
    }
    @GetMapping("/provincial-agents")
    public ApiResponse<PageResponse<ProvincialAgentListItemVO>> page(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int pageSize, @RequestParam(required = false) String agentName, @RequestParam(required = false) String regionCode) {
        return ApiResponse.ok(service.page(page, pageSize, agentName, regionCode));
    }
    @GetMapping("/provincial-agents/{id}")
    public ApiResponse<ProvincialAgentDetailVO> detail(@PathVariable Long id) {
        return ApiResponse.ok(service.detail(id));
    }
    @PostMapping("/provincial-agents")
    public ApiResponse<ProvincialAgentDetailVO> create(@Valid @RequestBody ProvincialAgentSaveRequest r) {
        return ApiResponse.ok(service.create(r));
    }
    @PutMapping("/provincial-agents/{id}")
    public ApiResponse<ProvincialAgentDetailVO> update(@PathVariable Long id, @Valid @RequestBody ProvincialAgentSaveRequest r) {
        return ApiResponse.ok(service.update(id, r));
    }
    @DeleteMapping("/provincial-agents/{id}")
    public ApiResponse<Void> delete(@PathVariable Long id) {
        service.delete(id);
        return ApiResponse.ok(null);
    }
}
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/dto/ProvincialAgentSaveRequest.java
@@ -15,20 +15,29 @@
    @NotBlank(message = "请输入代理负责人")
    @Size(max = 50, message = "代理负责人不能超过50个字")
    private String agentName;
    @NotBlank(message = "请选择负责省份")
    private String province;
    @Size(max = 100) private String bankName;
    @Size(max = 30) private String accountNumber;
    @Size(max = 50) private String accountHolder;
    @Size(max = 100) private String branchName;
    @Size(max = 200) private String contactInfo;
    @Size(max = 255) private String address;
    @Valid private List<RegionItem> regions = new ArrayList<RegionItem>();
    @Size(max = 100)
    private String bankName;
    @Size(max = 30)
    private String accountNumber;
    @Size(max = 50)
    private String accountHolder;
    @Size(max = 100)
    private String branchName;
    @Size(max = 200)
    private String contactInfo;
    @Size(max = 255)
    private String address;
    @Valid
    @Size(min = 1, message = "请选择负责省份")
    private List<RegionItem> regions = new ArrayList<RegionItem>();
    @Getter @Setter
    @Getter
    @Setter
    public static class RegionItem {
        @NotBlank(message = "区域编码不能为空") private String regionCode;
        @Size(max = 100) private String regionName;
        @NotBlank(message = "区域编码不能为空")
        private String regionCode;
        @Size(max = 100)
        private String regionName;
        private Integer regionLevel;
    }
}
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/service/ProvincialAgentAppService.java
@@ -1,6 +1,7 @@
package com.tcm.training.admin.agent.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tcm.training.admin.agent.dto.ProvincialAgentSaveRequest;
@@ -10,7 +11,11 @@
import com.tcm.training.admin.agent.mapper.ProvincialAgentMapper;
import com.tcm.training.admin.agent.vo.ProvincialAgentDetailVO;
import com.tcm.training.admin.agent.vo.ProvincialAgentListItemVO;
import com.tcm.training.admin.app.register.entity.SysRegion;
import com.tcm.training.admin.app.register.mapper.SysRegionMapper;
import com.tcm.training.admin.common.PageResponse;
import com.tcm.training.admin.live.entity.Clinic;
import com.tcm.training.admin.live.mapper.ClinicMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -21,18 +26,31 @@
public class ProvincialAgentAppService {
    private final ProvincialAgentMapper agentMapper;
    private final AgentRegionMapper regionMapper;
    private final SysRegionMapper sysRegionMapper;
    private final ClinicMapper clinicMapper;
    public ProvincialAgentAppService(ProvincialAgentMapper agentMapper, AgentRegionMapper regionMapper) {
    public ProvincialAgentAppService(ProvincialAgentMapper agentMapper, AgentRegionMapper regionMapper,
                                     SysRegionMapper sysRegionMapper, ClinicMapper clinicMapper) {
        this.agentMapper = agentMapper;
        this.regionMapper = regionMapper;
        this.sysRegionMapper = sysRegionMapper;
        this.clinicMapper = clinicMapper;
    }
    public PageResponse<ProvincialAgentListItemVO> page(int page, int pageSize, String agentName, String province) {
        LambdaQueryWrapper<ProvincialAgent> q = new LambdaQueryWrapper<ProvincialAgent>().eq(ProvincialAgent::getDeleted, 0)
                .like(agentName != null && !agentName.trim().isEmpty(), ProvincialAgent::getAgentName, trim(agentName))
                .like(province != null && !province.trim().isEmpty(), ProvincialAgent::getProvince, trim(province))
    public PageResponse<ProvincialAgentListItemVO> page(int page, int pageSize, String agentName, String regionCode) {
        LambdaQueryWrapper<ProvincialAgent> query = new LambdaQueryWrapper<ProvincialAgent>()
                .eq(ProvincialAgent::getDeleted, 0)
                .like(trim(agentName) != null, ProvincialAgent::getAgentName, trim(agentName))
                .orderByDesc(ProvincialAgent::getCreatedAt).orderByDesc(ProvincialAgent::getId);
        IPage<ProvincialAgent> result = agentMapper.selectPage(new Page<ProvincialAgent>(page, pageSize), q);
        if (trim(regionCode) != null) {
            List<Long> ids = new ArrayList<Long>();
            for (AgentRegion region : regionMapper.selectList(new LambdaQueryWrapper<AgentRegion>()
                    .eq(AgentRegion::getRegionCode, trim(regionCode))))
                ids.add(region.getAgentId());
            if (ids.isEmpty()) return PageResponse.of(new ArrayList<ProvincialAgentListItemVO>(), 0, page, pageSize);
            query.in(ProvincialAgent::getId, ids);
        }
        IPage<ProvincialAgent> result = agentMapper.selectPage(new Page<ProvincialAgent>(page, pageSize), query);
        List<ProvincialAgentListItemVO> list = new ArrayList<ProvincialAgentListItemVO>();
        for (ProvincialAgent item : result.getRecords()) list.add(toList(item));
        return PageResponse.of(list, result.getTotal(), result.getCurrent(), result.getSize());
@@ -44,21 +62,26 @@
    @Transactional
    public ProvincialAgentDetailVO create(ProvincialAgentSaveRequest request) {
        ProvincialAgent a = new ProvincialAgent();
        fill(a, request);
        a.setDeleted(0);
        agentMapper.insert(a);
        saveRegions(a, request);
        return detail(a.getId());
        boolean firstAgent = agentMapper.selectCount(new LambdaQueryWrapper<ProvincialAgent>()
                .eq(ProvincialAgent::getDeleted, 0)) == 0;
        ProvincialAgent agent = new ProvincialAgent();
        fill(agent, request);
        agent.setDeleted(0);
        agentMapper.insert(agent);
        saveRegions(agent, request);
        if (firstAgent) {
            clinicMapper.update(null, new LambdaUpdateWrapper<Clinic>().set(Clinic::getAgentId, agent.getId()));
        }
        return detail(agent.getId());
    }
    @Transactional
    public ProvincialAgentDetailVO update(Long id, ProvincialAgentSaveRequest request) {
        ProvincialAgent a = require(id);
        fill(a, request);
        agentMapper.updateById(a);
        ProvincialAgent agent = require(id);
        fill(agent, request);
        agentMapper.updateById(agent);
        regionMapper.delete(new LambdaQueryWrapper<AgentRegion>().eq(AgentRegion::getAgentId, id));
        saveRegions(a, request);
        saveRegions(agent, request);
        return detail(id);
    }
@@ -68,88 +91,117 @@
        agentMapper.deleteById(id);
    }
    private void fill(ProvincialAgent a, ProvincialAgentSaveRequest r) {
        a.setAgentName(trim(r.getAgentName()));
        a.setProvince(trim(r.getProvince()));
        a.setBankName(trim(r.getBankName()));
        a.setAccountNumber(trim(r.getAccountNumber()));
        a.setAccountHolder(trim(r.getAccountHolder()));
        a.setBranchName(trim(r.getBranchName()));
        a.setContactInfo(trim(r.getContactInfo()));
        a.setAddress(trim(r.getAddress()));
    private void fill(ProvincialAgent agent, ProvincialAgentSaveRequest request) {
        agent.setAgentName(trim(request.getAgentName()));
        agent.setProvince("");
        agent.setBankName(trim(request.getBankName()));
        agent.setAccountNumber(trim(request.getAccountNumber()));
        agent.setAccountHolder(trim(request.getAccountHolder()));
        agent.setBranchName(trim(request.getBranchName()));
        agent.setContactInfo(trim(request.getContactInfo()));
        agent.setAddress(trim(request.getAddress()));
    }
    private void saveRegions(ProvincialAgent a, ProvincialAgentSaveRequest r) {
        if (r.getRegions() == null) return;
        for (ProvincialAgentSaveRequest.RegionItem x : r.getRegions()) {
            if (x == null || trim(x.getRegionCode()) == null) continue;
            AgentRegion ar = new AgentRegion();
            ar.setAgentId(a.getId());
            ar.setAgentName(a.getAgentName());
            ar.setRegionCode(trim(x.getRegionCode()));
            ar.setRegionName(trim(x.getRegionName()));
            ar.setRegionLevel(x.getRegionLevel());
            regionMapper.insert(ar);
    private void saveRegions(ProvincialAgent agent, ProvincialAgentSaveRequest request) {
        if (request.getRegions() == null) return;
        for (ProvincialAgentSaveRequest.RegionItem item : request.getRegions()) {
            if (item == null || trim(item.getRegionCode()) == null) continue;
            RegionInfo info = resolveRegion(item.getRegionCode());
            AgentRegion region = new AgentRegion();
            region.setAgentId(agent.getId());
            region.setAgentName(agent.getAgentName());
            region.setRegionCode(trim(item.getRegionCode()));
            region.setRegionName(info.name);
            region.setRegionLevel(info.path.size());
            regionMapper.insert(region);
        }
    }
    private ProvincialAgent require(Long id) {
        ProvincialAgent a = agentMapper.selectOne(new LambdaQueryWrapper<ProvincialAgent>().eq(ProvincialAgent::getId, id).eq(ProvincialAgent::getDeleted, 0).last("limit 1"));
        if (a == null) throw new IllegalArgumentException("省代理不存在");
        return a;
        ProvincialAgent agent = agentMapper.selectOne(new LambdaQueryWrapper<ProvincialAgent>()
                .eq(ProvincialAgent::getId, id).eq(ProvincialAgent::getDeleted, 0).last("limit 1"));
        if (agent == null) throw new IllegalArgumentException("省代理不存在");
        return agent;
    }
    private ProvincialAgentListItemVO toList(ProvincialAgent a) {
        ProvincialAgentListItemVO v = new ProvincialAgentListItemVO();
        v.setId(a.getId());
        v.setAgentName(a.getAgentName());
        v.setProvince(a.getProvince());
        v.setContactInfo(a.getContactInfo());
        v.setAddress(a.getAddress());
        List<AgentRegion> rs = regions(a.getId());
        v.setRegionCount(rs.size());
    private ProvincialAgentListItemVO toList(ProvincialAgent agent) {
        ProvincialAgentListItemVO vo = new ProvincialAgentListItemVO();
        vo.setId(agent.getId());
        vo.setAgentName(agent.getAgentName());
        vo.setContactInfo(agent.getContactInfo());
        vo.setAddress(agent.getAddress());
        vo.setCreatedAt(agent.getCreatedAt());
        List<AgentRegion> regions = regions(agent.getId());
        vo.setRegionCount(regions.size());
        List<String> names = new ArrayList<String>();
        for (AgentRegion r : rs) names.add(r.getRegionName() == null ? r.getRegionCode() : r.getRegionName());
        v.setRegionNames(names);
        v.setCreatedAt(a.getCreatedAt());
        return v;
        for (AgentRegion region : regions) names.add(resolveRegion(region.getRegionCode()).name);
        vo.setRegionNames(names);
        return vo;
    }
    private ProvincialAgentDetailVO toDetail(ProvincialAgent a) {
        ProvincialAgentDetailVO v = new ProvincialAgentDetailVO();
        ProvincialAgentListItemVO base = toList(a);
        v.setId(base.getId());
        v.setAgentName(base.getAgentName());
        v.setProvince(base.getProvince());
        v.setContactInfo(base.getContactInfo());
        v.setAddress(base.getAddress());
        v.setRegionCount(base.getRegionCount());
        v.setRegionNames(base.getRegionNames());
        v.setCreatedAt(base.getCreatedAt());
        v.setBankName(a.getBankName());
        v.setAccountNumber(a.getAccountNumber());
        v.setAccountHolder(a.getAccountHolder());
        v.setBranchName(a.getBranchName());
        v.setUpdatedAt(a.getUpdatedAt());
        List<ProvincialAgentDetailVO.RegionVO> out = new ArrayList<ProvincialAgentDetailVO.RegionVO>();
        for (AgentRegion r : regions(a.getId())) {
            ProvincialAgentDetailVO.RegionVO x = new ProvincialAgentDetailVO.RegionVO();
            x.setRegionCode(r.getRegionCode());
            x.setRegionName(r.getRegionName());
            x.setRegionLevel(r.getRegionLevel());
            out.add(x);
    private ProvincialAgentDetailVO toDetail(ProvincialAgent agent) {
        ProvincialAgentDetailVO vo = new ProvincialAgentDetailVO();
        ProvincialAgentListItemVO base = toList(agent);
        vo.setId(base.getId());
        vo.setAgentName(base.getAgentName());
        vo.setContactInfo(base.getContactInfo());
        vo.setAddress(base.getAddress());
        vo.setRegionCount(base.getRegionCount());
        vo.setRegionNames(base.getRegionNames());
        vo.setCreatedAt(base.getCreatedAt());
        vo.setBankName(agent.getBankName());
        vo.setAccountNumber(agent.getAccountNumber());
        vo.setAccountHolder(agent.getAccountHolder());
        vo.setBranchName(agent.getBranchName());
        vo.setUpdatedAt(agent.getUpdatedAt());
        List<ProvincialAgentDetailVO.RegionVO> values = new ArrayList<ProvincialAgentDetailVO.RegionVO>();
        for (AgentRegion region : regions(agent.getId())) {
            RegionInfo info = resolveRegion(region.getRegionCode());
            ProvincialAgentDetailVO.RegionVO value = new ProvincialAgentDetailVO.RegionVO();
            value.setRegionCode(region.getRegionCode());
            value.setRegionName(info.name);
            value.setRegionLevel(info.path.size());
            value.setRegionPath(info.path);
            values.add(value);
        }
        v.setRegions(out);
        return v;
        vo.setRegions(values);
        return vo;
    }
    private List<AgentRegion> regions(Long id) {
        return regionMapper.selectList(new LambdaQueryWrapper<AgentRegion>().eq(AgentRegion::getAgentId, id).orderByAsc(AgentRegion::getId));
        return regionMapper.selectList(new LambdaQueryWrapper<AgentRegion>()
                .eq(AgentRegion::getAgentId, id).orderByAsc(AgentRegion::getId));
    }
    private String trim(String s) {
        if (s == null) return null;
        String x = s.trim();
        return x.isEmpty() ? null : x;
    private RegionInfo resolveRegion(String code) {
        List<String> path = new ArrayList<String>();
        List<String> names = new ArrayList<String>();
        SysRegion current = sysRegionMapper.selectByCode(code);
        while (current != null) {
            path.add(0, current.getCode());
            names.add(0, current.getName());
            current = current.getParentId() == null || current.getParentId() == 0 ? null : sysRegionMapper.selectById(current.getParentId());
        }
        if (path.isEmpty()) {
            path.add(code);
            names.add(code);
        }
        return new RegionInfo(path, String.join("", names));
    }
}
    private String trim(String value) {
        if (value == null) return null;
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }
    private static class RegionInfo {
        private final List<String> path;
        private final String name;
        private RegionInfo(List<String> path, String name) {
            this.path = path;
            this.name = name;
        }
    }
}
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/vo/ProvincialAgentDetailVO.java
@@ -2,12 +2,26 @@
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Getter @Setter
@Getter
@Setter
public class ProvincialAgentDetailVO extends ProvincialAgentListItemVO {
    private String bankName; private String accountNumber; private String accountHolder;
    private String branchName; private LocalDateTime updatedAt; private List<RegionVO> regions;
    @Getter @Setter public static class RegionVO { private String regionCode; private String regionName; private Integer regionLevel; }
    private String bankName;
    private String accountNumber;
    private String accountHolder;
    private String branchName;
    private LocalDateTime updatedAt;
    private List<RegionVO> regions;
    @Getter
    @Setter
    public static class RegionVO {
        private String regionCode;
        private String regionName;
        private Integer regionLevel;
        private List<String> regionPath;
    }
}
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/agent/vo/ProvincialAgentListItemVO.java
@@ -2,12 +2,18 @@
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Getter @Setter
@Getter
@Setter
public class ProvincialAgentListItemVO {
    private Long id; private String agentName; private String province;
    private String contactInfo; private String address; private Integer regionCount;
    private List<String> regionNames; private LocalDateTime createdAt;
    private Long id;
    private String agentName;
    private String contactInfo;
    private String address;
    private Integer regionCount;
    private List<String> regionNames;
    private LocalDateTime createdAt;
}
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/live/entity/Clinic.java
@@ -23,6 +23,9 @@
    private String name;
    @TableField("agent_id")
    private Long agentId;
    @TableField("contact_name")
    private String contactName;
tcm-training-admin/src/pages/member/agent/index.tsx
@@ -5,13 +5,148 @@
import React, { useEffect, useState } from 'react';
import { createAgent, deleteAgent, getAgentDetail, getAgentPage, updateAgent } from './service';
type Region={regionCode:string|string[];regionName?:string;regionLevel?:number}; type Item={id:number;agentName:string;province:string;contactInfo?:string;address?:string;regionNames?:string[];regionCount:number;createdAt?:string};
const AgentPage:React.FC=()=>{const access=useAccess();const [search]=Form.useForm();const [form]=Form.useForm();const [list,setList]=useState<Item[]>([]);const [pagination,setPagination]=useState({page:1,pageSize:10,total:0});const [loading,setLoading]=useState(false);const [open,setOpen]=useState(false);const [detail,setDetail]=useState<any>();const [editing,setEditing]=useState<number>();const canView=access['member:agent:view'];
 const load=async(page=1,pageSize=pagination.pageSize)=>{setLoading(true);try{const r=await getAgentPage({page,pageSize,...search.getFieldsValue()});setList(r.data?.list||[]);setPagination({page:Number(r.data?.page||page),pageSize:Number(r.data?.pageSize||pageSize),total:Number(r.data?.total||0)});}finally{setLoading(false);}};useEffect(()=>{if(!canView){history.replace('/Welcome');return;}load();},[canView]);
 const edit=async(id?:number)=>{setEditing(id);setOpen(true);if(id){const r=await getAgentDetail(id);const d=r.data;setDetail(d);form.setFieldsValue({...d,regions:d.regions||[]});}else{setDetail(undefined);form.resetFields();form.setFieldsValue({regions:[{}]});}};
 const submit=async()=>{const v=await form.validateFields();const regions=(v.regions||[]).filter((x:Region)=>x?.regionCode).map((x:Region)=>{const path=Array.isArray(x.regionCode)?x.regionCode:[x.regionCode];return {...x,regionCode:path[path.length-1],regionName:x.regionName||path[path.length-1],regionLevel:path.length};});const data={...v,regions};try{if(editing)await updateAgent(editing,data);else await createAgent(data);message.success(editing?'保存成功':'新增成功');setOpen(false);load(editing?pagination.page:1);}catch(e){message.error((e as Error).message||'保存失败');}};
 const columns=[{title:'序号',width:70,render:(_:any,__ :any,i:number)=>(pagination.page-1)*pagination.pageSize+i+1},{title:'代理负责人',dataIndex:'agentName'},{title:'负责省份',dataIndex:'province'},{title:'负责区域',render:(_:any,r:Item)=>(r.regionNames||[]).join('、')||'-'},{title:'联系信息',dataIndex:'contactInfo',render:(v:string)=>v||'-'},{title:'创建时间',dataIndex:'createdAt',width:180},{title:'操作',render:(_:any,r:Item)=><Space><Button type="link" onClick={()=>{getAgentDetail(r.id).then(x=>{setDetail(x.data);setOpen(true);setEditing(undefined);});}}>详情</Button>{access['member:agent:edit']&&<Button type="link" onClick={()=>edit(r.id)}>编辑</Button>}{access['member:agent:delete']&&<Button type="link" danger onClick={()=>Modal.confirm({title:'确定删除该省代理吗?',onOk:async()=>{await deleteAgent(r.id);message.success('删除成功');load();}})}>删除</Button>}</Space>}];
 return <div style={{padding:24}}><h2>省代理管理</h2><Form form={search} layout="inline" onFinish={()=>load(1)}><Form.Item name="agentName" label="代理负责人"><Input allowClear placeholder="请输入"/></Form.Item><Form.Item name="province" label="负责省份"><Input allowClear placeholder="请输入"/></Form.Item><Button onClick={()=>{search.resetFields();load(1)}}>重置</Button><Button type="primary" htmlType="submit" style={{marginLeft:8}}>查询</Button></Form><div style={{margin:'16px 0'}}>{access['member:agent:add']&&<Button type="primary" icon={<PlusOutlined/>} onClick={()=>edit()}>新增</Button>}</div><Table rowKey="id" columns={columns as any} dataSource={list} loading={loading} pagination={false}/><Pagination style={{marginTop:16,textAlign:'right'}} current={pagination.page} pageSize={pagination.pageSize} total={pagination.total} showSizeChanger onChange={load}/>
 <Modal open={open} title={detail&&!editing?'省代理详情':editing?'编辑省代理':'新增省代理'} width={760} onCancel={()=>{setOpen(false);setDetail(undefined)}} footer={detail&&!editing?null:<><Button onClick={()=>setOpen(false)}>取消</Button><Button type="primary" onClick={submit}>确定</Button></>}>
 {detail&&!editing?<Descriptions bordered column={2} items={[['代理负责人',detail.agentName],['负责省份',detail.province],['负责区域',(detail.regionNames||[]).join('、')||'-'],['开户银行',detail.bankName],['银行账号',detail.accountNumber],['账户名称',detail.accountHolder],['开户支行',detail.branchName],['联系信息',detail.contactInfo],['详细地址',detail.address]].map(([label,children])=>({label,children:children||'-'}))}/>:<Form form={form} labelCol={{span:5}}><Form.Item name="agentName" label="代理负责人" rules={[{required:true,message:'请输入代理负责人'}]}><Input maxLength={50}/></Form.Item><Form.Item name="province" label="负责省份" rules={[{required:true,message:'请输入负责省份'}]}><Input maxLength={20}/></Form.Item><Form.List name="regions">{(fields,{add,remove})=><>{fields.map(({key,name,...rest})=><Space key={key} align="baseline"><Form.Item {...rest} name={[name,'regionCode']} rules={[{required:true,message:'请选择区域'}]}><RegionCascader/></Form.Item><Form.Item {...rest} name={[name,'regionName']} hidden><Input/></Form.Item><Button onClick={()=>remove(name)}>删除</Button></Space>)}<Button type="dashed" onClick={()=>add({})}>添加区域</Button></>}</Form.List><Form.Item name="bankName" label="开户银行"><Input/></Form.Item><Form.Item name="accountNumber" label="银行账号"><Input/></Form.Item><Form.Item name="accountHolder" label="账户名称"><Input/></Form.Item><Form.Item name="branchName" label="开户支行"><Input/></Form.Item><Form.Item name="contactInfo" label="联系信息"><Input/></Form.Item><Form.Item name="address" label="详细地址"><Input.TextArea rows={3}/></Form.Item></Form>}
 </Modal></div>};export default AgentPage;
type RegionValue = { regionCode?: string[]; regionName?: string };
type AgentItem = { id: number; agentName: string; contactInfo?: string; address?: string; regionNames?: string[]; createdAt?: string };
const AgentPage: React.FC = () => {
  const access = useAccess();
  const [searchForm] = Form.useForm<{ regionPath?: string[]; agentName?: string }>();
  const [form] = Form.useForm();
  const [list, setList] = useState<AgentItem[]>([]);
  const [pagination, setPagination] = useState({ page: 1, pageSize: 10, total: 0 });
  const [loading, setLoading] = useState(false);
  const [open, setOpen] = useState(false);
  const [detail, setDetail] = useState<any>();
  const [editingId, setEditingId] = useState<number>();
  const canView = access['member:agent:view'];
  const load = async (page = 1, pageSize = pagination.pageSize) => {
    const search = searchForm.getFieldsValue();
    setLoading(true);
    try {
      const response = await getAgentPage({
        page,
        pageSize,
        agentName: search.agentName?.trim() || undefined,
        regionCode: search.regionPath?.[search.regionPath.length - 1],
      });
      setList(response.data?.list || []);
      setPagination({
        page: Number(response.data?.page || page),
        pageSize: Number(response.data?.pageSize || pageSize),
        total: Number(response.data?.total || 0),
      });
    } finally {
      setLoading(false);
    }
  };
  useEffect(() => {
    if (!canView) history.replace('/Welcome');
    else void load();
  }, [canView]);
  const openForm = async (id?: number) => {
    setEditingId(id);
    setDetail(undefined);
    setOpen(true);
    form.resetFields();
    if (!id) {
      form.setFieldsValue({ regions: [{}] });
      return;
    }
    const response = await getAgentDetail(id);
    form.setFieldsValue({
      ...response.data,
      regions: (response.data?.regions || []).map((region: any) => ({
        ...region,
        regionCode: region.regionPath,
      })),
    });
  };
  const openDetail = async (id: number) => {
    const response = await getAgentDetail(id);
    setEditingId(undefined);
    setDetail(response.data);
    setOpen(true);
  };
  const submit = async () => {
    const values = await form.validateFields();
    const regions = (values.regions || []).map((region: RegionValue) => ({
      regionCode: region.regionCode?.[region.regionCode.length - 1],
    }));
    try {
      if (editingId) await updateAgent(editingId, { ...values, regions });
      else await createAgent({ ...values, regions });
      message.success(editingId ? '保存成功' : '新增成功');
      setOpen(false);
      await load(editingId ? pagination.page : 1);
    } catch (error) {
      message.error((error as Error)?.message || '保存失败');
    }
  };
  const columns = [
    { title: '序号', width: 70, render: (_: any, __: any, index: number) => (pagination.page - 1) * pagination.pageSize + index + 1 },
    { title: '代理负责人', dataIndex: 'agentName' },
    { title: '负责省份', render: (_: any, record: AgentItem) => (record.regionNames || []).join('、') || '-' },
    { title: '联系信息', dataIndex: 'contactInfo', render: (value: string) => value || '-' },
    { title: '详细地址', dataIndex: 'address', render: (value: string) => value || '-' },
    { title: '创建时间', dataIndex: 'createdAt', width: 180 },
    {
      title: '操作',
      width: 180,
      render: (_: any, record: AgentItem) => <Space size={0}>
        {access['member:agent:detail'] && <Button type="link" onClick={() => openDetail(record.id)}>详情</Button>}
        {access['member:agent:edit'] && <Button type="link" onClick={() => openForm(record.id)}>编辑</Button>}
        {access['member:agent:delete'] && <Button type="link" danger onClick={() => Modal.confirm({
          title: '确定删除该省代理吗?',
          onOk: async () => { await deleteAgent(record.id); message.success('删除成功'); await load(); },
        })}>删除</Button>}
      </Space>,
    },
  ];
  return <div style={{ padding: 24 }}>
    <h2>省代理管理</h2>
    <Form form={searchForm} layout="inline" onFinish={() => load(1)}>
      <Form.Item name="regionPath" label="负责省份"><RegionCascader allowClear changeOnSelect placeholder="全部" /></Form.Item>
      <Form.Item name="agentName" label="代理负责人"><Input allowClear placeholder="请输入" /></Form.Item>
      <Button onClick={() => { searchForm.resetFields(); void load(1); }}>重置</Button>
      <Button type="primary" htmlType="submit" style={{ marginLeft: 8 }}>查询</Button>
    </Form>
    <div style={{ margin: '16px 0' }}>{access['member:agent:add'] && <Button type="primary" icon={<PlusOutlined />} onClick={() => openForm()}>新增</Button>}</div>
    <Table rowKey="id" columns={columns as any} dataSource={list} loading={loading} pagination={false} />
    <Pagination style={{ marginTop: 16, textAlign: 'right' }} current={pagination.page} pageSize={pagination.pageSize} total={pagination.total} showSizeChanger onChange={load} />
    <Modal open={open} title={detail ? '省代理详情' : editingId ? '编辑省代理' : '新增省代理'} width={760} onCancel={() => setOpen(false)} footer={detail ? null : <><Button onClick={() => setOpen(false)}>取消</Button><Button type="primary" onClick={submit}>确定</Button></>}>
      {detail ? <Descriptions bordered column={2} items={[
        ['代理负责人', detail.agentName], ['负责省份', (detail.regionNames || []).join('、')],
        ['开户银行', detail.bankName], ['银行账号', detail.accountNumber], ['账户名称', detail.accountHolder],
        ['开户支行', detail.branchName], ['联系信息', detail.contactInfo], ['详细地址', detail.address],
      ].map(([label, children]) => ({ label, children: children || '-' }))} /> :
        <Form form={form} labelCol={{ span: 5 }}>
          <Form.Item name="agentName" label="代理负责人" rules={[{ required: true, message: '请输入代理负责人' }]}><Input maxLength={50} /></Form.Item>
          <Form.List name="regions" rules={[{ validator: async (_, value) => { if (!value?.length) throw new Error('请选择负责省份'); } }]}>
            {(fields, { add, remove }, { errors }) => <Form.Item label="负责省份" required>
              {fields.map(({ key, name, ...rest }) => <Space key={key} align="baseline">
                <Form.Item {...rest} name={[name, 'regionCode']} rules={[{ required: true, message: '请选择负责省份' }]}><RegionCascader changeOnSelect /></Form.Item>
                {fields.length > 1 && <Button onClick={() => remove(name)}>删除</Button>}
              </Space>)}
              <Button type="dashed" onClick={() => add({})}>添加负责省份</Button>
              <Form.ErrorList errors={errors} />
            </Form.Item>}
          </Form.List>
          <Form.Item name="bankName" label="开户银行"><Input maxLength={100} /></Form.Item>
          <Form.Item name="accountNumber" label="银行账号"><Input maxLength={30} /></Form.Item>
          <Form.Item name="accountHolder" label="账户名称"><Input maxLength={50} /></Form.Item>
          <Form.Item name="branchName" label="开户支行"><Input maxLength={100} /></Form.Item>
          <Form.Item name="contactInfo" label="联系信息"><Input maxLength={200} /></Form.Item>
          <Form.Item name="address" label="详细地址"><Input.TextArea rows={3} maxLength={255} /></Form.Item>
        </Form>}
    </Modal>
  </div>;
};
export default AgentPage;