From dab1e0a3e0b1279ee7be77d359ffc93a831455d4 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期一, 24 八月 2026 17:57:27 +0800
Subject: [PATCH] feat(preferences): 公司品牌信息化编辑与全局联动(登录页/菜单栏/网页标题),偏好设置 tab

---
 src/packages/locales/src/langs/en-US/preferences.json                           |   19 ++
 src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue     |   11 +
 src/api/system/company-config/index.ts                                          |   27 +++
 src/bootstrap.ts                                                                |   14 ++
 src/store/index.ts                                                              |    1 
 src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts            |    1 
 src/packages/locales/src/langs/zh-CN/preferences.json                           |   19 ++
 src/packages/effects/layouts/src/widgets/preferences/blocks/company/company.vue |  151 +++++++++++++++++++++
 src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue      |   25 +++
 src/store/company-config.ts                                                     |   71 ++++++++++
 src/api/system/storage/index.ts                                                 |   11 +
 src/layouts/auth.vue                                                            |   21 ++
 src/utils/company-config.ts                                                     |   32 ++++
 13 files changed, 397 insertions(+), 6 deletions(-)

diff --git a/src/api/system/company-config/index.ts b/src/api/system/company-config/index.ts
new file mode 100644
index 0000000..dbd1405
--- /dev/null
+++ b/src/api/system/company-config/index.ts
@@ -0,0 +1,27 @@
+import { requestClient } from '#/api/request';
+
+export namespace SystemCompanyConfigApi {
+  /** 鍏徃鍝佺墝淇℃伅 */
+  export interface CompanyConfig {
+    id?: number;
+    companyName?: string;
+    logoUrl?: string;
+    logoBlobId?: number;
+    loginTitle?: string;
+    loginDescription?: string;
+  }
+}
+
+/** 鑾峰緱鍏徃鍝佺墝淇℃伅锛堝厤鐧诲綍鍏紑锛岀櫥褰曢〉 / 搴旂敤鍚姩鏃朵娇鐢級 */
+export async function getCompanyConfig() {
+  return requestClient.get<SystemCompanyConfigApi.CompanyConfig | null>(
+    '/system/company-config/get',
+  );
+}
+
+/** 淇濆瓨鍏徃鍝佺墝淇℃伅 */
+export async function saveCompanyConfig(
+  data: SystemCompanyConfigApi.CompanyConfig,
+) {
+  return requestClient.post<number>('/system/company-config/save', data);
+}
diff --git a/src/api/system/storage/index.ts b/src/api/system/storage/index.ts
index 994d3ad..d1c3557 100644
--- a/src/api/system/storage/index.ts
+++ b/src/api/system/storage/index.ts
@@ -45,6 +45,17 @@
   );
 }
 
