This commit is contained in:
2026-08-11 15:03:02 +08:00
parent c97304b786
commit fb8f52fe76
40 changed files with 376 additions and 787 deletions
+1 -68
View File
@@ -11,6 +11,7 @@
<script setup> <script setup>
import { computed, onBeforeUnmount, onMounted, watch } from 'vue'; import { computed, onBeforeUnmount, onMounted, watch } from 'vue';
import '@/styles/dark-mode-overrides.css';
import { useUserStore } from './store/user'; import { useUserStore } from './store/user';
import { ElConfigProvider } from 'element-plus'; import { ElConfigProvider } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn'; import zhCn from 'element-plus/es/locale/lang/zh-cn';
@@ -37,7 +38,6 @@ watch(
); );
onMounted(() => { onMounted(() => {
applyDarkModeClass();
darkModeObserver = new MutationObserver(() => { darkModeObserver = new MutationObserver(() => {
const enabled = Boolean(userStore.darkMode); const enabled = Boolean(userStore.darkMode);
const rootMatches = document.documentElement.classList.contains('dark-mode') === enabled; const rootMatches = document.documentElement.classList.contains('dark-mode') === enabled;
@@ -150,71 +150,4 @@ body {
.common-tabs .el-tabs__active-bar { .common-tabs .el-tabs__active-bar {
background-color: var(--primary-color); background-color: var(--primary-color);
} }
/* 深色模式下的Element Plus组件样式 */
.dark-mode {
/* 输入框文字颜色(EP 变量覆盖不到原生 input) */
input[type="text"],
input[type="password"],
input[type="email"] {
color: var(--el-input-text-color);
}
/* message box 背景统一 */
.el-message-box,
.el-message-box__header,
.el-message-box__content,
.el-message-box__btns {
background-color: var(--el-fill-color-blank);
}
.el-message-box__header { border-bottom: none; }
.el-message-box__btns { border-top: none; }
/* select dropdown 项 */
.el-select-dropdown__item.selected {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
/* date picker panel */
.el-picker-panel__body,
.el-picker-panel__content {
background-color: var(--el-fill-color-blank);
color: var(--el-text-color-regular);
}
.el-date-table td.in-range div,
.el-date-table td.available:hover div {
background-color: var(--bg-medium);
}
/* 数字输入按钮 */
.el-input-number__decrease,
.el-input-number__increase {
background-color: var(--bg-medium);
color: var(--text-dark);
border-color: var(--border-color);
}
.el-input-number__decrease:hover,
.el-input-number__increase:hover {
background-color: var(--bg-light);
}
/* 对话框边框 */
.el-dialog__header {
border-bottom: 1px solid var(--el-border-color);
}
.el-dialog__footer {
border-top: 1px solid var(--el-border-color);
}
/* 加载动画 */
.el-loading-spinner .path {
stroke: var(--primary-color);
}
}
</style> </style>
-3
View File
@@ -2,9 +2,6 @@ import { ElMessage } from 'element-plus';
import { useAsyncAction } from '@/composables/actions/useAsyncAction'; import { useAsyncAction } from '@/composables/actions/useAsyncAction';
export const isActionCancelled = (error) => { export const isActionCancelled = (error) => {
if (error === null || error === undefined) {
return false;
}
if (typeof error === 'string') { if (typeof error === 'string') {
return error === 'cancel' || error === 'close'; return error === 'cancel' || error === 'close';
} }
@@ -105,8 +105,8 @@ export function useRichTextLinkOverlay(options = {}) {
const maxTop = container.scrollTop + container.clientHeight - cardHeight - 8; const maxTop = container.scrollTop + container.clientHeight - cardHeight - 8;
linkHoverStyle.value = { linkHoverStyle.value = {
left: `${Math.max(minLeft, Math.min(rawLeft, Math.max(minLeft, maxLeft)))}px`, left: `${Math.max(minLeft, Math.min(rawLeft, maxLeft))}px`,
top: `${Math.max(minTop, Math.min(rawTop, Math.max(minTop, maxTop)))}px` top: `${Math.max(minTop, Math.min(rawTop, maxTop))}px`
}; };
linkHoverHref.value = href; linkHoverHref.value = href;
hoverAnchorPos.value = resolveAnchorPos(anchorEl); hoverAnchorPos.value = resolveAnchorPos(anchorEl);
@@ -16,8 +16,6 @@ export function useDocumentEditorPolicy(options = {}) {
const isSlideDocument = computed(() => normalizedDocType.value === '3'); const isSlideDocument = computed(() => normalizedDocType.value === '3');
const isRichTextDocument = computed(() => normalizedDocType.value === '4'); const isRichTextDocument = computed(() => normalizedDocType.value === '4');
const canManageAttachments = computed(() => hasDocument.value);
const canManageSettings = computed(() => hasDocument.value);
const collabEnabled = computed( const collabEnabled = computed(
() => hasDocument.value && (isMarkdownDocument.value || isRichTextDocument.value) () => hasDocument.value && (isMarkdownDocument.value || isRichTextDocument.value)
); );
@@ -35,8 +33,6 @@ export function useDocumentEditorPolicy(options = {}) {
isTableDocument, isTableDocument,
isSlideDocument, isSlideDocument,
isRichTextDocument, isRichTextDocument,
canManageAttachments,
canManageSettings,
collabEnabled, collabEnabled,
inlineCommentEnabled, inlineCommentEnabled,
createDialogKnowledgeBaseId, createDialogKnowledgeBaseId,
@@ -13,7 +13,7 @@ export function useDocumentRouteContext({ props, route }) {
const collabEnabled = computed(() => !!docId.value); const collabEnabled = computed(() => !!docId.value);
const collabRoom = computed(() => (docId.value ? `${docId.value}` : '')); const collabRoom = computed(() => (docId.value ? `${docId.value}` : ''));
const collabUrl = computed(() => '/ws/doc'); const collabUrl = computed(() => '/ws/doc');
const collabToken = computed(() => localStorage.getItem('token') || ''); const collabToken = localStorage.getItem('token') || '';
const collabReady = ref(false); const collabReady = ref(false);
const lastSyncedDocId = ref(''); const lastSyncedDocId = ref('');
@@ -1,17 +1,5 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { resolveBool } from '@/utils/resolveBool';
const resolveBool = (valueOrGetterOrRef, fallback = false) => {
if (typeof valueOrGetterOrRef === 'function') {
return valueOrGetterOrRef();
}
if (valueOrGetterOrRef && typeof valueOrGetterOrRef === 'object' && 'value' in valueOrGetterOrRef) {
return valueOrGetterOrRef.value;
}
if (valueOrGetterOrRef === undefined) {
return fallback;
}
return Boolean(valueOrGetterOrRef);
};
export function useDocumentTreeContextMenu(options = {}) { export function useDocumentTreeContextMenu(options = {}) {
const { enabled = true } = options; const { enabled = true } = options;
@@ -1,17 +1,5 @@
import { nextTick, ref } from 'vue'; import { nextTick, ref } from 'vue';
import { resolveBool } from '@/utils/resolveBool';
const resolveBool = (valueOrGetterOrRef, fallback = false) => {
if (typeof valueOrGetterOrRef === 'function') {
return valueOrGetterOrRef();
}
if (valueOrGetterOrRef && typeof valueOrGetterOrRef === 'object' && 'value' in valueOrGetterOrRef) {
return valueOrGetterOrRef.value;
}
if (valueOrGetterOrRef === undefined) {
return fallback;
}
return Boolean(valueOrGetterOrRef);
};
export function useInlineRename(options = {}) { export function useInlineRename(options = {}) {
const { revertRename = true, onConfirm = null } = options; const { revertRename = true, onConfirm = null } = options;
@@ -11,6 +11,7 @@ import { useTemplateCatalog } from '@/composables/template/useTemplateCatalog';
import { usePageBoot } from '@/composables/shell/usePageBoot'; import { usePageBoot } from '@/composables/shell/usePageBoot';
import { useApiAction } from '@/composables/actions/useApiAction'; import { useApiAction } from '@/composables/actions/useApiAction';
import { DEFAULT_DOCUMENT_TYPE, normalizeDocumentType } from '@/utils/documentType'; import { DEFAULT_DOCUMENT_TYPE, normalizeDocumentType } from '@/utils/documentType';
import { formatDate as _formatDate } from '@/utils/formatDate';
export function useKnowledgeBaseDetailPage({ t, router, route, userStore, locale }) { export function useKnowledgeBaseDetailPage({ t, router, route, userStore, locale }) {
const { runWithLoading, createApiErrorHandler } = useApiAction({ t }); const { runWithLoading, createApiErrorHandler } = useApiAction({ t });
@@ -73,11 +74,7 @@ export function useKnowledgeBaseDetailPage({ t, router, route, userStore, locale
{ value: 'type', label: t('knowledgeBase.sortByType') } { value: 'type', label: t('knowledgeBase.sortByType') }
]); ]);
const formatDate = (dateString) => { const formatDate = (value) => _formatDate(value, { fallback: t('common.unknown') });
if (!dateString) return t('common.unknown');
const date = new Date(dateString);
return date.toLocaleDateString();
};
const knowledgeBaseStats = computed(() => [ const knowledgeBaseStats = computed(() => [
{ key: 'documents', icon: Document, label: t('knowledgeBase.documentsCount'), value: totalDocuments.value }, { key: 'documents', icon: Document, label: t('knowledgeBase.documentsCount'), value: totalDocuments.value },
@@ -88,16 +85,19 @@ export function useKnowledgeBaseDetailPage({ t, router, route, userStore, locale
const directoryTreeData = computed(() => { const directoryTreeData = computed(() => {
if (!knowledgeBaseData.value?.documents) return []; if (!knowledgeBaseData.value?.documents) return [];
const mapDoc = (doc, parentId = null) => ({ const mapDoc = (doc, parentId = null) => {
id: doc.external_id, const isFolder = doc.hasChildren || (doc.children && doc.children.length > 0);
label: doc.title, return {
type: doc.type, id: doc.external_id,
isFolder: doc.hasChildren || (doc.children && doc.children.length > 0), label: doc.title,
isLeaf: !(doc.hasChildren || (doc.children && doc.children.length > 0)), type: doc.type,
tags: doc.tags || [], isFolder,
parentId, isLeaf: !isFolder,
children: doc.children ? doc.children.map((child) => mapDoc(child, doc.external_id)) : [] tags: doc.tags || [],
}); parentId,
children: doc.children ? doc.children.map((child) => mapDoc(child, doc.external_id)) : []
};
};
return knowledgeBaseData.value.documents.map((doc) => mapDoc(doc)); return knowledgeBaseData.value.documents.map((doc) => mapDoc(doc));
}); });
@@ -3,6 +3,7 @@ import * as api from '@/services/api';
import { usePagedList } from '@/composables/list/usePagedList'; import { usePagedList } from '@/composables/list/usePagedList';
import { useLazyTabLoader } from '@/composables/list/useLazyTabLoader'; import { useLazyTabLoader } from '@/composables/list/useLazyTabLoader';
import { isActionCancelled, useApiAction } from '@/composables/actions/useApiAction'; import { isActionCancelled, useApiAction } from '@/composables/actions/useApiAction';
import { formatDate as _formatDate } from '@/utils/formatDate';
export function useKnowledgeBaseListPage({ t, router }) { export function useKnowledgeBaseListPage({ t, router }) {
const { runApi } = useApiAction({ t }); const { runApi } = useApiAction({ t });
@@ -39,11 +40,7 @@ export function useKnowledgeBaseListPage({ t, router }) {
} }
}); });
const formatDate = (dateString) => { const formatDate = (value) => _formatDate(value, { fallback: t('common.unknown') });
if (!dateString) return t('common.unknown');
const date = new Date(dateString);
return date.toLocaleDateString();
};
const myTagMapper = (kb) => (kb.is_public ? null : { type: 'info', label: t('knowledgeBase.private') }); const myTagMapper = (kb) => (kb.is_public ? null : { type: 'info', label: t('knowledgeBase.private') });
const recentTagMapper = (kb) => (kb.is_public ? { type: 'success', label: t('knowledgeBase.public') } : null); const recentTagMapper = (kb) => (kb.is_public ? { type: 'success', label: t('knowledgeBase.public') } : null);
@@ -0,0 +1,105 @@
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
const resolveCurrentUserKey = () => {
if (typeof window === 'undefined') return 'guest';
try {
const raw = localStorage.getItem('userInfo');
if (!raw) return 'guest';
const userInfo = JSON.parse(raw);
return String(userInfo?.external_id || userInfo?.externalId || 'guest').trim() || 'guest';
} catch {
return 'guest';
}
};
export const createExpiringCache = ({ storageKey, itemsField }) => {
const memoryCache = new Map();
const readStorage = () => {
if (typeof window === 'undefined') return {};
try {
const raw = localStorage.getItem(storageKey);
if (!raw) return {};
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object') return {};
return parsed;
} catch {
return {};
}
};
const writeStorage = (cacheObj) => {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(storageKey, JSON.stringify(cacheObj));
} catch {}
};
const normalizeItems = (items) => {
if (!Array.isArray(items)) return [];
return items.map((item) => String(item || '').trim()).filter(Boolean);
};
const isEntryExpired = (entry, now = Date.now()) => {
const expiresAt = Number(entry?.expiresAt || 0);
return !Number.isFinite(expiresAt) || expiresAt <= now;
};
const normalizeEntry = (entry) => {
if (Array.isArray(entry)) return null;
if (!entry || typeof entry !== 'object') return null;
const items = normalizeItems(entry[itemsField]);
const expiresAt = Number(entry.expiresAt || 0);
if (!Number.isFinite(expiresAt)) return null;
return { [itemsField]: items, expiresAt };
};
const purgeExpired = (storageCache, now = Date.now()) => {
let changed = false;
Object.keys(storageCache || {}).forEach((key) => {
const normalized = normalizeEntry(storageCache[key]);
if (!normalized || isEntryExpired(normalized, now)) {
delete storageCache[key];
changed = true;
}
});
if (changed) writeStorage(storageCache);
};
const buildKey = (scope) => {
const scopeKey = String(scope || '').trim();
if (!scopeKey) return '';
return `${resolveCurrentUserKey()}::${scopeKey}`;
};
const get = (scope) => {
const key = buildKey(scope);
if (!key) return null;
const now = Date.now();
const memoryValue = normalizeEntry(memoryCache.get(key));
if (memoryValue && !isEntryExpired(memoryValue, now)) {
return [...memoryValue[itemsField]];
}
if (memoryValue) memoryCache.delete(key);
const storageCache = readStorage();
purgeExpired(storageCache, now);
const storageEntry = normalizeEntry(storageCache[key]);
if (!storageEntry || isEntryExpired(storageEntry, now)) return null;
memoryCache.set(key, storageEntry);
return [...storageEntry[itemsField]];
};
const set = (scope, items) => {
const key = buildKey(scope);
if (!key) return;
const normalized = normalizeItems(items);
const entry = { [itemsField]: normalized, expiresAt: Date.now() + CACHE_TTL_MS };
memoryCache.set(key, entry);
const storageCache = readStorage();
purgeExpired(storageCache);
storageCache[key] = entry;
writeStorage(storageCache);
};
return { get, set };
};
+8 -8
View File
@@ -16,7 +16,7 @@ export function usePanelSidebar(options = {}) {
const toValidWidth = (value) => { const toValidWidth = (value) => {
const dynamicMax = typeof getMaxWidth === 'function' ? getMaxWidth() : maxWidth; const dynamicMax = typeof getMaxWidth === 'function' ? getMaxWidth() : maxWidth;
const hardMax = Math.max(minWidth, dynamicMax || maxWidth); const hardMax = Math.max(minWidth, dynamicMax ?? maxWidth);
return Math.min(Math.max(value, minWidth), hardMax); return Math.min(Math.max(value, minWidth), hardMax);
}; };
@@ -41,8 +41,8 @@ export function usePanelSidebar(options = {}) {
}); });
const resizing = ref(false); const resizing = ref(false);
const startX = ref(0); let startX = 0;
const startWidth = ref(width.value); let startWidth = width.value;
const stopResize = () => { const stopResize = () => {
if (!resizing.value) return; if (!resizing.value) return;
@@ -56,17 +56,17 @@ export function usePanelSidebar(options = {}) {
const onResizeMove = (event) => { const onResizeMove = (event) => {
if (!resizing.value) return; if (!resizing.value) return;
const delta = resizeEdge === 'left' const delta = resizeEdge === 'left'
? startX.value - event.clientX ? startX - event.clientX
: event.clientX - startX.value; : event.clientX - startX;
width.value = toValidWidth(startWidth.value + delta); width.value = toValidWidth(startWidth + delta);
}; };
const startResize = (event) => { const startResize = (event) => {
if (collapsed.value) return; if (collapsed.value) return;
event.preventDefault(); event.preventDefault();
resizing.value = true; resizing.value = true;
startX.value = event.clientX; startX = event.clientX;
startWidth.value = width.value; startWidth = width.value;
document.body.style.cursor = 'col-resize'; document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none'; document.body.style.userSelect = 'none';
window.addEventListener('mousemove', onResizeMove); window.addEventListener('mousemove', onResizeMove);
@@ -1,126 +1,6 @@
const CACHE_STORAGE_KEY = 'sideNavDisplayTabsCacheV2'; import { createExpiringCache } from './createExpiringCache';
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
const memoryCache = new Map();
const resolveCurrentUserKey = () => { const cache = createExpiringCache({ storageKey: 'sideNavDisplayTabsCacheV2', itemsField: 'tabs' });
if (typeof window === 'undefined') return 'guest';
try {
const raw = localStorage.getItem('userInfo');
if (!raw) return 'guest';
const userInfo = JSON.parse(raw);
return String(userInfo?.external_id || userInfo?.externalId || 'guest').trim() || 'guest';
} catch {
return 'guest';
}
};
const buildScopedSceneKey = (scene) => { export const getSideBarDisplayTabsCache = (scene) => cache.get(scene);
const sceneKey = String(scene || '').trim(); export const setSideBarDisplayTabsCache = (scene, tabs) => cache.set(scene, tabs);
if (!sceneKey) return '';
return `${resolveCurrentUserKey()}::${sceneKey}`;
};
const readStorageCache = () => {
if (typeof window === 'undefined') return {};
try {
const raw = localStorage.getItem(CACHE_STORAGE_KEY);
if (!raw) return {};
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object') return {};
return parsed;
} catch {
return {};
}
};
const writeStorageCache = (cacheObj) => {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(cacheObj));
} catch {
// ignore storage write errors
}
};
const normalizeTabs = (tabs) => {
if (!Array.isArray(tabs)) return [];
return tabs
.map((item) => String(item || '').trim())
.filter(Boolean);
};
const isEntryExpired = (entry, now = Date.now()) => {
const expiresAt = Number(entry?.expiresAt || 0);
return !Number.isFinite(expiresAt) || expiresAt <= now;
};
const normalizeEntry = (entry) => {
if (Array.isArray(entry)) {
// Backward compatibility for old schema: treat as expired and ignore.
return null;
}
if (!entry || typeof entry !== 'object') return null;
const tabs = normalizeTabs(entry.tabs);
const expiresAt = Number(entry.expiresAt || 0);
if (!Number.isFinite(expiresAt)) return null;
return {
tabs,
expiresAt
};
};
const purgeExpiredStorageEntries = (storageCache, now = Date.now()) => {
let changed = false;
Object.keys(storageCache || {}).forEach((key) => {
const normalized = normalizeEntry(storageCache[key]);
if (!normalized || isEntryExpired(normalized, now)) {
delete storageCache[key];
changed = true;
}
});
if (changed) {
writeStorageCache(storageCache);
}
};
export const getSideBarDisplayTabsCache = (scene) => {
const key = buildScopedSceneKey(scene);
if (!key) return null;
const now = Date.now();
const memoryValue = normalizeEntry(memoryCache.get(key));
if (memoryValue && !isEntryExpired(memoryValue, now)) {
return [...memoryValue.tabs];
}
if (memoryValue && isEntryExpired(memoryValue, now)) {
memoryCache.delete(key);
}
const storageCache = readStorageCache();
purgeExpiredStorageEntries(storageCache, now);
const storageEntry = normalizeEntry(storageCache[key]);
if (!storageEntry || isEntryExpired(storageEntry, now)) {
return null;
}
memoryCache.set(key, storageEntry);
return [...storageEntry.tabs];
};
export const setSideBarDisplayTabsCache = (scene, tabs) => {
const key = buildScopedSceneKey(scene);
if (!key) return;
const normalized = normalizeTabs(tabs);
const entry = {
tabs: normalized,
expiresAt: Date.now() + CACHE_TTL_MS
};
memoryCache.set(key, entry);
const storageCache = readStorageCache();
purgeExpiredStorageEntries(storageCache);
storageCache[key] = entry;
writeStorageCache(storageCache);
};
+4 -116
View File
@@ -1,118 +1,6 @@
const CACHE_STORAGE_KEY = 'topNavDisplayMenusCacheV1'; import { createExpiringCache } from './createExpiringCache';
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
const memoryCache = new Map();
const resolveCurrentUserKey = () => { const cache = createExpiringCache({ storageKey: 'topNavDisplayMenusCacheV1', itemsField: 'menus' });
if (typeof window === 'undefined') return 'guest';
try {
const raw = localStorage.getItem('userInfo');
if (!raw) return 'guest';
const userInfo = JSON.parse(raw);
return String(userInfo?.external_id || userInfo?.externalId || 'guest').trim() || 'guest';
} catch {
return 'guest';
}
};
const buildScopedKey = () => `${resolveCurrentUserKey()}::top-nav`; export const getTopNavDisplayMenusCache = () => cache.get('top-nav');
export const setTopNavDisplayMenusCache = (menus) => cache.set('top-nav', menus);
const readStorageCache = () => {
if (typeof window === 'undefined') return {};
try {
const raw = localStorage.getItem(CACHE_STORAGE_KEY);
if (!raw) return {};
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object') return {};
return parsed;
} catch {
return {};
}
};
const writeStorageCache = (cacheObj) => {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(cacheObj));
} catch {
// ignore storage write errors
}
};
const normalizeMenus = (menus) => {
if (!Array.isArray(menus)) return [];
return menus
.map((item) => String(item || '').trim())
.filter(Boolean);
};
const isEntryExpired = (entry, now = Date.now()) => {
const expiresAt = Number(entry?.expiresAt || 0);
return !Number.isFinite(expiresAt) || expiresAt <= now;
};
const normalizeEntry = (entry) => {
if (Array.isArray(entry)) {
// Backward compatibility for old schema: treat as expired.
return null;
}
if (!entry || typeof entry !== 'object') return null;
const menus = normalizeMenus(entry.menus);
const expiresAt = Number(entry.expiresAt || 0);
if (!Number.isFinite(expiresAt)) return null;
return {
menus,
expiresAt
};
};
const purgeExpiredStorageEntries = (storageCache, now = Date.now()) => {
let changed = false;
Object.keys(storageCache || {}).forEach((key) => {
const normalized = normalizeEntry(storageCache[key]);
if (!normalized || isEntryExpired(normalized, now)) {
delete storageCache[key];
changed = true;
}
});
if (changed) {
writeStorageCache(storageCache);
}
};
export const getTopNavDisplayMenusCache = () => {
const key = buildScopedKey();
const now = Date.now();
const memoryValue = normalizeEntry(memoryCache.get(key));
if (memoryValue && !isEntryExpired(memoryValue, now)) {
return [...memoryValue.menus];
}
if (memoryValue && isEntryExpired(memoryValue, now)) {
memoryCache.delete(key);
}
const storageCache = readStorageCache();
purgeExpiredStorageEntries(storageCache, now);
const storageEntry = normalizeEntry(storageCache[key]);
if (!storageEntry || isEntryExpired(storageEntry, now)) {
return null;
}
memoryCache.set(key, storageEntry);
return [...storageEntry.menus];
};
export const setTopNavDisplayMenusCache = (menus) => {
const key = buildScopedKey();
const normalized = normalizeMenus(menus);
const entry = {
menus: normalized,
expiresAt: Date.now() + CACHE_TTL_MS
};
memoryCache.set(key, entry);
const storageCache = readStorageCache();
purgeExpiredStorageEntries(storageCache);
storageCache[key] = entry;
writeStorageCache(storageCache);
};
@@ -1,11 +1,12 @@
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { listNotifications, markNotificationsRead, markAllNotificationsRead } from '@/services/api'; import { listNotifications, markNotificationsRead, markAllNotificationsRead } from '@/services/api';
import { useApiAction } from '@/composables/actions/useApiAction'; import { useApiAction } from '@/composables/actions/useApiAction';
import { formatDate as _formatDate } from '@/utils/formatDate';
export function useNotificationCenter({ t }) { export function useNotificationCenter({ t }) {
const { runApi, runSilent, runWithLoading } = useApiAction({ t }); const { runApi, runSilent, runWithLoading } = useApiAction({ t });
const runNotificationSilent = (context, action, options = {}) => const runNotificationSilent = (action, options = {}) =>
runSilent(action, { context, ...options }); runSilent(action, options);
const activeTab = ref('all'); const activeTab = ref('all');
const notifications = ref([]); const notifications = ref([]);
@@ -23,25 +24,23 @@ export function useNotificationCenter({ t }) {
{ key: 'actions', label: t('common.actions'), minWidth: 160, align: 'center' } { key: 'actions', label: t('common.actions'), minWidth: 160, align: 'center' }
]); ]);
const displayNotifications = computed(() => notifications.value);
const selectAll = computed(() => { const selectAll = computed(() => {
if (!displayNotifications.value.length) return false; if (!notifications.value.length) return false;
return displayNotifications.value.every((item) => selectedIds.value.includes(item.external_id)); return notifications.value.every((item) => selectedIds.value.includes(item.external_id));
}); });
const selectIndeterminate = computed(() => { const selectIndeterminate = computed(() => {
const selectedCount = displayNotifications.value.filter((item) => selectedIds.value.includes(item.external_id)).length; const selectedCount = notifications.value.filter((item) => selectedIds.value.includes(item.external_id)).length;
return selectedCount > 0 && selectedCount < displayNotifications.value.length; return selectedCount > 0 && selectedCount < notifications.value.length;
}); });
const toggleSelectAll = (value) => { const toggleSelectAll = (value) => {
if (value) { if (value) {
selectedIds.value = displayNotifications.value.map((item) => item.external_id); selectedIds.value = notifications.value.map((item) => item.external_id);
return; return;
} }
selectedIds.value = selectedIds.value.filter( selectedIds.value = selectedIds.value.filter(
(id) => !displayNotifications.value.some((item) => item.external_id === id) (id) => !notifications.value.some((item) => item.external_id === id)
); );
}; };
@@ -67,12 +66,7 @@ export function useNotificationCenter({ t }) {
return row.title || row.content || '-'; return row.title || row.content || '-';
}; };
const formatDate = (value) => { const formatDate = (value) => _formatDate(value, { fallback: '-', useLocaleString: true });
if (!value) return '-';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleString();
};
const emitUnread = (hasUnread) => { const emitUnread = (hasUnread) => {
window.dispatchEvent(new CustomEvent('notifications:unread', { detail: { hasUnread } })); window.dispatchEvent(new CustomEvent('notifications:unread', { detail: { hasUnread } }));
@@ -80,7 +74,6 @@ export function useNotificationCenter({ t }) {
const refreshUnreadStatus = async () => { const refreshUnreadStatus = async () => {
await runNotificationSilent( await runNotificationSilent(
'refreshUnreadStatus',
() => listNotifications({ page: 1, page_size: 1, status: 'unread' }), () => listNotifications({ page: 1, page_size: 1, status: 'unread' }),
{ {
onSuccess: (resp) => { onSuccess: (resp) => {
@@ -97,7 +90,6 @@ export function useNotificationCenter({ t }) {
await runWithLoading( await runWithLoading(
loading, loading,
() => runNotificationSilent( () => runNotificationSilent(
'loadNotifications',
() => () =>
listNotifications({ listNotifications({
page: currentPage.value, page: currentPage.value,
@@ -177,11 +169,11 @@ export function useNotificationCenter({ t }) {
return { return {
activeTab, activeTab,
notifications,
totalCount, totalCount,
currentPage, currentPage,
pageSize, pageSize,
columns, columns,
displayNotifications,
selectedIds, selectedIds,
selectAll, selectAll,
selectIndeterminate, selectIndeterminate,
@@ -8,6 +8,7 @@ import { useLazyTabLoader } from '@/composables/list/useLazyTabLoader';
import { useTemplateCatalog } from '@/composables/template/useTemplateCatalog'; import { useTemplateCatalog } from '@/composables/template/useTemplateCatalog';
import { useApiAction } from '@/composables/actions/useApiAction'; import { useApiAction } from '@/composables/actions/useApiAction';
import { DEFAULT_DOCUMENT_TYPE, normalizeDocumentType } from '@/utils/documentType'; import { DEFAULT_DOCUMENT_TYPE, normalizeDocumentType } from '@/utils/documentType';
import { formatDate as _formatDate } from '@/utils/formatDate';
export function useTemplateListPage({ t, router }) { export function useTemplateListPage({ t, router }) {
const { runWithLoading, createApiErrorHandler } = useApiAction({ t }); const { runWithLoading, createApiErrorHandler } = useApiAction({ t });
@@ -55,12 +56,7 @@ export function useTemplateListPage({ t, router }) {
onError: handleTemplateLoadError onError: handleTemplateLoadError
}); });
const formatDate = (value) => { const formatDate = (value) => _formatDate(value, { fallback: t('common.unknown') });
if (!value) return t('common.unknown');
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleDateString();
};
const openTemplate = (tpl) => { const openTemplate = (tpl) => {
if (!tpl?.id) return; if (!tpl?.id) return;
@@ -3,6 +3,7 @@ import { ElMessage } from 'element-plus';
import { createDocument as createDocumentApi, getTemplate } from '@/services/api'; import { createDocument as createDocumentApi, getTemplate } from '@/services/api';
import { useApiAction } from '@/composables/actions/useApiAction'; import { useApiAction } from '@/composables/actions/useApiAction';
import { DEFAULT_DOCUMENT_TYPE, normalizeDocumentType } from '@/utils/documentType'; import { DEFAULT_DOCUMENT_TYPE, normalizeDocumentType } from '@/utils/documentType';
import { formatDate as _formatDate } from '@/utils/formatDate';
export function useTemplatePreviewPage({ t, route, router }) { export function useTemplatePreviewPage({ t, route, router }) {
const { runWithLoading } = useApiAction({ t }); const { runWithLoading } = useApiAction({ t });
@@ -14,11 +15,7 @@ export function useTemplatePreviewPage({ t, route, router }) {
const templateId = computed(() => route.params.templateId || route.params.id); const templateId = computed(() => route.params.templateId || route.params.id);
const formatDate = (dateString) => { const formatDate = (value) => _formatDate(value, { fallback: t('common.unknown') });
if (!dateString) return t('common.unknown');
const date = new Date(dateString);
return date.toLocaleDateString();
};
const scopeLabel = computed(() => { const scopeLabel = computed(() => {
const scope = template.value?.scope; const scope = template.value?.scope;
+1 -1
View File
@@ -8,7 +8,7 @@ const defaultBaseURL = (() => {
return ''; return '';
})(); })();
export const API_BASE_URL = trimTrailingSlash(import.meta.env.VITE_API_BASE_URL || defaultBaseURL); const API_BASE_URL = trimTrailingSlash(import.meta.env.VITE_API_BASE_URL || defaultBaseURL);
export const resolveWithApiBase = (target = '') => { export const resolveWithApiBase = (target = '') => {
const value = String(target || '').trim(); const value = String(target || '').trim();
+1 -2
View File
@@ -14,8 +14,7 @@ const i18n = createI18n({
fallbackLocale: 'en', fallbackLocale: 'en',
messages: { messages: {
en: enUS, en: enUS,
zh: zhCN, zh: zhCN
'zh-CN': zhCN
} }
}); });
-2
View File
@@ -408,7 +408,6 @@ export default {
documentsCount: 'Documents Count', documentsCount: 'Documents Count',
createdAt: 'Created At', createdAt: 'Created At',
updatedAt: 'Updated At', updatedAt: 'Updated At',
owner: 'Owner: {owner}',
access: 'Access', access: 'Access',
view: 'View Details', view: 'View Details',
fetchError: 'Failed to fetch knowledge bases', fetchError: 'Failed to fetch knowledge bases',
@@ -434,7 +433,6 @@ export default {
template: 'Template', template: 'Template',
selectTemplate: 'Select template', selectTemplate: 'Select template',
createDocumentSuccess: 'Document created successfully', createDocumentSuccess: 'Document created successfully',
documentCreatedSuccessfully: 'Document created successfully',
createDocumentError: 'Failed to create document', createDocumentError: 'Failed to create document',
newDocument: 'New Document', newDocument: 'New Document',
treeRefreshed: 'Tree structure refreshed', treeRefreshed: 'Tree structure refreshed',
-2
View File
@@ -406,7 +406,6 @@ export default {
documentsCount: '文档数量', documentsCount: '文档数量',
createdAt: '创建时间', createdAt: '创建时间',
updatedAt: '更新时间', updatedAt: '更新时间',
owner: '所有者: {owner}',
access: '访问', access: '访问',
view: '查看详情', view: '查看详情',
fetchError: '获取知识库失败', fetchError: '获取知识库失败',
@@ -432,7 +431,6 @@ export default {
template: '模板', template: '模板',
selectTemplate: '请选择模板', selectTemplate: '请选择模板',
createDocumentSuccess: '文档创建成功', createDocumentSuccess: '文档创建成功',
documentCreatedSuccessfully: '文档创建成功',
createDocumentError: '创建文档失败', createDocumentError: '创建文档失败',
newDocument: '新文档', newDocument: '新文档',
treeRefreshed: '树形结构已刷新', treeRefreshed: '树形结构已刷新',
+36 -62
View File
@@ -6,6 +6,40 @@ const { t } = i18n.global;
const APP_NAME = 'Yoresee'; const APP_NAME = 'Yoresee';
const makeDocumentChildRoutes = (basePath, namePrefix, propsFactory) => [
{
path: `${basePath}/:docId`,
name: `${namePrefix}Editor`,
component: () => import('../views/document/DocumentEditor.vue'),
meta: { requiresAuth: true, dynamicTitle: true },
props: propsFactory
},
{
path: `${basePath}/:docId/attachments`,
name: `${namePrefix}Attachments`,
component: () => import('../views/document/DocumentAttachments.vue'),
meta: { requiresAuth: true, titleKey: 'document.attachments.title' }
},
{
path: `${basePath}/:docId/history`,
name: `${namePrefix}History`,
component: () => import('../views/document/DocumentHistory.vue'),
meta: { requiresAuth: true, titleKey: 'history.title' }
},
{
path: `${basePath}/:docId/setting`,
name: `${namePrefix}Settings`,
component: () => import('../views/document/DocumentSettings.vue'),
meta: { requiresAuth: true, titleKey: 'document.settings.title' }
},
{
path: `${basePath}/:docId/attachment/:attachmentId`,
name: `${namePrefix}AttachmentOpen`,
component: () => import('../views/document/DocumentAttachmentOpen.vue'),
meta: { requiresAuth: true, titleKey: 'document.attachments.previewTitle' }
}
];
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes: [ routes: [
@@ -38,37 +72,7 @@ const router = createRouter({
name: 'MyDocumentsAlias', name: 'MyDocumentsAlias',
redirect: '/mydocuments' redirect: '/mydocuments'
}, },
{ ...makeDocumentChildRoutes('/mydocument', 'MyDocument', (route) => ({ kbId: 'personal', docId: route.params.docId })),
path: '/mydocument/:docId',
name: 'MyDocumentEditor',
component: () => import('../views/document/DocumentEditor.vue'),
meta: { requiresAuth: true, dynamicTitle: true },
props: (route) => ({ kbId: 'personal', docId: route.params.docId })
},
{
path: '/mydocument/:docId/attachments',
name: 'MyDocumentAttachments',
component: () => import('../views/document/DocumentAttachments.vue'),
meta: { requiresAuth: true, titleKey: 'document.attachments.title' }
},
{
path: '/mydocument/:docId/history',
name: 'MyDocumentHistory',
component: () => import('../views/document/DocumentHistory.vue'),
meta: { requiresAuth: true, titleKey: 'history.title' }
},
{
path: '/mydocument/:docId/setting',
name: 'MyDocumentSettings',
component: () => import('../views/document/DocumentSettings.vue'),
meta: { requiresAuth: true, titleKey: 'document.settings.title' }
},
{
path: '/mydocument/:docId/attachment/:attachmentId',
name: 'MyDocumentAttachmentOpen',
component: () => import('../views/document/DocumentAttachmentOpen.vue'),
meta: { requiresAuth: true, titleKey: 'document.attachments.previewTitle' }
},
{ {
path: '/knowledge-base', path: '/knowledge-base',
name: 'KnowledgeBase', name: 'KnowledgeBase',
@@ -100,37 +104,7 @@ const router = createRouter({
meta: { requiresAuth: true, dynamicTitle: true }, meta: { requiresAuth: true, dynamicTitle: true },
props: true props: true
}, },
{ ...makeDocumentChildRoutes('/knowledge-base/:kbId/document', 'KnowledgeBaseDocument', true),
path: '/knowledge-base/:kbId/document/:docId',
name: 'KnowledgeBaseDocumentEditor',
component: () => import('../views/document/DocumentEditor.vue'),
meta: { requiresAuth: true, dynamicTitle: true },
props: true
},
{
path: '/knowledge-base/:kbId/document/:docId/attachments',
name: 'KnowledgeBaseDocumentAttachments',
component: () => import('../views/document/DocumentAttachments.vue'),
meta: { requiresAuth: true, titleKey: 'document.attachments.title' }
},
{
path: '/knowledge-base/:kbId/document/:docId/history',
name: 'KnowledgeBaseDocumentHistory',
component: () => import('../views/document/DocumentHistory.vue'),
meta: { requiresAuth: true, titleKey: 'history.title' }
},
{
path: '/knowledge-base/:kbId/document/:docId/setting',
name: 'KnowledgeBaseDocumentSettings',
component: () => import('../views/document/DocumentSettings.vue'),
meta: { requiresAuth: true, titleKey: 'document.settings.title' }
},
{
path: '/knowledge-base/:kbId/document/:docId/attachment/:attachmentId',
name: 'KnowledgeBaseDocumentAttachmentOpen',
component: () => import('../views/document/DocumentAttachmentOpen.vue'),
meta: { requiresAuth: true, titleKey: 'document.attachments.previewTitle' }
},
{ {
path: '/user_info/profile', path: '/user_info/profile',
name: 'UserProfile', name: 'UserProfile',
-20
View File
@@ -6,23 +6,3 @@ export * from './api/invitation';
export * from './api/setting'; export * from './api/setting';
export * from './api/notification'; export * from './api/notification';
export * from './api/comment'; export * from './api/comment';
import * as knowledgeBaseApi from './api/knowledgeBase';
import * as documentApi from './api/document';
import * as templateApi from './api/template';
import * as membershipApi from './api/membership';
import * as invitationApi from './api/invitation';
import * as settingApi from './api/setting';
import * as notificationApi from './api/notification';
import * as commentApi from './api/comment';
export default {
...knowledgeBaseApi,
...documentApi,
...templateApi,
...membershipApi,
...invitationApi,
...settingApi,
...notificationApi,
...commentApi
};
+3 -4
View File
@@ -91,10 +91,9 @@ export const getRecentKnowledgeBases = async (params = {}) => {
export const getKnowledgeBaseDocuments = async (knowledgeBaseExternalID, params = {}) => { export const getKnowledgeBaseDocuments = async (knowledgeBaseExternalID, params = {}) => {
return getKnowledgeBaseDetail(knowledgeBaseExternalID, { return getKnowledgeBaseDetail(knowledgeBaseExternalID, {
...params,
record_recent_log: false, record_recent_log: false,
page: 1, page: params.page ?? 1,
page_size: 1000, page_size: params.page_size ?? 1000
directory_only: params.directory_only || false,
...params
}); });
}; };
+5 -5
View File
@@ -36,7 +36,9 @@ export function handleResponse(base, data) {
if (base.code === 0) { if (base.code === 0) {
return { ...base, ...data }; return { ...base, ...data };
} }
throw new Error('request failed'); const err = new Error(base.message || 'request failed');
err.code = base.code;
throw err;
} }
export function toTimeRange(input) { export function toTimeRange(input) {
@@ -162,8 +164,7 @@ export function mapInvitationRecord(rec) {
used_by: rec.usedBy, used_by: rec.usedBy,
used_by_external_id: rec.usedByExternalId ?? null, used_by_external_id: rec.usedByExternalId ?? null,
used_at: rec.usedAt, used_at: rec.usedAt,
status: rec.status, status: rec.status
row_key: `${rec.code || ''}_${rec.usedAt || ''}_${rec.status || ''}`
}; };
} }
@@ -176,8 +177,7 @@ export function mapNotification(item) {
title: item.title, title: item.title,
content: item.content, content: item.content,
payload: item.payload, payload: item.payload,
created_at: item.createdAt, created_at: item.createdAt
read: item.status === 'read'
}; };
} }
+3 -15
View File
@@ -6,7 +6,7 @@ import {
handleResponse, handleResponse,
mapTemplate mapTemplate
} from './shared'; } from './shared';
import { normalizeDocumentType } from '@/utils/documentType'; import { toDocumentTypeNumber } from '@/utils/documentType';
const { const {
CreateTemplateRequest, CreateTemplateRequest,
@@ -23,24 +23,12 @@ const containerMap = {
public: CreateTemplateContainer.PUBLIC_TEMPLATE public: CreateTemplateContainer.PUBLIC_TEMPLATE
}; };
const resolveDocumentType = (type) => {
const normalized = normalizeDocumentType(type, '');
if (!normalized) {
return undefined;
}
const parsed = Number(normalized);
if (!Number.isFinite(parsed)) {
return undefined;
}
return parsed;
};
export const createTemplate = async (data = {}) => { export const createTemplate = async (data = {}) => {
const req = new CreateTemplateRequest({ const req = new CreateTemplateRequest({
targetContainer: containerMap[data.target_container] ?? CreateTemplateContainer.OWN_TEMPLATE, targetContainer: containerMap[data.target_container] ?? CreateTemplateContainer.OWN_TEMPLATE,
knowledgeBaseId: data.knowledge_base_id || '', knowledgeBaseId: data.knowledge_base_id || '',
templateContent: data.template_content || '', templateContent: data.template_content || '',
type: resolveDocumentType(data.type) type: toDocumentTypeNumber(data.type)
}); });
const resp = await unaryCall(documentClient, 'createTemplate', req); const resp = await unaryCall(documentClient, 'createTemplate', req);
@@ -52,7 +40,7 @@ export const listTemplates = async (params = {}) => {
const req = new ListTemplatesRequest({ const req = new ListTemplatesRequest({
onlyMine: Boolean(params.only_mine), onlyMine: Boolean(params.only_mine),
targetContainer: params.target_container ? containerMap[params.target_container] : undefined, targetContainer: params.target_container ? containerMap[params.target_container] : undefined,
type: resolveDocumentType(params.type), type: toDocumentTypeNumber(params.type),
knowledgeBaseId: params.knowledge_base_id || undefined, knowledgeBaseId: params.knowledge_base_id || undefined,
nameKeyword: params.name_keyword || undefined, nameKeyword: params.name_keyword || undefined,
orderBy: params.order_by || undefined, orderBy: params.order_by || undefined,
+2 -2
View File
@@ -29,7 +29,7 @@ const settingClient = createPromiseClient(SettingService, transport);
const systemClient = createPromiseClient(SystemService, transport); const systemClient = createPromiseClient(SystemService, transport);
const invitationClient = createPromiseClient(InvitationService, transport); const invitationClient = createPromiseClient(InvitationService, transport);
export function buildHeaders() { function buildHeaders() {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
const language = localStorage.getItem('language') || 'en'; const language = localStorage.getItem('language') || 'en';
const headers = { const headers = {
@@ -46,7 +46,7 @@ export async function unaryCall(client, method, request) {
return await client[method](request, { headers: buildHeaders() }); return await client[method](request, { headers: buildHeaders() });
} catch (err) { } catch (err) {
const code = err?.code; const code = err?.code;
if (code === Code.Unauthenticated || code === 'unauthenticated') { if (code === Code.Unauthenticated) {
localStorage.removeItem('token'); localStorage.removeItem('token');
localStorage.removeItem('userInfo'); localStorage.removeItem('userInfo');
window.location.href = '/login'; window.location.href = '/login';
+60
View File
@@ -0,0 +1,60 @@
.auth-form {
width: 100%;
}
.auth-form .el-form-item {
margin-bottom: var(--spacing-md);
}
.auth-form .el-input {
height: 40px;
border-radius: var(--border-radius-md);
}
.auth-form .el-input__wrapper {
box-shadow: none;
}
.auth-form .el-input__wrapper.is-focus {
box-shadow: 0 0 0 2px var(--primary-light);
}
.auth-form .login-button {
width: 100%;
height: 40px;
border-radius: var(--border-radius-md);
font-size: 14px;
font-weight: 500;
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.auth-form .login-button:hover,
.auth-form .login-button:active {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
.auth-form .error-message {
margin-bottom: var(--spacing-md);
}
.auth-form .error-alert {
padding: 8px 12px;
font-size: 12px;
}
.auth-form .register-link {
text-align: center;
margin-top: var(--spacing-md);
}
.auth-form .register-link a {
color: var(--primary-color);
font-size: 14px;
text-decoration: none;
}
.auth-form .register-link a:hover {
text-decoration: underline;
}
+58
View File
@@ -0,0 +1,58 @@
.dark-mode {
input[type="text"],
input[type="password"],
input[type="email"] {
color: var(--el-input-text-color);
}
.el-message-box,
.el-message-box__header,
.el-message-box__content,
.el-message-box__btns {
background-color: var(--el-fill-color-blank);
}
.el-message-box__header { border-bottom: none; }
.el-message-box__btns { border-top: none; }
.el-select-dropdown__item.selected {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.el-picker-panel__body,
.el-picker-panel__content {
background-color: var(--el-fill-color-blank);
color: var(--el-text-color-regular);
}
.el-date-table td.in-range div,
.el-date-table td.available:hover div {
background-color: var(--bg-medium);
}
.el-input-number__decrease,
.el-input-number__increase {
background-color: var(--bg-medium);
color: var(--text-dark);
border-color: var(--border-color);
}
.el-input-number__decrease:hover,
.el-input-number__increase:hover {
background-color: var(--bg-light);
}
.el-dialog__header {
border-bottom: 1px solid var(--el-border-color);
}
.el-dialog__footer {
border-top: 1px solid var(--el-border-color);
}
.el-loading-spinner .path {
stroke: var(--primary-color);
}
}
+11
View File
@@ -0,0 +1,11 @@
import { unref } from 'vue';
export const navigateToDocument = (router, kbId, docId) => {
const kb = unref(kbId);
const doc = unref(docId);
if (kb === 'personal') {
router.push(`/mydocument/${doc}`);
return;
}
router.push(`/knowledge-base/${kb}/document/${doc}`);
};
+6
View File
@@ -0,0 +1,6 @@
export const formatDate = (value, { fallback = '-', useLocaleString = false } = {}) => {
if (!value) return fallback;
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return useLocaleString ? date.toLocaleString() : date.toLocaleDateString();
};
+12
View File
@@ -0,0 +1,12 @@
export const resolveBool = (valueOrGetterOrRef, fallback = false) => {
if (typeof valueOrGetterOrRef === 'function') {
return valueOrGetterOrRef();
}
if (valueOrGetterOrRef && typeof valueOrGetterOrRef === 'object' && 'value' in valueOrGetterOrRef) {
return valueOrGetterOrRef.value;
}
if (valueOrGetterOrRef === undefined) {
return fallback;
}
return Boolean(valueOrGetterOrRef);
};
+2 -63
View File
@@ -11,7 +11,7 @@
:model="loginForm" :model="loginForm"
:rules="loginRules" :rules="loginRules"
ref="loginFormRef" ref="loginFormRef"
class="login-form" class="auth-form"
> >
<el-form-item prop="email"> <el-form-item prop="email">
<el-input <el-input
@@ -67,6 +67,7 @@ import { useI18n } from 'vue-i18n';
import AuthLayout from '@/components/layout/AuthLayout.vue'; import AuthLayout from '@/components/layout/AuthLayout.vue';
import { useAuthShell } from '@/composables/shell/useAuthShell'; import { useAuthShell } from '@/composables/shell/useAuthShell';
import { usePageBoot } from '@/composables/shell/usePageBoot'; import { usePageBoot } from '@/composables/shell/usePageBoot';
import '@/styles/auth-form.css';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
@@ -130,65 +131,3 @@ onMounted(() => {
}); });
</script> </script>
<style scoped>
.login-form {
width: 100%;
}
.login-form .el-form-item {
margin-bottom: var(--spacing-md);
}
.login-form .el-input {
height: 40px;
border-radius: var(--border-radius-md);
}
.login-form .el-input__wrapper {
box-shadow: none;
}
.login-form .el-input__wrapper.is-focus {
box-shadow: 0 0 0 2px var(--primary-light);
}
.login-button {
width: 100%;
height: 40px;
border-radius: var(--border-radius-md);
font-size: 14px;
font-weight: 500;
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.login-button:hover,
.login-button:active {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
.error-message {
margin-bottom: var(--spacing-md);
}
.error-alert {
padding: 8px 12px;
font-size: 12px;
}
.register-link {
text-align: center;
margin-top: var(--spacing-md);
}
.register-link a {
color: var(--primary-color);
font-size: 14px;
text-decoration: none;
}
.register-link a:hover {
text-decoration: underline;
}
</style>
+2 -63
View File
@@ -7,7 +7,7 @@
@change-language="handleLanguageChange" @change-language="handleLanguageChange"
@toggle-theme="toggleTheme" @toggle-theme="toggleTheme"
> >
<el-form :model="registerForm" :rules="registerRules" ref="registerFormRef" class="register-form"> <el-form :model="registerForm" :rules="registerRules" ref="registerFormRef" class="auth-form">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input
v-model="registerForm.username" v-model="registerForm.username"
@@ -89,6 +89,7 @@ import { useI18n } from 'vue-i18n';
import AuthLayout from '@/components/layout/AuthLayout.vue'; import AuthLayout from '@/components/layout/AuthLayout.vue';
import { useAuthShell } from '@/composables/shell/useAuthShell'; import { useAuthShell } from '@/composables/shell/useAuthShell';
import { usePageBoot } from '@/composables/shell/usePageBoot'; import { usePageBoot } from '@/composables/shell/usePageBoot';
import '@/styles/auth-form.css';
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
@@ -194,65 +195,3 @@ onMounted(() => {
}); });
</script> </script>
<style scoped>
.register-form {
width: 100%;
}
.register-form .el-form-item {
margin-bottom: var(--spacing-md);
}
.register-form .el-input {
height: 40px;
border-radius: var(--border-radius-md);
}
.register-form .el-input__wrapper {
box-shadow: none;
}
.register-form .el-input__wrapper.is-focus {
box-shadow: 0 0 0 2px var(--primary-light);
}
.login-button {
width: 100%;
height: 40px;
border-radius: var(--border-radius-md);
font-size: 14px;
font-weight: 500;
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.login-button:hover,
.login-button:active {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
.error-message {
margin-bottom: var(--spacing-md);
}
.error-alert {
padding: 8px 12px;
font-size: 12px;
}
.register-link {
text-align: center;
margin-top: var(--spacing-md);
}
.register-link a {
color: var(--primary-color);
font-size: 14px;
text-decoration: none;
}
.register-link a:hover {
text-decoration: underline;
}
</style>
+3 -7
View File
@@ -202,13 +202,9 @@ const formatSize = (bytes) => {
return `${(value / (1024 * 1024)).toFixed(1)} MB`; return `${(value / (1024 * 1024)).toFixed(1)} MB`;
}; };
const goBackToDocument = () => { import { navigateToDocument } from '@/utils/documentRoute';
if (kbId.value === 'personal') {
router.push(`/mydocument/${docId.value}`); const goBackToDocument = () => navigateToDocument(router, kbId, docId);
return;
}
router.push(`/knowledge-base/${kbId.value}/document/${docId.value}`);
};
onMounted(async () => { onMounted(async () => {
await initLanguage(); await initLanguage();
+3 -17
View File
@@ -341,8 +341,7 @@ const {
isTableDocument, isTableDocument,
isSlideDocument, isSlideDocument,
isRichTextDocument, isRichTextDocument,
canManageAttachments, hasDocument,
canManageSettings,
collabEnabled, collabEnabled,
inlineCommentEnabled, inlineCommentEnabled,
createDialogKnowledgeBaseId, createDialogKnowledgeBaseId,
@@ -352,6 +351,8 @@ const {
docId, docId,
currentDocType currentDocType
}); });
const canManageAttachments = computed(() => hasDocument.value);
const canManageSettings = computed(() => hasDocument.value);
const { const {
isEditingTitle, isEditingTitle,
pendingTitle, pendingTitle,
@@ -532,21 +533,6 @@ watch(collabReady, async (ready) => {
color: var(--text-dark); color: var(--text-dark);
} }
.editor-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-sm) var(--spacing-lg);
border-top: 1px solid var(--border-color);
font-size: 12px;
color: var(--text-light);
}
.dark-mode .editor-footer {
border-color: var(--border-color);
color: var(--text-light);
}
.editor-fullscreen-header-btn { .editor-fullscreen-header-btn {
color: var(--text-medium); color: var(--text-medium);
} }
+3 -7
View File
@@ -248,13 +248,9 @@ const handleSizeChange = async (size) => {
await fetchVersions(); await fetchVersions();
}; };
const goBackToDocument = () => { import { navigateToDocument } from '@/utils/documentRoute';
if (kbId.value === 'personal') {
router.push(`/mydocument/${docId.value}`); const goBackToDocument = () => navigateToDocument(router, kbId, docId);
return;
}
router.push(`/knowledge-base/${kbId.value}/document/${docId.value}`);
};
onMounted(async () => { onMounted(async () => {
await initLanguage(); await initLanguage();
+3 -7
View File
@@ -97,13 +97,9 @@ const settings = reactive({
isPublic: false isPublic: false
}); });
const goBackToDocument = () => { import { navigateToDocument } from '@/utils/documentRoute';
if (kbId.value === 'personal') {
router.push(`/mydocument/${docId.value}`); const goBackToDocument = () => navigateToDocument(router, kbId, docId);
return;
}
router.push(`/knowledge-base/${kbId.value}/document/${docId.value}`);
};
const loadSettings = async () => { const loadSettings = async () => {
if (!docId.value) { if (!docId.value) {
-101
View File
@@ -142,111 +142,10 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
/* 垂直布局 */
.knowledge-base-vertical-layout { .knowledge-base-vertical-layout {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-lg); gap: var(--spacing-lg);
height: auto; height: auto;
} }
/* 深色模式支持 */
.dark-mode .search-input :deep(.el-input__wrapper) {
background-color: var(--input-bg);
border-color: var(--input-border);
color: var(--input-text);
}
.dark-mode .search-input :deep(.el-input__inner) {
background-color: var(--input-bg);
border-color: var(--input-border);
color: var(--input-text);
}
.dark-mode .filter-select :deep(.el-input__wrapper) {
background-color: var(--select-bg);
border-color: var(--select-border);
color: var(--select-text);
}
.dark-mode .filter-select :deep(.el-input__inner) {
background-color: var(--select-bg);
border-color: var(--select-border);
color: var(--select-text);
}
.dark-mode .filter-select :deep(.el-select__dropdown) {
background-color: var(--select-bg);
border-color: var(--select-border);
}
.dark-mode .filter-select :deep(.el-popper) {
background-color: var(--select-bg);
border-color: var(--select-border);
color: var(--select-text);
}
.dark-mode .filter-select :deep(.el-select-dropdown__item) {
background-color: var(--select-option-bg);
color: var(--select-text);
}
.dark-mode .filter-select :deep(.el-select-dropdown__item:hover) {
background-color: var(--select-option-hover);
}
.dark-mode .el-pagination :deep(.el-pager li) {
background-color: var(--bg-white);
color: var(--text-primary);
border-color: var(--border-color);
}
.dark-mode .el-pagination :deep(button) {
background-color: var(--bg-white);
color: var(--text-primary);
border-color: var(--border-color);
}
.dark-mode .el-pagination.is-background :deep(.btn-next),
.dark-mode .el-pagination.is-background :deep(.btn-prev),
.dark-mode .el-pagination.is-background :deep(.el-pager li) {
background-color: var(--bg-medium);
color: var(--text-primary);
}
.dark-mode .el-pagination.is-background :deep(.el-pager li:not(.is-disabled):hover) {
color: var(--primary-color);
}
.dark-mode .el-pagination.is-background :deep(.el-pager li.is-active) {
background-color: var(--primary-color);
color: white;
}
.dark-mode .el-button {
border-color: var(--border-color);
color: var(--text-primary);
}
.dark-mode .el-button--primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.dark-mode .el-button--primary:hover {
background-color: var(--primary-light);
border-color: var(--primary-light);
color: white;
}
.dark-mode .column {
background-color: var(--bg-medium);
border: 1px solid var(--border-color);
}
.dark-mode .column-header {
background-color: var(--bg-medium);
border-bottom: 1px solid var(--border-color);
}
</style> </style>
@@ -25,7 +25,7 @@
:section-title="t('system.organization.placeholderTitle')" :section-title="t('system.organization.placeholderTitle')"
body-padding="md" body-padding="md"
mode="tree" mode="tree"
:rows="pagedOrgNodes" :rows="orgTreeData"
:columns="orgColumns" :columns="orgColumns"
:is-dark="isDarkMode" :is-dark="isDarkMode"
row-key="external_id" row-key="external_id"
@@ -259,8 +259,6 @@ const orgColumns = computed(() => [
const pageSizes = [6, 10, 20]; const pageSizes = [6, 10, 20];
const paginationLayout = 'total, prev, pager, next'; const paginationLayout = 'total, prev, pager, next';
const pagedOrgNodes = computed(() => orgTreeData.value);
const dialogTitle = computed(() => { const dialogTitle = computed(() => {
if (createParent.value?.name) { if (createParent.value?.name) {
return t('system.organization.createChildWith', { name: createParent.value.name }); return t('system.organization.createChildWith', { name: createParent.value.name });
+2 -2
View File
@@ -21,7 +21,7 @@
</el-tabs> </el-tabs>
<CommonList <CommonList
:rows="displayNotifications" :rows="notifications"
:columns="columns" :columns="columns"
:is-dark="isDarkMode" :is-dark="isDarkMode"
row-key="external_id" row-key="external_id"
@@ -127,7 +127,7 @@ const {
currentPage, currentPage,
pageSize, pageSize,
columns, columns,
displayNotifications, notifications,
selectedIds, selectedIds,
selectAll, selectAll,
selectIndeterminate, selectIndeterminate,