Przeglądaj źródła

add localiazed

localiazed
unknown 1 tydzień temu
rodzic
commit
e1f10fec95
10 zmienionych plików z 410 dodań i 88 usunięć
  1. +2
    -1
      model/ability.go
  2. +12
    -0
      model/model_meta.go
  3. +3
    -1
      model/pricing.go
  4. +140
    -0
      web/src/components/common/ui/HorizontalFilterRow.jsx
  5. +7
    -6
      web/src/components/table/model-pricing/view/card/PricingCardView.jsx
  6. +11
    -0
      web/src/hooks/model-pricing/useModelPricingData.jsx
  7. +26
    -0
      web/src/hooks/model-pricing/usePricingFilterCounts.js
  8. +197
    -0
      web/src/pages/Home/HomePricingFilters.jsx
  9. +7
    -80
      web/src/pages/Home/index.jsx
  10. +5
    -0
      web/vite.config.js

+ 2
- 1
model/ability.go Wyświetl plik

@@ -33,7 +33,8 @@ func GetAllEnableAbilityWithChannels() ([]AbilityWithChannel, error) {
err := DB.Table("abilities").
Select("abilities.*, channels.type as channel_type").
Joins("left join channels on abilities.channel_id = channels.id").
Where("abilities.enabled = ?", true).
Joins("left join models on abilities.model = models.model_name").
Where("abilities.enabled = ? and models.model_name is NOT null", true).
Scan(&abilities).Error
return abilities, err
}


+ 12
- 0
model/model_meta.go Wyświetl plik

@@ -15,6 +15,17 @@ const (
NameRuleSuffix
)

const (
ModelTypeChat = 1 + iota
ModelTypeImage
ModelTypeAudio
ModelTypeVideo
ModelTypeEmbedding
ModelTypeRerank
ModelTypeVision
ModelTypeOther
)