+/** 鍏叡鏂囦欢涓婁紶锛堟案涔呮湁鏁堬紝杩斿洖姘镐箙鍏紑 previewURL锛岀敤浜庣櫥褰曢〉绛夊厤鐧诲綍灞曠ず鐨勫満鏅級 */
+export function publicUploadFile(files: globalThis.File[]) {
+  const formData = new FormData();
+  files.forEach((file) => formData.append('files', file));
+  return requestClient.post<SystemStorageApi.StorageBlob[]>(
+    '/system/storage-blob/public-upload',
+    formData,
+    { headers: { 'Content-Type': 'multipart/form-data' } },
+  );
+}
+
 /** 鏌ヨ闄勪欢鍒楄〃 */
 export function listAttachments(params: SystemStorageApi.AttachmentListParams) {
   return requestClient.get<SystemStorageApi.StorageBlob[]>(
diff --git a/src/bootstrap.ts b/src/bootstrap.ts
index 6a97710..bbfb549 100644
--- a/src/bootstrap.ts
+++ b/src/bootstrap.ts
@@ -18,6 +18,9 @@
 import App from './app.vue';
 import { router } from './router';
 
+import { getCompanyConfig } from '#/api/system/company-config';
+import { applyCompanyConfig } from '#/utils/company-config';
+
 async function bootstrap(namespace: string) {
   // 鍒濆鍖栫粍浠堕�傞厤鍣�
   await initComponentAdapter();
@@ -49,6 +52,17 @@
   // 閰嶇疆 pinia-store
   await initStores(app, { namespace });
 
+  // 鍔犺浇鍏徃鍝佺墝淇℃伅锛堜粠鍚庣锛屽厤鐧诲綍鎺ュ彛锛夛紝鑱斿姩鐧诲綍椤�/鑿滃崟鏍�/缃戦〉鏍囬銆�
+  // 娉細姝ゆ椂 pinia 灏氭湭鍦ㄧ粍浠跺婵�娲伙紝杩欓噷鐢ㄧ函鍑芥暟锛屼笉渚濊禆 store銆�
+  try {
+    const companyConfig = await getCompanyConfig();
+    if (companyConfig) {
+      applyCompanyConfig(companyConfig);
+    }
+  } catch {
+    // 鍚庣涓嶅彲鐢ㄦ垨鏈厤缃紝淇濇寔缂栬瘧鏈熼粯璁�
+  }
+
   // 瀹夎鏉冮檺鎸囦护
   registerAccessDirective(app);
 
diff --git a/src/layouts/auth.vue b/src/layouts/auth.vue
index 1cdccb9..b3386c1 100644
--- a/src/layouts/auth.vue
+++ b/src/layouts/auth.vue
@@ -1,16 +1,31 @@
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, onMounted } from 'vue';
 
 import { AuthPageLayout } from '@vben/layouts';
 import { preferences } from '@vben/preferences';
 
 import { $t } from '#/locales';
+import { useCompanyConfigStore } from '#/store/company-config';
 
 declare const __APP_LOGO__: string;
+
+const companyConfigStore = useCompanyConfigStore();
+
+onMounted(() => {
+  // 鐧诲綍椤靛睍绀� loginTitle锛氬惎鍔ㄥ姞杞借蛋鐨勬槸绾嚱鏁帮紙涓嶅~ store锛夛紝杩涘叆鐧诲綍椤垫椂鎷夊彇涓�娆�
+  companyConfigStore.loadFromServer();
+});
 
 const appName = computed(() => preferences.copyright.companyName);
 const logo = computed(() => __APP_LOGO__ || preferences.logo.source);
 const logoDark = computed(() => preferences.logo.sourceDark);
+// 鐧诲綍椤垫爣棰�/鎻忚堪浼樺厛鍙栧悗绔厤缃紝鏈厤缃椂鍥為��鍒� i18n 榛樿
+const pageTitle = computed(
+  () => companyConfigStore.loginTitle || $t('authentication.pageTitle'),
+);
+const pageDescription = computed(
+  () => companyConfigStore.loginDescription || $t('authentication.pageDesc'),
+);
 </script>
 
 <template>
@@ -18,8 +33,8 @@
     :app-name="appName"
     :logo="logo"
     :logo-dark="logoDark"
-    :page-description="$t('authentication.pageDesc')"
-    :page-title="$t('authentication.pageTitle')"
+    :page-description="pageDescription"
+    :page-title="pageTitle"
   >
     <!-- 鑷畾涔夊伐鍏锋爮 -->
     <!-- <template #toolbar></template> -->
diff --git a/src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue b/src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue
index 8898556..fbaa8a3 100644
--- a/src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue
+++ b/src/packages/@core/ui-kit/shadcn-ui/src/components/segmented/segmented.vue
@@ -5,17 +5,22 @@
 
 import { TabsTrigger } from 'reka-ui';
 
+import { cn } from '../../../../../base/shared/src/utils';
+
 import { Tabs, TabsContent, TabsList } from '../../ui';
 import TabsIndicator from './tabs-indicator.vue';
 
 interface Props {
   defaultValue?: string;
   tabs?: SegmentedItem[];
+  /** 鏄惁鍏佽妯悜婊氬姩锛歵ab 杈冨鏃跺惎鐢紝鍚� tab 鎸夊唴瀹瑰搴︽帓鍒楋紝瓒呭嚭閮ㄥ垎鍙粦鍔� */
+  scrollable?: boolean;
 }
 
 const props = withDefaults(defineProps<Props>(), {
   defaultValue: '',
   tabs: () => [],
+  scrollable: false,
 });
 
 const activeTab = defineModel<string>();
@@ -25,12 +30,18 @@
 });
 
 const tabsStyle = computed(() => {
+  if (props.scrollable) {
+    return undefined;
+  }
   return {
     'grid-template-columns': `repeat(${props.tabs.length}, minmax(0, 1fr))`,
   };
 });
 
 const tabsIndicatorStyle = computed(() => {
+  if (props.scrollable) {
+    return undefined;
+  }
   return {
     width: `${(100 / props.tabs.length).toFixed(0)}%`,
   };
@@ -45,14 +56,22 @@
   <Tabs v-model="activeTab" :default-value="getDefaultValue">
     <TabsList
       :style="tabsStyle"
-      class="bg-accent outline-heavy! relative grid w-full outline-2!"
+      :class="
+        cn(
+          'bg-accent outline-heavy! relative outline-2!',
+          // 鍙粴鍔ㄦā寮忥細宸﹀榻愶紙瑕嗙洊 reka-ui 鐨� justify-center锛屽惁鍒欏唴瀹硅秴瀹芥椂宸︿晶琚尋鍑轰笖鏃犳硶婊氬姩锛夈�佸叏瀹姐�佹í鍚戞粴鍔�
+          scrollable
+            ? 'flex! w-full! overflow-x-auto justify-start!'
+            : 'grid w-full',
+        )
+      "
     >
-      <TabsIndicator :style="tabsIndicatorStyle" />
+      <TabsIndicator v-if="!scrollable" :style="tabsIndicatorStyle" />
       <template v-for="tab in tabs" :key="tab.value">
         <TabsTrigger
           :value="tab.value"
           :class="activeClass(tab.value)"
-          class="hover:text-primary z-20 inline-flex items-center justify-center rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap disabled:pointer-events-none disabled:opacity-50"
+          class="hover:text-primary z-20 inline-flex shrink-0 items-center justify-center rounded-md px-2 py-1 text-sm font-medium whitespace-nowrap disabled:pointer-events-none disabled:opacity-50"
         >
           {{ tab.label }}
         </TabsTrigger>
diff --git a/src/packages/effects/layouts/src/widgets/preferences/blocks/company/company.vue b/src/packages/effects/layouts/src/widgets/preferences/blocks/company/company.vue
new file mode 100644
index 0000000..24fcdf0
--- /dev/null
+++ b/src/packages/effects/layouts/src/widgets/preferences/blocks/company/company.vue
@@ -0,0 +1,151 @@
+<script setup lang="ts">
+import { message } from 'ant-design-vue';
+import { Button, Input, Upload } from 'ant-design-vue';
+import { onMounted, ref } from 'vue';
+
+// 娉ㄦ剰锛氭湰鏂囦欢浣嶄簬 @vben 鐨� packages 鍐呴儴锛屾棤娉曚娇鐢ㄥ簲鐢ㄥ眰鍒悕 #/锛岄渶鐢ㄧ浉瀵硅矾寰勶紙涓庡悓鐩綍 custom.vue 淇濇寔涓�鑷达級
+import { publicUploadFile } from '../../../../../../../../api/system/storage';
+import { $t } from '../../../../../../../locales/src';
+import { useCompanyConfigStore } from '../../../../../../../../store/company-config';
+
+defineOptions({
+  name: 'PreferenceCompanyConfig',
+});
+
+const companyConfigStore = useCompanyConfigStore();
+
+const companyName = ref(companyConfigStore.companyName);
+const loginTitle = ref(companyConfigStore.loginTitle);
+const loginDescription = ref(companyConfigStore.loginDescription);
+const logoUrl = ref(companyConfigStore.logoUrl);
+const uploading = ref(false);
+
+// 鎵撳紑鍖哄潡鏃朵粠鍚庣鎷夊彇鏈�鏂板叕鍙搁厤缃苟鍥炲~琛ㄥ崟锛坰tore 鍒濆�煎彲鑳戒负绌猴紝鍥犲惎鍔ㄥ姞杞借蛋鐨勬槸绾嚱鏁帮級
+onMounted(async () => {
+  await companyConfigStore.loadFromServer();
+  companyName.value = companyConfigStore.companyName;
+  loginTitle.value = companyConfigStore.loginTitle;
+  loginDescription.value = companyConfigStore.loginDescription;
+  logoUrl.value = companyConfigStore.logoUrl;
+});
+
+/** 涓婁紶 logo锛堟案涔呭叕寮�锛屼緵鐧诲綍椤靛厤鐧诲綍灞曠ず锛� */
+async function handleBeforeUpload(file: File) {
+  uploading.value = true;
+  try {
+    const blobs = await publicUploadFile([file]);
+    const blob = blobs?.[0];
+    if (blob?.previewURL || blob?.url) {
+      logoUrl.value = blob.previewURL || blob.url || '';
+      message.success($t('preferences.company.uploadSuccess'));
+    } else {
+      message.error($t('preferences.company.uploadFailed'));
+    }
+  } catch {
+    message.error($t('preferences.company.uploadFailed'));
+  } finally {
+    uploading.value = false;
+  }
+  // 杩斿洖 false锛岄樆姝㈢粍浠堕粯璁や笂浼狅紙宸查�氳繃鎺ュ彛涓婁紶锛�
+  return false;
+}
+
+async function handleSave() {
+  if (!companyName.value.trim()) {
+    message.warning($t('preferences.company.companyNameRequired'));
+    return;
+  }
+  try {
+    await companyConfigStore.save({
+      companyName: companyName.value.trim(),
+      logoUrl: logoUrl.value || undefined,
+      loginTitle: loginTitle.value,
+      loginDescription: loginDescription.value,
+    });
+    message.success($t('preferences.company.saveSuccess'));
+  } catch {
+    message.error($t('preferences.company.saveFailed'));
+  }
+}
+</script>
+
+<template>
+  <div class="flex flex-col">
+    <!-- 鍏徃鍚嶇О -->
+    <div class="my-1 flex w-full items-center justify-between rounded-md px-2 py-1">
+      <span class="flex items-center text-sm">
+        {{ $t('preferences.company.companyName') }}
+      </span>
+      <Input
+        v-model:value="companyName"
+        :placeholder="$t('preferences.company.companyNamePlaceholder')"
+        class="w-41.25"
+        allow-clear
+      />
+    </div>
+
+    <!-- 鐧诲綍椤垫爣棰� -->
+    <div class="my-1 flex w-full items-center justify-between rounded-md px-2 py-1">
+      <span class="flex items-center text-sm">
+        {{ $t('preferences.company.loginTitle') }}
+      </span>
+      <Input
+        v-model:value="loginTitle"
+        :placeholder="$t('preferences.company.loginTitlePlaceholder')"
+        class="w-41.25"
+        allow-clear
+      />
+    </div>
+
+    <!-- 鐧诲綍椤垫弿杩� -->
+    <div class="my-1 flex w-full items-center justify-between rounded-md px-2 py-1">
+      <span class="flex items-center text-sm">
+        {{ $t('preferences.company.loginDescription') }}
+      </span>
+      <Input
+        v-model:value="loginDescription"
+        :placeholder="$t('preferences.company.loginDescriptionPlaceholder')"
+        class="w-41.25"
+        allow-clear
+      />
+    </div>
+
+    <!-- 鍏徃 Logo -->
+    <div class="my-1 flex w-full items-center justify-between rounded-md px-2 py-1">
+      <span class="flex items-center text-sm">
+        {{ $t('preferences.company.logo') }}
+      </span>
+      <div class="flex items-center gap-2">
+        <Upload
+          :show-upload-list="false"
+          accept="image/*"
+          :before-upload="handleBeforeUpload"
+        >
+          <Button size="small" :loading="uploading">
+            {{ $t('preferences.company.uploadLogo') }}
+          </Button>
+        </Upload>
+        <img
+          v-if="logoUrl"
+          :src="logoUrl"
+          alt="company-logo"
+          class="h-9 w-9 rounded border border-border object-contain"
+        />
+        <span v-else class="text-xs text-muted-foreground">
+          {{ $t('preferences.company.logoEmpty') }}
+        </span>
+      </div>
+    </div>
+
+    <div v-if="uploading" class="px-2 pb-1 text-xs text-muted-foreground">
+      {{ $t('preferences.company.uploading') }}
+    </div>
+
+    <!-- 淇濆瓨 -->
+    <div class="mt-3 px-2">
+      <Button type="primary" class="w-full" @click="handleSave">
+        {{ $t('preferences.company.save') }}
+      </Button>
+    </div>
+  </div>
+</template>
diff --git a/src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts b/src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts
index b1b8eac..5b62459 100644
--- a/src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts
+++ b/src/packages/effects/layouts/src/widgets/preferences/blocks/index.ts
@@ -1,4 +1,5 @@
 export { default as Block } from './block.vue';
+export { default as Company } from './company/company.vue';
 export { default as Custom } from './custom/custom.vue';
 export { default as Animation } from './general/animation.vue';
 export { default as General } from './general/general.vue';
diff --git a/src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue b/src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
index 12f4076..5b3a05e 100644
--- a/src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
+++ b/src/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
@@ -43,6 +43,7 @@
   Breadcrumb,
   BuiltinTheme,
   ColorMode,
+  Company,
   Content,
   Copyright,
   Custom,
@@ -256,6 +257,10 @@
       label: $t('preferences.general'),
       value: 'general',
     },
+    {
+      label: $t('preferences.company.title'),
+      value: 'company',
+    },
   ];
 
   if (showCustomTab.value) {
@@ -352,6 +357,7 @@
         <VbenSegmented
           v-model="activeTab"
           :tabs="tabs"
+          scrollable
           :class="{
             'sticky-tabs-header': appEnableStickyPreferencesNavigationBar,
           }"
