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 @@