npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cimom/vben-preferences

v5.6.9

Published

用户偏好设置管理包,提供了一套完整的用户偏好设置管理机制,用于存储和管理用户的主题、语言、布局等偏好设置。该包继承了 `@cimom/vben-core-preferences` 的所有能力,并允许在应用级别覆盖默认偏好设置。

Readme

@cimom/vben-preferences

用户偏好设置管理包,提供了一套完整的用户偏好设置管理机制,用于存储和管理用户的主题、语言、布局等偏好设置。该包继承了 @cimom/vben-core-preferences 的所有能力,并允许在应用级别覆盖默认偏好设置。

安装

# 进入目标应用目录,例如 apps/xxxx-app
# cd apps/xxxx-app
pnpm add @cimom/vben-preferences

基本使用

获取和更新偏好设置

import { usePreferences } from '@cimom/vben-preferences';

// 在组件中使用
const { preferences, updatePreferences, resetPreferences } = usePreferences();

// 获取当前偏好设置
console.log('当前主题:', preferences.theme);
console.log('当前语言:', preferences.locale);
console.log('菜单模式:', preferences.menuMode);

// 更新偏好设置
function changeTheme(theme: 'light' | 'dark') {
  updatePreferences({
    theme,
  });
}

// 更新多个偏好设置
function updateSettings(settings) {
  updatePreferences({
    theme: settings.theme,
    locale: settings.locale,
    menuMode: settings.menuMode,
  });
}

// 重置为默认偏好设置
function resetToDefaults() {
  resetPreferences();
}

在模板中使用

<template>
  <div>
    <h2>用户偏好设置</h2>

    <div class="setting-item">
      <span>主题模式:</span>
      <select v-model="currentTheme" @change="handleThemeChange">
        <option value="light">浅色主题</option>
        <option value="dark">深色主题</option>
        <option value="auto">跟随系统</option>
      </select>
    </div>

    <div class="setting-item">
      <span>语言:</span>
      <select v-model="currentLocale" @change="handleLocaleChange">
        <option value="zh-CN">简体中文</option>
        <option value="en-US">English</option>
      </select>
    </div>

    <div class="setting-item">
      <span>菜单模式:</span>
      <select v-model="currentMenuMode" @change="handleMenuModeChange">
        <option value="vertical">垂直菜单</option>
        <option value="horizontal">水平菜单</option>
        <option value="inline">内嵌菜单</option>
      </select>
    </div>

    <button @click="resetToDefaults">重置为默认设置</button>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { usePreferences } from '@cimom/vben-preferences';

const { preferences, updatePreferences, resetPreferences } = usePreferences();

// 计算属性,用于双向绑定
const currentTheme = computed({
  get: () => preferences.theme,
  set: (value) => updatePreferences({ theme: value }),
});

const currentLocale = computed({
  get: () => preferences.locale,
  set: (value) => updatePreferences({ locale: value }),
});

const currentMenuMode = computed({
  get: () => preferences.menuMode,
  set: (value) => updatePreferences({ menuMode: value }),
});

// 处理变更
function handleThemeChange() {
  // 可以在这里添加额外的逻辑
  console.log('主题已更改为:', currentTheme.value);
}

function handleLocaleChange() {
  console.log('语言已更改为:', currentLocale.value);
}

function handleMenuModeChange() {
  console.log('菜单模式已更改为:', currentMenuMode.value);
}

// 重置为默认设置
function resetToDefaults() {
  resetPreferences();
}
</script>

可用偏好设置

以下是可用的偏好设置项及其类型:

interface Preferences {
  /**
   * 主题模式
   * - light: 浅色主题
   * - dark: 深色主题
   * - auto: 跟随系统
   */
  theme: 'light' | 'dark' | 'auto';

  /**
   * 语言设置
   */
  locale: string;

  /**
   * 菜单模式
   * - vertical: 垂直菜单
   * - horizontal: 水平菜单
   * - inline: 内嵌菜单
   */
  menuMode: 'vertical' | 'horizontal' | 'inline';

  /**
   * 菜单主题
   * - light: 浅色主题
   * - dark: 深色主题
   */
  menuTheme: 'light' | 'dark';

  /**
   * 是否固定头部
   */
  fixedHeader: boolean;

  /**
   * 是否固定侧边栏
   */
  fixedSidebar: boolean;

  /**
   * 是否显示标签页
   */
  showTabs: boolean;

  /**
   * 是否显示面包屑
   */
  showBreadcrumb: boolean;

  /**
   * 是否显示页脚
   */
  showFooter: boolean;

  /**
   * 内容区域宽度模式
   * - fixed: 固定宽度
   * - full: 流式宽度
   */
  contentWidth: 'fixed' | 'full';

  /**
   * 是否折叠菜单
   */
  collapsed: boolean;

