Skip to content

Commit 5162191

Browse files
YunaiVgitee-org
authored andcommitted
!474 【优化】spu:新增商品属性属性值为空校验。【新增】mall 客服消息下拉加载,有新消息提醒
Merge pull request !474 from puhui999/dev-crm
2 parents 82b53b9 + 848bc60 commit 5162191

File tree

5 files changed

+147
-60
lines changed

5 files changed

+147
-60
lines changed

src/views/mall/product/spu/components/SkuList.vue

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ import { createImageViewer } from '@/components/ImageViewer'
292292
import { RuleConfig } from '@/views/mall/product/spu/components/index'
293293
import { PropertyAndValues } from './index'
294294
import { ElTable } from 'element-plus'
295+
import { isEmpty } from '@/utils/is'
295296
296297
defineOptions({ name: 'SkuList' })
297298
const message = useMessage() // 消息弹窗
@@ -340,11 +341,22 @@ const imagePreview = (imgUrl: string) => {
340341
341342
/** 批量添加 */
342343
const batchAdd = () => {
344+
validateProperty()
343345
formData.value!.skus!.forEach((item) => {
344346
copyValueToTarget(item, skuList.value[0])
345347
})
346348
}
347-
349+
/** 校验商品属性属性值 */
350+
const validateProperty = () => {
351+
// 校验商品属性属性值是否为空,有一个为空都不给过
352+
const warningInfo = '存在属性属性值为空,请先检查完善属性值后重试!!!'
353+
for (const item of props.propertyList) {
354+
if (!item.values || isEmpty(item.values)) {
355+
message.warning(warningInfo)
356+
throw new Error(warningInfo)
357+
}
358+
}
359+
}
348360
/** 删除 sku */
349361
const deleteSku = (row) => {
350362
const index = formData.value!.skus!.findIndex(
@@ -358,6 +370,7 @@ const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表
358370
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
359371
*/
360372
const validateSku = () => {
373+
validateProperty()
361374
let warningInfo = '请检查商品各行相关属性配置,'
362375
let validate = true // 默认通过
363376
for (const sku of formData.value!.skus!) {
@@ -421,7 +434,7 @@ watch(
421434
const generateTableData = (propertyList: any[]) => {
422435
// 构建数据结构
423436
const propertyValues = propertyList.map((item) =>
424-
item.values.map((v) => ({
437+
item.values.map((v: any) => ({
425438
propertyId: item.id,
426439
propertyName: item.name,
427440
valueId: v.id,
@@ -464,15 +477,14 @@ const generateTableData = (propertyList: any[]) => {
464477
*/
465478
const validateData = (propertyList: any[]) => {
466479
const skuPropertyIds: number[] = []
467-
formData.value!.skus!.forEach(
468-
(sku) =>
469-
sku.properties
470-
?.map((property) => property.propertyId)
471-
?.forEach((propertyId) => {
472-
if (skuPropertyIds.indexOf(propertyId!) === -1) {
473-
skuPropertyIds.push(propertyId!)
474-
}
475-
})
480+
formData.value!.skus!.forEach((sku) =>
481+
sku.properties
482+
?.map((property) => property.propertyId)
483+
?.forEach((propertyId) => {
484+
if (skuPropertyIds.indexOf(propertyId!) === -1) {
485+
skuPropertyIds.push(propertyId!)
486+
}
487+
})
476488
)
477489
const propertyIds = propertyList.map((item) => item.id)
478490
return skuPropertyIds.length === propertyIds.length
@@ -543,7 +555,7 @@ watch(
543555
return
544556
}
545557
// 添加新属性没有属性值也不做处理
546-
if (propertyList.some((item) => item.values!.length === 0)) {
558+
if (propertyList.some((item) => !item.values || isEmpty(item.values))) {
547559
return
548560
}
549561
// 生成 table 数据,即 sku 列表

src/views/mall/product/spu/form/ProductAttributes.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<el-col v-for="(item, index) in attributeList" :key="index">
44
<div>
55
<el-text class="mx-1">属性名:</el-text>
6-
<el-tag class="mx-1" :closable="!isDetail" type="success" @close="handleCloseProperty(index)">
6+
<el-tag :closable="!isDetail" class="mx-1" type="success" @close="handleCloseProperty(index)">
77
{{ item.name }}
88
</el-tag>
99
</div>
@@ -12,8 +12,8 @@
1212
<el-tag
1313
v-for="(value, valueIndex) in item.values"
1414
:key="value.id"
15-
class="mx-1"
1615
:closable="!isDetail"
16+
class="mx-1"
1717
@close="handleCloseValue(index, valueIndex)"
1818
>
1919
{{ value.name }}
@@ -44,7 +44,6 @@
4444
<script lang="ts" setup>
4545
import { ElInput } from 'element-plus'
4646
import * as PropertyApi from '@/api/mall/product/property'
47-
import { PropertyVO } from '@/api/mall/product/property'
4847
import { PropertyAndValues } from '@/views/mall/product/spu/components'
4948
import { propTypes } from '@/utils/propTypes'
5049
@@ -59,9 +58,9 @@ const inputVisible = computed(() => (index: number) => {
5958
if (attributeIndex.value === null) return false
6059
if (attributeIndex.value === index) return true
6160
})
62-
const inputRef = ref([]) //标签输入框Ref
61+
const inputRef = ref<any[]>([]) //标签输入框Ref
6362
/** 解决 ref 在 v-for 中的获取问题*/
64-
const setInputRef = (el) => {
63+
const setInputRef = (el: any) => {
6564
if (el === null || typeof el === 'undefined') return
6665
// 如果不存在 id 相同的元素才添加
6766
if (!inputRef.value.some((item) => item.input?.attributes.id === el.input?.attributes.id)) {
@@ -81,7 +80,7 @@ watch(
8180
() => props.propertyList,
8281
(data) => {
8382
if (!data) return
84-
attributeList.value = data
83+
attributeList.value = data as any
8584
},
8685
{
8786
deep: true,
@@ -97,6 +96,7 @@ const handleCloseValue = (index: number, valueIndex: number) => {
9796
/** 删除属性*/
9897
const handleCloseProperty = (index: number) => {
9998
attributeList.value?.splice(index, 1)
99+
emit('success', attributeList.value)
100100
}
101101
102102
/** 显示输入框并获取焦点 */

src/views/mall/product/spu/form/SkuForm.vue

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<!-- 商品发布 - 库存价格 -->
22
<template>
3-
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" :disabled="isDetail">
3+
<el-form ref="formRef" :disabled="isDetail" :model="formData" :rules="rules" label-width="120px">
44
<el-form-item label="分销类型" props="subCommissionType">
55
<el-radio-group
66
v-model="formData.subCommissionType"
7-
@change="changeSubCommissionType"
87
class="w-80"
8+
@change="changeSubCommissionType"
99
>
1010
<el-radio :label="false">默认设置</el-radio>
1111
<el-radio :label="true" class="radio">单独设置</el-radio>
1212
</el-radio-group>
1313
</el-form-item>
1414
<el-form-item label="商品规格" props="specType">
15-
<el-radio-group v-model="formData.specType" @change="onChangeSpec" class="w-80">
15+
<el-radio-group v-model="formData.specType" class="w-80" @change="onChangeSpec">
1616
<el-radio :label="false" class="radio">单规格</el-radio>
1717
<el-radio :label="true">多规格</el-radio>
1818
</el-radio-group>
@@ -29,22 +29,22 @@
2929
<el-form-item v-if="formData.specType" label="商品属性">
3030
<el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加属性</el-button>
3131
<ProductAttributes
32+
:is-detail="isDetail"
3233
:property-list="propertyList"
3334
@success="generateSkus"
34-
:is-detail="isDetail"
3535
/>
3636
</el-form-item>
3737
<template v-if="formData.specType && propertyList.length > 0">
38-
<el-form-item label="批量设置" v-if="!isDetail">
38+
<el-form-item v-if="!isDetail" label="批量设置">
3939
<SkuList :is-batch="true" :prop-form-data="formData" :property-list="propertyList" />
4040
</el-form-item>
4141
<el-form-item label="规格列表">
4242
<SkuList
4343
ref="skuListRef"
44+
:is-detail="isDetail"
4445
:prop-form-data="formData"
4546
:property-list="propertyList"
4647
:rule-config="ruleConfig"
47-
:is-detail="isDetail"
4848
/>
4949
</el-form-item>
5050
</template>
@@ -181,7 +181,7 @@ const onChangeSpec = () => {
181181
}
182182
183183
/** 调用 SkuList generateTableData 方法*/
184-
const generateSkus = (propertyList) => {
184+
const generateSkus = (propertyList: any[]) => {
185185
skuListRef.value.generateTableData(propertyList)
186186
}
187187
</script>

src/views/mall/promotion/kefu/components/KeFuChatBox.vue

Lines changed: 108 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44
<div class="kefu-title">{{ keFuConversation.userNickname }}</div>
55
</el-header>
66
<el-main class="kefu-content" style="overflow: visible">
7-
<el-scrollbar ref="scrollbarRef" always height="calc(100vh - 495px)">
7+
<div
8+
v-show="loadingMore"
9+
class="loadingMore flex justify-center items-center cursor-pointer"
10+
@click="handleOldMessage"
11+
>
12+
加载更多
13+
</div>
14+
<el-scrollbar ref="scrollbarRef" always height="calc(100vh - 495px)" @scroll="handleScroll">
815
<div ref="innerRef" class="w-[100%] pb-3px">
9-
<div v-for="(item, index) in messageList" :key="item.id" class="w-[100%]">
16+
<div v-for="(item, index) in getMessageList0" :key="item.id" class="w-[100%]">
1017
<div class="flex justify-center items-center mb-20px">
1118
<!-- 日期 -->
1219
<div
@@ -58,6 +65,14 @@
5865
</div>
5966
</div>
6067
</el-scrollbar>
68+
<div
69+
v-show="showNewMessageTip"
70+
class="newMessageTip flex items-center cursor-pointer"
71+
@click="handleToNewMessage"
72+
>
73+
<span>有新消息</span>
74+
<Icon class="ml-5px" icon="ep:bottom" />
75+
</div>
6176
</el-main>
6277
<el-footer height="230px">
6378
<div class="h-[100%]">
@@ -101,23 +116,47 @@ const messageTool = useMessage()
101116
const message = ref('') // 消息
102117
const messageList = ref<KeFuMessageRespVO[]>([]) // 消息列表
103118
const keFuConversation = ref<KeFuConversationRespVO>({} as KeFuConversationRespVO) // 用户会话
104-
// 获得消息 TODO puhui999: 先不考虑下拉加载历史消息
119+
const showNewMessageTip = ref(false) // 显示有新消息提示
120+
const queryParams = reactive({
121+
pageNo: 1,
122+
conversationId: 0
123+
})
124+
const total = ref(0) // 消息总条数
125+
// 获得消息
105126
const getMessageList = async (conversation: KeFuConversationRespVO) => {
106127
keFuConversation.value = conversation
107-
const { list } = await KeFuMessageApi.getKeFuMessagePage({
108-
pageNo: 1,
109-
conversationId: conversation.id
110-
})
111-
messageList.value = list.reverse()
112-
// TODO puhui999: 首次加载时滚动到最新消息,如果加载的是历史消息则不滚动
128+
queryParams.conversationId = conversation.id
129+
const messageTotal = messageList.value.length
130+
if (total.value > 0 && messageTotal > 0 && messageTotal === total.value) {
131+
return
132+
}
133+
const res = await KeFuMessageApi.getKeFuMessagePage(queryParams)
134+
total.value = res.total
135+
for (const item of res.list) {
136+
if (messageList.value.some((val) => val.id === item.id)) {
137+
continue
138+
}
139+
messageList.value.push(item)
140+
}
113141
await scrollToBottom()
114142
}
143+
const getMessageList0 = computed(() => {
144+
messageList.value.sort((a: any, b: any) => a.createTime - b.createTime)
145+
return messageList.value
146+
})
147+
115148
// 刷新消息列表
116-
const refreshMessageList = () => {
149+
const refreshMessageList = async () => {
117150
if (!keFuConversation.value) {
118151
return
119152
}
120-
getMessageList(keFuConversation.value)
153+
154+
queryParams.pageNo = 1
155+
await getMessageList(keFuConversation.value)
156+
if (loadHistory.value) {
157+
// 有下角显示有新消息提示
158+
showNewMessageTip.value = true
159+
}
121160
}
122161
defineExpose({ getMessageList, refreshMessageList })
123162
// 是否显示聊天区域
@@ -140,7 +179,7 @@ const handleSendPicture = async (picUrl: string) => {
140179
const handleSendMessage = async () => {
141180
// 1. 校验消息是否为空
142181
if (isEmpty(unref(message.value))) {
143-
messageTool.warning('请输入消息后再发送哦!')
182+
messageTool.notifyWarning('请输入消息后再发送哦!')
144183
return
145184
}
146185
// 2. 组织发送消息
@@ -167,12 +206,41 @@ const innerRef = ref<HTMLDivElement>()
167206
const scrollbarRef = ref<InstanceType<typeof ElScrollbarType>>()
168207
// 滚动到底部
169208
const scrollToBottom = async () => {
170-
// 1. 滚动到最新消息
209+
// 1. 首次加载时滚动到最新消息,如果加载的是历史消息则不滚动
210+
if (loadHistory.value) {
211+
return
212+
}
213+
// 2.1 滚动到最新消息,关闭新消息提示
171214
await nextTick()
172215
scrollbarRef.value!.setScrollTop(innerRef.value!.clientHeight)
173-
// 2. 消息已读
216+
showNewMessageTip.value = false
217+
// 2.2 消息已读
174218
await KeFuMessageApi.updateKeFuMessageReadStatus(keFuConversation.value.id)
175219
}
220+
// 查看新消息
221+
const handleToNewMessage = async () => {
222+
loadHistory.value = false
223+
await scrollToBottom()
224+
}
225+
226+
const loadingMore = ref(false) // 滚动到顶部加载更多
227+
const loadHistory = ref(false) // 加载历史消息
228+
const handleScroll = async ({ scrollTop }) => {
229+
const messageTotal = messageList.value.length
230+
if (total.value > 0 && messageTotal > 0 && messageTotal === total.value) {
231+
return
232+
}
233+
// 距顶 20 加载下一页数据
234+
loadingMore.value = scrollTop < 20
235+
}
236+
const handleOldMessage = async () => {
237+
loadHistory.value = true
238+
// 加载消息列表
239+
queryParams.pageNo += 1
240+
await getMessageList(keFuConversation.value)
241+
loadingMore.value = false
242+
// TODO puhui999: 等页面加载完后,获得上一页最后一条消息的位置,控制滚动到它所在位置
243+
}
176244
/**
177245
* 是否显示时间
178246
* @param {*} item - 数据
@@ -196,6 +264,32 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
196264
}
197265
198266
&-content {
267+
position: relative;
268+
269+
.loadingMore {
270+
position: absolute;
271+
top: 0;
272+
left: 0;
273+
width: 100%;
274+
height: 50px;
275+
background-color: #eee;
276+
color: #666;
277+
text-align: center;
278+
line-height: 50px;
279+
transform: translateY(-100%);
280+
transition: transform 0.3s ease-in-out;
281+
}
282+
283+
.newMessageTip {
284+
position: absolute;
285+
bottom: 35px;
286+
right: 35px;
287+
background-color: #fff;
288+
padding: 10px;
289+
border-radius: 30px;
290+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
291+
}
292+
199293
.ss-row-left {
200294
justify-content: flex-start;
201295

0 commit comments

Comments
 (0)