first commit

This commit is contained in:
zc
2026-02-26 17:31:18 +08:00
commit f1b87df6ca
803 changed files with 297148 additions and 0 deletions

View File

@@ -0,0 +1,219 @@
<template>
<a-drawer
v-model:visible="visible"
:title="title"
:mask-closable="false"
:esc-to-close="false"
:width="width >= 500 ? 500 : '100%'"
@before-ok="save"
@close="reset"
>
<GiForm ref="formRef" v-model="form" :columns="columns" />
</a-drawer>
</template>
<script setup lang="ts">
import { Message, type TreeNodeData } from '@arco-design/web-vue'
import { useWindowSize } from '@vueuse/core'
import { addUser, getUser, updateUser } from '@/apis/system/user'
import { type ColumnItem, GiForm } from '@/components/GiForm'
import type { Gender, Status } from '@/types/global'
import { GenderList } from '@/constant/common'
import { useResetReactive } from '@/hooks'
import { useDept, useRole } from '@/hooks/app'
import { encryptByRsa } from '@/utils/encrypt'
const emit = defineEmits<{
(e: 'save-success'): void
}>()
const { width } = useWindowSize()
const dataId = ref('')
const visible = ref(false)
const isUpdate = computed(() => !!dataId.value)
const title = computed(() => (isUpdate.value ? '修改用户' : '新增用户'))
const formRef = ref<InstanceType<typeof GiForm>>()
const { roleList, getRoleList } = useRole()
const { deptList, getDeptList } = useDept()
const [form, resetForm] = useResetReactive({
gender: 1 as Gender,
status: 1 as Status,
})
const columns: ColumnItem[] = reactive([
{
label: '用户名',
field: 'username',
type: 'input',
span: 24,
required: true,
props: {
maxLength: 64,
},
},
{
label: '昵称',
field: 'nickname',
type: 'input',
span: 24,
required: true,
props: {
maxLength: 30,
},
},
{
label: '密码',
field: 'password',
type: 'input-password',
span: 24,
required: true,
props: {
maxLength: 32,
showWordLimit: true,
},
hide: () => isUpdate.value,
},
{
label: '手机号码',
field: 'phone',
type: 'input',
span: 24,
props: {
maxLength: 11,
},
},
{
label: '邮箱',
field: 'email',
type: 'input',
span: 24,
props: {
maxLength: 255,
},
},
{
label: '性别',
field: 'gender',
type: 'radio-group',
span: 24,
props: {
options: GenderList,
},
},
{
label: '所属部门',
field: 'deptId',
type: 'tree-select',
span: 24,
required: true,
props: {
data: deptList,
allowClear: true,
allowSearch: true,
fallbackOption: false,
filterTreeNode(searchKey: string, nodeData: TreeNodeData) {
if (nodeData.title) {
return nodeData.title.toLowerCase().includes(searchKey.toLowerCase())
}
return false
},
},
},
{
label: '角色',
field: 'roleIds',
type: 'select',
span: 24,
required: true,
props: {
options: roleList,
multiple: true,
allowClear: true,
allowSearch: true,
},
},
{
label: '描述',
field: 'description',
type: 'textarea',
span: 24,
},
{
label: '状态',
field: 'status',
type: 'switch',
span: 24,
props: {
type: 'round',
checkedValue: 1,
uncheckedValue: 2,
checkedText: '启用',
uncheckedText: '禁用',
},
},
])
// 重置
const reset = () => {
formRef.value?.formRef?.resetFields()
resetForm()
}
// 保存
const save = async () => {
const rawPassword = form.password
try {
const isInvalid = await formRef.value?.formRef?.validate()
if (isInvalid) return false
if (isUpdate.value) {
await updateUser(form, dataId.value)
Message.success('修改成功')
} else {
if (rawPassword) {
form.password = encryptByRsa(rawPassword) || ''
}
await addUser(form)
Message.success('新增成功')
}
emit('save-success')
return true
} catch (error) {
form.password = rawPassword
return false
}
}
// 新增
const onAdd = async () => {
reset()
if (!deptList.value.length) {
await getDeptList()
}
if (!roleList.value.length) {
await getRoleList()
}
dataId.value = ''
visible.value = true
}
// 修改
const onUpdate = async (id: string) => {
reset()
dataId.value = id
if (!deptList.value.length) {
await getDeptList()
}
if (!roleList.value.length) {
await getRoleList()
}
const { data } = await getUser(id)
Object.assign(form, data)
visible.value = true
}
defineExpose({ onAdd, onUpdate })
</script>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,57 @@
<template>
<a-drawer v-model:visible="visible" title="用户详情" :width="width >= 500 ? 500 : '100%'" :footer="false">
<a-descriptions :column="2" size="large" class="general-description">
<a-descriptions-item label="ID" :span="2">
<a-typography-paragraph copyable>{{ dataDetail?.id }}</a-typography-paragraph>
</a-descriptions-item>
<a-descriptions-item label="用户名">{{ dataDetail?.username }}</a-descriptions-item>
<a-descriptions-item label="昵称">{{ dataDetail?.nickname }}</a-descriptions-item>
<a-descriptions-item label="性别">
<span v-if="dataDetail?.gender === 1"></span>
<span v-else-if="dataDetail?.gender === 2"></span>
<span v-else>未知</span>
</a-descriptions-item>
<a-descriptions-item label="状态">
<a-tag v-if="dataDetail?.status === 1" color="green">启用</a-tag>
<a-tag v-else color="red">禁用</a-tag>
</a-descriptions-item>
<a-descriptions-item label="手机号">{{ dataDetail?.phone || '暂无' }}</a-descriptions-item>
<a-descriptions-item label="邮箱">{{ dataDetail?.email || '暂无' }}</a-descriptions-item>
<a-descriptions-item label="所属部门">{{ dataDetail?.deptName }}</a-descriptions-item>
<a-descriptions-item label="角色"><GiCellTags :data="dataDetail?.roleNames" /></a-descriptions-item>
<a-descriptions-item label="创建人">{{ dataDetail?.createUserString }}</a-descriptions-item>
<a-descriptions-item label="创建时间">{{ dataDetail?.createTime }}</a-descriptions-item>
<a-descriptions-item label="修改人">{{ dataDetail?.updateUserString }}</a-descriptions-item>
<a-descriptions-item label="修改时间">{{ dataDetail?.updateTime }}</a-descriptions-item>
<a-descriptions-item label="描述" :span="2">{{ dataDetail?.description }}</a-descriptions-item>
</a-descriptions>
</a-drawer>
</template>
<script setup lang="ts">
import { useWindowSize } from '@vueuse/core'
import { type UserDetailResp, getUser as getDetail } from '@/apis/system/user'
const { width } = useWindowSize()
const dataId = ref('')
const dataDetail = ref<UserDetailResp>()
const visible = ref(false)
// 查询详情
const getDataDetail = async () => {
const { data } = await getDetail(dataId.value)
dataDetail.value = data
}
// 打开
const onOpen = async (id: string) => {
dataId.value = id
await getDataDetail()
visible.value = true
}
defineExpose({ onOpen })
</script>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,208 @@
<template>
<a-drawer
v-model:visible="visible"
title="导入用户"
:mask-closable="false"
:esc-to-close="false"
:width="width >= 600 ? 600 : '100%'"
ok-text="确认导入"
cancel-text="取消导入"
@before-ok="save"
@close="reset"
>
<a-form ref="formRef" :model="form" size="large" auto-label-width>
<a-alert v-if="!form.disabled" style="margin-bottom: 15px">
请按照模板要求填写数据填写完毕后请先上传并进行解析
<template #action>
<a-link @click="downloadTemplate">
<template #icon><GiSvgIcon name="file-excel" :size="16" /></template>
<template #default>下载模板</template>
</a-link>
</template>
</a-alert>
<fieldset>
<legend>1.解析数据</legend>
<div class="file-box">
<a-upload
draggable
:custom-request="handleUpload"
:limit="1"
:show-retry-butto="false"
:show-cancel-button="false" tip="仅支持xls、xlsx格式"
:file-list="uploadFile"
accept=".xls, .xlsx, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
/>
</div>
<div v-if="dataResult.importKey">
<div class="file-box">
<a-space size="large">
<a-statistic title="总计行数" :value="dataResult.totalRows" />
<a-statistic title="正常行数" :value="dataResult.validRows" />
</a-space>
</div>
<div class="file-box">
<a-space size="large">
<a-statistic title="已存在用户" :value="dataResult.duplicateUserRows" />
<a-statistic title="已存在邮箱" :value="dataResult.duplicateEmailRows" />
<a-statistic title="已存在手机" :value="dataResult.duplicatePhoneRows" />
</a-space>
</div>
</div>
</fieldset>
<fieldset>
<legend>2.导入策略</legend>
<a-form-item label="用户已存在" field="duplicateUser">
<a-radio-group v-model="form.duplicateUser" type="button">
<a-radio :value="1">跳过该行</a-radio>
<a-radio :value="3">停止导入</a-radio>
<a-radio :value="2">修改数据</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="邮箱已存在" field="duplicateEmail">
<a-radio-group v-model="form.duplicateEmail" type="button">
<a-radio :value="1">跳过该行</a-radio>
<a-radio :value="3">停止导入</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="手机已存在" field="duplicatePhone">
<a-radio-group v-model="form.duplicatePhone" type="button">
<a-radio :value="1">跳过该行</a-radio>
<a-radio :value="3">停止导入</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="默认状态" field="defaultStatus">
<a-switch
v-model="form.defaultStatus"
:checked-value="1"
:unchecked-value="2"
checked-text="启用"
unchecked-text="禁用"
type="round"
/>
</a-form-item>
</fieldset>
</a-form>
</a-drawer>
</template>
<script setup lang="ts">
import { type FormInstance, Message, type RequestOption } from '@arco-design/web-vue'
import { useWindowSize } from '@vueuse/core'
import {
type UserImportResp,
downloadUserImportTemplate,
importUser,
parseImportUser,
} from '@/apis/system/user'
import { useDownload, useResetReactive } from '@/hooks'
const emit = defineEmits<{
(e: 'save-success'): void
}>()
const { width } = useWindowSize()
const visible = ref(false)
const formRef = ref<FormInstance>()
const uploadFile = ref([])
const [form, resetForm] = useResetReactive({
errorPolicy: 1,
duplicateUser: 1,
duplicateEmail: 1,
duplicatePhone: 1,
defaultStatus: 1,
})
const dataResult = ref<UserImportResp>({
importKey: '',
totalRows: 0,
validRows: 0,
duplicateUserRows: 0,
duplicateEmailRows: 0,
duplicatePhoneRows: 0,
})
// 重置
const reset = () => {
formRef.value?.resetFields()
dataResult.value.importKey = ''
uploadFile.value = []
resetForm()
}
// 下载模板
const downloadTemplate = () => {
useDownload(() => downloadUserImportTemplate())
}
// 上传解析导入数据
const handleUpload = (options: RequestOption) => {
const controller = new AbortController();
(async function requestWrap() {
const { onProgress, onError, onSuccess, fileItem, name = 'file' } = options
onProgress(20)
const formData = new FormData()
formData.append(name as string, fileItem.file as Blob)
try {
const res = await parseImportUser(formData)
dataResult.value = res.data
Message.success('上传解析成功')
onSuccess(res)
} catch (error) {
onError(error)
}
})()
return {
abort() {
controller.abort()
},
}
}
// 执行导入
const save = async () => {
try {
if (!dataResult.value.importKey) {
Message.warning('请先上传文件,解析导入数据')
return false
}
form.importKey = dataResult.value.importKey
const res = await importUser(form)
Message.success(`导入成功! 新增${res.data.insertRows}, 修改${res.data.updateRows}`)
emit('save-success')
return true
} catch (error) {
return false
}
}
// 打开
const onOpen = () => {
reset()
visible.value = true
}
defineExpose({ onOpen })
</script>
<style scoped lang="scss">
fieldset {
padding: 15px 15px 0 15px;
margin-bottom: 15px;
border: 1px solid var(--color-neutral-3);
border-radius: 3px;
}
fieldset legend {
color: rgb(var(--gray-10));
padding: 2px 5px 2px 5px;
border: 1px solid var(--color-neutral-3);
border-radius: 3px;
}
.file-box {
margin-bottom: 20px;
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<a-modal
v-model:visible="visible"
title="重置密码"
:mask-closable="false"
:esc-to-close="false"
:width="width >= 500 ? 500 : '100%'"
draggable
@before-ok="save"
@close="reset"
>
<GiForm ref="formRef" v-model="form" :columns="columns" />
</a-modal>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue'
import { useWindowSize } from '@vueuse/core'
import { resetUserPwd } from '@/apis/system'
import { type ColumnItem, GiForm } from '@/components/GiForm'
import { useResetReactive } from '@/hooks'
import { encryptByRsa } from '@/utils/encrypt'
const emit = defineEmits<{
(e: 'save-success'): void
}>()
const { width } = useWindowSize()
const dataId = ref('')
const visible = ref(false)
const formRef = ref<InstanceType<typeof GiForm>>()
const [form, resetForm] = useResetReactive({})
const columns: ColumnItem[] = reactive([
{ label: '密码', field: 'newPassword', type: 'input-password', span: 24, required: true },
])
// 重置
const reset = () => {
formRef.value?.formRef?.resetFields()
resetForm()
}
// 保存
const save = async () => {
try {
const isInvalid = await formRef.value?.formRef?.validate()
if (isInvalid) return false
await resetUserPwd({ newPassword: encryptByRsa(form.newPassword) || '' }, dataId.value)
Message.success('重置成功')
emit('save-success')
return true
} catch (error) {
return false
}
}
// 打开
const onOpen = (id: string) => {
reset()
dataId.value = id
visible.value = true
}
defineExpose({ onOpen })
</script>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,87 @@
<template>
<a-modal
v-model:visible="visible"
title="分配角色"
:mask-closable="false"
:esc-to-close="false"
:width="width >= 500 ? 500 : '100%'"
draggable
@before-ok="save"
@close="reset"
>
<GiForm ref="formRef" v-model="form" :columns="columns" />
</a-modal>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue'
import { useWindowSize } from '@vueuse/core'
import { getUser, updateUserRole } from '@/apis/system'
import { type ColumnItem, GiForm } from '@/components/GiForm'
import { useResetReactive } from '@/hooks'
import { useRole } from '@/hooks/app'
const emit = defineEmits<{
(e: 'save-success'): void
}>()
const { width } = useWindowSize()
const dataId = ref('')
const visible = ref(false)
const formRef = ref<InstanceType<typeof GiForm>>()
const { roleList, getRoleList } = useRole()
const [form, resetForm] = useResetReactive({})
const columns: ColumnItem[] = reactive([
{
label: '角色',
field: 'roleIds',
type: 'select',
span: 24,
required: true,
props: {
options: roleList,
multiple: true,
allowClear: true,
allowSearch: { retainInputValue: true },
},
},
])
// 重置
const reset = () => {
formRef.value?.formRef?.resetFields()
resetForm()
}
// 保存
const save = async () => {
try {
const isInvalid = await formRef.value?.formRef?.validate()
if (isInvalid) return false
await updateUserRole({ roleIds: form.roleIds }, dataId.value)
Message.success('分配成功')
emit('save-success')
return true
} catch (error) {
return false
}
}
// 初始化
const onOpen = async (id: string) => {
reset()
dataId.value = id
if (!roleList.value.length) {
await getRoleList()
}
const { data } = await getUser(id)
Object.assign(form, data)
visible.value = true
}
defineExpose({ onOpen })
</script>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,164 @@
<template>
<div class="container">
<div class="search">
<a-input v-model="searchKey" placeholder="搜索部门名称" allow-clear>
<template #prefix><icon-search /></template>
</a-input>
</div>
<div class="tree-wrapper">
<div class="tree">
<a-tree
ref="treeRef"
:data="treeData"
show-line
block-node
default-expand-all
:selected-keys="selectedKeys"
@select="select"
>
<template #switcher-icon="node, { isLeaf }">
<IconCaretDown v-if="!isLeaf" />
<IconIdcard v-else />
</template>
<template #title="node">
<a-typography-paragraph
:ellipsis="{
rows: 1,
showTooltip: true,
css: true,
}"
>
{{ node?.title }}
</a-typography-paragraph>
</template>
</a-tree>
</div>
</div>
</div>
</template>
<script setup lang="tsx">
import type { TreeInstance, TreeNodeData } from '@arco-design/web-vue'
import { ref } from 'vue'
import { useDept } from '@/hooks/app'
const emit = defineEmits<{
(e: 'node-click', keys: Array<any>): void
}>()
// 选中节点
const selectedKeys = ref()
const select = (keys: Array<any>) => {
if (selectedKeys.value && selectedKeys.value[0] === keys[0]) {
return
}
selectedKeys.value = keys
emit('node-click', keys)
}
const treeRef = ref<TreeInstance>()
// 查询树列表
const { deptList, getDeptList } = useDept({
onSuccess: () => {
nextTick(() => {
treeRef.value?.expandAll(true)
select([deptList.value[0]?.key])
})
},
})
// 过滤树
const searchKey = ref('')
const search = (keyword: string) => {
const loop = (data: TreeNodeData[]) => {
const result = [] as TreeNodeData[]
data.forEach((item: TreeNodeData) => {
if (item.title?.toLowerCase().includes(keyword)) {
result.push({ ...item })
} else if (item.children) {
const filterData = loop(item.children)
if (filterData.length) {
result.push({
...item,
children: filterData,
})
}
}
})
return result
}
return loop(deptList.value)
}
const treeData = computed(() => {
if (!searchKey.value) return deptList.value
return search(searchKey.value.toLowerCase())
})
onMounted(() => {
getDeptList()
})
</script>
<style scoped lang="scss">
:deep(.arco-tree-node-title-text) {
width: 100%;
white-space: nowrap;
}
:deep(.arco-tree-node) {
line-height: normal;
border-radius: var(--border-radius-medium);
&:hover {
background-color: var(--color-secondary-hover);
}
.arco-tree-node-title {
&:hover {
background-color: transparent;
}
}
}
:deep(.arco-tree-node-selected) {
font-weight: bold;
background-color: rgba(var(--primary-6), 0.1);
&:hover {
background-color: rgba(var(--primary-6), 0.1);
}
.arco-typography {
color: rgb(var(--primary-6));
}
}
.container {
flex: 1;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
box-sizing: border-box;
height: 100%;
.search {
margin-bottom: 16px;
}
.tree-wrapper {
flex: 1;
overflow: hidden;
background-color: var(--color-bg-1);
position: relative;
height: 100%;
/* margin-bottom:10px;*/
.tree {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: auto
}
}
}
</style>

