Skip to content

Commit 814b463

Browse files
committed
商品:完善后台商品评论功能
1 parent 2e68a52 commit 814b463

File tree

5 files changed

+805
-0
lines changed

5 files changed

+805
-0
lines changed

src/api/mall/product/comment.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import request from '@/config/axios'
2+
3+
export interface CommentVO {
4+
id: number
5+
userId: number
6+
userNickname: string
7+
userAvatar: string
8+
anonymous: boolean
9+
orderId: number
10+
orderItemId: number
11+
spuId: number
12+
spuName: string
13+
skuId: number
14+
visible: boolean
15+
scores: number
16+
descriptionScores: number
17+
benefitScores: number
18+
content: string
19+
picUrls: string
20+
replyStatus: boolean
21+
replyUserId: number
22+
replyContent: string
23+
replyTime: Date
24+
}
25+
26+
// 查询商品评论列表
27+
export const getCommentPage = async (params) => {
28+
return await request.get({ url: `/product/comment/page`, params })
29+
}
30+
31+
// 查询商品评论详情
32+
export const getComment = async (id: number) => {
33+
return await request.get({ url: `/product/comment/get?id=` + id })
34+
}
35+
36+
// 添加自评
37+
export const createComment = async (data: CommentVO) => {
38+
return await request.post({ url: `/product/comment/create`, data })
39+
}
40+
41+
// 显示 / 隐藏评论
42+
export const updateCommentVisible = async (data: any) => {
43+
return await request.put({ url: `/product/comment/update-visible`, data })
44+
}
45+
46+
// 商家回复
47+
export const replyComment = async (data: any) => {
48+
return await request.put({ url: `/product/comment/reply`, data })
49+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<template>
2+
<Dialog title="添加虚拟评论" v-model="dialogVisible">
3+
<el-form
4+
ref="formRef"
5+
:model="formData"
6+
:rules="formRules"
7+
label-width="100px"
8+
v-loading="formLoading"
9+
>
10+
<el-form-item label="商品" prop="spuId">
11+
<div @click="handleSelectSpu" class="w-60px h-60px">
12+
<div v-if="spuData && spuData.picUrl">
13+
<el-image :src="spuData.picUrl" />
14+
</div>
15+
<div v-else class="select-box">
16+
<Icon icon="ep:plus" />
17+
</div>
18+
</div>
19+
</el-form-item>
20+
<el-form-item label="商品规格" prop="skuId" v-if="formData.spuId">
21+
<div @click="handleSelectSku" class="w-60px h-60px">
22+
<div v-if="skuData && skuData.picUrl">
23+
<el-image :src="skuData.picUrl" />
24+
</div>
25+
<div v-else class="select-box">
26+
<Icon icon="ep:plus" />
27+
</div>
28+
</div>
29+
</el-form-item>
30+
<el-form-item label="用户头像" prop="userAvatar">
31+
<UploadImg v-model="formData.userAvatar" height="60px" width="60px" />
32+
</el-form-item>
33+
<el-form-item label="用户名称" prop="userNickname">
34+
<el-input v-model="formData.userNickname" placeholder="请输入用户名称" />
35+
</el-form-item>
36+
<el-form-item label="评论内容" prop="content">
37+
<el-input type="textarea" v-model="formData.content" />
38+
</el-form-item>
39+
<el-form-item label="评分星级" prop="scores">
40+
<el-rate v-model="formData.scores" />
41+
</el-form-item>
42+
<el-form-item label="描述星级" prop="descriptionScores">
43+
<el-rate v-model="formData.descriptionScores" />
44+
</el-form-item>
45+
<el-form-item label="服务星级" prop="benefitScores">
46+
<el-rate v-model="formData.benefitScores" />
47+
</el-form-item>
48+
<el-form-item label="评论图片" prop="picUrls">
49+
<UploadImgs v-model="formData.picUrls" :limit="9" height="60px" width="60px" />
50+
</el-form-item>
51+
</el-form>
52+
<template #footer>
53+
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
54+
<el-button @click="dialogVisible = false">取 消</el-button>
55+
</template>
56+
</Dialog>
57+
<SpuTableSelect ref="spuTableSelectRef" @change="handleSpuChange" />
58+
<SkuTableSelect ref="skuTableSelectRef" @change="handleSkuChange" :spu-id="spuData.id" />
59+
</template>
60+
<script setup lang="ts">
61+
import * as CommentApi from '@/api/mall/product/comment'
62+
import SpuTableSelect from '@/views/mall/product/comment/components/SpuTableSelect.vue'
63+
import * as ProductSpuApi from '@/api/mall/product/spu'
64+
import SkuTableSelect from '@/views/mall/product/comment/components/SkuTableSelect.vue'
65+
66+
const { t } = useI18n() // 国际化
67+
const message = useMessage() // 消息弹窗
68+
69+
const dialogVisible = ref(false) // 弹窗的是否展示
70+
const dialogTitle = ref('') // 弹窗的标题
71+
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
72+
const formType = ref('') // 表单的类型:create - 新增;update - 修改
73+
const formData = ref({
74+
id: undefined,
75+
userId: undefined,
76+
userNickname: undefined,
77+
userAvatar: undefined,
78+
spuId: undefined,
79+
spuName: undefined,
80+
skuId: undefined,
81+
scores: 5,
82+
descriptionScores: 5,
83+
benefitScores: 5,
84+
content: undefined,
85+
picUrls: []
86+
})
87+
const formRules = reactive({
88+
spuId: [{ required: true, message: '商品不能为空', trigger: 'blur' }],
89+
skuId: [{ required: true, message: '规格不能为空', trigger: 'blur' }],
90+
userAvatar: [{ required: true, message: '用户头像不能为空', trigger: 'blur' }],
91+
userNickname: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
92+
content: [{ required: true, message: '评论内容不能为空', trigger: 'blur' }],
93+
scores: [{ required: true, message: '评分星级不能为空', trigger: 'blur' }],
94+
descriptionScores: [{ required: true, message: '描述星级不能为空', trigger: 'blur' }],
95+
benefitScores: [{ required: true, message: '服务星级不能为空', trigger: 'blur' }]
96+
})
97+
const formRef = ref() // 表单 Ref
98+
const spuData = ref<ProductSpuApi.Spu>({})
99+
const skuData = ref({
100+
id: -1,
101+
name: '',
102+
picUrl: ''
103+
})
104+
105+
/** 打开弹窗 */
106+
const open = async (type: string, id?: number) => {
107+
dialogVisible.value = true
108+
dialogTitle.value = t('action.' + type)
109+
formType.value = type
110+
resetForm()
111+
// 修改时,设置数据
112+
if (id) {
113+
formLoading.value = true
114+
try {
115+
formData.value = await CommentApi.getComment(id)
116+
} finally {
117+
formLoading.value = false
118+
}
119+
}
120+
}
121+
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
122+
123+
/** 提交表单 */
124+
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
125+
const submitForm = async () => {
126+
// 校验表单
127+
if (!formRef) return
128+
const valid = await formRef.value.validate()
129+
if (!valid) return
130+
// 提交请求
131+
formLoading.value = true
132+
try {
133+
//处理评论图片
134+
const picUrls = formData.value.picUrls.map((item) => {
135+
return item?.url ? item.url : item
136+
})
137+
const data = { ...formData.value, picUrls }
138+
if (formType.value === 'create') {
139+
await CommentApi.createComment(data)
140+
message.success(t('common.createSuccess'))
141+
}
142+
dialogVisible.value = false
143+
// 发送操作成功的事件
144+
emit('success')
145+
} finally {
146+
formLoading.value = false
147+
}
148+
}
149+
150+
/** 重置表单 */
151+
const resetForm = () => {
152+
formData.value = {
153+
id: undefined,
154+
userId: undefined,
155+
userNickname: undefined,
156+
userAvatar: undefined,
157+
spuId: undefined,
158+
skuId: undefined,
159+
scores: 5,
160+
descriptionScores: 5,
161+
benefitScores: 5,
162+
content: undefined,
163+
picUrls: []
164+
}
165+
formRef.value?.resetFields()
166+
}
167+
168+
/** SPU 表格选择 */
169+
const spuTableSelectRef = ref()
170+
const handleSelectSpu = () => {
171+
spuTableSelectRef.value.open()
172+
}
173+
const handleSpuChange = (spu: ProductSpuApi.Spu) => {
174+
spuData.value = spu
175+
formData.value.spuId = spu.id
176+
}
177+
178+
/** SKU 表格选择 */
179+
const skuTableSelectRef = ref()
180+
const handleSelectSku = () => {
181+
skuTableSelectRef.value.open()
182+
}
183+
const handleSkuChange = (sku: ProductSpuApi.Sku) => {
184+
skuData.value = sku
185+
formData.value.skuId = sku.id
186+
}
187+
</script>
188+
<style>
189+
.select-box {
190+
display: flex;
191+
align-items: center;
192+
justify-content: center;
193+
border: 1px dashed var(--el-border-color-darker);
194+
border-radius: 8px;
195+
width: 100%;
196+
height: 100%;
197+
}
198+
</style>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<template>
2+
<Dialog v-model="dialogVisible" :appendToBody="true" title="选择规格" width="700">
3+
<el-table v-loading="loading" :data="list" show-overflow-tooltip>
4+
<el-table-column label="#" width="55">
5+
<template #default="{ row }">
6+
<el-radio :label="row.id" v-model="selectedSkuId" @change="handleSelected(row)"
7+
>&nbsp;
8+
</el-radio>
9+
</template>
10+
</el-table-column>
11+
<el-table-column label="图片" min-width="80">
12+
<template #default="{ row }">
13+
<el-image
14+
:src="row.picUrl"
15+
class="w-30px h-30px"
16+
:preview-src-list="[row.picUrl]"
17+
preview-teleported
18+
/>
19+
</template>
20+
</el-table-column>
21+
<el-table-column label="规格" align="center" min-width="80">
22+
<template #default="{ row }">
23+
{{ row.properties?.map((p) => p.valueName)?.join(' ') }}
24+
</template>
25+
</el-table-column>
26+
<el-table-column align="center" label="销售价(元)" min-width="80">
27+
<template #default="{ row }">
28+
{{ row.price }}
29+
</template>
30+
</el-table-column>
31+
</el-table>
32+
</Dialog>
33+
</template>
34+
35+
<script lang="ts" setup>
36+
import { ElTable } from 'element-plus'
37+
import * as ProductSpuApi from '@/api/mall/product/spu'
38+
import { propTypes } from '@/utils/propTypes'
39+
40+
defineOptions({ name: 'SkuTableSelect' })
41+
42+
const props = defineProps({
43+
spuId: propTypes.number.def(null)
44+
})
45+
46+
const message = useMessage() // 消息弹窗
47+
const list = ref<any[]>([]) // 列表的数据
48+
const loading = ref(false) // 列表的加载中
49+
const dialogVisible = ref(false) // 弹窗的是否展示
50+
51+
const selectedSkuId = ref() // 选中的商品 spuId
52+
53+
/** 选中时触发 */
54+
const handleSelected = (row: ProductSpuApi.Sku) => {
55+
emits('change', row)
56+
// 关闭弹窗
57+
dialogVisible.value = false
58+
selectedSkuId.value = undefined
59+
}
60+
61+
// 确认选择时的触发事件
62+
const emits = defineEmits<{
63+
(e: 'change', spu: ProductSpuApi.Sku): void
64+
}>()
65+
66+
/** 打开弹窗 */
67+
const open = () => {
68+
dialogVisible.value = true
69+
}
70+
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
71+
72+
/** 查询列表 */
73+
const getSpuDetail = async () => {
74+
loading.value = true
75+
try {
76+
const spu = await ProductSpuApi.getSpu(props.spuId)
77+
list.value = spu.skus
78+
} finally {
79+
loading.value = false
80+
}
81+
}
82+
83+
/** 初始化 **/
84+
onMounted(async () => {})
85+
watch(
86+
() => props.spuId,
87+
() => {
88+
if (!props.spuId) {
89+
return
90+
}
91+
getSpuDetail()
92+
}
93+
)
94+
</script>

0 commit comments

Comments
 (0)