@@ -526,6 +532,11 @@
               />
             </Block>
           </template>
+          <template #company>
+            <Block :title="$t('preferences.company.title')">
+              <Company />
+            </Block>
+          </template>
           <template #custom>
             <Block :title="customTabTitle">
               <Custom
diff --git a/src/packages/locales/src/langs/en-US/preferences.json b/src/packages/locales/src/langs/en-US/preferences.json
index d027227..853651a 100644
--- a/src/packages/locales/src/langs/en-US/preferences.json
+++ b/src/packages/locales/src/langs/en-US/preferences.json
@@ -229,5 +229,24 @@
         "placeholder": "Please enter the default report title"
       }
     }
+  },
+  "company": {
+    "title": "Company Info",
+    "companyName": "Company Name",
+    "companyNamePlaceholder": "Please enter company name",
+    "companyNameRequired": "Company name is required",
+    "loginTitle": "Login Page Title",
+    "loginTitlePlaceholder": "Enter login page title (optional)",
+    "loginDescription": "Login Page Description",
+    "loginDescriptionPlaceholder": "Enter login page description (optional)",
+    "logo": "Company Logo",
+    "uploadLogo": "Upload Logo",
+    "uploadSuccess": "Logo uploaded successfully",
+    "uploadFailed": "Logo upload failed",
+    "uploading": "Uploading...",
+    "logoEmpty": "No logo set",
+    "save": "Save",
+    "saveSuccess": "Company info saved successfully",
+    "saveFailed": "Failed to save company info"
   }
 }
