feat(web): package
This commit is contained in:
14
web/src/api/package.ts
Normal file
14
web/src/api/package.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
import type { Package } from '@/views/Package/types'
|
||||
|
||||
export const SYS_API_PREFIX = '/sys';
|
||||
// 套餐列表
|
||||
export const getPackageListUrl = `${SYS_API_PREFIX}/package-plans`
|
||||
export const getPackageList = (query: { category: Package['category']; status: boolean; }) => {
|
||||
return request.get(getPackageListUrl, query)
|
||||
}
|
||||
// 获取套餐详情
|
||||
export const getPackageDetail = (package_plan_id: string) => {
|
||||
return request.get(`${SYS_API_PREFIX}/package-plans/${package_plan_id}`)
|
||||
}
|
||||
@@ -3016,5 +3016,69 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
||||
apply: 'Apply',
|
||||
tools: 'Tools',
|
||||
},
|
||||
package: {
|
||||
package: 'Package Management',
|
||||
saas_personal: 'SaaS Personal',
|
||||
commercial_deployment: 'Commercial Deployment',
|
||||
noCommercialPackages: 'No commercial deployment packages available',
|
||||
|
||||
addPackage: 'Add Plan',
|
||||
packageName: 'Plan Name',
|
||||
packageNameZh: 'Plan Name (中文)',
|
||||
packageNameEn: 'Plan Name (English)',
|
||||
packageNamePlaceholder: '中文, 例如:记忆体验版',
|
||||
packageNamePlaceholderEn: 'English, e.g. Memory Trial Plan',
|
||||
packageCategory: 'Package Category',
|
||||
price: 'Price',
|
||||
pricePlaceholder: 'e.g. 0, 19, 299 or Contact Us',
|
||||
billingPeriod: 'Billing Period',
|
||||
monthly: 'Monthly',
|
||||
yearly: 'Yearly',
|
||||
permanent_free: 'Permanent Free',
|
||||
local_deployment: 'Local Deployment',
|
||||
coreValue: 'Core Value',
|
||||
coreValueZh: 'Core Value (中文)',
|
||||
coreValueEn: 'Core Value (English)',
|
||||
coreValuePlaceholder: '中文, 一句话描述核心价值',
|
||||
coreValuePlaceholderEn: 'EngLish, describe the core value in one sentence',
|
||||
tech_support: 'Technical Support',
|
||||
tech_support_zh: 'Technical Support (中文)',
|
||||
tech_support_en: 'Technical Support (English)',
|
||||
technicalSupportPlaceholder: '中文, 例如:社群交流、工单支持',
|
||||
technicalSupportPlaceholderEn: 'English, e.g. Community support, ticket support',
|
||||
sla: 'SLA & Compliance',
|
||||
slaZh: 'SLA & Compliance (中文)',
|
||||
slaEn: 'SLA & Compliance (English)',
|
||||
slaPlaceholder: '中文, 例如:无、验证力加强+审计日志',
|
||||
slaPlaceholderEn: 'English, e.g. None, dedicated compute pool + audit logs',
|
||||
customPage: 'Chat Page Customization',
|
||||
customPageZh: 'Chat Page Customization (中文)',
|
||||
customPageEn: 'Chat Page Customization (English)',
|
||||
customPagePlaceholder: '中文, 例如:LOGO定制',
|
||||
customPagePlaceholderEn: 'English, e.g. Logo customization',
|
||||
primaryColor: 'Primary Color',
|
||||
status: 'Status',
|
||||
active: 'Active',
|
||||
inactive: 'Inactive',
|
||||
api_ops_rate_limit: 'API OPS Rate Limit',
|
||||
ops: 'req/s',
|
||||
pcs: 'pcs',
|
||||
GB: 'GB',
|
||||
tier_level: 'Tier Level',
|
||||
numberPlaceholder: 'e.g. 10',
|
||||
|
||||
packageDetail: 'Package Detail',
|
||||
basicInfo: 'Basic Info',
|
||||
featureConfig: 'Billing Unit Quota',
|
||||
workspace_quota: 'Workspace Quota',
|
||||
skill_quota: 'Skill Library Quota',
|
||||
app_quota: 'App Quota',
|
||||
knowledge_capacity_quota: 'Knowledge Base Capacity',
|
||||
memory_engine_quota: 'Memory Engine Quota',
|
||||
end_user_quota: 'Memorable End Users',
|
||||
ontology_project_quota: 'Ontology Project',
|
||||
model_quota: 'Model Quota',
|
||||
editPackage: 'Edit Package',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2980,5 +2980,69 @@ export const zh = {
|
||||
apply: '应用',
|
||||
tools: '工具',
|
||||
},
|
||||
package: {
|
||||
package: '套餐管理',
|
||||
saas_personal: 'SaaS 个人版',
|
||||
commercial_deployment: '商业化部署',
|
||||
noCommercialPackages: '暂无商业化部署套餐',
|
||||
|
||||
addPackage: '添加套餐',
|
||||
packageName: '套餐名称',
|
||||
packageNameZh: '套餐名称 (中文)',
|
||||
packageNameEn: '套餐名称 (English)',
|
||||
packageNamePlaceholder: '中文, 例如:记忆体验版',
|
||||
packageNamePlaceholderEn: 'English, e.g. Memory Trial Plan',
|
||||
packageCategory: '套餐分类',
|
||||
price: '价格',
|
||||
pricePlaceholder: '例如: 0, 19, 299 或联系我们',
|
||||
billingPeriod: '计费周期',
|
||||
monthly: '月',
|
||||
yearly: '年',
|
||||
permanent_free: '永久免费',
|
||||
local_deployment: '本地化部署',
|
||||
coreValue: '核心价值',
|
||||
coreValueZh: '核心价值 (中文)',
|
||||
coreValueEn: '核心价值 (English)',
|
||||
coreValuePlaceholder: '中文, 一句话描述核心价值',
|
||||
coreValuePlaceholderEn: 'EngLish, describe the core value in one sentence',
|
||||
tech_support: '技术支持',
|
||||
tech_support_zh: '技术支持 (中文)',
|
||||
tech_support_en: '技术支持 (English)',
|
||||
technicalSupportPlaceholder: '中文, 例如:社群交流、工单支持',
|
||||
technicalSupportPlaceholderEn: 'English, e.g. Community support, ticket support',
|
||||
sla: 'SLA与合规',
|
||||
slaZh: 'SLA与合规 (中文)',
|
||||
slaEn: 'SLA与合规 (English)',
|
||||
slaPlaceholder: '中文, 例如:无、验证力加强+审计日志',
|
||||
slaPlaceholderEn: 'English, e.g. None, dedicated compute pool + audit logs',
|
||||
customPage: '对应页面个性化配置',
|
||||
customPageZh: '对应页面个性化配置 (中文)',
|
||||
customPageEn: '对应页面个性化配置 (English)',
|
||||
customPagePlaceholder: '中文, 例如:LOGO定制',
|
||||
customPagePlaceholderEn: 'English, e.g. Logo customization',
|
||||
primaryColor: '主题色',
|
||||
status: '状态',
|
||||
active: '启用',
|
||||
inactive: '停用',
|
||||
api_ops_rate_limit: 'API OPS 频次',
|
||||
ops: '次/秒',
|
||||
pcs: '个',
|
||||
GB: 'GB',
|
||||
tier_level: '层级',
|
||||
numberPlaceholder: '如: 10',
|
||||
|
||||
packageDetail: '套餐详情',
|
||||
basicInfo: '基础信息',
|
||||
featureConfig: '计费单元配额',
|
||||
workspace_quota: '空间数量',
|
||||
skill_quota: '技能库数量',
|
||||
app_quota: '应用数量',
|
||||
knowledge_capacity_quota: '知识库容量',
|
||||
memory_engine_quota: '记忆引擎数量',
|
||||
end_user_quota: '可记忆终端用户数',
|
||||
ontology_project_quota: '本体工程',
|
||||
model_quota: '可负载模型数量',
|
||||
editPackage: '编辑套餐',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-02 16:33:11
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-04 18:11:34
|
||||
* @Last Modified time: 2026-04-13 16:53:15
|
||||
*/
|
||||
/**
|
||||
* Route Configuration
|
||||
@@ -76,13 +76,12 @@ const componentMap: Record<string, LazyExoticComponent<ComponentType<object>>> =
|
||||
SpaceManagement: lazy(() => import('@/views/SpaceManagement')),
|
||||
ApiKeyManagement: lazy(() => import('@/views/ApiKeyManagement')),
|
||||
EmotionEngine: lazy(() => import('@/views/EmotionEngine')),
|
||||
StatementDetail: lazy(() => import('@/views/UserMemoryDetail/pages/StatementDetail')),
|
||||
ForgetDetail: lazy(() => import('@/views/UserMemoryDetail/pages/ForgetDetail')),
|
||||
MemoryNodeDetail: lazy(() => import('@/views/UserMemoryDetail/pages/index')),
|
||||
SelfReflectionEngine: lazy(() => import('@/views/SelfReflectionEngine')),
|
||||
OrderPayment: lazy(() => import('@/views/OrderPayment')),
|
||||
OrderHistory: lazy(() => import('@/views/OrderHistory')),
|
||||
Pricing: lazy(() => import('@/views/Pricing')),
|
||||
Package: lazy(() => import('@/views/Package')),
|
||||
ToolManagement: lazy(() => import('@/views/ToolManagement')),
|
||||
SpaceConfig: lazy(() => import('@/views/SpaceConfig')),
|
||||
Ontology: lazy(() => import('@/views/Ontology')),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{ "path": "/model", "element": "ModelManagement" },
|
||||
{ "path": "/space", "element": "SpaceManagement" },
|
||||
{ "path": "/tool", "element": "ToolManagement" },
|
||||
{ "path": "/pricing", "element": "Pricing" },
|
||||
{ "path": "/pricing", "element": "Package" },
|
||||
{ "path": "/order-pay", "element": "OrderPayment" },
|
||||
{ "path": "/orders", "element": "OrderHistory" },
|
||||
{ "path": "/skills", "element": "Skills" },
|
||||
@@ -48,7 +48,6 @@
|
||||
{ "path": "/application/config/:id", "element": "ApplicationConfig" },
|
||||
{ "path": "/application/config/:id/:source", "element": "ApplicationConfig" },
|
||||
{ "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" },
|
||||
{ "path": "/statement/:id", "element": "StatementDetail" },
|
||||
{ "path": "/user-memory/detail/:id/:type", "element": "MemoryNodeDetail" },
|
||||
{ "path": "/ontology/:id", "element": "OntologyDetail" }
|
||||
]
|
||||
|
||||
@@ -421,21 +421,7 @@
|
||||
"display": false,
|
||||
"level": 3,
|
||||
"sort": 0,
|
||||
"subs": [
|
||||
{
|
||||
"id": 2211,
|
||||
"parent": 221,
|
||||
"code": "statementDetail",
|
||||
"label": "记忆详情",
|
||||
"i18nKey": "menu.statementDetail",
|
||||
"path": "/statement/:id",
|
||||
"enable": true,
|
||||
"display": false,
|
||||
"level": 4,
|
||||
"sort": 0,
|
||||
"subs": null
|
||||
}
|
||||
]
|
||||
"subs": []
|
||||
},
|
||||
{
|
||||
"id": 222,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-02 16:35:15
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-06 10:39:00
|
||||
* @Last Modified time: 2026-04-14 14:43:54
|
||||
*/
|
||||
/**
|
||||
* HTTP Request Utility Module
|
||||
@@ -23,6 +23,7 @@ import { clearAuthData } from './auth';
|
||||
import { message } from 'antd';
|
||||
import { refreshTokenUrl, refreshToken, loginUrl, logoutUrl } from '@/api/user'
|
||||
import i18n from '@/i18n'
|
||||
import { SYS_API_PREFIX } from '@/api/package'
|
||||
|
||||
/**
|
||||
* Standard API response structure
|
||||
@@ -74,6 +75,10 @@ let requests: RequestQueueItem[] = [];
|
||||
// Request interceptor
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
console.log('config', config, config.url?.startsWith(SYS_API_PREFIX))
|
||||
if (config.url?.startsWith(SYS_API_PREFIX)) {
|
||||
config.baseURL = '';
|
||||
}
|
||||
if (!config.headers.Authorization) {
|
||||
const token = cookieUtils.get('authToken');
|
||||
if (token) {
|
||||
|
||||
34
web/src/views/Package/constant.ts
Normal file
34
web/src/views/Package/constant.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export const billingUnits = [
|
||||
{
|
||||
key: 'workspace_quota',
|
||||
unit: '个', placeholder: '如: 10',
|
||||
},
|
||||
{
|
||||
key: 'skill_quota',
|
||||
unit: '个', placeholder: '如: 10',
|
||||
},
|
||||
{
|
||||
key: 'app_quota',
|
||||
unit: '个', placeholder: '如: 10',
|
||||
},
|
||||
{
|
||||
key: 'knowledge_capacity_quota',
|
||||
unit: 'GB', placeholder: '如: 10',
|
||||
},
|
||||
{
|
||||
key: 'memory_engine_quota',
|
||||
unit: '个', placeholder: '如: 10',
|
||||
},
|
||||
{
|
||||
key: 'end_user_quota',
|
||||
unit: '个', placeholder: '如: 10',
|
||||
},
|
||||
{
|
||||
key: 'ontology_project_quota',
|
||||
unit: '个', placeholder: '如: 10',
|
||||
},
|
||||
{
|
||||
key: 'model_quota',
|
||||
unit: '次/秒', placeholder: '如: 10',
|
||||
},
|
||||
]
|
||||
145
web/src/views/Package/index.tsx
Normal file
145
web/src/views/Package/index.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-25
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-04-14 14:48:13
|
||||
*/
|
||||
/**
|
||||
* Package Component
|
||||
*
|
||||
* Package management page with:
|
||||
* - Tabs for SaaS Personal and Commercial Deployment
|
||||
* - Package cards showing features and pricing
|
||||
* - Edit and delete actions
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
import { useMemo, useState, useEffect, type FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Flex, Row, Col, type SegmentedProps } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import type { Package } from './types'
|
||||
import { getPackageList } from '@/api/package';
|
||||
import PageTabs from '@/components/PageTabs'
|
||||
import { billingUnits } from './constant'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import BodyWrapper from '@/components/Empty/BodyWrapper'
|
||||
import { useI18n } from '@/store/locale'
|
||||
import RbButton from '@/components/RbButton'
|
||||
|
||||
const Package: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { language } = useI18n()
|
||||
const navigate = useNavigate();
|
||||
const [data, setData] = useState<Package[]>([])
|
||||
|
||||
const [activeTab, setActiveTab] = useState('saas_personal');
|
||||
const formatTabItems = useMemo(() => {
|
||||
return ['saas_personal', 'commercial_deployment'].map(value => ({
|
||||
value,
|
||||
label: t(`package.${value}`),
|
||||
}))
|
||||
}, [t])
|
||||
/** Handle tab change */
|
||||
const handleChangeTab = (value: SegmentedProps['value']) => {
|
||||
setActiveTab(value as string);
|
||||
}
|
||||
const getList = () => {
|
||||
getPackageList({ category: activeTab as Package['category'], status: true })
|
||||
.then(res => {
|
||||
setData(res as Package[] || [])
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getList()
|
||||
}, [activeTab])
|
||||
|
||||
const getKeyWithLanguage = (key: string) => {
|
||||
return (language === 'en' ? `${key}_en` : key) as keyof Package
|
||||
}
|
||||
/** Navigate to order history */
|
||||
const goToHistory = () => {
|
||||
navigate('/orders');
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Flex justify="space-between" className="rb:mb-4!">
|
||||
<PageTabs
|
||||
value={activeTab}
|
||||
options={formatTabItems}
|
||||
onChange={handleChangeTab}
|
||||
/>
|
||||
<RbButton className="rb:text-[#212332] rb:font-medium!" onClick={goToHistory}>
|
||||
<div
|
||||
className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/order/order.svg')]"
|
||||
></div>
|
||||
{t('pricing.orderHistory')}
|
||||
</RbButton>
|
||||
</Flex>
|
||||
<BodyWrapper empty={data.length < 1}>
|
||||
<Row gutter={[12, 12]} className="rb:max-h-[calc(100%-48px)]! rb:overflow-y-auto">
|
||||
{data.map((pkg) => (
|
||||
<Col key={pkg.id} span={8}>
|
||||
<RbCard
|
||||
className="rb:h-full! rb:shadow-md hover:rb:shadow-lg rb:transition-shadow"
|
||||
bodyClassName="rb:p-6! rb:h-full!"
|
||||
headerClassName="rb:min-h-0!"
|
||||
>
|
||||
<Flex vertical justify="space-between" className="rb:h-full!">
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="rb:text-center rb:mb-6">
|
||||
<h3 className="rb:text-xl rb:font-bold rb:mb-2 rb:min-h-7" style={{ color: pkg.theme_color }}>
|
||||
{String(pkg[getKeyWithLanguage('name')] ?? '')}
|
||||
</h3>
|
||||
<p className="rb:text-sm rb:text-gray-500 rb:mb-4 rb:min-h-5">{String(pkg[getKeyWithLanguage('core_value')] ?? '')}</p>
|
||||
<div className="rb:text-4xl rb:font-bold rb:mb-2">
|
||||
{pkg.billing_cycle !== 'permanent_free' && <>¥{pkg.price}</>}
|
||||
{pkg.billing_cycle && <span className={clsx("", {
|
||||
'rb:text-base rb:font-normal rb:text-gray-500': pkg.billing_cycle !== 'permanent_free'
|
||||
})}>{pkg.billing_cycle !== 'permanent_free' && '/'}{t(`package.${pkg.billing_cycle}`)}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="rb:space-y-3">
|
||||
{billingUnits.map(({ key, unit }) => {
|
||||
if (typeof pkg.quotas[key as keyof Package['quotas']] === 'number') {
|
||||
return (
|
||||
<div key={key} className="rb:flex rb:items-center rb:justify-between rb:text-sm">
|
||||
<span className="rb:text-gray-500">{t(`package.${key}`)}</span>
|
||||
<span>{pkg.quotas[key as keyof Package['quotas']]}{unit}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})}
|
||||
{pkg.tech_support &&
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:text-sm">
|
||||
<span className="rb:text-gray-500">{t(`package.tech_support`)}</span>
|
||||
<span>{String(pkg[getKeyWithLanguage('tech_support')] ?? '')}</span>
|
||||
</div>
|
||||
}
|
||||
{pkg.api_ops_rate_limit &&
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:text-sm">
|
||||
<span className="rb:text-gray-500">{t(`package.api_ops_rate_limit`)}</span>
|
||||
<span>{pkg.api_ops_rate_limit}(次/秒)</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
</RbCard>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</BodyWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Package;
|
||||
61
web/src/views/Package/types.ts
Normal file
61
web/src/views/Package/types.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-04-14 11:35:01
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-04-14 14:28:46
|
||||
*/
|
||||
export interface Package {
|
||||
id: string;
|
||||
// 名称
|
||||
name: string;
|
||||
name_en: string;
|
||||
// 类型
|
||||
category: "saas_personal" | "commercial_deployment";
|
||||
tier_level: number;
|
||||
// 版本
|
||||
version: string;
|
||||
// 状态
|
||||
status: boolean;
|
||||
// 价格
|
||||
price: string;
|
||||
// 计费周期
|
||||
billing_cycle: "monthly" | "yearly" | "permanent_free" | "local_deployment";
|
||||
// 核心价值
|
||||
core_value: string;
|
||||
core_value_en: string;
|
||||
// 技术支持
|
||||
tech_support: string;
|
||||
tech_support_en: string;
|
||||
// SLA与合规
|
||||
sla_compliance: string;
|
||||
sla_compliance_en: string;
|
||||
// 对话页面个性化配置
|
||||
page_customization: string;
|
||||
page_customization_en: string;
|
||||
// API OPS 频次(次/秒)
|
||||
api_ops_rate_limit: number;
|
||||
// 主题色
|
||||
theme_color: string;
|
||||
quotas: {
|
||||
// 空间数量
|
||||
workspace_quota: number;
|
||||
// 技能库数量
|
||||
skill_quota: number;
|
||||
// 应用数量
|
||||
app_quota: number;
|
||||
// 知识库容量
|
||||
knowledge_capacity_quota: string;
|
||||
// 记忆引擎数量
|
||||
memory_engine_quota: number;
|
||||
// 可记忆终端用户数
|
||||
end_user_quota: number;
|
||||
// 本体工程
|
||||
ontology_project_quota: number;
|
||||
// 可负载模型数量
|
||||
model_quota: number;
|
||||
},
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
created_by: string;
|
||||
updated_by: string | null;
|
||||
}
|
||||
Reference in New Issue
Block a user