This commit is contained in:
王子琦
2026-01-13 16:30:16 +08:00
parent 6af59d985f
commit fecc30b7f0
8 changed files with 167 additions and 15 deletions

View File

@@ -1,4 +1,4 @@
package com.flower.confession;
package com.flower.confession;
import com.flower.common.ApiException;
import com.flower.common.ApiResponse;
@@ -41,6 +41,7 @@ public class ConfessionController {
if (!order.getUserId().equals(AuthContext.get().getId())) {
throw new ApiException(403, "无权限");
}
ensureOrderPaid(order);
Confession confession = new Confession();
confession.setOrderId(order.getId());
confession.setUserId(order.getUserId());
@@ -68,6 +69,9 @@ public class ConfessionController {
if (!confession.getUserId().equals(AuthContext.get().getId())) {
throw new ApiException(403, "无权限");
}
Order order = orderRepository.findById(confession.getOrderId())
.orElseThrow(() -> new ApiException(404, "订单不存在"));
ensureOrderPaid(order);
confession.setTitle(request.getTitle());
confession.setMessage(request.getMessage());
confession.setImageUrl(request.getImageUrl());
@@ -122,4 +126,11 @@ public class ConfessionController {
private String sender;
private String content;
}
private void ensureOrderPaid(Order order) {
String status = order.getStatus();
if (!"PAID".equals(status) && !"SHIPPED".equals(status) && !"COMPLETED".equals(status)) {
throw new ApiException(400, "请至少支付后再定制告白");
}
}
}

View File

@@ -17,19 +17,25 @@ import AdminReviews from '../views/admin/AdminReviews.vue';
Vue.use(Router);
const originalPush = Router.prototype.push;
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch((err) => err);
};
const router = new Router({
routes: [
{ path: '/', component: Home },
{ path: '/product/:id', component: ProductDetail },
{ path: '/product/:id', component: ProductDetail, meta: { requiresAuth: true } },
{ path: '/login', component: Login },
{ path: '/register', component: Register },
{ path: '/orders', component: Orders },
{ path: '/profile', component: Profile },
{ path: '/confession/create/:orderId', component: ConfessionCreate },
{ path: '/orders', component: Orders, meta: { requiresAuth: true } },
{ path: '/profile', component: Profile, meta: { requiresAuth: true } },
{ path: '/confession/create/:orderId', component: ConfessionCreate, meta: { requiresAuth: true } },
{ path: '/gift/:code', component: GiftPage },
{
path: '/admin',
component: AdminLayout,
meta: { requiresAuth: true },
children: [
{ path: '', component: AdminDashboard },
{ path: 'products', component: AdminProducts },
@@ -41,4 +47,15 @@ const router = new Router({
]
});
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
const token = localStorage.getItem('token');
if (!token) {
next({ path: '/login', query: { redirect: to.fullPath } });
return;
}
}
next();
});
export default router;

View File

@@ -37,7 +37,10 @@
<el-button type="primary" @click="submit">生成页面</el-button>
</div>
<div v-if="confession" class="result">
<p>专属链接{{ giftUrl }}</p>
<p>
专属链接
<a class="gift-link" :href="giftUrl" target="_blank" rel="noopener noreferrer">{{ giftUrl }}</a>
</p>
<img :src="qrUrl" class="qr" />
</div>
</el-card>
@@ -178,6 +181,13 @@ export default {
.result {
margin-top: 16px;
}
.gift-link {
color: #409eff;
text-decoration: none;
}
.gift-link:hover {
text-decoration: underline;
}
.preview img {
margin-top: 8px;
width: 200px;

View File

@@ -4,8 +4,8 @@
<el-header class="header">
<div class="logo">植愈线上花店</div>
<div>
<el-button type="text" @click="$router.push('/orders')">我的订单</el-button>
<el-button type="text" @click="$router.push('/profile')">个人中心</el-button>
<el-button v-if="isLoggedIn" type="text" @click="$router.push('/orders')">我的订单</el-button>
<el-button v-if="isLoggedIn" type="text" @click="$router.push('/profile')">个人中心</el-button>
<el-button v-if="isAdmin" type="text" @click="$router.push('/admin')">后台管理</el-button>
<el-button v-if="!isLoggedIn" type="primary" @click="$router.push('/login')">登录</el-button>
<el-button v-else type="danger" @click="handleLogout">注销</el-button>

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="page">
<el-card>
<el-button type="text" @click="$router.push('/')">返回首页</el-button>
@@ -14,23 +14,63 @@
<el-table-column prop="order.totalAmount" label="金额" />
<el-table-column label="操作">
<template slot-scope="scope">
<el-button v-if="scope.row.order.status === 'CREATED'" type="text" @click="pay(scope.row.order.id)">支付</el-button>
<el-button v-if="scope.row.order.status === 'CREATED'" type="text" @click="cancel(scope.row.order.id)">取消</el-button>
<el-button type="text" @click="goConfession(scope.row.order.id)">告白弹幕</el-button>
<el-button
v-if="scope.row.order.status === 'CREATED'"
type="text"
@click="pay(scope.row.order.id)"
>支付</el-button>
<el-button
v-if="scope.row.order.status === 'CREATED'"
type="text"
@click="cancel(scope.row.order.id)"
>取消</el-button>
<el-button
v-if="canConfession(scope.row.order.status)"
type="text"
@click="goConfession(scope.row.order.id)"
>告白弹幕</el-button>
<el-button
v-if="canReview(scope.row.order.status)"
type="text"
@click="openReview(scope.row)"
>评价</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog title="订单评价" :visible.sync="showReview">
<el-form :model="reviewForm">
<el-form-item label="评分">
<el-rate v-model="reviewForm.rating" />
</el-form-item>
<el-form-item label="内容">
<el-input type="textarea" v-model="reviewForm.content" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="showReview = false">取消</el-button>
<el-button type="primary" @click="submitReview">提交</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listOrders, payOrder, cancelOrder } from '../api/order';
import { createReview } from '../api/review';
export default {
data() {
return {
orders: []
orders: [],
showReview: false,
reviewForm: {
orderId: null,
productId: null,
rating: 5,
content: ''
}
};
},
created() {
@@ -66,6 +106,32 @@ export default {
},
goConfession(orderId) {
this.$router.push(`/confession/create/${orderId}`);
},
canReview(status) {
return status === 'PAID' || status === 'COMPLETED' || status === 'SHIPPED';
},
canConfession(status) {
return status === 'PAID' || status === 'COMPLETED' || status === 'SHIPPED';
},
openReview(row) {
const firstItem = row.items && row.items[0];
if (!firstItem) {
this.$message.warning('订单无商品');
return;
}
this.reviewForm = {
orderId: row.order.id,
productId: firstItem.productId,
rating: 5,
content: ''
};
this.showReview = true;
},
submitReview() {
createReview(this.reviewForm).then(() => {
this.$message.success('提交成功,等待审核');
this.showReview = false;
});
}
}
};

View File

@@ -27,6 +27,16 @@
</el-col>
</el-row>
</el-card>
<el-card class="reviews">
<div class="review-title">用户评价</div>
<div v-if="reviews.length === 0" class="review-empty">暂无评价</div>
<div v-else>
<div v-for="item in reviews" :key="item.id" class="review-item">
<el-rate :value="item.rating" disabled />
<div class="review-content">{{ item.content }}</div>
</div>
</div>
</el-card>
</div>
</template>
@@ -34,6 +44,7 @@
import { getProduct } from '../api/product';
import { listAddresses } from '../api/address';
import { createOrder } from '../api/order';
import { listReviewsByProduct } from '../api/review';
import { codeToText } from 'element-china-area-data';
export default {
@@ -43,12 +54,14 @@ export default {
quantity: 1,
addresses: [],
addressId: null,
reviews: [],
placeholder: 'https://via.placeholder.com/400x300?text=Flower'
};
},
created() {
this.loadDetail();
this.loadAddresses();
this.loadReviews();
},
methods: {
loadDetail() {
@@ -92,6 +105,11 @@ export default {
const order = res.data.data.order;
this.$router.push(`/orders?highlight=${order.id}`);
});
},
loadReviews() {
listReviewsByProduct(this.$route.params.id).then((res) => {
this.reviews = res.data.data || [];
});
}
}
};
@@ -123,4 +141,23 @@ export default {
margin-right: 12px;
width: 300px;
}
.reviews {
margin-top: 16px;
}
.review-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}
.review-empty {
color: #909399;
}
.review-item {
padding: 10px 0;
border-bottom: 1px solid #f0f0f0;
}
.review-content {
margin-top: 6px;
color: #606266;
}
</style>

View File

@@ -5,7 +5,13 @@
<el-table-column prop="productId" label="商品ID" />
<el-table-column prop="rating" label="评分" />
<el-table-column prop="content" label="内容" />
<el-table-column prop="status" label="状态" />
<el-table-column label="状态">
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 'PENDING'" type="warning">待审核</el-tag>
<el-tag v-else-if="scope.row.status === 'APPROVED'" type="success">通过</el-tag>
<el-tag v-else type="danger">拒绝</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-select v-model="scope.row.status">

View File

@@ -4,7 +4,12 @@
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="username" label="账号" />
<el-table-column prop="nickname" label="昵称" />
<el-table-column prop="role" label="角色" />
<el-table-column label="角色">
<template slot-scope="scope">
<el-tag v-if="scope.row.role === 'ADMIN'" type="danger">管理员</el-tag>
<el-tag v-else type="info">用户</el-tag>
</template>
</el-table-column>
<el-table-column label="状态">
<template slot-scope="scope">
<el-tag v-if="scope.row.disabled" type="danger">禁用</el-tag>