feat: 删除JSP视图层,完善评价和通知系统,新增拼团模块
- 删除所有 JSP 页面(20个文件),前端完全迁移至 Vue 3 SPA - 完善评价系统:ReviewDialog 组件、用户评价历史页、评价状态检查API - 新增通知系统:Notification 实体/仓库/服务/控制器,NotificationCenter 接入真实API - 新增拼团模块:GroupBuying 全套后端和前端页面 - 修复 review check API 参数双重包装导致请求格式错误 - 修复通知 API 路径缺少 /api 前缀和响应格式处理 - MessageListenerService 集成 NotificationService 创建持久化通知 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -128,10 +128,22 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="form.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" class="w-full" />
|
||||
<el-date-picker
|
||||
v-model="form.startTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled-date="disablePastDate"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="form.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" class="w-full" />
|
||||
<el-date-picker
|
||||
v-model="form.endTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled-date="disablePastDate"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -187,6 +199,9 @@ const formRef = ref<FormInstance>()
|
||||
const flashSales = ref<FlashSale[]>([])
|
||||
const currentItem = ref<FlashSale | null>(null)
|
||||
const productOptions = ref<AdminProductRow[]>([])
|
||||
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
|
||||
const CREATE_START_LEAD_MINUTES = 5
|
||||
const CREATE_DURATION_DAYS = 1
|
||||
|
||||
const query = reactive({
|
||||
keyword: '',
|
||||
@@ -206,13 +221,16 @@ const stats = reactive<AdminFlashSaleStats>({
|
||||
endedFlashSales: 0,
|
||||
})
|
||||
|
||||
const buildDefaultStartTime = () => dayjs().add(CREATE_START_LEAD_MINUTES, 'minute').startOf('minute').format(TIME_FORMAT)
|
||||
const buildDefaultEndTime = (startTime = buildDefaultStartTime()) => dayjs(startTime).add(CREATE_DURATION_DAYS, 'day').format(TIME_FORMAT)
|
||||
|
||||
const form = reactive({
|
||||
id: 0,
|
||||
productId: undefined as number | undefined,
|
||||
flashPrice: 0.01,
|
||||
flashStock: 1,
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
startTime: buildDefaultStartTime(),
|
||||
endTime: buildDefaultEndTime(),
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
@@ -254,13 +272,38 @@ const getStockRate = (item: FlashSale) => {
|
||||
return Math.round((item.remainingStock / item.flashStock) * 100)
|
||||
}
|
||||
|
||||
const disablePastDate = (date: Date) => dayjs(date).endOf('day').isBefore(dayjs())
|
||||
|
||||
const validateTimeRange = () => {
|
||||
const now = dayjs()
|
||||
const startTime = dayjs(form.startTime)
|
||||
const endTime = dayjs(form.endTime)
|
||||
|
||||
if (!startTime.isValid() || !endTime.isValid()) {
|
||||
ElMessage.error('开始时间或结束时间格式无效')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!startTime.isAfter(now)) {
|
||||
ElMessage.error('开始时间必须晚于当前时间')
|
||||
return false
|
||||
}
|
||||
|
||||
if (!endTime.isAfter(startTime)) {
|
||||
ElMessage.error('结束时间必须晚于开始时间')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.id = 0
|
||||
form.productId = undefined
|
||||
form.flashPrice = 0.01
|
||||
form.flashStock = 1
|
||||
form.startTime = ''
|
||||
form.endTime = ''
|
||||
form.startTime = buildDefaultStartTime()
|
||||
form.endTime = buildDefaultEndTime(form.startTime)
|
||||
}
|
||||
|
||||
const loadStats = async () => {
|
||||
@@ -315,6 +358,7 @@ const submitForm = async () => {
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
if (!validateTimeRange()) return
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
@@ -435,19 +479,20 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.mini-stat {
|
||||
@apply rounded-xl text-white p-5 shadow-sm;
|
||||
|
||||
&.purple { background: linear-gradient(135deg, #8b5cf6, #7c3aed); }
|
||||
&.red { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
||||
&.orange { background: linear-gradient(135deg, #f59e0b, #ea580c); }
|
||||
&.gray { background: linear-gradient(135deg, #64748b, #475569); }
|
||||
@apply rounded-xl p-5 shadow-sm;
|
||||
background: #fffaf2;
|
||||
color: #171715;
|
||||
border: 1px solid #d8cebf;
|
||||
box-shadow: 0 10px 24px rgba(23, 22, 20, 0.04);
|
||||
|
||||
&__value { @apply text-3xl font-bold; }
|
||||
&__label { @apply text-sm opacity-90 mt-2; }
|
||||
}
|
||||
|
||||
.panel-card {
|
||||
@apply bg-white rounded-xl shadow-sm p-5;
|
||||
@apply bg-white rounded-xl p-5;
|
||||
border: 1px solid #d8cebf;
|
||||
box-shadow: 0 10px 24px rgba(23, 22, 20, 0.04);
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
@@ -468,7 +513,7 @@ onMounted(() => {
|
||||
height: 56px;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid #d8cebf;
|
||||
}
|
||||
|
||||
.detail-image {
|
||||
@@ -499,7 +544,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.flash-price {
|
||||
@apply text-3xl font-bold text-rose-500;
|
||||
@apply text-3xl font-bold;
|
||||
color: #171715;
|
||||
}
|
||||
|
||||
.origin-price {
|
||||
|
||||
Reference in New Issue
Block a user