fix(admin): 禁止修改员工登录账号 (bug #24027)
| | |
| | | async function remove(row: AdminStaff) { await ElMessageBox.confirm(`确认删除员工“${row.displayName}”?`, '删除员工', { type: 'warning' }); await deleteAdminStaff(row.id); ElMessage.success('已删除'); load() } |
| | | onMounted(load) |
| | | </script> |
| | | <template><div class="page-stack"><section class="panel"><div class="panel-header"><h3>账号</h3></div><el-form class="filters" inline><el-form-item label="用户名称"><el-input v-model="filters.name" /></el-form-item><el-form-item label="用户电话"><el-input v-model="filters.mobile" /></el-form-item><el-form-item label="角色"><el-select v-model="filters.role" clearable placeholder="请选择"><el-option v-for="role in roles" :key="role.id" :label="role.roleName" :value="role.roleCode" /></el-select></el-form-item><el-form-item label="账号状态"><el-select v-model="filters.status" clearable placeholder="请选择"><el-option label="启用" value="1" /><el-option label="禁用" value="0" /></el-select></el-form-item><el-form-item><el-button :icon="Refresh" @click="reset">重置</el-button><el-button type="primary" :icon="Search">搜索</el-button></el-form-item></el-form><div class="tools"><el-button type="primary" :icon="Plus" @click="create">新增</el-button></div><el-table v-loading="loading" :data="filtered" stripe class="table"><el-table-column type="index" label="序号" width="60"/><el-table-column prop="displayName" label="人员名称" min-width="96"/><el-table-column prop="mobile" label="联系电话" min-width="118"/><el-table-column prop="account" label="登录账号" min-width="116"/><el-table-column label="角色" min-width="110"><template #default="{row}">{{ roleName(row.roleCode) }}</template></el-table-column><el-table-column prop="positionName" label="职务" min-width="92"/><el-table-column prop="customerWechat" label="客服微信" min-width="116"/><el-table-column label="状态" width="78"><template #default="{row}">{{row.status === 1 ? '启用' : '禁用'}}</template></el-table-column><el-table-column label="创建时间" min-width="155"><template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template></el-table-column><el-table-column label="操作" width="225" fixed="right"><template #default="{row}"><el-button link type="primary" @click="edit(row)">编辑</el-button><el-button v-if="!isDefaultAdmin(row)" link type="primary" @click="changeStatus(row)">{{row.status === 1 ? '禁用' : '启用'}}</el-button><el-button link type="primary" @click="password(row)">重置密码</el-button><el-button v-if="!isDefaultAdmin(row)" link type="danger" @click="remove(row)">删除</el-button></template></el-table-column></el-table></section><el-dialog v-model="visible" :title="editId ? '编辑员工' : '新增员工'" width="560px" destroy-on-close><el-form label-width="92px"><el-form-item label="人员名称" required><el-input v-model="form.displayName"/></el-form-item><el-form-item label="联系电话"><el-input v-model="form.mobile" inputmode="numeric" maxlength="11" @input="keepMobileDigits"/></el-form-item><el-form-item label="登录账号" required><el-input v-model="form.account" maxlength="32" @input="keepAccountCharacters"/></el-form-item><el-form-item label="登录密码" :required="!editId"><el-input v-model="form.password" type="password" :placeholder="editId ? '留空则不修改' : '至少 6 位'"/></el-form-item><el-form-item label="角色" required><el-select v-model="form.roleCode" class="full"><el-option v-for="role in roles" :key="role.id" :label="role.roleName" :value="role.roleCode"/></el-select></el-form-item><el-form-item label="职务" required><el-input v-model="form.positionName"/></el-form-item><el-form-item label="客服微信"><el-input v-model="form.customerWechat"/></el-form-item><el-form-item label="账号状态"><el-switch v-model="form.status" :disabled="editingDefaultAdmin" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="禁用"/></el-form-item></el-form><template #footer><el-button @click="visible=false">取消</el-button><el-button type="primary" :loading="saving" @click="save">保存</el-button></template></el-dialog></div></template> |
| | | <template><div class="page-stack"><section class="panel"><div class="panel-header"><h3>账号</h3></div><el-form class="filters" inline><el-form-item label="用户名称"><el-input v-model="filters.name" /></el-form-item><el-form-item label="用户电话"><el-input v-model="filters.mobile" /></el-form-item><el-form-item label="角色"><el-select v-model="filters.role" clearable placeholder="请选择"><el-option v-for="role in roles" :key="role.id" :label="role.roleName" :value="role.roleCode" /></el-select></el-form-item><el-form-item label="账号状态"><el-select v-model="filters.status" clearable placeholder="请选择"><el-option label="启用" value="1" /><el-option label="禁用" value="0" /></el-select></el-form-item><el-form-item><el-button :icon="Refresh" @click="reset">重置</el-button><el-button type="primary" :icon="Search">搜索</el-button></el-form-item></el-form><div class="tools"><el-button type="primary" :icon="Plus" @click="create">新增</el-button></div><el-table v-loading="loading" :data="filtered" stripe class="table"><el-table-column type="index" label="序号" width="60"/><el-table-column prop="displayName" label="人员名称" min-width="96"/><el-table-column prop="mobile" label="联系电话" min-width="118"/><el-table-column prop="account" label="登录账号" min-width="116"/><el-table-column label="角色" min-width="110"><template #default="{row}">{{ roleName(row.roleCode) }}</template></el-table-column><el-table-column prop="positionName" label="职务" min-width="92"/><el-table-column prop="customerWechat" label="客服微信" min-width="116"/><el-table-column label="状态" width="78"><template #default="{row}">{{row.status === 1 ? '启用' : '禁用'}}</template></el-table-column><el-table-column label="创建时间" min-width="155"><template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template></el-table-column><el-table-column label="操作" width="225" fixed="right"><template #default="{row}"><el-button link type="primary" @click="edit(row)">编辑</el-button><el-button v-if="!isDefaultAdmin(row)" link type="primary" @click="changeStatus(row)">{{row.status === 1 ? '禁用' : '启用'}}</el-button><el-button link type="primary" @click="password(row)">重置密码</el-button><el-button v-if="!isDefaultAdmin(row)" link type="danger" @click="remove(row)">删除</el-button></template></el-table-column></el-table></section><el-dialog v-model="visible" :title="editId ? '编辑员工' : '新增员工'" width="560px" destroy-on-close><el-form label-width="92px"><el-form-item label="人员名称" required><el-input v-model="form.displayName"/></el-form-item><el-form-item label="联系电话"><el-input v-model="form.mobile" inputmode="numeric" maxlength="11" @input="keepMobileDigits"/></el-form-item><el-form-item label="登录账号" required><el-input v-model="form.account" :disabled="Boolean(editId)" maxlength="32" @input="keepAccountCharacters"/></el-form-item><el-form-item label="登录密码" :required="!editId"><el-input v-model="form.password" type="password" :placeholder="editId ? '留空则不修改' : '至少 6 位'"/></el-form-item><el-form-item label="角色" required><el-select v-model="form.roleCode" class="full"><el-option v-for="role in roles" :key="role.id" :label="role.roleName" :value="role.roleCode"/></el-select></el-form-item><el-form-item label="职务" required><el-input v-model="form.positionName"/></el-form-item><el-form-item label="客服微信"><el-input v-model="form.customerWechat"/></el-form-item><el-form-item label="账号状态"><el-switch v-model="form.status" :disabled="editingDefaultAdmin" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="禁用"/></el-form-item></el-form><template #footer><el-button @click="visible=false">取消</el-button><el-button type="primary" :loading="saving" @click="save">保存</el-button></template></el-dialog></div></template> |
| | | <style scoped>.filters{margin:14px 20px 0;padding:16px;border:1px solid #dce3ea;border-radius:4px}.filters :deep(.el-input),.filters :deep(.el-select){width:130px}.tools{display:flex;justify-content:flex-end;padding:10px 20px 14px}.table{margin:0 20px;width:calc(100% - 40px)}.full{width:100%}</style> |
| | |
| | | |
| | | public List<Map<String, Object>> staff() { return rows("select id,account,display_name,mobile,role_code,position_name,customer_wechat,status,created_at from admin_operator where deleted=0 order by id"); } |
| | | @Transactional public Map<String, Object> createStaff(AdminStaffRequest request) { if (request.password() == null || request.password().isBlank()) throw new BusinessException(ErrorCode.BAD_REQUEST, "A password is required for a new staff account"); jdbc.update("insert into admin_operator(account,password_hash,display_name,mobile,role_code,position_name,customer_wechat,status) values (?,?,?,?,?,?,?,?)", request.account().trim(), MerchantPasswordHasher.encode(request.password()), request.displayName().trim(), blankToNull(request.mobile()), request.roleCode(), request.positionName().trim(), blankToNull(request.customerWechat()), request.status()); return staffOne("last_insert_id()"); } |
| | | @Transactional public Map<String, Object> updateStaff(Long id, AdminStaffRequest request) { if (request.status() == 0 && isDefaultAdmin(id)) throw new BusinessException(ErrorCode.CONFLICT, "超级管理员不可禁用"); String sql = request.password() == null || request.password().isBlank() ? "update admin_operator set account=?,display_name=?,mobile=?,role_code=?,position_name=?,customer_wechat=?,status=? where id=?" : "update admin_operator set account=?,password_hash=?,display_name=?,mobile=?,role_code=?,position_name=?,customer_wechat=?,status=? where id=?"; int updated = request.password() == null || request.password().isBlank() ? jdbc.update(sql, request.account().trim(), request.displayName().trim(), blankToNull(request.mobile()), request.roleCode(), request.positionName().trim(), blankToNull(request.customerWechat()), request.status(), id) : jdbc.update(sql, request.account().trim(), MerchantPasswordHasher.encode(request.password()), request.displayName().trim(), blankToNull(request.mobile()), request.roleCode(), request.positionName().trim(), blankToNull(request.customerWechat()), request.status(), id); if (updated == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "Staff account does not exist"); return staffOne("?", id); } |
| | | @Transactional public Map<String, Object> updateStaff(Long id, AdminStaffRequest request) { if (!matchesExistingAccount(id, request.account())) throw new BusinessException(ErrorCode.CONFLICT, "登录账号不可修改"); if (request.status() == 0 && isDefaultAdmin(id)) throw new BusinessException(ErrorCode.CONFLICT, "超级管理员不可禁用"); String sql = request.password() == null || request.password().isBlank() ? "update admin_operator set account=?,display_name=?,mobile=?,role_code=?,position_name=?,customer_wechat=?,status=? where id=?" : "update admin_operator set account=?,password_hash=?,display_name=?,mobile=?,role_code=?,position_name=?,customer_wechat=?,status=? where id=?"; int updated = request.password() == null || request.password().isBlank() ? jdbc.update(sql, request.account().trim(), request.displayName().trim(), blankToNull(request.mobile()), request.roleCode(), request.positionName().trim(), blankToNull(request.customerWechat()), request.status(), id) : jdbc.update(sql, request.account().trim(), MerchantPasswordHasher.encode(request.password()), request.displayName().trim(), blankToNull(request.mobile()), request.roleCode(), request.positionName().trim(), blankToNull(request.customerWechat()), request.status(), id); if (updated == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "Staff account does not exist"); return staffOne("?", id); } |
| | | @Transactional public Map<String, Object> resetStaffPassword(Long id, String password) { if (password == null || password.length() < 6 || password.length() > 64) throw new BusinessException(ErrorCode.BAD_REQUEST, "Password must be 6-64 characters"); int updated = jdbc.update("update admin_operator set password_hash=?, login_failed_count=0, locked_until=null where id=? and deleted=0", MerchantPasswordHasher.encode(password), id); if (updated == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "Staff account does not exist"); return Map.of("id", id, "reset", true); } |
| | | @Transactional public Map<String, Object> deleteStaff(Long id) { int updated = jdbc.update("update admin_operator set deleted=1, status=0 where id=? and deleted=0 and account <> 'admin'", id); if (updated == 0) throw new BusinessException(ErrorCode.CONFLICT, "The account cannot be deleted or does not exist"); return Map.of("id", id, "deleted", true); } |
| | | |
| | | private String roleCode(String supplied) { return supplied == null || supplied.isBlank() ? "role_" + System.currentTimeMillis() : supplied.trim(); } |
| | | private boolean matchesExistingAccount(Long id, String account) { return Boolean.TRUE.equals(jdbc.queryForObject("select exists(select 1 from admin_operator where id=? and deleted=0 and account=?)", Boolean.class, id, account.trim())); } |
| | | private boolean isDefaultAdmin(Long id) { return Boolean.TRUE.equals(jdbc.queryForObject("select exists(select 1 from admin_operator where id=? and deleted=0 and account='admin')", Boolean.class, id)); } |
| | | private Map<String, Object> staffOne(String where, Object... args) { return one("select id,account,display_name,mobile,role_code,position_name,customer_wechat,status,created_at from admin_operator where id=" + where, args); } |
| | | private String blankToNull(String value) { return value == null || value.isBlank() ? null : value.trim(); } |