This commit is contained in:
2026-05-31 12:09:14 +08:00
commit e4f9eedb80
1121 changed files with 209380 additions and 0 deletions

View File

@@ -0,0 +1,345 @@
<template>
<a-modal
:width="500"
:visible="visible"
:footer="null"
title="邀请注册"
@update:visible="updateVisible"
>
<div style="text-align: center">
<div style="margin-bottom: 20px">
<a-typography-title :level="4">邀请新成员注册</a-typography-title>
<a-typography-text type="secondary">
分享以下链接或二维码邀请新管理人员注册(非普通用户)
</a-typography-text>
</div>
<!-- 邀请链接 -->
<div style="margin-bottom: 20px">
<a-input :value="invitationLink" readonly style="margin-bottom: 8px">
<template #addonAfter>
<a-button type="link" size="small" @click="copyLink">
复制链接
</a-button>
</template>
</a-input>
</div>
<!-- 二维码选择 -->
<div style="margin-bottom: 16px">
<a-radio-group v-model:value="qrCodeType" @change="onQRCodeTypeChange">
<a-radio-button value="web">网页二维码</a-radio-button>
<a-radio-button value="miniprogram">小程序码</a-radio-button>
</a-radio-group>
</div>
<!-- 二维码显示 -->
<div style="margin-bottom: 20px">
<div
style="
display: inline-block;
padding: 10px;
background: white;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
"
>
<!-- 网页二维码 -->
<ele-qr-code-svg
v-if="qrCodeType === 'web'"
:value="invitationLink"
:size="200"
/>
<!-- 小程序码 -->
<div
v-else-if="qrCodeType === 'miniprogram'"
style="
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
"
>
<img
v-if="miniProgramCodeUrl"
:src="miniProgramCodeUrl"
style="width: 180px; height: 180px; object-fit: contain"
alt="小程序码"
@error="onMiniProgramCodeError"
@load="onMiniProgramCodeLoad"
/>
<a-spin v-else-if="loadingMiniCode" tip="正在生成小程序码..." />
<div v-else style="color: #999; text-align: center">
<div>小程序码加载失败</div>
<a-button size="small" @click="loadMiniProgramCode"
>重新加载</a-button
>
</div>
</div>
</div>
</div>
<!-- 使用说明 -->
<div
style="
text-align: left;
background: #f5f5f5;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
"
>
<div style="font-weight: 500; margin-bottom: 8px">使用说明</div>
<div style="font-size: 12px; color: #666; line-height: 1.5">
<template v-if="qrCodeType === 'web'">
1. 复制邀请链接发送给用户或让用户扫描网页二维码<br />
2. 用户点击链接或扫码进入注册页面<br />
3. 用户完成注册后系统自动建立推荐关系<br />
4. 您可以在"推荐关系管理"中查看邀请结果
</template>
<template v-else>
1. 让用户扫描小程序码进入小程序<br />
2. 小程序会自动识别邀请信息<br />
3. 用户在小程序内完成注册后系统自动建立推荐关系<br />
4. 您可以在"推荐关系管理"中查看邀请结果
</template>
</div>
</div>
<!-- 调试信息 -->
<div
v-if="showDebugInfo"
style="
margin-bottom: 16px;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
font-size: 12px;
"
>
<div><strong>调试信息:</strong></div>
<div>邀请人ID: {{ inviterId }}</div>
<div>邀请链接: {{ invitationLink }}</div>
<div v-if="qrCodeType === 'miniprogram'"
>小程序码URL: {{ miniProgramCodeUrl }}</div
>
<div>BaseUrl: {{ baseUrl }}</div>
</div>
<!-- 操作按钮 -->
<div>
<a-space>
<a-button @click="downloadQRCode">下载二维码</a-button>
<a-button type="primary" @click="copyLink">复制链接</a-button>
</a-space>
</div>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import { useRouter } from 'vue-router';
import { generateInviteCode } from '@/api/miniprogram';
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
visible: boolean;
inviterId?: number; // 邀请人ID当前登录用户ID
}>();
// 二维码类型
const qrCodeType = ref<'web' | 'miniprogram'>('web');
// 小程序码URL
const miniProgramCodeUrl = ref<string>('');
// 小程序码加载状态
const loadingMiniCode = ref(false);
// 显示调试信息
const showDebugInfo = ref(false);
// 基础URL用于调试
const baseUrl = ref('');
// 获取邀请人ID
const inviterId = computed(() => {
return props.inviterId || Number(localStorage.getItem('UserId'));
});
// 邀请链接需要带上 tenantId避免未登录用户打开链接时后端无法识别租户导致角色/权限初始化失败
const tenantId = computed(() => {
const tid = localStorage.getItem('TenantId');
return tid ? Number(tid) : undefined;
});
// 生成邀请链接
const invitationLink = computed(() => {
const baseUrl = window.location.origin;
const params = new URLSearchParams();
params.set('inviter', String(inviterId.value));
if (tenantId.value) {
params.set('tenantId', String(tenantId.value));
}
return `${baseUrl}/dealer/register?${params.toString()}`;
});
// 复制链接
const copyLink = async () => {
try {
await navigator.clipboard.writeText(invitationLink.value);
message.success('邀请链接已复制到剪贴板');
} catch (e) {
// 降级方案
const textArea = document.createElement('textarea');
textArea.value = invitationLink.value;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
message.success('邀请链接已复制到剪贴板');
}
};
// 加载小程序码
const loadMiniProgramCode = async () => {
const currentInviterId = inviterId.value;
if (!currentInviterId) {
console.error('邀请人ID不存在');
message.error('邀请人ID不存在');
return;
}
console.log('开始加载小程序码邀请人ID:', currentInviterId);
loadingMiniCode.value = true;
try {
const codeUrl = await generateInviteCode(currentInviterId);
console.log('小程序码生成成功:', codeUrl);
miniProgramCodeUrl.value = codeUrl;
message.success('小程序码加载成功');
} catch (e: any) {
console.error('加载小程序码失败:', e);
message.error(`小程序码加载失败: ${e.message}`);
} finally {
loadingMiniCode.value = false;
}
};
// 小程序码加载错误
const onMiniProgramCodeError = () => {
console.error('小程序码图片加载失败');
message.error('小程序码显示失败');
};
// 小程序码加载成功
const onMiniProgramCodeLoad = () => {
console.log('小程序码图片加载成功');
};
// 二维码类型切换
const onQRCodeTypeChange = () => {
if (qrCodeType.value === 'miniprogram' && !miniProgramCodeUrl.value) {
loadMiniProgramCode();
}
};
// 下载二维码
const downloadQRCode = () => {
try {
if (qrCodeType.value === 'web') {
// 下载网页二维码 - 查找SVG元素
const svgElement = document.querySelector(
'.ant-modal-body svg'
) as SVGElement;
if (svgElement) {
// 将SVG转换为Canvas再下载
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
// 获取SVG的XML字符串
const svgData = new XMLSerializer().serializeToString(svgElement);
const svgBlob = new Blob([svgData], {
type: 'image/svg+xml;charset=utf-8'
});
const url = URL.createObjectURL(svgBlob);
img.onload = () => {
canvas.width = img.width || 200;
canvas.height = img.height || 200;
ctx?.drawImage(img, 0, 0);
const link = document.createElement('a');
link.download = `邀请注册二维码.png`;
link.href = canvas.toDataURL('image/png');
link.click();
URL.revokeObjectURL(url);
message.success('二维码已下载');
};
img.onerror = () => {
URL.revokeObjectURL(url);
message.error('二维码下载失败');
};
img.src = url;
} else {
message.error('未找到二维码,请稍后重试');
}
} else {
// 下载小程序码
if (miniProgramCodeUrl.value) {
const link = document.createElement('a');
link.download = `邀请小程序码.png`;
link.href = miniProgramCodeUrl.value;
link.target = '_blank';
link.click();
message.success('小程序码已下载');
} else {
message.error('小程序码未加载');
}
}
} catch (e) {
console.error('下载失败:', e);
message.error('下载失败');
}
};
// 更新visible
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 监听弹窗显示状态
watch(
() => props.visible,
(visible) => {
if (visible) {
// 重置状态
qrCodeType.value = 'web';
miniProgramCodeUrl.value = '';
loadingMiniCode.value = false;
showDebugInfo.value = false;
// 获取调试信息
import('@/config/setting').then(({ SERVER_API_URL }) => {
baseUrl.value = SERVER_API_URL;
});
}
}
);
</script>
<style lang="less" scoped>
:deep(.ant-typography-title) {
margin-bottom: 8px !important;
}
:deep(.ant-input-group-addon) {
padding: 0;
}
</style>