diff --git a/src/packages/locales/src/langs/zh-CN/preferences.json b/src/packages/locales/src/langs/zh-CN/preferences.json
index 8cbc607..aa36730 100644
--- a/src/packages/locales/src/langs/zh-CN/preferences.json
+++ b/src/packages/locales/src/langs/zh-CN/preferences.json
@@ -229,5 +229,24 @@
         "placeholder": "璇疯緭鍏ユ姤琛ㄩ粯璁ゆ爣棰�"
       }
     }
+  },
+  "company": {
+    "title": "鍏徃淇℃伅",
+    "companyName": "鍏徃鍚嶇О",
+    "companyNamePlaceholder": "璇疯緭鍏ュ叕鍙稿悕绉�",
+    "companyNameRequired": "鍏徃鍚嶇О涓嶈兘涓虹┖",
+    "loginTitle": "鐧诲綍椤垫爣棰�",
+    "loginTitlePlaceholder": "璇疯緭鍏ョ櫥褰曢〉鏍囬锛堝彲閫夛級",
+    "loginDescription": "鐧诲綍椤垫弿杩�",
+    "loginDescriptionPlaceholder": "璇疯緭鍏ョ櫥褰曢〉鎻忚堪锛堝彲閫夛級",
+    "logo": "鍏徃 Logo",
+    "uploadLogo": "涓婁紶 Logo",
+    "uploadSuccess": "Logo 涓婁紶鎴愬姛",
+    "uploadFailed": "Logo 涓婁紶澶辫触",
+    "uploading": "涓婁紶涓�...",
+    "logoEmpty": "鏈缃� Logo",
+    "save": "淇濆瓨",
+    "saveSuccess": "鍏徃淇℃伅淇濆瓨鎴愬姛",
+    "saveFailed": "鍏徃淇℃伅淇濆瓨澶辫触"
   }
 }
