ModeSwitcher.vue 5.18 KB
<template>
  <div 
    class="mode-switcher-wrapper _1edpcnw6" 
    role="radiogroup" 
    aria-required="false" 
    dir="ltr" 
    data-icon-mode="true"
    tabindex="0"
    style="outline: none;"
  >
    <el-segmented
      v-model="internalValue"
      :options="options"
      class="affine-mode-switcher"
      @change="handleChange"
    >
      <template #default="{ item }">
        <div class="mode-item" :title="item.label">
          <div class="icon-wrapper">
            <el-icon v-if="item.value === 'page'"><Document /></el-icon>
            <div v-else-if="item.value === 'edgeless'" class="custom-icon edgeless-icon">
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="16" height="16">
                <g>
                  <g transform="matrix(10,0,0,10,0,-1)">
                    <g transform="matrix(1,0,0,1,6.818,6.618)">
                      <path fill="none" stroke="currentColor" stroke-miterlimit="4" stroke-width="1.5" d="M0-2.618c1.446 0 2.618 1.172 2.618 2.618s-1.172 2.618-2.618 2.618-2.618-1.172-2.618-2.618 1.172-2.618 2.618-2.618z"></path>
                    </g>
                  </g>
                  <g transform="matrix(10,0,0,10,0,-0.5)">
                    <g transform="matrix(1,0,0,1,6.818,17.091)">
                      <path fill="none" stroke="currentColor" stroke-miterlimit="4" stroke-width="1.5" d="M0-2.618c1.446 0 2.618 1.172 2.618 2.618s-1.172 2.618-2.618 2.618-2.618-1.172-2.618-2.618 1.172-2.618 2.618-2.618z"></path>
                    </g>
                  </g>
                  <g transform="matrix(10,0,0,10,0,2)">
                    <g transform="matrix(1,0,0,1,17.344,16.829)">
                      <path fill="none" stroke="currentColor" stroke-width="1.5" d="M-2.357-2.357h4.714v4.714h-4.714z"></path>
                    </g>
                  </g>
                  <g transform="matrix(10,0,0,10,0,0)">
                    <path fill="none" stroke="currentColor" stroke-miterlimit="4" stroke-width="1.5" d="M6.818 9.76v4.189"></path>
                  </g>
                  <g transform="matrix(11,0,0,11,-16.35,-10.5)">
                    <g transform="matrix(1,0,0,1,13.626,10.441)">
                      <path fill="none" stroke="currentColor" stroke-miterlimit="4" stroke-width="1.5" d="M3.665 3.299c0-1.885-.726-3.847-2.095-5.027-2.094-1.571-3.36-1.571-5.235-1.571"></path>
                    </g>
                  </g>
                </g>
              </svg>
            </div>
            <i v-else :class="item.icon"></i>
          </div>
          <span class="mode-label">{{ item.label }}</span>
        </div>
      </template>
    </el-segmented>
  </div>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { Document } from '@element-plus/icons-vue';

const { t } = useI18n();

const props = defineProps<{
  modelValue: 'page' | 'edgeless';
}>();

const emit = defineEmits<{
  (e: 'update:modelValue', value: 'page' | 'edgeless'): void;
  (e: 'change', value: 'page' | 'edgeless'): void;
}>();

const internalValue = ref(props.modelValue);

const options = [
  { 
    label: t('affine.askAi.modes.page') || '文档', 
    value: 'page', 
    icon: 'fas fa-file-alt' 
  },
  { 
    label: t('affine.askAi.modes.edgeless') || '白板', 
    value: 'edgeless', 
    icon: 'fas fa-vector-square' 
  }
];

watch(() => props.modelValue, (val) => {
  internalValue.value = val;
});

const handleChange = (val: any) => {
  emit('update:modelValue', val);
  emit('change', val);
};
</script>

<style scoped lang="scss">
.mode-switcher-wrapper {
  /* Using variables from AFFiNE as provided */
  --_1edpcnw1: 8px; /* Border Radius */

  display: inline-flex;
  align-items: center;
  background: var(--affine-mode-switcher-bg, #f4f4f5);
  padding: 2px;
  border-radius: var(--_1edpcnw1);
  height: 32px;
}

.affine-mode-switcher {
  --el-segmented-bg-color: transparent;
  --el-segmented-item-selected-bg-color: #ffffff;
  --el-segmented-item-selected-color: #1e6fff;
  --el-segmented-item-hover-bg-color: rgba(0, 0, 0, 0.05);
  --el-segmented-padding: 0;
  
  background-color: transparent;
  border: none;
}

.mode-item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 8px;
  height: 28px;
  font-size: 13px;
  font-weight: 500;
  border-radius: 6px;
  transition: all 0.2s ease;

  .icon-wrapper {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
  }

  .el-icon {
    font-size: 16px;
  }

  .custom-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    color: inherit;

    svg {
      width: 100%;
      height: 100%;
    }
  }

  .mode-label {
    white-space: nowrap;
  }
}

:deep(.el-segmented__item) {
  padding: 0;
}

:deep(.el-segmented__item-selected) {
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
  border-radius: 6px;
}

/* 深色主题适配 */
:global([data-theme='dark']) {
  .mode-switcher-wrapper {
    background: #2c2c2e;
  }
  
  .affine-mode-switcher {
    --el-segmented-item-selected-bg-color: #444;
    --el-segmented-item-selected-color: #409eff;
    --el-segmented-item-hover-bg-color: rgba(255, 255, 255, 0.1);
  }
}
</style>