Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import ManualKnowledgeEditor from '@/components/manual-knowledge-editor.vue'
import UploadConfirmHost from '@/components/UploadConfirmHost.vue'
import { useAuthStore } from '@/stores/auth'
import { useSettingsStore } from '@/stores/settings'
import { useUIStore } from '@/stores/ui'
import { getCurrentUser, userInfoFromApi } from '@/api/auth'
import { consumePendingTenantSwitchToast } from '@/utils/tenantSwitch'
import { useRoleLabel } from '@/composables/useRoleLabel'
import { notifyLoginSuccess } from '@/utils/loginNotify'
import { renderWorkspaceNotifyContent } from '@/utils/workspaceNotifyContent'
import { evalBreakpoint, startListening, stopListening, isMobile } from '@/composables/useBreakpoint'

// TDesign locale configs
import enUSConfig from 'tdesign-vue-next/esm/locale/en_US'
Expand All @@ -24,6 +26,7 @@ const { formatRole, roleIcon } = useRoleLabel()
const router = useRouter()
const authStore = useAuthStore()
const settingsStore = useSettingsStore()
const uiStore = useUIStore()

const tdLocaleMap: Record<string, object> = {
'en-US': enUSConfig,
Expand Down Expand Up @@ -194,6 +197,16 @@ watch(
{ immediate: true },
)

// 同步断点响应式状态到 UI Store。immediate 让 store 在首帧就与视口一致,
// 否则手机上要等 onMounted 才切形态,会先闪一下桌面侧栏。
watch(isMobile, (val) => {
uiStore.setMobile(val)
// 从移动端切换到桌面端时,关闭移动端菜单
if (!val) {
uiStore.closeMobileMenu()
}
}, { immediate: true })

// 切换空间后会 hard reload;切换前 stash 的 toast 这里 consume 并弹出,
// 这样 toast 显示在新页面上,duration 才真正生效。
const showPendingTenantSwitchToast = () => {
Expand Down Expand Up @@ -223,6 +236,10 @@ const showPendingTenantSwitchToast = () => {
}

onMounted(() => {
// 初始化全局响应式断点监听,同步到 UI Store
startListening()
evalBreakpoint()

handleGlobalOIDCCallback()
showPendingTenantSwitchToast()

Expand Down Expand Up @@ -250,6 +267,7 @@ onMounted(() => {
})

onUnmounted(() => {
stopListening()
if (updateCheckTimer) {
clearInterval(updateCheckTimer)
}
Expand Down
84 changes: 84 additions & 0 deletions frontend/src/assets/responsive.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// ============================================================
// WeKnora 响应式断点 Less 混入
// ============================================================
// 使用移动端优先 (mobile-first) 策略:
// 基础样式 = 移动端,
// .tablet-up() → 平板及以上增强,
// .desktop() → 桌面端增强。
// ============================================================

// 断点值(与 useBreakpoint.ts 保持一致)
@bp-mobile-max: 767px;
@bp-tablet-min: 768px;
@bp-tablet-max: 1023px;
@bp-desktop-min: 1024px;

// --- 设备类型混入 ---

// 仅手机 (< 768px)
.mobile(@rules) {
@media screen and (max-width: @bp-mobile-max) {
@rules();
}
}

// 仅平板 (768px - 1023px)
.tablet(@rules) {
@media screen and (min-width: @bp-tablet-min) and (max-width: @bp-tablet-max) {
@rules();
}
}

// 平板及以上 (≥ 768px) — mobile-first 增强用
.tablet-up(@rules) {
@media screen and (min-width: @bp-tablet-min) {
@rules();
}
}

// 仅桌面 (≥ 1024px)
.desktop(@rules) {
@media screen and (min-width: @bp-desktop-min) {
@rules();
}
}

// 非桌面 (< 1024px) — 即手机 + 平板
.not-desktop(@rules) {
@media screen and (max-width: @bp-tablet-max) {
@rules();
}
}

// --- 自定义断点混入(使用 min-width) ---
.min-width(@width, @rules) {
@media screen and (min-width: @width) {
@rules();
}
}

.max-width(@width, @rules) {
@media screen and (max-width: @width) {
@rules();
}
}

// --- 触摸设备 ---
.touch(@rules) {
@media (hover: none) {
@rules();
}
}

.hover-capable(@rules) {
@media (hover: hover) {
@rules();
}
}

// --- 减少动效 ---
.reduced-motion(@rules) {
@media (prefers-reduced-motion: reduce) {
@rules();
}
}
68 changes: 68 additions & 0 deletions frontend/src/assets/theme/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,74 @@

}

/* ============================================================
响应式断点 CSS 变量(与 useBreakpoint.ts / responsive.less 保持一致)
============================================================ */
:root {
--breakpoint-mobile-max: 767px;
--breakpoint-tablet-min: 768px;
--breakpoint-tablet-max: 1023px;
--breakpoint-desktop-min: 1024px;
--app-min-touch-target: 44px;
}

/* ============================================================
移动端全局样式增强
============================================================ */

/* 触摸设备:放大 TDesign 交互元素的点击区域。
会话行的常显操作按钮改在 menu.vue 的 scoped 块里覆写,
因为默认的 opacity: 0 也带着 scoped 属性选择器的权重。 */
@media (hover: none) {
/* 下拉菜单项最小触摸区域 */
.t-dropdown__item {
min-height: 44px;
display: flex;
align-items: center;
}

/* 表单按钮 */
.t-button {
min-height: 40px;
}

/* 小按钮可以小一些 */
.t-button.t-size-s {
min-height: 32px;
}
}

/* ============================================================
移动端全局排版调整
============================================================ */
@media screen and (max-width: 767px) {
/* 移动端基础字号略大,补偿手持距离 */
body,
html {
font-size: 15px;
}

/* TDesign 表格/列表在移动端的水平滚动 */
.t-table {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}

/* 标签在小屏幕上换行 */
.t-tag {
white-space: normal;
word-break: break-word;
}
}

/* 平板端微调 */
@media screen and (min-width: 768px) and (max-width: 1023px) {
body,
html {
font-size: 14px;
}
}

/* 全局深色模式滚动条样式 */
:root[theme-mode="dark"] *::-webkit-scrollbar-thumb {
background-color: #4b4b4b !important;
Expand Down
75 changes: 75 additions & 0 deletions frontend/src/components/Input-field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2738,6 +2738,7 @@ const getImgSrc = (url: string) => {
}
</script>
<style scoped lang="less">
@import '@/assets/responsive.less';
@import './css/chat-resource-chips.less';

.answers-input {
Expand All @@ -2747,15 +2748,27 @@ const getImgSrc = (url: string) => {
left: 50%;
transform: translateX(-50%);
width: 100%;
min-width: 0;
box-sizing: border-box;
display: flex;
justify-content: center;

// 窄屏留出左右留白,否则输入框圆角与阴影会被屏幕边缘切掉
.not-desktop({
padding-inline: 16px;
});

.mobile({
padding-inline: 12px;
});

&.is-embedded {
position: relative;
bottom: auto;
left: auto;
transform: none;
z-index: auto;
padding-inline: 0;

.rich-input-container {
max-width: 100%;
Expand All @@ -2768,6 +2781,8 @@ const getImgSrc = (url: string) => {
position: relative;
width: 100%;
max-width: 960px;
min-width: 0;
box-sizing: border-box;
background: var(--td-bg-color-container, #FFF);
border-radius: 12px;
border: 1px solid var(--td-component-stroke, #dcdcdc);
Expand Down Expand Up @@ -3465,6 +3480,7 @@ const getImgSrc = (url: string) => {

.model-selector-name {
flex: 1;
min-width: 0;
font-size: 12px;
font-weight: 500;
color: var(--td-text-color-secondary, #666);
Expand All @@ -3489,6 +3505,65 @@ const getImgSrc = (url: string) => {
color: var(--td-text-color-placeholder, #999);
}

// 窄屏:控制栏保持单行。默认的 flex-wrap 会把模型选择器和发送按钮挤到第二行,
// 既抬高输入框又让发送键离开拇指区。可选控件改为横向滑动,发送键固定不收缩。
.mobile({
.control-bar {
left: 12px;
right: 12px;
bottom: 10px;
gap: 6px;
flex-wrap: nowrap;
max-height: none;
}

.control-left {
gap: 4px;
flex-wrap: nowrap;
overflow-x: auto;
overscroll-behavior-x: contain;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;

&::-webkit-scrollbar {
display: none;
}
}

.control-right {
flex-shrink: 0;
}

.agent-mode-btn {
max-width: 104px;
padding: 0 8px;
}

.agent-mode-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.model-display {
min-width: 0;
margin-left: 0;
flex-shrink: 1;
}

.model-selector-trigger {
min-width: 72px;
max-width: 104px;
padding: 2px 6px;
}

:deep(.t-textarea__inner) {
min-height: 104px !important;
padding-bottom: 50px;
}
});

.model-selector-overlay {
position: fixed;
inset: 0;
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/components/MyInvitationsDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -321,5 +321,33 @@ watch(
gap: 6px;
flex-shrink: 0;
}

// 移动端:卡片纵向堆叠 + 弹窗全宽
@media screen and (max-width: 767px) {
.invitation-card {
flex-direction: column;
gap: 10px;
}

.invitation-card-actions {
flex-direction: row;
justify-content: flex-end;
}

:deep(.t-dialog) {
width: 100vw !important;
max-width: 100vw !important;
margin: 0 !important;
border-radius: 0 !important;
}

:deep(.t-dialog__header) {
padding: 14px 16px;
}

:deep(.t-dialog__body) {
padding: 16px;
}
}
}
</style>
Loading