  /**
   * 主色调
   */
  primaryColor: string;

  /**
   * 是否开启色弱模式
   */
  colorWeak: boolean;

  /**
   * 是否开启灰色模式
   */
  grayMode: boolean;

  /**
   * 自定义扩展设置
   */
  [key: string]: any;
}

高级用法

覆盖默认偏好设置

如果你想为所有应用提供统一的默认偏好设置,可以使用 defineOverridesPreferences 函数:

// 在应用入口文件中
import { defineOverridesPreferences } from '@cimom/vben-preferences';

// 定义覆盖默认偏好设置
defineOverridesPreferences({
  theme: 'light',
  locale: 'zh-CN',
  menuMode: 'vertical',
  menuTheme: 'light',
  fixedHeader: true,
  fixedSidebar: true,
  showTabs: true,
  showBreadcrumb: true,
  showFooter: false,
  contentWidth: 'fixed',
  collapsed: false,
  primaryColor: '#1890ff',
  colorWeak: false,
  grayMode: false,
});

监听偏好设置变化

import { watch } from 'vue';
import { usePreferences } from '@cimom/vben-preferences';

const { preferences } = usePreferences();

// 监听整个偏好设置对象的变化
watch(
  () => preferences,
  (newPreferences) => {
    console.log('偏好设置已更改:', newPreferences);
  },
  { deep: true },
);

// 监听特定偏好设置的变化
watch(
  () => preferences.theme,
  (newTheme) => {
    console.log('主题已更改为:', newTheme);
    // 更新 HTML 标签的类名以应用主题
    if (newTheme === 'dark') {
      document.documentElement.classList.add('dark');
    } else {
      document.documentElement.classList.remove('dark');
    }
  },
);

创建偏好设置管理组件

<!-- PreferencesPanel.vue -->
<template>
  <div class="preferences-panel">
    <h2>系统设置</h2>

    <div class="section">
      <h3>外观</h3>

      <div class="setting-item">
        <span>主题模式:</span>
        <div class="theme-options">
          <div
            v-for="theme in themeOptions"
            :key="theme.value"
            class="theme-option"
            :class="{ active: preferences.theme === theme.value }"
            @click="updatePreferences({ theme: theme.value })"
          >
            <div class="theme-preview" :class="theme.value"></div>
            <span>{{ theme.label }}</span>
          </div>
        </div>
      </div>

      <div class="setting-item">
        <span>主色调:</span>
        <div class="color-picker">
          <div
            v-for="color in colorOptions"
            :key="color.value"
            class="color-option"
            :style="{ backgroundColor: color.value }"
            :class="{ active: preferences.primaryColor === color.value }"
            @click="updatePreferences({ primaryColor: color.value })"
          ></div>
        </div>
      </div>

      <div class="setting-item">
        <span>特殊模式:</span>
        <div class="checkbox-group">
          <label>
            <input
              type="checkbox"
              :checked="preferences.colorWeak"
              @change="updatePreferences({ colorWeak: !preferences.colorWeak })"
            />
            色弱模式
          </label>

          <label>
            <input
              type="checkbox"
              :checked="preferences.grayMode"
              @change="updatePreferences({ grayMode: !preferences.grayMode })"
            />
            灰色模式
          </label>
        </div>
      </div>
    </div>

    <div class="section">
      <h3>布局</h3>

      <div class="setting-item">
        <span>菜单模式:</span>
        <div class="radio-group">
          <label v-for="mode in menuModeOptions" :key="mode.value">
            <input
              type="radio"
              :value="mode.value"
              :checked="preferences.menuMode === mode.value"
              @change="updatePreferences({ menuMode: mode.value })"
            />
            {{ mode.label }}
          </label>
        </div>
      </div>

      <div class="setting-item">
        <span>菜单主题:</span>
        <div class="radio-group">
          <label v-for="theme in menuThemeOptions" :key="theme.value">
            <input
              type="radio"
              :value="theme.value"
              :checked="preferences.menuTheme === theme.value"
              @change="updatePreferences({ menuTheme: theme.value })"
            />
            {{ theme.label }}
          </label>
        </div>
      </div>

      <div class="setting-item">
        <span>内容区域:</span>
        <div class="radio-group">
          <label v-for="width in contentWidthOptions" :key="width.value">
            <input
              type="radio"
              :value="width.value"
              :checked="preferences.contentWidth === width.value"
              @change="updatePreferences({ contentWidth: width.value })"
            />
            {{ width.label }}
          </label>
        </div>
      </div>

      <div class="setting-item">
        <span>固定选项:</span>
        <div class="checkbox-group">
          <label>
            <input
              type="checkbox"
              :checked="preferences.fixedHeader"
              @change="
                updatePreferences({ fixedHeader: !preferences.fixedHeader })
              "
            />
            固定头部
          </label>

          <label>
            <input
              type="checkbox"
              :checked="preferences.fixedSidebar"
              @change="
                updatePreferences({ fixedSidebar: !preferences.fixedSidebar })
              "
            />
            固定侧边栏
          </label>
        </div>
      </div>

      <div class="setting-item">
        <span>显示选项:</span>
        <div class="checkbox-group">
          <label>
            <input
              type="checkbox"
              :checked="preferences.showTabs"
              @change="updatePreferences({ showTabs: !preferences.showTabs })"
            />
            显示标签页
          </label>

          <label>
            <input
              type="checkbox"
              :checked="preferences.showBreadcrumb"
              @change="
                updatePreferences({
                  showBreadcrumb: !preferences.showBreadcrumb,
                })
              "
            />
            显示面包屑
          </label>

          <label>
            <input
              type="checkbox"
              :checked="preferences.showFooter"
              @change="
                updatePreferences({ showFooter: !preferences.showFooter })
              "
            />
            显示页脚
          </label>
        </div>
      </div>
    </div>

    <div class="section">
      <h3>语言</h3>

      <div class="setting-item">
        <span>系统语言:</span>
        <select
          :value="preferences.locale"
          @change="updatePreferences({ locale: $event.target.value })"
        >
          <option value="zh-CN">简体中文</option>
          <option value="en-US">English</option>
        </select>
      </div>
    </div>

    <div class="actions">
      <button @click="resetPreferences">恢复默认设置</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { usePreferences } from '@cimom/vben-preferences';

