feat: 客服

This commit is contained in:
2026-07-20 15:31:52 +08:00
parent 0099ccaa60
commit 82a8043578
23 changed files with 5071 additions and 360 deletions
+138 -53
View File
@@ -47,7 +47,8 @@
}}</text>
<text class="price_o">¥{{ orderInfo.adPrice }}</text>
</view>
<view class="text-24" style="color: #ff5a5a; height: 38rpx; display: flex; align-items: center; gap: 5rpx" v-if="orderInfo?.goodsTagList">
<view class="text-24" style="color: #ff5a5a; height: 38rpx; display: flex; align-items: center; gap: 5rpx"
v-if="orderInfo?.goodsTagList">
<image v-for="val in orderInfo.goodsTagList" :src="val.icon" mode="heightFix" style="height: 38rpx">
</image>
</view>
@@ -232,13 +233,13 @@
<view v-for="(item, index) in specificationList" :key="item.id" class="list_warp">
<view class="list_title_warp">{{ item.type }}</view>
<view class="specification_item_warp">
<view :class="oItem.inventory === 0
<view :class="oItem.disabled || oItem.inventory === 0
? 'item_Warp item_Warp_no_warp'
: item.selectVal === oItem.n
? 'item_Warp item_Warp_yes_warp'
: 'item_Warp'
" v-for="(oItem, oIndex) in item.items" :key="oItem.n"
@click="clickSpecification(oIndex, item, oItem)">
" v-for="(oItem, oIndex) in item.items" :key="oItem.id || oItem.n"
@click="clickSpecification(index, item, oItem)">
{{ oItem.n }}
</view>
</view>
@@ -338,7 +339,7 @@
</template>
<script>
import { getGoodsDetail, addGoods, getGoodsSpecs } from "@/api/product.js";
import { getGoodsDetail, addGoods, getGoodsSpecs, getGoodsSpecsInfo } from "@/api/product.js";
import { getValue, PRODUCT_DELIVERY_TIME, ACTIVITY_TYPE } from "@/utils/enumUtils.js";
import { copyData } from "@/utils/index.js";
import Evaluation from "@/pages/other_package/productInfo/evaluation.vue";
@@ -395,6 +396,8 @@ export default {
selectSpecification: [], // 选择的规格
specificationTempl: [], // 规格模版数据
selectSpecificationTempl: {}, // 选择规格模版数据
selectedSpecValueIds: [],
skuBySpecKey: {},
serviceShow: false, // 联系客服弹框
openShow: false,
source: null, // 来源:供应链,自营
@@ -518,8 +521,17 @@ export default {
if (this.isShareUrl) {
return;
}
uni.setStorageSync("kefu-pending-context", {
type: "product",
data: {
goodsId: this.id,
goodsName: this.orderInfo.name,
goodsMainGraph: this.orderInfo.mainGraph,
currentPrice: this.orderInfo.adPrice,
},
});
uni.navigateTo({
url: "/pages/rwa_package/setting/kefu?type=5260",
url: `/pages/rwa_package/setting/kefu?scene=product&goodsId=${this.id}`,
});
},
getValue,
@@ -858,7 +870,6 @@ export default {
const resp = await getGoodsDetail(params);
if (resp && resp.bizcode === 100) {
const data = resp.data;
// 限时秒杀 和 积分兑换 不需要购物车
this.isShowCart = this.activedType === 'seckill' || data.type == 3 ? 1 : 0;
// 商品图
@@ -939,9 +950,11 @@ export default {
}
// 需要查询一下最新的规格
if (val) {
await this.querySpecification();
const data = this.orderInfo;
this.getSpecification(data.specs);
const hasStructuredSpecs = await this.querySpecification();
if (!hasStructuredSpecs) {
uni.showToast({ title: "SKU规格加载失败,请稍后重试", icon: "none" });
return;
}
} else {
this.specificationList = []; //规格
this.selectSpecification = []; // 选择的规格
@@ -950,66 +963,138 @@ export default {
this.specificationShow = val;
this.specificationShowType = type;
},
// 拆解规格
getSpecification(specs) {
const optionArr = [];
if (specs) {
const arr = JSON.parse(specs);
arr.map((item) => {
optionArr.push({
type: item.type,
items: item.items,
selectVal: null,
});
});
}
this.specificationList = optionArr;
},
// 获取规格模版数据
async querySpecification() {
const params = {
id: this.id,
};
if (this.isCoupon) {
params.type = -1;
let infoResp = null;
try {
infoResp = await getGoodsSpecsInfo({ id: this.id });
} catch (e) {
return false;
}
if (this.activedType == "seckill") {
params["activityType"] = this.activedType; // 活动类型
params["activityId"] = this.activityId;// 秒杀活动ID
if (!infoResp || infoResp.bizcode !== 100 || !infoResp.data) {
return false;
}
const resp = await getGoodsSpecs(params);
if (resp && resp.bizcode === 100) {
const data = resp.data;
let stock = 0;
data.map((item) => {
stock = stock + item.stock;
const groups = infoResp.data.specGroups || [];
const skus = infoResp.data.skus || [];
if (!groups.length || !skus.length) {
return false;
}
const skuBySpecKey = {};
skus.forEach((sku) => {
const valueIds = this.getSkuValueIds(sku, groups);
if (valueIds.length !== groups.length) {
return;
}
const normalizedSku = Object.assign({}, sku, {
id: sku.id,
valueIds,
minBuyNum: sku.minBuyNum || 1,
combTypes: this.formatSkuSelection(groups, valueIds),
});
this.specificationTempl = data;
this.orderInfo.inventory = stock; // 库存
skuBySpecKey[valueIds.join("_")] = normalizedSku;
});
if (!Object.keys(skuBySpecKey).length) {
return false;
}
this.skuBySpecKey = skuBySpecKey;
this.specificationTempl = Object.keys(skuBySpecKey).map((key) => skuBySpecKey[key]);
this.orderInfo.inventory = this.specificationTempl.reduce(
(total, sku) => total + Number(sku.stock || 0),
0
);
this.selectedSpecValueIds = groups.map(() => null);
this.selectSpecification = groups.map(() => null);
this.selectSpecificationTempl = {};
this.specificationList = groups.map((group) => ({
id: group.id,
type: group.name,
selectVal: null,
items: (group.values || []).map((value) => ({
id: value.id,
groupId: group.id,
n: value.name,
image: value.image,
disabled: false,
})),
}));
this.refreshSpecOptionState();
return true;
},
getSkuValueIds(sku, groups) {
if (sku.specValueIds) {
try {
const ids = JSON.parse(sku.specValueIds);
if (Array.isArray(ids)) {
return ids.map((id) => Number(id));
}
} catch (e) { }
}
let path = {};
try {
path = JSON.parse(sku.specValuePath || "{}") || {};
} catch (e) { }
const legacyValues = String(path.legacyName || "")
.split(/\s*(?:\/|♧)\s*/)
.filter(Boolean);
return groups.map((group, index) => {
const selectedName = path[group.name] || legacyValues[index];
const value = (group.values || []).find((item) => item.name === selectedName);
return value ? Number(value.id) : null;
}).filter((id) => id !== null);
},
formatSkuSelection(groups, valueIds) {
return groups.map((group, index) => {
const value = (group.values || []).find((item) => Number(item.id) === Number(valueIds[index]));
return value ? `${group.name}:${value.name}` : "";
}).filter(Boolean).join(";");
},
refreshSpecOptionState() {
this.specificationList.forEach((group, groupIndex) => {
group.items.forEach((value) => {
const candidate = this.selectedSpecValueIds.slice();
candidate[groupIndex] = Number(value.id);
value.disabled = !Object.keys(this.skuBySpecKey).some((key) => {
const sku = this.skuBySpecKey[key];
const skuIds = sku.valueIds || [];
return candidate.every((selectedId, index) =>
selectedId == null || Number(skuIds[index]) === Number(selectedId)
) && Number(sku.stock || 0) > 0;
});
});
});
},
//改变规格的样式
clickSpecification(pIndex, item, oItem) {
clickSpecification(groupIndex, item, oItem) {
console.log(
"改变规格的样式",
pIndex,
groupIndex,
item,
oItem,
this.specificationTempl,
this.specificationList
);
const currentSelectSpecification = this.specificationTempl[pIndex];
if (oItem.disabled) {
return;
}
this.selectedSpecValueIds.splice(groupIndex, 1, Number(oItem.id));
this.selectSpecification.splice(groupIndex, 1, oItem.n);
this.specificationList[groupIndex].selectVal = oItem.n;
this.refreshSpecOptionState();
console.log("当前规格", currentSelectSpecification);
this.orderInfo.quantity = currentSelectSpecification?.minBuyNum || 1;
this.specificationList[0].selectVal = oItem.n;
this.selectSpecificationTempl = currentSelectSpecification;
this.orderInfo.inventory = currentSelectSpecification.stock; // 库存
// 计算规格的价格
// this.selectSpecificationFun(pIndex, oItem);
if (this.selectedSpecValueIds.every((id) => id != null)) {
const currentSelectSpecification = this.skuBySpecKey[this.selectedSpecValueIds.join("_")] || {};
this.selectSpecificationTempl = currentSelectSpecification;
this.orderInfo.quantity = currentSelectSpecification.minBuyNum || 1;
this.orderInfo.inventory = currentSelectSpecification.stock || 0;
} else {
this.selectSpecificationTempl = {};
}
},
// 选择规格
selectSpecificationFun(index, item) {