shinnosuke
2026-08-06 f12cbba923cebd30416d170eff2b116f4f013a29
fix(admin): 补齐角色启停与删除操作 (bug #24020)
1个文件已添加
4个文件已修改
497 ■■■■■ 已修改文件
talent-card-admin/src/api/admin.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/BusinessManagementView.vue 486 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminRoleStatusRequest.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/api/admin.ts
@@ -80,6 +80,8 @@
export function getAdminRoles() { return http.get<AdminRole[], AdminRole[]>('/api/admin/management/roles') }
export function createAdminRole(payload: Omit<AdminRole, 'id' | 'createdAt'>) { return http.post<AdminRole, AdminRole>('/api/admin/management/roles', payload) }
export function updateAdminRole(id: number | string, payload: Omit<AdminRole, 'id' | 'createdAt'>) { return http.put<AdminRole, AdminRole>(`/api/admin/management/roles/${id}`, payload) }
export function updateAdminRoleStatus(id: number | string, status: number) { return http.patch<AdminRole, AdminRole>(`/api/admin/management/roles/${id}/status`, { status }) }
export function deleteAdminRole(id: number | string) { return http.delete<any, any>(`/api/admin/management/roles/${id}`) }
export function getAdminStaff() { return http.get<AdminStaff[], AdminStaff[]>('/api/admin/management/staff') }
export function createAdminStaff(payload: Omit<AdminStaff, 'id' | 'createdAt'> & { password: string }) { return http.post<AdminStaff, AdminStaff>('/api/admin/management/staff', payload) }
export function updateAdminStaff(id: number | string, payload: Omit<AdminStaff, 'id' | 'createdAt'> & { password?: string }) { return http.put<AdminStaff, AdminStaff>(`/api/admin/management/staff/${id}`, payload) }
talent-card-admin/src/views/BusinessManagementView.vue
@@ -1,145 +1,447 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Download, Edit, Plus, Refresh, Search, View } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { computed, onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import {
  Download,
  Edit,
  Plus,
  Refresh,
  Search,
  View,
} from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  createAdminRole,
  createAdminStaff,
  deleteAdminRole,
  getAdminRoles,
  getAdminStaff,
  getUpgradeApplications,
  updateAdminRole,
  updateAdminRoleStatus,
  updateAdminStaff,
} from '../api/admin'
import type { AdminRole, AdminStaff } from '../types/admin'
import { downloadExcel } from '../utils/excel'
} from "../api/admin";
import type { AdminRole, AdminStaff } from "../types/admin";
import { downloadExcel } from "../utils/excel";
const route = useRoute()
const router = useRouter()
const kind = computed(() => String(route.meta.businessKind || 'upgrade'))
const isUpgrade = computed(() => kind.value === 'upgrade')
const isRole = computed(() => kind.value === 'role')
const title = computed(() => isUpgrade.value ? '升级申请' : isRole.value ? '角色管理' : '员工管理')
const loading = ref(false)
const rows = ref<Record<string, unknown>[]>([])
const dialogVisible = ref(false)
const editVisible = ref(false)
const activeRow = ref<Record<string, unknown>>()
const editingId = ref<number | string>()
const filters = reactive({ keyword: '', cardLevel: '', status: '', timeRange: [] as string[] })
const pagination = reactive({ page: 1, size: 10, total: 0 })
const form = reactive({ name: '', code: '', password: '', remark: '', status: 1 })
const pagedRows = computed(() => isUpgrade.value ? rows.value : rows.value.slice((pagination.page - 1) * pagination.size, pagination.page * pagination.size))
const route = useRoute();
const router = useRouter();
const kind = computed(() => String(route.meta.businessKind || "upgrade"));
const isUpgrade = computed(() => kind.value === "upgrade");
const isRole = computed(() => kind.value === "role");
const title = computed(() =>
  isUpgrade.value ? "升级申请" : isRole.value ? "角色管理" : "员工管理",
);
const loading = ref(false);
const rows = ref<Record<string, unknown>[]>([]);
const dialogVisible = ref(false);
const editVisible = ref(false);
const activeRow = ref<Record<string, unknown>>();
const editingId = ref<number | string>();
const filters = reactive({
  keyword: "",
  cardLevel: "",
  status: "",
  timeRange: [] as string[],
});
const pagination = reactive({ page: 1, size: 10, total: 0 });
const form = reactive({
  name: "",
  code: "",
  password: "",
  remark: "",
  status: 1,
});
const pagedRows = computed(() =>
  isUpgrade.value
    ? rows.value
    : rows.value.slice(
        (pagination.page - 1) * pagination.size,
        pagination.page * pagination.size,
      ),
);
const columns = computed(() => isUpgrade.value
  ? [['applicationNo', '申请编号'], ['talentName', '持卡人名称'], ['talentMobile', '持卡人手机号'], ['targetLevel', '卡片等级'], ['applyTime', '申请时间'], ['status', '状态']]
  : isRole.value
    ? [['serialNo', '序号'], ['roleName', '角色名称'], ['remark', '备注'], ['statusText', '状态'], ['createdAt', '创建时间']]
    : [['account', '登录账号'], ['displayName', '员工姓名'], ['roleCode', '所属角色'], ['statusText', '状态'], ['createdAt', '创建时间']])