diff --git a/src/store/company-config.ts b/src/store/company-config.ts
new file mode 100644
index 0000000..1668f86
--- /dev/null
+++ b/src/store/company-config.ts
@@ -0,0 +1,71 @@
+import { ref } from 'vue';
+import { defineStore } from 'pinia';
+
+import {
+  getCompanyConfig,
+  saveCompanyConfig as saveCompanyConfigApi,
+} from '#/api/system/company-config';
+import { applyCompanyConfig } from '#/utils/company-config';
+
+/**
+ * 鍏徃鍝佺墝淇℃伅鍏ㄥ眬閰嶇疆
+ *
+ * 鍚姩鏃朵粠鍚庣鍔犺浇锛屼繚瀛樻椂鍐欏洖鍚庣锛涘苟灏嗗叕鍙稿悕/logo 鍚屾鍒� preferences锛�
+ * 浠庤�岃仈鍔ㄧ櫥褰曢〉銆佽彍鍗曟爮銆佺綉椤垫爣棰樼瓑鎵�鏈夋秷璐圭偣銆�
+ */
+export const useCompanyConfigStore = defineStore('companyConfig', () => {
+  const companyName = ref('');
+  const logoUrl = ref('');
+  const loginTitle = ref('');
+  const loginDescription = ref('');
+
+  /** 灏嗗叕鍙镐俊鎭簲鐢ㄥ埌 preferences锛岃仈鍔ㄧ櫥褰曢〉/鑿滃崟鏍�/鏍囬 */
+  function applyToPreferences() {
+    applyCompanyConfig({
+      companyName: companyName.value,
+      logoUrl: logoUrl.value,
+    });
+  }
+
+  /** 浠庡悗绔姞杞藉叕鍙搁厤缃苟搴旂敤 */
+  async function loadFromServer() {
+    try {
+      const config = await getCompanyConfig();
+      if (config) {
+        companyName.value = config.companyName ?? '';
+        logoUrl.value = config.logoUrl ?? '';
+        loginTitle.value = config.loginTitle ?? '';
+        loginDescription.value = config.loginDescription ?? '';
+        applyToPreferences();
+      }
+    } catch {
+      // 鍚庣涓嶅彲鐢ㄦ垨鏈厤缃紝淇濇寔缂栬瘧鏈熼粯璁�
+    }
+  }
+
+  /** 淇濆瓨鍏徃閰嶇疆鍒板悗绔苟搴旂敤 */
+  async function save(data: {
+    companyName: string;
+    logoUrl?: string;
+    logoBlobId?: number;
+    loginTitle?: string;
+    loginDescription?: string;
+  }) {
+    await saveCompanyConfigApi(data);
+    companyName.value = data.companyName;
+    logoUrl.value = data.logoUrl ?? '';
+    loginTitle.value = data.loginTitle ?? '';
+    loginDescription.value = data.loginDescription ?? '';
+    applyToPreferences();
+  }
+
+  return {
+    companyName,
+    logoUrl,
+    loginTitle,
+    loginDescription,
+    applyToPreferences,
+    loadFromServer,
+    save,
+  };
+});
diff --git a/src/store/index.ts b/src/store/index.ts
index 269586e..10d8c3d 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -1 +1,2 @@
 export * from './auth';