const { preferences, updatePreferences, resetPreferences } = usePreferences();

// 选项数据
const themeOptions = [
  { label: '浅色', value: 'light' },
  { label: '深色', value: 'dark' },
  { label: '跟随系统', value: 'auto' },
];

const colorOptions = [
  { value: '#1890ff' }, // 蓝色
  { value: '#f5222d' }, // 红色
  { value: '#fa8c16' }, // 橙色
  { value: '#faad14' }, // 黄色
  { value: '#52c41a' }, // 绿色
  { value: '#13c2c2' }, // 青色
  { value: '#722ed1' }, // 紫色
];

const menuModeOptions = [
  { label: '垂直', value: 'vertical' },
  { label: '水平', value: 'horizontal' },
  { label: '内嵌', value: 'inline' },
];

const menuThemeOptions = [
  { label: '浅色', value: 'light' },
  { label: '深色', value: 'dark' },
];

const contentWidthOptions = [
  { label: '固定宽度', value: 'fixed' },
  { label: '流式宽度', value: 'full' },
];
</script>

<style scoped>
.preferences-panel {
  padding: 16px;
  max-width: 800px;
  margin: 0 auto;
}

.section {
  margin-bottom: 24px;
  border-bottom: 1px solid #f0f0f0;
  padding-bottom: 16px;
}

.setting-item {
  margin-bottom: 16px;
  display: flex;
  align-items: flex-start;
}

.setting-item > span {
  width: 100px;
  text-align: right;
  margin-right: 16px;
  flex-shrink: 0;
}

.theme-options {
  display: flex;
  gap: 16px;
}

.theme-option {
  text-align: center;
  cursor: pointer;
}

.theme-option.active {
  border: 2px solid #1890ff;
  border-radius: 4px;
}

.theme-preview {
  width: 48px;
  height: 48px;
  border-radius: 4px;
  margin-bottom: 8px;
}

.theme-preview.light {
  background-color: #fff;
  border: 1px solid #f0f0f0;
}

.theme-preview.dark {
  background-color: #141414;
}

