| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- <template>
- <Modal :open="open" title="用户管理" :width="920" :footer="null" destroy-on-close @cancel="handleClose">
- <div class="user-manage__toolbar">
- <InputSearch
- v-model:value="keyword"
- placeholder="搜索用户名 / 显示名"
- allow-clear
- style="width: 240px"
- @search="handleSearch"
- />
- <Button type="primary" @click="openCreate">新建用户</Button>
- </div>
- <Table
- :data-source="records"
- :columns="columns"
- :loading="loading"
- :pagination="pagination"
- row-key="id"
- size="small"
- @change="handleTableChange"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'isAdmin'">
- <Tag :color="Number(record.isAdmin) === 1 ? 'blue' : 'default'">
- {{ Number(record.isAdmin) === 1 ? '管理员' : '普通用户' }}
- </Tag>
- </template>
- <template v-else-if="column.key === 'status'">
- <Tag :color="Number(record.status) === 1 ? 'success' : 'error'">
- {{ Number(record.status) === 1 ? '启用' : '停用' }}
- </Tag>
- </template>
- <template v-else-if="column.key === 'action'">
- <a class="user-manage__link" @click="openEdit(record)">编辑</a>
- <Divider type="vertical" />
- <a class="user-manage__link" @click="openResetPwd(record)">重置密码</a>
- <Divider type="vertical" />
- <a class="user-manage__link" @click="handleKickout(record)">强制下线</a>
- <Divider type="vertical" />
- <a class="user-manage__link user-manage__link--danger" @click="handleDelete(record)">删除</a>
- </template>
- </template>
- </Table>
- </Modal>
- <Modal
- v-model:open="formVisible"
- :title="form.id ? '编辑用户' : '新建用户'"
- :width="460"
- :confirm-loading="submitting"
- destroy-on-close
- @ok="handleSubmit"
- >
- <Form layout="vertical">
- <FormItem label="用户名" required>
- <Input v-model:value="form.username" :disabled="!!form.id" placeholder="登录名,创建后不可修改" />
- </FormItem>
- <FormItem v-if="!form.id" label="初始密码" required>
- <InputPassword v-model:value="form.password" placeholder="至少 6 位" autocomplete="new-password" />
- </FormItem>
- <FormItem label="显示名">
- <Input v-model:value="form.displayName" placeholder="不填则与用户名一致" />
- </FormItem>
- <FormItem label="角色">
- <RadioGroup v-model:value="form.isAdmin">
- <Radio :value="0">普通用户</Radio>
- <Radio :value="1">管理员</Radio>
- </RadioGroup>
- </FormItem>
- <FormItem v-if="form.id" label="状态">
- <RadioGroup v-model:value="form.status">
- <Radio :value="1">启用</Radio>
- <Radio :value="0">停用</Radio>
- </RadioGroup>
- </FormItem>
- </Form>
- </Modal>
- <Modal
- v-model:open="pwdVisible"
- title="重置密码"
- :width="420"
- :confirm-loading="submitting"
- destroy-on-close
- @ok="handleResetPwd"
- >
- <p class="user-manage__hint">为「{{ pwdTarget?.username }}」设置新密码,重置后该用户会被强制下线。</p>
- <InputPassword v-model:value="newPassword" placeholder="至少 6 位" autocomplete="new-password" />
- </Modal>
- </template>
- <script lang="ts">
- import { defineComponent, reactive, ref, watch } from 'vue';
- import {
- Button,
- Divider,
- Form,
- FormItem,
- Input,
- InputPassword,
- InputSearch,
- Modal,
- Radio,
- RadioGroup,
- Table,
- Tag,
- } from 'ant-design-vue';
- import { useMessage } from '@/core/hooks/web/useMessage';
- import {
- createUserApi,
- deleteUserApi,
- kickoutUserApi,
- listUsersApi,
- resetUserPwdApi,
- updateUserApi,
- type UserInfo,
- } from '@/core/api/auth/authApi';
- export default defineComponent({
- name: 'UserManageModal',
- components: {
- Button,
- Divider,
- Form,
- FormItem,
- Input,
- InputPassword,
- InputSearch,
- Modal,
- Radio,
- RadioGroup,
- Table,
- Tag,
- },
- props: {
- open: {
- type: Boolean,
- default: false,
- },
- },
- emits: ['update:open'],
- setup(props, { emit }) {
- const { createMessage, createConfirm } = useMessage();
- const loading = ref(false);
- const submitting = ref(false);
- const keyword = ref('');
- const records = ref<UserInfo[]>([]);
- const pagination = reactive({
- current: 1,
- pageSize: 10,
- total: 0,
- showSizeChanger: true,
- showTotal: (total: number) => `共 ${total} 条`,
- });
- const columns = [
- { title: '用户名', dataIndex: 'username', key: 'username', width: 140 },
- { title: '显示名', dataIndex: 'displayName', key: 'displayName', width: 140 },
- { title: '角色', key: 'isAdmin', width: 100 },
- { title: '状态', key: 'status', width: 90 },
- { title: '最后登录', dataIndex: 'lastLoginTime', key: 'lastLoginTime', width: 170 },
- { title: '操作', key: 'action', width: 260 },
- ];
- const formVisible = ref(false);
- const form = reactive<{
- id?: string;
- username: string;
- password: string;
- displayName: string;
- isAdmin: number;
- status: number;
- }>({ username: '', password: '', displayName: '', isAdmin: 0, status: 1 });
- const pwdVisible = ref(false);
- const newPassword = ref('');
- const pwdTarget = ref<UserInfo | null>(null);
- async function loadList() {
- loading.value = true;
- try {
- const res = await listUsersApi({
- keyword: keyword.value || undefined,
- page: pagination.current,
- size: pagination.pageSize,
- });
- records.value = res?.records || [];
- pagination.total = Number(res?.total || 0);
- } catch {
- records.value = [];
- } finally {
- loading.value = false;
- }
- }
- function handleSearch() {
- pagination.current = 1;
- void loadList();
- }
- function handleTableChange(pag: { current?: number; pageSize?: number }) {
- pagination.current = pag?.current || 1;
- pagination.pageSize = pag?.pageSize || 10;
- void loadList();
- }
- function openCreate() {
- Object.assign(form, { id: undefined, username: '', password: '', displayName: '', isAdmin: 0, status: 1 });
- formVisible.value = true;
- }
- function openEdit(record: Record<string, any>) {
- Object.assign(form, {
- id: record.id,
- username: record.username,
- password: '',
- displayName: record.displayName,
- isAdmin: Number(record.isAdmin),
- status: Number(record.status),
- });
- formVisible.value = true;
- }
- async function handleSubmit() {
- const username = form.username?.trim();
- if (!form.id && !username) {
- createMessage.warning('请填写用户名');
- return;
- }
- if (!form.id && (!form.password || form.password.length < 6)) {
- createMessage.warning('初始密码不能少于 6 位');
- return;
- }
- submitting.value = true;
- try {
- if (form.id) {
- await updateUserApi({
- id: form.id,
- displayName: form.displayName,
- isAdmin: form.isAdmin,
- status: form.status,
- });
- } else {
- await createUserApi({
- username,
- password: form.password,
- displayName: form.displayName,
- isAdmin: form.isAdmin,
- });
- }
- formVisible.value = false;
- await loadList();
- } catch {
- // 错误提示由 defHttp 统一弹出
- } finally {
- submitting.value = false;
- }
- }
- function openResetPwd(record: Record<string, any>) {
- pwdTarget.value = record as unknown as UserInfo;
- newPassword.value = '';
- pwdVisible.value = true;
- }
- async function handleResetPwd() {
- if (!pwdTarget.value || !newPassword.value || newPassword.value.length < 6) {
- createMessage.warning('新密码不能少于 6 位');
- return;
- }
- submitting.value = true;
- try {
- await resetUserPwdApi({ id: pwdTarget.value.id, newPassword: newPassword.value });
- pwdVisible.value = false;
- createMessage.success('密码已重置');
- } catch {
- // 错误提示由 defHttp 统一弹出
- } finally {
- submitting.value = false;
- }
- }
- function handleKickout(record: Record<string, any>) {
- createConfirm({
- iconType: 'warning',
- title: '提示',
- content: `确认强制「${record.username}」下线?`,
- onOk: async () => {
- await kickoutUserApi(record.id);
- createMessage.success('已强制下线');
- },
- });
- }
- function handleDelete(record: Record<string, any>) {
- createConfirm({
- iconType: 'warning',
- title: '提示',
- content: `确认删除用户「${record.username}」?该操作不可恢复。`,
- onOk: async () => {
- await deleteUserApi(record.id);
- createMessage.success('已删除');
- await loadList();
- },
- });
- }
- function handleClose() {
- emit('update:open', false);
- }
- watch(
- () => props.open,
- (val) => {
- if (val) {
- pagination.current = 1;
- keyword.value = '';
- void loadList();
- }
- },
- { immediate: true },
- );
- return {
- loading,
- submitting,
- keyword,
- records,
- pagination,
- columns,
- formVisible,
- form,
- pwdVisible,
- newPassword,
- pwdTarget,
- handleSearch,
- handleTableChange,
- openCreate,
- openEdit,
- handleSubmit,
- openResetPwd,
- handleResetPwd,
- handleKickout,
- handleDelete,
- handleClose,
- };
- },
- });
- </script>
- <style lang="less" scoped>
- .user-manage {
- &__toolbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 12px;
- }
- &__link {
- color: @primary-color;
- &--danger {
- color: @error-color;
- }
- }
- &__hint {
- margin-bottom: 12px;
- color: @cb-text-secondary;
- }
- }
- </style>
|