!148 优化组件

Merge pull request !148 from xingyu/dev
pull/151/head
xingyu 2025-06-18 06:51:28 +00:00 committed by Gitee
commit 84ae36aca9
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
21 changed files with 286 additions and 434 deletions

View File

@ -8,6 +8,27 @@ body:
value: |
感谢对项目的支持与关注。在提出问题之前,请确保你已查看相关开发或使用文档:
- https://doc.iocoder.cn/
- type: dropdown
id: version
attributes:
label: 分支
description: 你当前正在使用我们软件的哪个分支?
options:
- master (默认)
- dev (开发分支)
validations:
required: true
- type: dropdown
id: version
attributes:
label: 版本
description: 你当前正在使用我们软件的哪个版本?
options:
- antd-design-vue
- element-plus
- naiveui
validations:
required: true
- type: checkboxes
attributes:
label: 这个问题是否已经存在?
@ -42,13 +63,3 @@ body:
description: 如果可以的话,上传任何关于 bug 的截图。
value: |
[在这里上传图片]
- type: dropdown
id: version
attributes:
label: 版本
description: 你当前正在使用我们软件的哪个版本/分支?
options:
- master (默认)
- dev (开发分支)
validations:
required: true

View File

@ -1,11 +1,14 @@
<script lang="ts" setup>
import type { NotificationItem } from '@vben/layouts';
import type { SystemTenantApi } from '#/api/system/tenant';
import { computed, onMounted, ref, watch } from 'vue';
import { useAccess } from '@vben/access';
import { AuthenticationLoginExpiredModal, useVbenModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
import { useWatermark } from '@vben/hooks';
import { isTenantEnable, useTabs, useWatermark } from '@vben/hooks';
import {
AntdProfileOutlined,
BookOpenText,
@ -14,32 +17,36 @@ import {
} from '@vben/icons';
import {
BasicLayout,
Help,
LockScreen,
Notification,
TenantDropdown,
UserDropdown,
} from '@vben/layouts';
import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { formatDateTime, openWindow } from '@vben/utils';
import { message } from 'ant-design-vue';
import {
getUnreadNotifyMessageCount,
getUnreadNotifyMessageList,
updateAllNotifyMessageRead,
updateNotifyMessageRead,
} from '#/api/system/notify/message';
import { getSimpleTenantList } from '#/api/system/tenant';
import { $t } from '#/locales';
import { router } from '#/router';
import { useAuthStore } from '#/store';
import LoginForm from '#/views/_core/authentication/login.vue';
import Help from './components/help.vue';
import TenantDropdown from './components/tenant-dropdown.vue';
const userStore = useUserStore();
const authStore = useAuthStore();
const accessStore = useAccessStore();
const { hasAccessByCodes } = useAccess();
const { destroyWatermark, updateWatermark } = useWatermark();
const { closeOtherTabs, refreshTab } = useTabs();
const notifications = ref<NotificationItem[]>([]);
const unreadCount = ref(0);
@ -148,10 +155,41 @@ function handleNotificationOpen(open: boolean) {
handleNotificationGetUnreadCount();
}
//
const tenants = ref<SystemTenantApi.Tenant[]>([]);
const tenantEnable = computed(
() => hasAccessByCodes(['system:tenant:visit']) && isTenantEnable(),
);
/** 获取租户列表 */
async function handleGetTenantList() {
if (tenantEnable.value) {
tenants.value = await getSimpleTenantList();
}
}
/** 处理租户切换 */
async function handleTenantChange(tenant: SystemTenantApi.Tenant) {
if (!tenant || !tenant.id) {
message.error('切换租户失败');
return;
}
// 访 ID
accessStore.setVisitTenantId(tenant.id as number);
//
await closeOtherTabs();
//
await refreshTab();
//
message.success(`切换当前租户为: ${tenant.name}`);
}
// ========== ==========
onMounted(() => {
//
handleNotificationGetUnreadCount();
//
handleGetTenantList();
//
setInterval(
() => {
@ -204,7 +242,14 @@ watch(
/>
</template>
<template #header-right-1>
<TenantDropdown class="w-30 mr-2" />
<div v-if="tenantEnable">
<TenantDropdown
class="mr-2"
:tenant-list="tenants"
:visit-tenant-id="accessStore.visitTenantId"
@success="handleTenantChange"
/>
</div>
</template>
<template #extra>
<AuthenticationLoginExpiredModal

View File

@ -1,63 +0,0 @@
<script lang="ts" setup>
import type { SelectValue } from 'ant-design-vue/es/select';
import type { SystemTenantApi } from '#/api/system/tenant';
import { onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { isTenantEnable, useTabs } from '@vben/hooks';
import { useAccessStore } from '@vben/stores';
import { message, Select } from 'ant-design-vue';
import { getSimpleTenantList } from '#/api/system/tenant';
import { $t } from '#/locales';
const { closeOtherTabs, refreshTab } = useTabs();
const { hasAccessByCodes } = useAccess();
const accessStore = useAccessStore();
const tenantEnable = isTenantEnable();
// 访 ID
const value = ref<number | undefined>(accessStore.visitTenantId ?? undefined);
//
const tenants = ref<SystemTenantApi.Tenant[]>([]);
async function handleChange(id: SelectValue) {
// 访 ID
accessStore.setVisitTenantId(id as number);
//
await closeOtherTabs();
//
await refreshTab();
//
const tenant = tenants.value.find((item) => item.id === id);
if (tenant) {
message.success(`切换当前租户为: ${tenant.name}`);
}
}
onMounted(async () => {
if (!tenantEnable) {
return;
}
tenants.value = await getSimpleTenantList();
});
</script>
<template>
<div v-if="tenantEnable && hasAccessByCodes(['system:tenant:visit'])">
<Select
v-model:value="value"
:field-names="{ label: 'name', value: 'id' }"
:options="tenants"
:placeholder="$t('page.tenant.placeholder')"
:dropdown-style="{ position: 'fixed', zIndex: 1666 }"
allow-clear
class="w-40"
@change="handleChange"
/>
</div>
</template>

View File

@ -306,17 +306,13 @@ onMounted(async () => {
<template>
<Layout.Sider
width="260px"
class="!bg-primary-foreground conversation-container relative flex h-full flex-col justify-between overflow-hidden py-2.5 pb-0 pt-2.5"
width="280px"
class="!bg-primary-foreground conversation-container relative flex h-full flex-col justify-between overflow-hidden p-4"
>
<Drawer />
<!-- 左顶部对话 -->
<div class="flex h-full flex-col">
<Button
class="btn-new-conversation h-9 w-full"
type="primary"
@click="createConversation"
>
<Button class="h-9 w-full" type="primary" @click="createConversation">
<IconifyIcon icon="lucide:plus" class="mr-1" />
新建对话
</Button>
@ -324,7 +320,7 @@ onMounted(async () => {
<Input
v-model:value="searchName"
size="large"
class="search-input mt-5"
class="search-input mt-4"
placeholder="搜索历史记录"
@keyup="searchConversation"
>
@ -334,7 +330,7 @@ onMounted(async () => {
</Input>
<!-- 左中间对话列表 -->
<div class="conversation-list mt-2.5 flex-1 overflow-auto">
<div class="conversation-list mt-2 flex-1 overflow-auto">
<!-- 情况一加载中 -->
<Empty v-if="loading" description="." v-loading="loading" />
@ -346,7 +342,7 @@ onMounted(async () => {
>
<div
v-if="conversationMap[conversationKey].length > 0"
class="conversation-item classify-title pt-2.5"
class="conversation-item classify-title pt-2"
>
<b class="mx-1">
{{ conversationKey }}
@ -362,7 +358,7 @@ onMounted(async () => {
class="conversation-item mt-1"
>
<div
class="conversation flex cursor-pointer flex-row items-center justify-between rounded-lg px-2.5 leading-10"
class="conversation flex cursor-pointer flex-row items-center justify-between rounded-lg px-2 leading-10"
:class="[
conversation.id === activeConversationId ? 'bg-gray-100' : '',
]"
@ -374,7 +370,7 @@ onMounted(async () => {
/>
<SvgGptIcon v-else class="size-8" />
<span
class="max-w-36 overflow-hidden text-ellipsis whitespace-nowrap px-2.5 py-1 text-sm font-normal text-gray-600"
class="max-w-36 overflow-hidden text-ellipsis whitespace-nowrap p-2 text-sm font-normal text-gray-600"
>
{{ conversation.title }}
</span>
@ -424,7 +420,7 @@ onMounted(async () => {
<!-- 左底部工具栏 -->
<div
class="tool-box absolute bottom-0 left-0 right-0 flex items-center justify-between bg-gray-50 px-5 leading-9 text-gray-400 shadow-sm"
class="absolute bottom-1 left-0 right-0 mb-4 flex items-center justify-between bg-gray-50 px-5 leading-9 text-gray-400 shadow-sm"
>
<div
class="flex cursor-pointer items-center text-gray-400"

View File

@ -506,7 +506,7 @@ onMounted(async () => {
/>
<!-- 右侧详情部分 -->
<Layout class="ml-4 bg-white">
<Layout class="mx-4 bg-white">
<Layout.Header
class="flex items-center justify-between !bg-gray-50 shadow-none"
>
@ -572,7 +572,7 @@ onMounted(async () => {
class="my-5 mb-5 mt-2 flex flex-col rounded-xl border border-gray-200 px-2 py-2.5"
>
<textarea
class="box-border h-20 resize-none overflow-auto border-none px-0 py-0.5 focus:outline-none"
class="box-border h-24 resize-none overflow-auto border-none px-0 py-1 focus:outline-none"
v-model="prompt"
@keydown="handleSendByKeydown"
@input="handlePromptInput"

View File

@ -73,6 +73,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
search: true,
},
} as VxeTableGridOptions<AiChatConversationApi.ChatConversationVO>,
separator: false,
});
onMounted(async () => {
//

View File

@ -70,6 +70,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
search: true,
},
} as VxeTableGridOptions<AiChatConversationApi.ChatConversationVO>,
separator: false,
});
onMounted(async () => {
//

View File

@ -80,28 +80,28 @@ onMounted(async () => {
</div>
<div class="flex">
<Button
class="m-0 p-2.5"
class="m-0 p-2"
type="text"
@click="handleButtonClick('download', detail)"
>
<IconifyIcon icon="lucide:download" />
</Button>
<Button
class="m-0 p-2.5"
class="m-0 p-2"
type="text"
@click="handleButtonClick('regeneration', detail)"
>
<IconifyIcon icon="lucide:refresh-cw" />
</Button>
<Button
class="m-0 p-2.5"
class="m-0 p-2"
type="text"
@click="handleButtonClick('delete', detail)"
>
<IconifyIcon icon="lucide:trash" />
</Button>
<Button
class="m-0 p-2.5"
class="m-0 p-2"
type="text"
@click="handleButtonClick('more', detail)"
>

View File

@ -123,7 +123,7 @@ defineExpose({ settingValues });
<div>
<b>随机热词</b>
</div>
<Space wrap class="mt-4 flex flex-col flex-wrap justify-start">
<Space wrap class="mt-4 flex flex-wrap justify-start">
<Button
shape="round"
class="m-0"

View File

@ -91,19 +91,17 @@ onMounted(() => {
<template>
<Page auto-content-height>
<div class="flex w-full gap-4">
<Card class="min-w-300 flex-1">
<Card class="w-3/4 flex-1">
<div class="mb-15">
<h3
class="m-0 mb-2 text-lg font-semibold leading-none tracking-tight"
>
<h3 class="m-2 text-lg font-semibold leading-none tracking-tight">
召回测试
</h3>
<div class="text-sm text-gray-500">
<div class="m-2 text-sm text-gray-500">
根据给定的查询文本测试召回效果
</div>
</div>
<div>
<div class="relative mb-2">
<div class="relative m-2">
<Textarea
v-model:value="queryParams.content"
:rows="8"
@ -113,7 +111,7 @@ onMounted(() => {
{{ queryParams.content?.length }} / 200
</div>
</div>
<div class="mb-2 flex items-center">
<div class="m-2 flex items-center">
<span class="w-16 text-gray-500">topK:</span>
<InputNumber
v-model:value="queryParams.topK"
@ -122,7 +120,7 @@ onMounted(() => {
class="w-full"
/>
</div>
<div class="mb-2 flex items-center">
<div class="m-2 flex items-center">
<span class="w-16 text-gray-500">相似度:</span>
<InputNumber
v-model:value="queryParams.similarityThreshold"
@ -176,7 +174,7 @@ onMounted(() => {
class="mb-10 overflow-hidden whitespace-pre-wrap rounded bg-gray-50 p-10 text-sm transition-all duration-100"
:class="{
'max-h-50 line-clamp-2': !segment.expanded,
'max-h-500': segment.expanded,
'max-h-[1500px]': segment.expanded,
}"
>
{{ segment.content }}

View File

@ -1,11 +1,14 @@
<script lang="ts" setup>
import type { NotificationItem } from '@vben/layouts';
import type { SystemTenantApi } from '#/api/system/tenant';
import { computed, onMounted, ref, watch } from 'vue';
import { useAccess } from '@vben/access';
import { AuthenticationLoginExpiredModal, useVbenModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
import { useWatermark } from '@vben/hooks';
import { isTenantEnable, useTabs, useWatermark } from '@vben/hooks';
import {
AntdProfileOutlined,
BookOpenText,
@ -14,32 +17,36 @@ import {
} from '@vben/icons';
import {
BasicLayout,
Help,
LockScreen,
Notification,
TenantDropdown,
UserDropdown,
} from '@vben/layouts';
import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { formatDateTime, openWindow } from '@vben/utils';
import { ElMessage } from 'element-plus';
import {
getUnreadNotifyMessageCount,
getUnreadNotifyMessageList,
updateAllNotifyMessageRead,
updateNotifyMessageRead,
} from '#/api/system/notify/message';
import { getSimpleTenantList } from '#/api/system/tenant';
import { $t } from '#/locales';
import { router } from '#/router';
import { useAuthStore } from '#/store';
import LoginForm from '#/views/_core/authentication/login.vue';
import Help from './components/help.vue';
import TenantDropdown from './components/tenant-dropdown.vue';
const userStore = useUserStore();
const authStore = useAuthStore();
const accessStore = useAccessStore();
const { hasAccessByCodes } = useAccess();
const { destroyWatermark, updateWatermark } = useWatermark();
const { closeOtherTabs, refreshTab } = useTabs();
const notifications = ref<NotificationItem[]>([]);
const unreadCount = ref(0);
@ -148,10 +155,41 @@ function handleNotificationOpen(open: boolean) {
handleNotificationGetUnreadCount();
}
//
const tenants = ref<SystemTenantApi.Tenant[]>([]);
const tenantEnable = computed(
() => hasAccessByCodes(['system:tenant:visit']) && isTenantEnable(),
);
/** 获取租户列表 */
async function handleGetTenantList() {
if (tenantEnable.value) {
tenants.value = await getSimpleTenantList();
}
}
/** 处理租户切换 */
async function handleTenantChange(tenant: SystemTenantApi.Tenant) {
if (!tenant || !tenant.id) {
ElMessage.error('切换租户失败');
return;
}
// 访 ID
accessStore.setVisitTenantId(tenant.id as number);
//
await closeOtherTabs();
//
await refreshTab();
//
ElMessage.success(`切换当前租户为: ${tenant.name}`);
}
// ========== ==========
onMounted(() => {
//
handleNotificationGetUnreadCount();
//
handleGetTenantList();
//
setInterval(
() => {
@ -204,7 +242,14 @@ watch(
/>
</template>
<template #header-right-1>
<TenantDropdown class="w-30 mr-2" />
<div v-if="tenantEnable">
<TenantDropdown
class="mr-2"
:tenant-list="tenants"
:visit-tenant-id="accessStore.visitTenantId"
@success="handleTenantChange"
/>
</div>
</template>
<template #extra>
<AuthenticationLoginExpiredModal

View File

@ -1,91 +0,0 @@
<script lang="ts" setup>
// TODO @xingyu 3 layouts components
import { useVbenModal, VbenButton, VbenButtonGroup } from '@vben/common-ui';
import { openWindow } from '@vben/utils';
import { ElImage, ElTag } from 'element-plus';
import { $t } from '#/locales';
const [Modal, modalApi] = useVbenModal({
draggable: true,
overlayBlur: 5,
footer: false,
onCancel() {
modalApi.close();
},
});
</script>
<template>
<Modal class="w-[40%]" :title="$t('ui.widgets.qa')">
<div class="mt-2 flex flex-col">
<div class="mt-2 flex flex-row">
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
<p class="p-2">项目地址:</p>
<VbenButton
variant="link"
@click="
openWindow('https://gitee.com/yudaocode/yudao-ui-admin-vben')
"
>
Gitee
</VbenButton>
<VbenButton
variant="link"
@click="
openWindow('https://github.com/yudaocode/yudao-ui-admin-vben')
"
>
Github
</VbenButton>
</VbenButtonGroup>
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
<p class="p-2">issues:</p>
<VbenButton
variant="link"
@click="
openWindow(
'https://gitee.com/yudaocode/yudao-ui-admin-vben/issues',
)
"
>
Gitee
</VbenButton>
<VbenButton
variant="link"
@click="
openWindow(
'https://github.com/yudaocode/yudao-ui-admin-vben/issues',
)
"
>
Github
</VbenButton>
</VbenButtonGroup>
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
<p class="p-2">开发文档:</p>
<VbenButton
variant="link"
@click="openWindow('https://doc.iocoder.cn/quick-start/')"
>
项目文档
</VbenButton>
<VbenButton variant="link" @click="openWindow('https://antdv.com/')">
antdv 文档
</VbenButton>
</VbenButtonGroup>
</div>
<p class="mt-2 flex justify-center">
<span>
<ElImage src="/wx-xingyu.png" alt="数舵科技" />
</span>
</p>
<p class="mt-2 flex justify-center pt-4 text-sm italic">
本项目采用<ElTag type="primary">MIT</ElTag>开源协议个人与企业可100%
免费使用
</p>
</div>
</Modal>
</template>

View File

@ -1,67 +0,0 @@
<script lang="ts" setup>
// TODO @puhui999
import type { SystemTenantApi } from '#/api/system/tenant';
import { onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { isTenantEnable, useTabs } from '@vben/hooks';
import { useAccessStore } from '@vben/stores';
import { ElMessage, ElOption, ElSelect } from 'element-plus';
import { getSimpleTenantList } from '#/api/system/tenant';
import { $t } from '#/locales';
const { closeOtherTabs, refreshTab } = useTabs();
const { hasAccessByCodes } = useAccess();
const accessStore = useAccessStore();
const tenantEnable = isTenantEnable();
const value = ref<number>(accessStore.visitTenantId ?? 0); // 访 ID
const tenants = ref<SystemTenantApi.Tenant[]>([]); //
// TODO @xingyu 3
async function handleChange(id: number) {
if (id === null) return;
// 访 ID
accessStore.setVisitTenantId(id);
//
await closeOtherTabs();
//
await refreshTab();
//
const tenant = tenants.value.find((item) => item.id === id);
if (tenant) {
ElMessage.success(`切换当前租户为: ${tenant.name}`);
}
}
onMounted(async () => {
if (!tenantEnable) {
return;
}
tenants.value = await getSimpleTenantList();
});
</script>
<template>
<div v-if="tenantEnable && hasAccessByCodes(['system:tenant:visit'])">
<ElSelect
v-model="value"
:placeholder="$t('page.tenant.placeholder')"
clearable
class="w-40"
@change="handleChange"
>
<ElOption
v-for="item in tenants"
:key="item.id"
:label="item.name"
:value="item.id || 0"
/>
</ElSelect>
</div>
</template>

View File

@ -1,11 +1,14 @@
<script lang="ts" setup>
import type { NotificationItem } from '@vben/layouts';
import type { SystemTenantApi } from '#/api/system/tenant';
import { computed, onMounted, ref, watch } from 'vue';
import { useAccess } from '@vben/access';
import { AuthenticationLoginExpiredModal, useVbenModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
import { useWatermark } from '@vben/hooks';
import { isTenantEnable, useTabs, useWatermark } from '@vben/hooks';
import {
AntdProfileOutlined,
BookOpenText,
@ -14,32 +17,35 @@ import {
} from '@vben/icons';
import {
BasicLayout,
Help,
LockScreen,
Notification,
TenantDropdown,
UserDropdown,
} from '@vben/layouts';
import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { formatDateTime, openWindow } from '@vben/utils';
import { message } from '#/adapter/naive';
import {
getUnreadNotifyMessageCount,
getUnreadNotifyMessageList,
updateAllNotifyMessageRead,
updateNotifyMessageRead,
} from '#/api/system/notify/message';
import { getSimpleTenantList } from '#/api/system/tenant';
import { $t } from '#/locales';
import { router } from '#/router';
import { useAuthStore } from '#/store';
import LoginForm from '#/views/_core/authentication/login.vue';
import Help from './components/help.vue';
import TenantDropdown from './components/tenant-dropdown.vue';
const userStore = useUserStore();
const authStore = useAuthStore();
const accessStore = useAccessStore();
const { hasAccessByCodes } = useAccess();
const { destroyWatermark, updateWatermark } = useWatermark();
const { closeOtherTabs, refreshTab } = useTabs();
const notifications = ref<NotificationItem[]>([]);
const unreadCount = ref(0);
@ -148,10 +154,40 @@ function handleNotificationOpen(open: boolean) {
handleNotificationGetUnreadCount();
}
//
const tenants = ref<SystemTenantApi.Tenant[]>([]);
const tenantEnable = computed(
() => hasAccessByCodes(['system:tenant:visit']) && isTenantEnable(),
);
/** 获取租户列表 */
async function handleGetTenantList() {
if (tenantEnable.value) {
tenants.value = await getSimpleTenantList();
}
}
/** 处理租户切换 */
async function handleTenantChange(tenant: SystemTenantApi.Tenant) {
if (!tenant || !tenant.id) {
message.error('切换租户失败');
return;
}
// 访 ID
accessStore.setVisitTenantId(tenant.id as number);
//
await closeOtherTabs();
//
await refreshTab();
//
message.success(`切换当前租户为: ${tenant.name}`);
}
// ========== ==========
onMounted(() => {
//
handleNotificationGetUnreadCount();
//
handleGetTenantList();
//
setInterval(
() => {
@ -204,7 +240,14 @@ watch(
/>
</template>
<template #header-right-1>
<TenantDropdown class="mr-2" />
<div v-if="tenantEnable">
<TenantDropdown
class="mr-2"
:tenant-list="tenants"
:visit-tenant-id="accessStore.visitTenantId"
@success="handleTenantChange"
/>
</div>
</template>
<template #extra>
<AuthenticationLoginExpiredModal

View File

@ -1,90 +0,0 @@
<script lang="ts" setup>
import { useVbenModal, VbenButton, VbenButtonGroup } from '@vben/common-ui';
import { openWindow } from '@vben/utils';
import { NImage, NTag } from 'naive-ui';
import { $t } from '#/locales';
const [Modal, modalApi] = useVbenModal({
draggable: true,
overlayBlur: 5,
footer: false,
onCancel() {
modalApi.close();
},
});
</script>
<template>
<Modal class="w-[40%]" :title="$t('ui.widgets.qa')">
<div class="mt-2 flex flex-col">
<div class="mt-2 flex flex-row">
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
<p class="p-2">项目地址:</p>
<VbenButton
variant="link"
@click="
openWindow('https://gitee.com/yudaocode/yudao-ui-admin-vben')
"
>
Gitee
</VbenButton>
<VbenButton
variant="link"
@click="
openWindow('https://github.com/yudaocode/yudao-ui-admin-vben')
"
>
Github
</VbenButton>
</VbenButtonGroup>
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
<p class="p-2">issues:</p>
<VbenButton
variant="link"
@click="
openWindow(
'https://gitee.com/yudaocode/yudao-ui-admin-vben/issues',
)
"
>
Gitee
</VbenButton>
<VbenButton
variant="link"
@click="
openWindow(
'https://github.com/yudaocode/yudao-ui-admin-vben/issues',
)
"
>
Github
</VbenButton>
</VbenButtonGroup>
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
<p class="p-2">开发文档:</p>
<VbenButton
variant="link"
@click="openWindow('https://doc.iocoder.cn/quick-start/')"
>
项目文档
</VbenButton>
<VbenButton variant="link" @click="openWindow('https://antdv.com/')">
antdv 文档
</VbenButton>
</VbenButtonGroup>
</div>
<p class="mt-2 flex justify-center">
<span>
<NImage src="/wx-xingyu.png" alt="数舵科技" />
</span>
</p>
<p class="mt-2 flex justify-center pt-4 text-sm italic">
本项目采用<NTag color="blue">MIT</NTag>开源协议个人与企业可100%
免费使用
</p>
</div>
</Modal>
</template>

View File

@ -1,62 +0,0 @@
<script lang="ts" setup>
import type { SelectValue } from 'naive-ui/es/select';
import type { SystemTenantApi } from '#/api/system/tenant';
import { onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { isTenantEnable, useTabs } from '@vben/hooks';
import { useAccessStore } from '@vben/stores';
import { NSelect } from 'naive-ui';
import { message } from '#/adapter/naive';
import { getSimpleTenantList } from '#/api/system/tenant';
import { $t } from '#/locales';
const { closeOtherTabs, refreshTab } = useTabs();
const { hasAccessByCodes } = useAccess();
const accessStore = useAccessStore();
const tenantEnable = isTenantEnable();
const value = ref<number>(accessStore.visitTenantId ?? undefined); // 访 ID
const tenants = ref<SystemTenantApi.Tenant[]>([]); //
async function handleChange(id: SelectValue) {
// 访 ID
accessStore.setVisitTenantId(id as number);
//
await closeOtherTabs();
//
await refreshTab();
//
const tenant = tenants.value.find((item) => item.id === id);
if (tenant) {
message.success(`切换当前租户为: ${tenant.name}`);
}
}
onMounted(async () => {
if (!tenantEnable) {
return;
}
tenants.value = await getSimpleTenantList();
});
</script>
<template>
<div v-if="tenantEnable && hasAccessByCodes(['system:tenant:visit'])">
<NSelect
v-model:value="value"
value-field="id"
label-field="name"
:options="tenants"
:placeholder="$t('page.tenant.placeholder')"
allow-clear
class="w-40"
@update:value="handleChange"
/>
</div>
</template>

View File

@ -1,11 +1,20 @@
<script lang="ts" setup>
// TODO @xingyu 3 layouts components
import { useVbenModal, VbenButton, VbenButtonGroup } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { openWindow } from '@vben/utils';
import { Image, Tag } from 'ant-design-vue';
import { useVbenModal } from '@vben-core/popup-ui';
import { Badge, VbenButton, VbenButtonGroup } from '@vben-core/shadcn-ui';
import { $t } from '#/locales';
import { useMagicKeys, whenever } from '@vueuse/core';
defineOptions({
name: 'Help',
});
const keys = useMagicKeys();
whenever(keys['Alt+KeyH']!, () => {
modalApi.open();
});
const [Modal, modalApi] = useVbenModal({
draggable: true,
@ -79,12 +88,12 @@ const [Modal, modalApi] = useVbenModal({
</div>
<p class="mt-2 flex justify-center">
<span>
<Image src="/wx-xingyu.png" alt="数舵科技" />
<img src="/wx-xingyu.png" alt="数舵科技" />
</span>
</p>
<p class="mt-2 flex justify-center pt-4 text-sm italic">
本项目采用<Tag color="blue">MIT</Tag>开源协议个人与企业可100%
免费使用
本项目采用<Badge variant="destructive">MIT</Badge>
开源协议个人与企业可100% 免费使用
</p>
</div>
</Modal>

View File

@ -0,0 +1 @@
export { default as Help } from './help.vue';

View File

@ -2,10 +2,12 @@ export { default as Breadcrumb } from './breadcrumb.vue';
export * from './check-updates';
export { default as AuthenticationColorToggle } from './color-toggle.vue';
export * from './global-search';
export * from './help';
export { default as LanguageToggle } from './language-toggle.vue';
export { default as AuthenticationLayoutToggle } from './layout-toggle.vue';
export * from './lock-screen';
export * from './notification';
export * from './preferences';
export * from './tenant-dropdown';
export * from './theme-toggle';
export * from './user-dropdown';

View File

@ -0,0 +1 @@
export { default as TenantDropdown } from './tenant-dropdown.vue';

View File

@ -0,0 +1,72 @@
<script lang="ts" setup>
import { computed } from 'vue';
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@vben-core/shadcn-ui';
interface Tenant {
id?: number;
name: string;
packageId: number;
contactName: string;
contactMobile: string;
accountCount: number;
expireTime: Date;
website: string;
status: number;
}
defineOptions({
name: 'TenantDropdown',
});
const props = defineProps<{
tenantList?: Tenant[];
visitTenantId?: null | number;
}>();
const emit = defineEmits(['success']);
//
const tenants = computed(() => props.tenantList ?? []);
async function handleChange(id: number | undefined) {
if (!id) {
return;
}
const tenant = tenants.value.find((item) => item.id === id);
emit('success', tenant);
}
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button
variant="outline"
class="hover:bg-accent ml-1 mr-2 h-8 w-24 cursor-pointer rounded-full p-1.5"
>
{{ tenants.find((item) => item.id === visitTenantId)?.name }}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-56 p-0 pb-1">
<DropdownMenuGroup>
<DropdownMenuItem
v-for="tenant in tenants"
:key="tenant.id"
:disabled="tenant.id === visitTenantId"
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
@click="handleChange(tenant.id)"
>
{{ tenant.name }}
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</template>