.theme-preview.auto {
  background: linear-gradient(to right, #fff 50%, #141414 50%);
  border: 1px solid #f0f0f0;
}

.color-picker {
  display: flex;
  gap: 8px;
}

.color-option {
  width: 20px;
  height: 20px;
  border-radius: 4px;
  cursor: pointer;
}

.color-option.active {
  box-shadow:
    0 0 0 2px #fff,
    0 0 0 4px #1890ff;
}

.checkbox-group,
.radio-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.actions {
  margin-top: 24px;
  text-align: center;
}

button {
  padding: 8px 16px;
  background-color: #1890ff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #40a9ff;
}

select {
  padding: 4px 8px;
  border-radius: 4px;
  border: 1px solid #d9d9d9;
}
</style>

与其他包的集成

与 @cimom/vben-effects-hooks 集成

import { watch } from 'vue';
import { usePreferences } from '@cimom/vben-preferences';
import { useDesignTokens } from '@cimom/vben-effects-hooks';

// 创建主题钩子
export function useThemeManager() {
  const { preferences, updatePreferences } = usePreferences();
  const { primaryColor } = useDesignTokens();

  // 监听主题变化
  watch(
    () => preferences.theme,
    (theme) => {
      // 更新 HTML 类名
      const htmlEl = document.documentElement;

      if (theme === 'dark') {
        htmlEl.classList.add('dark');
        htmlEl.classList.remove('light');
      } else if (theme === 'light') {
        htmlEl.classList.add('light');
        htmlEl.classList.remove('dark');
      } else if (theme === 'auto') {
        // 检测系统主题
        const prefersDark = window.matchMedia(
          '(prefers-color-scheme: dark)',
        ).matches;
        if (prefersDark) {
          htmlEl.classList.add('dark');
          htmlEl.classList.remove('light');
        } else {
          htmlEl.classList.add('light');
          htmlEl.classList.remove('dark');
        }

        // 监听系统主题变化
        window
          .matchMedia('(prefers-color-scheme: dark)')
          .addEventListener('change', (e) => {
            if (preferences.theme === 'auto') {
              if (e.matches) {
                htmlEl.classList.add('dark');
                htmlEl.classList.remove('light');
              } else {
                htmlEl.classList.add('light');
                htmlEl.classList.remove('dark');
              }
            }
          });
      }
    },
    { immediate: true },
  );

  // 监听色弱模式变化
  watch(
    () => preferences.colorWeak,
    (colorWeak) => {
      const htmlEl = document.documentElement;
      if (colorWeak) {
        htmlEl.classList.add('color-weak');
      } else {
        htmlEl.classList.remove('color-weak');
      }
    },
    { immediate: true },
  );

  // 监听灰色模式变化
  watch(
    () => preferences.grayMode,
    (grayMode) => {
      const htmlEl = document.documentElement;
      if (grayMode) {
        htmlEl.classList.add('gray-mode');
      } else {
        htmlEl.classList.remove('gray-mode');
      }
    },
    { immediate: true },
  );

  return {
    preferences,
    updatePreferences,
    primaryColor,
  };
}

与 @cimom/vben-locales 集成

import { watch } from 'vue';
import { usePreferences } from '@cimom/vben-preferences';
import { useI18n } from '@cimom/vben-locales';

// 创建语言管理钩子
export function useLanguageManager() {
  const { preferences, updatePreferences } = usePreferences();
  const { locale, setLocale, t } = useI18n();

  // 监听语言变化
  watch(
    () => preferences.locale,
    (newLocale) => {
      // 更新 i18n 语言设置
      if (newLocale !== locale.value) {
        setLocale(newLocale);
      }
    },
    { immediate: true },
  );

  // 更新语言并同时更新偏好设置
  function changeLanguage(lang: string) {
    updatePreferences({ locale: lang });
  }

  return {
    locale,
    changeLanguage,
    t,
  };
}

注意事项

  1. 偏好设置会自动保存到浏览器的 localStorage 中,在用户下次访问时会自动恢复。
  2. 更新偏好设置时,只需要提供要更改的属性,不需要提供完整的偏好设置对象。
  3. 如果需要在多个应用中共享相同的默认偏好设置,应该使用 defineOverridesPreferences 函数,而不是修改 @cimom/vben-core-preferences 中的默认设置。
  4. 偏好设置的变更是响应式的,可以使用 Vue 的 watchcomputed 来监听和响应变化。

常见问题

偏好设置没有正确保存

确保没有禁用浏览器的 localStorage 功能,可以通过以下方式检查:

function checkLocalStorage() {
  try {
    localStorage.setItem('test', 'test');
    localStorage.removeItem('test');
    return true;
  } catch (e) {
    return false;
  }
}

if (!checkLocalStorage()) {
  console.warn('localStorage 不可用,偏好设置将无法保存');
}

主题切换不生效

确保在应用中正确应用了主题类名:

import { watch } from 'vue';
import { usePreferences } from '@cimom/vben-preferences';

const { preferences } = usePreferences();

// 在应用初始化时设置主题
watch(
  () => preferences.theme,
  (theme) => {
    if (theme === 'dark') {
      document.documentElement.classList.add('dark');
      document.documentElement.classList.remove('light');
    } else {
      document.documentElement.classList.add('light');
      document.documentElement.classList.remove('dark');
    }
  },
  { immediate: true },
);

自定义扩展偏好设置

如果需要添加自定义的偏好设置项,可以通过类型扩展:

import { Preferences, usePreferences } from '@cimom/vben-preferences';

// 扩展偏好设置类型
declare module '@cimom/vben-preferences' {
  interface Preferences {
    // 添加自定义偏好设置
    customSetting1: string;
    customSetting2: boolean;
  }
}

// 使用扩展后的偏好设置
const { preferences, updatePreferences } = usePreferences();

// 更新自定义偏好设置
updatePreferences({
  customSetting1: 'value',
  customSetting2: true,
});