diff --git a/web/src/api/memory.ts b/web/src/api/memory.ts index 6d75aad3..1a496961 100644 --- a/web/src/api/memory.ts +++ b/web/src/api/memory.ts @@ -131,6 +131,9 @@ export const getEmotionHealth = (group_id: string) => { export const getEmotionSuggestions = (group_id: string) => { return request.post(`/memory/emotion/suggestions`, { group_id, limit: 20 }) } +export const analyticsRefresh = (end_user_id: string) => { + return request.post('/memory-storage/analytics/generate_cache', { end_user_id }) +} /*************** end 用户记忆 相关接口 ******************************/ @@ -189,7 +192,7 @@ export const updateMemoryReflectionConfig = (values: SelfReflectionEngineConfig) return request.post('/memory/reflection/save', values) } // 反思引擎-试运行 -export const pilotRunMemoryReflectionConfig = (values: { config_id: number | string; dialogue_text: string; }) => { +export const pilotRunMemoryReflectionConfig = (values: { config_id: number | string; language_type: string; }) => { return request.get('/memory/reflection/run', values) } diff --git a/web/src/api/order.ts b/web/src/api/order.ts new file mode 100644 index 00000000..e5d9d916 --- /dev/null +++ b/web/src/api/order.ts @@ -0,0 +1,17 @@ +import { request } from '@/utils/request' +import type { VoucherForm } from '@/views/OrderPayment/types' + +export const getOrderListUrl = '/v1/orders/customer' + +// 提交支付凭证API +export const submitPaymentVoucherAPI = (voucherData: VoucherForm) => { + return request.post('/v1/orders/', voucherData) +} +// 订单详情 +export const getOrderDetail = (order_no: string) => { + return request.get(`/v1/orders/customer/${order_no}`) +} +export const orderStatusUrl = '/v1/order-status/' +export const getOrderStatus = () => { + return request.get(orderStatusUrl) +} \ No newline at end of file diff --git a/web/src/api/workspaces.ts b/web/src/api/workspaces.ts index 428b1280..4e78194b 100644 --- a/web/src/api/workspaces.ts +++ b/web/src/api/workspaces.ts @@ -1,5 +1,6 @@ import { request } from '@/utils/request' import type { SpaceModalData } from '@/views/SpaceManagement/types' +import type { ConfigModalData } from '@/views/UserMemory/types' // 空间列表 export const getWorkspaces = () => { @@ -22,6 +23,6 @@ export const getWorkspaceModels = () => { return request.get(`/workspaces/workspace_models`) } // 更新空间模型配置 -export const updateWorkspaceModels = () => { - return request.post(`/workspaces/workspace_models`) +export const updateWorkspaceModels = (data: ConfigModalData) => { + return request.put(`/workspaces/workspace_models`, data) } diff --git a/web/src/assets/images/menu/pricing.svg b/web/src/assets/images/menu/pricing.svg new file mode 100644 index 00000000..5510ba23 --- /dev/null +++ b/web/src/assets/images/menu/pricing.svg @@ -0,0 +1,22 @@ + + + 菜单-收费管理 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menu/pricing_active.svg b/web/src/assets/images/menu/pricing_active.svg new file mode 100644 index 00000000..f708877d --- /dev/null +++ b/web/src/assets/images/menu/pricing_active.svg @@ -0,0 +1,22 @@ + + + 菜单-收费管理 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/order/alert.svg b/web/src/assets/images/order/alert.svg new file mode 100644 index 00000000..18e9fd5c --- /dev/null +++ b/web/src/assets/images/order/alert.svg @@ -0,0 +1,13 @@ + + + 注意 + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/order/bg.png b/web/src/assets/images/order/bg.png new file mode 100644 index 00000000..da821814 Binary files /dev/null and b/web/src/assets/images/order/bg.png differ diff --git a/web/src/assets/images/order/biz.png b/web/src/assets/images/order/biz.png new file mode 100644 index 00000000..bacdb54e Binary files /dev/null and b/web/src/assets/images/order/biz.png differ diff --git a/web/src/assets/images/order/check.svg b/web/src/assets/images/order/check.svg new file mode 100644 index 00000000..7221eb31 --- /dev/null +++ b/web/src/assets/images/order/check.svg @@ -0,0 +1,15 @@ + + + 对号备份 + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/order/commerce.png b/web/src/assets/images/order/commerce.png new file mode 100644 index 00000000..14748ecb Binary files /dev/null and b/web/src/assets/images/order/commerce.png differ diff --git a/web/src/assets/images/order/corporate.svg b/web/src/assets/images/order/corporate.svg new file mode 100644 index 00000000..07b92a45 --- /dev/null +++ b/web/src/assets/images/order/corporate.svg @@ -0,0 +1,15 @@ + + + 企业_画板 39@2x + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/order/order.svg b/web/src/assets/images/order/order.svg new file mode 100644 index 00000000..5ddb6680 --- /dev/null +++ b/web/src/assets/images/order/order.svg @@ -0,0 +1,16 @@ + + + 订单 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/order/order_hover.svg b/web/src/assets/images/order/order_hover.svg new file mode 100644 index 00000000..7e9c7b46 --- /dev/null +++ b/web/src/assets/images/order/order_hover.svg @@ -0,0 +1,16 @@ + + + 订单 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/order/personal.png b/web/src/assets/images/order/personal.png new file mode 100644 index 00000000..1e64efd6 Binary files /dev/null and b/web/src/assets/images/order/personal.png differ diff --git a/web/src/assets/images/order/team.png b/web/src/assets/images/order/team.png new file mode 100644 index 00000000..2a059d73 Binary files /dev/null and b/web/src/assets/images/order/team.png differ diff --git a/web/src/components/SiderMenu/index.tsx b/web/src/components/SiderMenu/index.tsx index 91d3d0f1..a39a0b4e 100644 --- a/web/src/components/SiderMenu/index.tsx +++ b/web/src/components/SiderMenu/index.tsx @@ -38,6 +38,8 @@ import toolIcon from '@/assets/images/menu/tool.png'; import toolActiveIcon from '@/assets/images/menu/tool_active.png'; import apiKeyIcon from '@/assets/images/menu/apiKey.png'; import apiKeyActiveIcon from '@/assets/images/menu/apiKey_active.png'; +import pricingIcon from '@/assets/images/menu/pricing.svg' +import pricingActiveIcon from '@/assets/images/menu/pricing_active.svg' // 图标路径映射表 const iconPathMap: Record = { @@ -65,6 +67,8 @@ const iconPathMap: Record = { 'toolActive': toolActiveIcon, 'apiKey': apiKeyIcon, 'apiKeyActive': apiKeyActiveIcon, + 'pricing': pricingIcon, + 'pricingActive': pricingActiveIcon }; const { Sider } = Layout; diff --git a/web/src/components/Table/index.tsx b/web/src/components/Table/index.tsx index 6524d1a8..62c68dc3 100644 --- a/web/src/components/Table/index.tsx +++ b/web/src/components/Table/index.tsx @@ -19,6 +19,7 @@ interface TableComponentProps extends Omit { isScroll?: boolean; scrollX?: number | string | true; // 支持自定义横向滚动宽度 scrollY?: number | string; // 支持自定义纵向滚动高度 + currentPageKey?: string; } export interface TableRef { loadData: () => void; @@ -48,6 +49,7 @@ const TableComponent = forwardRef(({ isScroll = false, scrollX, scrollY, + currentPageKey = 'page', ...props }, ref) => { const { t } = useTranslation(); @@ -86,7 +88,7 @@ const TableComponent = forwardRef(({ ...currentPagination, ...pageData, }) - params = {...params, ...pageData} + params = { ...params, ...pageData, [currentPageKey]: pageData.page} } setLoading(true) // 构建查询参数并调用API @@ -95,7 +97,7 @@ const TableComponent = forwardRef(({ // 支持两种响应格式:直接返回 total 或在 page 对象中返回 const totalCount = res.page?.total ?? res.total ?? 0; setTotal(totalCount) - setData(Array.isArray(res.items) ? res.items : Array.isArray(res.hosts) ? res.hosts : res || []) + setData(Array.isArray(res.items) ? res.items : Array.isArray(res.hosts) ? res.hosts : Array.isArray(res.list) ? res.list : res || []) setLoading(false) }) .catch(err => { diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index e05be03f..92e5010c 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -38,6 +38,9 @@ export const en = { emotionEngine: 'Emotion Engine', statementDetail: 'Emotion Memory', selfReflectionEngine: 'Self Reflection Engine', + pricing: 'Pricing Management', + orderPayment: 'Order Payment', + orderHistory: 'Order History', }, dashboard: { totalMemoryCapacity: 'Total Memory Capacity', @@ -326,6 +329,7 @@ export const en = { publicApiCannotRefreshToken: 'Public API cannot refresh token', refreshTokenNotExist: 'Refresh token does not exist', reset: 'Reset', + refresh: 'Refresh' }, model: { searchPlaceholder: 'search model…', @@ -1053,6 +1057,16 @@ export const en = { MemorySummary: 'Episodic Memory', Statement: 'Emotional Memory', ExtractedEntity: 'Short-term Memory', + + PERCEPTUAL_MEMORY: 'Perceptual Memory', + WORKING_MEMORY: 'Working Memory', + SHORT_TERM_MEMORY: 'Shot Term Memory', + LONG_TERM_MEMORY: 'Long Term Memory', + EXPLICIT_MEMORY: 'Explicit Memory', + IMPLICIT_MEMORY: 'Implicit Memory', + EMOTIONAL_MEMORY: 'Emotional Memory', + EPISODIC_MEMORY: 'Episodic Memory', + endUserProfile: 'Core Profile', editEndUserProfile: 'Edit', name: 'Name', @@ -1721,6 +1735,123 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re privacy_types: 'Privacy Types', summary: 'Audit Summary', } - } + }, + pricing: { + title: 'Flexible Pricing for Teams of All Sizes', + desc: 'Transparent pricing that helps you easily find a plan that fits your budget.', + solution: 'Product Positioning', + targetAudience: 'Target Audience', + orderPayment: 'Order Payment', + creationTime: 'Creation Time', + orderPaymentDesc: 'Please confirm the order information and complete the payment.', + orderInformation: 'Order Information', + paymentMethod: 'Payment Method', + paymentVoucher: 'Payment Voucher', + corporateTransfer: 'Corporate transfer', + corporateTransferDesc: 'Pay via corporate bank transfer.', + payeeInformation: 'Payee Account Information', + receivingEntity: 'Receiving Entity', + bankName: 'Bank Name', + bankAccountNumber: 'Bank Account Number', + pay_txn_id: 'Payment Transaction ID', + pay_txn_idPlaceholder: 'Please enter the bank transfer transaction ID.', + pay_txn_idDesc: 'Please fill in the serial number on the bank transfer voucher for verification of payment', + payer: 'Payer(Company/Individual)', + payerPlaceholder: 'Please enter the bank transfer transaction ID.', + redirectCountdown: 'seconds until redirecting to Memory Bear...', + confirmRedirect: 'Payment voucher submitted successfully!', + confirmRedirectContent: 'We will verify your payment information within 1-3 business days. Would you like to go to Memory Bear now to start your experience?', + stayCurrentPage: 'Stay on current page', + goBack: 'Go to backend', + transferDate: 'Transfer Date', + payerAccount: 'Payer Account', + remark: 'Remark', + remarkPlaceholder: 'Please provide any additional remarks here (optional).', + confirm: 'SUBMIT PAYMENT', + submitting: 'Submitting...', + payInfo: 'After submission, we will verify your payment information within 1–3 business days.', + paySuccess: 'Once verified, your subscription service will be automatically activated by the system.', + comboName: 'Combo Name', + spAndTa: 'Solution positioning and target audience', + versionInformation: 'Version information', + orderCycle: 'Order Cycle', + orderAmount: 'Order Amount', + personal: { + type: 'Personal', + label: 'Current Package', + typeDesc: 'For individuals', + solution: "A person's second brain, capable of storing up to 2000 memories.", + targetAudience: 'individual users, students, and first-time users', + priceDesc: '/Forever free', + supportServices: 'Community Forum + Email Support', + }, + team: { + type: 'Team', + label: 'Small Team', + typeDesc: 'Small Team Version', + solution: "Enable every team to build a shared second brain in seconds.", + targetAudience: 'Small teams, early-stage startups, and small projects.', + priceDesc: '/Month', + supportServices: 'Standard customer service support', + }, + biz: { + type: 'Biz+', + label: 'Most Popular', + typeDesc: 'Enterprise Growth Edition', + solution: "Scale your organization with a powerful, enterprise-ready second-brain system.", + targetAudience: 'Growing teams, startups, and SMBs requiring advanced memory capabilities.', + priceDesc: '/Month/workspace', + supportServices: 'Priority customer service support', + }, + commerce: { + type: 'Commerce', + label: 'Commercial OEM', + typeDesc: 'Commercial OEM version', + solution: "Seamlessly integrate advanced memory capabilities into your SaaS or enterprise product.", + targetAudience: 'Large enterprises, SaaS vendors, and system integrators requiring fully customizable and secure deployment.', + priceDesc: 'On-premises deployment', + supportServices: 'Standard customer service support', + flexibleDeployment: 'Supports localized deployment in data centers', + reliableGuarantee: '99.9% SLA guarantee' + }, + mostPopular: 'Most Popular', + startedBtn: 'GET STARTED', + choosePlanBtn: 'CHOOSE PLAN', + contactBtn: 'CONTACT US', + memoryCapacity: 'Memory capacity:', + entries: 'entries', + intelligentSearchFrequency: 'Intelligent search frequency:', + timesMonth: 'times/month', + supportServices: 'Support Services:', + flexibleDeployment: 'Flexible deployment:', + reliableGuarantee: 'Reliable guarantee:', + alertTitle: 'Intellectual Property Authorization Reminder', + alertContent: 'Please note: Using certain AI models (such as GPT-4, Claude, etc.) may involve third-party API call fees, which are not included in the Memory Bear platform subscription fee. You need to pay the relevant fees separately to the model provider. Memory Bear only charges platform management and service fees and does not bear the usage fees of third-party APIs.', + currentAccountType: 'Current Account Type', + validUntil: 'Valid Until', + orderHistory: 'Order History', + order_no: 'Order Number', + product_type: 'Package Name', + payable_amount: 'Order Amount', + status: 'Order Status', + pay_time: 'Payment Time', + viewDetail: 'View Details', + PENDING: 'Pending Review', + APPROVED: 'Approved', + REJECTED: 'Rejected', + allStatus: 'All Status', + allTime: 'All Time', + today: 'Today', + week: 'Last Week', + month: 'Last Month', + threeMonth: 'Last Three Months', + year: 'Last Year', + searchPlaceholder: 'Search order number', + allType: 'All Packages', + orderDetail: 'Order Details', + orderInfo: 'Order Information', + orderPayInfo: 'Payment Information', + create_time: 'Creation Time', + }, }, }; diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index c1b6b448..8349f735 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -38,6 +38,9 @@ export const zh = { emotionEngine: '情感引擎', statementDetail: '情绪记忆', selfReflectionEngine: '反思引擎', + pricing: '收费管理', + orderPayment: '订单支付', + orderHistory: '订单记录', }, knowledgeBase: { home: '首页', @@ -808,7 +811,8 @@ export const zh = { logoutApiCannotRefreshToken: '退出登录接口不能刷新token', publicApiCannotRefreshToken: '公共接口不能刷新token', refreshTokenNotExist: '刷新token不存在', - reset: '重置' + reset: '重置', + refresh: '刷新' }, product: { applicationManagement: '应用管理', @@ -1124,10 +1128,21 @@ export const zh = { nodeStatistics: '记忆分类', total: '总计', + Chunk: '长期记忆', MemorySummary: '情景记忆', Statement: '情绪记忆', ExtractedEntity: '短期记忆', + + PERCEPTUAL_MEMORY: '感知记忆', + WORKING_MEMORY: '工作记忆', + SHORT_TERM_MEMORY: '短期记忆', + LONG_TERM_MEMORY: '长期记忆', + EXPLICIT_MEMORY: '显性记忆', + IMPLICIT_MEMORY: '隐性记忆', + EMOTIONAL_MEMORY: '情绪记忆', + EPISODIC_MEMORY: '情景记忆', + endUserProfile: '核心档案', editEndUserProfile: '编辑', name: '姓名', @@ -1808,6 +1823,123 @@ export const zh = { privacy_types: '隐私类型', summary: '审核摘要', } - } + }, + pricing: { + title: '灵活定价,满足各类团队需求', + desc: '透明的定价策略,助您轻松找到符合预算的方案', + solution: '方案定位', + targetAudience: '目标人群', + orderPayment: '订单支付', + orderPaymentDesc: '请确认订单信息并完成支付', + orderInformation: '订单信息', + paymentMethod: '支付方式', + paymentVoucher: '支付凭证', + corporateTransfer: '企业转账', + corporateTransferDesc: '通过企业对公账户转账支付', + receivingEntity: '收款单位', + bankName: '开户银行', + bankAccountNumber: '银行账号', + pay_txn_id: '支付流水号', + payer: '付款单位/个人', + transferDate: '转账日期', + payerAccount: '付款账号', + remark: '备注信息', + remarkPlaceholder: '如有其他说明,请在此填写(选填)', + confirm: '确认支付', + submitting: '提交中...', + payInfo: '提交后,我们将在1-3个工作日内核实您的付款信息', + paySuccess: '核实通过后,系统将自动开通您的套餐服务', + pay_txn_idPlaceholder: '请输入支付流水号', + pay_txn_idDesc: '请填写转账时的交易流水号,以便我们快速确认您的付款', + payerPlaceholder: '请输入付款单位或个人姓名', + redirectCountdown: '秒后跳转后台...', + confirmRedirect: '支付凭证已提交成功!', + confirmRedirectContent: '我们将在1-3个工作日内核实您的付款信息。是否立即跳转到记忆熊开始体验?', + stayCurrentPage: '留在当前页', + goBack: '跳转后台', + payeeInformation: '收款信息', + creationTime: '创建时间', + comboName: '套餐名称', + spAndTa: '方案定位与目标人群', + versionInformation: '版本信息', + orderCycle: '订购周期', + orderAmount: '订单金额', + personal: { + type: '个人版', + label: '当前安装包', + typeDesc: '个人玩家版本', + solution: '个人的第二大脑,最多可存储2000条记忆。', + targetAudience: '个人用户、学生及初次使用者', + priceDesc: '/永久免费', + supportServices: '社区论坛 + 邮件支持' + }, + team: { + type: '团队版', + label: '小型团队', + typeDesc: '小型团队版本', + solution: '让每一条业务记录瞬间成为团队的第二大脑。', + targetAudience: '初创团队、小微型企业、小型项目', + priceDesc: '/月', + supportServices: '标准客服支持' + }, + biz: { + type: '企业增长版', + label: '最受欢迎', + typeDesc: '企业增长版本', + solution: '让每一条业务记录瞬间成为团队的第二大脑。', + targetAudience: '初创团队、小微型企业、小型项目', + priceDesc: '/月/工作区', + supportServices: '优先客服支持' + }, + commerce: { + type: '商业OEM版', + label: '商业OEM', + typeDesc: '商业OEM版本', + solution: '将强大的记忆能力无缝嵌入您的SaaS产品中。', + targetAudience: '需要集成解决方案的大型企业、SaaS厂商及系统集成商。', + priceDesc: '本地化部署', + supportServices: '标准客服支持', + flexibleDeployment: '支持在数据中心进行本地化部署', + reliableGuarantee: '99.9% SLA保障' + }, + mostPopular: '最受欢迎', + startedBtn: '立即开始', + choosePlanBtn: '选择方案', + contactBtn: '联系我们', + memoryCapacity: '记忆容量:', + entries: '条', + intelligentSearchFrequency: '智能搜索次数:', + timesMonth: '次/月', + supportServices: '支持服务:', + flexibleDeployment: '灵活部署:', + reliableGuarantee: '可靠保障:', + alertTitle: '知识产权授权提醒', + alertContent: '请注意:使用某些AI模型(如GPT-4、Claude等)可能涉及第三方API调用费用,这些费用不包含在Memory Bear平台订阅费中。您需要单独向模型提供商支付相关费用。Memory Bear仅收取平台管理和服务费,不承担第三方API的使用费用。', + currentAccountType: '当前账户类型', + validUntil: '有效期至', + orderHistory: '订单记录', + order_no: '订单号', + product_type: '套餐名称', + payable_amount: '订单金额', + status: '订单状态', + pay_time: '支付时间', + viewDetail: '查看详情', + PENDING: '待审核', + APPROVED: '审核通过', + REJECTED: '审核不通过', + allStatus: '全部状态', + allTime: '全部时间', + today: '今天', + week: '最近一周', + month: '最近一月', + threeMonth: '最近三个月', + year: '最近一年', + searchPlaceholder: '搜索订单号', + allType: '全部套餐', + orderDetail: '订单详情', + orderInfo: '订单信息', + orderPayInfo: '支付信息', + create_time: '创建时间', + }, }, } \ No newline at end of file diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx index 06032d5b..e7624994 100644 --- a/web/src/routes/index.tsx +++ b/web/src/routes/index.tsx @@ -58,6 +58,9 @@ const componentMap: Record>> = EmotionEngine: lazy(() => import('@/views/EmotionEngine')), StatementDetail: lazy(() => import('@/views/UserMemoryDetail/pages/StatementDetail')), SelfReflectionEngine: lazy(() => import('@/views/SelfReflectionEngine')), + OrderPayment: lazy(() => import('@/views/OrderPayment')), + OrderHistory: lazy(() => import('@/views/OrderHistory')), + Pricing: lazy(() => import('@/views/Pricing')), Login: lazy(() => import('@/views/Login')), InviteRegister: lazy(() => import('@/views/InviteRegister')), NoPermission: lazy(() => import('@/views/NoPermission')), diff --git a/web/src/routes/routes.json b/web/src/routes/routes.json index d4f6a12e..45e187d6 100644 --- a/web/src/routes/routes.json +++ b/web/src/routes/routes.json @@ -5,6 +5,9 @@ { "path": "/user-management", "element": "UserManagement" }, { "path": "/model", "element": "ModelManagement" }, { "path": "/space", "element": "SpaceManagement" }, + { "path": "/pricing", "element": "Pricing" }, + { "path": "/order-pay", "element": "OrderPayment" }, + { "path": "/orders", "element": "OrderHistory" }, { "path": "/no-permission", "element": "NoPermission" } ] }, diff --git a/web/src/views/OrderHistory/components/OrderDetail.tsx b/web/src/views/OrderHistory/components/OrderDetail.tsx new file mode 100644 index 00000000..562397be --- /dev/null +++ b/web/src/views/OrderHistory/components/OrderDetail.tsx @@ -0,0 +1,76 @@ +import { forwardRef, useImperativeHandle, useState, useCallback } from 'react'; +import { Descriptions } from 'antd'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; + +import type { Order, OrderDetailRef } from '../types' +import RbModal from '@/components/RbModal' +import { getOrderDetail } from '@/api/order' +import { STATUS } from '../index'; + + +const OrderDetail = forwardRef((_props, ref) => { + const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + const [data, setData] = useState({}) + + // 封装取消方法,添加关闭弹窗逻辑 + const handleClose = () => { + setVisible(false); + }; + + const handleOpen = (order: Order) => { + setVisible(true); + getOrderDetail(order.order_no) + .then(res => { + setData(res as Order) + }) + }; + const formatItems = useCallback(() => { + if (!data) return [] + return ['order_no', 'product_type', 'payable_amount', 'status', 'pay_time', 'create_time'].map(key => { + const value = (data as any)[key] + return { + key, + label: t(`pricing.${key}`), + children: ['pay_time', 'create_time'].includes(key) && value + ? dayjs(value).format('YYYY-MM-DD HH:mm:ss') + : key === 'status' && value + ? t(`pricing.${STATUS[value as keyof typeof STATUS].key}`) + : key === 'product_type' && value + ? t(`pricing.${value.toLowerCase()}.type`) + : value + } + }) + }, [data]) + const formatPayItems = useCallback(() => { + if (!data) return [] + return ['pay_txn_id', 'payer'].map(key => ({ + key, + label: t(`pricing.${key}`), + children: (data as any)[key] + })) + }, [data]) + + // 暴露给父组件的方法 + useImperativeHandle(ref, () => ({ + handleOpen, + handleClose + })); + // ['pay_txn_id', 'payer'] + // ['pay_txn_id', 'payer'] + return ( + + + + + ); +}); + +export default OrderDetail; \ No newline at end of file diff --git a/web/src/views/OrderHistory/index.tsx b/web/src/views/OrderHistory/index.tsx new file mode 100644 index 00000000..1dd31376 --- /dev/null +++ b/web/src/views/OrderHistory/index.tsx @@ -0,0 +1,227 @@ +import React, { useRef, useState, useEffect } from 'react'; +import { Button, Space, Select, Flex } from 'antd'; +import { useTranslation } from 'react-i18next'; +import type { ColumnsType } from 'antd/es/table'; +import type { SelectProps } from 'antd/es/select' +import dayjs from 'dayjs'; + +import Table, { type TableRef } from '@/components/Table' +import StatusTag from '@/components/StatusTag' +import { getOrderListUrl, getOrderStatus } from '@/api/order' +import { formatDateTime } from '@/utils/format'; +import type { Order, OrderDetailRef, Query } from './types' +import OrderDetail from './components/OrderDetail' +import SearchInput from '@/components/SearchInput' +import { PRICE_LIST } from '@/views/Pricing' + + +export const STATUS = { + 100: { + status: 'warning', + key: 'PENDING' + }, + 150: { + key: 'APPROVED', + status: 'success' + }, + 500: { + key: 'REJECTED', + status: 'error' + } +} +const OrderHistory: React.FC = () => { + const { t } = useTranslation(); + const orderDetailRef = useRef(null) + const tableRef = useRef(null); + const [query, setQuery] = useState({ + status: null, + product_type: null, + start_time: null, + end_time: null + } as Query) + const [statusOptions, setStatusOptions] = useState([]) + const [timeType, setTimeType] = useState('all') + const timeOptions = [ + { label: t('pricing.allTime'), value: 'all' }, + { label: t('pricing.today'), value: 'today' }, + { label: t('pricing.week'), value: '7d' }, + { label: t('pricing.month'), value: '1month' }, + { label: t('pricing.threeMonth'), value: '3month' }, + { label: t('pricing.year'), value: '1year' }, + ] + const productTypeOptions = [ + { label: t('pricing.allType'), value: null }, + ...PRICE_LIST.map(vo => ({ + label: t(`pricing.${vo.type}.type`), + value: vo.type + })) + ] + + const handleView = (order: Order) => { + orderDetailRef.current?.handleOpen(order) + } + + useEffect(() => { + getStatus() + }, []) + const getStatus = () => { + getOrderStatus() + .then(res => { + const response = res as Record + setStatusOptions([ + { + label: t(`pricing.allStatus`), + value: null + }, + ...Object.keys(response).map(key => ({ + label: t(`pricing.${key}`), + value: response[key].value + })) + ]) + }) + } + const handleChangeStatus = (value: string) => { + if (value !== query.status) { + setQuery(prev => ({ + ...prev, + status: value + })) + } + } + const handleChangeType = (value: string) => { + if (value !== query.product_type) { + setQuery(prev => ({ + ...prev, + product_type: value + })) + } + } + const handleChangeTime = (value: string) => { + setTimeType(value) + let start_time = null; + let end_time: number | null = dayjs().endOf('day').valueOf() + + switch(value) { + case 'all': + start_time = null; + end_time = null + break + case 'today': + start_time = dayjs().startOf('day').valueOf() + break + case '7d': + start_time = dayjs().subtract(7, 'day').startOf('day').valueOf() + break + case '1month': + start_time = dayjs().subtract(1, 'month').startOf('day').valueOf() + break + case '3month': + start_time = dayjs().subtract(3, 'month').startOf('day').valueOf() + break + case '1year': + start_time = dayjs().subtract(1, 'year').startOf('day').valueOf() + break + } + setQuery(prev => ({ + ...prev, + start_time, + end_time + })) + } + // 表格列配置 + const columns: ColumnsType = [ + { + title: t('pricing.order_no'), + dataIndex: 'order_no', + key: 'order_no', + fixed: 'left', + }, + { + title: t('pricing.product_type'), + dataIndex: 'product_type', + key: 'product_type', + render: (type) => t(`pricing.${type.toLowerCase()}.type`) + }, + { + title: t('pricing.payable_amount'), + dataIndex: 'payable_amount', + key: 'payable_amount', + render: (amount: number) => `¥${amount}`, + }, + { + title: t('pricing.status'), + dataIndex: 'status', + key: 'status', + render: (status: number) => + }, + { + title: t('pricing.pay_time'), + dataIndex: 'pay_time', + key: 'pay_time', + render: (pay_time: unknown) => formatDateTime(pay_time as string, 'YYYY-MM-DD HH:mm:ss'), + }, + { + title: t('common.operation'), + key: 'action', + fixed: 'right', + render: (_, record) => ( + + + + ), + }, + ]; + + return ( +
+ + + + + + + + + + + + +