const columns = computed(() =>
  isUpgrade.value
    ? [
        ["applicationNo", "申请编号"],
        ["talentName", "持卡人名称"],
        ["talentMobile", "持卡人手机号"],
        ["targetLevel", "卡片等级"],
        ["applyTime", "申请时间"],
        ["status", "状态"],
      ]
    : isRole.value
      ? [
          ["serialNo", "序号"],
          ["roleName", "角色名称"],
          ["remark", "备注"],
          ["statusText", "状态"],
          ["createdAt", "创建时间"],
        ]
      : [
          ["account", "登录账号"],
          ["displayName", "员工姓名"],
          ["roleCode", "所属角色"],
          ["statusText", "状态"],
          ["createdAt", "创建时间"],
        ],
);
function statusText(value: unknown) { return Number(value) === 1 ? '启用' : '停用' }
function formatDateTime(value: unknown) {
  return typeof value === 'string' && value ? value.replace('T', ' ').slice(0, 19) : ''
function statusText(value: unknown) {
  return Number(value) === 1 ? "启用" : "停用";
}
function normalizeRole(row: AdminRole, index: number) { return { ...row, serialNo: index + 1, createdAt: formatDateTime(row.createdAt), statusText: statusText(row.status) } }
function normalizeStaff(row: AdminStaff) { return { ...row, createdAt: formatDateTime(row.createdAt), statusText: statusText(row.status) } }
function normalizeUpgrade(row: Record<string, unknown>) { return { ...row, applyTime: formatDateTime(row.applyTime) } }
function formatDateTime(value: unknown) {
  return typeof value === "string" && value
    ? value.replace("T", " ").slice(0, 19)
    : "";
}
function normalizeRole(row: AdminRole, index: number) {
  return {
    ...row,
    serialNo: index + 1,
    createdAt: formatDateTime(row.createdAt),
    statusText: statusText(row.status),
  };
}
function normalizeStaff(row: AdminStaff) {
  return {
    ...row,
    createdAt: formatDateTime(row.createdAt),
    statusText: statusText(row.status),
  };
}
function normalizeUpgrade(row: Record<string, unknown>) {
  return { ...row, applyTime: formatDateTime(row.applyTime) };
}
async function loadData() {
  loading.value = true
  loading.value = true;
  try {
    if (isUpgrade.value) {
      const result = await getUpgradeApplications({ page: pagination.page, size: pagination.size, keyword: filters.keyword || undefined, targetLevel: filters.cardLevel || undefined, status: filters.status || undefined, applyStart: filters.timeRange[0], applyEnd: filters.timeRange[1] })
      rows.value = result.records.map((row) => normalizeUpgrade(row as unknown as Record<string, unknown>))
      pagination.total = result.total
      const result = await getUpgradeApplications({
        page: pagination.page,
        size: pagination.size,
        keyword: filters.keyword || undefined,
        targetLevel: filters.cardLevel || undefined,
        status: filters.status || undefined,
        applyStart: filters.timeRange[0],
        applyEnd: filters.timeRange[1],
      });
      rows.value = result.records.map((row) =>
        normalizeUpgrade(row as unknown as Record<string, unknown>),
      );
      pagination.total = result.total;
    } else if (isRole.value) {
      rows.value = (await getAdminRoles()).map(normalizeRole)
      pagination.total = rows.value.length
      rows.value = (await getAdminRoles()).map(normalizeRole);
      pagination.total = rows.value.length;
    } else {
      rows.value = (await getAdminStaff()).map(normalizeStaff)
      pagination.total = rows.value.length
      rows.value = (await getAdminStaff()).map(normalizeStaff);
      pagination.total = rows.value.length;
    }
  } finally { loading.value = false }
  } finally {
    loading.value = false;
  }
}
function search() { pagination.page = 1; loadData() }
function reset() { filters.keyword = ''; filters.cardLevel = ''; filters.status = ''; filters.timeRange = []; search() }
function changePage() { if (isUpgrade.value) loadData() }
function search() {
  pagination.page = 1;
  loadData();
}
function reset() {
  filters.keyword = "";
  filters.cardLevel = "";
  filters.status = "";
  filters.timeRange = [];
  search();
}
function changePage() {
  if (isUpgrade.value) loadData();
}
function view(row: Record<string, unknown>) {
  if (isUpgrade.value) {
    router.push(`/upgrade-applications/${row.id}`)
    return
    router.push(`/upgrade-applications/${row.id}`);
    return;
  }
  activeRow.value = row
  dialogVisible.value = true
  activeRow.value = row;
  dialogVisible.value = true;
}
function openCreate() {
  if (isRole.value) { router.push('/roles/new'); return }
  editingId.value = undefined
  Object.assign(form, { name: '', code: '', password: '', remark: '', status: 1 })
  editVisible.value = true
  if (isRole.value) {
    router.push("/roles/new");
    return;
  }
  editingId.value = undefined;
  Object.assign(form, {
    name: "",
    code: "",
    password: "",
    remark: "",
    status: 1,
  });
  editVisible.value = true;
}
function openEdit(row: Record<string, unknown>) {
  if (isRole.value) { router.push(`/roles/${row.id}`); return }
  editingId.value = row.id as number | string
  Object.assign(form, { name: String(row.roleName || row.displayName || ''), code: String(row.roleCode || row.account || ''), password: '', remark: String(row.remark || ''), status: Number(row.status) })
  editVisible.value = true
  if (isRole.value) {
    router.push(`/roles/${row.id}`);
    return;
  }
  editingId.value = row.id as number | string;
  Object.assign(form, {
    name: String(row.roleName || row.displayName || ""),
    code: String(row.roleCode || row.account || ""),
    password: "",
    remark: String(row.remark || ""),
    status: Number(row.status),
  });
  editVisible.value = true;
}
async function changeRoleStatus(row: Record<string, unknown>) {
  const status = Number(row.status) === 1 ? 0 : 1;
  await updateAdminRoleStatus(row.id as number | string, status);
  ElMessage.success(status === 1 ? "已启用" : "已禁用");
  await loadData();
}
async function removeRole(row: Record<string, unknown>) {
  await ElMessageBox.confirm(`确认删除角色“${row.roleName}”?`, "删除角色", {
    type: "warning",
  });
  await deleteAdminRole(row.id as number | string);
  ElMessage.success("已删除");
  await loadData();
}
async function save() {
  if (!form.name.trim() || !form.code.trim()) { ElMessage.warning('请填写名称和编码/账号'); return }
  if (isRole.value) {
    const payload = { roleName: form.name.trim(), roleCode: form.code.trim(), remark: form.remark.trim(), status: form.status }
    if (editingId.value) await updateAdminRole(editingId.value, payload); else await createAdminRole(payload)
  } else {
    if (!editingId.value && !form.password) { ElMessage.warning('请设置登录密码'); return }
    const payload = { displayName: form.name.trim(), account: form.code.trim(), password: form.password || undefined, roleCode: 'platform_admin', status: form.status }
    if (editingId.value) await updateAdminStaff(editingId.value, payload); else await createAdminStaff({ ...payload, password: form.password })
  if (!form.name.trim() || !form.code.trim()) {
    ElMessage.warning("请填写名称和编码/账号");
    return;
  }
  editVisible.value = false
  ElMessage.success('已保存')
  await loadData()
  if (isRole.value) {
    const payload = {
      roleName: form.name.trim(),
      roleCode: form.code.trim(),
      remark: form.remark.trim(),
      status: form.status,
    };
    if (editingId.value) await updateAdminRole(editingId.value, payload);
    else await createAdminRole(payload);
  } else {
    if (!editingId.value && !form.password) {
      ElMessage.warning("请设置登录密码");
      return;
    }
    const payload = {
      displayName: form.name.trim(),
      account: form.code.trim(),
      password: form.password || undefined,
      roleCode: "platform_admin",
      status: form.status,
    };
    if (editingId.value) await updateAdminStaff(editingId.value, payload);
    else await createAdminStaff({ ...payload, password: form.password });
  }
  editVisible.value = false;
  ElMessage.success("已保存");
  await loadData();
}
async function download() {
  let data = rows.value
  let data = rows.value;
  if (isUpgrade.value) {
    const result = await getUpgradeApplications({ page: 1, size: 1000, keyword: filters.keyword || undefined, targetLevel: filters.cardLevel || undefined, status: filters.status || undefined, applyStart: filters.timeRange[0], applyEnd: filters.timeRange[1] })
    data = result.records.map((row) => normalizeUpgrade(row as unknown as Record<string, unknown>))
    const result = await getUpgradeApplications({
      page: 1,
      size: 1000,
      keyword: filters.keyword || undefined,
      targetLevel: filters.cardLevel || undefined,
      status: filters.status || undefined,
      applyStart: filters.timeRange[0],
      applyEnd: filters.timeRange[1],
    });
    data = result.records.map((row) =>
      normalizeUpgrade(row as unknown as Record<string, unknown>),
    );
  }
  downloadExcel(title.value, columns.value.map(([key, label]) => ({ title: label, value: (row: Record<string, unknown>) => {
    const value = row[key]
    return value === null || value === undefined ? '' : typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ? value : String(value)
  } })), data)
  ElMessage.success(`已导出 ${data.length} 条${title.value}数据`)
  downloadExcel(
    title.value,
    columns.value.map(([key, label]) => ({
      title: label,
      value: (row: Record<string, unknown>) => {
        const value = row[key];
        return value === null || value === undefined
          ? ""
          : typeof value === "string" ||
              typeof value === "number" ||
              typeof value === "boolean"
            ? value
            : String(value);
      },
    })),
    data,
  );
  ElMessage.success(`已导出 ${data.length} 条${title.value}数据`);
}
watch(() => route.fullPath, reset)
onMounted(loadData)
watch(() => route.fullPath, reset);
onMounted(loadData);
</script>
<template>
  <div class="page-stack prototype-page business-fill-page">
    <section class="panel">
      <div class="panel-header"><h3>{{ title }}</h3></div>
      <div class="panel-header">
        <h3>{{ title }}</h3>
      </div>
      <el-form class="business-fill-filter-form" label-width="86px">
        <el-form-item :label="isUpgrade ? '持卡人' : isRole ? '角色' : '员工'"><el-input v-model="filters.keyword" :placeholder="isUpgrade ? '持卡人姓名、手机号、卡号' : '请输入关键字'" clearable /></el-form-item>
        <el-form-item v-if="isUpgrade" label="卡片等级"><el-select v-model="filters.cardLevel" clearable placeholder="全部"><el-option label="全部" value="" /><el-option label="卓越卡" value="EXCELLENT" /><el-option label="杰出卡" value="OUTSTANDING" /><el-option label="菁英卡" value="ELITE" /></el-select></el-form-item>
        <el-form-item label="状态"><el-select v-model="filters.status" clearable placeholder="全部"><el-option v-if="!isUpgrade" label="启用" value="1" /><el-option v-if="!isUpgrade" label="停用" value="0" /><el-option v-if="isUpgrade" label="全部" value="" /><el-option v-if="isUpgrade" label="待审核" value="待审核" /><el-option v-if="isUpgrade" label="已通过" value="已通过" /><el-option v-if="isUpgrade" label="已驳回" value="已驳回" /></el-select></el-form-item>
        <el-form-item v-if="isUpgrade" label="申请时间"><el-date-picker v-model="filters.timeRange" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" /></el-form-item>
        <div class="business-fill-filter-actions"><el-button type="primary" :icon="Search" @click="search">查询</el-button><el-button :icon="Refresh" @click="reset">重置</el-button></div>
        <el-form-item :label="isUpgrade ? '持卡人' : isRole ? '角色' : '员工'"
          ><el-input
            v-model="filters.keyword"
            :placeholder="
              isUpgrade ? '持卡人姓名、手机号、卡号' : '请输入关键字'
            "
            clearable
        /></el-form-item>
        <el-form-item v-if="isUpgrade" label="卡片等级"
          ><el-select v-model="filters.cardLevel" clearable placeholder="全部"
            ><el-option label="全部" value="" /><el-option
              label="卓越卡"
              value="EXCELLENT" /><el-option
              label="杰出卡"
              value="OUTSTANDING" /><el-option
              label="菁英卡"
              value="ELITE" /></el-select
        ></el-form-item>
        <el-form-item label="状态"
          ><el-select v-model="filters.status" clearable placeholder="全部"
            ><el-option v-if="!isUpgrade" label="启用" value="1" /><el-option
              v-if="!isUpgrade"
              label="停用"
              value="0" /><el-option
              v-if="isUpgrade"
              label="全部"
              value="" /><el-option
              v-if="isUpgrade"
              label="待审核"
              value="待审核" /><el-option
              v-if="isUpgrade"
              label="已通过"
              value="已通过" /><el-option
              v-if="isUpgrade"
              label="已驳回"
              value="已驳回" /></el-select
        ></el-form-item>
        <el-form-item v-if="isUpgrade" label="申请时间"
          ><el-date-picker
            v-model="filters.timeRange"
            type="daterange"
            value-format="YYYY-MM-DD"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
        /></el-form-item>
        <div class="business-fill-filter-actions">
          <el-button type="primary" :icon="Search" @click="search"
            >查询</el-button
          ><el-button :icon="Refresh" @click="reset">重置</el-button>
        </div>
      </el-form>
      <div class="business-fill-tools"><el-button v-if="!isUpgrade" type="primary" :icon="Plus" @click="openCreate">新建</el-button><el-button :icon="Download" @click="download">下载</el-button></div>
      <el-table v-loading="loading" :data="pagedRows" border class="business-fill-table" :empty-text="`暂无${title}数据`">
        <el-table-column v-for="[key, label] in columns" :key="key" :prop="key" :label="label" min-width="130" show-overflow-tooltip />
        <el-table-column label="操作" width="150" fixed="right" align="center"><template #default="{ row }"><el-button link type="primary" :icon="View" @click="view(row)">查看</el-button><el-button v-if="!isUpgrade" link type="primary" :icon="Edit" @click="openEdit(row)">编辑</el-button></template></el-table-column>
      <div class="business-fill-tools">
        <el-button
          v-if="!isUpgrade"
          type="primary"
          :icon="Plus"
          @click="openCreate"
          >新建</el-button
        ><el-button :icon="Download" @click="download">下载</el-button>
      </div>
      <el-table
        v-loading="loading"
        :data="pagedRows"
        border
        class="business-fill-table"
        :empty-text="`暂无${title}数据`"
      >
        <el-table-column
          v-for="[key, label] in columns"
          :key="key"
          :prop="key"
          :label="label"
          min-width="130"
          show-overflow-tooltip
        />
        <el-table-column label="操作" width="150" fixed="right" align="center"
          ><template #default="{ row }"
            ><el-button link type="primary" :icon="View" @click="view(row)"
              >查看</el-button
            ><el-button
              v-if="!isUpgrade"
              link
              type="primary"
              :icon="Edit"
              @click="openEdit(row)"
              >编辑</el-button
            ><template v-if="isRole">
              <el-button link type="primary" @click="changeRoleStatus(row)">{{
                Number(row.status) === 1 ? "禁用" : "启用"
              }}</el-button>
              <el-button link type="danger" @click="removeRole(row)"
                >删除</el-button
              >
            </template></template
          ></el-table-column
        >
      </el-table>
      <div class="pagination-row"><el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.size" background layout="total, prev, pager, next" :total="pagination.total" @change="changePage" /></div>
      <div class="pagination-row">
        <el-pagination
          v-model:current-page="pagination.page"
          v-model:page-size="pagination.size"
          background
          layout="total, prev, pager, next"
          :total="pagination.total"
          @change="changePage"
        />
      </div>
    </section>
    <el-dialog v-model="dialogVisible" :title="title" width="520px"><el-descriptions v-if="activeRow" :column="1" border><el-descriptions-item v-for="[key, label] in columns" :key="key" :label="label">{{ activeRow[key] || '-' }}</el-descriptions-item></el-descriptions></el-dialog>
    <el-dialog v-model="editVisible" :title="`编辑${title}`" width="480px" destroy-on-close><el-form label-width="92px"><el-form-item :label="isRole ? '角色名称' : '员工姓名'"><el-input v-model="form.name" /></el-form-item><el-form-item :label="isRole ? '角色编码' : '登录账号'"><el-input v-model="form.code" /></el-form-item><el-form-item v-if="!isRole" label="登录密码"><el-input v-model="form.password" type="password" :placeholder="editingId ? '留空则不修改' : '请输入登录密码'" /></el-form-item><el-form-item v-if="isRole" label="备注"><el-input v-model="form.remark" /></el-form-item><el-form-item label="状态"><el-switch v-model="form.status" :active-value="1" :inactive-value="0" /></el-form-item></el-form><template #footer><el-button @click="editVisible = false">取消</el-button><el-button type="primary" @click="save">保存</el-button></template></el-dialog>
    <el-dialog v-model="dialogVisible" :title="title" width="520px"
      ><el-descriptions v-if="activeRow" :column="1" border
        ><el-descriptions-item
          v-for="[key, label] in columns"
          :key="key"
          :label="label"
          >{{ activeRow[key] || "-" }}</el-descriptions-item
        ></el-descriptions
      ></el-dialog
    >
    <el-dialog
      v-model="editVisible"
      :title="`编辑${title}`"
      width="480px"
      destroy-on-close
      ><el-form label-width="92px"
        ><el-form-item :label="isRole ? '角色名称' : '员工姓名'"
          ><el-input v-model="form.name" /></el-form-item
        ><el-form-item :label="isRole ? '角色编码' : '登录账号'"
          ><el-input v-model="form.code" /></el-form-item
        ><el-form-item v-if="!isRole" label="登录密码"
          ><el-input
            v-model="form.password"
            type="password"
            :placeholder="
              editingId ? '留空则不修改' : '请输入登录密码'
            " /></el-form-item
        ><el-form-item v-if="isRole" label="备注"
          ><el-input v-model="form.remark" /></el-form-item
        ><el-form-item label="状态"
          ><el-switch
            v-model="form.status"
            :active-value="1"
            :inactive-value="0" /></el-form-item></el-form
      ><template #footer
        ><el-button @click="editVisible = false">取消</el-button
        ><el-button type="primary" @click="save">保存</el-button></template
      ></el-dialog
    >
  </div>
</template>
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementController.java
@@ -21,6 +21,8 @@
  @GetMapping("/roles") public ApiResponse<List<Map<String,Object>>> roles() { return ApiResponse.success(service.roles()); }
  @PostMapping("/roles") public ApiResponse<Map<String,Object>> createRole(@Valid @RequestBody AdminRoleRequest request) { return ApiResponse.success(service.createRole(request)); }
  @PutMapping("/roles/{id}") public ApiResponse<Map<String,Object>> updateRole(@PathVariable Long id, @Valid @RequestBody AdminRoleRequest request) { return ApiResponse.success(service.updateRole(id, request)); }
  @PatchMapping("/roles/{id}/status") public ApiResponse<Map<String,Object>> updateRoleStatus(@PathVariable Long id, @Valid @RequestBody AdminRoleStatusRequest request) { return ApiResponse.success(service.updateRoleStatus(id, request.status())); }
  @DeleteMapping("/roles/{id}") public ApiResponse<Map<String,Object>> deleteRole(@PathVariable Long id) { return ApiResponse.success(service.deleteRole(id)); }
  @GetMapping("/staff") public ApiResponse<List<Map<String,Object>>> staff() { return ApiResponse.success(service.staff()); }
  @PostMapping("/staff") public ApiResponse<Map<String,Object>> createStaff(@Valid @RequestBody AdminStaffRequest request) { return ApiResponse.success(service.createStaff(request)); }
  @PutMapping("/staff/{id}") public ApiResponse<Map<String,Object>> updateStaff(@PathVariable Long id, @Valid @RequestBody AdminStaffRequest request) { return ApiResponse.success(service.updateStaff(id, request)); }
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminManagementService.java
@@ -25,6 +25,8 @@
    public List<Map<String, Object>> roles() { return rows("select id, role_name, role_code, remark, permission_codes, status, created_at from admin_role order by id"); }
    @Transactional public Map<String, Object> createRole(AdminRoleRequest request) { String code = roleCode(request.roleCode()); jdbc.update("insert into admin_role(role_name,role_code,remark,permission_codes,status) values (?,?,?,?,?)", request.roleName().trim(), code, request.remark(), permissions(request.permissionCodes()), request.status()); return one("select id, role_name, role_code, remark, permission_codes, status, created_at from admin_role where id=last_insert_id()"); }
    @Transactional public Map<String, Object> updateRole(Long id, AdminRoleRequest request) { String code = request.roleCode() == null || request.roleCode().isBlank() ? existingRoleCode(id) : request.roleCode().trim(); if (jdbc.update("update admin_role set role_name=?,role_code=?,remark=?,permission_codes=?,status=? where id=?", request.roleName().trim(), code, request.remark(), permissions(request.permissionCodes()), request.status(), id) == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "Role does not exist"); return one("select id, role_name, role_code, remark, permission_codes, status, created_at from admin_role where id=?", id); }
    @Transactional public Map<String, Object> updateRoleStatus(Long id, Byte status) { String code = existingRoleCode(id); if (status == 0 && "platform_admin".equals(code)) throw new BusinessException(ErrorCode.CONFLICT, "平台管理员角色不可禁用"); jdbc.update("update admin_role set status=? where id=?", status, id); return one("select id, role_name, role_code, remark, permission_codes, status, created_at from admin_role where id=?", id); }
    @Transactional public Map<String, Object> deleteRole(Long id) { String code = existingRoleCode(id); if ("platform_admin".equals(code)) throw new BusinessException(ErrorCode.CONFLICT, "平台管理员角色不可删除"); if (Boolean.TRUE.equals(jdbc.queryForObject("select exists(select 1 from admin_operator where role_code=? and deleted=0)", Boolean.class, code))) throw new BusinessException(ErrorCode.CONFLICT, "该角色仍有员工使用,不能删除"); jdbc.update("delete from admin_role where id=?", id); return Map.of("id", id, "deleted", true); }
    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()"); }
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminRoleStatusRequest.java
New file
@@ -0,0 +1,5 @@
package com.talentcard.server.business.admin;
import jakarta.validation.constraints.NotNull;
public record AdminRoleStatusRequest(@NotNull Byte status) {}