mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-13 04:18:55 +08:00
refactor: 四端完成 Options API → script setup + vuex → pinia 迁移
- buyer(95) / seller(169) / im(53) / manager(280) 共 597 个组件全部从 Options API 转换为 <script setup> - 四端 vuex 全部移除,状态管理统一迁移至 pinia 4(stores/ 目录) - util.js 等基础设施去除 vm 首参依赖;全局属性($Message/$filters 等)在 script 内改为具名导入 - 被外部 ref 访问的公共组件均已补齐 defineExpose(清单见各端 MIGRATION-VUE3.md) - 各端 eslint 启用 vue/component-api-style 防止新代码回退 Options API - 四端 yarn build 全部通过;迁移约定与注意事项已更新至各端 MIGRATION-VUE3.md
This commit is contained in:
@@ -18,60 +18,57 @@
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { AlarmClock } from '@element-plus/icons-vue';
|
||||
export default {
|
||||
components: { AlarmClock },
|
||||
props: {
|
||||
time: { // 传入的初始时间
|
||||
default: 1718977559428
|
||||
},
|
||||
type: { // 区分是在详情还是购物车调用
|
||||
default: 'goodsDetail', // 设置两个值,goodsDetail和cart,样式不同
|
||||
type: String
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
time: { // 传入的初始时间
|
||||
default: 1718977559428
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
end: '', // 结束状态
|
||||
hours: '', // 小时
|
||||
minutes: '', // 分钟
|
||||
seconds: '', // 秒
|
||||
interval: '' // 定时器
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
countDown (val) { // 倒计时方法
|
||||
function addZero (i) {
|
||||
return i < 10 ? '0' + i : i + '';
|
||||
}
|
||||
var nowtime = new Date();
|
||||
var endtime = new Date(val);
|
||||
var lefttime = parseInt((endtime.getTime() - nowtime.getTime()) / 1000);
|
||||
var h = parseInt((lefttime / (60 * 60)) % 24);
|
||||
var m = parseInt((lefttime / 60) % 60);
|
||||
var s = parseInt(lefttime % 60);
|
||||
h = addZero(h);
|
||||
m = addZero(m);
|
||||
s = addZero(s);
|
||||
this.hours = h;
|
||||
this.minutes = m
|
||||
this.seconds = s;
|
||||
if (lefttime <= 0) {
|
||||
this.end = `活动已结束`;
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
},
|
||||
init () { // 初始化
|
||||
this.interval = setInterval(() => {
|
||||
this.countDown(this.time);
|
||||
}, 1000);
|
||||
}
|
||||
type: { // 区分是在详情还是购物车调用
|
||||
default: 'goodsDetail', // 设置两个值,goodsDetail和cart,样式不同
|
||||
type: String
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const end = ref(''); // 结束状态
|
||||
const hours = ref(''); // 小时
|
||||
const minutes = ref(''); // 分钟
|
||||
const seconds = ref(''); // 秒
|
||||
let interval = null; // 定时器
|
||||
|
||||
function countDown (val) { // 倒计时方法
|
||||
function addZero (i) {
|
||||
return i < 10 ? '0' + i : i + '';
|
||||
}
|
||||
var nowtime = new Date();
|
||||
var endtime = new Date(val);
|
||||
var lefttime = parseInt((endtime.getTime() - nowtime.getTime()) / 1000);
|
||||
var h = parseInt((lefttime / (60 * 60)) % 24);
|
||||
var m = parseInt((lefttime / 60) % 60);
|
||||
var s = parseInt(lefttime % 60);
|
||||
h = addZero(h);
|
||||
m = addZero(m);
|
||||
s = addZero(s);
|
||||
hours.value = h;
|
||||
minutes.value = m
|
||||
seconds.value = s;
|
||||
if (lefttime <= 0) {
|
||||
end.value = `活动已结束`;
|
||||
clearInterval(interval)
|
||||
}
|
||||
}
|
||||
|
||||
function init () { // 初始化
|
||||
interval = setInterval(() => {
|
||||
countDown(props.time);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.cart-promotion{
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
class="item-coupon"
|
||||
v-for="(item, index) in promotionMap['COUPON'].slice(0, 6)"
|
||||
:key="index"
|
||||
@click="receiveCoupon(item.id)"
|
||||
@click="handleReceiveCoupon(item.id)"
|
||||
>
|
||||
<span v-if="item.couponType == 'PRICE'"
|
||||
>满{{ item.consumeThreshold }}减{{ item.price }}</span>
|
||||
@@ -125,7 +125,7 @@
|
||||
class="item-coupon"
|
||||
v-for="(item, index) in promotionMap['COUPON'].slice(6, promotionMap['COUPON'].length)"
|
||||
:key="index"
|
||||
@click="receiveCoupon(item.id)"
|
||||
@click="handleReceiveCoupon(item.id)"
|
||||
>
|
||||
<span v-if="item.couponType == 'PRICE'"
|
||||
>满{{ item.consumeThreshold }}减{{ item.price }}</span>
|
||||
@@ -318,11 +318,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { Message } from "@/utils/message";
|
||||
import { StarFilled } from '@element-plus/icons-vue';
|
||||
import Promotion from "./Promotion.vue";
|
||||
import DPlayer from "dplayer";
|
||||
import storage from "@/plugins/storage.js";
|
||||
import {
|
||||
collectGoods,
|
||||
isCollection,
|
||||
@@ -344,375 +347,389 @@ import {
|
||||
E_COUPON_CART_POINTS,
|
||||
} from "@/constants/goodsType";
|
||||
|
||||
export default {
|
||||
name: "ShowGoods",
|
||||
props: {
|
||||
// 商品数据
|
||||
detail: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
defineOptions({ name: "ShowGoods" });
|
||||
|
||||
const props = defineProps({
|
||||
// 商品数据
|
||||
detail: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
watch: {
|
||||
detail: {
|
||||
handler(val) {
|
||||
this.skuDetail = val.data;
|
||||
this.wholesaleList = val.wholesaleList;
|
||||
this.syncCountWithQuantity();
|
||||
this.promotion();
|
||||
this.swiperGoodsImg();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
showGoodsVideo(val){
|
||||
if(val){
|
||||
this.initVideo();
|
||||
});
|
||||
|
||||
const emit = defineEmits(["handleClickSku"]);
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const showGoodsVideo = ref(false);
|
||||
const goodsVideo = ref("");
|
||||
const wholesaleList = ref([]);
|
||||
const count = ref(1); // 商品数量
|
||||
const imgIndex = ref(0); // 展示图片下标
|
||||
const currentSelceted = ref([]); // 当前商品sku
|
||||
const imgList = ref([]); // 商品图片列表
|
||||
const skuDetail = ref({
|
||||
specList: [],
|
||||
}); // sku详情
|
||||
const goodsSpecList = ref(props.detail.specs); // 商品spec
|
||||
const promotionMap = ref(createEmptyPromotionMap());
|
||||
const formatList = ref([]); // 选择商品品类的数组
|
||||
const loading = ref(false); // 立即购买loading
|
||||
const loading1 = ref(false); // 加入购物车loading
|
||||
const loadingPintuan = ref(false);
|
||||
const isCollected = ref(false); // 是否收藏
|
||||
const skuList = ref([]); // formatSku 中赋值(原 data 未声明)
|
||||
|
||||
// startCheckout 通过字符串 key 操作对应 loading
|
||||
const loadings = { loading, loading1, loadingPintuan };
|
||||
|
||||
const currentImg = computed(() => {
|
||||
const item = imgList.value[imgIndex.value];
|
||||
return item ? item.url || item : "";
|
||||
});
|
||||
|
||||
const wholesalePrice = computed(() => {
|
||||
return wholesaleList.value.length
|
||||
? wholesaleList.value.map((item) => {
|
||||
return item.price;
|
||||
})
|
||||
: [];
|
||||
});
|
||||
|
||||
const wholesaleNum = computed(() => {
|
||||
return wholesaleList.value.length
|
||||
? wholesaleList.value.map((item) => {
|
||||
return item.num;
|
||||
})
|
||||
: [];
|
||||
});
|
||||
|
||||
/** 卡密商品:禁加购,仅立即购买;库存读 quantity(M-02 起支持促销) */
|
||||
const isECouponGoods = computed(() => {
|
||||
return isECoupon(skuDetail.value.goodsType);
|
||||
});
|
||||
|
||||
const displayStock = computed(() => {
|
||||
if (isECouponGoods.value) {
|
||||
return getECouponStock(skuDetail.value);
|
||||
}
|
||||
return Number(skuDetail.value.quantity) || 0;
|
||||
});
|
||||
|
||||
const quantityMax = computed(() => {
|
||||
if (isECouponGoods.value) {
|
||||
return getECouponMaxBuyNum(skuDetail.value);
|
||||
}
|
||||
const qty = Number(skuDetail.value.quantity) || 0;
|
||||
return qty > 0 ? qty : 1;
|
||||
});
|
||||
|
||||
const isOutOfStock = computed(() => {
|
||||
if (isECouponGoods.value) {
|
||||
return getECouponStock(skuDetail.value) <= 0;
|
||||
}
|
||||
return (Number(skuDetail.value.quantity) || 0) <= 0;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.detail,
|
||||
(val) => {
|
||||
skuDetail.value = val.data;
|
||||
wholesaleList.value = val.wholesaleList;
|
||||
syncCountWithQuantity();
|
||||
promotion();
|
||||
swiperGoodsImg();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
watch(showGoodsVideo, (val) => {
|
||||
if (val) {
|
||||
initVideo();
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化video
|
||||
function initVideo() {
|
||||
if (!goodsVideo.value) {
|
||||
setTimeout(() => {
|
||||
goodsVideo.value = new DPlayer({
|
||||
container: document.getElementById('dplayer'),
|
||||
video: {
|
||||
url: skuDetail.value.goodsVideo,
|
||||
},
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function syncCountWithQuantity() {
|
||||
const qty = isECouponGoods.value
|
||||
? getECouponStock(skuDetail.value)
|
||||
: Number(skuDetail.value.quantity) || 0;
|
||||
if (qty <= 0) {
|
||||
count.value = 1;
|
||||
return;
|
||||
}
|
||||
if (!isECouponGoods.value && wholesaleList.value && wholesaleList.value.length > 0) {
|
||||
count.value = Math.min(Math.max(wholesaleList.value[0].num, 1), qty);
|
||||
return;
|
||||
}
|
||||
const max = isECouponGoods.value ? getECouponMaxBuyNum(skuDetail.value) : qty;
|
||||
if (count.value > max) {
|
||||
count.value = max;
|
||||
} else if (count.value < 1) {
|
||||
count.value = 1;
|
||||
}
|
||||
}
|
||||
|
||||
function changeCount(val) {
|
||||
if (wholesaleList.value && wholesaleList.value.length > 0) {
|
||||
if (count.value <= wholesaleList.value[0].num) {
|
||||
Message.warning("批发商品购买数量不能小于起批数量");
|
||||
count.value = wholesaleList.value[0].num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function select(index, value) {
|
||||
// 选择规格
|
||||
currentSelceted.value[index] = value;
|
||||
let selectedSkuId = goodsSpecList.value.find((i) => {
|
||||
let matched = true;
|
||||
let specValues = i.specValues.filter((j) => j.specName !== "images");
|
||||
for (let n = 0; n < specValues.length; n++) {
|
||||
if (specValues[n].specValue !== currentSelceted.value[n]) {
|
||||
matched = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
playIcon,
|
||||
showGoodsVideo:false,
|
||||
goodsVideo:"",
|
||||
wholesaleList: [],
|
||||
count: 1, // 商品数量
|
||||
imgIndex: 0, // 展示图片下标
|
||||
currentSelceted: [], // 当前商品sku
|
||||
imgList: [], // 商品图片列表
|
||||
skuDetail: {
|
||||
specList: [],
|
||||
}, // sku详情
|
||||
goodsSpecList: this.detail.specs, // 商品spec
|
||||
promotionMap: createEmptyPromotionMap(),
|
||||
formatList: [], // 选择商品品类的数组
|
||||
loading: false, // 立即购买loading
|
||||
loading1: false, // 加入购物车loading
|
||||
loadingPintuan: false,
|
||||
isCollected: false, // 是否收藏
|
||||
};
|
||||
},
|
||||
components: { Promotion, StarFilled },
|
||||
computed: {
|
||||
currentImg() {
|
||||
const item = this.imgList[this.imgIndex];
|
||||
return item ? item.url || item : "";
|
||||
},
|
||||
wholesalePrice(key) {
|
||||
return this.wholesaleList.length
|
||||
? this.wholesaleList.map((item) => {
|
||||
return item.price;
|
||||
})
|
||||
: [];
|
||||
},
|
||||
wholesaleNum(key) {
|
||||
return this.wholesaleList.length
|
||||
? this.wholesaleList.map((item) => {
|
||||
return item.num;
|
||||
})
|
||||
: [];
|
||||
},
|
||||
/** 卡密商品:禁加购,仅立即购买;库存读 quantity(M-02 起支持促销) */
|
||||
isECouponGoods() {
|
||||
return isECoupon(this.skuDetail.goodsType);
|
||||
},
|
||||
displayStock() {
|
||||
if (this.isECouponGoods) {
|
||||
return getECouponStock(this.skuDetail);
|
||||
}
|
||||
return Number(this.skuDetail.quantity) || 0;
|
||||
},
|
||||
quantityMax() {
|
||||
if (this.isECouponGoods) {
|
||||
return getECouponMaxBuyNum(this.skuDetail);
|
||||
}
|
||||
const qty = Number(this.skuDetail.quantity) || 0;
|
||||
return qty > 0 ? qty : 1;
|
||||
},
|
||||
isOutOfStock() {
|
||||
if (this.isECouponGoods) {
|
||||
return getECouponStock(this.skuDetail) <= 0;
|
||||
}
|
||||
return (Number(this.skuDetail.quantity) || 0) <= 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
eCouponAfterSaleHint,
|
||||
// 初始化video
|
||||
initVideo(){
|
||||
if(!this.goodsVideo ){
|
||||
setTimeout(()=>{
|
||||
this.goodsVideo = new DPlayer({
|
||||
container: document.getElementById('dplayer'),
|
||||
video: {
|
||||
url:this.skuDetail.goodsVideo,
|
||||
},
|
||||
});
|
||||
},100)
|
||||
}
|
||||
if (matched) {
|
||||
return i;
|
||||
}
|
||||
});
|
||||
emit("handleClickSku", {
|
||||
skuId: selectedSkuId.skuId,
|
||||
goodsId: skuDetail.value.goodsId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
syncCountWithQuantity() {
|
||||
const qty = this.isECouponGoods
|
||||
? getECouponStock(this.skuDetail)
|
||||
: Number(this.skuDetail.quantity) || 0;
|
||||
if (qty <= 0) {
|
||||
this.count = 1;
|
||||
return;
|
||||
}
|
||||
if (!this.isECouponGoods && this.wholesaleList && this.wholesaleList.length > 0) {
|
||||
this.count = Math.min(Math.max(this.wholesaleList[0].num, 1), qty);
|
||||
return;
|
||||
}
|
||||
const max = this.isECouponGoods ? getECouponMaxBuyNum(this.skuDetail) : qty;
|
||||
if (this.count > max) {
|
||||
this.count = max;
|
||||
} else if (this.count < 1) {
|
||||
this.count = 1;
|
||||
}
|
||||
},
|
||||
changeCount(val) {
|
||||
if (this.wholesaleList && this.wholesaleList.length > 0) {
|
||||
if (this.count <= this.wholesaleList[0].num) {
|
||||
Message.warning("批发商品购买数量不能小于起批数量");
|
||||
this.count = this.wholesaleList[0].num;
|
||||
}
|
||||
}
|
||||
},
|
||||
select(index, value) {
|
||||
// 选择规格
|
||||
this.currentSelceted[index] = value;
|
||||
let selectedSkuId = this.goodsSpecList.find((i) => {
|
||||
let matched = true;
|
||||
let specValues = i.specValues.filter((j) => j.specName !== "images");
|
||||
for (let n = 0; n < specValues.length; n++) {
|
||||
if (specValues[n].specValue !== this.currentSelceted[n]) {
|
||||
matched = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
return i;
|
||||
}
|
||||
});
|
||||
this.$emit("handleClickSku", {
|
||||
skuId: selectedSkuId.skuId,
|
||||
goodsId: this.skuDetail.goodsId,
|
||||
});
|
||||
},
|
||||
|
||||
addShoppingCartBtn() {
|
||||
// FR-B-01:E_COUPON 禁止加购物车(后端亦返回 CARD_KEY_E_COUPON_CART_FORBIDDEN)
|
||||
if (this.isECouponGoods) {
|
||||
Message.warning("电子卡券请使用立即购买");
|
||||
return;
|
||||
}
|
||||
// 添加购物车
|
||||
const params = {
|
||||
num: this.count,
|
||||
skuId: this.skuDetail.id,
|
||||
};
|
||||
this.loading = true;
|
||||
addCartGoods(params)
|
||||
.then((res) => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.$router.push({
|
||||
path: "/shoppingCart",
|
||||
query: {
|
||||
skuId: this.skuDetail.id,
|
||||
goodsId: this.skuDetail.goodsId,
|
||||
thumbnail: this.skuDetail.thumbnail,
|
||||
goodsName: this.skuDetail.goodsName,
|
||||
count: this.count,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
Message.warning(res.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
buyNow() {
|
||||
this.startCheckout(E_COUPON_CART_BUY_NOW, "loading1");
|
||||
},
|
||||
pintuanBuy() {
|
||||
this.startCheckout(E_COUPON_CART_PINTUAN, "loadingPintuan");
|
||||
},
|
||||
pointPay() {
|
||||
this.startCheckout(E_COUPON_CART_POINTS, "loading");
|
||||
},
|
||||
startCheckout(action, loadingKey) {
|
||||
const params = {
|
||||
num: this.count,
|
||||
skuId: this.skuDetail.id,
|
||||
cartType: "BUY_NOW",
|
||||
};
|
||||
if (this.skuDetail.goodsType === "VIRTUAL_GOODS") {
|
||||
params.cartType = "VIRTUAL";
|
||||
} else if (this.isECouponGoods) {
|
||||
params.cartType = resolveECouponCartType({
|
||||
routeWay: this.$route.query.way,
|
||||
action,
|
||||
});
|
||||
}
|
||||
this[loadingKey] = true;
|
||||
addCartGoods(params)
|
||||
.then((res) => {
|
||||
this[loadingKey] = false;
|
||||
if (res.success) {
|
||||
this.$router.push({
|
||||
path: "/pay",
|
||||
query: { way: params.cartType },
|
||||
});
|
||||
} else {
|
||||
Message.warning(res.message);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this[loadingKey] = false;
|
||||
});
|
||||
},
|
||||
async collect() {
|
||||
if (!this.Cookies.getItem("accessToken") || !this.Cookies.getItem("userInfo")) {
|
||||
this.$router.push({
|
||||
path: "/login",
|
||||
function addShoppingCartBtn() {
|
||||
// FR-B-01:E_COUPON 禁止加购物车(后端亦返回 CARD_KEY_E_COUPON_CART_FORBIDDEN)
|
||||
if (isECouponGoods.value) {
|
||||
Message.warning("电子卡券请使用立即购买");
|
||||
return;
|
||||
}
|
||||
// 添加购物车
|
||||
const params = {
|
||||
num: count.value,
|
||||
skuId: skuDetail.value.id,
|
||||
};
|
||||
loading.value = true;
|
||||
addCartGoods(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (res.success) {
|
||||
router.push({
|
||||
path: "/shoppingCart",
|
||||
query: {
|
||||
rePath: this.$route.path,
|
||||
query: JSON.stringify(this.$route.query || {}),
|
||||
skuId: skuDetail.value.id,
|
||||
goodsId: skuDetail.value.goodsId,
|
||||
thumbnail: skuDetail.value.thumbnail,
|
||||
goodsName: skuDetail.value.goodsName,
|
||||
count: count.value,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 收藏商品
|
||||
if (this.isCollected) {
|
||||
let cancel = await cancelCollect("GOODS", this.skuDetail.id);
|
||||
if (cancel.success) {
|
||||
Message.success("取消收藏成功");
|
||||
this.isCollected = false;
|
||||
}
|
||||
} else {
|
||||
let collect = await collectGoods("GOODS", this.skuDetail.id);
|
||||
if (collect.code === 200) {
|
||||
this.isCollected = true;
|
||||
Message.success("收藏商品成功,可以前往个人中心我的收藏查看");
|
||||
}
|
||||
Message.warning(res.message);
|
||||
}
|
||||
},
|
||||
// 格式化数据
|
||||
formatSku(list) {
|
||||
let arr = [{}];
|
||||
list.forEach((item, index) => {
|
||||
item.specValues.forEach((spec, specIndex) => {
|
||||
let name = spec.specName;
|
||||
let values = {
|
||||
value: spec.specValue,
|
||||
quantity: item.quantity,
|
||||
};
|
||||
if (name === "images") {
|
||||
return;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
arr.forEach((arrItem, arrIndex) => {
|
||||
if (
|
||||
arrItem.name === name &&
|
||||
arrItem.values &&
|
||||
!arrItem.values.find((i) => i.value === values.value)
|
||||
) {
|
||||
arrItem.values.push(values);
|
||||
}
|
||||
function buyNow() {
|
||||
startCheckout(E_COUPON_CART_BUY_NOW, "loading1");
|
||||
}
|
||||
|
||||
let keys = arr.map((key) => {
|
||||
return key.name;
|
||||
});
|
||||
if (!keys.includes(name)) {
|
||||
arr.push({
|
||||
name: name,
|
||||
values: [values],
|
||||
});
|
||||
}
|
||||
});
|
||||
function pintuanBuy() {
|
||||
startCheckout(E_COUPON_CART_PINTUAN, "loadingPintuan");
|
||||
}
|
||||
|
||||
function pointPay() {
|
||||
startCheckout(E_COUPON_CART_POINTS, "loading");
|
||||
}
|
||||
|
||||
function startCheckout(action, loadingKey) {
|
||||
const params = {
|
||||
num: count.value,
|
||||
skuId: skuDetail.value.id,
|
||||
cartType: "BUY_NOW",
|
||||
};
|
||||
if (skuDetail.value.goodsType === "VIRTUAL_GOODS") {
|
||||
params.cartType = "VIRTUAL";
|
||||
} else if (isECouponGoods.value) {
|
||||
params.cartType = resolveECouponCartType({
|
||||
routeWay: route.query.way,
|
||||
action,
|
||||
});
|
||||
}
|
||||
loadings[loadingKey].value = true;
|
||||
addCartGoods(params)
|
||||
.then((res) => {
|
||||
loadings[loadingKey].value = false;
|
||||
if (res.success) {
|
||||
router.push({
|
||||
path: "/pay",
|
||||
query: { way: params.cartType },
|
||||
});
|
||||
});
|
||||
arr.shift();
|
||||
this.formatList = arr;
|
||||
|
||||
let cur = list.filter((i) => i.skuId === this.$route.query.skuId)[0];
|
||||
if (cur) {
|
||||
cur.specValues
|
||||
.filter((i) => i.specName !== "images")
|
||||
.forEach((value, _index) => {
|
||||
this.currentSelceted[_index] = value.specValue;
|
||||
});
|
||||
} else {
|
||||
Message.warning(res.message);
|
||||
}
|
||||
this.skuList = list;
|
||||
},
|
||||
receiveCoupon(id) {
|
||||
// 领取优惠券
|
||||
receiveCoupon(id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("优惠券领取成功");
|
||||
} else {
|
||||
Message.warning(res.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
promotion() {
|
||||
this.promotionMap = createEmptyPromotionMap();
|
||||
if (!this.detail || !this.detail.promotionMap) return false;
|
||||
const keysArr = Object.keys(this.detail.promotionMap);
|
||||
if (keysArr.length === 0) return false;
|
||||
})
|
||||
.catch(() => {
|
||||
loadings[loadingKey].value = false;
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < keysArr.length; i++) {
|
||||
const key = keysArr[i].split("-")[0];
|
||||
if (key === "COUPON") {
|
||||
if (!Array.isArray(this.promotionMap.COUPON)) {
|
||||
this.promotionMap.COUPON = [];
|
||||
}
|
||||
this.promotionMap.COUPON.push(this.detail.promotionMap[keysArr[i]]);
|
||||
} else if (Object.prototype.hasOwnProperty.call(this.promotionMap, key)) {
|
||||
this.promotionMap[key] = this.detail.promotionMap[keysArr[i]];
|
||||
} else {
|
||||
this.promotionMap[key] = this.detail.promotionMap[keysArr[i]];
|
||||
}
|
||||
}
|
||||
},
|
||||
// 相册里会混进规格 JSON,只留图片地址;没有相册时用主图
|
||||
swiperGoodsImg() {
|
||||
const source = this.skuDetail.goodsGalleryList || [];
|
||||
const gallery = Array.from(source).filter((item) => {
|
||||
if (typeof item !== "string") return false;
|
||||
return item.indexOf('"url":') === -1 && item.indexOf('"status":') === -1;
|
||||
});
|
||||
if (gallery.length) {
|
||||
this.imgList = gallery;
|
||||
async function collect() {
|
||||
if (!storage.getItem("accessToken") || !storage.getItem("userInfo")) {
|
||||
router.push({
|
||||
path: "/login",
|
||||
query: {
|
||||
rePath: route.path,
|
||||
query: JSON.stringify(route.query || {}),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 收藏商品
|
||||
if (isCollected.value) {
|
||||
let cancel = await cancelCollect("GOODS", skuDetail.value.id);
|
||||
if (cancel.success) {
|
||||
Message.success("取消收藏成功");
|
||||
isCollected.value = false;
|
||||
}
|
||||
} else {
|
||||
let collect = await collectGoods("GOODS", skuDetail.value.id);
|
||||
if (collect.code === 200) {
|
||||
isCollected.value = true;
|
||||
Message.success("收藏商品成功,可以前往个人中心我的收藏查看");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化数据
|
||||
function formatSku(list) {
|
||||
let arr = [{}];
|
||||
list.forEach((item, index) => {
|
||||
item.specValues.forEach((spec, specIndex) => {
|
||||
let name = spec.specName;
|
||||
let values = {
|
||||
value: spec.specValue,
|
||||
quantity: item.quantity,
|
||||
};
|
||||
if (name === "images") {
|
||||
return;
|
||||
}
|
||||
this.imgList = this.skuDetail.original ? [this.skuDetail.original] : [];
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// 用户登录才会判断是否收藏(须同时有 token,避免残留 userInfo 触发 401 跳转登录)
|
||||
if (this.Cookies.getItem("accessToken") && this.Cookies.getItem("userInfo")) {
|
||||
isCollection("GOODS", this.skuDetail.id).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
this.isCollected = true;
|
||||
|
||||
arr.forEach((arrItem, arrIndex) => {
|
||||
if (
|
||||
arrItem.name === name &&
|
||||
arrItem.values &&
|
||||
!arrItem.values.find((i) => i.value === values.value)
|
||||
) {
|
||||
arrItem.values.push(values);
|
||||
}
|
||||
|
||||
let keys = arr.map((key) => {
|
||||
return key.name;
|
||||
});
|
||||
if (!keys.includes(name)) {
|
||||
arr.push({
|
||||
name: name,
|
||||
values: [values],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
arr.shift();
|
||||
formatList.value = arr;
|
||||
|
||||
this.formatSku(this.goodsSpecList);
|
||||
// this.promotion();
|
||||
document.title = this.skuDetail.goodsName;
|
||||
},
|
||||
};
|
||||
let cur = list.filter((i) => i.skuId === route.query.skuId)[0];
|
||||
if (cur) {
|
||||
cur.specValues
|
||||
.filter((i) => i.specName !== "images")
|
||||
.forEach((value, _index) => {
|
||||
currentSelceted.value[_index] = value.specValue;
|
||||
});
|
||||
}
|
||||
skuList.value = list;
|
||||
}
|
||||
|
||||
function handleReceiveCoupon(id) {
|
||||
// 领取优惠券
|
||||
receiveCoupon(id).then((res) => {
|
||||
if (res.success) {
|
||||
Message.success("优惠券领取成功");
|
||||
} else {
|
||||
Message.warning(res.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function promotion() {
|
||||
promotionMap.value = createEmptyPromotionMap();
|
||||
if (!props.detail || !props.detail.promotionMap) return false;
|
||||
const keysArr = Object.keys(props.detail.promotionMap);
|
||||
if (keysArr.length === 0) return false;
|
||||
|
||||
for (let i = 0; i < keysArr.length; i++) {
|
||||
const key = keysArr[i].split("-")[0];
|
||||
if (key === "COUPON") {
|
||||
if (!Array.isArray(promotionMap.value.COUPON)) {
|
||||
promotionMap.value.COUPON = [];
|
||||
}
|
||||
promotionMap.value.COUPON.push(props.detail.promotionMap[keysArr[i]]);
|
||||
} else if (Object.prototype.hasOwnProperty.call(promotionMap.value, key)) {
|
||||
promotionMap.value[key] = props.detail.promotionMap[keysArr[i]];
|
||||
} else {
|
||||
promotionMap.value[key] = props.detail.promotionMap[keysArr[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 相册里会混进规格 JSON,只留图片地址;没有相册时用主图
|
||||
function swiperGoodsImg() {
|
||||
const source = skuDetail.value.goodsGalleryList || [];
|
||||
const gallery = Array.from(source).filter((item) => {
|
||||
if (typeof item !== "string") return false;
|
||||
return item.indexOf('"url":') === -1 && item.indexOf('"status":') === -1;
|
||||
});
|
||||
if (gallery.length) {
|
||||
imgList.value = gallery;
|
||||
return;
|
||||
}
|
||||
imgList.value = skuDetail.value.original ? [skuDetail.value.original] : [];
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 用户登录才会判断是否收藏(须同时有 token,避免残留 userInfo 触发 401 跳转登录)
|
||||
if (storage.getItem("accessToken") && storage.getItem("userInfo")) {
|
||||
isCollection("GOODS", skuDetail.value.id).then((res) => {
|
||||
if (res.success && res.result) {
|
||||
isCollected.value = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
formatSku(goodsSpecList.value);
|
||||
// promotion();
|
||||
document.title = skuDetail.value.goodsName;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -132,160 +132,162 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, onMounted, onBeforeUnmount, reactive, ref } from 'vue';
|
||||
import { Refresh } from '@element-plus/icons-vue';
|
||||
import { goodsComment, goodsCommentNum } from '@/api/member.js';
|
||||
import StoreSide from './StoreSide.vue';
|
||||
|
||||
export default {
|
||||
components: { Refresh, StoreSide },
|
||||
name: 'ShowGoodsDetail',
|
||||
props: {
|
||||
detail: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
detailTabs: [
|
||||
{ name: 'intro', label: '商品介绍' },
|
||||
{ name: 'comment', label: '商品评价' },
|
||||
{ name: 'params', label: '商品参数' }
|
||||
],
|
||||
activeDetailTab: 'intro',
|
||||
anchorStuck: false,
|
||||
lockScrollSpy: false,
|
||||
scrollTimer: null,
|
||||
commentList: [],
|
||||
commentParams: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
grade: '',
|
||||
goodsId: ''
|
||||
},
|
||||
commentTypeNum: {},
|
||||
commentTotal: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
skuDetail () {
|
||||
return this.detail.data;
|
||||
},
|
||||
goodsParamsList () {
|
||||
const list = this.detail && Array.isArray(this.detail.goodsParamsDTOList) ? this.detail.goodsParamsDTOList : [];
|
||||
const flat = [];
|
||||
defineOptions({ name: 'ShowGoodsDetail' });
|
||||
|
||||
list.forEach((item) => {
|
||||
if (!item) return;
|
||||
if (Array.isArray(item.goodsParamsItemDTOList)) {
|
||||
flat.push(...item.goodsParamsItemDTOList);
|
||||
} else {
|
||||
flat.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
const cleaned = flat.filter((p) => p && (p.paramName || p.paramId));
|
||||
const hasSort = cleaned.some((p) => p && p.sort !== undefined && p.sort !== null);
|
||||
if (!hasSort) return cleaned;
|
||||
|
||||
return cleaned.slice().sort((a, b) => {
|
||||
const sa = Number(a && a.sort) || 0;
|
||||
const sb = Number(b && b.sort) || 0;
|
||||
return sa - sb;
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getNavOffset () {
|
||||
const nav = this.$refs.tabNav;
|
||||
return (nav ? nav.offsetHeight : 52) + 8;
|
||||
},
|
||||
scrollToSection (name) {
|
||||
const el = this.$refs[name];
|
||||
if (!el) return;
|
||||
this.activeDetailTab = name;
|
||||
this.lockScrollSpy = true;
|
||||
const top = el.getBoundingClientRect().top + window.pageYOffset - this.getNavOffset();
|
||||
window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });
|
||||
const unlock = () => {
|
||||
this.lockScrollSpy = false;
|
||||
window.removeEventListener('scrollend', unlock);
|
||||
};
|
||||
window.addEventListener('scrollend', unlock);
|
||||
window.clearTimeout(this.scrollTimer);
|
||||
this.scrollTimer = window.setTimeout(unlock, 900);
|
||||
},
|
||||
handleScroll () {
|
||||
const nav = this.$refs.tabNav;
|
||||
if (nav) {
|
||||
this.anchorStuck = nav.getBoundingClientRect().top <= 1;
|
||||
}
|
||||
if (this.lockScrollSpy) return;
|
||||
const offset = this.getNavOffset() + 16;
|
||||
let current = '';
|
||||
this.detailTabs.forEach((tab) => {
|
||||
const el = this.$refs[tab.name];
|
||||
if (el && el.getBoundingClientRect().top <= offset) {
|
||||
current = tab.name;
|
||||
}
|
||||
});
|
||||
if (current) {
|
||||
this.activeDetailTab = current;
|
||||
}
|
||||
},
|
||||
changePageNum (val) {
|
||||
this.commentParams.pageNumber = val;
|
||||
this.getList();
|
||||
},
|
||||
changePageSize (val) {
|
||||
this.commentParams.pageNumber = 1;
|
||||
this.commentParams.pageSize = val;
|
||||
this.getList();
|
||||
},
|
||||
getList () {
|
||||
this.commentParams.goodsId = this.skuDetail.goodsId;
|
||||
goodsComment(this.commentParams).then(res => {
|
||||
if (res.success) {
|
||||
this.commentList = res.result.records;
|
||||
this.commentTotal = res.result.total;
|
||||
}
|
||||
});
|
||||
goodsCommentNum(this.skuDetail.goodsId).then(res => {
|
||||
if (res.success) {
|
||||
this.commentTypeNum = res.result;
|
||||
}
|
||||
});
|
||||
},
|
||||
viewByGrade (grade) {
|
||||
this.commentParams.grade = grade;
|
||||
this.commentParams.pageNumber = 1;
|
||||
this.getList();
|
||||
},
|
||||
previewImg (img, item) {
|
||||
item.previewImg = img;
|
||||
item.deg = item.deg || 0;
|
||||
},
|
||||
hidePreviewImg (item) {
|
||||
item.previewImg = '';
|
||||
},
|
||||
rotatePreviewImg (type, item) {
|
||||
const current = item.deg || 0;
|
||||
item.deg = type ? current + 90 : current - 90;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('scroll', this.handleScroll, { passive: true });
|
||||
this.getList();
|
||||
if (this.skuDetail.grade === null || this.skuDetail.grade === undefined) {
|
||||
this.skuDetail.grade = 100;
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
window.clearTimeout(this.scrollTimer);
|
||||
const props = defineProps({
|
||||
detail: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const tabNav = ref(null);
|
||||
const intro = ref(null);
|
||||
const comment = ref(null);
|
||||
const params = ref(null);
|
||||
const sectionRefs = { intro, comment, params };
|
||||
|
||||
const detailTabs = ref([
|
||||
{ name: 'intro', label: '商品介绍' },
|
||||
{ name: 'comment', label: '商品评价' },
|
||||
{ name: 'params', label: '商品参数' }
|
||||
]);
|
||||
const activeDetailTab = ref('intro');
|
||||
const anchorStuck = ref(false);
|
||||
const lockScrollSpy = ref(false);
|
||||
let scrollTimer = null;
|
||||
const commentList = ref([]);
|
||||
const commentParams = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
grade: '',
|
||||
goodsId: ''
|
||||
});
|
||||
const commentTypeNum = ref({});
|
||||
const commentTotal = ref(0);
|
||||
|
||||
const skuDetail = computed(() => {
|
||||
return props.detail.data;
|
||||
});
|
||||
const goodsParamsList = computed(() => {
|
||||
const list = props.detail && Array.isArray(props.detail.goodsParamsDTOList) ? props.detail.goodsParamsDTOList : [];
|
||||
const flat = [];
|
||||
|
||||
list.forEach((item) => {
|
||||
if (!item) return;
|
||||
if (Array.isArray(item.goodsParamsItemDTOList)) {
|
||||
flat.push(...item.goodsParamsItemDTOList);
|
||||
} else {
|
||||
flat.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
const cleaned = flat.filter((p) => p && (p.paramName || p.paramId));
|
||||
const hasSort = cleaned.some((p) => p && p.sort !== undefined && p.sort !== null);
|
||||
if (!hasSort) return cleaned;
|
||||
|
||||
return cleaned.slice().sort((a, b) => {
|
||||
const sa = Number(a && a.sort) || 0;
|
||||
const sb = Number(b && b.sort) || 0;
|
||||
return sa - sb;
|
||||
});
|
||||
});
|
||||
|
||||
function getNavOffset () {
|
||||
const nav = tabNav.value;
|
||||
return (nav ? nav.offsetHeight : 52) + 8;
|
||||
}
|
||||
function scrollToSection (name) {
|
||||
const el = sectionRefs[name] && sectionRefs[name].value;
|
||||
if (!el) return;
|
||||
activeDetailTab.value = name;
|
||||
lockScrollSpy.value = true;
|
||||
const top = el.getBoundingClientRect().top + window.pageYOffset - getNavOffset();
|
||||
window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });
|
||||
const unlock = () => {
|
||||
lockScrollSpy.value = false;
|
||||
window.removeEventListener('scrollend', unlock);
|
||||
};
|
||||
window.addEventListener('scrollend', unlock);
|
||||
window.clearTimeout(scrollTimer);
|
||||
scrollTimer = window.setTimeout(unlock, 900);
|
||||
}
|
||||
function handleScroll () {
|
||||
const nav = tabNav.value;
|
||||
if (nav) {
|
||||
anchorStuck.value = nav.getBoundingClientRect().top <= 1;
|
||||
}
|
||||
if (lockScrollSpy.value) return;
|
||||
const offset = getNavOffset() + 16;
|
||||
let current = '';
|
||||
detailTabs.value.forEach((tab) => {
|
||||
const el = sectionRefs[tab.name] && sectionRefs[tab.name].value;
|
||||
if (el && el.getBoundingClientRect().top <= offset) {
|
||||
current = tab.name;
|
||||
}
|
||||
});
|
||||
if (current) {
|
||||
activeDetailTab.value = current;
|
||||
}
|
||||
}
|
||||
function changePageNum (val) {
|
||||
commentParams.pageNumber = val;
|
||||
getList();
|
||||
}
|
||||
function changePageSize (val) {
|
||||
commentParams.pageNumber = 1;
|
||||
commentParams.pageSize = val;
|
||||
getList();
|
||||
}
|
||||
function getList () {
|
||||
commentParams.goodsId = skuDetail.value.goodsId;
|
||||
goodsComment(commentParams).then(res => {
|
||||
if (res.success) {
|
||||
commentList.value = res.result.records;
|
||||
commentTotal.value = res.result.total;
|
||||
}
|
||||
});
|
||||
goodsCommentNum(skuDetail.value.goodsId).then(res => {
|
||||
if (res.success) {
|
||||
commentTypeNum.value = res.result;
|
||||
}
|
||||
});
|
||||
}
|
||||
function viewByGrade (grade) {
|
||||
commentParams.grade = grade;
|
||||
commentParams.pageNumber = 1;
|
||||
getList();
|
||||
}
|
||||
function previewImg (img, item) {
|
||||
item.previewImg = img;
|
||||
item.deg = item.deg || 0;
|
||||
}
|
||||
function hidePreviewImg (item) {
|
||||
item.previewImg = '';
|
||||
}
|
||||
function rotatePreviewImg (type, item) {
|
||||
const current = item.deg || 0;
|
||||
item.deg = type ? current + 90 : current - 90;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
getList();
|
||||
if (skuDetail.value.grade === null || skuDetail.value.grade === undefined) {
|
||||
skuDetail.value.grade = 100;
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
window.clearTimeout(scrollTimer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -65,149 +65,217 @@
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Message } from "@/utils/message";
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { Message, Modal } from "@/utils/message";
|
||||
import { goodsList } from "@/api/goods";
|
||||
import { getStoreById } from "@/api/shopentry";
|
||||
import { cancelStoreCollect, collectStore, isStoreCollection } from "@/api/member";
|
||||
import imTalk from "@/components/mixes/talkIm";
|
||||
import { getIMDetail } from "@/api/common";
|
||||
import { getMemberMsg } from "@/api/login";
|
||||
import storage from "@/plugins/storage.js";
|
||||
|
||||
export default {
|
||||
name: "StoreSide",
|
||||
mixins: [imTalk],
|
||||
props: {
|
||||
storeId: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
goodsId: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
skuId: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
storeNameFallback: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
defineOptions({ name: "StoreSide" });
|
||||
|
||||
const props = defineProps({
|
||||
storeId: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
store: {},
|
||||
logoError: false,
|
||||
collected: false,
|
||||
recommends: [],
|
||||
};
|
||||
goodsId: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
computed: {
|
||||
storeName() {
|
||||
return this.store.storeName || this.storeNameFallback || "店铺";
|
||||
},
|
||||
storeInitial() {
|
||||
return this.storeName.slice(0, 1);
|
||||
},
|
||||
// 自营标记后端可能是 true、1 或 "1"
|
||||
isSelfOperated() {
|
||||
const flag = this.store.selfOperated;
|
||||
return flag === true || flag === 1 || flag === "1";
|
||||
},
|
||||
skuId: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
watch: {
|
||||
storeId: {
|
||||
immediate: true,
|
||||
handler(id) {
|
||||
if (!id) return;
|
||||
this.logoError = false;
|
||||
this.loadStore(id);
|
||||
this.loadRecommend(id);
|
||||
this.loadCollected(id);
|
||||
storeNameFallback: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const store = ref({});
|
||||
const logoError = ref(false);
|
||||
const collected = ref(false);
|
||||
const recommends = ref([]);
|
||||
// 原 talkIm mixin 的 IM 链接
|
||||
const IMLink = ref("");
|
||||
|
||||
const storeName = computed(() => {
|
||||
return store.value.storeName || props.storeNameFallback || "店铺";
|
||||
});
|
||||
const storeInitial = computed(() => {
|
||||
return storeName.value.slice(0, 1);
|
||||
});
|
||||
// 自营标记后端可能是 true、1 或 "1"
|
||||
const isSelfOperated = computed(() => {
|
||||
const flag = store.value.selfOperated;
|
||||
return flag === true || flag === 1 || flag === "1";
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.storeId,
|
||||
(id) => {
|
||||
if (!id) return;
|
||||
logoError.value = false;
|
||||
loadStore(id);
|
||||
loadRecommend(id);
|
||||
loadCollected(id);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 没有评分或小于等于 0 时按满分展示
|
||||
function formatScore(value) {
|
||||
const score = Number(value);
|
||||
if (!Number.isFinite(score) || score <= 0) return "5.00";
|
||||
return score.toFixed(2);
|
||||
}
|
||||
|
||||
function loadStore(id) {
|
||||
getStoreById(id).then((res) => {
|
||||
if (res.success && res.result) store.value = res.result;
|
||||
}).catch((err) => {
|
||||
console.warn('获取店铺信息失败', err);
|
||||
});
|
||||
}
|
||||
|
||||
// 按销量取店内商品,去掉当前商品,最多 4 个
|
||||
function loadRecommend(id) {
|
||||
goodsList({
|
||||
storeId: id,
|
||||
pageNumber: 1,
|
||||
pageSize: 8,
|
||||
sort: "buyCount",
|
||||
order: "desc",
|
||||
}).then((res) => {
|
||||
const records = (res.success && res.result && res.result.records) || [];
|
||||
recommends.value = records
|
||||
.filter((item) => String(item.goodsId) !== String(props.goodsId))
|
||||
.slice(0, 4);
|
||||
}).catch(() => {
|
||||
// 推荐商品为可选数据,加载失败时静默
|
||||
});
|
||||
}
|
||||
|
||||
// 同时有 token 和用户信息才查收藏,避免残留 userInfo 触发登录跳转
|
||||
function loadCollected(id) {
|
||||
if (!storage.getItem("accessToken") || !storage.getItem("userInfo")) return;
|
||||
isStoreCollection("STORE", id).then((res) => {
|
||||
if (res.success && res.result) collected.value = true;
|
||||
}).catch(() => {
|
||||
// 收藏状态为可选数据,失败时静默
|
||||
});
|
||||
}
|
||||
|
||||
function goStore() {
|
||||
router.push({ path: "/merchant", query: { id: props.storeId } });
|
||||
}
|
||||
|
||||
// 推荐商品新开详情页,店铺页仍在当前页打开
|
||||
function goGoods(item) {
|
||||
const href = router.resolve({
|
||||
path: "/goodsDetail",
|
||||
query: { skuId: item.id, goodsId: item.goodsId },
|
||||
}).href;
|
||||
window.open(href, "_blank");
|
||||
}
|
||||
|
||||
// 原 talkIm mixin 的登录提示
|
||||
function promptLogin() {
|
||||
Modal.confirm({
|
||||
title: "温馨提示",
|
||||
content: "请登录后执行此操作",
|
||||
okText: "立即登录",
|
||||
cancelText: "取消",
|
||||
onOk: () => {
|
||||
router.push({
|
||||
path: "/login",
|
||||
query: {
|
||||
rePath: route.path,
|
||||
query: JSON.stringify(route.query || {}),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 获取im信息(原 talkIm mixin)
|
||||
async function getIMDetailMethods() {
|
||||
let res = await getIMDetail();
|
||||
if (res.success) {
|
||||
IMLink.value = res.result;
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转im客服(原 talkIm mixin;本组件无 storeMsg,mixin 的 storeMsg?.storeId 兜底恒为 undefined,等价于直接用传入的 id)
|
||||
async function IMService(id, goodsId, skuId) {
|
||||
if (!storage.getItem("accessToken")) {
|
||||
promptLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await getIMDetailMethods();
|
||||
const userInfo = await getMemberMsg();
|
||||
if (!userInfo.success) {
|
||||
promptLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = storage.getItem("accessToken");
|
||||
const storeId = id;
|
||||
const baseUrl = `${IMLink.value}?token=${accessToken}&id=${storeId}`;
|
||||
|
||||
if (goodsId && skuId) {
|
||||
window.open(`${baseUrl}&goodsId=${goodsId}&skuId=${skuId}`);
|
||||
} else {
|
||||
window.open(baseUrl);
|
||||
}
|
||||
} catch {
|
||||
// token 过期等场景由 request 拦截器处理;未登录时避免未捕获异常
|
||||
if (!storage.getItem("accessToken")) {
|
||||
promptLogin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function contact() {
|
||||
IMService(props.storeId, props.goodsId, props.skuId);
|
||||
}
|
||||
|
||||
// 未登录先去登录,并带回当前商品页
|
||||
async function toggleCollect() {
|
||||
if (!storage.getItem("accessToken") || !storage.getItem("userInfo")) {
|
||||
router.push({
|
||||
path: "/login",
|
||||
query: {
|
||||
rePath: route.path,
|
||||
query: JSON.stringify(route.query || {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 没有评分或小于等于 0 时按满分展示
|
||||
formatScore(value) {
|
||||
const score = Number(value);
|
||||
if (!Number.isFinite(score) || score <= 0) return "5.00";
|
||||
return score.toFixed(2);
|
||||
},
|
||||
loadStore(id) {
|
||||
getStoreById(id).then((res) => {
|
||||
if (res.success && res.result) this.store = res.result;
|
||||
}).catch((err) => {
|
||||
console.warn('获取店铺信息失败', err);
|
||||
});
|
||||
},
|
||||
// 按销量取店内商品,去掉当前商品,最多 4 个
|
||||
loadRecommend(id) {
|
||||
goodsList({
|
||||
storeId: id,
|
||||
pageNumber: 1,
|
||||
pageSize: 8,
|
||||
sort: "buyCount",
|
||||
order: "desc",
|
||||
}).then((res) => {
|
||||
const records = (res.success && res.result && res.result.records) || [];
|
||||
this.recommends = records
|
||||
.filter((item) => String(item.goodsId) !== String(this.goodsId))
|
||||
.slice(0, 4);
|
||||
}).catch(() => {
|
||||
// 推荐商品为可选数据,加载失败时静默
|
||||
});
|
||||
},
|
||||
// 同时有 token 和用户信息才查收藏,避免残留 userInfo 触发登录跳转
|
||||
loadCollected(id) {
|
||||
if (!this.Cookies.getItem("accessToken") || !this.Cookies.getItem("userInfo")) return;
|
||||
isStoreCollection("STORE", id).then((res) => {
|
||||
if (res.success && res.result) this.collected = true;
|
||||
}).catch(() => {
|
||||
// 收藏状态为可选数据,失败时静默
|
||||
});
|
||||
},
|
||||
goStore() {
|
||||
this.$router.push({ path: "/merchant", query: { id: this.storeId } });
|
||||
},
|
||||
// 推荐商品新开详情页,店铺页仍在当前页打开
|
||||
goGoods(item) {
|
||||
const href = this.$router.resolve({
|
||||
path: "/goodsDetail",
|
||||
query: { skuId: item.id, goodsId: item.goodsId },
|
||||
}).href;
|
||||
window.open(href, "_blank");
|
||||
},
|
||||
contact() {
|
||||
this.IMService(this.storeId, this.goodsId, this.skuId);
|
||||
},
|
||||
// 未登录先去登录,并带回当前商品页
|
||||
async toggleCollect() {
|
||||
if (!this.Cookies.getItem("accessToken") || !this.Cookies.getItem("userInfo")) {
|
||||
this.$router.push({
|
||||
path: "/login",
|
||||
query: {
|
||||
rePath: this.$route.path,
|
||||
query: JSON.stringify(this.$route.query || {}),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.collected) {
|
||||
const cancel = await cancelStoreCollect("STORE", this.storeId);
|
||||
if (cancel.success) {
|
||||
this.collected = false;
|
||||
Message.success("已取消收藏");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const collect = await collectStore("STORE", this.storeId);
|
||||
if (collect.code === 200) {
|
||||
this.collected = true;
|
||||
Message.success("收藏店铺成功,可以前往个人中心我的收藏查看");
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (collected.value) {
|
||||
const cancel = await cancelStoreCollect("STORE", props.storeId);
|
||||
if (cancel.success) {
|
||||
collected.value = false;
|
||||
Message.success("已取消收藏");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const collect = await collectStore("STORE", props.storeId);
|
||||
if (collect.code === 200) {
|
||||
collected.value = true;
|
||||
Message.success("收藏店铺成功,可以前往个人中心我的收藏查看");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
Reference in New Issue
Block a user