feat(system/client) :客户端管理扩展登录配置

This commit is contained in:
KAI
2025-11-25 08:14:19 +00:00
committed by Charles7c
parent e78f7c61a0
commit 7f982b8b53
5 changed files with 154 additions and 15 deletions

View File

@@ -295,12 +295,17 @@ export interface ClientResp {
activeTimeout: string activeTimeout: string
timeout: string timeout: string
status: string status: string
isConcurrent: boolean
maxLoginCount: number
replacedRange: string
overflowLogoutMode: string
createUser: string createUser: string
createTime: string createTime: string
updateUser: string updateUser: string
updateTime: string updateTime: string
createUserString: string createUserString: string
updateUserString: string updateUserString: string
disabled: boolean
} }
export interface ClientDetailResp { export interface ClientDetailResp {
id: string id: string
@@ -309,7 +314,11 @@ export interface ClientDetailResp {
authType: string authType: string
activeTimeout: string activeTimeout: string
timeout: string timeout: string
status: string status: number
isConcurrent: boolean
maxLoginCount: number
replacedRange: string
overflowLogoutMode: string
createUser: string createUser: string
createTime: string createTime: string
updateUser: string updateUser: string

View File

@@ -10,7 +10,7 @@
:style="appStore.menuDark ? appStore.themeCSSVar : undefined" :style="appStore.menuDark ? appStore.themeCSSVar : undefined"
> >
<Logo :collapsed="appStore.menuCollapse" /> <Logo :collapsed="appStore.menuCollapse" />
<Menu :menus="twoLevelMenus" :menu-style="{ width: '200px', flex: 1 }" /> <Menu :menus="twoLevelMenus" :menu-style="{ flex: 1 }" />
<WwAds class="ads" /> <WwAds class="ads" />
</section> </section>
@@ -83,7 +83,6 @@ const checkAndShowNotices = () => {
// 如果有token检查未读公告 // 如果有token检查未读公告
if (token) { if (token) {
setTimeout(() => { setTimeout(() => {
console.log(noticePopupRef.value)
noticePopupRef.value?.open() noticePopupRef.value?.open()
}, 1000) // 延迟1秒显示让页面先加载完成 }, 1000) // 延迟1秒显示让页面先加载完成
} }

View File

@@ -32,16 +32,32 @@ const visible = ref(false)
const isUpdate = computed(() => !!dataId.value) const isUpdate = computed(() => !!dataId.value)
const title = computed(() => (isUpdate.value ? '修改客户端' : '新增客户端')) const title = computed(() => (isUpdate.value ? '修改客户端' : '新增客户端'))
const formRef = ref<InstanceType<typeof GiForm>>() const formRef = ref<InstanceType<typeof GiForm>>()
const { client_type, auth_type_enum } = useDict('auth_type_enum', 'client_type') const { client_type, auth_type_enum, replaced_range_enum, logout_mode_enum } = useDict('auth_type_enum', 'client_type', 'replaced_range_enum', 'logout_mode_enum')
const [form, resetForm] = useResetReactive({ const [form, resetForm] = useResetReactive({
activeTimeout: 1800, activeTimeout: 1800,
timeout: 86400, timeout: 86400,
isConcurrent: 1, isConcurrent: true,
isShare: 1, maxLoginCount: -1,
replacedRange: 'ALL_DEVICE_TYPE',
overflowLogoutMode: 'KICKOUT',
status: 1, status: 1,
}) })
// 监听 isConcurrent 的变化,处理字段互斥逻辑
watch(
() => form.isConcurrent,
(newVal) => {
if (!newVal) {
form.maxLoginCount = -1
// replacedRange 只有在 isConcurrent=false 时才有意义
} else if (newVal) {
// 当 isConcurrent=true 时,清空 maxLoginCount
form.maxLoginCount = -1
}
},
)
const columns: ColumnItem[] = reactive([ const columns: ColumnItem[] = reactive([
{ {
label: '客户端类型', label: '客户端类型',
@@ -87,7 +103,7 @@ const columns: ColumnItem[] = reactive([
{ {
label: () => ( label: () => (
<a-tooltip content="-1 代表永不过期"> <a-tooltip content="-1 代表永不过期">
Token 有效期&nbsp; Token 有效期
<icon-question-circle /> <icon-question-circle />
</a-tooltip> </a-tooltip>
), ),
@@ -104,6 +120,75 @@ const columns: ColumnItem[] = reactive([
}, },
rules: [{ required: true, message: '请输入 Token 有效期' }], rules: [{ required: true, message: '请输入 Token 有效期' }],
}, },
{
label: '是否允许同一账号多地同时登录',
field: 'isConcurrent',
type: 'switch',
span: 12,
props: {
type: 'round',
checkedValue: true,
uncheckedValue: false,
checkedText: '允许',
uncheckedText: '不允许',
},
},
{
label: () => (
<a-tooltip content="-1 代表不限">
最大登录数量
<icon-question-circle />
</a-tooltip>
),
field: 'maxLoginCount',
type: 'input-number',
span: 12,
slots: {
append: () => (
<span style={{ width: '80px', textAlign: 'center' }}></span>
),
},
props: {
placeholder: '请输入最大登录数量',
min: -1,
},
disabled: () => {
return !form.isConcurrent
},
rules: [
{
validator: (value: number, callback: (errorMessage?: string) => void) => {
if (value === 0) {
callback('最大登录数量不能为0请输入-1或正整数')
}
callback()
},
},
],
},
{
label: '顶人下线的范围',
field: 'replacedRange',
type: 'select',
span: 12,
props: {
options: replaced_range_enum,
placeholder: '请选择顶人下线的范围',
},
disabled: () => {
return form.isConcurrent
},
},
{
label: '溢出人数的注销方式',
field: 'overflowLogoutMode',
type: 'select',
span: 12,
props: {
options: logout_mode_enum,
placeholder: '请选择溢出人数的注销方式',
},
},
{ {
label: '状态', label: '状态',
field: 'status', field: 'status',

View File

@@ -17,6 +17,19 @@
<a-tag v-if="dataDetail?.status === 1" color="green">启用</a-tag> <a-tag v-if="dataDetail?.status === 1" color="green">启用</a-tag>
<a-tag v-else color="red">禁用</a-tag> <a-tag v-else color="red">禁用</a-tag>
</a-descriptions-item> </a-descriptions-item>
<a-descriptions-item label="是否允许多地登录" :span="2">
<a-tag v-if="dataDetail?.isConcurrent" color="blue">允许</a-tag>
<a-tag v-else color="orange">不允许</a-tag>
</a-descriptions-item>
<a-descriptions-item label="最大登录数量">
{{ dataDetail?.maxLoginCount === -1 ? '不限' : dataDetail?.maxLoginCount }}
</a-descriptions-item>
<a-descriptions-item label="顶人下线范围">
<GiCellTag :value="dataDetail?.replacedRange" :dict="replaced_range_enum" />
</a-descriptions-item>
<a-descriptions-item label="溢出注销方式" :span="2">
<GiCellTag :value="dataDetail?.overflowLogoutMode" :dict="logout_mode_enum" />
</a-descriptions-item>
<a-descriptions-item label="创建人">{{ dataDetail?.createUserString }}</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?.createTime }}</a-descriptions-item>
<a-descriptions-item label="更新人">{{ dataDetail?.updateUserString }}</a-descriptions-item> <a-descriptions-item label="更新人">{{ dataDetail?.updateUserString }}</a-descriptions-item>
@@ -29,11 +42,14 @@
import { useWindowSize } from '@vueuse/core' import { useWindowSize } from '@vueuse/core'
import { type ClientDetailResp, getClient as getDetail } from '@/apis/system/client' import { type ClientDetailResp, getClient as getDetail } from '@/apis/system/client'
import { useDict } from '@/hooks/app' import { useDict } from '@/hooks/app'
import GiCellTag from '@/components/GiCell/GiCellTag.vue'
const { const {
client_type, client_type,
auth_type_enum, auth_type_enum,
} = useDict('client_type', 'auth_type_enum') replaced_range_enum,
logout_mode_enum,
} = useDict('client_type', 'auth_type_enum', 'replaced_range_enum', 'logout_mode_enum')
const { width } = useWindowSize() const { width } = useWindowSize()

View File

@@ -60,7 +60,7 @@
<script setup lang="tsx"> <script setup lang="tsx">
import type { LabelValue } from '@arco-design/web-vue/es/tree-select/interface' import type { LabelValue } from '@arco-design/web-vue/es/tree-select/interface'
import type { TableInstance } from '@arco-design/web-vue' import { type TableInstance, Tag } from '@arco-design/web-vue'
import AddModal from './AddModal.vue' import AddModal from './AddModal.vue'
import DetailDrawer from './DetailDrawer.vue' import DetailDrawer from './DetailDrawer.vue'
import { type ClientQuery, type ClientResp, deleteClient, listClient } from '@/apis/system/client' import { type ClientQuery, type ClientResp, deleteClient, listClient } from '@/apis/system/client'
@@ -79,7 +79,9 @@ defineOptions({ name: 'SystemClient' })
const { const {
client_type, client_type,
auth_type_enum, auth_type_enum,
} = useDict('client_type', 'auth_type_enum') replaced_range_enum,
logout_mode_enum,
} = useDict('client_type', 'auth_type_enum', 'replaced_range_enum', 'logout_mode_enum')
const queryForm = reactive<ClientQuery>({ const queryForm = reactive<ClientQuery>({
clientType: '', clientType: '',
@@ -89,7 +91,7 @@ const queryForm = reactive<ClientQuery>({
}) })
const formatAuthType = (data: string[]) => { const formatAuthType = (data: string[]) => {
return data.map((item: string) => { return data.map((item: string) => {
return auth_type_enum.value.find((d: LabelValue) => d.value === item).label return auth_type_enum.value?.find((d: LabelValue) => d.value === item)?.label
}) })
} }
@@ -155,15 +157,43 @@ const columns: TableInstance['columns'] = [
return <GiCellStatus status={record.status} /> return <GiCellStatus status={record.status} />
}, },
}, },
{
title: '是否允许多地登录',
dataIndex: 'isConcurrent',
align: 'center',
render: ({ record }) => {
return <Tag>{record.isConcurrent ? '允许' : '不允许'}</Tag>
},
},
{
title: '最大登录数量',
dataIndex: 'maxLoginCount',
align: 'center',
render: ({ record }) => {
return record.maxLoginCount === -1 ? '不限' : record.maxLoginCount
},
},
{
title: '顶人下线范围',
dataIndex: 'replacedRange',
align: 'center',
render: ({ record }) => {
return <GiCellTag value={record.replacedRange} dict={replaced_range_enum.value} />
},
},
{
title: '溢出注销方式',
dataIndex: 'overflowLogoutMode',
align: 'center',
render: ({ record }) => {
return <GiCellTag value={record.overflowLogoutMode} dict={logout_mode_enum.value} />
},
},
{ title: '创建人', dataIndex: 'createUserString', width: 140, ellipsis: true, tooltip: true, show: false }, { 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: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
slotName: 'action', slotName: 'action',
width: 160,
align: 'center', align: 'center',
fixed: !isMobile() ? 'right' : undefined, fixed: !isMobile() ? 'right' : undefined,
show: has.hasPermOr(['system:client:get', 'system:client:update', 'system:client:delete']), show: has.hasPermOr(['system:client:get', 'system:client:update', 'system:client:delete']),