+export * from './company-config';
diff --git a/src/utils/company-config.ts b/src/utils/company-config.ts
new file mode 100644
index 0000000..4f57dcc
--- /dev/null
+++ b/src/utils/company-config.ts
@@ -0,0 +1,32 @@
+import { preferencesManager } from '@vben/preferences';
+
+/** 鍏徃鍝佺墝淇℃伅锛堜笌鍚庣 system_company_config 琛ㄥ瓧娈靛搴旓級 */
+export interface CompanyConfigData {
+  companyName?: string;
+  logoUrl?: string;
+  loginTitle?: string;
+  loginDescription?: string;
+}
+
+/**
+ * 灏嗗叕鍙稿搧鐗屼俊鎭簲鐢ㄥ埌鍏ㄥ眬锛�
+ * - 鍏徃鍚� 鈫� preferences.app.name / copyright.companyName锛堣仈鍔ㄨ彍鍗曟爮銆佺櫥褰曢〉銆佺綉椤垫爣棰樸�佺増鏉冿級
+ * - logo 鈫� preferences.logo.source锛堣仈鍔ㄧ櫥褰曢〉銆佷晶杈规爮 logo锛�
+ * - 鏈夊叕鍙稿悕鏃讹紝璁剧疆 document.title锛堝熀纭�鏍囬锛宒ynamicTitle 寮�鍚椂璺敱鏍囬鍦ㄦ鍩虹涓婃嫾鎺ワ級
+ */
+export function applyCompanyConfig(config: CompanyConfigData) {
+  const updates: Record<string, any> = {};
+  if (config.logoUrl) {
+    updates.logo = { source: config.logoUrl };
+  }
+  if (config.companyName) {
+    updates.app = { name: config.companyName };
+    updates.copyright = { companyName: config.companyName };
+  }
+  if (Object.keys(updates).length > 0) {
+    preferencesManager.updatePreferences(updates);
+  }
+  if (config.companyName) {
+    document.title = config.companyName;
+  }
+}

--
Gitblit v1.9.3