View File

@@ -0,0 +1,39 @@
<!-- 机构选择下拉框 -->
<template>
<a-tree-select
allow-clear
tree-default-expand-all
:placeholder="placeholder"
:value="value || undefined"
:tree-data="data"
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
@update:value="updateValue"
/>
</template>
<script lang="ts" setup>
import type { Organization } from '@/api/system/organization/model';
const emit = defineEmits<{
(e: 'update:value', value?: number): void;
}>();
withDefaults(
defineProps<{
// 选中的数据(v-modal)
value?: number;
// 提示信息
placeholder?: string;
// 机构数据
data: Organization[];
}>(),
{
placeholder: '请选择角色'
}
);
/* 更新选中数据 */
const updateValue = (value?: number) => {
emit('update:value', value);
};
</script>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,42 @@
<template>
<a-card title="管理员" style="margin-bottom: 20px">
<div class="title flex flex-col">
<div class="text-gray-400 pb-2">系统所有者拥有全部权限</div>
</div>
<div
v-if="item"
class="bg-gray-50 rounded-lg w-80 p-4 flex justify-between items-center"
>
<a-space>
<a-avatar size="large" :src="item.avatar" />
<div class="text-gray-400 flex flex-col">
<span>{{ item.nickname }}</span>
<span>{{ item.createTime }}</span>
</div>
</a-space>
<a>更换</a>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { listUsers } from '@/api/system/user';
import { User } from '@/api/system/user/model';
const item = ref<User>();
const reload = async () => {
const list = await listUsers({
isSuperAdmin: true
});
console.log(list);
if (list.length > 0) {
item.value = list[0];
}
};
onMounted(() => {
reload();
});
</script>

