hejianhao
2026-08-18 19e3ffa69142c78ae357a924e9766f419ed3e5cc
feat0806: 批量修改诊所功能
4个文件已修改
87 ■■■■■ 已修改文件
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/controller/UserController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/service/UserAppService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin/src/pages/member/user/list-page.tsx 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin/src/pages/member/user/service.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/controller/UserController.java
@@ -2,6 +2,7 @@
import com.tcm.training.admin.common.ApiResponse;
import com.tcm.training.admin.common.PageResponse;
import com.tcm.training.admin.member.dto.UserBatchClinicUpdateRequest;
import com.tcm.training.admin.member.dto.UserResetPasswordRequest;
import com.tcm.training.admin.member.dto.UserStatusRequest;
import com.tcm.training.admin.member.dto.UserUpdateRequest;
@@ -84,6 +85,12 @@
        return ApiResponse.ok(userAppService.detail(id));
    }
    @PutMapping("/users/batch-clinic")
    public ApiResponse<Void> batchUpdateClinic(@Valid @RequestBody UserBatchClinicUpdateRequest req) {
        userAppService.batchUpdateClinic(req);
        return ApiResponse.ok(null);
    }
    @PutMapping("/users/{id}")
    public ApiResponse<UserDetailVO> update(@PathVariable Long id, @Valid @RequestBody UserUpdateRequest req) {
        return ApiResponse.ok(userAppService.update(id, req));
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/member/service/UserAppService.java
@@ -9,22 +9,18 @@
import com.tcm.training.admin.common.PageResponse;
import com.tcm.training.admin.common.PhoneMasker;
import com.tcm.training.admin.live.entity.Clinic;
import com.tcm.training.admin.member.entity.AppUser;
import com.tcm.training.admin.live.mapper.ClinicMapper;
import com.tcm.training.admin.member.excel.MemberUserExcelRow;
import com.tcm.training.admin.member.dto.UserBatchClinicUpdateRequest;
import com.tcm.training.admin.member.dto.UserResetPasswordRequest;
import com.tcm.training.admin.member.dto.UserStatusRequest;
import com.tcm.training.admin.member.dto.UserUpdateRequest;
import com.tcm.training.admin.member.entity.AppUser;
import com.tcm.training.admin.member.excel.MemberUserExcelRow;
import com.tcm.training.admin.member.excel.UserExcelRow;
import com.tcm.training.admin.member.mapper.UserMapper;
import com.tcm.training.admin.member.row.UserDetailRow;
import com.tcm.training.admin.member.row.UserListRow;
import com.tcm.training.admin.member.vo.ClinicInUserVO;
import com.tcm.training.admin.member.vo.ClinicOptionVO;
import com.tcm.training.admin.member.vo.UserDetailVO;
import com.tcm.training.admin.member.vo.UserListItemVO;
import com.tcm.training.admin.member.vo.UserStatusVO;
import com.tcm.training.admin.member.vo.WechatBindingVO;
import com.tcm.training.admin.member.vo.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -90,7 +86,7 @@
            UserListItemVO item = new UserListItemVO();
            item.setId(row.getId());
            item.setNickname(row.getNickname());
            item.setPhone(PhoneMasker.mask(row.getPhone()));
            item.setPhone(row.getPhone());
            item.setAvatar(row.getAvatar());
            item.setType(row.getType());
            item.setClinicName(row.getClinicName());
@@ -210,6 +206,24 @@
        return detail(id);
    }
    @Transactional
    public void batchUpdateClinic(UserBatchClinicUpdateRequest req) {
        Clinic clinic = clinicMapper.selectById(req.getClinicId());
        if (clinic == null || Integer.valueOf(1).equals(clinic.getDeleted())) {
            throw new IllegalArgumentException("诊所不存在");
        }
        Long userCount = userMapper.selectCount(new LambdaQueryWrapper<AppUser>()
                .in(AppUser::getId, req.getUserIds())
                .eq(AppUser::getDeleted, 0));
        if (userCount != req.getUserIds().size()) {
            throw new IllegalArgumentException("部分用户不存在");
        }
        userMapper.update(null, new LambdaUpdateWrapper<AppUser>()
                .in(AppUser::getId, req.getUserIds())
                .eq(AppUser::getDeleted, 0)
                .set(AppUser::getClinicId, req.getClinicId()));
    }
    public List<ClinicOptionVO> listClinicOptions() {
        List<Clinic> clinics = clinicMapper.selectList(new LambdaQueryWrapper<Clinic>()
                .eq(Clinic::getDeleted, 0)
tcm-training-admin/src/pages/member/user/list-page.tsx
@@ -6,6 +6,7 @@
  Image,
  Input,
  message,
  Modal,
  Pagination,
  Select,
  Space,
@@ -15,7 +16,7 @@
import dayjs, { type Dayjs } from 'dayjs';
import React, { useEffect, useState } from 'react';
import { UserEditModal } from './modals';
import { exportUsers, getClinicOptions, getUserDetail, getUserPage, updateUser } from './service';
import { batchUpdateUserClinic, exportUsers, getClinicOptions, getUserDetail, getUserPage, updateUser } from './service';
import './style.less';
type UserListMode = 'all' | 'member';
@@ -151,6 +152,7 @@
  const access = useAccess();
  const [searchForm] = Form.useForm<SearchValues>();
  const [editForm] = Form.useForm();
  const [batchClinicForm] = Form.useForm<{ clinicId: number }>();
  const [list, setList] = useState<UserItem[]>([]);
  const [loading, setLoading] = useState(false);
  const [submitting, setSubmitting] = useState(false);
@@ -158,11 +160,29 @@
  const [pagination, setPagination] = useState({ page: 1, pageSize: 10, total: 0 });
  const [editOpen, setEditOpen] = useState(false);
  const [currentDetail, setCurrentDetail] = useState<UserDetail | null>(null);
  const [selectedUserIds, setSelectedUserIds] = useState<React.Key[]>([]);
  const [batchClinicOpen, setBatchClinicOpen] = useState(false);
  const [batchSubmitting, setBatchSubmitting] = useState(false);
  const canView = access[config.viewAccess];
  const canDetail = access['member:user:detail'];
  const canEdit = access['member:user:edit'];
  const canExport = access['member:user:export'];
  const handleBatchClinicSubmit = async () => {
    const { clinicId } = await batchClinicForm.validateFields();
    setBatchSubmitting(true);
    try {
      await batchUpdateUserClinic(selectedUserIds.map(Number), clinicId);
      message.success('批量修改诊所成功');
      setBatchClinicOpen(false);
      setSelectedUserIds([]);
      batchClinicForm.resetFields();
      await loadList();
    } finally {
      setBatchSubmitting(false);
    }
  };
  const loadClinics = async () => {
    const res = await getClinicOptions();
@@ -438,6 +458,11 @@
              导出
            </Button>
          ) : null}
          {mode === 'all' && canEdit ? (
            <Button disabled={!selectedUserIds.length} onClick={() => setBatchClinicOpen(true)}>
              批量修改诊所
            </Button>
          ) : null}
        </div>
        <div className="member-user-table-card">
          <Table<UserItem>
@@ -445,6 +470,7 @@
            loading={loading}
            columns={columns}
            dataSource={list}
            rowSelection={mode === 'all' ? { selectedRowKeys: selectedUserIds, onChange: setSelectedUserIds } : undefined}
            pagination={false}
            scroll={{ x: mode === 'member' ? 920 : 1320 }}
          />
@@ -462,6 +488,22 @@
        </div>
      </div>
      <Modal
        title="批量修改诊所"
        open={batchClinicOpen}
        confirmLoading={batchSubmitting}
        onOk={handleBatchClinicSubmit}
        onCancel={() => { setBatchClinicOpen(false); batchClinicForm.resetFields(); }}
        okText="确定"
        cancelText="取消"
      >
        <Form form={batchClinicForm} layout="vertical">
          <Form.Item name="clinicId" label="选择诊所" rules={[{ required: true, message: '请选择诊所' }]}>
            <Select showSearch optionFilterProp="label" placeholder="请选择诊所" options={clinics.map((item) => ({ label: item.name, value: item.id }))} />
          </Form.Item>
        </Form>
      </Modal>
      <UserEditModal
        open={editOpen}
        submitting={submitting}
tcm-training-admin/src/pages/member/user/service.ts
@@ -82,3 +82,7 @@
    data: formData,
  });
}
export async function batchUpdateUserClinic(userIds: number[], clinicId: number) {
  return request('/users/batch-clinic', { method: 'PUT', data: { userIds, clinicId } });
}