View File

@@ -0,0 +1,271 @@
<template>
<div class="gi_page">
<SplitPanel size="20%">
<template #left>
<DeptTree @node-click="handleSelectDept" />
</template>
<template #main>
<GiTable
row-key="id"
:data="dataList"
:columns="columns"
:loading="loading"
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }"
:pagination="pagination"
:disabled-tools="['size']"
:disabled-column-keys="['nickname']"
@refresh="search"
>
<template #top>
<GiForm v-model="queryForm" search :columns="queryFormColumns" size="medium" @search="search" @reset="reset"></GiForm>
</template>
<template #toolbar-left>
<a-button v-permission="['system:user:add']" type="primary" @click="onAdd">
<template #icon><icon-plus /></template>
<template #default>新增</template>
</a-button>
<a-button v-permission="['system:user:import']" @click="onImport">
<template #icon><icon-upload /></template>
<template #default>导入</template>
</a-button>
</template>
<template #toolbar-right>
<a-button v-permission="['system:user:export']" @click="onExport">
<template #icon><icon-download /></template>
<template #default>导出</template>
</a-button>
</template>
<template #nickname="{ record }">
<GiCellAvatar :avatar="record.avatar" :name="record.nickname" />
</template>
<template #gender="{ record }">
<GiCellGender :gender="record.gender" />
</template>
<template #roleNames="{ record }">
<GiCellTags :data="record.roleNames" />
</template>
<template #status="{ record }">
<GiCellStatus :status="record.status" />
</template>
<template #isSystem="{ record }">
<a-tag v-if="record.isSystem" color="red" size="small"></a-tag>
<a-tag v-else color="arcoblue" size="small"></a-tag>
</template>
<template #action="{ record }">
<a-space>
<a-link v-permission="['system:user:detail']" title="详情" @click="onDetail(record)">详情</a-link>
<a-link v-permission="['system:user:update']" title="修改" @click="onUpdate(record)">修改</a-link>
<a-link
v-permission="['system:user:delete']"
status="danger"
:disabled="record.isSystem"
:title="record.isSystem ? '系统内置数据不能删除' : '删除'"
@click="onDelete(record)"
>
删除
</a-link>
<a-dropdown>
<a-button v-if="has.hasPermOr(['system:user:resetPwd', 'system:user:updateRole'])" type="text" size="mini" title="更多">
<template #icon>
<icon-more :size="16" />
</template>
</a-button>
<template #content>
<a-doption v-permission="['system:user:resetPwd']" title="重置密码" @click="onResetPwd(record)">重置密码</a-doption>
<a-doption v-permission="['system:user:updateRole']" title="分配角色" @click="onUpdateRole(record)">分配角色</a-doption>
</template>
</a-dropdown>
</a-space>
</template>
</GiTable>
</template>
</SplitPanel>
<UserAddDrawer ref="UserAddDrawerRef" @save-success="search" />
<UserImportDrawer ref="UserImportDrawerRef" @save-success="search" />
<UserDetailDrawer ref="UserDetailDrawerRef" />
<UserResetPwdModal ref="UserResetPwdModalRef" />
<UserUpdateRoleModal ref="UserUpdateRoleModalRef" @save-success="search" />
</div>
</template>
<script setup lang="ts">
import DeptTree from './dept/index.vue'
import UserAddDrawer from './UserAddDrawer.vue'
import UserImportDrawer from './UserImportDrawer.vue'
import UserDetailDrawer from './UserDetailDrawer.vue'
import UserResetPwdModal from './UserResetPwdModal.vue'
import UserUpdateRoleModal from './UserUpdateRoleModal.vue'
import { type UserResp, deleteUser, exportUser, listUser } from '@/apis/system/user'
import type { TableInstanceColumns } from '@/components/GiTable/type'
import { DisEnableStatusList } from '@/constant/common'
import { useDownload, useResetReactive, useTable } from '@/hooks'
import { isMobile } from '@/utils'
import has from '@/utils/has'
import type { ColumnItem } from '@/components/GiForm'
defineOptions({ name: 'SystemUser' })
const [queryForm, resetForm] = useResetReactive({
sort: ['t1.id,desc'],
})
const queryFormColumns: ColumnItem[] = reactive([
{
type: 'input',
field: 'description',
span: { xs: 24, sm: 8, xxl: 8 },
formItemProps: {
hideLabel: true,
},
props: {
placeholder: '搜索用户名/昵称/描述',
showWordLimit: false,
},
},
{
type: 'select',
field: 'status',
span: { xs: 24, sm: 6, xxl: 8 },
formItemProps: {
hideLabel: true,
},
props: {
options: DisEnableStatusList,
placeholder: '请选择状态',
},
},
{
type: 'range-picker',
field: 'createTime',
span: { xs: 24, sm: 10, xxl: 8 },
formItemProps: {
hideLabel: true,
},
},
])
const {
tableData: dataList,
loading,
pagination,
search,
handleDelete,
} = useTable((page) => listUser({ ...queryForm, ...page }), { immediate: false })
const columns: TableInstanceColumns[] = [
{
title: '序号',
width: 66,
align: 'center',
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
fixed: !isMobile() ? 'left' : undefined,
},
{
title: '昵称',
dataIndex: 'nickname',
slotName: 'nickname',
minWidth: 140,
ellipsis: true,
tooltip: true,
fixed: !isMobile() ? 'left' : undefined,
},
{ title: '用户名', dataIndex: 'username', slotName: 'username', minWidth: 140, ellipsis: true, tooltip: true },
{ title: '状态', dataIndex: 'status', slotName: 'status', align: 'center' },
{ title: '性别', dataIndex: 'gender', slotName: 'gender', align: 'center' },
{ title: '所属部门', dataIndex: 'deptName', minWidth: 180, ellipsis: true, tooltip: true },
{ title: '角色', dataIndex: 'roleNames', slotName: 'roleNames', minWidth: 165 },
{ title: '手机号', dataIndex: 'phone', minWidth: 170, ellipsis: true, tooltip: true },
{ title: '邮箱', dataIndex: 'email', minWidth: 170, ellipsis: true, tooltip: true },
{ title: '系统内置', dataIndex: 'isSystem', slotName: 'isSystem', width: 100, align: 'center', show: false },
{ title: '描述', dataIndex: 'description', minWidth: 130, ellipsis: true, tooltip: true },
{ title: '创建人', dataIndex: 'createUserString', width: 140, ellipsis: true, tooltip: true, show: false },
{ title: '创建时间', dataIndex: 'createTime', width: 180 },
{ title: '修改人', dataIndex: 'updateUserString', width: 140, ellipsis: true, tooltip: true, show: false },
{ title: '修改时间', dataIndex: 'updateTime', width: 180, show: false },
{
title: '操作',
dataIndex: 'action',
slotName: 'action',
width: 190,
align: 'center',
fixed: !isMobile() ? 'right' : undefined,
show: has.hasPermOr([
'system:user:detail',
'system:user:update',
'system:user:delete',
'system:user:resetPwd',
'system:user:updateRole',
]),
},
]
// 重置
const reset = () => {
resetForm()
search()
}
// 删除
const onDelete = (record: UserResp) => {
return handleDelete(() => deleteUser(record.id), {
content: `是否确定删除用户「${record.nickname}(${record.username})」?`,
showModal: true,
})
}
// 导出
const onExport = () => {
useDownload(() => exportUser(queryForm))
}
// 根据选中部门查询
const handleSelectDept = (keys: Array<any>) => {
queryForm.deptId = keys.length === 1 ? keys[0] : undefined
search()
}
const UserImportDrawerRef = ref<InstanceType<typeof UserImportDrawer>>()
// 导入
const onImport = () => {
UserImportDrawerRef.value?.onOpen()
}
const UserAddDrawerRef = ref<InstanceType<typeof UserAddDrawer>>()
// 新增
const onAdd = () => {
UserAddDrawerRef.value?.onAdd()
}
// 修改
const onUpdate = (record: UserResp) => {
UserAddDrawerRef.value?.onUpdate(record.id)
}
const UserDetailDrawerRef = ref<InstanceType<typeof UserDetailDrawer>>()
// 详情
const onDetail = (record: UserResp) => {
UserDetailDrawerRef.value?.onOpen(record.id)
}
const UserResetPwdModalRef = ref<InstanceType<typeof UserResetPwdModal>>()
// 重置密码
const onResetPwd = (record: UserResp) => {
UserResetPwdModalRef.value?.onOpen(record.id)
}
const UserUpdateRoleModalRef = ref<InstanceType<typeof UserUpdateRoleModal>>()
// 分配角色
const onUpdateRole = (record: UserResp) => {
UserUpdateRoleModalRef.value?.onOpen(record.id)
}
</script>
<style scoped lang="scss">
.page_header {
flex: 0 0 auto;
}
.page_content {
flex: 1;
overflow: auto;
}
</style>