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 } }); }