shinnosuke
2026-08-06 d420ee7de1abdbed74f6118c84e5877873cd8550
fix(admin): 补充角色和员工分页 (bug #24023)
2个文件已修改
382 ■■■■■ 已修改文件
talent-card-admin/src/views/BusinessManagementView.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/StaffManagementView.vue 376 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/BusinessManagementView.vue
@@ -30,6 +30,7 @@
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', '状态']]
@@ -64,6 +65,7 @@
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}`)
@@ -131,11 +133,11 @@
        <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="rows" border class="business-fill-table" :empty-text="`暂无${title}数据`">
      <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>
      </el-table>
      <div class="pagination-row"><el-pagination v-if="isUpgrade" v-model:current-page="pagination.page" v-model:page-size="pagination.size" background layout="total, prev, pager, next" :total="pagination.total" @change="loadData" /></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>
talent-card-admin/src/views/StaffManagementView.vue
@@ -1,29 +1,351 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
import { createAdminStaff, deleteAdminStaff, getAdminRoles, getAdminStaff, resetAdminStaffPassword, updateAdminStaff } from '../api/admin'
import type { AdminRole, AdminStaff } from '../types/admin'
const loading = ref(false), visible = ref(false), saving = ref(false), editId = ref<number | string>()
const rows = ref<AdminStaff[]>([]), roles = ref<AdminRole[]>([])
const filters = reactive({ name: '', mobile: '', role: '', status: '' })
const form = reactive({ displayName: '', mobile: '', account: '', password: '', confirmPassword: '', roleCode: '', positionName: '', customerWechat: '', status: 1 })
const filtered = computed(() => rows.value.filter(r => (!filters.name || r.displayName.includes(filters.name)) && (!filters.mobile || (r.mobile || '').includes(filters.mobile)) && (!filters.role || r.roleCode === filters.role) && (!filters.status || String(r.status) === filters.status)))
const roleName = (code: string) => roles.value.find(r => r.roleCode === code)?.roleName || code
const formatDateTime = (value?: string) => value ? value.replace('T', ' ').slice(0, 19) : ''
const isDefaultAdmin = (row: AdminStaff) => row.account === 'admin'
const editingDefaultAdmin = computed(() => rows.value.some(row => String(row.id) === String(editId.value) && isDefaultAdmin(row)))
const keepMobileDigits = () => { form.mobile = form.mobile.replace(/\D/g, '').slice(0, 11) }
const keepAccountCharacters = () => { form.account = form.account.replace(/[^A-Za-z0-9]/g, '').slice(0, 32) }
async function load() { loading.value = true; try { const result = await Promise.all([getAdminStaff(), getAdminRoles()]); rows.value = result[0]; roles.value = result[1].filter(r => r.status === 1) } finally { loading.value = false } }
function reset() { Object.assign(filters, { name: '', mobile: '', role: '', status: '' }) }
function create() { editId.value = undefined; Object.assign(form, { displayName: '', mobile: '', account: '', password: '', confirmPassword: '', roleCode: roles.value[0]?.roleCode || '', positionName: '', customerWechat: '', status: 1 }); visible.value = true }
function edit(row: AdminStaff) { editId.value = row.id; Object.assign(form, { displayName: row.displayName, mobile: row.mobile || '', account: row.account, password: '', confirmPassword: '', roleCode: row.roleCode, positionName: row.positionName || '', customerWechat: row.customerWechat || '', status: row.status }); visible.value = true }
async function save() { if (!form.displayName.trim() || !form.account.trim() || !form.roleCode || !form.positionName.trim()) return ElMessage.warning('请填写人员名称、登录账号、角色和职务'); if (!editId.value && form.password.length < 6) return ElMessage.warning('新建员工密码至少 6 位'); if (!editId.value && form.password !== form.confirmPassword) return ElMessage.warning('两次输入的密码不一致'); saving.value = true; try { const { confirmPassword, ...formPayload } = form; const payload = { ...formPayload, password: form.password || undefined }; if (editId.value) await updateAdminStaff(editId.value, payload); else await createAdminStaff({ ...payload, password: form.password }); ElMessage.success('保存成功'); visible.value = false; load() } finally { saving.value = false } }
async function changeStatus(row: AdminStaff) { await updateAdminStaff(row.id, { displayName: row.displayName, mobile: row.mobile || '', account: row.account, roleCode: row.roleCode, positionName: row.positionName || '未设置', customerWechat: row.customerWechat || '', status: row.status === 1 ? 0 : 1 }); ElMessage.success(row.status === 1 ? '已禁用' : '已启用'); load() }
async function password(row: AdminStaff) { const result = await ElMessageBox.prompt('请输入 6-64 位新密码', `重置 ${row.displayName} 的密码`, { inputType: 'password', inputPattern: /^.{6,64}$/, inputErrorMessage: '密码长度为 6-64 位' }); await resetAdminStaffPassword(row.id, result.value); ElMessage.success('密码已重置') }
async function remove(row: AdminStaff) { await ElMessageBox.confirm(`确认删除员工“${row.displayName}”?`, '删除员工', { type: 'warning' }); await deleteAdminStaff(row.id); ElMessage.success('已删除'); load() }
onMounted(load)
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Refresh, Search } from "@element-plus/icons-vue";
import {
  createAdminStaff,
  deleteAdminStaff,
  getAdminRoles,
  getAdminStaff,
  resetAdminStaffPassword,
  updateAdminStaff,
} from "../api/admin";
import type { AdminRole, AdminStaff } from "../types/admin";
const loading = ref(false),
  visible = ref(false),
  saving = ref(false),
  editId = ref<number | string>();
const rows = ref<AdminStaff[]>([]),
  roles = ref<AdminRole[]>([]);
const filters = reactive({ name: "", mobile: "", role: "", status: "" });
const form = reactive({
  displayName: "",
  mobile: "",
  account: "",
  password: "",
  confirmPassword: "",
  roleCode: "",
  positionName: "",
  customerWechat: "",
  status: 1,
});
const filtered = computed(() =>
  rows.value.filter(
    (r) =>
      (!filters.name || r.displayName.includes(filters.name)) &&
      (!filters.mobile || (r.mobile || "").includes(filters.mobile)) &&
      (!filters.role || r.roleCode === filters.role) &&
      (!filters.status || String(r.status) === filters.status),
  ),
);
const pagination = reactive({ page: 1, size: 10 });
const pagedRows = computed(() =>
  filtered.value.slice(
    (pagination.page - 1) * pagination.size,
    pagination.page * pagination.size,
  ),
);
const roleName = (code: string) =>
  roles.value.find((r) => r.roleCode === code)?.roleName || code;
const formatDateTime = (value?: string) =>
  value ? value.replace("T", " ").slice(0, 19) : "";
const isDefaultAdmin = (row: AdminStaff) => row.account === "admin";
const editingDefaultAdmin = computed(() =>
  rows.value.some(
    (row) => String(row.id) === String(editId.value) && isDefaultAdmin(row),
  ),
);
const keepMobileDigits = () => {
  form.mobile = form.mobile.replace(/\D/g, "").slice(0, 11);
};
const keepAccountCharacters = () => {
  form.account = form.account.replace(/[^A-Za-z0-9]/g, "").slice(0, 32);
};
async function load() {
  loading.value = true;
  try {
    const result = await Promise.all([getAdminStaff(), getAdminRoles()]);
    rows.value = result[0];
    roles.value = result[1].filter((r) => r.status === 1);
  } finally {
    loading.value = false;
  }
}
function reset() {
  Object.assign(filters, { name: "", mobile: "", role: "", status: "" });
  pagination.page = 1;
}
function create() {
  editId.value = undefined;
  Object.assign(form, {
    displayName: "",
    mobile: "",
    account: "",
    password: "",
    confirmPassword: "",
    roleCode: roles.value[0]?.roleCode || "",
    positionName: "",
    customerWechat: "",
    status: 1,
  });
  visible.value = true;
}
function edit(row: AdminStaff) {
  editId.value = row.id;
  Object.assign(form, {
    displayName: row.displayName,
    mobile: row.mobile || "",
    account: row.account,
    password: "",
    confirmPassword: "",
    roleCode: row.roleCode,
    positionName: row.positionName || "",
    customerWechat: row.customerWechat || "",
    status: row.status,
  });
  visible.value = true;
}
async function save() {
  if (
    !form.displayName.trim() ||
    !form.account.trim() ||
    !form.roleCode ||
    !form.positionName.trim()
  )
    return ElMessage.warning("请填写人员名称、登录账号、角色和职务");
  if (!editId.value && form.password.length < 6)
    return ElMessage.warning("新建员工密码至少 6 位");
  if (!editId.value && form.password !== form.confirmPassword)
    return ElMessage.warning("两次输入的密码不一致");
  saving.value = true;
  try {
    const { confirmPassword, ...formPayload } = form;
    const payload = { ...formPayload, password: form.password || undefined };
    if (editId.value) await updateAdminStaff(editId.value, payload);
    else await createAdminStaff({ ...payload, password: form.password });
    ElMessage.success("保存成功");
    visible.value = false;
    load();
  } finally {
    saving.value = false;
  }
}
async function changeStatus(row: AdminStaff) {
  await updateAdminStaff(row.id, {
    displayName: row.displayName,
    mobile: row.mobile || "",
    account: row.account,
    roleCode: row.roleCode,
    positionName: row.positionName || "未设置",
    customerWechat: row.customerWechat || "",
    status: row.status === 1 ? 0 : 1,
  });
  ElMessage.success(row.status === 1 ? "已禁用" : "已启用");
  load();
}
async function password(row: AdminStaff) {
  const result = await ElMessageBox.prompt(
    "请输入 6-64 位新密码",
    `重置 ${row.displayName} 的密码`,
    {
      inputType: "password",
      inputPattern: /^.{6,64}$/,
      inputErrorMessage: "密码长度为 6-64 位",
    },
  );
  await resetAdminStaffPassword(row.id, result.value);
  ElMessage.success("密码已重置");
}
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" :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 v-if="!editId" label="确认密码" required><el-input v-model="form.confirmPassword" type="password" placeholder="请再次输入密码"/></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>
<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="pagedRows" 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
      >
      <div class="pagination-row">
        <el-pagination
          v-model:current-page="pagination.page"
          background
          layout="total, prev, pager, next"
          :page-size="pagination.size"
          :total="filtered.length"
        />
      </div>
    </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 v-if="!editId" label="确认密码" required
          ><el-input
            v-model="form.confirmPassword"
            type="password"
            placeholder="请再次输入密码" /></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>