View File

@@ -0,0 +1,275 @@
<!-- 管理员编辑弹窗 -->
<template>
<ele-modal
:width="500"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate ? '编辑项目成员' : '添加项目成员'"
:body-style="{ paddingBottom: '8px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 5, sm: 4, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 17, sm: 20, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="姓名" name="realName">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入真实姓名"
v-model:value="form.realName"
/>
</a-form-item>
<a-form-item label="手机号" name="phone">
<a-input
allow-clear
:maxlength="11"
:disabled="isUpdate"
placeholder="请输入手机号"
v-model:value="form.phone"
/>
</a-form-item>
<a-form-item v-if="!isUpdate" label="登录密码" name="password">
<a-input-password
:maxlength="20"
v-model:value="form.password"
placeholder="请输入登录密码"
/>
</a-form-item>
<!-- <a-form-item label="性别" name="sex">-->
<!-- <DictSelect-->
<!-- dict-code="sex"-->
<!-- :placeholder="`请选择性别`"-->
<!-- v-model:value="form.sexName"-->
<!-- @done="chooseSex"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="邮箱" name="email">-->
<!-- <a-input-->
<!-- allow-clear-->
<!-- :maxlength="100"-->
<!-- placeholder="请输入邮箱"-->
<!-- v-model:value="form.email"-->
<!-- />-->
<!-- </a-form-item>-->
<a-form-item label="所属机构" name="type">
<org-select
:data="organizationList"
placeholder="请选择所属机构"
v-model:value="form.organizationId"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue/es';
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
import { emailReg, phoneReg } from 'ele-admin-pro/es';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import useFormData from '@/utils/use-form-data';
import { addUser, updateUser, checkExistence } from '@/api/system/user';
import type { User } from '@/api/system/user/model';
import OrgSelect from './org-select.vue';
import { Organization } from '@/api/system/organization/model';
import { TEMPLATE_ID } from '@/config/setting';
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
// 获取字典数据
// const userTypeData = getDictionaryOptions('userType');
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: User | null;
// 全部机构
organizationList: Organization[];
}>();
//
const formRef = ref<FormInstance | null>(null);
// 是否是修改
const isUpdate = ref(false);
// 提交状态
const loading = ref(false);
// 表单数据
const { form, resetFields, assignFields } = useFormData<User>({
type: undefined,
userId: undefined,
username: '',
nickname: '',
realName: '',
companyName: '',
sex: undefined,
sexName: undefined,
roles: [],
email: '',
phone: '',
mobile: '',
password: '',
introduction: '',
organizationId: undefined,
birthday: '',
idCard: '',
comments: '',
gradeName: '',
isAdmin: true,
gradeId: undefined,
templateId: TEMPLATE_ID
});
// 表单验证规则
const rules = reactive<Record<string, Rule[]>>({
username: [
{
required: true,
type: 'string',
validator: (_rule: Rule, value: string) => {
return new Promise<void>((resolve, reject) => {
if (!value) {
return reject('请输入管理员账号');
}
checkExistence('username', value, props.data?.userId)
.then(() => {
reject('账号已经存在');
})
.catch(() => {
resolve();
});
});
},
trigger: 'blur'
}
],
nickname: [
{
required: true,
message: '请输入昵称',
type: 'string',
trigger: 'blur'
}
],
realName: [
{
required: true,
message: '请输入真实姓名',
type: 'string',
trigger: 'blur'
}
],
roles: [
{
required: true,
message: '请选择角色',
type: 'array',
trigger: 'blur'
}
],
email: [
{
pattern: emailReg,
message: '邮箱格式不正确',
type: 'string',
trigger: 'blur'
}
],
password: [
{
required: true,
type: 'string',
validator: async (_rule: Rule, value: string) => {
if (isUpdate.value || /^[\S]{5,18}$/.test(value)) {
return Promise.resolve();
}
return Promise.reject('密码必须为5-18位非空白字符');
},
trigger: 'blur'
}
],
phone: [
{
required: true,
pattern: phoneReg,
message: '手机号格式不正确',
type: 'string',
trigger: 'blur'
}
]
});
const chooseSex = (data: any) => {
form.sex = data.key;
form.sexName = data.label;
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const saveOrUpdate = isUpdate.value ? updateUser : addUser;
form.username = form.phone;
form.nickname = form.realName;
saveOrUpdate(form)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
watch(
() => props.visible,
(visible) => {
if (visible) {
if (props.data) {
assignFields({
...props.data,
password: ''
});
isUpdate.value = true;
} else {
isUpdate.value = false;
}
} else {
resetFields();
formRef.value?.clearValidate();
}
}
);
</script>

View File

@@ -0,0 +1,439 @@
<template>
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
<SuperAdmin />
<a-card :bordered="false">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="userId"
:columns="columns"
:datasource="datasource"
class="sys-org-table"
:scroll="{ x: 1300 }"
:where="defaultWhere"
:customRow="customRow"
cache-key="proSystemUserTable"
>
<template #toolbar>
<a-space>
<a-button
type="primary"
class="ele-btn-icon"
@click="openInvitation"
>
<template #icon>
<plus-outlined />
</template>
<span>邀请注册</span>
</a-button>
<a-input-search
allow-clear
v-model:value="searchText"
placeholder="请输入关键词"
@search="reload"
@pressEnter="reload"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'avatar'">
<a-avatar
:size="30"
:src="`${record.avatar}`"
style="margin-right: 4px"
>
<template #icon>
<UserOutlined />
</template>
</a-avatar>
</template>
<template v-if="column.key === 'realName'">
<div class="flex flex-col items-center">
<span>{{ record.realName }}</span>
<span class="text-gray-400" v-if="hasRole('superAdmin')">{{
record.phone
}}</span>
<span class="text-gray-400" v-else>{{ record.mobile }}</span>
</div>
</template>
<template v-if="column.key === 'roles'">
<a-tag v-for="item in record.roles" :key="item.roleId" color="blue">
{{ item.roleName }}
</a-tag>
</template>
<template v-if="column.key === 'platform'">
<WechatOutlined v-if="record.platform === 'MP-WEIXIN'" />
<Html5Outlined v-if="record.platform === 'H5'" />
<ChromeOutlined v-if="record.platform === 'WEB'" />
</template>
<template v-if="column.key === 'balance'">
<span class="ele-text-success">
{{ formatNumber(record.balance) }}
</span>
</template>
<template v-if="column.key === 'expendMoney'">
<span class="ele-text-warning">
{{ formatNumber(record.expendMoney) }}
</span>
</template>
<template v-if="column.key === 'isAdmin'">
<a-switch
:checked="record.isAdmin == 1"
@change="updateIsAdmin(record)"
/>
</template>
<template v-if="column.key === 'action'">
<div>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a @click="resetPsw(record)">重置</a>
<a-divider type="vertical" />
<a-popconfirm
placement="topRight"
title="确定要删除此用户吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</div>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<user-edit
v-model:visible="showEdit"
:data="current"
:organization-list="data"
@done="reload"
/>
<!-- 导入弹窗 -->
<user-import v-model:visible="showImport" @done="reload" />
<!-- 用户详情 -->
<user-info v-model:visible="showInfo" :data="current" @done="reload" />
<!-- 邀请注册弹窗 -->
<invitation-modal
v-model:visible="showInvitation"
:inviter-id="currentUserId"
/>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref, reactive, watch } from 'vue';
import { message, Modal } from 'ant-design-vue/es';
import {
PlusOutlined,
UserOutlined,
Html5Outlined,
ChromeOutlined,
WechatOutlined,
ExclamationCircleOutlined
} from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro/es';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import { messageLoading, formatNumber } from 'ele-admin-pro/es';
import UserEdit from './components/user-edit.vue';
import InvitationModal from './components/invitation-modal.vue';
import { toDateString } from 'ele-admin-pro';
import {
pageUsers,
removeUser,
updateUserPassword,
updateUser
} from '@/api/system/user';
import type { User, UserParam } from '@/api/system/user/model';
import { toTreeData, uuid } from 'ele-admin-pro';
import { listRoles } from '@/api/system/role';
import { listOrganizations } from '@/api/system/organization';
import { Organization } from '@/api/system/organization/model';
import { hasRole } from '@/utils/permission';
import { getPageTitle } from '@/utils/common';
import router from '@/router';
import SuperAdmin from './components/super-admin.vue';
// 加载状态
const loading = ref(true);
// 树形数据
const data = ref<Organization[]>([]);
// 树展开的key
const expandedRowKeys = ref<number[]>([]);
// 树选中的key
const selectedRowKeys = ref<number[]>([]);
// 表格选中数据
const selection = ref<User[]>([]);
// 当前编辑数据
const current = ref<User | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示用户详情
const showInfo = ref(false);
// 是否显示用户导入弹窗
const showImport = ref(false);
// 是否显示邀请注册弹窗
const showInvitation = ref(false);
const searchText = ref('');
// 当前用户ID
const currentUserId = ref<number>();
// 加载角色
const roles = ref<any[]>([]);
// 加载机构
listOrganizations()
.then((list) => {
loading.value = false;
const eks: number[] = [];
list.forEach((d) => {
d.key = d.organizationId;
d.value = d.organizationId;
d.title = d.organizationName;
if (typeof d.key === 'number') {
eks.push(d.key);
}
});
expandedRowKeys.value = eks;
data.value = toTreeData({
data: list,
idField: 'organizationId',
parentIdField: 'parentId'
});
if (list.length) {
if (typeof list[0].key === 'number') {
selectedRowKeys.value = [list[0].key];
}
// current.value = list[0];
} else {
selectedRowKeys.value = [];
// current.value = null;
}
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'userId',
width: 90,
showSorterTooltip: false
},
{
title: '真实姓名',
dataIndex: 'realName',
key: 'realName',
align: 'center',
showSorterTooltip: false
},
// {
// title: '所属部门',
// dataIndex: 'organizationName',
// key: 'organizationName',
// align: 'center'
// },
{
title: '角色',
dataIndex: 'roles',
key: 'roles',
align: 'center'
},
{
title: '注册时间',
dataIndex: 'createTime',
sorter: true,
align: 'center',
showSorterTooltip: false,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center'
}
]);
// 默认搜索条件
const defaultWhere = reactive({
username: '',
nickname: ''
});
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
where = {};
where.roleId = filters.roles;
where.keywords = searchText.value;
where.isAdmin = 1;
return pageUsers({ page, limit, ...where, ...orders });
};
/* 搜索 */
const reload = (where?: UserParam) => {
selection.value = [];
tableRef?.value?.reload({ where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: User) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 删除单个 */
const remove = (row: User) => {
const hide = messageLoading('请求中..', 0);
removeUser(row.userId)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 重置用户密码 */
const resetPsw = (row: User) => {
Modal.confirm({
title: '提示',
content: '确定要重置此用户的密码吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
const password = uuid(8);
updateUserPassword(row.userId, password)
.then((msg) => {
hide();
message.success(msg + ',新密码:' + password);
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 修改用户状态 */
const updateIsAdmin = (row: User) => {
row.isAdmin = !row.isAdmin;
updateUser(row)
.then((msg) => {
message.success(msg);
})
.catch((e) => {
message.error(e.message);
});
};
/* 自定义行属性 */
const customRow = (record: User) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
const query = async () => {
const info = await listRoles({});
if (info) {
roles.value = info;
}
};
/* 打开邀请注册弹窗 */
const openInvitation = () => {
// 获取当前用户ID
const userId = localStorage.getItem('UserId');
if (userId) {
currentUserId.value = Number(userId);
showInvitation.value = true;
} else {
message.error('获取用户信息失败');
}
};
watch(
() => router.currentRoute.value.query,
() => {
query();
},
{ immediate: true }
);
</script>
<script lang="ts">
export default {
name: 'SystemAdmin'
};
</script>
<style lang="less" scoped>
.sys-org-table {
:deep(.ant-table) {
.ant-table-thead > tr > th {
background: #fafafa;
font-weight: 600;
color: #262626;
border-bottom: 2px solid #f0f0f0;
}
.ant-table-tbody > tr > td {
padding: 12px 8px;
border-bottom: 1px solid #f5f5f5;
}
.ant-table-tbody > tr:hover > td {
background: #f8f9ff;
}
.ant-tag {
margin: 0;
border-radius: 4px;
font-size: 12px;
padding: 2px 8px;
}
}
}
.ele-text-primary {
color: #1890ff;
&:hover {
color: #40a9ff;
}
}
.ele-text-danger {
color: #ff4d4f;
&:hover {
color: #ff7875;
}
}
</style>