From e7d9f47c61fefd49361309e5d099527ddb0a18b9 Mon Sep 17 00:00:00 2001 From: Developer Date: Mon, 23 Mar 2026 16:38:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=83=E9=94=80=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E3=80=81=E5=8F=91=E7=A5=A8=E9=A1=B5=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E3=80=81=E5=89=8D=E7=AB=AF=E4=B8=AD=E6=96=87=E5=8C=96?= =?UTF-8?q?=E3=80=81=E8=AE=A2=E5=8D=95=E8=A1=A8=E8=BF=81=E7=A7=BB=E5=8F=8A?= =?UTF-8?q?=E5=A4=9A=E9=A1=B9=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端: - 发票页完整重构:智能按钮状态、防重复开票、空态引导、订单多选 - 全局中文化:Ant Design Vue locale配置、清除残留英文UI文本 - 新增关于我们、联系我们页面 - 首页活动专区、搜索页、Skill详情等多处优化 后端: - 订单模块:新增original_amount/promotion_deduct_amount字段及DB迁移 - 促销系统:完善促销规则、过期任务、批量查询等 - 新增RateLimit注解及拦截器、CORS过滤器、Health检查接口 - Logback日志配置、points枚举修复等 --- frontend/.env.development | 4 +- frontend/src/App.vue | 5 +- frontend/src/components/SkillCard.vue | 2 +- frontend/src/layouts/MainLayout.vue | 12 +- frontend/src/router/index.js | 12 + frontend/src/service/apiService.js | 1 + frontend/src/stores/admin.js | 16 +- frontend/src/stores/skill.js | 3 +- frontend/src/views/admin/comments.vue | 10 +- frontend/src/views/admin/community.vue | 2 +- frontend/src/views/admin/promotions.vue | 2 +- frontend/src/views/admin/skills.vue | 159 +++++- frontend/src/views/customize/index.vue | 8 +- frontend/src/views/help/article.vue | 11 +- frontend/src/views/home/index.vue | 131 ++++- frontend/src/views/join-us/index.vue | 92 +++- frontend/src/views/legal/about.vue | 232 +++++++++ frontend/src/views/legal/contact.vue | 189 ++++++++ frontend/src/views/order/detail.vue | 6 +- frontend/src/views/skill/detail.vue | 14 +- frontend/src/views/skill/list.vue | 1 + frontend/src/views/skill/search.vue | 49 +- frontend/src/views/user/favorites.vue | 18 +- frontend/src/views/user/forgot-password.vue | 10 + frontend/src/views/user/invite.vue | 20 +- frontend/src/views/user/invoices.vue | 308 ++++++++++-- frontend/src/views/user/notifications.vue | 26 +- frontend/src/views/user/skills.vue | 32 +- openclaw-backend/openclaw-backend/.gitignore | 13 + .../com/openclaw/OpenclawApplication.java | 3 +- .../com/openclaw/annotation/RateLimit.java | 24 + .../mq/consumer/InviteEventConsumer.java | 15 +- .../mq/consumer/OrderEventConsumer.java | 15 +- .../mq/consumer/PaymentEventConsumer.java | 12 +- .../common/mq/consumer/UserEventConsumer.java | 10 +- .../com/openclaw/config/CorsFilterConfig.java | 39 ++ .../com/openclaw/config/OpenApiConfig.java | 48 +- .../com/openclaw/config/SecurityConfig.java | 2 + .../com/openclaw/config/WebMvcConfig.java | 25 +- .../java/com/openclaw/constant/ErrorCode.java | 2 + .../openclaw/interceptor/AuthInterceptor.java | 17 + .../interceptor/RateLimitInterceptor.java | 71 +++ .../controller/ActivityController.java | 2 + .../controller/AdminActivityController.java | 2 + .../admin/controller/AdminController.java | 14 + .../module/admin/dto/AdminSkillUpdateDTO.java | 17 + .../module/admin/service/AdminService.java | 2 + .../admin/service/impl/AdminServiceImpl.java | 32 ++ .../common/controller/ConfigController.java | 2 + .../controller/FileUploadController.java | 2 + .../common/controller/HealthController.java | 70 +++ .../common/controller/StatsController.java | 2 + .../controller/AdminCommunityController.java | 2 + .../controller/CommunityController.java | 2 + .../controller/AnnouncementController.java | 2 + .../content/controller/BannerController.java | 2 + .../controller/AdminCouponController.java | 2 + .../coupon/controller/CouponController.java | 28 +- .../service/impl/CouponServiceImpl.java | 6 + .../controller/CustomizationController.java | 2 + .../controller/DeveloperController.java | 4 +- .../controller/FeedbackController.java | 2 + .../service/impl/FeedbackServiceImpl.java | 3 + .../help/controller/AdminHelpController.java | 2 + .../help/controller/HelpController.java | 2 + .../invite/controller/InviteController.java | 2 + .../invoice/controller/InvoiceController.java | 2 + .../controller/OperationLogController.java | 2 + .../AdminMemberLevelController.java | 2 + .../member/controller/MemberController.java | 2 + .../controller/NotificationController.java | 3 + .../order/controller/OrderController.java | 17 +- .../openclaw/module/order/entity/Order.java | 4 +- .../module/order/entity/OrderItem.java | 4 +- .../order/repository/OrderRepository.java | 8 + .../order/service/impl/OrderServiceImpl.java | 171 +++++-- .../openclaw/module/order/vo/OrderItemVO.java | 5 +- .../module/order/vo/OrderPreviewVO.java | 4 +- .../com/openclaw/module/order/vo/OrderVO.java | 2 + .../controller/MockPaymentController.java | 2 + .../payment/controller/PaymentController.java | 10 +- .../points/controller/PointsController.java | 3 + .../service/impl/PointsServiceImpl.java | 23 + .../controller/PromotionController.java | 2 + .../promotion/dto/PromotionCreateDTO.java | 3 +- .../module/promotion/dto/PromotionRules.java | 17 + .../promotion/dto/PromotionUpdateDTO.java | 2 +- .../repository/PromotionRepository.java | 4 + .../promotion/service/PromotionService.java | 3 + .../service/impl/PromotionServiceImpl.java | 316 ++++++++++-- .../promotion/task/PromotionExpireTask.java | 28 ++ .../promotion/vo/PromotionDetailVO.java | 3 +- .../module/promotion/vo/SkillPromotionVO.java | 1 + .../skill/controller/CategoryController.java | 7 +- .../skill/controller/SkillController.java | 23 +- .../controller/SkillFavoriteController.java | 15 +- .../skill/service/impl/SkillServiceImpl.java | 24 +- .../user/controller/UserController.java | 30 +- .../user/controller/WechatAuthController.java | 13 +- .../com/openclaw/module/user/entity/User.java | 2 + .../user/service/impl/UserServiceImpl.java | 40 +- .../src/main/resources/application.yml | 31 +- .../src/main/resources/db/init.sql | 5 +- .../V20240113__fix_points_records_enum.sql | 14 + ...40114__add_promotion_columns_to_orders.sql | 9 + .../src/main/resources/logback-spring.xml | 85 ++++ 测试服务器部署指南.md | 455 ++++++++++++++++++ 107 files changed, 2964 insertions(+), 309 deletions(-) create mode 100644 frontend/src/views/legal/about.vue create mode 100644 frontend/src/views/legal/contact.vue create mode 100644 openclaw-backend/openclaw-backend/.gitignore create mode 100644 openclaw-backend/openclaw-backend/src/main/java/com/openclaw/annotation/RateLimit.java create mode 100644 openclaw-backend/openclaw-backend/src/main/java/com/openclaw/config/CorsFilterConfig.java create mode 100644 openclaw-backend/openclaw-backend/src/main/java/com/openclaw/interceptor/RateLimitInterceptor.java create mode 100644 openclaw-backend/openclaw-backend/src/main/java/com/openclaw/module/admin/dto/AdminSkillUpdateDTO.java create mode 100644 openclaw-backend/openclaw-backend/src/main/java/com/openclaw/module/common/controller/HealthController.java create mode 100644 openclaw-backend/openclaw-backend/src/main/java/com/openclaw/module/promotion/dto/PromotionRules.java create mode 100644 openclaw-backend/openclaw-backend/src/main/java/com/openclaw/module/promotion/task/PromotionExpireTask.java create mode 100644 openclaw-backend/openclaw-backend/src/main/resources/db/migration/V20240113__fix_points_records_enum.sql create mode 100644 openclaw-backend/openclaw-backend/src/main/resources/db/migration/V20240114__add_promotion_columns_to_orders.sql create mode 100644 openclaw-backend/openclaw-backend/src/main/resources/logback-spring.xml create mode 100644 测试服务器部署指南.md diff --git a/frontend/.env.development b/frontend/.env.development index 28b0bba..e7ecb88 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1,2 +1,2 @@ -# 开发环境 API 基础地址 -VITE_API_BASE_URL=http://localhost:8080/api/v1 +# 开发环境 API 基础地址(使用相对路径,由 Vite proxy 转发到后端) +VITE_API_BASE_URL=/api/v1 diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 3957868..eb9bb6d 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,8 +1,11 @@ diff --git a/frontend/src/views/legal/contact.vue b/frontend/src/views/legal/contact.vue new file mode 100644 index 0000000..6dda1f3 --- /dev/null +++ b/frontend/src/views/legal/contact.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/frontend/src/views/order/detail.vue b/frontend/src/views/order/detail.vue index caa8e0b..8748dd1 100644 --- a/frontend/src/views/order/detail.vue +++ b/frontend/src/views/order/detail.vue @@ -13,7 +13,7 @@
订单号 - {{ order.id }} + {{ order.orderNo || order.id }}
创建时间 @@ -68,7 +68,7 @@ 去支付 取消订单 - diff --git a/frontend/src/views/user/invoices.vue b/frontend/src/views/user/invoices.vue index 0b082be..2ee2065 100644 --- a/frontend/src/views/user/invoices.vue +++ b/frontend/src/views/user/invoices.vue @@ -2,10 +2,21 @@
@@ -56,7 +67,23 @@
- +
+ + + + 去逛逛 + + + 立即申请 + + +
@@ -76,9 +103,54 @@ :confirm-loading="submitting" @ok="handleApply" @cancel="resetForm" - width="560px" + width="600px" + :ok-text="'提交申请'" + :ok-button-props="{ disabled: selectedOrderIds.length === 0 }" > + + + + +
+ +
+
+
+ + +
+ 开票金额 + ¥{{ selectedAmount.toFixed(2) }} + ({{ selectedOrderIds.length }} 笔订单) +
+ + + 个人 @@ -108,28 +180,6 @@ - - - - - - -
- - {{ order.orderNo }} - {{ order.skillName }} (¥{{ order.totalAmount }}) - -
- -
-
- @@ -179,8 +229,6 @@ const form = reactive({ const rules = { type: [{ required: true, message: '请选择发票类型' }], titleName: [{ required: true, message: '请输入发票抬头' }], - amount: [{ required: true, message: '请输入开票金额' }], - orderIds: [{ required: true, message: '请选择关联订单' }], email: [ { required: true, message: '请输入接收邮箱' }, { type: 'email', message: '请输入有效邮箱地址' } @@ -212,21 +260,61 @@ const fetchInvoices = async () => { } } -const paidOrders = computed(() => { - return orderStore.orders.filter(o => (o.status === 'paid' || o.status === 'completed') && Number(o.totalAmount) > 0) +const invoicedOrderIds = computed(() => { + const ids = new Set() + invoices.value + .filter(inv => inv.status === 'pending' || inv.status === 'approved') + .forEach(inv => { + if (inv.orderIds) { + String(inv.orderIds).split(',').forEach(id => ids.add(id.trim())) + } + }) + return ids }) +const invoicableOrders = computed(() => { + return orderStore.orders.filter(o => { + const isPaid = o.status === 'paid' || o.status === 'completed' + const hasAmount = Number(o.totalAmount) > 0 + const notInvoiced = !invoicedOrderIds.value.has(String(o.id)) + return isPaid && hasAmount && notInvoiced + }) +}) + +const invoicableAmount = computed(() => { + return invoicableOrders.value.reduce((sum, o) => sum + Number(o.totalAmount || 0), 0) +}) + +const selectedAmount = computed(() => { + return invoicableOrders.value + .filter(o => selectedOrderIds.value.includes(o.id)) + .reduce((sum, o) => sum + Number(o.totalAmount || 0), 0) +}) + +const isAllSelected = computed(() => { + return invoicableOrders.value.length > 0 && selectedOrderIds.value.length === invoicableOrders.value.length +}) + +const toggleSelectAll = () => { + if (isAllSelected.value) { + selectedOrderIds.value = [] + } else { + selectedOrderIds.value = invoicableOrders.value.map(o => o.id) + } +} + watch(selectedOrderIds, (ids) => { form.orderIds = ids.join(',') - const total = paidOrders.value - .filter(o => ids.includes(o.id)) - .reduce((sum, o) => sum + Number(o.totalAmount || 0), 0) - form.amount = total > 0 ? Number(total.toFixed(2)) : null + form.amount = selectedAmount.value > 0 ? Number(selectedAmount.value.toFixed(2)) : null }) +const openApplyModal = () => { + showApplyModal.value = true +} + const handleApply = async () => { if (selectedOrderIds.value.length === 0) { - message.warning('请选择关联订单') + message.warning('请选择开票订单') return } try { @@ -238,7 +326,7 @@ const handleApply = async () => { try { const res = await invoiceApi.apply({ ...form }) if (res.code === 200) { - message.success('发票申请已提交') + message.success('发票申请已提交,请等待审核') showApplyModal.value = false resetForm() fetchInvoices() @@ -246,7 +334,7 @@ const handleApply = async () => { message.error(res.message || '申请失败') } } catch (e) { - message.error('申请失败') + message.error('提交失败,请稍后重试') } finally { submitting.value = false } @@ -270,7 +358,11 @@ const resetForm = () => { onMounted(async () => { fetchInvoices() if (userStore.user) { - await orderStore.loadUserOrders(userStore.user.id) + try { + await orderStore.loadUserOrders(userStore.user.id) + } catch (e) { + message.error('加载订单数据失败,请刷新页面重试') + } } }) @@ -289,12 +381,42 @@ onMounted(async () => { color: #303133; margin: 0; } + + .header-actions { + display: flex; + align-items: center; + gap: 12px; + + .invoicable-hint { + font-size: 13px; + color: #52c41a; + font-weight: 500; + } + } } .filter-bar { margin-bottom: 20px; } + .empty-state { + padding: 60px 0; + text-align: center; + + .empty-desc { + p { + margin: 0; + color: #909399; + font-size: 14px; + } + .empty-hint { + font-size: 12px; + color: #c0c4cc; + margin-top: 4px; + } + } + } + .invoice-list { display: flex; flex-direction: column; @@ -374,4 +496,110 @@ onMounted(async () => { text-align: right; } } + +.order-label { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + + .select-all-link { + font-size: 12px; + font-weight: 400; + color: #1890ff; + cursor: pointer; + } +} + +.order-select-list { + max-height: 240px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 8px; +} + +.order-select-item { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 10px 12px; + border: 1px solid #f0f0f0; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + + &:hover { + border-color: #d9d9d9; + background: #fafafa; + } + + &.selected { + border-color: #1890ff; + background: #e6f7ff; + } + + .order-info { + flex: 1; + min-width: 0; + + .order-main { + display: flex; + justify-content: space-between; + align-items: center; + + .order-name { + font-size: 14px; + color: #303133; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .order-amount { + font-size: 14px; + font-weight: 600; + color: #e6a23c; + flex-shrink: 0; + margin-left: 8px; + } + } + + .order-meta { + display: flex; + gap: 12px; + margin-top: 4px; + font-size: 12px; + color: #909399; + } + } +} + +.amount-summary { + display: flex; + align-items: baseline; + padding: 12px 16px; + background: #f6ffed; + border: 1px solid #b7eb8f; + border-radius: 6px; + margin-bottom: 16px; + + .amount-label { + font-size: 14px; + color: #606266; + margin-right: 8px; + } + + .amount-value { + font-size: 22px; + font-weight: 700; + color: #52c41a; + } + + .amount-count { + font-size: 12px; + color: #909399; + margin-left: 8px; + } +} diff --git a/frontend/src/views/user/notifications.vue b/frontend/src/views/user/notifications.vue index 03bf233..1cb69b1 100644 --- a/frontend/src/views/user/notifications.vue +++ b/frontend/src/views/user/notifications.vue @@ -35,26 +35,34 @@