type BoundChannel struct {
Name string `json:"name"`
Type int `json:"type"`
@@ -26,6 +37,7 @@ type Model struct {
Description string `json:"description,omitempty" gorm:"type:text"`
Icon string `json:"icon,omitempty" gorm:"type:varchar(128)"`
Tags string `json:"tags,omitempty" gorm:"type:varchar(255)"`
Type int `json:"type" gorm:"default:0"`
VendorID int `json:"vendor_id,omitempty" gorm:"index"`
Endpoints string `json:"endpoints,omitempty" gorm:"type:text"`
Status int `json:"status" gorm:"default:1"`


+ 3
- 1
model/pricing.go Wyświetl plik

@@ -28,6 +28,7 @@ type Pricing struct {
EnableGroup []string `json:"enable_groups"`
SupportedEndpointTypes []constant.EndpointType `json:"supported_endpoint_types"`
PricingVersion string `json:"pricing_version,omitempty"`
Type int `json:"type"`
}

type PricingVendor struct {
@@ -279,13 +280,14 @@ func updatePricing() {
// 补充模型元数据(描述、标签、供应商、状态)
if meta, ok := metaMap[model]; ok {
// 若模型被禁用(status!=1),则直接跳过,不返回给前端
if meta.Status != 1 {
if meta.Status != 1 || meta.Type == 0 {
continue
}
pricing.Description = meta.Description
pricing.Icon = meta.Icon
pricing.Tags = meta.Tags
pricing.VendorID = meta.VendorID
pricing.Type = meta.Type
}
modelPrice, findPrice := ratio_setting.GetModelPrice(model, false)
if findPrice {


+ 140
- 0
web/src/components/common/ui/HorizontalFilterRow.jsx Wyświetl plik

@@ -0,0 +1,140 @@
/*
Copyright (C) 2025 QuantumNous

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

For commercial licensing, please contact support@quantumnous.com
*/

import React, { useRef } from 'react';
import { Button, Tag, Skeleton } from '@douyinfe/semi-ui';
import { IconChevronLeft, IconChevronRight } from '@douyinfe/semi-icons';
import { useMinimumLoadingTime } from '../../../hooks/common/useMinimumLoadingTime';

/**
* 横向筛选项行,通过左右箭头翻页
* @param {string} title 标题
* @param {Array<{value:any,label:string,icon?:React.ReactNode,tagCount?:number}>} items 选项列表
* @param {*} activeValue 当前选中值
* @param {(value:any)=>void} onChange 选择回调
* @param {boolean} loading 加载中
* @param {Function} t i18n
*/
const HorizontalFilterRow = ({
title,
items = [],
activeValue,
onChange,
loading = false,
t = (v) => v,
}) => {
const scrollRef = useRef(null);
const showSkeleton = useMinimumLoadingTime(loading);

const handleScroll = (dir) => {
const el = scrollRef.current;
if (!el) return;
const step = el.clientWidth * 0.8;
el.scrollBy({
left: dir === 'left' ? -step : step,
behavior: 'smooth',
});
};

if (showSkeleton) {
return (
<div className='mb-6'>
<Skeleton.Title active style={{ width: 60, marginBottom: 8 }} />
<div className='flex gap-2'>
{[1, 2, 3, 4, 5].map((i) => (
<Skeleton.Button
key={i}
active
style={{ width: 80, height: 32 }}
/>
))}
</div>
</div>
);
}

return (
<div className='mb-6 last:mb-0'>
<div className='text-sm font-medium text-semi-color-text-1 mb-3'>
{title}
</div>
<div className='flex items-center gap-2'>
<Button
type='tertiary'
theme='borderless'
icon={<IconChevronLeft />}
onClick={() => handleScroll('left')}
className='flex-shrink-0'
aria-label={t('向左')}
/>
<div
ref={scrollRef}
className='flex-1 min-w-0 flex gap-2 overflow-x-auto overflow-y-hidden scroll-smooth scrollbar-hide'
style={{
scrollbarWidth: 'none',
msOverflowStyle: 'none',
WebkitOverflowScrolling: 'touch',
}}
>
{items.map((item) => {
const isDisabled =
item.disabled ||
(typeof item.tagCount === 'number' && item.tagCount === 0);
const isActive = activeValue === item.value;

return (
<Button
key={item.value}
onClick={() => !isDisabled && onChange(item.value)}
theme={isActive ? 'solid' : 'borderless'}
type={isActive ? 'primary' : 'tertiary'}
disabled={isDisabled}
className='flex-shrink-0 !rounded-full'
>
<span className='flex items-center gap-2'>
{item.icon}
<span>{item.label}</span>
{item.tagCount !== undefined && (
<Tag
color={isActive ? 'white' : 'grey'}
shape='circle'
size='small'
>
{item.tagCount}
</Tag>
)}
</span>
</Button>
);
})}
</div>
<Button
type='tertiary'
theme='borderless'
icon={<IconChevronRight />}
onClick={() => handleScroll('right')}
className='flex-shrink-0'
aria-label={t('向右')}
/>
</div>
</div>
);
};

export default HorizontalFilterRow;

+ 7
- 6
web/src/components/table/model-pricing/view/card/PricingCardView.jsx Wyświetl plik

@@ -74,13 +74,14 @@ const PricingCardView = ({
selectedRowKeys = [],
setSelectedRowKeys,
openModelDetail,
gridColsClass = 'grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3',
showPagination = true,
}) => {
const showSkeleton = useMinimumLoadingTime(loading);
const startIndex = (currentPage - 1) * pageSize;
const paginatedModels = filteredModels.slice(
startIndex,
startIndex + pageSize,
);
const paginatedModels = showPagination
? filteredModels.slice(startIndex, startIndex + pageSize)
: filteredModels;
const getModelKey = (model) => model.key ?? model.model_name ?? model.id;
const isMobile = useIsMobile();

@@ -234,7 +235,7 @@ const PricingCardView = ({

return (
<div className='px-2 pt-2'>
<div className='grid grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 gap-4'>
<div className={`grid ${gridColsClass} gap-4`}>
{paginatedModels.map((model, index) => {
const modelKey = getModelKey(model);
const isSelected = selectedRowKeys.includes(modelKey);
@@ -357,7 +358,7 @@ const PricingCardView = ({
</div>

{/* 分页 */}
{filteredModels.length > 0 && (
{showPagination && filteredModels.length > 0 && (
<div className='flex justify-center mt-6 py-4 border-t pricing-pagination-divider'>
<Pagination
currentPage={currentPage}


+ 11
- 0
web/src/hooks/model-pricing/useModelPricingData.jsx Wyświetl plik

@@ -39,6 +39,7 @@ export const useModelPricingData = () => {
const [filterEndpointType, setFilterEndpointType] = useState('all'); // 端点类型筛选: 'all' | string
const [filterVendor, setFilterVendor] = useState('all'); // 供应商筛选: 'all' | 'unknown' | string
const [filterTag, setFilterTag] = useState('all'); // 模型标签筛选: 'all' | string
const [filterType, setFilterType] = useState('all'); // 模型类型筛选: 'all' | number (model_meta.ModelType)
const [pageSize, setPageSize] = useState(20);
const [currentPage, setCurrentPage] = useState(1);
const [currency, setCurrency] = useState('USD');
@@ -135,6 +136,12 @@ export const useModelPricingData = () => {
});
}

// 模型类型筛选(pricing.Type / model.Type)
if (filterType !== 'all') {
const typeNum = Number(filterType);
result = result.filter((model) => model.type === typeNum);
}

// 搜索筛选
if (searchValue.length > 0) {
const searchTerm = searchValue.toLowerCase();
@@ -159,6 +166,7 @@ export const useModelPricingData = () => {
filterEndpointType,
filterVendor,
filterTag,
filterType,
]);

const rowSelection = useMemo(
@@ -321,6 +329,7 @@ export const useModelPricingData = () => {
filterEndpointType,
filterVendor,
filterTag,
filterType,
searchValue,
]);

@@ -350,6 +359,8 @@ export const useModelPricingData = () => {
setFilterVendor,
filterTag,
setFilterTag,
filterType,
setFilterType,
pageSize,
setPageSize,
currentPage,


+ 26
- 0
web/src/hooks/model-pricing/usePricingFilterCounts.js Wyświetl plik

@@ -37,6 +37,7 @@ export const usePricingFilterCounts = ({
filterEndpointType = 'all',
filterVendor = 'all',
filterTag = 'all',
filterType = 'all',
searchValue = '',
}) => {
// 均使用同一份模型列表,避免创建新引用
@@ -84,6 +85,12 @@ export const usePricingFilterCounts = ({
if (!tagsArr.includes(filterTag.toLowerCase())) return false;
}

// 模型类型(pricing.Type / model.Type)
if (!ignore.includes('type') && filterType !== 'all') {
const typeNum = Number(filterType);
if (model.type !== typeNum) return false;
}

// 搜索
if (!ignore.includes('search') && searchValue) {
const term = searchValue.toLowerCase();
@@ -112,6 +119,7 @@ export const usePricingFilterCounts = ({
filterEndpointType,
filterVendor,
filterTag,
filterType,
searchValue,
],
);
@@ -124,6 +132,7 @@ export const usePricingFilterCounts = ({
filterQuotaType,
filterVendor,
filterTag,
filterType,
searchValue,
],
);
@@ -136,6 +145,7 @@ export const usePricingFilterCounts = ({
filterQuotaType,
filterEndpointType,
filterTag,
filterType,
searchValue,
],
);
@@ -148,6 +158,20 @@ export const usePricingFilterCounts = ({
filterQuotaType,
filterEndpointType,
filterVendor,
filterType,
searchValue,
],
);

const typeModels = useMemo(
() => allModels.filter((m) => matchesFilters(m, ['type'])),
[
allModels,
filterGroup,
filterQuotaType,
filterEndpointType,
filterVendor,
filterTag,
searchValue,
],
);
@@ -160,6 +184,7 @@ export const usePricingFilterCounts = ({
filterEndpointType,
filterVendor,
filterTag,
filterType,
searchValue,
],
);
@@ -170,5 +195,6 @@ export const usePricingFilterCounts = ({
vendorModels,
groupCountModels,
tagModels,
typeModels,
};
};

+ 197
- 0
web/src/pages/Home/HomePricingFilters.jsx Wyświetl plik

@@ -0,0 +1,197 @@
/*
Copyright (C) 2025 QuantumNous

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

For commercial licensing, please contact support@quantumnous.com
*/

import React, { useMemo, useState } from 'react';
import { ImagePreview } from '@douyinfe/semi-ui';
import HorizontalFilterRow from '../../components/common/ui/HorizontalFilterRow';
import PricingCardView from '../../components/table/model-pricing/view/card/PricingCardView';
import ModelDetailSideSheet from '../../components/table/model-pricing/modal/ModelDetailSideSheet';
import { useModelPricingData } from '../../hooks/model-pricing/useModelPricingData';
import { usePricingFilterCounts } from '../../hooks/model-pricing/usePricingFilterCounts';
import { getLobeHubIcon } from '../../helpers';

// 与 model_meta.go ModelType 常量一致:1=Chat, 2=Image, 3=Audio, 4=Video, 5=Embedding, 6=Rerank, 7=Vision, 8=Other
const MODEL_TYPE_LIST = [
{ value: 1, labelKey: '对话' },
{ value: 2, labelKey: '图像' },
{ value: 3, labelKey: '音频' },
{ value: 4, labelKey: '视频' },
{ value: 5, labelKey: '嵌入' },
{ value: 6, labelKey: '重排序' },
{ value: 7, labelKey: '视觉' },
{ value: 8, labelKey: '其他' },
];

const HomePricingFilters = ({ t }) => {
const [showRatio, setShowRatio] = useState(false);
const pricingData = useModelPricingData();
const {
vendorModels,
typeModels,
} = usePricingFilterCounts({
models: pricingData.models,
filterGroup: pricingData.filterGroup,
filterQuotaType: pricingData.filterQuotaType,
filterEndpointType: pricingData.filterEndpointType,
filterVendor: pricingData.filterVendor,
filterTag: pricingData.filterTag,
filterType: pricingData.filterType,
searchValue: pricingData.searchValue,
});

const vendorItems = useMemo(() => {
const models = pricingData.models;
const vendorSet = new Set();
const vendorIcons = new Map();
let hasUnknown = false;
(models || []).forEach((m) => {
if (m.vendor_name) {
vendorSet.add(m.vendor_name);
if (m.vendor_icon && !vendorIcons.has(m.vendor_name)) {
vendorIcons.set(m.vendor_name, m.vendor_icon);
}
} else {
hasUnknown = true;
}
});
const vendors = Array.from(vendorSet).sort();
const getCount = (v) => {
if (v === 'all') return vendorModels.length;
if (v === 'unknown') return vendorModels.filter((m) => !m.vendor_name).length;
return vendorModels.filter((m) => m.vendor_name === v).length;
};
const items = [
{ value: 'all', label: t('全部供应商'), tagCount: getCount('all'), disabled: (models || []).length === 0 },
];
vendors.forEach((v) => {
const count = getCount(v);
items.push({
value: v,
label: v,
icon: vendorIcons.get(v) ? getLobeHubIcon(vendorIcons.get(v), 16) : null,
tagCount: count,
disabled: count === 0,
});
});
if (hasUnknown) {
items.push({
value: 'unknown',
label: t('未知供应商'),
tagCount: getCount('unknown'),
disabled: getCount('unknown') === 0,
});
}
return items;
}, [pricingData.models, vendorModels, t]);

const typeItems = useMemo(() => {
const models = pricingData.models;
const getCount = (typeVal) => {
if (typeVal === 'all') return typeModels.length;
const n = Number(typeVal);
return typeModels.filter((m) => m.type === n).length;
};
const items = [
{ value: 'all', label: t('全部类型'), tagCount: getCount('all'), disabled: (models || []).length === 0 },
];
MODEL_TYPE_LIST.forEach(({ value, labelKey }) => {
const count = getCount(value);
items.push({
value: String(value),
label: t(labelKey),
tagCount: count,
disabled: count === 0,
});
});
return items;
}, [pricingData.models, typeModels, t]);

return (
<div className='w-full mt-8 md:mt-12'>
<div className='rounded-xl border border-semi-color-border bg-semi-color-bg-0 p-4'>
<HorizontalFilterRow
title={t('供应商')}
items={vendorItems}
activeValue={pricingData.filterVendor}
onChange={pricingData.setFilterVendor}
loading={pricingData.loading}
t={t}
/>
<HorizontalFilterRow
title={t('模型类型')}
items={typeItems}
activeValue={pricingData.filterType}
onChange={pricingData.setFilterType}
loading={pricingData.loading}
t={t}
/>
</div>

<div className='mt-6'>
<PricingCardView
filteredModels={pricingData.filteredModels}
loading={pricingData.loading}
rowSelection={null}
pageSize={pricingData.pageSize}
setPageSize={pricingData.setPageSize}
currentPage={pricingData.currentPage}
setCurrentPage={pricingData.setCurrentPage}
selectedGroup={pricingData.selectedGroup}
groupRatio={pricingData.groupRatio}
copyText={pricingData.copyText}
setModalImageUrl={pricingData.setModalImageUrl}
setIsModalOpenurl={pricingData.setIsModalOpenurl}
currency={pricingData.currency}
tokenUnit={pricingData.tokenUnit}
displayPrice={pricingData.displayPrice}
showRatio={showRatio}
t={t}
selectedRowKeys={[]}
openModelDetail={pricingData.openModelDetail}
gridColsClass='grid-cols-1 md:grid-cols-2 lg:grid-cols-3'
showPagination={false}
/>
</div>

<ImagePreview
src={pricingData.modalImageUrl}
visible={pricingData.isModalOpenurl}
onVisibleChange={(visible) => pricingData.setIsModalOpenurl(visible)}
/>

<ModelDetailSideSheet
visible={pricingData.showModelDetail}
onClose={pricingData.closeModelDetail}
modelData={pricingData.selectedModel}
groupRatio={pricingData.groupRatio}
usableGroup={pricingData.usableGroup}
currency={pricingData.currency}
tokenUnit={pricingData.tokenUnit}
displayPrice={pricingData.displayPrice}
showRatio={showRatio}
vendorsMap={pricingData.vendorsMap}
endpointMap={pricingData.endpointMap}
autoGroups={pricingData.autoGroups}
t={t}
/>
</div>
);
};

export default HomePricingFilters;

+ 7
- 80
web/src/pages/Home/index.jsx Wyświetl plik

@@ -40,6 +40,7 @@ import {
} from '@douyinfe/semi-icons';
import { Link } from 'react-router-dom';
import NoticeModal from '../../components/layout/NoticeModal';
import HomePricingFilters from './HomePricingFilters';
import {
Moonshot,
OpenAI,
@@ -162,9 +163,9 @@ const Home = () => {
{/* 背景模糊晕染球 */}
<div className='blur-ball blur-ball-indigo' />
<div className='blur-ball blur-ball-teal' />
<div className='flex items-center justify-center h-full px-4 py-20 md:py-24 lg:py-32 mt-10'>
<div className='flex flex-col items-center w-full px-4 py-20 md:py-24 lg:py-32 mt-10'>
{/* 居中内容区 */}
<div className='flex flex-col items-center justify-center text-center max-w-4xl mx-auto'>
<div className='flex flex-col items-center justify-center text-center max-w-4xl mx-auto w-full'>
<div className='flex flex-col items-center justify-center mb-6 md:mb-8'>
<h1
className={`text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-semi-color-text-0 leading-tight ${isChinese ? 'tracking-wide md:tracking-wider' : ''}`}
@@ -252,84 +253,10 @@ const Home = () => {
)}
</div>

{/* 框架兼容性图标 */}
<div className='mt-12 md:mt-16 lg:mt-20 w-full'>
<div className='flex items-center mb-6 md:mb-8 justify-center'>
<Text
type='tertiary'
className='text-lg md:text-xl lg:text-2xl font-light'
>
{t('支持众多的大模型供应商')}
</Text>
</div>
<div className='flex flex-wrap items-center justify-center gap-3 sm:gap-4 md:gap-6 lg:gap-8 max-w-5xl mx-auto px-4'>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Moonshot size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<OpenAI size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<XAI size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Zhipu.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Volcengine.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Cohere.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Claude.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Gemini.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Suno size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Minimax.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Wenxin.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Spark.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Qingyan.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<DeepSeek.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Qwen.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Midjourney size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Grok size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<AzureAI.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Hunyuan.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Xinference.Color size={40} />
</div>
<div className='w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 flex items-center justify-center'>
<Typography.Text className='!text-lg sm:!text-xl md:!text-2xl lg:!text-3xl font-bold'>
30+
</Typography.Text>
</div>
</div>
</div>
</div>
{/* 模型广场筛选项与列表 */}
<div className='w-full max-w-6xl mt-8 px-4'>
<HomePricingFilters t={t} />
</div>
</div>
</div>


+ 5
- 0
web/vite.config.js Wyświetl plik

@@ -29,6 +29,11 @@ export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
// 绕过 semi-ui exports 限制,dist/css/semi.css 不在 package.json exports 中
'@douyinfe/semi-ui/dist/css/semi.css': path.resolve(
__dirname,
'node_modules/@douyinfe/semi-ui/dist/css/semi.css',
),
},
},
plugins: [


Ładowanie…
Anuluj
Zapisz