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:
lifenlong
2026-10-13 07:06:59 +08:00
parent 870bacdd8c
commit 88fc1b0b2a
674 changed files with 71220 additions and 69943 deletions

View File

@@ -4,7 +4,7 @@
<!-- LOGO 搜索 -->
<div class="width_1200 logo">
<div>
<router-link to="/"><img :src="$store.state.logoImg" /></router-link>
<router-link to="/"><img :src="logoImg" /></router-link>
<div>
购物车(<span>{{ goodsTotal }}</span>)
</div>
@@ -242,264 +242,266 @@
</template>
<script>
import { Message, Modal } from "@/utils/message";
import Promotion from "@/components/goodsDetail/Promotion";
import * as APICart from "@/api/cart";
import * as APIMember from "@/api/member";
import { isECoupon } from "@/constants/goodsType";
export default {
name: "Cart",
beforeRouteEnter(to, from, next) {
window.scrollTo(0, 0);
next();
},
components: {
Promotion,
},
data() {
return {
couponAvailable: false, // 展示优惠券
stepIndex: 0, // 当前处于哪一步,购物车==0,填写订单信息==1,成功提交订单==2
goodsTotal: 0, // 商品数量
checkedNum: 0, // 选中数量
allChecked: false, // 全选
loading: false, // 加载状态
cartList: [], // 购物车列表
couponList: [], // 优惠券列表
priceDetailDTO: {}, // 价格明细
skuList: [], // sku列表
};
},
methods: {
// 跳转商品详情
goGoodsDetail(skuId, goodsId) {
let routeUrl = this.$router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routeUrl.href, "_blank");
},
// 跳转店铺首页
goShopPage(id) {
this.$router.push({
path: "/merchant",
query: { id },
});
},
// 同步购物车商品收藏状态
async syncCartCollectionStatus() {
if (!this.Cookies.getItem("userInfo") || !this.cartList.length) return;
const skuIds = [];
this.cartList.forEach((shop) => {
(shop.skuList || []).forEach((goods) => {
goods.isCollected = false;
if (goods.goodsSku && goods.goodsSku.id) {
skuIds.push(goods.goodsSku.id);
}
});
});
const uniqueSkuIds = [...new Set(skuIds)];
await Promise.all(
uniqueSkuIds.map(async (skuId) => {
try {
const res = await APIMember.isCollection("GOODS", skuId);
if (res.success && res.result) {
this.cartList.forEach((shop) => {
(shop.skuList || []).forEach((goods) => {
if (goods.goodsSku && goods.goodsSku.id === skuId) {
goods.isCollected = true;
}
});
});
}
} catch (e) {
// 单个查询失败不影响其他商品
}
})
);
this.$forceUpdate();
},
// 收藏 / 取消收藏商品
handleCollect(goods) {
const skuId = goods.goodsSku && goods.goodsSku.id;
if (!skuId) return;
if (goods.isCollected) {
Modal.confirm({
title: "取消收藏",
content: "确定取消收藏该商品吗?",
onOk: () => {
return APIMember.cancelCollect("GOODS", skuId).then((res) => {
if (res.success) {
goods.isCollected = false;
Message.success("取消收藏成功");
this.$forceUpdate();
};
</script>
<script setup>
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { Message, Modal } from "@/utils/message";
import Promotion from "@/components/goodsDetail/Promotion";
import * as APICart from "@/api/cart";
import * as APIMember from "@/api/member";
import { isECoupon } from "@/constants/goodsType";
import storage from "@/plugins/storage.js";
import { useAppStore } from "@/stores/app";
const router = useRouter();
const appStore = useAppStore();
const logoImg = computed(() => appStore.logoImg);
const couponAvailable = ref(false); // 展示优惠券
const stepIndex = ref(0); // 当前处于哪一步,购物车==0,填写订单信息==1,成功提交订单==2
const goodsTotal = ref(0); // 商品数量
const checkedNum = ref(0); // 选中数量
const allChecked = ref(false); // 全选
const loading = ref(false); // 加载状态
const cartList = ref([]); // 购物车列表
const couponList = ref([]); // 优惠券列表
const priceDetailDTO = ref({}); // 价格明细
const skuList = ref([]); // sku列表
// 跳转商品详情
function goGoodsDetail(skuId, goodsId) {
let routeUrl = router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routeUrl.href, "_blank");
}
// 跳转店铺首页
function goShopPage(id) {
router.push({
path: "/merchant",
query: { id },
});
}
// 同步购物车商品收藏状态
async function syncCartCollectionStatus() {
if (!storage.getItem("userInfo") || !cartList.value.length) return;
const skuIds = [];
cartList.value.forEach((shop) => {
(shop.skuList || []).forEach((goods) => {
goods.isCollected = false;
if (goods.goodsSku && goods.goodsSku.id) {
skuIds.push(goods.goodsSku.id);
}
});
});
const uniqueSkuIds = [...new Set(skuIds)];
await Promise.all(
uniqueSkuIds.map(async (skuId) => {
try {
const res = await APIMember.isCollection("GOODS", skuId);
if (res.success && res.result) {
cartList.value.forEach((shop) => {
(shop.skuList || []).forEach((goods) => {
if (goods.goodsSku && goods.goodsSku.id === skuId) {
goods.isCollected = true;
}
});
},
onCancel: () => {},
});
return;
}
Modal.confirm({
title: "收藏",
content: "商品收藏后可在个人中心我的收藏查看",
onOk: () => {
return APIMember.collectGoods("GOODS", skuId).then((res) => {
if (res.success) {
goods.isCollected = true;
Message.success("收藏商品成功");
this.$forceUpdate();
}
});
},
onCancel: () => {},
});
},
// 删除商品
delGoods(id) {
const idArr = [];
if (!id) {
const list = this.cartList;
list.forEach((shop) => {
shop.skuList.forEach((goods) => {
if (goods.checked) {
idArr.push(goods.goodsSku.id);
}
});
});
} else {
idArr.push(id);
}
Modal.confirm({
title: "删除",
content: "确定要删除该商品吗?",
onOk: () => {
APICart.delCartGoods({ skuIds: idArr.toString() }).then((res) => {
if (res.success) {
Message.success("删除成功");
this.getCartList();
} else {
Message.error(res.message);
}
});
},
});
},
// 清空购物车
clearCart() {
Modal.confirm({
title: "提示",
content: "确定要清空购物车吗?清空后不可恢复",
onOk: () => {
APICart.clearCart().then((res) => {
if (res.success) {
Message.success("清空购物车成功");
this.getCartList();
} else {
Message.error(res.message);
}
});
},
});
},
// 跳转支付页面
pay() {
if (!this.checkedNum) {
Message.warning("请至少选择一件商品");
return;
}
const hasECoupon = this.cartList.some((shop) =>
(shop.skuList || []).some(
(goods) => goods.checked && isECoupon(goods.goodsSku && goods.goodsSku.goodsType)
)
);
if (hasECoupon) {
Message.warning("电子卡券请使用立即购买,不可通过购物车结算");
return;
}
this.$router.push({ path: "/pay", query: { way: "CART" } });
},
// 展示优惠券
showCoupon(storeId, index) {
this.couponAvailable = index;
},
// 设置购买数量
changeNum(val, id) {
if (val) {
APICart.setCartGoodsNum({ skuId: id, num: val }).then((res) => {
if (res.success) {
this.getCartList();
}
});
}
},
// 设置商品选中状态
async changeChecked(status, type, id) {
const check = status ? 1 : 0;
if (type === "all") {
// 全选
await APICart.setCheckedAll({ checked: check });
} else if (type === "shop") {
// 选中店铺所有商品
await APICart.setCheckedSeller({ checked: check, storeId: id });
} else {
// 单个商品
await APICart.setCheckedGoods({ checked: check, skuId: id });
}
this.getCartList();
},
// 领取优惠券
async receiveShopCoupon(item) {
let res = await APIMember.receiveCoupon(item.id);
if (res.success) {
item["disabled"] = true;
Message.success("领取成功");
} else {
Message.error(res.message);
}
},
// 购物车列表
async getCartList() {
this.loading = true;
try {
let res = await APICart.cartGoodsAll();
this.loading = false;
if (res.success) {
this.cartList = res.result.cartList;
this.priceDetailDTO = res.result.priceDetailDTO;
this.skuList = res.result.skuList;
this.checkedNum = 0;
let allChecked = true;
for (let k = 0; k < this.cartList.length; k++) {
let shop = this.cartList[k];
let list = await APIMember.couponList({ storeId: shop.storeId });
shop.couponList.push(...list.result.records);
}
for (let i = 0; i < this.skuList.length; i++) {
if (this.skuList[i].checked) {
this.checkedNum += this.skuList[i].num;
} else {
allChecked = false;
}
}
this.$forceUpdate();
this.allChecked = allChecked;
this.syncCartCollectionStatus();
}
} catch (error) {
this.loading = false;
} catch (e) {
// 单个查询失败不影响其他商品
}
},
},
mounted() {
this.getCartList();
APICart.cartCount().then((res) => {
// 购物车商品数量
if (res.success) this.goodsTotal = res.result;
})
);
}
// 收藏 / 取消收藏商品
function handleCollect(goods) {
const skuId = goods.goodsSku && goods.goodsSku.id;
if (!skuId) return;
if (goods.isCollected) {
Modal.confirm({
title: "取消收藏",
content: "确定取消收藏该商品吗?",
onOk: () => {
return APIMember.cancelCollect("GOODS", skuId).then((res) => {
if (res.success) {
goods.isCollected = false;
Message.success("取消收藏成功");
}
});
},
onCancel: () => {},
});
},
};
return;
}
Modal.confirm({
title: "收藏",
content: "商品收藏后可在个人中心我的收藏查看",
onOk: () => {
return APIMember.collectGoods("GOODS", skuId).then((res) => {
if (res.success) {
goods.isCollected = true;
Message.success("收藏商品成功");
}
});
},
onCancel: () => {},
});
}
// 删除商品
function delGoods(id) {
const idArr = [];
if (!id) {
const list = cartList.value;
list.forEach((shop) => {
shop.skuList.forEach((goods) => {
if (goods.checked) {
idArr.push(goods.goodsSku.id);
}
});
});
} else {
idArr.push(id);
}
Modal.confirm({
title: "删除",
content: "确定要删除该商品吗?",
onOk: () => {
APICart.delCartGoods({ skuIds: idArr.toString() }).then((res) => {
if (res.success) {
Message.success("删除成功");
getCartList();
} else {
Message.error(res.message);
}
});
},
});
}
// 清空购物车
function clearCart() {
Modal.confirm({
title: "提示",
content: "确定要清空购物车吗?清空后不可恢复",
onOk: () => {
APICart.clearCart().then((res) => {
if (res.success) {
Message.success("清空购物车成功");
getCartList();
} else {
Message.error(res.message);
}
});
},
});
}
// 跳转支付页面
function pay() {
if (!checkedNum.value) {
Message.warning("请至少选择一件商品");
return;
}
const hasECoupon = cartList.value.some((shop) =>
(shop.skuList || []).some(
(goods) => goods.checked && isECoupon(goods.goodsSku && goods.goodsSku.goodsType)
)
);
if (hasECoupon) {
Message.warning("电子卡券请使用立即购买,不可通过购物车结算");
return;
}
router.push({ path: "/pay", query: { way: "CART" } });
}
// 展示优惠券
function showCoupon(storeId, index) {
couponAvailable.value = index;
}
// 设置购买数量
function changeNum(val, id) {
if (val) {
APICart.setCartGoodsNum({ skuId: id, num: val }).then((res) => {
if (res.success) {
getCartList();
}
});
}
}
// 设置商品选中状态
async function changeChecked(status, type, id) {
const check = status ? 1 : 0;
if (type === "all") {
// 全选
await APICart.setCheckedAll({ checked: check });
} else if (type === "shop") {
// 选中店铺所有商品
await APICart.setCheckedSeller({ checked: check, storeId: id });
} else {
// 单个商品
await APICart.setCheckedGoods({ checked: check, skuId: id });
}
getCartList();
}
// 领取优惠券
async function receiveShopCoupon(item) {
let res = await APIMember.receiveCoupon(item.id);
if (res.success) {
item["disabled"] = true;
Message.success("领取成功");
} else {
Message.error(res.message);
}
}
// 购物车列表
async function getCartList() {
loading.value = true;
try {
let res = await APICart.cartGoodsAll();
loading.value = false;
if (res.success) {
cartList.value = res.result.cartList;
priceDetailDTO.value = res.result.priceDetailDTO;
skuList.value = res.result.skuList;
checkedNum.value = 0;
let allCheckedVal = true;
for (let k = 0; k < cartList.value.length; k++) {
let shop = cartList.value[k];
let list = await APIMember.couponList({ storeId: shop.storeId });
shop.couponList.push(...list.result.records);
}
for (let i = 0; i < skuList.value.length; i++) {
if (skuList.value[i].checked) {
checkedNum.value += skuList.value[i].num;
} else {
allCheckedVal = false;
}
}
allChecked.value = allCheckedVal;
syncCartCollectionStatus();
}
} catch (error) {
loading.value = false;
}
}
onMounted(() => {
getCartList();
APICart.cartCount().then((res) => {
// 购物车商品数量
if (res.success) goodsTotal.value = res.result;
});
});
</script>
<style scoped lang="scss">

View File

@@ -43,126 +43,126 @@
<BaseFooter></BaseFooter>
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Modal } from "@/utils/message";
import storage from "@/plugins/storage";
import { couponList, receiveCoupon } from "@/api/member.js";
export default {
data() {
return {
list: [], // 优惠券列表
total: 0, // 优惠券总数
loading: false,
params: {
// 请求参数
getType: "FREE",
pageNumber: 1,
pageSize: 20,
},
};
},
methods: {
getList() {
if (this.loading) return;
this.loading = true;
couponList(this.params)
.then((res) => {
if (res && res.success && res.result) {
this.list = res.result.records || [];
this.total = res.result.total || 0;
} else {
this.list = [];
this.total = 0;
}
})
.catch(() => {
this.list = [];
this.total = 0;
})
.finally(() => {
this.loading = false;
});
},
// 分页 改变页码
changePageNum(val) {
this.params.pageNumber = val;
this.getList();
},
// 分页 改变每页数
changePageSize(val) {
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getList();
},
// 领取优惠券
receive(item) {
if (this.isFinished(item)) return;
const userInfo = storage.getItem("userInfo");
const isLogin = userInfo && JSON.parse(userInfo).username;
if (!isLogin) {
Modal.confirm({
title: "温馨提示",
content: "请登录后执行此操作",
okText: "立即登录",
cancelText: "取消",
onOk: () => {
this.$router.push({
path: "/login",
query: {
rePath: this.$route.path,
query: JSON.stringify(this.$route.query),
},
});
const route = useRoute();
const router = useRouter();
const list = ref([]); // 优惠券列表
const total = ref(0); // 优惠券总数
const loading = ref(false);
const params = reactive({
// 请求参数
getType: "FREE",
pageNumber: 1,
pageSize: 20,
});
function getList() {
if (loading.value) return;
loading.value = true;
couponList(params)
.then((res) => {
if (res && res.success && res.result) {
list.value = res.result.records || [];
total.value = res.result.total || 0;
} else {
list.value = [];
total.value = 0;
}
})
.catch(() => {
list.value = [];
total.value = 0;
})
.finally(() => {
loading.value = false;
});
}
// 分页 改变页码
function changePageNum(val) {
params.pageNumber = val;
getList();
}
// 分页 改变每页数
function changePageSize(val) {
params.pageNumber = 1;
params.pageSize = val;
getList();
}
// 领取优惠券
function receive(item) {
if (isFinished(item)) return;
const userInfo = storage.getItem("userInfo");
const isLogin = userInfo && JSON.parse(userInfo).username;
if (!isLogin) {
Modal.confirm({
title: "温馨提示",
content: "请登录后执行此操作",
okText: "立即登录",
cancelText: "取消",
onOk: () => {
router.push({
path: "/login",
query: {
rePath: route.path,
query: JSON.stringify(route.query),
},
});
return;
}
receiveCoupon(item.id)
.then((res) => {
if (!res || !res.success) return;
Modal.confirm({
title: "领取优惠券",
content: "优惠券领取成功,可到我的优惠券页面查看",
okText: "我的优惠券",
cancelText: "立即使用",
closable: true,
onOk: () => {
this.$router.push("/home/Coupons");
},
onCancel: () => {
this.$router.push({
path: "/goodsList",
query: { promotionsId: item.id, promotionType: "COUPON" },
});
},
},
});
return;
}
receiveCoupon(item.id)
.then((res) => {
if (!res || !res.success) return;
Modal.confirm({
title: "领取优惠券",
content: "优惠券领取成功,可到我的优惠券页面查看",
okText: "我的优惠券",
cancelText: "立即使用",
closable: true,
onOk: () => {
router.push("/home/Coupons");
},
onCancel: () => {
router.push({
path: "/goodsList",
query: { promotionsId: item.id, promotionType: "COUPON" },
});
})
.catch((err) => {
console.warn('领取优惠券失败', err);
});
},
// 发行量已领完
isFinished(item) {
return Number(item.publishNum) > 0 && Number(item.receivedNum) >= Number(item.publishNum);
},
// 使用门槛文案
thresholdText(item) {
const threshold = Number(item.consumeThreshold);
if (!threshold) return "无使用门槛";
return `满${item.consumeThreshold}元可用`;
},
// 券范围。店铺名为 platform 时是平台通用券
scopeTag(type, storeName) {
if (type === "PORTION_GOODS") return "商品券";
if (type === "PORTION_GOODS_CATEGORY") return "分类券";
if (storeName && storeName !== "platform") return "店铺券";
return "通用券";
},
},
mounted() {
this.getList();
},
};
},
});
})
.catch((err) => {
console.warn('领取优惠券失败', err);
});
}
// 发行量已领完
function isFinished(item) {
return Number(item.publishNum) > 0 && Number(item.receivedNum) >= Number(item.publishNum);
}
// 使用门槛文案
function thresholdText(item) {
const threshold = Number(item.consumeThreshold);
if (!threshold) return "无使用门槛";
return `满${item.consumeThreshold}元可用`;
}
// 券范围。店铺名为 platform 时是平台通用券
function scopeTag(type, storeName) {
if (type === "PORTION_GOODS") return "商品券";
if (type === "PORTION_GOODS_CATEGORY") return "分类券";
if (storeName && storeName !== "platform") return "店铺券";
return "通用券";
}
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
@import "../assets/styles/coupon.scss";

View File

@@ -4,7 +4,7 @@
<!-- 顶部logo -->
<div class="logo-box">
<img
:src="$store.state.logoImg" width='150'
:src="logoImg" width='150'
@click="$router.push('/')"
/>
<div>修改密码</div>
@@ -12,7 +12,7 @@
<div class="login-container">
<!-- 验证手机号 -->
<el-form
ref="formFirst"
ref="formFirstRef"
:model="formFirst"
:rules="ruleInline"
style="width:300px;"
@@ -47,7 +47,7 @@
</el-form-item>
</el-form>
<el-form
ref="form"
ref="formRef"
:model="form"
:rules="ruleInline"
style="width:300px;"
@@ -79,7 +79,7 @@
</el-form>
<!-- 拼图验证码 -->
<Verify
ref="verify"
ref="verifyRef"
class="verify-con"
:verifyType="verifyType"
@change="verifyChange"
@@ -103,8 +103,9 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { Message } from "@/utils/message";
import * as RegExp from '@/plugins/RegExp.js';
import { md5 } from '@/plugins/md5.js';
@@ -113,143 +114,148 @@ import { sendSms } from '@/api/common.js';
import Verify from '@/components/verify';
import { Lock, Iphone, Document } from '@element-plus/icons-vue';
import config from '@/config';
export default {
name: 'ForgetPassword',
components: { Verify, Lock, Iphone, Document },
data () {
return {
config,
loading: false, // 加载状态
loading1: false, // 第二步加载状态
formFirst: { // 手机验证码表单
// 注册表单
mobile: '',
code: ''
},
form: { // 密码
password: '',
oncePasd: ''
},
year: new Date().getFullYear(), // 当前年份
step: 0, // 步骤
ruleInline: {
// 验证规则
mobile: [
{ required: true, message: '请输入手机号码' },
{
pattern: RegExp.mobile,
trigger: 'blur',
message: '请输入正确的手机号'
}
],
code: [{ required: true, message: '请输入手机验证码' }],
password: [{required: true, message: '密码不能为空'}, {pattern: RegExp.password, message: '密码不能少于6位'}]
},
verifyStatus: false, // 图片验证状态
verifyType: 'FIND_USER', // 图片验证类型
codeMsg: '发送验证码', // 验证码文字
interval: '', // 定时器
time: 60 // 倒计时时间
};
},
methods: {
hideVerify() {
this.$refs.verify?.close?.();
},
// 提交短信验证码,修改密码
next () {
this.$refs.formFirst.validate((valid) => {
if (valid) {
this.loading = true;
let data = JSON.parse(JSON.stringify(this.formFirst));
apiLogin.validateCode(data).then((res) => {
this.loading = false;
if (res.success) {
// Message.success('');
this.step = 1;
} else {
Message.warning(res.message);
}
}).catch(() => { this.loading = false; });
}
});
},
handleSubmit () { // 提交密码
this.$refs.form.validate(valid => {
if (valid) {
let params = JSON.parse(JSON.stringify(this.form));
if (params.password !== params.oncePasd) {
Message.warning('两次输入密码不一致');
return;
};
params.mobile = this.formFirst.mobile;
params.password = md5(params.password);
delete params.oncePasd;
this.loading1 = true;
import { useAppStore } from '@/stores/app';
apiLogin.resetPassword(params).then(res => {
this.loading1 = false;
if (res.success) {
Message.success('修改密码成功');
this.$router.push('login');
}
}).catch(() => { this.loading = false; });
};
});
},
sendCode () { // 发送验证码
if (this.time === 60) {
if (this.formFirst.mobile === '') {
Message.warning('请先填写手机号');
return;
}
if (!this.verifyStatus) {
Message.warning('请先完成安全验证');
return;
}
let params = {
mobile: this.formFirst.mobile,
verificationEnums: 'FIND_USER'
};
sendSms(params).then(res => {
if (res.success) {
Message.success('验证码发送成功');
let that = this;
this.interval = setInterval(() => {
that.time--;
if (that.time === 0) {
that.time = 60;
that.codeMsg = '重新发送';
that.verifyStatus = false;
clearInterval(that.interval);
} else {
that.codeMsg = that.time;
}
}, 1000);
} else {
Message.warning(res.message);
}
});
}
},
verifyChange (con) { // 验证通过
if (!con.status) return;
this.hideVerify();
this.verifyStatus = true;
},
verifyBtnClick () {
if (!this.verifyStatus) {
this.$refs.verify?.init();
}
defineOptions({ name: 'ForgetPassword' });
const router = useRouter();
const appStore = useAppStore();
const logoImg = computed(() => appStore.logoImg);
const loading = ref(false); // 加载状态
const loading1 = ref(false); // 第二步加载状态
const formFirst = reactive({ // 手机验证码表单
// 注册表单
mobile: '',
code: ''
});
const form = reactive({ // 密码
password: '',
oncePasd: ''
});
const year = new Date().getFullYear(); // 当前年份
const step = ref(0); // 步骤
const ruleInline = {
// 验证规则
mobile: [
{ required: true, message: '请输入手机号码' },
{
pattern: RegExp.mobile,
trigger: 'blur',
message: '请输入正确的手机号'
}
},
mounted () {
document.querySelector('.forget-password').style.height = window.innerHeight + 'px'
this.$refs.formFirst.resetFields();
},
watch: {
}
],
code: [{ required: true, message: '请输入手机验证码' }],
password: [{required: true, message: '密码不能为空'}, {pattern: RegExp.password, message: '密码不能少于6位'}]
};
const verifyStatus = ref(false); // 图片验证状态
const verifyType = ref('FIND_USER'); // 图片验证类型
const codeMsg = ref('发送验证码'); // 验证码文字
const interval = ref(''); // 定时器
const time = ref(60); // 倒计时时间
const formFirstRef = ref(null);
const formRef = ref(null);
const verifyRef = ref(null);
function hideVerify() {
verifyRef.value?.close?.();
}
// 提交短信验证码,修改密码
function next () {
formFirstRef.value.validate((valid) => {
if (valid) {
loading.value = true;
let data = JSON.parse(JSON.stringify(formFirst));
apiLogin.validateCode(data).then((res) => {
loading.value = false;
if (res.success) {
// Message.success('');
step.value = 1;
} else {
Message.warning(res.message);
}
}).catch(() => { loading.value = false; });
}
});
}
function handleSubmit () { // 提交密码
formRef.value.validate(valid => {
if (valid) {
let params = JSON.parse(JSON.stringify(form));
if (params.password !== params.oncePasd) {
Message.warning('两次输入密码不一致');
return;
};
params.mobile = formFirst.mobile;
params.password = md5(params.password);
delete params.oncePasd;
loading1.value = true;
apiLogin.resetPassword(params).then(res => {
loading1.value = false;
if (res.success) {
Message.success('修改密码成功');
router.push('login');
}
}).catch(() => { loading.value = false; });
};
});
}
function sendCode () { // 发送验证码
if (time.value === 60) {
if (formFirst.mobile === '') {
Message.warning('请先填写手机号');
return;
}
if (!verifyStatus.value) {
Message.warning('请先完成安全验证');
return;
}
let params = {
mobile: formFirst.mobile,
verificationEnums: 'FIND_USER'
};
sendSms(params).then(res => {
if (res.success) {
Message.success('验证码发送成功');
interval.value = setInterval(() => {
time.value--;
if (time.value === 0) {
time.value = 60;
codeMsg.value = '重新发送';
verifyStatus.value = false;
clearInterval(interval.value);
} else {
codeMsg.value = time.value;
}
}, 1000);
} else {
Message.warning(res.message);
}
});
}
}
function verifyChange (con) { // 验证通过
if (!con.status) return;
hideVerify();
verifyStatus.value = true;
}
function verifyBtnClick () {
if (!verifyStatus.value) {
verifyRef.value?.init();
}
}
onMounted(() => {
document.querySelector('.forget-password').style.height = window.innerHeight + 'px'
formFirstRef.value.resetFields();
});
</script>
<style scoped lang="scss">

View File

@@ -30,120 +30,120 @@
</template>
<script>
import { Message } from "@/utils/message";
import Search from "@/components/Search";
import ShowGoods from "@/components/goodsDetail/ShowGoods";
import empty from "@/components/empty/Main";
import ShowGoodsDetail from "@/components/goodsDetail/ShowGoodsDetail";
import { goodsSkuDetail } from "@/api/goods";
import { getGoodsDistribution } from "@/api/member";
export default {
name: "GoodsDetail",
beforeRouteEnter (to, from, next) {
window.scrollTo(0, 0);
next();
},
created () {
this.getGoodsDetail();
},
data () {
return {
goodsMsg: {}, // 商品信息
isLoading: false, // 加载状态
categoryBar: [], // 分类
takeDownSale:false, // 是否下架
};
},
methods: {
// 跳转首页或商品页面
target(url){
this.$router.push({path: url})
},
// 点击规格
targetClickSku (val) {
this.getGoodsDetail(val);
},
// 获取商品详情
getGoodsDetail (val) {
this.isLoading = true;
const params = val || this.$route.query;
// 分销员id
let distributionId =
params && params.distributionId
? params.distributionId
: this.Cookies.getItem("distributionId");
// 如果有分销信息
if (distributionId) {
// 先存储
this.Cookies.setItem("distributionId", params.distributionId);
let _this = this;
// 绑定关系
getGoodsDistribution(params.distributionId).then((res) => {
// 绑定成功,则清除关系
if (res.success) {
_this.Cookies.removeItem("distributionId");
}
});
}
goodsSkuDetail(params)
.then((res) => {
this.isLoading = false;
if (res.success) {
const result = res.result;
const cateName = res.result.categoryName;
const cateId = result.data.categoryPath.split(",");
const cateArr = [];
cateId.forEach((e, index) => {
// 插入分类id和name
cateArr.push({
id: e,
name: cateName ? cateName[index] : "",
});
});
this.categoryBar = cateArr;
this["goodsMsg"] = res.result;
if (!this.goodsMsg.data.intro) {
this.goodsMsg.data.intro = ''
}
} else {
Message.error(res.message);
this.isLoading = false
}
})
.catch((e) => {
this.isLoading = false
if(e.code === 11001){
this.takeDownSale = true
}
});
},
goGoodsList (currIndex) {
// 跳转商品列表
const arr = [];
this.categoryBar.forEach((e, index) => {
if (index <= currIndex) {
arr.push(e.id);
}
});
return {
path: "/goodsList",
query: { categoryId: arr.toString() },
};
},
},
watch: {},
components: { Search,
ShowGoods,
ShowGoodsDetail,
empty },
};
</script>
<script setup>
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "@/utils/message";
import storage from "@/plugins/storage.js";
import Search from "@/components/Search";
import ShowGoods from "@/components/goodsDetail/ShowGoods";
import empty from "@/components/empty/Main";
import ShowGoodsDetail from "@/components/goodsDetail/ShowGoodsDetail";
import { goodsSkuDetail } from "@/api/goods";
import { getGoodsDistribution } from "@/api/member";
const route = useRoute();
const router = useRouter();
const goodsMsg = ref({}); // 商品信息
const isLoading = ref(false); // 加载状态
const categoryBar = ref([]); // 分类
const takeDownSale = ref(false); // 是否下架
// 跳转首页或商品页面
function target(url){
router.push({path: url})
}
// 点击规格
function targetClickSku (val) {
getGoodsDetail(val);
}
// 获取商品详情
function getGoodsDetail (val) {
isLoading.value = true;
const params = val || route.query;
// 分销员id
let distributionId =
params && params.distributionId
? params.distributionId
: storage.getItem("distributionId");
// 如果有分销信息
if (distributionId) {
// 先存储
storage.setItem("distributionId", params.distributionId);
// 绑定关系
getGoodsDistribution(params.distributionId).then((res) => {
// 绑定成功,则清除关系
if (res.success) {
storage.removeItem("distributionId");
}
});
}
goodsSkuDetail(params)
.then((res) => {
isLoading.value = false;
if (res.success) {
const result = res.result;
const cateName = res.result.categoryName;
const cateId = result.data.categoryPath.split(",");
const cateArr = [];
cateId.forEach((e, index) => {
// 插入分类id和name
cateArr.push({
id: e,
name: cateName ? cateName[index] : "",
});
});
categoryBar.value = cateArr;
goodsMsg.value = res.result;
if (!goodsMsg.value.data.intro) {
goodsMsg.value.data.intro = ''
}
} else {
Message.error(res.message);
isLoading.value = false
}
})
.catch((e) => {
isLoading.value = false
if(e.code === 11001){
takeDownSale.value = true
}
});
}
function goGoodsList (currIndex) {
// 跳转商品列表
const arr = [];
categoryBar.value.forEach((e, index) => {
if (index <= currIndex) {
arr.push(e.id);
}
});
return {
path: "/goodsList",
query: { categoryId: arr.toString() },
};
}
// 初始化(原 created 钩子逻辑)
getGoodsDetail();
</script>
<style scoped lang="scss">
.goods-detail-page {
background: #f5f5f5;

View File

@@ -104,199 +104,207 @@
</template>
<script>
import { Message } from "@/utils/message";
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import GoodsClassNav from "@/components/nav/GoodsClassNav";
import GoodsThumb from "@/components/goods/GoodsThumb.vue";
import * as apiGoods from "@/api/goods";
export default {
name: "GoodsList",
beforeRouteEnter(to, from, next) {
window.scrollTo(0, 0);
next();
},
data() {
return {
sortIndex: 0, // 排序状态
priceSortIndex: 4,
sortPriceIndex: false, // 判断价格升序还是降序
searched: false,
recommendList: [],
goodsTool: [
// 排序类型
{ title: "综合", en: "" },
{ title: "销量", en: "buyCount" },
{ title: "评论数", en: "commentNum" },
{ title: "新品", en: "releaseTime" },
],
goodsList: [], // 商品列表
loading: false, // 加载状态
total: 0, // 列表总数
params: {
// 请求参数
pageNumber: 1,
pageSize: 20,
categoryId: "",
},
};
},
watch: {
$route() {
const { keyword, categoryId, promotionType, promotionsId } = this.$route.query;
if (categoryId) {
const cateId = categoryId.split(",");
Object.assign(this.params, this.$route.query);
this.params.categoryId = cateId[cateId.length - 1];
}
if (promotionType) {
this.params.promotionType = promotionType;
}
if (promotionsId) {
this.params.promotionsId = promotionsId;
}
if (keyword) {
// handleSearch 内部会调 getGoodsList,此处不再重复请求
this.handleSearch(keyword);
} else {
this.params.keyword = "";
this.getGoodsList();
}
},
},
computed: {
displayGoods() {
if (this.goodsList.length) return this.goodsList;
return this.searched && !this.loading ? this.recommendList : [];
},
emptyTitle() {
const keyword = this.params.keyword;
return keyword ? `没有找到与“${keyword}”相关的商品` : "暂无商品";
},
},
methods: {
// 搜索
handleSearch(key) {
this.params.keyword = key;
this.$route.query.keyword = key
this.params.pageNumber = 1;
this.getGoodsList();
},
orderBy(data, index) {
// 排序
this.sortIndex = index;
this.params.sort = data;
this.params.order = "desc";
if (data === "price") {
if (!this.sortPriceIndex) {
this.sortPriceIndex = "asc";
} else {
this.sortPriceIndex === "desc"
? (this.sortPriceIndex = "asc")
: (this.sortPriceIndex = "desc");
}
this.params.order = this.sortPriceIndex;
} else {
this.sortPriceIndex = false;
}
this.getGoodsList();
},
formatGrade(grade) {
if (grade === null || grade === undefined || grade === "") {
return 100;
}
const num = Number(grade);
if (Number.isNaN(num)) {
return 100;
}
const fixed = Number(num.toFixed(1));
return fixed % 1 === 0 ? fixed.toFixed(0) : String(fixed);
},
goGoodsDetail(skuId, goodsId) {
const location = {
path: "/goodsDetail",
query: { skuId, goodsId },
};
window.open(this.$router.resolve(location).href, "_blank");
},
goShopPage(id) {
if (!id) {
Message.warning("店铺信息不存在");
return;
}
this.$router.push({
path: "/merchant",
query: { id },
});
},
// 分页 修改页码
changePageNum(val) {
this.params.pageNumber = val;
this.getGoodsList();
},
// 分页 修改页数
changePageSize(val) {
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getGoodsList();
},
// 获取商品列表
getGoodsList() {
this.loading = true;
apiGoods
.goodsList(this.params)
.then((res) => {
this.loading = false;
if (res.success) {
this.goodsList = res.result.records || [];
this.total = res.result.total;
if (!this.goodsList.length) this.loadRecommend();
}
})
.catch(() => {
this.goodsList = [];
})
.finally(() => {
this.loading = false;
this.searched = true;
});
},
loadRecommend() {
if (this.recommendList.length) return;
apiGoods
.goodsList({ pageNumber: 1, pageSize: 10, sort: "buyCount", order: "desc" })
.then((res) => {
if (res.success) this.recommendList = res.result.records || [];
})
.catch(() => {
// 推荐列表为可选数据,加载失败不阻塞搜索页
});
},
getParams(val) {
// 筛选条件回显
Object.assign(this.params, val);
this.getGoodsList();
},
},
created() {
if (this.$route.query.categoryId) {
let cateId = this.$route.query.categoryId.split(",");
Object.assign(this.params, this.$route.query);
this.params.categoryId = cateId[cateId.length - 1];
} else {
Object.assign(this.params, this.$route.query);
}
this.getGoodsList();
},
components: {
GoodsClassNav,
GoodsThumb,
ArrowDown,
ArrowUp,
},
};
</script>
<script setup>
import { computed, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "@/utils/message";
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import GoodsClassNav from "@/components/nav/GoodsClassNav";
import GoodsThumb from "@/components/goods/GoodsThumb.vue";
import * as apiGoods from "@/api/goods";
const route = useRoute();
const router = useRouter();
const sortIndex = ref(0); // 排序状态
const priceSortIndex = ref(4);
const sortPriceIndex = ref(false); // 判断价格升序还是降序
const searched = ref(false);
const recommendList = ref([]);
// 排序类型
const goodsTool = [
{ title: "综合", en: "" },
{ title: "销量", en: "buyCount" },
{ title: "评论数", en: "commentNum" },
{ title: "新品", en: "releaseTime" },
];
const goodsList = ref([]); // 商品列表
const loading = ref(false); // 加载状态
const total = ref(0); // 列表总数
// 请求参数
const params = reactive({
pageNumber: 1,
pageSize: 20,
categoryId: "",
});
const displayGoods = computed(() => {
if (goodsList.value.length) return goodsList.value;
return searched.value && !loading.value ? recommendList.value : [];
});
const emptyTitle = computed(() => {
const keyword = params.keyword;
return keyword ? `没有找到与“${keyword}”相关的商品` : "暂无商品";
});
watch(
() => route.query,
(query) => {
const { keyword, categoryId, promotionType, promotionsId } = query;
if (categoryId) {
const cateId = categoryId.split(",");
Object.assign(params, query);
params.categoryId = cateId[cateId.length - 1];
}
if (promotionType) {
params.promotionType = promotionType;
}
if (promotionsId) {
params.promotionsId = promotionsId;
}
if (keyword) {
// handleSearch 内部会调 getGoodsList,此处不再重复请求
handleSearch(keyword);
} else {
params.keyword = "";
getGoodsList();
}
}
);
// 搜索
function handleSearch(key) {
params.keyword = key;
route.query.keyword = key;
params.pageNumber = 1;
getGoodsList();
}
// 排序
function orderBy(data, index) {
sortIndex.value = index;
params.sort = data;
params.order = "desc";
if (data === "price") {
if (!sortPriceIndex.value) {
sortPriceIndex.value = "asc";
} else {
sortPriceIndex.value === "desc"
? (sortPriceIndex.value = "asc")
: (sortPriceIndex.value = "desc");
}
params.order = sortPriceIndex.value;
} else {
sortPriceIndex.value = false;
}
getGoodsList();
}
function formatGrade(grade) {
if (grade === null || grade === undefined || grade === "") {
return 100;
}
const num = Number(grade);
if (Number.isNaN(num)) {
return 100;
}
const fixed = Number(num.toFixed(1));
return fixed % 1 === 0 ? fixed.toFixed(0) : String(fixed);
}
function goGoodsDetail(skuId, goodsId) {
const location = {
path: "/goodsDetail",
query: { skuId, goodsId },
};
window.open(router.resolve(location).href, "_blank");
}
function goShopPage(id) {
if (!id) {
Message.warning("店铺信息不存在");
return;
}
router.push({
path: "/merchant",
query: { id },
});
}
// 分页 修改页码
function changePageNum(val) {
params.pageNumber = val;
getGoodsList();
}
// 分页 修改页数
function changePageSize(val) {
params.pageNumber = 1;
params.pageSize = val;
getGoodsList();
}
// 获取商品列表
function getGoodsList() {
loading.value = true;
apiGoods
.goodsList(params)
.then((res) => {
loading.value = false;
if (res.success) {
goodsList.value = res.result.records || [];
total.value = res.result.total;
if (!goodsList.value.length) loadRecommend();
}
})
.catch(() => {
goodsList.value = [];
})
.finally(() => {
loading.value = false;
searched.value = true;
});
}
function loadRecommend() {
if (recommendList.value.length) return;
apiGoods
.goodsList({ pageNumber: 1, pageSize: 10, sort: "buyCount", order: "desc" })
.then((res) => {
if (res.success) recommendList.value = res.result.records || [];
})
.catch(() => {
// 推荐列表为可选数据,加载失败不阻塞搜索页
});
}
// 筛选条件回显
function getParams(val) {
Object.assign(params, val);
getGoodsList();
}
// 初始化(原 created 钩子逻辑)
if (route.query.categoryId) {
const cateId = route.query.categoryId.split(",");
Object.assign(params, route.query);
params.categoryId = cateId[cateId.length - 1];
} else {
Object.assign(params, route.query);
}
getGoodsList();
</script>
<style scoped lang="scss">
@import "../assets/styles/goodsList.scss";
.cate-nav{

View File

@@ -32,7 +32,7 @@
<div class="index-main">
<!-- 商品分类 + 楼层装修(同一宽度容器,保证与分类栏左缘对齐) -->
<cateNav :showAlways="true" v-if="showNav" :large="carouselLarge" :opacity="carouselOpacity"></cateNav>
<model-form ref="modelForm" :data="modelForm"></model-form>
<model-form :data="modelFormData"></model-form>
</div>
<!-- 底部栏 -->
<BaseFooter></BaseFooter>
@@ -41,184 +41,179 @@
</div>
</template>
<script>
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import Search from '@/components/Search';
import ModelForm from '@/components/indexDecorate/ModelForm';
import HoverSearch from '@/components/header/hoverSearch';
import fixedBar from '@/components/fixed/index';
import storage from "@/plugins/storage";
import { indexData,getAutoCoup } from '@/api/index.js';
import {seckillByDay} from '@/api/promotion'
export default {
name: 'Index',
mounted () {
this.getIndexData();
let that = this;
window.onscroll = function () {
let top = document.documentElement.scrollTop || document.body.scrollTop;
if (top > 300) {
that.topSearchShow = true;
} else {
that.topSearchShow = false;
}
};
if(storage.getItem('userInfo')){
this.getAutoCoup()
}
},
data () {
return {
autoCoupList:[],
showCpmodel:false,
modelForm: { list: [] }, // 楼层装修数据
topAdvert: {}, // 顶部广告
showNav: false, // 是否展示分类栏
topSearchShow: false, // 滚动后顶部搜索栏展示
carouselLarge: false, // 不同轮播分类尺寸
carouselOpacity: false // 不同轮播分类样式
};
},
props:{
pageData:{
type:null,
default:""
}
},
// created(){
import { indexData, getAutoCoup } from '@/api/index.js';
import { seckillByDay } from '@/api/promotion';
// },
methods: {
// 优惠券可用范围
useScope (type, storeName) {
let shop = '平台';
let goods = '全部商品'
if (storeName !== 'platform') shop = storeName
switch (type) {
case 'ALL':
goods = '全部商品'
break;
case 'PORTION_GOODS':
goods = '部分商品'
break;
case 'PORTION_GOODS_CATEGORY':
goods = '部分分类商品'
break;
defineOptions({ name: "Index" });
const props = defineProps({
pageData: {
type: null,
default: "",
},
});
const autoCoupList = ref([]);
const showCpmodel = ref(false);
const modelFormData = ref({ list: [] }); // 楼层装修数据
const topAdvert = ref({}); // 顶部广告
const showNav = ref(false); // 是否展示分类栏
const topSearchShow = ref(false); // 滚动后顶部搜索栏展示
const carouselLarge = ref(false); // 不同轮播分类尺寸
const carouselOpacity = ref(false); // 不同轮播分类样式
// 优惠券可用范围
function useScope(type, storeName) {
let shop = '平台';
let goods = '全部商品';
if (storeName !== 'platform') shop = storeName;
switch (type) {
case 'ALL':
goods = '全部商品';
break;
case 'PORTION_GOODS':
goods = '部分商品';
break;
case 'PORTION_GOODS_CATEGORY':
goods = '部分分类商品';
break;
}
return `${shop}${goods}可用`;
}
function fetchAutoCoup() {
let data = new Date();
let datas = data.getDate();
let hours = data.getHours();
let flagCoup = storage.getItem('getTimes'); //缓存
if (flagCoup && flagCoup != undefined && flagCoup != null) { //判断当前是否有缓存
if (Number(datas) > Number(flagCoup)) { //判断缓存是否小于当前天数
if (Number(hours) >= 6) { //超过或等于6时清楚缓存
storage.setItem('getTimes', datas); //存储缓存
getcps();
}
return `${shop}${goods}可用`
},
getAutoCoup(){
let data = new Date()
let datas = data.getDate()
let hours = data.getHours()
let flagCoup = storage.getItem('getTimes') //缓存
if(flagCoup && flagCoup != undefined && flagCoup != null){ //判断当前是否有缓存
if(Number(datas) > Number(flagCoup)){ //判断缓存是否小于当前天数
if(Number(hours) >= 6){//超过或等于6时清楚缓存
storage.setItem('getTimes',datas)//存储缓存
this.getcps()
}
}
} else {
getcps();
}
}
function getcps() {
let data = new Date();
let datas = data.getDate();
getAutoCoup().then(res => { //调用自动发券
if (res.success) {
autoCoupList.value.push(...res.result);
let objs = {};
autoCoupList.value = autoCoupList.value.reduce((cur, next) => {
//对象去重
if (next.id != undefined) {
objs[next.id]
? ""
: (objs[next.id] = true && cur.push(next));
}
}else{
// window.localStorage.setItem('getTimes',datas)//存储缓存
this.getcps()
return cur;
}, []);
if (autoCoupList.value != '' && autoCoupList.value.length > 0) {
showCpmodel.value = true;
}
},
getcps(){
let data = new Date()
let datas = data.getDate()
getAutoCoup().then(res=>{ //调用自动发券
if(res.success){
this.autoCoupList.push(...res.result);
let objs = {};
this.autoCoupList = this.autoCoupList.reduce((cur, next) => {
//对象去重
if (next.id != undefined) {
objs[next.id]
? ""
: (objs[next.id] = true && cur.push(next));
}
return cur;
}, []);
if(this.autoCoupList != '' && this.autoCoupList.length > 0){
this.showCpmodel = true;
}
storage.setItem('getTimes',datas)//存储缓存
}
}).catch((err) => {
console.warn('获取自动发券信息失败', err);
});
},
handleReachBottom(){
storage.setItem('getTimes', datas); //存储缓存
}
}).catch((err) => {
console.warn('获取自动发券信息失败', err);
});
}
} ,
getIndexData () {
if(this.pageData){
this.parsePageData(JSON.stringify(this.pageData)).catch(() => {
function handleReachBottom() {
}
function getIndexData() {
if (props.pageData) {
parsePageData(JSON.stringify(props.pageData)).catch(() => {
// 装修数据解析失败时保持默认空楼层
});
} else {
// 获取首页装修数据
indexData({ clientType: 'PC' }).then(async (res) => {
if (res.success && res.result?.pageData) {
try {
await parsePageData(res.result.pageData);
} catch {
// 装修数据解析失败时保持默认空楼层
});
}
else{
// 获取首页装修数据
indexData({ clientType: 'PC' }).then(async (res) => {
if (res.success && res.result?.pageData) {
try {
await this.parsePageData(res.result.pageData);
} catch {
// 装修数据解析失败时保持默认空楼层
}
}
}).catch((err) => {
console.warn('获取首页装修数据失败', err);
});
}
},
async parsePageData(pageData){
const dataJson = typeof pageData === "string" ? JSON.parse(pageData) : pageData;
if (!dataJson?.list?.length) {
return;
}
// 秒杀活动不是装修的数据,需要调用接口判断是否有秒杀商品
// 轮播图根据不同轮播,样式不同
for (let i = 0; i < dataJson.list.length; i++) {
let type = dataJson.list[i].type
if (type === 'carousel2') {
this.carouselLarge = true;
} else if (type === 'carousel1') {
this.carouselLarge = true
this.carouselOpacity = true
} else if (type === 'seckill') {
let seckill = await this.getListByDay()
dataJson.list[i].options.list = seckill
}
}
this.modelForm = dataJson;
if (dataJson.list[1]) {
storage.setItem('navList', dataJson.list[1])
}
this.showNav = true
if (dataJson.list[0]) {
this.topAdvert = dataJson.list[0];
}
},
}).catch((err) => {
console.warn('获取首页装修数据失败', err);
});
}
}
async getListByDay () { // 当天秒杀活动
try {
const res = await seckillByDay()
if (res.success && res.result?.length) {
return res.result
}
} catch {
// 秒杀接口失败时不影响首页其他模块
}
return []
async function parsePageData(pageData) {
const dataJson = typeof pageData === "string" ? JSON.parse(pageData) : pageData;
if (!dataJson?.list?.length) {
return;
}
// 秒杀活动不是装修的数据,需要调用接口判断是否有秒杀商品
// 轮播图根据不同轮播,样式不同
for (let i = 0; i < dataJson.list.length; i++) {
let type = dataJson.list[i].type;
if (type === 'carousel2') {
carouselLarge.value = true;
} else if (type === 'carousel1') {
carouselLarge.value = true;
carouselOpacity.value = true;
} else if (type === 'seckill') {
let seckill = await getListByDay();
dataJson.list[i].options.list = seckill;
}
},
components: { Search,
ModelForm,
HoverSearch,
fixedBar }
};
}
modelFormData.value = dataJson;
if (dataJson.list[1]) {
storage.setItem('navList', dataJson.list[1]);
}
showNav.value = true;
if (dataJson.list[0]) {
topAdvert.value = dataJson.list[0];
}
}
async function getListByDay() { // 当天秒杀活动
try {
const res = await seckillByDay();
if (res.success && res.result?.length) {
return res.result;
}
} catch {
// 秒杀接口失败时不影响首页其他模块
}
return [];
}
function handleScroll() {
const top = document.documentElement.scrollTop || document.body.scrollTop;
topSearchShow.value = top > 300;
}
onMounted(() => {
getIndexData();
window.onscroll = handleScroll;
if (storage.getItem('userInfo')) {
fetchAutoCoup();
}
});
onUnmounted(() => {
if (window.onscroll === handleScroll) {
window.onscroll = null;
}
});
</script>
<style scoped lang="scss">

View File

@@ -3,7 +3,7 @@
<!-- 顶部logo -->
<div class="top-content" @click="hideVerify">
<div class="logo-box">
<img :src="$store.state.logoImg" @click="$router.push('/')" />
<img :src="logoImg" @click="$router.push('/')" />
<div>欢迎登录</div>
</div>
</div>
@@ -27,7 +27,7 @@
<div>
<div>
<!-- 账号密码登录 -->
<el-form ref="formInline" :model="formData" :rules="ruleInline" v-show="type === true"
<el-form ref="formInlineRef" :model="formData" :rules="ruleInline" v-show="type === true"
@click.self="hideVerify">
<el-form-item prop="username">
<el-input type="text" v-model="formData.username" clearable placeholder="用户名">
@@ -44,7 +44,7 @@
</el-form-item>
</el-form>
<!-- 验证码登录 -->
<el-form ref="formSms" :model="formSms" :rules="ruleInline" v-show="type === false"
<el-form ref="formSmsRef" :model="formSms" :rules="ruleInline" v-show="type === false"
@click.self="hideVerify">
<el-form-item prop="mobile">
<el-input type="text" v-model="formSms.mobile" clearable placeholder="手机号">
@@ -88,7 +88,7 @@
<!-- 拼图验证码 -->
<verify ref="verify" class="verify-con" verifyType="LOGIN" @change="verifyChange"></verify>
<verify ref="verifyRef" class="verify-con" verifyType="LOGIN" @change="verifyChange"></verify>
</div>
<div class="foot">
<div type="flex" justify="space-around" class="help">
@@ -105,8 +105,10 @@
</div>
</template>
<script>
<script setup>
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message, Spin } from "@/utils/message";
import * as RegExp from "@/plugins/RegExp.js";
import { md5 } from "@/plugins/md5.js";
@@ -117,235 +119,240 @@ import storage from "@/plugins/storage.js";
import verify from "@/components/verify";
import { User, Lock, Document, Iphone } from "@element-plus/icons-vue";
import config from "@/config";
import { useAppStore } from "@/stores/app";
export default {
name: "Login",
components: {
verify,
User,
Lock,
Document,
Iphone,
},
data() {
return {
config,
type: true, // true 账号登录 false 验证码登录
formData: {
// 登录表单
username: "",
password: "",
},
formSms: {
// 手机号登录
code: "",
mobile: "",
},
verifyStatus: false, // 是否图片验证通过
codeSent: false, // 是否已成功发送短信验证码
ruleInline: {
// 验证规则
username: [{required: true, message: "请输入用户名"}],
password: [
{required: true, message: "请输入密码"},
{type: "string", min: 6, message: "密码不能少于6位"},
],
mobile: [
{required: true, message: "请输入手机号码"},
{
pattern: RegExp.mobile,
message: "请输入正确的手机号",
},
],
code: [{required: true, message: "请输入手机验证码"}],
},
codeMsg: "发送验证码", // 验证码文字
interval: null, // 定时器
time: 60, // 倒计时
year: new Date().getFullYear(),
};
},
methods: {
hideVerify() {
this.$refs.verify?.close?.();
},
defineOptions({ name: "Login" });
// 登录
handleSubmit(name) {
this.$refs[name].validate((valid) => {
if (valid) {
if (this.type) {
this.$refs.verify?.init();
} else {
if (!this.codeSent) {
Message.warning("请先获取短信验证码");
return;
}
const data = JSON.parse(JSON.stringify(this.formSms));
apiLogin.smsLogin(data).then((res) => {
this.hideVerify();
if (res.success) {
Message.success("登录成功");
storage.setItem("accessToken", res.result.accessToken);
storage.setItem("refreshToken", res.result.refreshToken);
storage.setItem('getTimes',0)
apiLogin.getMemberMsg().then((res) => {
if (res.success) {
storage.setItem("userInfo", res.result);
let query = this.$route.query;
if (query.rePath) {
this.$router.push({
path: query.rePath,
query: JSON.parse(query.query),
});
} else {
this.$router.push("/");
}
}
});
} else {
Message.error(res.message);
}
}).catch((err) => {
console.warn('短信登录失败', err);
});
}
}
});
},
// 发送手机验证码
sendCode() {
if (this.time !== 60) return;
if (this.formSms.mobile === "") {
Message.warning("请先填写手机号");
return;
}
if (!RegExp.mobile.test(this.formSms.mobile)) {
Message.warning("请输入正确的手机号");
return;
}
this.$refs.verify?.init();
},
dispatchSms() {
sendSms({
mobile: this.formSms.mobile,
verificationEnums: "LOGIN",
}).then((res) => {
if (res.success) {
Message.success("验证码发送成功");
this.codeSent = true;
this.interval = setInterval(() => {
this.time--;
if (this.time === 0) {
this.time = 60;
this.codeMsg = "重新发送";
this.verifyStatus = false;
this.codeSent = false;
clearInterval(this.interval);
} else {
this.codeMsg = this.time;
}
}, 1000);
} else {
this.verifyStatus = false;
Message.warning(res.message);
}
}).catch(() => {
this.verifyStatus = false;
});
},
verifyChange(con) {
// 拼图验证码回显
if (!con.status) return;
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
if (this.type === true) {
// 账号密码登录
let data = JSON.parse(JSON.stringify(this.formData));
data.password = md5(data.password);
this.hideVerify();
Spin.show();
apiLogin
.login(data)
.then((res) => {
if (res.success) {
this.loginSuccess(res.result.accessToken,res.result.refreshToken);
} else {
Spin.hide();
Message.error(res.message);
}
})
.catch(() => {
Spin.hide();
});
} else {
this.verifyStatus = true;
this.hideVerify();
this.dispatchSms();
}
},
handleWebLogin(type) {
// 第三方登录
webLogin(type);
},
loginSuccess(accessToken,refreshToken){
Message.success("登录成功");
storage.setItem("accessToken", accessToken);
storage.setItem("refreshToken", refreshToken);
apiLogin.getMemberMsg().then((res) => {
Spin.hide();
if (res.success) {
storage.setItem("userInfo", res.result);
let query = this.$route.query;
if (query.rePath) {
this.$router.push({
path: query.rePath,
query: JSON.parse(query.query),
});
} else {
this.$router.push("/");
}
}
});
},
const logoImg = computed(() => appStore.logoImg);
},
beforeUnmount() {
clearInterval(this.interval);
},
mounted() {
let uuid = this.$route.query.state;
if (uuid) {
storage.setItem("uuid", uuid);
loginCallback(uuid).then((res) => {
if (res.success) {
this.loginSuccess(res.result.accessToken,res.result.refreshToken);
}
});
}
},
watch: {
type(v) {
if (v) {
this.$refs.formInline.resetFields();
} else {
this.$refs.formSms.resetFields();
}
this.verifyStatus = false;
this.codeSent = false;
this.hideVerify();
clearInterval(this.interval);
this.codeMsg = "发送验证码";
this.time = 60;
},
"formSms.mobile"() {
this.codeSent = false;
this.verifyStatus = false;
clearInterval(this.interval);
this.codeMsg = "发送验证码";
this.time = 60;
},
},
const formInlineRef = ref(null);
const formSmsRef = ref(null);
const verifyRef = ref(null);
const formRefs = {
formInline: formInlineRef,
formSms: formSmsRef,
};
const type = ref(true); // true 账号登录 false 验证码登录
const formData = reactive({
// 登录表单
username: "",
password: "",
});
const formSms = reactive({
// 手机号登录
code: "",
mobile: "",
});
const verifyStatus = ref(false); // 是否图片验证通过
const codeSent = ref(false); // 是否已成功发送短信验证码
const ruleInline = {
// 验证规则
username: [{required: true, message: "请输入用户名"}],
password: [
{required: true, message: "请输入密码"},
{type: "string", min: 6, message: "密码不能少于6位"},
],
mobile: [
{required: true, message: "请输入手机号码"},
{
pattern: RegExp.mobile,
message: "请输入正确的手机号",
},
],
code: [{required: true, message: "请输入手机验证码"}],
};
const codeMsg = ref("发送验证码"); // 验证码文字
const interval = ref(null); // 定时器
const time = ref(60); // 倒计时
const year = new Date().getFullYear();
function hideVerify() {
verifyRef.value?.close?.();
}
// 登录
function handleSubmit(name) {
formRefs[name].value.validate((valid) => {
if (valid) {
if (type.value) {
verifyRef.value?.init();
} else {
if (!codeSent.value) {
Message.warning("请先获取短信验证码");
return;
}
const data = JSON.parse(JSON.stringify(formSms));
apiLogin.smsLogin(data).then((res) => {
hideVerify();
if (res.success) {
Message.success("登录成功");
storage.setItem("accessToken", res.result.accessToken);
storage.setItem("refreshToken", res.result.refreshToken);
storage.setItem('getTimes',0)
apiLogin.getMemberMsg().then((res) => {
if (res.success) {
storage.setItem("userInfo", res.result);
let query = route.query;
if (query.rePath) {
router.push({
path: query.rePath,
query: JSON.parse(query.query),
});
} else {
router.push("/");
}
}
});
} else {
Message.error(res.message);
}
}).catch((err) => {
console.warn('短信登录失败', err);
});
}
}
});
}
// 发送手机验证码
function sendCode() {
if (time.value !== 60) return;
if (formSms.mobile === "") {
Message.warning("请先填写手机号");
return;
}
if (!RegExp.mobile.test(formSms.mobile)) {
Message.warning("请输入正确的手机号");
return;
}
verifyRef.value?.init();
}
function dispatchSms() {
sendSms({
mobile: formSms.mobile,
verificationEnums: "LOGIN",
}).then((res) => {
if (res.success) {
Message.success("验证码发送成功");
codeSent.value = true;
interval.value = setInterval(() => {
time.value--;
if (time.value === 0) {
time.value = 60;
codeMsg.value = "重新发送";
verifyStatus.value = false;
codeSent.value = false;
clearInterval(interval.value);
} else {
codeMsg.value = time.value;
}
}, 1000);
} else {
verifyStatus.value = false;
Message.warning(res.message);
}
}).catch(() => {
verifyStatus.value = false;
});
}
function verifyChange(con) {
// 拼图验证码回显
if (!con.status) return;
if (type.value === true) {
// 账号密码登录
let data = JSON.parse(JSON.stringify(formData));
data.password = md5(data.password);
hideVerify();
Spin.show();
apiLogin
.login(data)
.then((res) => {
if (res.success) {
loginSuccess(res.result.accessToken,res.result.refreshToken);
} else {
Spin.hide();
Message.error(res.message);
}
})
.catch(() => {
Spin.hide();
});
} else {
verifyStatus.value = true;
hideVerify();
dispatchSms();
}
}
function handleWebLogin(type) {
// 第三方登录
webLogin(type);
}
function loginSuccess(accessToken,refreshToken){
Message.success("登录成功");
storage.setItem("accessToken", accessToken);
storage.setItem("refreshToken", refreshToken);
apiLogin.getMemberMsg().then((res) => {
Spin.hide();
if (res.success) {
storage.setItem("userInfo", res.result);
let query = route.query;
if (query.rePath) {
router.push({
path: query.rePath,
query: JSON.parse(query.query),
});
} else {
router.push("/");
}
}
});
}
onBeforeUnmount(() => {
clearInterval(interval.value);
});
onMounted(() => {
let uuid = route.query.state;
if (uuid) {
storage.setItem("uuid", uuid);
loginCallback(uuid).then((res) => {
if (res.success) {
loginSuccess(res.result.accessToken,res.result.refreshToken);
}
});
}
});
watch(type, (v) => {
if (v) {
formInlineRef.value.resetFields();
} else {
formSmsRef.value.resetFields();
}
verifyStatus.value = false;
codeSent.value = false;
hideVerify();
clearInterval(interval.value);
codeMsg.value = "发送验证码";
time.value = 60;
});
watch(
() => formSms.mobile,
() => {
codeSent.value = false;
verifyStatus.value = false;
clearInterval(interval.value);
codeMsg.value = "发送验证码";
time.value = 60;
}
);
</script>
<style scoped lang="scss">
.login {

View File

@@ -65,7 +65,7 @@
<div v-if="showFloorView">
<!-- 楼层装修部分 -->
<model-form ref="modelForm" :data="modelForm"></model-form>
<model-form ref="modelFormRef" :data="modelForm"></model-form>
</div>
@@ -120,9 +120,11 @@
</div>
</template>
<script>
<script setup>
import { Message } from "@/utils/message";
import { reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message, Modal } from "@/utils/message";
import { ArrowDown, StarFilled } from '@element-plus/icons-vue';
import {getCateById, getDetailById} from "@/api/shopentry";
import {
@@ -130,254 +132,325 @@ import {
collectStore,
isStoreCollection,
} from "@/api/member";
import {goodsList} from "@/api/goods";
import {goodsList as goodsListApi} from "@/api/goods";
import Search from "@/components/Search";
import ModelForm from "@/components/indexDecorate/ModelForm";
import storage from "@/plugins/storage";
import {getFloorStoreData} from "@/api/index.js";
import { seckillByDay } from "@/api/promotion";
import imTalk from '@/components/mixes/talkIm'
import { getIMDetail } from "@/api/common";
import { getMemberMsg } from "@/api/login";
export default {
name: "Merchant",
components: { Search, ModelForm, StarFilled, ArrowDown },
mixins: [imTalk],
data() {
return {
// 店铺装修的内容
modelForm: {list: []}, // 楼层装修数据
topAdvert: {}, // 顶部广告
showNav: false, // 是否展示分类栏
topSearchShow: false, // 滚动后顶部搜索栏展示
carouselLarge: false, // 不同轮播分类尺寸
carouselOpacity: false, // 不同轮播分类样式,
enablePageData: false, //是否显示楼层装修内容
basePageData: false, //基础店铺信息
showFloorView: false, // 是否展示楼层装修(有有效装修数据时为 true)
storeMsg: {}, // 店铺信息
cateList: [], // 店铺分裂
goodsList: [], // 商品列表
total: 0, // 商品数量
params: {
// 请求参数
pageNumber: 1,
pageSize: 20,
keyword: "",
storeId: this.$route.query.id,
storeCatId: "",
},
cateName: "店铺推荐", // 分类名称
storeCollected: false, // 是否收藏
};
},
created() {
this.getStoreMsg();
window.onscroll = () => {
const top =
document.documentElement.scrollTop || document.body.scrollTop;
this.topSearchShow = top > 300;
};
},
methods: {
isFloorEnabled() {
const pageShow = this.storeMsg && this.storeMsg.pageShow;
return pageShow === "1" || pageShow === 1 || pageShow === "OPEN" || pageShow === true;
},
loadStoreGoodsView() {
this.showFloorView = false;
this.getGoodsList();
this.getCateList();
},
getIndexData() {
getFloorStoreData({
clientType: "PC",
num: this.$route.query.id,
pageType: "STORE",
})
.then(async (res) => {
if (res.success && res.result?.pageData) {
try {
await this.parsePageData(res.result.pageData);
this.showFloorView = true;
} catch {
this.loadStoreGoodsView();
}
} else {
this.loadStoreGoodsView();
}
})
.catch(() => {
this.loadStoreGoodsView();
});
},
async parsePageData(pageData) {
const dataJson = JSON.parse(pageData);
for (let i = 0; i < dataJson.list.length; i++) {
const type = dataJson.list[i].type;
if (type === "carousel2") {
this.carouselLarge = true;
} else if (type === "carousel1") {
this.carouselLarge = true;
this.carouselOpacity = true;
} else if (type === "seckill") {
dataJson.list[i].options.list = await this.getListByDay();
defineOptions({ name: "Merchant" });
const route = useRoute();
const router = useRouter();
// 店铺装修的内容
const modelForm = ref({list: []}); // 楼层装修数据
const modelFormRef = ref(null);
const topAdvert = ref({}); // 顶部广告
const showNav = ref(false); // 是否展示分类栏
const topSearchShow = ref(false); // 滚动后顶部搜索栏展示
const carouselLarge = ref(false); // 不同轮播分类尺寸
const carouselOpacity = ref(false); // 不同轮播分类样式,
const enablePageData = ref(false); //是否显示楼层装修内容
const basePageData = ref(false); //基础店铺信息
const showFloorView = ref(false); // 是否展示楼层装修(有有效装修数据时为 true)
const storeMsg = ref({}); // 店铺信息
const cateList = ref([]); // 店铺分裂
const goodsList = ref([]); // 商品列表
const total = ref(0); // 商品数量
const params = reactive({
// 请求参数
pageNumber: 1,
pageSize: 20,
keyword: "",
storeId: route.query.id,
storeCatId: "",
});
const cateName = ref("店铺推荐"); // 分类名称
const storeCollected = ref(false); // 是否收藏
// 原 talkIm mixin 的 IM 链接
const IMLink = ref("");
function isFloorEnabled() {
const pageShow = storeMsg.value && storeMsg.value.pageShow;
return pageShow === "1" || pageShow === 1 || pageShow === "OPEN" || pageShow === true;
}
function loadStoreGoodsView() {
showFloorView.value = false;
getGoodsList();
getCateList();
}
function getIndexData() {
getFloorStoreData({
clientType: "PC",
num: route.query.id,
pageType: "STORE",
})
.then(async (res) => {
if (res.success && res.result?.pageData) {
try {
await parsePageData(res.result.pageData);
showFloorView.value = true;
} catch {
loadStoreGoodsView();
}
} else {
loadStoreGoodsView();
}
this.modelForm = dataJson;
storage.setItem("navList", dataJson.list[1]);
this.showNav = true;
this.topAdvert = dataJson.list[0];
},
async getListByDay() {
const res = await seckillByDay();
if (res.success && res.result?.length) {
return res.result;
}
return [];
},
})
.catch(() => {
loadStoreGoodsView();
});
}
async function parsePageData(pageData) {
const dataJson = JSON.parse(pageData);
for (let i = 0; i < dataJson.list.length; i++) {
const type = dataJson.list[i].type;
if (type === "carousel2") {
carouselLarge.value = true;
} else if (type === "carousel1") {
carouselLarge.value = true;
carouselOpacity.value = true;
} else if (type === "seckill") {
dataJson.list[i].options.list = await getListByDay();
}
}
modelForm.value = dataJson;
storage.setItem("navList", dataJson.list[1]);
showNav.value = true;
topAdvert.value = dataJson.list[0];
}
checkStoreCollectionStatus() {
const storeId = this.storeMsg && this.storeMsg.storeId;
if (!storeId || !this.Cookies.getItem("userInfo")) {
return;
async function getListByDay() {
const res = await seckillByDay();
if (res.success && res.result?.length) {
return res.result;
}
return [];
}
function checkStoreCollectionStatus() {
const storeId = storeMsg.value && storeMsg.value.storeId;
if (!storeId || !storage.getItem("userInfo")) {
return;
}
isStoreCollection("STORE", storeId)
.then((res) => {
if (res.success && res.result) {
storeCollected.value = true;
}
isStoreCollection("STORE", storeId)
.then((res) => {
if (res.success && res.result) {
this.storeCollected = true;
}
})
.catch(() => {
// 收藏状态查询失败不影响店铺页展示,静默处理
});
},
getStoreMsg() {
const storeId = this.$route.query.id;
if (!storeId) {
Message.warning("店铺不存在");
return;
}
getDetailById(storeId)
.then((res) => {
if (res.success && res.result) {
this.storeMsg = res.result;
document.title = this.storeMsg.storeName || "店铺";
this.checkStoreCollectionStatus();
if (this.isFloorEnabled()) {
this.getIndexData();
} else {
this.loadStoreGoodsView();
}
} else {
this.storeMsg = { storeId };
this.loadStoreGoodsView();
}
})
.catch(() => {
this.storeMsg = { storeId };
this.loadStoreGoodsView();
});
},
getCateList() {
// 店铺分类
getCateById(this.$route.query.id).then((res) => {
if (res.success) {
this.cateList = res.result;
}
});
},
getGoodsList() {
// 商品信息
goodsList(this.params)
.then((res) => {
if (res.success) {
this.goodsList = res.result.records;
this.total = res.result.total;
}
})
.catch((err) => {
console.warn('获取商品列表失败', err);
});
},
goGoodsDetail(skuId, goodsId) {
// 跳转商品详情
let routeUrl = this.$router.resolve({
path: "/goodsDetail",
query: {skuId, goodsId},
});
window.open(routeUrl.href, "_blank");
},
search(val) {
// 搜索本店商品
this.params.keyword = val;
this.getGoodsList();
},
searchByCate(cate) {
// 搜索同分类下商品
this.params.storeCatId = cate.id;
this.params.pageNumber = 1;
this.cateName = cate.labelName;
this.getGoodsList();
},
isCateActive(cate) {
if (!cate || !this.params.storeCatId) return false;
if (this.params.storeCatId === cate.id) return true;
return Array.isArray(cate.children) && cate.children.some((sec) => sec.id === this.params.storeCatId);
},
formatGrade(grade) {
if (grade === null || grade === undefined || grade === "") return 100;
const num = Number(grade);
if (Number.isNaN(num)) return 100;
const fixed = Number(num.toFixed(1));
return fixed % 1 === 0 ? fixed.toFixed(0) : String(fixed);
},
// 分页 修改页码
changePageNum(val) {
this.params.pageNumber = val;
this.getGoodsList();
},
// 分页 修改页数
changePageSize(val) {
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getGoodsList();
},
async collect() {
// 收藏店铺
const storeId = this.storeMsg && this.storeMsg.storeId;
if (!storeId) {
return;
}
if (!this.Cookies.getItem("userInfo")) {
Message.warning("请先登录");
this.$router.push("/login");
return;
}
try {
if (this.storeCollected) {
const cancel = await cancelStoreCollect("STORE", storeId);
if (cancel.success) {
Message.success("已取消收藏");
this.storeCollected = false;
}
})
.catch(() => {
// 收藏状态查询失败不影响店铺页展示,静默处理
});
}
function getStoreMsg() {
const storeId = route.query.id;
if (!storeId) {
Message.warning("店铺不存在");
return;
}
getDetailById(storeId)
.then((res) => {
if (res.success && res.result) {
storeMsg.value = res.result;
document.title = storeMsg.value.storeName || "店铺";
checkStoreCollectionStatus();
if (isFloorEnabled()) {
getIndexData();
} else {
const collect = await collectStore("STORE", storeId);
if (collect.success || collect.code === 200) {
this.storeCollected = true;
Message.success(
"收藏店铺成功,可以前往个人中心我的收藏查看"
);
}
}
} catch (err) {
const msg = err?.message || err?.data?.message || "";
if (msg.includes("重复收藏")) {
this.storeCollected = true;
loadStoreGoodsView();
}
} else {
storeMsg.value = { storeId };
loadStoreGoodsView();
}
})
.catch(() => {
storeMsg.value = { storeId };
loadStoreGoodsView();
});
}
function getCateList() {
// 店铺分类
getCateById(route.query.id).then((res) => {
if (res.success) {
cateList.value = res.result;
}
});
}
function getGoodsList() {
// 商品信息
goodsListApi(params)
.then((res) => {
if (res.success) {
goodsList.value = res.result.records;
total.value = res.result.total;
}
})
.catch((err) => {
console.warn('获取商品列表失败', err);
});
}
function goGoodsDetail(skuId, goodsId) {
// 跳转商品详情
let routeUrl = router.resolve({
path: "/goodsDetail",
query: {skuId, goodsId},
});
window.open(routeUrl.href, "_blank");
}
function search(val) {
// 搜索本店商品
params.keyword = val;
getGoodsList();
}
function searchByCate(cate) {
// 搜索同分类下商品
params.storeCatId = cate.id;
params.pageNumber = 1;
cateName.value = cate.labelName;
getGoodsList();
}
function isCateActive(cate) {
if (!cate || !params.storeCatId) return false;
if (params.storeCatId === cate.id) return true;
return Array.isArray(cate.children) && cate.children.some((sec) => sec.id === params.storeCatId);
}
function formatGrade(grade) {
if (grade === null || grade === undefined || grade === "") return 100;
const num = Number(grade);
if (Number.isNaN(num)) return 100;
const fixed = Number(num.toFixed(1));
return fixed % 1 === 0 ? fixed.toFixed(0) : String(fixed);
}
// 分页 修改页码
function changePageNum(val) {
params.pageNumber = val;
getGoodsList();
}
// 分页 修改页数
function changePageSize(val) {
params.pageNumber = 1;
params.pageSize = val;
getGoodsList();
}
async function collect() {
// 收藏店铺
const storeId = storeMsg.value && storeMsg.value.storeId;
if (!storeId) {
return;
}
if (!storage.getItem("userInfo")) {
Message.warning("请先登录");
router.push("/login");
return;
}
try {
if (storeCollected.value) {
const cancel = await cancelStoreCollect("STORE", storeId);
if (cancel.success) {
Message.success("已取消收藏");
storeCollected.value = false;
}
} else {
const collect = await collectStore("STORE", storeId);
if (collect.success || collect.code === 200) {
storeCollected.value = true;
Message.success(
"收藏店铺成功,可以前往个人中心我的收藏查看"
);
}
}
} catch (err) {
const msg = err?.message || err?.data?.message || "";
if (msg.includes("重复收藏")) {
storeCollected.value = true;
}
}
}
// 原 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)
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 || storeMsg.value?.storeId;
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();
}
}
}
getStoreMsg();
window.onscroll = () => {
const top =
document.documentElement.scrollTop || document.body.scrollTop;
topSearchShow.value = top > 300;
};
</script>

View File

@@ -11,10 +11,8 @@
</div>
</template>
<script>
export default {
name: "NotFound",
};
<script setup>
defineOptions({ name: "NotFound" });
</script>
<style scoped lang="scss">

View File

@@ -45,23 +45,28 @@ export default {
window.scrollTo(0, 0);
next();
},
data () {
return {
detail: {} // 商品详情
};
},
mounted () {
const { skuId, goodsId, thumbnail, goodsName } = this.$route.query;
this.detail = {
skuId,
goodsId,
thumbnail,
goodsName
};
}
};
</script>
<script setup>
import { onMounted, ref } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
const detail = ref({}); // 商品详情
onMounted(() => {
const { skuId, goodsId, thumbnail, goodsName } = route.query;
detail.value = {
skuId,
goodsId,
thumbnail,
goodsName
};
});
</script>
<style scoped lang="scss">
/****************************加入购物车页面开始*****************************/
.add-info-box-container{

View File

@@ -3,7 +3,7 @@
<div class="top-content">
<div class="top-inner">
<div class="logo-box">
<img :src="$store.state.logoImg" @click="$router.push('/')" />
<img :src="logoImg" @click="$router.push('/')" />
<div>欢迎注册</div>
</div>
<div class="to-login">已有账号?<a @click.stop="$router.push('login')">立即登录</a></div>
@@ -17,7 +17,7 @@
/>
<div class="signup-card">
<el-form
ref="formRegist"
ref="formRegistRef"
:model="formRegist"
:rules="ruleInline"
class="signup-form"
@@ -91,8 +91,9 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref, computed } from "vue";
import { useRouter } from "vue-router";
import { Message } from "@/utils/message";
import * as RegExp from '@/plugins/RegExp.js';
import { md5 } from '@/plugins/md5.js';
@@ -101,120 +102,126 @@ import { sendSms } from '@/api/common.js';
import Verify from '@/components/verify';
import { User, Lock, Iphone, Document } from '@element-plus/icons-vue';
import config from '@/config';
export default {
name: 'SignUp',
components: { Verify, User, Lock, Iphone, Document },
data () {
return {
config,
year: new Date().getFullYear(),
formRegist: {
// 注册表单
mobilePhone: '',
code: '',
username: '',
password: ''
},
ruleInline: {
// 验证规则
username: [{ required: true, message: '请输入用户名' }],
password: [
{ required: true, message: '请输入密码' },
{ type: 'string', min: 6, message: '密码不能少于6位' }
],
mobilePhone: [
{ required: true, message: '请输入手机号码' },
{
pattern: RegExp.mobile,
trigger: 'blur',
message: '请输入正确的手机号'
}
],
code: [{ required: true, message: '请输入手机验证码' }]
},
verifyStatus: false, // 是否验证通过
verifyType: 'REGISTER', // 验证状态
codeMsg: '发送验证码', // 提示文字
interval: '', // 定时器
time: 60 // 倒计时
};
},
methods: {
hideVerify() {
this.$refs.verify?.close?.();
},
// 注册
handleRegist () {
this.$refs.formRegist.validate((valid) => {
if (!valid) {
return;
}
const data = JSON.parse(JSON.stringify(this.formRegist));
data.password = md5(data.password);
apiLogin.regist(data).then((res) => {
if (res.success) {
Message.success('注册成功!');
this.$router.push('login');
} else {
Message.warning(res.message || '注册失败');
}
}).catch((err) => {
console.warn('注册请求失败', err);
});
}).catch(() => {
// 表单校验使用回调模式,此 catch 仅为兜底
});
},
// 发送短信验证码
sendCode () {
if (this.time !== 60) return;
if (this.formRegist.mobilePhone === '') {
Message.warning('请先填写手机号');
return;
}
if (!RegExp.mobile.test(this.formRegist.mobilePhone)) {
Message.warning('请输入正确的手机号');
return;
}
this.$refs.verify?.init();
},
dispatchSms () {
sendSms({
mobile: this.formRegist.mobilePhone,
verificationEnums: 'REGISTER'
}).then(res => {
if (res.success) {
Message.success('验证码发送成功');
this.interval = setInterval(() => {
this.time--;
if (this.time === 0) {
this.time = 60;
this.codeMsg = '重新发送';
this.verifyStatus = false;
clearInterval(this.interval);
} else {
this.codeMsg = this.time;
}
}, 1000);
} else {
this.verifyStatus = false;
Message.warning(res.message || '验证码发送失败');
}
}).catch(() => {
this.verifyStatus = false;
});
},
verifyChange (con) {
if (!con.status) return;
this.hideVerify();
this.verifyStatus = true;
this.dispatchSms();
import { useAppStore } from '@/stores/app';
defineOptions({ name: 'SignUp' });
const router = useRouter();
const appStore = useAppStore();
const logoImg = computed(() => appStore.logoImg);
const year = new Date().getFullYear();
const formRegist = reactive({
// 注册表单
mobilePhone: '',
code: '',
username: '',
password: ''
});
const ruleInline = reactive({
// 验证规则
username: [{ required: true, message: '请输入用户名' }],
password: [
{ required: true, message: '请输入密码' },
{ type: 'string', min: 6, message: '密码不能少于6位' }
],
mobilePhone: [
{ required: true, message: '请输入手机号码' },
{
pattern: RegExp.mobile,
trigger: 'blur',
message: '请输入正确的手机号'
}
},
mounted () {
this.$refs.formRegist.resetFields();
],
code: [{ required: true, message: '请输入手机验证码' }]
});
const verifyStatus = ref(false); // 是否验证通过
const verifyType = ref('REGISTER'); // 验证状态
const codeMsg = ref('发送验证码'); // 提示文字
const interval = ref(''); // 定时器
const time = ref(60); // 倒计时
const formRegistRef = ref(null);
const verify = ref(null);
function hideVerify() {
verify.value?.close?.();
}
// 注册
function handleRegist () {
formRegistRef.value.validate((valid) => {
if (!valid) {
return;
}
const data = JSON.parse(JSON.stringify(formRegist));
data.password = md5(data.password);
apiLogin.regist(data).then((res) => {
if (res.success) {
Message.success('注册成功!');
router.push('login');
} else {
Message.warning(res.message || '注册失败');
}
}).catch((err) => {
console.warn('注册请求失败', err);
});
}).catch(() => {
// 表单校验使用回调模式,此 catch 仅为兜底
});
}
// 发送短信验证码
function sendCode () {
if (time.value !== 60) return;
if (formRegist.mobilePhone === '') {
Message.warning('请先填写手机号');
return;
}
};
if (!RegExp.mobile.test(formRegist.mobilePhone)) {
Message.warning('请输入正确的手机号');
return;
}
verify.value?.init();
}
function dispatchSms () {
sendSms({
mobile: formRegist.mobilePhone,
verificationEnums: 'REGISTER'
}).then(res => {
if (res.success) {
Message.success('验证码发送成功');
interval.value = setInterval(() => {
time.value--;
if (time.value === 0) {
time.value = 60;
codeMsg.value = '重新发送';
verifyStatus.value = false;
clearInterval(interval.value);
} else {
codeMsg.value = time.value;
}
}, 1000);
} else {
verifyStatus.value = false;
Message.warning(res.message || '验证码发送失败');
}
}).catch(() => {
verifyStatus.value = false;
});
}
function verifyChange (con) {
if (!con.status) return;
hideVerify();
verifyStatus.value = true;
dispatchSms();
}
onMounted(() => {
formRegistRef.value.resetFields();
});
</script>
<style scoped lang="scss">
.sign-up {

View File

@@ -4,33 +4,31 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import Index from './Index.vue'
import { getTopicData } from '@/api/index'
export default {
data() {
return {
pageData: ""
}
},
components: { Index },
mounted() {
// 接收id进行查询
const id = this.$route.query.id
this.init(id)
},
methods: {
async init(id) {
const res = await getTopicData(id)
if (res.success) {
// 直接复用首页就行
if (res.result.pageData) {
this.pageData = JSON.parse(res.result.pageData)
}
}
const route = useRoute()
const pageData = ref("")
async function init(id) {
const res = await getTopicData(id)
if (res.success) {
// 直接复用首页就行
if (res.result.pageData) {
pageData.value = JSON.parse(res.result.pageData)
}
}
}
onMounted(() => {
// 接收id进行查询
const id = route.query.id
init(id)
})
</script>
<style lang="scss" scoped></style>

View File

@@ -10,35 +10,36 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { articleDetail } from "@/api/common.js";
import config from "@/config/index";
export default {
data() {
return {
config,
detail: "",
};
},
mounted() {
this.init();
},
methods: {
back() {
this.$router.push("/");
},
// 初始化文章
init() {
const id = this.$route.query.id;
articleDetail(id).then((res) => {
if (res.success) {
this.detail = res.result;
}
});
},
},
};
const route = useRoute();
const router = useRouter();
const configData = ref(config);
const detail = ref("");
function back() {
router.push("/");
}
// 初始化文章
function init() {
const id = route.query.id;
articleDetail(id).then((res) => {
if (res.success) {
detail.value = res.result;
}
});
}
onMounted(() => {
init();
});
</script>
<style lang="scss" scoped>

View File

@@ -48,87 +48,89 @@
</div>
</template>
<script>
import { articleCateList, articleDetail, articleList } from '@/api/common.js'
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { useRoute } from 'vue-router'
import { articleCateList, articleDetail, articleList as fetchArticleList } from '@/api/common.js'
export default {
name: 'ArticleIndex',
data () {
return {
list: [],
detail: '',
articleList: [],
activeName: '1347456734864367616',
openName: [],
menuKey: 0,
params: {
pageNumber: 1,
pageSize: 100,
categoryId: '',
sort: 'sort'
},
showList: true
};
},
methods: {
onSelect (id) {
this.getList(id)
this.detail = ''
this.showList = true
},
getCateList () {
articleCateList().then(res => {
if (res.success) {
this.list = res.result || []
this.openName = []
if (this.$route.query.id && this.detail && this.detail.categoryId) {
this.activeName = this.detail.categoryId
this.list.forEach(e => {
const children = e.children || []
const hitParent = String(e.id) === String(this.activeName)
const hitChild = children.some(i => i && String(i.id) === String(this.activeName))
if ((hitParent || hitChild) && children.length) {
this.openName.push(String(e.id))
}
})
} else if (this.list[0] && this.list[0].id) {
this.activeName = this.list[0].id
if (this.list[0].children && this.list[0].children.length) {
this.openName.push(String(this.list[0].id))
}
}
this.menuKey++
if (this.activeName) {
this.getList(this.activeName)
defineOptions({ name: 'ArticleIndex' })
const route = useRoute()
const list = ref([])
const detail = ref('')
const articleList = ref([])
const activeName = ref('1347456734864367616')
const openName = ref([])
const menuKey = ref(0)
const params = reactive({
pageNumber: 1,
pageSize: 100,
categoryId: '',
sort: 'sort'
})
const showList = ref(true)
function onSelect (id) {
getList(id)
detail.value = ''
showList.value = true
}
function getCateList () {
articleCateList().then(res => {
if (res.success) {
list.value = res.result || []
openName.value = []
if (route.query.id && detail.value && detail.value.categoryId) {
activeName.value = detail.value.categoryId
list.value.forEach(e => {
const children = e.children || []
const hitParent = String(e.id) === String(activeName.value)
const hitChild = children.some(i => i && String(i.id) === String(activeName.value))
if ((hitParent || hitChild) && children.length) {
openName.value.push(String(e.id))
}
})
} else if (list.value[0] && list.value[0].id) {
activeName.value = list.value[0].id
if (list.value[0].children && list.value[0].children.length) {
openName.value.push(String(list.value[0].id))
}
})
},
getList (id) {
this.params.categoryId = id
articleList(this.params).then(res => {
if (res.success) {
this.articleList = res.result.records
}
})
},
async getDetail (id) {
await articleDetail(id).then(res => {
if (res.success) {
this.detail = res.result
this.showList = false
}
})
}
menuKey.value++
if (activeName.value) {
getList(activeName.value)
}
}
},
async mounted () {
const articleId = this.$route.query.id
if (articleId) {
await this.getDetail(articleId)
})
}
function getList (id) {
params.categoryId = id
fetchArticleList(params).then(res => {
if (res.success) {
articleList.value = res.result.records
}
this.getCateList()
})
}
async function getDetail (id) {
await articleDetail(id).then(res => {
if (res.success) {
detail.value = res.result
showList.value = false
}
})
}
onMounted(async () => {
const articleId = route.query.id
if (articleId) {
await getDetail(articleId)
}
};
getCateList()
})
</script>
<style scoped lang="scss">

View File

@@ -44,88 +44,84 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { Bell } from "@element-plus/icons-vue";
import storage from "@/plugins/storage";
import { getOrderList } from "@/api/order";
import { afterSaleList, getMembersWallet, memberCouponList } from "@/api/member";
import { refreshUnreadMessage } from "@/utils/unreadMessage";
import { useAppStore } from "@/stores/app";
defineOptions({ name: "UserCenterHome" });
const router = useRouter();
const appStore = useAppStore();
function totalOf(res) {
return res && res.success ? Number(res.result && res.result.total) || 0 : 0;
}
export default {
name: "UserCenterHome",
components: { Bell },
data() {
return {
displayName: "会员",
waitPay: 0,
waitShip: 0,
waitReceive: 0,
afterSale: 0,
couponCount: 0,
balance: 0,
};
},
computed: {
unreadMsg() {
return Number(this.$store.state.unreadMsg) || 0;
},
unreadLabel() {
return this.unreadMsg > 99 ? "99+" : String(this.unreadMsg);
},
orderStats() {
return [
{ key: "pay", label: "待付款", count: this.waitPay, action: () => this.goOrder("WAIT_PAY") },
{ key: "ship", label: "待发货", count: this.waitShip, action: () => this.goOrder("WAIT_SHIP") },
{ key: "receive", label: "待收货", count: this.waitReceive, action: () => this.goOrder("WAIT_USE") },
{ key: "after", label: "售后", count: this.afterSale, action: () => this.go("AfterSale") },
];
},
},
mounted() {
this.readUser();
this.loadSummary();
},
methods: {
readUser() {
try {
const raw = storage.getItem("userInfo");
const user = raw ? JSON.parse(raw) : {};
this.displayName = user.nickName || user.username || "会员";
} catch {
this.displayName = "会员";
}
},
loadSummary() {
const page = { pageNumber: 1, pageSize: 1 };
Promise.all([
getOrderList({ ...page, tag: "WAIT_PAY" }).catch(() => null),
getOrderList({ ...page, tag: "WAIT_SHIP" }).catch(() => null),
getOrderList({ ...page, tag: "WAIT_USE" }).catch(() => null),
afterSaleList({ ...page, sort: "createTime", order: "desc" }).catch(() => null),
memberCouponList({ ...page, memberCouponStatus: "NEW" }).catch(() => null),
getMembersWallet().catch(() => null),
refreshUnreadMessage(),
]).then(([pay, ship, receive, afterSale, coupon, wallet]) => {
this.waitPay = totalOf(pay);
this.waitShip = totalOf(ship);
this.waitReceive = totalOf(receive);
this.afterSale = totalOf(afterSale);
this.couponCount = totalOf(coupon);
this.balance = wallet && wallet.success ? wallet.result.memberWallet || 0 : 0;
});
},
go(name) {
this.$router.push({ name });
},
goOrder(tag) {
this.$router.push({ name: "MyOrder", query: { tag } });
},
},
};
const displayName = ref("会员");
const waitPay = ref(0);
const waitShip = ref(0);
const waitReceive = ref(0);
const afterSale = ref(0);
const couponCount = ref(0);
const balance = ref(0);
const unreadMsg = computed(() => appStore.unreadMsg);
const unreadLabel = computed(() => (unreadMsg.value > 99 ? "99+" : String(unreadMsg.value)));
const orderStats = computed(() => [
{ key: "pay", label: "待付款", count: waitPay.value, action: () => goOrder("WAIT_PAY") },
{ key: "ship", label: "待发货", count: waitShip.value, action: () => goOrder("WAIT_SHIP") },
{ key: "receive", label: "待收货", count: waitReceive.value, action: () => goOrder("WAIT_USE") },
{ key: "after", label: "售后", count: afterSale.value, action: () => go("AfterSale") },
]);
function readUser() {
try {
const raw = storage.getItem("userInfo");
const user = raw ? JSON.parse(raw) : {};
displayName.value = user.nickName || user.username || "会员";
} catch {
displayName.value = "会员";
}
}
function loadSummary() {
const page = { pageNumber: 1, pageSize: 1 };
Promise.all([
getOrderList({ ...page, tag: "WAIT_PAY" }).catch(() => null),
getOrderList({ ...page, tag: "WAIT_SHIP" }).catch(() => null),
getOrderList({ ...page, tag: "WAIT_USE" }).catch(() => null),
afterSaleList({ ...page, sort: "createTime", order: "desc" }).catch(() => null),
memberCouponList({ ...page, memberCouponStatus: "NEW" }).catch(() => null),
getMembersWallet().catch(() => null),
refreshUnreadMessage(),
]).then(([pay, ship, receive, afterSaleRes, coupon, wallet]) => {
waitPay.value = totalOf(pay);
waitShip.value = totalOf(ship);
waitReceive.value = totalOf(receive);
afterSale.value = totalOf(afterSaleRes);
couponCount.value = totalOf(coupon);
balance.value = wallet && wallet.success ? wallet.result.memberWallet || 0 : 0;
});
}
function go(name) {
router.push({ name });
}
function goOrder(tag) {
router.push({ name: "MyOrder", query: { tag } });
}
onMounted(() => {
readUser();
loadSummary();
});
</script>
<style scoped lang="scss">

View File

@@ -77,191 +77,187 @@
</div>
</template>
<script>
<script setup>
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { Message, Modal } from "@/utils/message";
import { Key, Link } from '@element-plus/icons-vue';
// import { getPwdStatus } from '@/api/account';
import storage from '@/plugins/storage'
import { bindThirdAccount, getThirdAccountBindList, getWechatH5QrCode, unbindThirdAccount } from '@/api/login.js'
export default {
components: { Key, Link },
name: 'AccountSafe',
data () {
return {
pwdStatus: '',
connectLoading: false,
bindModalVisible: false,
bindSubmitLoading: false,
connectBoundTypes: [],
thirdAccountTypes: [
{ type: 'WECHAT', label: '微信' }
],
qrCodeLoading: false,
qrCodeBase64: '',
bindForm: {
type: '',
typeLabel: '',
unionID: ''
}
}
},
computed: {
wechatConnect () {
return this.thirdAccountTypes[0]
},
qrCodeSrc () {
if (!this.qrCodeBase64) return ''
if (this.qrCodeBase64.startsWith('data:image')) return this.qrCodeBase64
return `data:image/png;base64,${this.qrCodeBase64}`
},
bindModalOkText () {
if (this.bindForm.type === 'WECHAT') return '我已完成绑定'
return '确定'
}
},
mounted () {
this.getPwdStatus()
this.refreshConnectBinds()
},
methods: {
// 设置支付密码
goModifyPwd () {
this.$router.push({name: 'ModifyPwd', query: { status: 2 }})
},
modifyPwd () { // 修改密码
this.$router.push({name: 'ModifyPwd', query: { status: 1 }})
},
// 获取密码状态
getPwdStatus () {},
// getPwdStatus () {
// getPwdStatus().then(res => {
// if (res) {
// this.pwdStatus = '修改密码'
// } else {
// this.pwdStatus = '设置密码'
// }
// });
// }
isBound (type) {
return this.connectBoundTypes.includes(type)
},
refreshConnectBinds () {
this.connectLoading = true
return getThirdAccountBindList()
.then(res => {
if (res && res.success) {
this.connectBoundTypes = Array.isArray(res.result) ? res.result : []
}
})
.finally(() => {
this.connectLoading = false
})
},
openBindModal (item) {
this.bindForm.type = item.type
this.bindForm.typeLabel = item.label
this.bindForm.unionID = ''
this.qrCodeBase64 = ''
this.bindModalVisible = true
if (item.type === 'WECHAT') {
this.fetchWechatQrCode()
}
},
resetBindForm () {
this.bindForm.type = ''
this.bindForm.typeLabel = ''
this.bindForm.unionID = ''
this.qrCodeLoading = false
this.qrCodeBase64 = ''
this.bindSubmitLoading = false
},
fetchWechatQrCode () {
if (this.bindForm.type !== 'WECHAT') return
const rawUserInfo = storage.getItem('userInfo')
const userInfo = rawUserInfo ? JSON.parse(rawUserInfo) : null
const userId = userInfo && userInfo.id ? String(userInfo.id) : ''
if (!userId) {
this.qrCodeBase64 = ''
Message.error('未获取到当前用户ID,请重新登录后重试')
return
}
this.qrCodeLoading = true
getWechatH5QrCode({ scene: userId })
.then(res => {
if (res && res.success) {
this.qrCodeBase64 = res.result || ''
return
}
this.qrCodeBase64 = ''
Message.error((res && res.message) || '二维码获取失败')
})
.catch(() => {
this.qrCodeBase64 = ''
})
.finally(() => {
this.qrCodeLoading = false
})
},
submitBind () {
if (this.bindSubmitLoading) return
if (this.bindForm.type === 'WECHAT' && !this.bindForm.unionID) {
this.bindSubmitLoading = true
this.refreshConnectBinds()
.then(() => {
if (this.isBound(this.bindForm.type)) {
Message.success('绑定成功')
this.bindModalVisible = false
this.resetBindForm()
return
}
Message.warning('暂未检测到绑定,请扫码后重试')
this.$nextTick(() => {
this.bindModalVisible = true
})
})
.finally(() => {
this.bindSubmitLoading = false
})
return
}
if (!this.bindForm.type) {
Message.error('绑定类型不能为空')
return
}
if (!this.bindForm.unionID) {
Message.error('OpenID/UnionID 不能为空')
this.$nextTick(() => {
this.bindModalVisible = true
})
return
}
this.bindSubmitLoading = true
bindThirdAccount({ unionID: this.bindForm.unionID, type: this.bindForm.type })
.then(() => {
Message.success('绑定成功')
this.bindModalVisible = false
this.resetBindForm()
this.refreshConnectBinds()
})
.finally(() => {
this.bindSubmitLoading = false
})
},
confirmUnbind (item) {
Modal.confirm({
title: '解绑确认',
content: `<p>确定解绑 ${item.label} 吗?</p>`,
onOk: () => {
unbindThirdAccount({ type: item.type }).then(() => {
Message.success('解绑成功')
this.refreshConnectBinds()
})
}
})
}
defineOptions({ name: 'AccountSafe' });
const router = useRouter();
const pwdStatus = ref('');
const connectLoading = ref(false);
const bindModalVisible = ref(false);
const bindSubmitLoading = ref(false);
const connectBoundTypes = ref([]);
const thirdAccountTypes = ref([
{ type: 'WECHAT', label: '微信' }
]);
const qrCodeLoading = ref(false);
const qrCodeBase64 = ref('');
const bindForm = reactive({
type: '',
typeLabel: '',
unionID: ''
});
const wechatConnect = computed(() => thirdAccountTypes.value[0]);
const qrCodeSrc = computed(() => {
if (!qrCodeBase64.value) return ''
if (qrCodeBase64.value.startsWith('data:image')) return qrCodeBase64.value
return `data:image/png;base64,${qrCodeBase64.value}`
});
const bindModalOkText = computed(() => {
if (bindForm.type === 'WECHAT') return '我已完成绑定'
return '确定'
});
// 设置支付密码
function goModifyPwd () {
router.push({name: 'ModifyPwd', query: { status: 2 }})
}
function modifyPwd () { // 修改密码
router.push({name: 'ModifyPwd', query: { status: 1 }})
}
// 获取密码状态
function getPwdStatus () {}
// function getPwdStatus () {
// getPwdStatus().then(res => {
// if (res) {
// pwdStatus.value = '修改密码'
// } else {
// pwdStatus.value = '设置密码'
// }
// });
// }
function isBound (type) {
return connectBoundTypes.value.includes(type)
}
function refreshConnectBinds () {
connectLoading.value = true
return getThirdAccountBindList()
.then(res => {
if (res && res.success) {
connectBoundTypes.value = Array.isArray(res.result) ? res.result : []
}
})
.finally(() => {
connectLoading.value = false
})
}
function openBindModal (item) {
bindForm.type = item.type
bindForm.typeLabel = item.label
bindForm.unionID = ''
qrCodeBase64.value = ''
bindModalVisible.value = true
if (item.type === 'WECHAT') {
fetchWechatQrCode()
}
}
function resetBindForm () {
bindForm.type = ''
bindForm.typeLabel = ''
bindForm.unionID = ''
qrCodeLoading.value = false
qrCodeBase64.value = ''
bindSubmitLoading.value = false
}
function fetchWechatQrCode () {
if (bindForm.type !== 'WECHAT') return
const rawUserInfo = storage.getItem('userInfo')
const userInfo = rawUserInfo ? JSON.parse(rawUserInfo) : null
const userId = userInfo && userInfo.id ? String(userInfo.id) : ''
if (!userId) {
qrCodeBase64.value = ''
Message.error('未获取到当前用户ID,请重新登录后重试')
return
}
qrCodeLoading.value = true
getWechatH5QrCode({ scene: userId })
.then(res => {
if (res && res.success) {
qrCodeBase64.value = res.result || ''
return
}
qrCodeBase64.value = ''
Message.error((res && res.message) || '二维码获取失败')
})
.catch(() => {
qrCodeBase64.value = ''
})
.finally(() => {
qrCodeLoading.value = false
})
}
function submitBind () {
if (bindSubmitLoading.value) return
if (bindForm.type === 'WECHAT' && !bindForm.unionID) {
bindSubmitLoading.value = true
refreshConnectBinds()
.then(() => {
if (isBound(bindForm.type)) {
Message.success('绑定成功')
bindModalVisible.value = false
resetBindForm()
return
}
Message.warning('暂未检测到绑定,请扫码后重试')
nextTick(() => {
bindModalVisible.value = true
})
})
.finally(() => {
bindSubmitLoading.value = false
})
return
}
if (!bindForm.type) {
Message.error('绑定类型不能为空')
return
}
if (!bindForm.unionID) {
Message.error('OpenID/UnionID 不能为空')
nextTick(() => {
bindModalVisible.value = true
})
return
}
bindSubmitLoading.value = true
bindThirdAccount({ unionID: bindForm.unionID, type: bindForm.type })
.then(() => {
Message.success('绑定成功')
bindModalVisible.value = false
resetBindForm()
refreshConnectBinds()
})
.finally(() => {
bindSubmitLoading.value = false
})
}
function confirmUnbind (item) {
Modal.confirm({
title: '解绑确认',
content: `<p>确定解绑 ${item.label} 吗?</p>`,
onOk: () => {
unbindThirdAccount({ type: item.type }).then(() => {
Message.success('解绑成功')
refreshConnectBinds()
})
}
})
}
onMounted(() => {
getPwdStatus()
refreshConnectBinds()
});
</script>
<style scoped lang="scss">

View File

@@ -50,52 +50,55 @@
</div>
</template>
<script>
import {evolutionList} from '@/api/order.js';
export default {
name: 'CommentList',
data () {
return {
commentWay: [`待评价`, `待追评`, `已评价`], // 评价分类
loading: false, // 加载状态
list: [], // 评价列表
total: 0, // 评价总数
params: { // 请求参数
pageNumber: 1,
pageSize: 20
}
};
},
mounted () {
this.getList()
},
methods: {
getList () { // 获取评价列表
evolutionList(this.params).then(res => {
if (res.success) {
const list = res.result.records;
list.forEach(element => {
element.descriptionScore = Number(element.descriptionScore)
});
this.list = list;
this.total = res.result.total
}
})
},
changePageNum (val) { // 修改页码
this.params.pageNumber = val;
this.getList()
},
changePageSize (val) { // 修改页数
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getList()
},
evaluateDetail (id) { // 跳转评价详情
this.$router.push({path: '/home/evalDetail', query: { id }})
<script setup>
import { reactive, ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { evolutionList } from '@/api/order.js';
defineOptions({ name: 'CommentList' });
const router = useRouter();
const commentWay = ref([`待评价`, `待追评`, `已评价`]); // 评价分类
const loading = ref(false); // 加载状态
const list = ref([]); // 评价列表
const total = ref(0); // 评价总数
const params = reactive({ // 请求参数
pageNumber: 1,
pageSize: 20
});
function getList () { // 获取评价列表
evolutionList(params).then(res => {
if (res.success) {
const records = res.result.records;
records.forEach(element => {
element.descriptionScore = Number(element.descriptionScore)
});
list.value = records;
total.value = res.result.total
}
}
};
})
}
function changePageNum (val) { // 修改页码
params.pageNumber = val;
getList()
}
function changePageSize (val) { // 修改页数
params.pageNumber = 1;
params.pageSize = val;
getList()
}
function evaluateDetail (id) { // 跳转评价详情
router.push({path: '/home/evalDetail', query: { id }})
}
onMounted(() => {
getList()
});
</script>
<style scoped lang="scss">

View File

@@ -129,75 +129,74 @@
</UserCenterLayout>
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "@/utils/message";
import { View } from '@element-plus/icons-vue';
import {getComplainDetail} from '@/api/member.js';
import {communication} from '@/api/order';
export default {
components: { View },
data () {
return {
detail: {}, // 评价详情
visible: false, // 图片预览
previewImage: '', // 预览图片地址
loading: false, // 加载状态
submitLoading: false, // 回复消息loading
// 状态
statusLabel: {
NO_APPLY: '未申请',
APPLYING: '申请中',
COMPLETE: '已完成',
EXPIRED: '已失效',
CANCEL: '已取消',
NEW: '待审核'
},
// 商家回复内容
params: {
content: '',
complainId: ''
}
}
},
methods: {
getDetail () { // 获取投诉详情
getComplainDetail(this.$route.query.id).then(res => {
if (res.success) this.detail = res.result
})
},
goGoodsDetail (skuId, goodsId) { // 跳转商品详情
let routerUrl = this.$router.resolve({
path: '/goodsDetail',
query: {skuId, goodsId}
})
window.open(routerUrl.href, '_blank')
},
handleView (name) { // 预览图片
this.previewImage = name;
this.visible = true;
},
// 回复消息
handleSubmit () {
if (this.params.content === '') {
Message.error('请填写对话内容');
return;
}
this.submitLoading = true;
this.params.complainId = this.$route.query.id;
communication(this.params).then((res) => {
this.submitLoading = false;
if (res.success) {
Message.success('对话成功');
this.params.content = '';
this.getDetail();
}
});
}
},
mounted () {
this.getDetail()
}
const route = useRoute();
const router = useRouter();
const detail = ref({}); // 评价详情
const visible = ref(false); // 图片预览
const previewImage = ref(''); // 预览图片地址
const loading = ref(false); // 加载状态
const submitLoading = ref(false); // 回复消息loading
// 状态
const statusLabel = {
NO_APPLY: '未申请',
APPLYING: '申请中',
COMPLETE: '已完成',
EXPIRED: '已失效',
CANCEL: '已取消',
NEW: '待审核'
};
// 商家回复内容
const params = reactive({
content: '',
complainId: ''
});
function getDetail () { // 获取投诉详情
getComplainDetail(route.query.id).then(res => {
if (res.success) detail.value = res.result
})
}
function goGoodsDetail (skuId, goodsId) { // 跳转商品详情
let routerUrl = router.resolve({
path: '/goodsDetail',
query: {skuId, goodsId}
})
window.open(routerUrl.href, '_blank')
}
function handleView (name) { // 预览图片
previewImage.value = name;
visible.value = true;
}
// 回复消息
function handleSubmit () {
if (params.content === '') {
Message.error('请填写对话内容');
return;
}
submitLoading.value = true;
params.complainId = route.query.id;
communication(params).then((res) => {
submitLoading.value = false;
if (res.success) {
Message.success('对话成功');
params.content = '';
getDetail();
}
});
}
onMounted(() => {
getDetail()
})
</script>
<style lang="scss" scoped>
.speak-way {

View File

@@ -58,72 +58,75 @@
</div>
</template>
<script>
<script setup>
import { reactive, ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { Message, Modal } from "@/utils/message";
import {complainList, clearComplain} from '@/api/member.js';
export default {
name: 'ComplainList',
data () {
return {
loading: false, // 加载状态
list: [], // 投诉列表
statusLabel: { // 投诉状态
NO_APPLY: '未申请',
APPLYING: '申请中',
COMPLETE: '已完成',
EXPIRED: '已失效',
CANCEL: '已取消',
NEW: '新订单'
},
total: 0, // 投诉总数
params: { // 请求参数
pageNumber: 1,
pageSize: 20
}
};
},
mounted () {
this.getList()
},
methods: {
getList () { // 获取投诉列表
complainList(this.params).then(res => {
import { complainList, clearComplain } from '@/api/member.js';
defineOptions({ name: 'ComplainList' });
const router = useRouter();
const loading = ref(false); // 加载状态
const list = ref([]); // 投诉列表
const statusLabel = { // 投诉状态
NO_APPLY: '未申请',
APPLYING: '申请中',
COMPLETE: '已完成',
EXPIRED: '已失效',
CANCEL: '已取消',
NEW: '新订单'
};
const total = ref(0); // 投诉总数
const params = reactive({ // 请求参数
pageNumber: 1,
pageSize: 20
});
function getList() { // 获取投诉列表
complainList(params).then(res => {
if (res.success) {
list.value = res.result.records;
total.value = res.result.total
}
})
}
function changePageNum(val) { // 改变页码
params.pageNumber = val;
getList()
}
function changePageSize(val) { // 改变页数
params.pageNumber = 1;
params.pageSize = val;
getList()
}
function cancel(id) { // 取消投诉
Modal.confirm({
title: '取消投诉',
content: '确定取消投诉吗?',
onOk: () => {
clearComplain(id).then((res) => {
if (res.success) {
const list = res.result.records;
this.list = list;
this.total = res.result.total
Message.success('取消投诉成功');
getList();
}
})
},
changePageNum (val) { // 改变页码
this.params.pageNumber = val;
this.getList()
},
changePageSize (val) { // 改变页数
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getList()
},
cancel (id) { // 取消投诉
Modal.confirm({
title: '取消投诉',
content: '确定取消投诉吗?',
onOk: () => {
clearComplain(id).then((res) => {
if (res.success) {
Message.success('取消投诉成功');
this.getList();
}
});
},
onCancel: () => { }
});
},
goDetail (id) { // 跳转投诉详情
this.$router.push({path: '/home/complainDetail', query: { id }})
}
}
};
onCancel: () => { }
});
}
function goDetail(id) { // 跳转投诉详情
router.push({ path: '/home/complainDetail', query: { id } })
}
onMounted(() => {
getList()
});
</script>
<style scoped lang="scss">

View File

@@ -84,136 +84,144 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Modal } from "@/utils/message";
import { collectList, cancelCollect, storeCollectList, cancelStoreCollect } from '@/api/member.js'
export default {
name: 'Favorites',
props: {
homePage: {
type: Boolean,
default: false
}
},
data() {
return {
favoriteWay: ['收藏商品', '收藏店铺'], // 收藏分类
list: [], // 收藏列表
total: 0, // 收藏总数
params: { // 请求参数
pageNumber: 1,
pageSize: 100,
type: this.$route.query.type || 'GOODS'
},
spinShow: false // 加载状态
};
},
computed: {
activeTabIndex() {
return this.params.type === 'STORE' ? 1 : 0;
},
},
methods: {
loadFavorites() {
if (this.params.type === 'STORE') {
this.getStoreList();
} else {
this.getList();
}
},
getList() { // 获取商品收藏列表
this.spinShow = true
collectList(this.params).then(res => {
if (res.success && res.result) {
this.list = res.result.records || [];
}
}).catch(() => {
this.list = [];
}).finally(() => {
this.spinShow = false
})
},
getStoreList() { // 获取店铺收藏列表
this.spinShow = true
storeCollectList(this.params).then(res => {
if (res.success && res.result) {
this.list = res.result.records || [];
}
}).catch(() => {
this.list = [];
}).finally(() => {
this.spinShow = false
})
},
change(index) { // tab栏切换
if (index === 0) { this.params.type = 'GOODS', this.getList() }
if (index === 1) { this.params.type = 'STORE', this.getStoreList() }
},
cancel(id) { // 取消收藏
let typeName = this.params.type === 'GOODS' ? '商品' : '店铺'
Modal.confirm({
title: '取消收藏',
content: `确定取消收藏该${typeName}吗?`,
onOk: () => {
cancelCollect(this.params.type, id).then(res => {
if (res.success) {
this.getList();
}
})
}
});
},
cancelStore(id) { // 取消收藏
let typeName = this.params.type === 'GOODS' ? '商品' : '店铺'
Modal.confirm({
title: '取消收藏',
content: `确定取消收藏该${typeName}吗?`,
onOk: () => {
cancelStoreCollect(this.params.type, id).then(res => {
if (res.success) {
this.getStoreList();
}
})
}
});
},
openItem (item) {
if (this.params.type === 'STORE') {
this.goShop(item.id)
return
}
this.buynow(item.skuId, item.goodsId)
},
buynow(skuId, goodsId,storeId) { // 跳转详情
let url
if (this.params.type === 'STORE') {
url = this.$router.resolve({
path: '/merchant',
query: { 'id': storeId }
})
} else {
url = this.$router.resolve({
path: '/goodsDetail',
query: { skuId, goodsId }
})
defineOptions({ name: 'Favorites' });
}
window.open(url.href, '_blank')
},
goShop(id) { // 跳转店铺页面
this.$router.push({
path: '/merchant',
query: { id }
})
}
},
mounted() {
if (this.homePage) this.params.pageSize = 5;
this.loadFavorites();
const props = defineProps({
homePage: {
type: Boolean,
default: false
}
};
});
const route = useRoute();
const router = useRouter();
const favoriteWay = ref(['收藏商品', '收藏店铺']); // 收藏分类
const list = ref([]); // 收藏列表
const total = ref(0); // 收藏总数
const params = reactive({ // 请求参数
pageNumber: 1,
pageSize: 100,
type: route.query.type || 'GOODS'
});
const spinShow = ref(false); // 加载状态
const activeTabIndex = computed(() => {
return params.type === 'STORE' ? 1 : 0;
});
function loadFavorites() {
if (params.type === 'STORE') {
getStoreList();
} else {
getList();
}
}
function getList() { // 获取商品收藏列表
spinShow.value = true
collectList(params).then(res => {
if (res.success && res.result) {
list.value = res.result.records || [];
}
}).catch(() => {
list.value = [];
}).finally(() => {
spinShow.value = false
})
}
function getStoreList() { // 获取店铺收藏列表
spinShow.value = true
storeCollectList(params).then(res => {
if (res.success && res.result) {
list.value = res.result.records || [];
}
}).catch(() => {
list.value = [];
}).finally(() => {
spinShow.value = false
})
}
function change(index) { // tab栏切换
if (index === 0) { params.type = 'GOODS', getList() }
if (index === 1) { params.type = 'STORE', getStoreList() }
}
function cancel(id) { // 取消收藏
let typeName = params.type === 'GOODS' ? '商品' : '店铺'
Modal.confirm({
title: '取消收藏',
content: `确定取消收藏该${typeName}吗?`,
onOk: () => {
cancelCollect(params.type, id).then(res => {
if (res.success) {
getList();
}
})
}
});
}
function cancelStore(id) { // 取消收藏
let typeName = params.type === 'GOODS' ? '商品' : '店铺'
Modal.confirm({
title: '取消收藏',
content: `确定取消收藏该${typeName}吗?`,
onOk: () => {
cancelStoreCollect(params.type, id).then(res => {
if (res.success) {
getStoreList();
}
})
}
});
}
function openItem (item) {
if (params.type === 'STORE') {
goShop(item.id)
return
}
buynow(item.skuId, item.goodsId)
}
function buynow(skuId, goodsId, storeId) { // 跳转详情
let url
if (params.type === 'STORE') {
url = router.resolve({
path: '/merchant',
query: { 'id': storeId }
})
} else {
url = router.resolve({
path: '/goodsDetail',
query: { skuId, goodsId }
})
}
window.open(url.href, '_blank')
}
function goShop(id) { // 跳转店铺页面
router.push({
path: '/merchant',
query: { id }
})
}
onMounted(() => {
if (props.homePage) params.pageSize = 5;
loadFavorites();
});
</script>
<style scoped lang="scss">

View File

@@ -74,206 +74,218 @@
</template>
<script>
<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { memberGradeCurrent, memberGradeList, memberGradeRules } from '@/api/member.js'
export default {
name: 'MemberGrade',
data () {
return {
ruleList: [],
rulesLoading: false,
rulesDescription: '',
/** 接口返回 List MemberGradeDetailVO,每项 { grade, benefits } */
gradeDetails: [],
gradeList: [],
currentGrade: {},
currentExperience: 0,
currentGradeId: '',
selectedRuleKey: '',
activeGradeTab: 'rules',
logLoading: false,
gradeLoading: false,
params: {
pageNumber: 1,
pageSize: 20
},
experienceLogs: {}
}
},
computed: {
rulesMap () {
const map = {}
this.ruleList.forEach(item => {
map[item.ruleKey] = item.ruleName
})
return map
},
sortedGradeList () {
const list = Array.isArray(this.gradeList) ? this.gradeList.slice() : []
list.sort((a, b) => {
const sa = a && a.gradeSort != null ? Number(a.gradeSort) : 0
const sb = b && b.gradeSort != null ? Number(b.gradeSort) : 0
if (sa !== sb) return sa - sb
const ea = a && a.requiredExperience != null ? Number(a.requiredExperience) : 0
const eb = b && b.requiredExperience != null ? Number(b.requiredExperience) : 0
return ea - eb
})
return list
},
currentGradeIndex () {
if (!this.currentGradeId) return -1
return this.sortedGradeList.findIndex(g => g && g.id === this.currentGradeId)
},
nextGrade () {
const idx = this.currentGradeIndex
if (idx < 0) return null
return this.sortedGradeList[idx + 1] || null
},
diffExperience () {
if (!this.nextGrade || this.nextGrade.requiredExperience == null) return 0
const diff = Number(this.nextGrade.requiredExperience) - Number(this.currentExperience || 0)
return diff > 0 ? diff : 0
},
/** 当前等级在列表详情中对应的启用权益(顺序与后台 benefits 一致) */
currentGradeBenefits () {
const id = this.currentGradeId
if (!id || !Array.isArray(this.gradeDetails) || !this.gradeDetails.length) return []
const detail = this.gradeDetails.find(
(d) => d && d.grade && String(d.grade.id) === String(id)
)
const list = detail && Array.isArray(detail.benefits) ? detail.benefits : []
return list.filter((b) => b && b.benefitState === 'OPEN')
},
},
mounted () {
this.init()
},
methods: {
init () {
this.loadRules()
this.loadGradeList()
this.loadCurrent()
},
loadRules () {
this.rulesLoading = true
memberGradeRules()
.then(res => {
if (res && res.success) {
const items = res.result && Array.isArray(res.result.items) ? res.result.items : []
this.rulesDescription = (res.result && res.result.description) || ''
this.ruleList = items
}
})
.finally(() => {
this.rulesLoading = false
})
},
loadGradeList () {
this.gradeLoading = true
memberGradeList()
.then(res => {
if (res && res.success) {
const raw = Array.isArray(res.result) ? res.result : []
this.gradeDetails = raw
.map((item) => {
if (!item) return null
if (item.grade != null) {
return {
grade: item.grade,
benefits: Array.isArray(item.benefits) ? item.benefits : []
}
}
return { grade: item, benefits: [] }
})
.filter(Boolean)
this.gradeList = this.gradeDetails.map((d) => d.grade).filter(Boolean)
} else {
this.gradeDetails = []
this.gradeList = []
}
})
.finally(() => {
this.gradeLoading = false
})
},
loadCurrent () {
this.logLoading = true
const params = {
pageNumber: this.params.pageNumber,
pageSize: this.params.pageSize
defineOptions({ name: 'MemberGrade' })
const ruleList = ref([])
const rulesLoading = ref(false)
const rulesDescription = ref('')
/** 接口返回 List MemberGradeDetailVO,每项 { grade, benefits } */
const gradeDetails = ref([])
const gradeList = ref([])
const currentGrade = ref({})
const currentExperience = ref(0)
const currentGradeId = ref('')
const selectedRuleKey = ref('')
const activeGradeTab = ref('rules')
const logLoading = ref(false)
const gradeLoading = ref(false)
const params = reactive({
pageNumber: 1,
pageSize: 20
})
const experienceLogs = ref({})
const rulesMap = computed(() => {
const map = {}
ruleList.value.forEach(item => {
map[item.ruleKey] = item.ruleName
})
return map
})
const sortedGradeList = computed(() => {
const list = Array.isArray(gradeList.value) ? gradeList.value.slice() : []
list.sort((a, b) => {
const sa = a && a.gradeSort != null ? Number(a.gradeSort) : 0
const sb = b && b.gradeSort != null ? Number(b.gradeSort) : 0
if (sa !== sb) return sa - sb
const ea = a && a.requiredExperience != null ? Number(a.requiredExperience) : 0
const eb = b && b.requiredExperience != null ? Number(b.requiredExperience) : 0
return ea - eb
})
return list
})
const currentGradeIndex = computed(() => {
if (!currentGradeId.value) return -1
return sortedGradeList.value.findIndex(g => g && g.id === currentGradeId.value)
})
const nextGrade = computed(() => {
const idx = currentGradeIndex.value
if (idx < 0) return null
return sortedGradeList.value[idx + 1] || null
})
const diffExperience = computed(() => {
if (!nextGrade.value || nextGrade.value.requiredExperience == null) return 0
const diff = Number(nextGrade.value.requiredExperience) - Number(currentExperience.value || 0)
return diff > 0 ? diff : 0
})
/** 当前等级在列表详情中对应的启用权益(顺序与后台 benefits 一致) */
const currentGradeBenefits = computed(() => {
const id = currentGradeId.value
if (!id || !Array.isArray(gradeDetails.value) || !gradeDetails.value.length) return []
const detail = gradeDetails.value.find(
(d) => d && d.grade && String(d.grade.id) === String(id)
)
const list = detail && Array.isArray(detail.benefits) ? detail.benefits : []
return list.filter((b) => b && b.benefitState === 'OPEN')
})
onMounted(() => {
init()
})
function init () {
loadRules()
loadGradeList()
loadCurrent()
}
function loadRules () {
rulesLoading.value = true
memberGradeRules()
.then(res => {
if (res && res.success) {
const items = res.result && Array.isArray(res.result.items) ? res.result.items : []
rulesDescription.value = (res.result && res.result.description) || ''
ruleList.value = items
}
if (this.selectedRuleKey) params.ruleKey = this.selectedRuleKey
memberGradeCurrent(params)
.then(res => {
if (res && res.success && res.result) {
this.currentGrade = res.result.grade || {}
this.currentExperience = res.result.experience || 0
this.currentGradeId = res.result.gradeId || (this.currentGrade && this.currentGrade.id) || ''
this.experienceLogs = res.result.experienceLogs || {}
}
})
.finally(() => {
this.logLoading = false
})
},
selectRule (ruleKey) {
this.selectedRuleKey = ruleKey || ''
this.params.pageNumber = 1
this.loadCurrent()
},
changePage (val) {
this.params.pageNumber = val
this.loadCurrent()
},
changePageSize (val) {
this.params.pageSize = val
this.params.pageNumber = 1
this.loadCurrent()
},
getRuleName (ruleKey) {
if (!ruleKey) return '-'
return this.rulesMap[ruleKey] || ruleKey
},
getRuleDesc (ruleKey) {
const map = {
CONSUME: '客户消费1元可获取经验值,向下取整',
REGISTER: '会员注册成功后可获取经验值',
SIGN_IN: '客户每日签到后可获取的经验值',
COMMENT: '对已购买商品完成发表评价获取经验值;仅针对评论字数大于30字的评论进行发放',
SHARE: '会员分享商城页面可获取的经验值',
PROFILE: '完善个人基本信息可获得经验值,每个会员仅可获得一次',
FOLLOW_STORE: '关注店铺可获得经验值,每个客户ID同店铺仅第一次关注可进行获得',
BIND_WECHAT: '绑定微信成功后获得经验值,每个会员仅可获得一次',
ADD_ADDRESS: '添加收货地址可获得经验值,每个会员仅可获得一次',
SHARE_REGISTER: '仅被注册成功后可获得相应奖励经验值',
SHARE_BUY: '仅被购买成功后可获得相应奖励经验值'
})
.finally(() => {
rulesLoading.value = false
})
}
function loadGradeList () {
gradeLoading.value = true
memberGradeList()
.then(res => {
if (res && res.success) {
const raw = Array.isArray(res.result) ? res.result : []
gradeDetails.value = raw
.map((item) => {
if (!item) return null
if (item.grade != null) {
return {
grade: item.grade,
benefits: Array.isArray(item.benefits) ? item.benefits : []
}
}
return { grade: item, benefits: [] }
})
.filter(Boolean)
gradeList.value = gradeDetails.value.map((d) => d.grade).filter(Boolean)
} else {
gradeDetails.value = []
gradeList.value = []
}
if (!ruleKey) return '-'
return map[ruleKey] || '-'
},
getLogDeltaRaw (row) {
if (!row) return 0
const candidate = row.variableExperience != null ? row.variableExperience : (row.changeExperience != null ? row.changeExperience : (row.experience != null ? row.experience : row.value))
if (candidate == null) return 0
const num = Number(candidate)
if (Number.isNaN(num)) return 0
if (row.type === 'DECREASE') return -Math.abs(num)
if (row.type === 'INCREASE') return Math.abs(num)
return num
},
getLogDeltaSign (row) {
const num = this.getLogDeltaRaw(row)
return num >= 0 ? '+' : '-'
},
getLogDeltaAbsValue (row) {
const num = this.getLogDeltaRaw(row)
return Math.abs(num)
},
getLogDeltaColor (row) {
return this.getLogDeltaRaw(row) >= 0 ? 'green' : 'red'
}
})
.finally(() => {
gradeLoading.value = false
})
}
function loadCurrent () {
logLoading.value = true
const reqParams = {
pageNumber: params.pageNumber,
pageSize: params.pageSize
}
if (selectedRuleKey.value) reqParams.ruleKey = selectedRuleKey.value
memberGradeCurrent(reqParams)
.then(res => {
if (res && res.success && res.result) {
currentGrade.value = res.result.grade || {}
currentExperience.value = res.result.experience || 0
currentGradeId.value = res.result.gradeId || (currentGrade.value && currentGrade.value.id) || ''
experienceLogs.value = res.result.experienceLogs || {}
}
})
.finally(() => {
logLoading.value = false
})
}
function selectRule (ruleKey) {
selectedRuleKey.value = ruleKey || ''
params.pageNumber = 1
loadCurrent()
}
function changePage (val) {
params.pageNumber = val
loadCurrent()
}
function changePageSize (val) {
params.pageSize = val
params.pageNumber = 1
loadCurrent()
}
function getRuleName (ruleKey) {
if (!ruleKey) return '-'
return rulesMap.value[ruleKey] || ruleKey
}
function getRuleDesc (ruleKey) {
const map = {
CONSUME: '客户消费1元可获取经验值,向下取整',
REGISTER: '会员注册成功后可获取经验值',
SIGN_IN: '客户每日签到后可获取的经验值',
COMMENT: '对已购买商品完成发表评价获取经验值;仅针对评论字数大于30字的评论进行发放',
SHARE: '会员分享商城页面可获取的经验值',
PROFILE: '完善个人基本信息可获得经验值,每个会员仅可获得一次',
FOLLOW_STORE: '关注店铺可获得经验值,每个客户ID同店铺仅第一次关注可进行获得',
BIND_WECHAT: '绑定微信成功后获得经验值,每个会员仅可获得一次',
ADD_ADDRESS: '添加收货地址可获得经验值,每个会员仅可获得一次',
SHARE_REGISTER: '仅被注册成功后可获得相应奖励经验值',
SHARE_BUY: '仅被购买成功后可获得相应奖励经验值'
}
if (!ruleKey) return '-'
return map[ruleKey] || '-'
}
function getLogDeltaRaw (row) {
if (!row) return 0
const candidate = row.variableExperience != null ? row.variableExperience : (row.changeExperience != null ? row.changeExperience : (row.experience != null ? row.experience : row.value))
if (candidate == null) return 0
const num = Number(candidate)
if (Number.isNaN(num)) return 0
if (row.type === 'DECREASE') return -Math.abs(num)
if (row.type === 'INCREASE') return Math.abs(num)
return num
}
function getLogDeltaSign (row) {
const num = getLogDeltaRaw(row)
return num >= 0 ? '+' : '-'
}
function getLogDeltaAbsValue (row) {
const num = getLogDeltaRaw(row)
return Math.abs(num)
}
function getLogDeltaColor (row) {
return getLogDeltaRaw(row) >= 0 ? 'green' : 'red'
}
</script>

View File

@@ -2,7 +2,7 @@
<div class="wrapper">
<UserCenterLayout>
<!-- 手机号验证 -->
<el-form ref="formData" :model="formData" label-position="left" label-width="100px" :rules="ruleInLines"
<el-form ref="formDataRef" :model="formData" label-position="left" label-width="100px" :rules="ruleInLines"
v-if="(status == 2 || status == 3) && !showPwd">
<el-form-item label="手机号">
<div class="phone">{{ displayMobile }}</div>
@@ -21,7 +21,7 @@
</div>
<!-- 修改 -->
<el-form ref="formRegister" :model="formRegister" :rules="ruleInline" label-width="80px" v-if="status == 1 && !showPwd">
<el-form ref="formRegisterRef" :model="formRegister" :rules="ruleInline" label-width="80px" v-if="status == 1 && !showPwd">
<el-form-item label="旧密码" prop="password">
<el-input type="password" v-model="formRegister.password" clearable size="large" placeholder="请输入旧密码"
style="width: 300px"
@@ -49,7 +49,7 @@
<el-button type="primary" class="ml_10" @click="handleRegist">修改</el-button>
</div>
<!-- 设置&修改的第二种情况 -->
<el-form ref="formDataUpdate" :model="formDataUpdate" label-position="left" label-width="100px" :rules="ruleIn"
<el-form ref="formDataUpdateRef" :model="formDataUpdate" label-position="left" label-width="100px" :rules="ruleIn"
v-if="showPwd">
<el-form-item label="新密码" prop="newPassword">
<el-input type="password" v-model="formDataUpdate.newPassword" clearable size="large" placeholder="请输入新密码"
@@ -73,7 +73,9 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "@/utils/message";
import { Lock } from '@element-plus/icons-vue';
import {
@@ -82,166 +84,171 @@ import {
} from '@/api/account';
import { getMemberMsg } from '@/api/login.js';
import storage from '@/plugins/storage.js';
import {md5} from '@/plugins/md5.js'
import { md5 } from '@/plugins/md5.js'
import { secrecyMobile } from '@/plugins/filters.js'
export default {
components: { Lock },
name: 'modifyPwd',
data () {
return {
// 1为修改 2为设置 3为提交校验的下一步
status: '',
mobile: '',
showPwd: false, // 显示密码
formData: { // 验证表单
picture: '',
msg: ''
},
formDataUpdate: { // 直接输入新密码表单
newPassword: '',
againPassword: ''
},
formRegister: { // 第三步 新密码表单
password: '',
againPassword: '',
newPassword: ''
},
ruleInLines: { // 验证规则
picture: [
{required: true, message: '请输入图片验证码', trigger: 'blur'}
],
msg: [
{required: true, message: '请输入短信验证码', trigger: 'blur'}
]
},
ruleIn: { // 验证规则
newPassword: [
{required: true, message: '请输入新密码', trigger: 'blur'},
{type: 'string', min: 6, message: '密码不能少于6位'}
],
againPassword: [
{required: true, message: '请输入新密码', trigger: 'blur'},
{type: 'string', min: 6, message: '密码不能少于6位'}
]
},
ruleInline: { // 验证规则
password: [
{required: true, message: '请输入旧密码', trigger: 'blur'}
],
newPassword: [
{required: true, message: '请输入新密码', trigger: 'blur'},
{type: 'string', min: 6, message: '密码不能少于6位'}
],
againPassword: [
{required: true, message: '请确认新密码', trigger: 'blur'},
{type: 'string', min: 6, message: '密码不能少于6位'}
]
}
}
},
computed: {
displayMobile () {
if (!this.mobile) return '未绑定手机号';
return this.$filters.secrecyMobile(this.mobile);
}
},
mounted () {
this.status = this.$route.query.status
this.loadMobile()
},
methods: {
loadMobile () {
const rawUserInfo = storage.getItem('userInfo');
if (rawUserInfo) {
try {
const userInfo = JSON.parse(rawUserInfo);
if (userInfo && userInfo.mobile) this.mobile = userInfo.mobile;
} catch {
// 缓存的用户信息解析失败时忽略,改走接口获取
}
}
if (!storage.getItem('accessToken')) return;
getMemberMsg().then(res => {
if (res.success && res.result) {
storage.setItem('userInfo', res.result);
this.mobile = res.result.mobile || '';
}
}).catch((err) => {
console.warn('获取会员信息失败', err);
});
},
normalizePwdError (message) {
if (!message) return '修改失败,请稍后重试';
if (String(message).includes('旧密码') || String(message).includes('密码不正确')) {
return '旧密码不正确';
}
return message;
},
// 修改
handleRegist () {
this.$refs['formRegister'].validate((valid) => {
if (valid) {
const {newPassword, againPassword, password} = this.formRegister
if (newPassword !== againPassword) {
Message.error({
content: '新密码和确认密码不一致'
});
return
}
const params = {newPassword, password}
params.newPassword = md5(newPassword)
params.password = md5(password)
editPwd(params).then(res => {
if (res.success && res.result) {
Message.success('修改密码成功');
this.$router.push('/home')
} else {
Message.error(this.normalizePwdError(res.message));
}
}).catch((err) => {
Message.error(this.normalizePwdError(err?.message || err?.data?.message));
});
}
})
},
// 提交验证
submitRegister () {
this.$refs['formData'].validate((valid) => {
if (valid) {
this.showPwd = true
}
})
},
// 设置密码
setPassword () {
this.$refs['formDataUpdate'].validate((valid) => {
if (valid) {
const {newPassword, againPassword} = this.formDataUpdate
if (newPassword === '') {
Message.error({
content: '请输入密码'
});
return
}
if (newPassword !== againPassword) {
Message.error({
content: '新密码和确认密码不一致'
});
return
}
const params = {password: newPassword}
params.password = md5(newPassword)
setPwd(params).then(res => {
if (res.message === 'success' && res.result) {
Message.success('支付密码设置成功');
this.$router.push('/home')
}
});
}
})
defineOptions({ name: 'modifyPwd' });
const route = useRoute();
const router = useRouter();
// 1为修改 2为设置 3为提交校验的下一步
const status = ref('');
const mobile = ref('');
const showPwd = ref(false); // 显示密码
const formData = reactive({ // 验证表单
picture: '',
msg: ''
});
const formDataUpdate = reactive({ // 直接输入新密码表单
newPassword: '',
againPassword: ''
});
const formRegister = reactive({ // 第三步 新密码表单
password: '',
againPassword: '',
newPassword: ''
});
const ruleInLines = { // 验证规则
picture: [
{required: true, message: '请输入图片验证码', trigger: 'blur'}
],
msg: [
{required: true, message: '请输入短信验证码', trigger: 'blur'}
]
};
const ruleIn = { // 验证规则
newPassword: [
{required: true, message: '请输入新密码', trigger: 'blur'},
{type: 'string', min: 6, message: '密码不能少于6位'}
],
againPassword: [
{required: true, message: '请输入新密码', trigger: 'blur'},
{type: 'string', min: 6, message: '密码不能少于6位'}
]
};
const ruleInline = { // 验证规则
password: [
{required: true, message: '请输入旧密码', trigger: 'blur'}
],
newPassword: [
{required: true, message: '请输入新密码', trigger: 'blur'},
{type: 'string', min: 6, message: '密码不能少于6位'}
],
againPassword: [
{required: true, message: '请确认新密码', trigger: 'blur'},
{type: 'string', min: 6, message: '密码不能少于6位'}
]
};
const formDataRef = ref(null);
const formRegisterRef = ref(null);
const formDataUpdateRef = ref(null);
const displayMobile = computed(() => {
if (!mobile.value) return '未绑定手机号';
return secrecyMobile(mobile.value);
});
function loadMobile () {
const rawUserInfo = storage.getItem('userInfo');
if (rawUserInfo) {
try {
const userInfo = JSON.parse(rawUserInfo);
if (userInfo && userInfo.mobile) mobile.value = userInfo.mobile;
} catch {
// 缓存的用户信息解析失败时忽略,改走接口获取
}
}
if (!storage.getItem('accessToken')) return;
getMemberMsg().then(res => {
if (res.success && res.result) {
storage.setItem('userInfo', res.result);
mobile.value = res.result.mobile || '';
}
}).catch((err) => {
console.warn('获取会员信息失败', err);
});
}
function normalizePwdError (message) {
if (!message) return '修改失败,请稍后重试';
if (String(message).includes('旧密码') || String(message).includes('密码不正确')) {
return '旧密码不正确';
}
return message;
}
// 修改
function handleRegist () {
formRegisterRef.value.validate((valid) => {
if (valid) {
const {newPassword, againPassword, password} = formRegister
if (newPassword !== againPassword) {
Message.error({
content: '新密码和确认密码不一致'
});
return
}
const params = {newPassword, password}
params.newPassword = md5(newPassword)
params.password = md5(password)
editPwd(params).then(res => {
if (res.success && res.result) {
Message.success('修改密码成功');
router.push('/home')
} else {
Message.error(normalizePwdError(res.message));
}
}).catch((err) => {
Message.error(normalizePwdError(err?.message || err?.data?.message));
});
}
})
}
// 提交验证
function submitRegister () {
formDataRef.value.validate((valid) => {
if (valid) {
showPwd.value = true
}
})
}
// 设置密码
function setPassword () {
formDataUpdateRef.value.validate((valid) => {
if (valid) {
const {newPassword, againPassword} = formDataUpdate
if (newPassword === '') {
Message.error({
content: '请输入密码'
});
return
}
if (newPassword !== againPassword) {
Message.error({
content: '新密码和确认密码不一致'
});
return
}
const params = {password: newPassword}
params.password = md5(newPassword)
setPwd(params).then(res => {
if (res.message === 'success' && res.result) {
Message.success('支付密码设置成功');
router.push('/home')
}
});
}
})
}
onMounted(() => {
status.value = route.query.status
loadMobile()
});
</script>
<style scoped lang="scss">

View File

@@ -42,48 +42,47 @@
</UserCenterLayout>
</div>
</template>
<script>
import {memberPoint, memberPointHistory} from '@/api/member.js'
<script setup>
import { onMounted, reactive, ref } from "vue";
import { memberPoint, memberPointHistory } from '@/api/member.js'
export default {
name: 'Point',
data () {
return {
logData: {}, // 积分日志
pointObj: {}, // 积分明细
loading: false, // 请求接口加载框
params: { // 积分列表请求参数
pageNumber: 1,
pageSize: 20
},
}
},
mounted () {
this.getHistory()
this.getPoint()
},
methods: {
getHistory () { // 获取积分历史
memberPointHistory(this.params).then(res => {
this.logData = res.result;
})
},
getPoint () { // 获取积分详情
memberPoint().then(res => {
if (res.success) this.pointObj = res.result
})
},
changePage (val) { // 修改页码
this.params.pageNumber = val
this.getHistory()
},
changePageSize (val) { // 修改页数
this.params.pageSize = val
this.params.pageNumber = 1
this.getHistory()
}
}
defineOptions({ name: "Point" });
const logData = ref({}); // 积分日志
const pointObj = ref({}); // 积分明细
const loading = ref(false); // 请求接口加载框
const params = reactive({ // 积分列表请求参数
pageNumber: 1,
pageSize: 20
});
function getHistory() { // 获取积分历史
memberPointHistory(params).then(res => {
logData.value = res.result;
})
}
function getPoint() { // 获取积分详情
memberPoint().then(res => {
if (res.success) pointObj.value = res.result
})
}
function changePage(val) { // 修改页码
params.pageNumber = val
getHistory()
}
function changePageSize(val) { // 修改页数
params.pageSize = val
params.pageNumber = 1
getHistory()
}
onMounted(() => {
getHistory()
getPoint()
});
</script>
<style lang="scss" scoped>
h3 {

View File

@@ -1,7 +1,7 @@
<template>
<div class="wrapper">
<UserCenterLayout>
<el-form class="profile-form" :model="formItem" :rules="rules" ref="form" label-width="72px" label-position="left">
<el-form class="profile-form" :model="formItem" :rules="rules" ref="formRef" label-width="72px" label-position="left">
<el-form-item label="头像" class="avatar-form-item">
<div class="avatar-upload">
<el-avatar v-if="formItem.face" :src="formItem.face" :size="72" />
@@ -42,114 +42,115 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "@/utils/message";
import { editMemberInfo } from '@/api/account.js';
import { getMemberMsg } from '@/api/login.js';
import { buyerUrl } from '@/plugins/request.js';
import storage from '@/plugins/storage.js';
import { User } from '@element-plus/icons-vue';
export default {
components: { User },
name: 'Profile',
data () {
return {
rules: { // 验证规则
nickName: [{required: true, message: '用户昵称不能为空'}, { max: 16, message: '用户昵称不能超过15个字符' }]
},
formItem: {}, // 表单数据
action: buyerUrl + '/buyer/common/upload/file', // 上传接口
accessToken: {} // 验证token
defineOptions({ name: 'Profile' });
const route = useRoute();
const router = useRouter();
const formRef = ref(null);
const rules = { // 验证规则
nickName: [{required: true, message: '用户昵称不能为空'}, { max: 16, message: '用户昵称不能超过15个字符' }]
};
const formItem = ref({}); // 表单数据
const action = buyerUrl + '/buyer/common/upload/file'; // 上传接口
const accessToken = reactive({}); // 验证token
onMounted(() => {
accessToken.accessToken = storage.getItem('accessToken');
loadUserInfo();
});
function loadUserInfo () {
const rawUserInfo = storage.getItem('userInfo');
if (rawUserInfo) {
applyUserInfo(JSON.parse(rawUserInfo));
return;
}
if (!storage.getItem('accessToken')) {
redirectToLogin();
return;
}
getMemberMsg().then(res => {
if (res.success && res.result) {
storage.setItem('userInfo', res.result);
applyUserInfo(res.result);
} else {
redirectToLogin();
}
},
mounted () {
this.accessToken.accessToken = storage.getItem('accessToken');
this.loadUserInfo();
},
methods: {
loadUserInfo () {
const rawUserInfo = storage.getItem('userInfo');
if (rawUserInfo) {
this.applyUserInfo(JSON.parse(rawUserInfo));
return;
}).catch(() => {
redirectToLogin();
});
}
function applyUserInfo (userInfo) {
formItem.value = { ...userInfo };
formItem.value.birthday = normalizeBirthday(formItem.value.birthday);
}
function redirectToLogin () {
router.push({
path: '/login',
query: {
rePath: route.path,
query: JSON.stringify(route.query || {}),
},
});
}
function normalizeBirthday (value) {
if (!value) return ''
if (value instanceof Date) return value
if (typeof value === 'number') return new Date(value)
if (typeof value === 'string') {
const m = value.match(/^(\d{4})-(\d{2})-(\d{2})/)
if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]))
const d = new Date(value)
return isNaN(d.getTime()) ? '' : d
}
return ''
}
function formatBirthday (value) {
if (!value) return null
if (typeof value === 'string') {
const m = value.match(/^(\d{4})-(\d{2})-(\d{2})/)
return m ? `${m[1]}-${m[2]}-${m[3]}` : null
}
const d = value instanceof Date ? value : new Date(value)
if (isNaN(d.getTime())) return null
const yyyy = d.getFullYear()
const mm = String(d.getMonth() + 1).padStart(2, '0')
const dd = String(d.getDate()).padStart(2, '0')
return `${yyyy}-${mm}-${dd}`
}
function save () { // 保存
formRef.value.validate(valid => {
if (valid) {
let params = {
birthday: formatBirthday(formItem.value.birthday),
face: formItem.value.face,
nickName: formItem.value.nickName,
sex: formItem.value.sex
}
if (!storage.getItem('accessToken')) {
this.redirectToLogin();
return;
}
getMemberMsg().then(res => {
if (res.success && res.result) {
storage.setItem('userInfo', res.result);
this.applyUserInfo(res.result);
} else {
this.redirectToLogin();
}
}).catch(() => {
this.redirectToLogin();
});
},
applyUserInfo (userInfo) {
this.formItem = { ...userInfo };
this.formItem.birthday = this.normalizeBirthday(this.formItem.birthday);
},
redirectToLogin () {
this.$router.push({
path: '/login',
query: {
rePath: this.$route.path,
query: JSON.stringify(this.$route.query || {}),
},
});
},
normalizeBirthday (value) {
if (!value) return ''
if (value instanceof Date) return value
if (typeof value === 'number') return new Date(value)
if (typeof value === 'string') {
const m = value.match(/^(\d{4})-(\d{2})-(\d{2})/)
if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]))
const d = new Date(value)
return isNaN(d.getTime()) ? '' : d
}
return ''
},
formatBirthday (value) {
if (!value) return null
if (typeof value === 'string') {
const m = value.match(/^(\d{4})-(\d{2})-(\d{2})/)
return m ? `${m[1]}-${m[2]}-${m[3]}` : null
}
const d = value instanceof Date ? value : new Date(value)
if (isNaN(d.getTime())) return null
const yyyy = d.getFullYear()
const mm = String(d.getMonth() + 1).padStart(2, '0')
const dd = String(d.getDate()).padStart(2, '0')
return `${yyyy}-${mm}-${dd}`
},
save () { // 保存
this.$refs.form.validate(valid => {
if (valid) {
let params = {
birthday: this.formatBirthday(this.formItem.birthday),
face: this.formItem.face,
nickName: this.formItem.nickName,
sex: this.formItem.sex
}
editMemberInfo(params).then(res => {
if (res.success) {
Message.success('修改个人资料成功')
storage.setItem('userInfo', res.result)
this.$router.go(0)
}
})
editMemberInfo(params).then(res => {
if (res.success) {
Message.success('修改个人资料成功')
storage.setItem('userInfo', res.result)
router.go(0)
}
})
},
handleSuccess (res, file) { // 上传成功
this.formItem['face'] = res.result
},
}
}
})
}
function handleSuccess (res, file) { // 上传成功
formItem.value['face'] = res.result
}
</script>

View File

@@ -74,119 +74,125 @@
</UserCenterLayout>
</div>
</template>
<script>
<script setup>
import { getCurrentInstance, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message, Notice } from "@/utils/message";
import { Camera, Delete, View } from '@element-plus/icons-vue';
import { orderDetail } from '@/api/order.js';
import { addEvaluation } from '@/api/member.js';
import { buyerUrl } from '@/plugins/request.js';
import storage from '@/plugins/storage';
export default {
components: { Camera, Delete, View },
data () {
return {
order: {}, // 订单详情
orderGoods: {}, // 订单商品
form: { // 评分展示
deliveryScore: 5,
serviceScore: 5,
descriptionScore: 5
}, // 表单
visible: false, // 图片预览
action: buyerUrl + '/buyer/common/upload/file', // 上传地址
accessToken: {}, // 验证token
previewImage: '', // 预览图片地址
loading: false, // 提交加载状态
// el-rate SVG 通过 colors 写入 fill-color,跟随平台主题色
rateThemeColors: ['var(--theme-color)', 'var(--theme-color)', 'var(--theme-color)']
const route = useRoute();
const router = useRouter();
const instance = getCurrentInstance();
const order = ref({}); // 订单详情
const orderGoods = ref({}); // 订单商品
const form = reactive({ // 评分展示
deliveryScore: 5,
serviceScore: 5,
descriptionScore: 5
}); // 表单
const visible = ref(false); // 图片预览
const action = buyerUrl + '/buyer/common/upload/file'; // 上传地址
const accessToken = reactive({}); // 验证token
const previewImage = ref(''); // 预览图片地址
const loading = ref(false); // 提交加载状态
// el-rate SVG 通过 colors 写入 fill-color,跟随平台主题色
const rateThemeColors = ['var(--theme-color)', 'var(--theme-color)', 'var(--theme-color)'];
function getOrderDetail () { // 获取订单详情
orderDetail(route.query.sn).then(res => {
order.value = res.result
orderGoods.value = res.result.orderItems[route.query.index]
orderGoods.value['grade'] = 'GOOD'
orderGoods.value.uploadList = []
})
}
function save () { // 保存评价
if (!form.serviceScore || !form.deliveryScore) {
Message.warning('物流服务评价不能为空')
return false;
}
if (!form.descriptionScore) {
Message.warning('描述评价不能为空')
return false;
}
if (!orderGoods.value.content || !String(orderGoods.value.content).trim()) {
Message.warning('评论内容不能为空')
return false;
}
loading.value = true;
let goods = orderGoods.value
let params = {
goodsId: goods.goodsId,
orderItemSn: goods.sn,
skuId: goods.skuId,
descriptionScore: form.descriptionScore,
serviceScore: form.serviceScore,
deliveryScore: form.deliveryScore,
grade: goods.grade,
content: goods.content.trim(),
images: goods.uploadList.length ? goods.uploadList.join(',') : ''
}
addEvaluation(params).then(res => {
loading.value = false
if (res.success) {
Message.success('评价成功')
router.push('/home/CommentList')
} else {
Message.error(res.message || '评价失败,请稍后重试')
}
},
methods: {
getOrderDetail () { // 获取订单详情
orderDetail(this.$route.query.sn).then(res => {
this.order = res.result
this.orderGoods = res.result.orderItems[this.$route.query.index]
this.orderGoods['grade'] = 'GOOD'
this.orderGoods.uploadList = []
})
},
save () { // 保存评价
if (!this.form.serviceScore || !this.form.deliveryScore) {
Message.warning('物流服务评价不能为空')
return false;
}
}).catch((err) => {
loading.value = false;
Message.error(err?.message || '评价失败,请稍后重试')
})
}
if (!this.form.descriptionScore) {
Message.warning('描述评价不能为空')
return false;
}
function goGoodsDetail (skuId, goodsId) { // 跳转商品详情
let routerUrl = router.resolve({
path: '/goodsDetail',
query: {skuId, goodsId}
})
window.open(routerUrl.href, '_blank')
}
if (!this.orderGoods.content || !String(this.orderGoods.content).trim()) {
Message.warning('评论内容不能为空')
return false;
}
function handleView (name) { // 预览图片
previewImage.value = name;
visible.value = true;
}
this.loading = true;
let goods = this.orderGoods
let params = {
goodsId: goods.goodsId,
orderItemSn: goods.sn,
skuId: goods.skuId,
descriptionScore: this.form.descriptionScore,
serviceScore: this.form.serviceScore,
deliveryScore: this.form.deliveryScore,
grade: goods.grade,
content: goods.content.trim(),
images: goods.uploadList.length ? goods.uploadList.join(',') : ''
}
addEvaluation(params).then(res => {
this.loading = false
if (res.success) {
Message.success('评价成功')
this.$router.push('/home/CommentList')
} else {
Message.error(res.message || '评价失败,请稍后重试')
}
}).catch((err) => {
this.loading = false;
Message.error(err?.message || '评价失败,请稍后重试')
})
},
goGoodsDetail (skuId, goodsId) { // 跳转商品详情
let routerUrl = this.$router.resolve({
path: '/goodsDetail',
query: {skuId, goodsId}
})
window.open(routerUrl.href, '_blank')
},
handleView (name) { // 预览图片
this.previewImage = name;
this.visible = true;
},
handleRemove (index) { // 移除图片
this.orderGoods.uploadList.splice(index, 1)
this.$forceUpdate()
},
handleSuccess (res, file) { // 上传成功回调
this.orderGoods.uploadList.push(res.result)
this.$forceUpdate()
},
handleBeforeUpload () { // 上传之前钩子
const check = this.orderGoods.uploadList.length < 9;
if (!check) {
Notice.warning({
title: '最多可以上传九张图片'
});
return check;
}
}
},
mounted () {
window.scrollTo(0, 0)
this.accessToken.accessToken = storage.getItem('accessToken');
this.getOrderDetail()
function handleRemove (index) { // 移除图片
orderGoods.value.uploadList.splice(index, 1)
instance.proxy.$forceUpdate()
}
function handleSuccess (res, file) { // 上传成功回调
orderGoods.value.uploadList.push(res.result)
instance.proxy.$forceUpdate()
}
function handleBeforeUpload () { // 上传之前钩子
const check = orderGoods.value.uploadList.length < 9;
if (!check) {
Notice.warning({
title: '最多可以上传九张图片'
});
return check;
}
}
onMounted(() => {
window.scrollTo(0, 0)
accessToken.accessToken = storage.getItem('accessToken');
getOrderDetail()
})
</script>
<style lang="scss" scoped>
.delivery-rate {

View File

@@ -79,51 +79,50 @@
</UserCenterLayout>
</div>
</template>
<script>
<script setup>
import { computed, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { evaluationDetail } from "@/api/member.js";
export default {
data() {
return {
order: {}, // 订单详情
orderGoods: {}, // 订单商品
visible: false, // 图片预览
previewImage: "", // 预览图片链接
loading: false, // 加载状态
// el-rate SVG 通过 colors 写入 fill-color,跟随平台主题色
rateThemeColors: ["var(--theme-color)", "var(--theme-color)", "var(--theme-color)"],
};
},
computed: {
gradeText () {
const map = { GOOD: '好评', MODERATE: '中评', WORSE: '差评' }
return map[this.orderGoods.grade] || '评价详情'
}
},
methods: {
getDetail() {
// 获取评价详情
evaluationDetail(this.$route.query.id).then((res) => {
if (res.success) this.orderGoods = res.result;
});
},
goGoodsDetail(skuId, goodsId) {
// 跳转商品详情
let routerUrl = this.$router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routerUrl.href, "_blank");
},
handleView(name) {
// 预览图片
this.previewImage = name;
this.visible = true;
},
},
mounted() {
this.getDetail();
},
};
const route = useRoute();
const router = useRouter();
const order = ref({}); // 订单详情
const orderGoods = ref({}); // 订单商品
const visible = ref(false); // 图片预览
const previewImage = ref(""); // 预览图片链接
const loading = ref(false); // 加载状态
// el-rate SVG 通过 colors 写入 fill-color,跟随平台主题色
const rateThemeColors = ref(["var(--theme-color)", "var(--theme-color)", "var(--theme-color)"]);
const gradeText = computed(() => {
const map = { GOOD: '好评', MODERATE: '中评', WORSE: '差评' }
return map[orderGoods.value.grade] || '评价详情'
});
function getDetail() {
// 获取评价详情
evaluationDetail(route.query.id).then((res) => {
if (res.success) orderGoods.value = res.result;
});
}
function goGoodsDetail(skuId, goodsId) {
// 跳转商品详情
let routerUrl = router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routerUrl.href, "_blank");
}
function handleView(name) {
// 预览图片
previewImage.value = name;
visible.value = true;
}
onMounted(() => {
getDetail();
});
</script>
<style lang="scss" scoped>
.add-eval {

View File

@@ -22,86 +22,90 @@
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Message, Modal } from "@/utils/message";
import { delMemberMsg, getMemberMessage, readMemberMsg } from '@/api/member.js';
import { refreshUnreadMessage } from '@/utils/unreadMessage';
defineOptions({ name: 'MsgDetail' });
const route = useRoute();
const router = useRouter();
const CACHE_KEY = 'buyer-member-message';
export default {
name: 'MsgDetail',
data () {
return {
detail: {},
loaded: false
}
},
mounted () {
this.loadDetail()
},
methods: {
loadDetail () {
const id = this.$route.query.id
if (!id) {
this.loaded = true
return
}
const cached = this.readCache(id)
if (cached) this.detail = cached
getMemberMessage(id).then(res => {
if (res && res.success && res.result && res.result.id) {
this.detail = res.result
sessionStorage.setItem(CACHE_KEY, JSON.stringify(res.result))
}
}).catch((err) => {
console.warn('获取消息详情失败', err)
}).finally(() => {
this.loaded = true
this.markRead()
})
},
readCache (id) {
try {
const raw = sessionStorage.getItem(CACHE_KEY)
if (!raw) return null
const message = JSON.parse(raw)
return message && String(message.id) === String(id) ? message : null
} catch (e) {
return null
}
},
markRead () {
if (!this.detail.id || this.detail.status !== 'UN_READY') return
readMemberMsg(this.detail.id).then(res => {
if (res && res.success) {
this.detail.status = 'ALREADY_READY'
refreshUnreadMessage()
}
}).catch(() => {
// 标记已读为尽力而为操作,失败可忽略
})
},
goBack () {
this.$router.push({ name: 'MsgList' })
},
removeMessage () {
Modal.confirm({
title: '确认删除',
content: '确认要删除此消息?',
loading: true,
onOk: () =>
delMemberMsg(this.detail.id).then((res) => {
if (res.success) {
Message.success('消息已成功放入回收站')
refreshUnreadMessage()
this.goBack()
}
})
})
const detail = ref({});
const loaded = ref(false);
function loadDetail () {
const id = route.query.id
if (!id) {
loaded.value = true
return
}
const cached = readCache(id)
if (cached) detail.value = cached
getMemberMessage(id).then(res => {
if (res && res.success && res.result && res.result.id) {
detail.value = res.result
sessionStorage.setItem(CACHE_KEY, JSON.stringify(res.result))
}
}).catch((err) => {
console.warn('获取消息详情失败', err)
}).finally(() => {
loaded.value = true
markRead()
})
}
function readCache (id) {
try {
const raw = sessionStorage.getItem(CACHE_KEY)
if (!raw) return null
const message = JSON.parse(raw)
return message && String(message.id) === String(id) ? message : null
} catch (e) {
return null
}
}
function markRead () {
if (!detail.value.id || detail.value.status !== 'UN_READY') return
readMemberMsg(detail.value.id).then(res => {
if (res && res.success) {
detail.value.status = 'ALREADY_READY'
refreshUnreadMessage()
}
}).catch(() => {
// 标记已读为尽力而为操作,失败可忽略
})
}
function goBack () {
router.push({ name: 'MsgList' })
}
function removeMessage () {
Modal.confirm({
title: '确认删除',
content: '确认要删除此消息?',
loading: true,
onOk: () =>
delMemberMsg(detail.value.id).then((res) => {
if (res.success) {
Message.success('消息已成功放入回收站')
refreshUnreadMessage()
goBack()
}
})
})
}
onMounted(() => {
loadDetail()
})
</script>
<style lang="scss" scoped>

View File

@@ -47,85 +47,85 @@
</UserCenterLayout>
</div>
</template>
<script>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { Message, Modal } from "@/utils/message";
import {memberMsgList, readMemberMsg, delMemberMsg} from '@/api/member.js'
export default {
data() {
return {
messageData: {},
status: ['未读', '已读', '回收站'],
params: {
pageNumber: 1,
pageSize: 20,
status: 'UN_READY'
},
}
},
computed: {
activeStatusIndex () {
const map = ['UN_READY', 'ALREADY_READY', 'ALREADY_REMOVE'];
const index = map.indexOf(this.params.status);
return index > -1 ? index : 0;
}
},
methods: {
statusChange (index) {
if (index === 0) { this.params.status = 'UN_READY' }
if (index === 1) { this.params.status = 'ALREADY_READY' }
if (index === 2) { this.params.status = 'ALREADY_REMOVE' }
this.params.pageNumber = 1
this.getList()
},
changePage (v) {
this.params.pageNumber = v;
this.getList();
},
changePageSize (v) {
this.params.pageNumber = 1
this.params.pageSize = v;
this.getList();
},
openDetail (row) {
sessionStorage.setItem('buyer-member-message', JSON.stringify(row))
this.$router.push({ name: 'MsgDetail', query: { id: row.id } })
},
getList () {
memberMsgList(this.params).then(res => {
if (res.success) {
this.messageData = res.result;
if (this.params.status === 'UN_READY') {
this.$store.commit('SET_UNREAD_MSG', Number(res.result.total) || 0);
}
}
})
},
setRead (id) {
readMemberMsg(id).then(res => {
if (res.success) {
this.getList()
}
})
},
removeMessage (id) {
Modal.confirm({
title: '确认删除',
content: '确认要删除此消息?',
loading: true,
onOk: () =>
delMemberMsg(id).then((res) => {
if (res.success) {
Message.success('消息已成功放入回收站');
this.getList();
}
}),
});
}
},
mounted () {
this.getList()
}
import { useAppStore } from "@/stores/app";
const router = useRouter();
const appStore = useAppStore();
const messageData = ref({});
const status = ref(['未读', '已读', '回收站']);
const params = reactive({
pageNumber: 1,
pageSize: 20,
status: 'UN_READY'
});
const activeStatusIndex = computed(() => {
const map = ['UN_READY', 'ALREADY_READY', 'ALREADY_REMOVE'];
const index = map.indexOf(params.status);
return index > -1 ? index : 0;
});
function statusChange (index) {
if (index === 0) { params.status = 'UN_READY' }
if (index === 1) { params.status = 'ALREADY_READY' }
if (index === 2) { params.status = 'ALREADY_REMOVE' }
params.pageNumber = 1
getList()
}
function changePage (v) {
params.pageNumber = v;
getList();
}
function changePageSize (v) {
params.pageNumber = 1
params.pageSize = v;
getList();
}
function openDetail (row) {
sessionStorage.setItem('buyer-member-message', JSON.stringify(row))
router.push({ name: 'MsgDetail', query: { id: row.id } })
}
function getList () {
memberMsgList(params).then(res => {
if (res.success) {
messageData.value = res.result;
if (params.status === 'UN_READY') {
appStore.setUnreadMsg(Number(res.result.total) || 0);
}
}
})
}
function setRead (id) {
readMemberMsg(id).then(res => {
if (res.success) {
getList()
}
})
}
function removeMessage (id) {
Modal.confirm({
title: '确认删除',
content: '确认要删除此消息?',
loading: true,
onOk: () =>
delMemberMsg(id).then((res) => {
if (res.success) {
Message.success('消息已成功放入回收站');
getList();
}
}),
});
}
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped>
.msg-table {

View File

@@ -2,14 +2,14 @@
<div>
<UserCenterLayout>
<div class="add-box">
<el-form :model="formData" ref="form" label-position="left" label-width="100px" :rules="ruleInline">
<el-form :model="formData" ref="formRef" label-position="left" label-width="100px" :rules="ruleInline">
<el-form-item label="收件人" prop="name">
<el-input v-model="formData.name" placeholder="请输入收件人姓名" style="width: 600px"></el-input>
</el-form-item>
<el-form-item label="收件地区" prop="address">
<span>{{ formData.address || '暂无地址' }}</span>
<el-button type="default" style="margin-left: 10px;" size="small" @click="$refs.map.open()">选择</el-button>
<el-button type="default" style="margin-left: 10px;" size="small" @click="mapRef.open()">选择</el-button>
</el-form-item>
<el-form-item label="详细地址" prop="detail">
<el-input v-model="formData.detail" placeholder="请输入详细地址" style="width: 600px"></el-input>
@@ -32,11 +32,13 @@
</div>
</UserCenterLayout>
<multipleMap ref="map" @callback="getAddress" />
<multipleMap ref="mapRef" @callback="getAddress" />
</div>
</template>
<script>
<script setup>
import { onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import multipleMap from "@/components/map/multiple-map";
import * as RegExp from "@/plugins/RegExp.js";
import {
@@ -44,108 +46,107 @@ import {
editMemberAddress,
getAddrDetail,
} from "@/api/address";
export default {
name: "AddAddress",
data() {
return {
formData: {
// 添加地址表单
isDefault: false,
},
ruleInline: {
// 验证规则
name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
address: [{ required: true, message: "请输入地址", trigger: "change" }],
detail: [
{ required: true, message: "请输入详细地址", trigger: "blur" },
],
mobile: [
{ required: true, message: "请输入手机号码" },
{
pattern: RegExp.mobile,
trigger: "blur",
message: "请输入正确的手机号",
},
],
},
loading: false, // 加载状态
mapMsg: {}, // 地图信息
};
},
methods: {
save() {
// 保存地址
this.$refs.form.validate((valid) => {
if (valid) {
const params = JSON.parse(JSON.stringify(this.formData));
params.consigneeAddressPath = params.address.replace(/\s/g, ",");
delete params.address;
this.loading = true;
if (this.$route.query.id) {
editMemberAddress(params)
.then((res) => {
this.loading = false;
if (res.success) {
this.$router.push("/home/MyAddress");
}
})
.catch(() => {
this.loading = false;
});
} else {
newMemberAddress(params)
.then((res) => {
this.loading = false;
if (res.success) {
this.$router.push("/home/MyAddress");
}
})
.catch(() => {
this.loading = false;
});
}
}
});
},
getAddrById(id) {
// 获取地址详情
getAddrDetail(id).then((res) => {
if (res.success) {
const data = res.result;
data.address = res.result.consigneeAddressPath.replace(/,/g, " ");
this.formData = data;
}
});
},
getAddress(val) {
// 获取地图选择信息
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
this.formData['address'] = paths
this.formData['consigneeAddressIdPath'] = ids
const coord = val.data[val.data.length - 1].center.split(',')
this.formData.lat = coord[1]
this.formData.lon = coord[0]
}else{
this.formData["address"] = val.data.addr;
this.formData["consigneeAddressIdPath"] = val.data.addrId;
this.formData["detail"] = val.data.address;
this.formData.lat = val.data.position.lat;
this.formData.lon = val.data.position.lng;
}
defineOptions({ name: "AddAddress" });
const route = useRoute();
const router = useRouter();
const formRef = ref(null);
const mapRef = ref(null);
const formData = ref({
// 添加地址表单
isDefault: false,
});
const ruleInline = {
// 验证规则
name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
address: [{ required: true, message: "请输入地址", trigger: "change" }],
detail: [
{ required: true, message: "请输入详细地址", trigger: "blur" },
],
mobile: [
{ required: true, message: "请输入手机号码" },
{
pattern: RegExp.mobile,
trigger: "blur",
message: "请输入正确的手机号",
},
},
mounted() {
const id = this.$route.query.id;
if (id) this.getAddrById(id);
},
components: {
multipleMap
},
],
};
const loading = ref(false); // 加载状态
const mapMsg = ref({}); // 地图信息
// 保存地址
function save() {
formRef.value.validate((valid) => {
if (valid) {
const params = JSON.parse(JSON.stringify(formData.value));
params.consigneeAddressPath = params.address.replace(/\s/g, ",");
delete params.address;
loading.value = true;
if (route.query.id) {
editMemberAddress(params)
.then((res) => {
loading.value = false;
if (res.success) {
router.push("/home/MyAddress");
}
})
.catch(() => {
loading.value = false;
});
} else {
newMemberAddress(params)
.then((res) => {
loading.value = false;
if (res.success) {
router.push("/home/MyAddress");
}
})
.catch(() => {
loading.value = false;
});
}
}
});
}
function getAddrById(id) {
// 获取地址详情
getAddrDetail(id).then((res) => {
if (res.success) {
const data = res.result;
data.address = res.result.consigneeAddressPath.replace(/,/g, " ");
formData.value = data;
}
});
}
function getAddress(val) {
// 获取地图选择信息
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
formData.value['address'] = paths
formData.value['consigneeAddressIdPath'] = ids
const coord = val.data[val.data.length - 1].center.split(',')
formData.value.lat = coord[1]
formData.value.lon = coord[0]
}else{
formData.value["address"] = val.data.addr;
formData.value["consigneeAddressIdPath"] = val.data.addrId;
formData.value["detail"] = val.data.address;
formData.value.lat = val.data.position.lat;
formData.value.lon = val.data.position.lng;
}
}
onMounted(() => {
const id = route.query.id;
if (id) getAddrById(id);
});
</script>
<style scoped lang="scss">

View File

@@ -102,7 +102,7 @@
</div>
</div>
</div>
<el-form ref="form" :model="form" label-position="left" label-width="100px" :rules="rules">
<el-form ref="formRef" :model="form" label-position="left" label-width="100px" :rules="rules">
<el-form-item label="物流公司" prop="logisticsId">
<el-select v-model="form.logisticsId" placeholder="请选择物流公司">
<el-option v-for="item in companyList" :value="item.id" :key="item.id">{{ item.name }}</el-option>
@@ -131,170 +131,169 @@
</div>
</template>
<script>
<script setup>
import { nextTick, onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { Message, Modal } from "@/utils/message";
import {afterSaleList, cancelAfterSale} from '@/api/member.js';
import {afterSaleDelivery, getLogisticsCompany} from '@/api/order.js';
import {afterSaleStatusList} from '../enumeration.js'
export default {
name: 'AfterSale',
data() {
return {
orderList: [], // 订单列表
params: { // 请求参数
pageNumber: 1,
pageSize: 20,
keywords: '',
sn: '',
sort: 'createTime',
order: 'desc'
},
// 状态数组
afterSaleStatusList,
total: 0, // 订单总数
spinShow: false, // 加载状态
companyList: [], // 物流公司列表
logisticsShow: false, // 物流信息modal
singleOrder: {}, // 单独的售后信息
form: { // 物流信息数据
afterSaleSn: '',
logisticsId: '',
logisticsNo: '',
mDeliverTime: ''
},
rules: { // 必填校验
logisticsId: [{required: true, message: '请选择物流公司'}],
logisticsNo: [{required: true, message: '请填写物流编号'}],
mDeliverTime: [{required: true, message: '请选择发货时间'}]
},
submitLoading: false, // 提交加载状态
changeWay: [
{ name: '全部', status: '' },
{ name: '申请中', status: 'APPLY' },
{ name: '通过', status: 'PASS' },
{ name: '完成售后', status: 'COMPLETE' },
],
activeTab: 0,
};
},
mounted() {
this.getList();
},
methods: {
goDetail(sn) { // 跳转售后详情
this.$router.push({
name: 'AfterSaleDetail',
query: {sn}
})
},
cancel(sn) { // 取消售后申请
Modal.confirm({
title: '取消',
content: '确定取消此次售后申请吗?',
onOk: () => {
cancelAfterSale(sn).then(res => {
if (res.success) {
Message.success('取消售后申请成功')
this.getList()
}
})
},
onCancel: () => {
}
});
},
goodsDetail(skuId, goodsId) {
// 跳转商品详情
let routeUrl = this.$router.resolve({
path: '/goodsDetail',
query: {skuId, goodsId}
});
window.open(routeUrl.href, '_blank');
},
// 跳转店铺首页
shopPage(id) {
this.$router.push({
path: '/merchant',
query: {id: id}
});
},
change(index) {
this.activeTab = index
this.params.pageNumber = 1
const current = this.changeWay[index]
if (!current || !current.status) {
delete this.params.serviceStatus
} else {
this.params.serviceStatus = current.status
}
this.getList()
},
getList() { // 获取售后列表
this.spinShow = true;
let params = JSON.parse(JSON.stringify(this.params))
afterSaleList(params).then(res => {
this.spinShow = false
if (res.success) {
this.orderList = res.result.records;
this.total = res.result.total;
}
});
},
changePageNum(val) { // 修改页码
this.params.pageNumber = val;
this.getList()
},
changePageSize(val) { // 修改页数
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getList()
},
filterOrderStatus(status) { // 获取订单状态中文
const ob = this.afterSaleStatusList.filter(e => {
return e.status === status
});
return ob && ob[0] ? ob[0].name : status
},
// 获取物流公司列表
getCompany() {
getLogisticsCompany().then(res => {
if (res.success) {
this.companyList = res.result
}
})
},
// 提交物流信息
submitDelivery() {
this.$refs.form?.validate((valid) => {
if (!valid) return;
this.submitLoading = true;
afterSaleDelivery(this.form).then(res => {
if (res.success) {
this.logisticsShow = false;
Message.success('提交成功');
this.getList();
}
this.submitLoading = false;
}).catch(() => {
this.submitLoading = false;
});
});
},
// 提交物流modal
openModal(row) {
this.singleOrder = row;
this.logisticsShow = true;
if (!this.companyList.length) {
this.getCompany();
}
this.$nextTick(() => {
this.$refs.form?.resetFields();
this.form.afterSaleSn = row.sn;
});
}
}
defineOptions({ name: 'AfterSale' });
const router = useRouter();
const formRef = ref(null);
const orderList = ref([]); // 订单列表
const params = reactive({ // 请求参数
pageNumber: 1,
pageSize: 20,
keywords: '',
sn: '',
sort: 'createTime',
order: 'desc'
});
const total = ref(0); // 订单总数
const spinShow = ref(false); // 加载状态
const companyList = ref([]); // 物流公司列表
const logisticsShow = ref(false); // 物流信息modal
const singleOrder = ref({}); // 单独的售后信息
const form = reactive({ // 物流信息数据
afterSaleSn: '',
logisticsId: '',
logisticsNo: '',
mDeliverTime: ''
});
const rules = { // 必填校验
logisticsId: [{required: true, message: '请选择物流公司'}],
logisticsNo: [{required: true, message: '请填写物流编号'}],
mDeliverTime: [{required: true, message: '请选择发货时间'}]
};
const submitLoading = ref(false); // 提交加载状态
const changeWay = [
{ name: '全部', status: '' },
{ name: '申请中', status: 'APPLY' },
{ name: '通过', status: 'PASS' },
{ name: '完成售后', status: 'COMPLETE' },
];
const activeTab = ref(0);
function goDetail(sn) { // 跳转售后详情
router.push({
name: 'AfterSaleDetail',
query: {sn}
})
}
function cancel(sn) { // 取消售后申请
Modal.confirm({
title: '取消',
content: '确定取消此次售后申请吗?',
onOk: () => {
cancelAfterSale(sn).then(res => {
if (res.success) {
Message.success('取消售后申请成功')
getList()
}
})
},
onCancel: () => {
}
});
}
function goodsDetail(skuId, goodsId) {
// 跳转商品详情
let routeUrl = router.resolve({
path: '/goodsDetail',
query: {skuId, goodsId}
});
window.open(routeUrl.href, '_blank');
}
// 跳转店铺首页
function shopPage(id) {
router.push({
path: '/merchant',
query: {id: id}
});
}
function change(index) {
activeTab.value = index
params.pageNumber = 1
const current = changeWay[index]
if (!current || !current.status) {
delete params.serviceStatus
} else {
params.serviceStatus = current.status
}
getList()
}
function getList() { // 获取售后列表
spinShow.value = true;
let paramsData = JSON.parse(JSON.stringify(params))
afterSaleList(paramsData).then(res => {
spinShow.value = false
if (res.success) {
orderList.value = res.result.records;
total.value = res.result.total;
}
});
}
function changePageNum(val) { // 修改页码
params.pageNumber = val;
getList()
}
function changePageSize(val) { // 修改页数
params.pageNumber = 1;
params.pageSize = val;
getList()
}
function filterOrderStatus(status) { // 获取订单状态中文
const ob = afterSaleStatusList.filter(e => {
return e.status === status
});
return ob && ob[0] ? ob[0].name : status
}
// 获取物流公司列表
function getCompany() {
getLogisticsCompany().then(res => {
if (res.success) {
companyList.value = res.result
}
})
}
// 提交物流信息
function submitDelivery() {
formRef.value?.validate((valid) => {
if (!valid) return;
submitLoading.value = true;
afterSaleDelivery(form).then(res => {
if (res.success) {
logisticsShow.value = false;
Message.success('提交成功');
getList();
}
submitLoading.value = false;
}).catch(() => {
submitLoading.value = false;
});
});
}
// 提交物流modal
function openModal(row) {
singleOrder.value = row;
logisticsShow.value = true;
if (!companyList.value.length) {
getCompany();
}
nextTick(() => {
formRef.value?.resetFields();
form.afterSaleSn = row.sn;
});
}
onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">

View File

@@ -106,7 +106,7 @@
<span>提交物流信息</span>
</p></template>
<div>
<el-form ref="form" :model="form" label-position="left" label-width="100px" :rules="rules">
<el-form ref="formRef" :model="form" label-position="left" label-width="100px" :rules="rules">
<el-form-item label="物流公司" prop="logisticsId">
<el-select v-model="form.logisticsId" placeholder="请选择物流公司">
<el-option v-for="item in companyList" :value="item.id" :key="item.id">{{ item.name }}</el-option>
@@ -135,150 +135,151 @@
</UserCenterLayout>
</div>
</template>
<script>
<script setup>
import { computed, nextTick, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message, Modal } from "@/utils/message";
import { afterSaleDetail, afterSaleLog, afterSaleReason, cancelAfterSale } from '@/api/member.js';
import { afterSaleDelivery, getLogisticsCompany } from '@/api/order.js';
import { afterSaleStatusList } from '../enumeration.js'
export default {
name: 'aftersale-detail',
data () {
return {
afterSale: {}, // 售后详情数据
logList: [], // 日志
reasonList: [], // 售后原因列表
afterSaleStatusList, // 售后状态列表
companyList: [], // 物流公司列表
logisticsShow: false, // 物流信息modal
form: { // 物流信息数据
afterSaleSn: '',
logisticsId: '',
logisticsNo: '',
mDeliverTime: ''
},
rules: { // 必填校验
logisticsId: [{ required: true, message: '请选择物流公司' }],
logisticsNo: [{ required: true, message: '请填写物流编号' }],
mDeliverTime: [{ required: true, message: '请选择发货时间' }]
},
submitLoading: false // 提交加载状态
};
},
computed: {
canSubmitLogistics () {
return this.afterSale.serviceStatus == 'PASS' && this.afterSale.serviceType != 'RETURN_MONEY'
},
canCancel () {
return this.afterSale.afterSaleAllowOperationVO && this.afterSale.afterSaleAllowOperationVO.cancel
}
},
methods: {
getDetail () { // 售后详情
afterSaleDetail(this.$route.query.sn).then(res => {
if (res.success) {
this.afterSale = res.result;
this.afterSale.serviceName = this.filterOrderStatus(this.afterSale.serviceStatus)
// 如果是原因id,则调接口查询原因中文释义
const pattern3 = new RegExp('[0-9]+');
if (pattern3.test(this.afterSale.reason)) {
this.getReason(this.afterSale.serviceType)
} else {
this.afterSale['reasonName'] = this.afterSale.reason
}
}
})
},
getReason (type) { // 获取售后原因
afterSaleReason(type).then(res => {
if (res.success) {
this.reasonList = res.result
this.reasonList.forEach(e => {
if (e.id === this.afterSale.reason) {
this.afterSale['reasonName'] = e.reason
}
})
}
})
},
getLog () { // 获取售后日志
afterSaleLog(this.$route.query.sn).then(res => {
this.logList = (res.result || []).slice().sort((a, b) => {
return new Date(a.createTime).getTime() - new Date(b.createTime).getTime()
})
})
},
logDesc (log) {
const operator = log.operatorName ? `操作人:${log.operatorName}` : ''
return [operator, log.createTime].filter(Boolean).join(' ')
},
shopPage (id) {
if (!id) return
this.$router.push({
path: '/merchant',
query: { id }
})
},
filterOrderStatus (status) { // 获取订单状态中文
const ob = this.afterSaleStatusList.filter(e => { return e.status === status });
if (ob.length) return ob[0].name
},
perviewImg (img) { // 查看图片
window.open(img, '_blank')
},
cancel (sn) { // 取消售后申请
Modal.confirm({
title: '取消',
content: '<p>确定取消此次售后申请吗?</p>',
onOk: () => {
cancelAfterSale(sn).then(res => {
if (res.success) {
Message.success('取消售后申请成功')
this.getDetail()
}
})
},
onCancel: () => {}
});
},
// 获取物流公司列表
getCompany () {
getLogisticsCompany().then(res => {
if (res.success) {
this.companyList = res.result
}
})
},
// 提交物流信息
submitDelivery () {
this.submitLoading = true
afterSaleDelivery(this.form).then(res => {
if (res.success) {
this.logisticsShow = false;
Message.success('提交成功')
this.getDetail()
}
this.submitLoading = false
}).catch(() => {
this.submitLoading = false
})
},
// 提交物流modal
openModal () {
this.logisticsShow = true;
if (!this.companyList.length) {
this.getCompany();
}
this.$nextTick(() => {
this.$refs.form?.resetFields();
this.form.afterSaleSn = this.afterSale.sn;
});
}
},
mounted () {
this.getDetail();
this.getLog()
}
defineOptions({ name: 'aftersale-detail' });
const route = useRoute();
const router = useRouter();
const afterSale = ref({}); // 售后详情数据
const logList = ref([]); // 日志
const reasonList = ref([]); // 售后原因列表
const companyList = ref([]); // 物流公司列表
const logisticsShow = ref(false); // 物流信息modal
const form = reactive({ // 物流信息数据
afterSaleSn: '',
logisticsId: '',
logisticsNo: '',
mDeliverTime: ''
});
const rules = { // 必填校验
logisticsId: [{ required: true, message: '请选择物流公司' }],
logisticsNo: [{ required: true, message: '请填写物流编号' }],
mDeliverTime: [{ required: true, message: '请选择发货时间' }]
};
const submitLoading = ref(false); // 提交加载状态
const formRef = ref(null);
const canSubmitLogistics = computed(() => {
return afterSale.value.serviceStatus == 'PASS' && afterSale.value.serviceType != 'RETURN_MONEY'
});
const canCancel = computed(() => {
return afterSale.value.afterSaleAllowOperationVO && afterSale.value.afterSaleAllowOperationVO.cancel
});
function getDetail () { // 售后详情
afterSaleDetail(route.query.sn).then(res => {
if (res.success) {
afterSale.value = res.result;
afterSale.value.serviceName = filterOrderStatus(afterSale.value.serviceStatus)
// 如果是原因id,则调接口查询原因中文释义
const pattern3 = new RegExp('[0-9]+');
if (pattern3.test(afterSale.value.reason)) {
getReason(afterSale.value.serviceType)
} else {
afterSale.value['reasonName'] = afterSale.value.reason
}
}
})
}
function getReason (type) { // 获取售后原因
afterSaleReason(type).then(res => {
if (res.success) {
reasonList.value = res.result
reasonList.value.forEach(e => {
if (e.id === afterSale.value.reason) {
afterSale.value['reasonName'] = e.reason
}
})
}
})
}
function getLog () { // 获取售后日志
afterSaleLog(route.query.sn).then(res => {
logList.value = (res.result || []).slice().sort((a, b) => {
return new Date(a.createTime).getTime() - new Date(b.createTime).getTime()
})
})
}
function logDesc (log) {
const operator = log.operatorName ? `操作人:${log.operatorName}` : ''
return [operator, log.createTime].filter(Boolean).join(' ')
}
function shopPage (id) {
if (!id) return
router.push({
path: '/merchant',
query: { id }
})
}
function filterOrderStatus (status) { // 获取订单状态中文
const ob = afterSaleStatusList.filter(e => { return e.status === status });
if (ob.length) return ob[0].name
}
function perviewImg (img) { // 查看图片
window.open(img, '_blank')
}
function cancel (sn) { // 取消售后申请
Modal.confirm({
title: '取消',
content: '<p>确定取消此次售后申请吗?</p>',
onOk: () => {
cancelAfterSale(sn).then(res => {
if (res.success) {
Message.success('取消售后申请成功')
getDetail()
}
})
},
onCancel: () => {}
});
}
// 获取物流公司列表
function getCompany () {
getLogisticsCompany().then(res => {
if (res.success) {
companyList.value = res.result
}
})
}
// 提交物流信息
function submitDelivery () {
submitLoading.value = true
afterSaleDelivery(form).then(res => {
if (res.success) {
logisticsShow.value = false;
Message.success('提交成功')
getDetail()
}
submitLoading.value = false
}).catch(() => {
submitLoading.value = false
})
}
// 提交物流modal
function openModal () {
logisticsShow.value = true;
if (!companyList.value.length) {
getCompany();
}
nextTick(() => {
formRef.value?.resetFields();
form.afterSaleSn = afterSale.value.sn;
});
}
onMounted(() => {
getDetail();
getLog()
});
</script>
<style lang="scss" scoped>
.order-detail {

View File

@@ -18,7 +18,7 @@
<el-table-column prop="num" label="购买数量" />
</el-table>
<div>
<el-form :model="form" ref="form" class="mt_10" :rules="rules" label-width="80px">
<el-form :model="form" ref="formRef" class="mt_10" :rules="rules" label-width="80px">
<el-form-item label="售后类别">
<el-radio-group v-model="form.serviceType" @change="changeReason" type="button" button-style="solid">
<el-radio v-if="info.returnGoods" label="RETURN_GOODS">退货</el-radio>
@@ -101,125 +101,125 @@
</UserCenterLayout>
</div>
</template>
<script>
<script setup>
import { nextTick, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message, Notice } from "@/utils/message";
import { Camera, Delete, View } from '@element-plus/icons-vue';
import { afterSaleReason, afterSaleInfo, applyAfterSale } from '@/api/member.js'
import { buyerUrl } from '@/plugins/request.js';
import storage from '@/plugins/storage';
import * as RegExp from '@/plugins/RegExp'
export default {
components: { Camera, Delete, View },
data () {
const checkNum = (rule, value, callback) => {
if (value === '') {
callback(new Error('请填写提交数量'));
} else if (value > this.info.num) {
callback(new Error('提交数量不能大于购买数量'));
} else if (!RegExp.integer.test(value)) {
callback(new Error('提交数量必须为正整数'));
} else {
callback();
}
};
return {
goodsData: [], // 商品数据
reasonList: [], // 售后原因列表
info: {}, // 售后信息
form: { // 售后数据
serviceType: 'RETURN_GOODS',
num: 1
},
uploadList: [], // 上传列表
action: buyerUrl + '/buyer/common/upload/file', // 上传地址
accessToken: {}, // 验证token
visible: false, // 图片预览
previewImage: '', // 预览图片url
rules: { // 验证规则
num: [{ validator: checkNum }],
reason: [{ required: true, message: '请选择提交原因' }],
problemDesc: [{ required: true, message: '请输入问题描述' }],
bankAccountNumber: [
{ required: true, message: '请输入银行卡号' },
{
type: 'string',
pattern: /^[0-9]\d*$/,
message: '请输入正确的银行卡号'
}
],
bankAccountName: [{ required: true, message: '请输入银行开户名' }],
bankDepositName: [{ required: true, message: '请输入银行开户行' }]
}
const route = useRoute();
const router = useRouter();
const formRef = ref(null);
const goodsData = ref([]); // 商品数据
const reasonList = ref([]); // 售后原因列表
const info = ref({}); // 售后信息
const form = reactive({ // 售后数据
serviceType: 'RETURN_GOODS',
num: 1
});
const uploadList = ref([]); // 上传列表
const action = buyerUrl + '/buyer/common/upload/file'; // 上传地址
const accessToken = reactive({}); // 验证token
const visible = ref(false); // 图片预览
const previewImage = ref(''); // 预览图片url
const checkNum = (rule, value, callback) => {
if (value === '') {
callback(new Error('请填写提交数量'));
} else if (value > info.value.num) {
callback(new Error('提交数量不能大于购买数量'));
} else if (!RegExp.integer.test(value)) {
callback(new Error('提交数量必须为正整数'));
} else {
callback();
}
};
const rules = { // 验证规则
num: [{ validator: checkNum }],
reason: [{ required: true, message: '请选择提交原因' }],
problemDesc: [{ required: true, message: '请输入问题描述' }],
bankAccountNumber: [
{ required: true, message: '请输入银行卡号' },
{
type: 'string',
pattern: /^[0-9]\d*$/,
message: '请输入正确的银行卡号'
}
},
methods: {
getInfo () { // 获取售后信息
afterSaleInfo(this.$route.query.sn).then(res => {
],
bankAccountName: [{ required: true, message: '请输入银行开户名' }],
bankDepositName: [{ required: true, message: '请输入银行开户行' }]
};
function getInfo () { // 获取售后信息
afterSaleInfo(route.query.sn).then(res => {
if (res.success) {
info.value = res.result
goodsData.value.push(res.result)
if (!info.value.returnGoods && info.value.returnMoney) {
form.serviceType = 'RETURN_MONEY'
}
getReason(form.serviceType)
}
})
}
function getReason (type) { // 获取售后原因
afterSaleReason(type).then(res => {
if (res.success) reasonList.value = res.result
})
}
function changeReason (type) { // 改变售后原因列表
form.reason = undefined
getReason(type)
nextTick(() => {
formRef.value?.clearValidate('reason')
})
}
function apply () { // 售后申请提交
formRef.value.validate(valid => {
if (valid) {
let params = Object.assign(info.value, form)
params.images = uploadList.value.toString()
params.orderItemSn = route.query.sn
params.reason = reasonList.value.find(item => item.id == params.reason).reason
applyAfterSale(params).then(res => {
if (res.success) {
this.info = res.result
this.goodsData.push(res.result)
if (!this.info.returnGoods && this.info.returnMoney) {
this.form.serviceType = 'RETURN_MONEY'
}
this.getReason(this.form.serviceType)
Message.success('售后申请提交成功,请到售后订单查看!')
router.push({name: 'AfterSale'})
}
})
},
getReason (type) { // 获取售后原因
afterSaleReason(type).then(res => {
if (res.success) this.reasonList = res.result
})
},
changeReason (type) { // 改变售后原因列表
this.form.reason = undefined
this.getReason(type)
this.$nextTick(() => {
this.$refs.form?.clearValidate('reason')
})
},
apply () { // 售后申请提交
this.$refs.form.validate(valid => {
if (valid) {
let params = Object.assign(this.info, this.form)
params.images = this.uploadList.toString()
params.orderItemSn = this.$route.query.sn
params.reason = this.reasonList.find(item => item.id == params.reason).reason
applyAfterSale(params).then(res => {
if (res.success) {
Message.success('售后申请提交成功,请到售后订单查看!')
this.$router.push({name: 'AfterSale'})
}
})
}
})
},
handleView (name) { // 预览图片
this.previewImage = name;
this.visible = true;
},
handleRemove (index) { // 移除图片
this.uploadList.splice(index, 1)
this.$forceUpdate()
},
handleSuccess (res, file) { // 上传成功回调
this.uploadList.push(res.result)
this.$forceUpdate()
},
handleBeforeUpload () { // 上传之前钩子函数
const check = this.uploadList.length < 6;
if (!check) {
Notice.warning({
title: '最多可以上传5张图片'
});
return check;
}
}
},
mounted () {
this.accessToken.accessToken = storage.getItem('accessToken');
this.getInfo()
})
}
function handleView (name) { // 预览图片
previewImage.value = name;
visible.value = true;
}
function handleRemove (index) { // 移除图片
uploadList.value.splice(index, 1)
}
function handleSuccess (res, file) { // 上传成功回调
uploadList.value.push(res.result)
}
function handleBeforeUpload () { // 上传之前钩子函数
const check = uploadList.value.length < 6;
if (!check) {
Notice.warning({
title: '最多可以上传5张图片'
});
return check;
}
}
onMounted(() => {
accessToken.accessToken = storage.getItem('accessToken');
getInfo()
});
</script>
<style lang="scss" scoped>

View File

@@ -61,101 +61,107 @@
</UserCenterLayout>
</div>
</template>
<script>
<script setup>
import { getCurrentInstance, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message, Notice } from "@/utils/message";
import { Camera, Delete, View } from '@element-plus/icons-vue';
import { orderDetail } from '@/api/order.js';
import { afterSaleReason, handleComplain } from '@/api/member.js';
import { buyerUrl } from '@/plugins/request.js';
import storage from '@/plugins/storage';
export default {
components: { Camera, Delete, View },
data () {
return {
order: {}, // 订单详情
orderGoods: {}, // 订单商品
form: { // 投诉表单
complainTopic: '',
content: ''
}, // 表单
visible: false, // 图片预览
action: buyerUrl + '/buyer/common/upload/file', // 上传地址
accessToken: {}, // 验证token
previewImage: '', // 图片地址
loading: false, // 加载状态
reasonList: [] // 投诉原因
const route = useRoute();
const router = useRouter();
const instance = getCurrentInstance();
const order = ref({}); // 订单详情
const orderGoods = ref({}); // 订单商品
const form = reactive({ // 投诉表单
complainTopic: '',
content: ''
}); // 表单
const visible = ref(false); // 图片预览
const action = ref(buyerUrl + '/buyer/common/upload/file'); // 上传地址
const accessToken = reactive({}); // 验证token
const previewImage = ref(''); // 图片地址
const loading = ref(false); // 加载状态
const reasonList = ref([]); // 投诉原因
function getOrderDetail() { // 获取订单详情
orderDetail(route.query.sn).then(res => {
order.value = res.result
orderGoods.value = res.result.orderItems[route.query.index]
orderGoods.value.uploadList = []
})
afterSaleReason('COMPLAIN').then(res => {
if (res.success) {
reasonList.value = res.result
form['complainTopic'] = res.result[0].reason
instance.proxy.$forceUpdate()
}
},
methods: {
getOrderDetail () { // 获取订单详情
orderDetail(this.$route.query.sn).then(res => {
this.order = res.result
this.orderGoods = res.result.orderItems[this.$route.query.index]
this.orderGoods.uploadList = []
})
afterSaleReason('COMPLAIN').then(res => {
if (res.success) {
this.reasonList = res.result
this.form['complainTopic'] = res.result[0].reason
this.$forceUpdate()
}
})
},
save () { // 提交投诉信息
if (!this.form.content || !String(this.form.content).trim()) {
Message.warning('投诉内容不能为空')
return
}
let params = {
goodsId: this.orderGoods.goodsId,
complainTopic: this.form.complainTopic,
content: this.form.content,
images: this.orderGoods.uploadList.toString(),
orderSn: this.$route.query.sn,
skuId: this.orderGoods.skuId
}
handleComplain(params).then(res => {
if (res.success) {
Message.success('投诉申请已提交,感谢您的反馈')
this.$router.push({name: 'ComplainList'})
}
})
},
goGoodsDetail (skuId, goodsId) { // 跳转商品详情
let routerUrl = this.$router.resolve({
path: '/goodsDetail',
query: {skuId, goodsId}
})
window.open(routerUrl.href, '_blank')
},
handleView (name) { // 预览图片
this.previewImage = name;
this.visible = true;
},
handleRemove (index) { // 移除图片
this.orderGoods.uploadList.splice(index, 1)
this.$forceUpdate()
},
handleSuccess (res, file) { // 上传成功回调
this.orderGoods.uploadList.push(res.result)
this.$forceUpdate()
},
handleBeforeUpload () { // 上传之前钩子函数
const check = this.orderGoods.uploadList.length < 6;
if (!check) {
Notice.warning({
title: '最多可以上传五张图片'
});
return check;
}
})
}
function save() { // 提交投诉信息
if (!form.content || !String(form.content).trim()) {
Message.warning('投诉内容不能为空')
return
}
let params = {
goodsId: orderGoods.value.goodsId,
complainTopic: form.complainTopic,
content: form.content,
images: orderGoods.value.uploadList.toString(),
orderSn: route.query.sn,
skuId: orderGoods.value.skuId
}
handleComplain(params).then(res => {
if (res.success) {
Message.success('投诉申请已提交,感谢您的反馈')
router.push({name: 'ComplainList'})
}
},
mounted () {
this.accessToken.accessToken = storage.getItem('accessToken');
this.getOrderDetail()
window.scrollTo(0, 0)
})
}
function goGoodsDetail(skuId, goodsId) { // 跳转商品详情
let routerUrl = router.resolve({
path: '/goodsDetail',
query: {skuId, goodsId}
})
window.open(routerUrl.href, '_blank')
}
function handleView(name) { // 预览图片
previewImage.value = name;
visible.value = true;
}
function handleRemove(index) { // 移除图片
orderGoods.value.uploadList.splice(index, 1)
instance.proxy.$forceUpdate()
}
function handleSuccess(res, file) { // 上传成功回调
orderGoods.value.uploadList.push(res.result)
instance.proxy.$forceUpdate()
}
function handleBeforeUpload() { // 上传之前钩子函数
const check = orderGoods.value.uploadList.length < 6;
if (!check) {
Notice.warning({
title: '最多可以上传五张图片'
});
return check;
}
}
onMounted(() => {
accessToken.accessToken = storage.getItem('accessToken');
getOrderDetail()
window.scrollTo(0, 0)
})
</script>
<style lang="scss" scoped>
.delivery-rate {

View File

@@ -35,59 +35,59 @@
</div>
</template>
<script>
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { Message, Modal } from "@/utils/message";
import { Delete, Edit } from '@element-plus/icons-vue';
import { memberAddress, delMemberAddress } from '@/api/address.js';
export default {
components: { Delete, Edit },
name: 'MyAddress',
defineOptions({ name: 'MyAddress' });
data () {
return {
list: [] // 地址列表
};
},
methods: {
add () {
this.$router.push('/home/addAddress');
},
edit (id) {
// 编辑地址
this.$router.push({ path: '/home/addAddress', query: { id: id } });
},
del (id) {
// 删除地址
Modal.confirm({
title: '提示',
content: '你确定删除这个收货地址',
onOk: () => {
delMemberAddress(id).then((res) => {
if (res.success) {
Message.success('删除成功');
this.getAddrList();
}
});
},
onCancel: () => {
Message.info('取消删除');
}
});
},
getAddrList () {
// 获取地址列表
memberAddress().then((res) => {
const router = useRouter();
const list = ref([]); // 地址列表
function add () {
router.push('/home/addAddress');
}
// 编辑地址
function edit (id) {
router.push({ path: '/home/addAddress', query: { id: id } });
}
// 删除地址
function del (id) {
Modal.confirm({
title: '提示',
content: '你确定删除这个收货地址',
onOk: () => {
delMemberAddress(id).then((res) => {
if (res.success) {
this.list = res.result.records;
Message.success('删除成功');
getAddrList();
}
});
},
onCancel: () => {
Message.info('取消删除');
}
},
mounted () {
this.getAddrList();
}
};
});
}
// 获取地址列表
function getAddrList () {
memberAddress().then((res) => {
if (res.success) {
list.value = res.result.records;
}
});
}
onMounted(() => {
getAddrList();
});
</script>
<style scoped lang="scss">

View File

@@ -197,266 +197,264 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message, Modal } from "@/utils/message";
import { getOrderList, sureReceived, cancelOrder, delOrder } from '@/api/order';
import { getOrderList, sureReceived, cancelOrder, delOrder as deleteOrder } from '@/api/order';
import { afterSaleReason } from '@/api/member';
import { orderStatusList, afterSaleStatusList } from '../enumeration.js'
export default {
name: 'MyOrder',
props: {
homePage: { // 判断是否个人中心首页展示内容
type: Boolean,
default: false
}
},
data () {
return {
orderList: [], // 订单列表
params: { // 请求参数
pageNumber: 1,
pageSize: 20,
// orderStatus: 'ALL',
keywords: '',
tag: 'ALL'
},
cancelParams: { // 取消售后参数
orderSn: '',
reason: ''
},
// 状态数组
orderStatusList,
afterSaleStatusList,
changeWay: ['全部', '待付款', '待发货', '待收货/使用'],
activeOrderTab: 0,
total: 0, // 数据总数
spinShow: false, // 加载状态
afterSaleModal: false, // 选择售后商品模态框
afterSaleArr: [], // 售后商品列表
cancelAvail: false, // 取消订单modal控制
cancelReason: [] // 取消订单原因
};
},
mounted () {
if (this.homePage) this.params.pageSize = 5;
if (!this.homePage) this.applyRouteTag();
this.getList();
},
watch: {
'$route.query.tag' () {
if (this.homePage || this.$route.name !== 'MyOrder') return;
const next = this.$route.query.tag || 'ALL';
if (this.params.tag === next) return;
this.applyRouteTag();
this.getList();
}
},
methods: {
// 退款状态枚举
refundPriceList(status) {
switch (status) {
case 'ALL_REFUND':
return "全部退款";
case 'PART_REFUND':
return "部分退款";
case 'NO_REFUND':
return "";
case 'REFUNDING':
return "退款中";
default:
return "";
}
},
goodsDetail (skuId, goodsId) {
// 跳转商品详情
let routeUrl = this.$router.resolve({
path: '/goodsDetail',
query: { skuId, goodsId }
});
window.open(routeUrl.href, '_blank');
},
orderTags () {
return ['ALL', 'WAIT_PAY', 'WAIT_SHIP', 'WAIT_USE'];
},
applyRouteTag () {
const tags = this.orderTags();
const tag = this.$route.query.tag;
const index = tags.indexOf(tag);
this.activeOrderTab = index > -1 ? index : 0;
this.params.tag = tags[this.activeOrderTab];
this.params.pageNumber = 1;
},
// 切换订单状态
change (index) {
this.activeOrderTab = index;
this.params.pageNumber = 1;
delete this.params.commentStatus;
const tags = this.orderTags();
this.params.tag = tags[index] || 'ALL';
const query = { ...this.$route.query };
if (index === 0) delete query.tag;
else query.tag = this.params.tag;
const current = this.$route.query.tag || 'ALL';
if (current !== this.params.tag) {
this.$router.replace({ name: 'MyOrder', query });
}
this.getList()
},
// 跳转店铺首页
shopPage (id) {
this.$router.push({
path: '/merchant',
query: { id: id }
});
},
orderDetail (sn) {
this.$router.push({ name: 'OrderDetail', query: {sn} });
},
goAfterSaleDetail (sn) {
this.$router.push({ name: 'AfterSaleDetail', query: { sn } });
},
received (sn) { // 确认收货
sureReceived(sn).then(res => {
if (res.success) {
Message.success('确认收货成功')
this.getList()
}
})
},
goPay (sn) { // 去支付
this.$router.push({path: '/payment', query: {orderType: 'ORDER', sn}});
},
applyAfterSale (goodsItem) { // 申请售后
let arr = []
goodsItem.forEach(e => {
if (e.afterSaleStatus === 'NOT_APPLIED'|| e.afterSaleStatus === 'PART_AFTER_SALE') {
arr.push(e)
}
});
if (arr.length === 1) {
this.$router.push({name: 'ApplyAfterSale', query: {sn: arr[0].sn}})
} else {
this.afterSaleArr = arr;
this.afterSaleModal = true
}
},
// 申请售后
afterSaleSelect (item) {
this.$router.push({name: 'ApplyAfterSale', query: {sn: item.sn}})
},
complain (sn, goodsIndex) { // 投诉
this.$router.push({name: 'Complain', query: {sn, index: goodsIndex}})
},
delOrder (sn) { // 删除订单
Modal.confirm({
title: '删除订单',
content: '<p>确认删除当前订单吗?</p>',
onOk: () => {
delOrder(sn).then(res => {
if (res.success) {
Message.success('删除成功');
this.getList()
}
})
},
onCancel: () => {}
});
},
getList () {
this.spinShow = true;
let params = JSON.parse(JSON.stringify(this.params))
if (params.orderStatus === 'ALL') {
delete params.orderStatus
}
getOrderList(params).then(res => {
this.spinShow = false
if (res.success) {
this.orderList = res.result.records;
this.total = res.result.total;
}
});
},
mapAfterSaleOrder (item) {
return {
...item,
isAfterSale: true,
serviceSn: item.sn,
sn: item.orderSn,
orderStatus: item.serviceStatus,
flowPrice: item.applyRefundPrice,
orderItems: [
{
image: item.goodsImage,
name: item.goodsName,
skuId: item.skuId,
goodsId: item.goodsId,
goodsPrice: item.flowPrice,
num: item.num,
},
],
allowOperationVO: {},
}
},
changePageNum (val) { // 修改页码
this.params.pageNumber = val;
this.getList()
},
changePageSize (val) { // 修改页数
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getList()
},
handleCancelOrder (sn) {
// 取消订单
this.cancelParams.orderSn = sn;
afterSaleReason('CANCEL').then(res => {
if (res.success) {
this.cancelReason = res.result;
this.cancelAvail = true
this.cancelParams.reason = this.cancelReason[0].reason
}
})
},
sureCancel () { // 确定取消
cancelOrder(this.cancelParams).then(res => {
if (res.success) {
Message.success('取消订单成功')
this.getList()
this.cancelAvail = false
}
})
},
filterOrderStatus (status) { // 获取订单状态中文
const ob = this.orderStatusList.filter(e => { return e.status === status });
return ob && ob[0] ? ob[0].name : status
},
filterAfterSaleStatus (status) {
const ob = this.afterSaleStatusList.filter(e => e.status === status);
return ob && ob[0] ? ob[0].name : status
},
canAfterSale (order) {
if (order.isAfterSale || order.orderType === 'E_COUPON' || !order.groupAfterSaleStatus) {
return false
}
return order.groupAfterSaleStatus.includes('NOT_APPLIED') || order.groupAfterSaleStatus.includes('PART_AFTER_SALE')
},
paymentLabel (order) {
const map = {
WECHAT: '微信支付',
ALIPAY: '支付宝',
WALLET: '余额支付',
BANK_TRANSFER: '线下转账'
}
if (order.paymentMethod && map[order.paymentMethod]) {
return map[order.paymentMethod]
}
if (order.payStatus === 'UNPAID' || order.orderStatus === 'UNPAID') {
return '待支付'
}
return ''
}
defineOptions({ name: 'MyOrder' });
const props = defineProps({
homePage: { // 判断是否个人中心首页展示内容
type: Boolean,
default: false
}
};
});
const route = useRoute();
const router = useRouter();
const orderList = ref([]); // 订单列表
const params = reactive({ // 请求参数
pageNumber: 1,
pageSize: 20,
// orderStatus: 'ALL',
keywords: '',
tag: 'ALL'
});
const cancelParams = reactive({ // 取消售后参数
orderSn: '',
reason: ''
});
const changeWay = ref(['全部', '待付款', '待发货', '待收货/使用']);
const activeOrderTab = ref(0);
const total = ref(0); // 数据总数
const spinShow = ref(false); // 加载状态
const afterSaleModal = ref(false); // 选择售后商品模态框
const afterSaleArr = ref([]); // 售后商品列表
const cancelAvail = ref(false); // 取消订单modal控制
const cancelReason = ref([]); // 取消订单原因
// 退款状态枚举
function refundPriceList(status) {
switch (status) {
case 'ALL_REFUND':
return "全部退款";
case 'PART_REFUND':
return "部分退款";
case 'NO_REFUND':
return "";
case 'REFUNDING':
return "退款中";
default:
return "";
}
}
function goodsDetail (skuId, goodsId) {
// 跳转商品详情
let routeUrl = router.resolve({
path: '/goodsDetail',
query: { skuId, goodsId }
});
window.open(routeUrl.href, '_blank');
}
function orderTags () {
return ['ALL', 'WAIT_PAY', 'WAIT_SHIP', 'WAIT_USE'];
}
function applyRouteTag () {
const tags = orderTags();
const tag = route.query.tag;
const index = tags.indexOf(tag);
activeOrderTab.value = index > -1 ? index : 0;
params.tag = tags[activeOrderTab.value];
params.pageNumber = 1;
}
// 切换订单状态
function change (index) {
activeOrderTab.value = index;
params.pageNumber = 1;
delete params.commentStatus;
const tags = orderTags();
params.tag = tags[index] || 'ALL';
const query = { ...route.query };
if (index === 0) delete query.tag;
else query.tag = params.tag;
const current = route.query.tag || 'ALL';
if (current !== params.tag) {
router.replace({ name: 'MyOrder', query });
}
getList()
}
// 跳转店铺首页
function shopPage (id) {
router.push({
path: '/merchant',
query: { id: id }
});
}
function orderDetail (sn) {
router.push({ name: 'OrderDetail', query: {sn} });
}
function goAfterSaleDetail (sn) {
router.push({ name: 'AfterSaleDetail', query: { sn } });
}
function received (sn) { // 确认收货
sureReceived(sn).then(res => {
if (res.success) {
Message.success('确认收货成功')
getList()
}
})
}
function goPay (sn) { // 去支付
router.push({path: '/payment', query: {orderType: 'ORDER', sn}});
}
function applyAfterSale (goodsItem) { // 申请售后
let arr = []
goodsItem.forEach(e => {
if (e.afterSaleStatus === 'NOT_APPLIED'|| e.afterSaleStatus === 'PART_AFTER_SALE') {
arr.push(e)
}
});
if (arr.length === 1) {
router.push({name: 'ApplyAfterSale', query: {sn: arr[0].sn}})
} else {
afterSaleArr.value = arr;
afterSaleModal.value = true
}
}
// 申请售后
function afterSaleSelect (item) {
router.push({name: 'ApplyAfterSale', query: {sn: item.sn}})
}
function complain (sn, goodsIndex) { // 投诉
router.push({name: 'Complain', query: {sn, index: goodsIndex}})
}
function delOrder (sn) { // 删除订单
Modal.confirm({
title: '删除订单',
content: '<p>确认删除当前订单吗?</p>',
onOk: () => {
deleteOrder(sn).then(res => {
if (res.success) {
Message.success('删除成功');
getList()
}
})
},
onCancel: () => {}
});
}
function getList () {
spinShow.value = true;
let _params = JSON.parse(JSON.stringify(params))
if (_params.orderStatus === 'ALL') {
delete _params.orderStatus
}
getOrderList(_params).then(res => {
spinShow.value = false
if (res.success) {
orderList.value = res.result.records;
total.value = res.result.total;
}
});
}
function mapAfterSaleOrder (item) {
return {
...item,
isAfterSale: true,
serviceSn: item.sn,
sn: item.orderSn,
orderStatus: item.serviceStatus,
flowPrice: item.applyRefundPrice,
orderItems: [
{
image: item.goodsImage,
name: item.goodsName,
skuId: item.skuId,
goodsId: item.goodsId,
goodsPrice: item.flowPrice,
num: item.num,
},
],
allowOperationVO: {},
}
}
function changePageNum (val) { // 修改页码
params.pageNumber = val;
getList()
}
function changePageSize (val) { // 修改页数
params.pageNumber = 1;
params.pageSize = val;
getList()
}
function handleCancelOrder (sn) {
// 取消订单
cancelParams.orderSn = sn;
afterSaleReason('CANCEL').then(res => {
if (res.success) {
cancelReason.value = res.result;
cancelAvail.value = true
cancelParams.reason = cancelReason.value[0].reason
}
})
}
function sureCancel () { // 确定取消
cancelOrder(cancelParams).then(res => {
if (res.success) {
Message.success('取消订单成功')
getList()
cancelAvail.value = false
}
})
}
function filterOrderStatus (status) { // 获取订单状态中文
const ob = orderStatusList.filter(e => { return e.status === status });
return ob && ob[0] ? ob[0].name : status
}
function filterAfterSaleStatus (status) {
const ob = afterSaleStatusList.filter(e => e.status === status);
return ob && ob[0] ? ob[0].name : status
}
function canAfterSale (order) {
if (order.isAfterSale || order.orderType === 'E_COUPON' || !order.groupAfterSaleStatus) {
return false
}
return order.groupAfterSaleStatus.includes('NOT_APPLIED') || order.groupAfterSaleStatus.includes('PART_AFTER_SALE')
}
function paymentLabel (order) {
const map = {
WECHAT: '微信支付',
ALIPAY: '支付宝',
WALLET: '余额支付',
BANK_TRANSFER: '线下转账'
}
if (order.paymentMethod && map[order.paymentMethod]) {
return map[order.paymentMethod]
}
if (order.payStatus === 'UNPAID' || order.orderStatus === 'UNPAID') {
return '待支付'
}
return ''
}
watch(() => route.query.tag, () => {
if (props.homePage || route.name !== 'MyOrder') return;
const next = route.query.tag || 'ALL';
if (params.tag === next) return;
applyRouteTag();
getList();
});
onMounted(() => {
if (props.homePage) params.pageSize = 5;
if (!props.homePage) applyRouteTag();
getList();
});
</script>
<style scoped lang="scss">

View File

@@ -410,7 +410,9 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "@/utils/message";
import {
orderDetail,
@@ -438,276 +440,278 @@ import {
* 订单详情:E_COUPON 展示 orderItems[].cardKeys,屏蔽售后/物流(FR-B-03 / FR-B-04)。
* 移动端已完成订单用 Dialog 查看卡密(原型 P-09 / D-01)。
*/
export default {
name: "order-detail",
data() {
return {
order: {}, // 订单详情数据
progressList: [], // 订单流程
logistics: "", // 物流数据
cardKeyDialogVisible: false,
cancelParams: {
// 取消售后参数
orderSn: "",
reason: "",
},
cancelAvail: false, // 取消订单modal控制
cancelReason: [], // 取消订单原因
orderPackage: [],
packageTraceList: [],
logisticsModal: false,
};
},
computed: {
/** 电子卡券订单:无物流/核验码,卡密数据来自 API-B-01 orderItems[].cardKeys */
isECouponOrder() {
return checkECouponOrder(this.order?.order?.orderType);
},
isVirtualOrder() {
return this.order?.order?.orderType === "VIRTUAL";
},
isNonPhysicalOrder() {
return this.isVirtualOrder || this.isECouponOrder;
},
/** 是否展示卡密区块(仅当前订单 orderItems) */
hasCardKeySection() {
return orderHasCardKeySection({
orderItems: this.order.orderItems,
isECouponOrder: this.isECouponOrder,
});
},
cardKeyFulfillLines() {
return collectCardKeyFulfillLines(this.order.orderItems);
},
ecouponCardKeyRows() {
return flattenOrderCardKeys(this.order.orderItems, false);
},
/** 商家以无需配送方式发货(包裹 fulfillmentMode=NO_DELIVERY) */
isNoDeliveryOrder() {
return checkNoDeliveryOrder(this.orderPackage);
},
displayDeliveryMethodValue() {
if (this.isNoDeliveryOrder) return "无需配送";
return this.order?.deliveryMethodValue || "-";
},
noDeliveryPackages() {
return (this.orderPackage || []).filter(isNoDeliveryPackage);
},
noDeliveryRemark() {
const pkg = this.noDeliveryPackages.find((p) => p.deliveryRemark);
return pkg?.deliveryRemark || "";
},
noDeliveryShipTime() {
const pkg = this.noDeliveryPackages[0];
return pkg?.logisticsTime || "";
},
},
methods: {
isNoDeliveryPackage,
getFulfillmentModeLabel,
cardKeyFulfillAlertType,
resolveCardKeyFulfillMessage,
lineCardKeyRows(line) {
return line.cardKeys || [];
},
copyCardKey(row) {
const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`;
this.copyText(text);
},
copyAllCardKeys() {
const text = this.ecouponCardKeyRows
.map((row, i) => `${i + 1}. 卡号:${row.cardNo || ""} 卡密:${row.cardSecret || ""}`)
.join("\n");
this.copyText(text || "");
},
copyText(text) {
if (!text) return;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
Message.success("已复制到剪贴板");
});
} else {
const ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
Message.success("已复制到剪贴板");
defineOptions({ name: "order-detail" });
const route = useRoute();
const router = useRouter();
const order = ref({}); // 订单详情数据
const progressList = ref([]); // 订单流程
const logistics = ref(""); // 物流数据
const cardKeyDialogVisible = ref(false);
const cancelParams = reactive({
// 取消售后参数
orderSn: "",
reason: "",
});
const cancelAvail = ref(false); // 取消订单modal控制
const cancelReason = ref([]); // 取消订单原因
const orderPackage = ref([]);
const packageTraceList = ref([]);
const logisticsModal = ref(false);
/** 电子卡券订单:无物流/核验码,卡密数据来自 API-B-01 orderItems[].cardKeys */
const isECouponOrder = computed(() => checkECouponOrder(order.value?.order?.orderType));
const isVirtualOrder = computed(() => order.value?.order?.orderType === "VIRTUAL");
const isNonPhysicalOrder = computed(() => isVirtualOrder.value || isECouponOrder.value);
/** 是否展示卡密区块(仅当前订单 orderItems) */
const hasCardKeySection = computed(() =>
orderHasCardKeySection({
orderItems: order.value.orderItems,
isECouponOrder: isECouponOrder.value,
})
);
const cardKeyFulfillLines = computed(() => collectCardKeyFulfillLines(order.value.orderItems));
const ecouponCardKeyRows = computed(() => flattenOrderCardKeys(order.value.orderItems, false));
/** 商家以无需配送方式发货(包裹 fulfillmentMode=NO_DELIVERY) */
const isNoDeliveryOrder = computed(() => checkNoDeliveryOrder(orderPackage.value));
const displayDeliveryMethodValue = computed(() => {
if (isNoDeliveryOrder.value) return "无需配送";
return order.value?.deliveryMethodValue || "-";
});
const noDeliveryPackages = computed(() => (orderPackage.value || []).filter(isNoDeliveryPackage));
const noDeliveryRemark = computed(() => {
const pkg = noDeliveryPackages.value.find((p) => p.deliveryRemark);
return pkg?.deliveryRemark || "";
});
const noDeliveryShipTime = computed(() => {
const pkg = noDeliveryPackages.value[0];
return pkg?.logisticsTime || "";
});
function lineCardKeyRows(line) {
return line.cardKeys || [];
}
function copyCardKey(row) {
const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`;
copyText(text);
}
function copyAllCardKeys() {
const text = ecouponCardKeyRows.value
.map((row, i) => `${i + 1}. 卡号:${row.cardNo || ""} 卡密:${row.cardSecret || ""}`)
.join("\n");
copyText(text || "");
}
function copyText(text) {
if (!text) return;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
Message.success("已复制到剪贴板");
});
} else {
const ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
Message.success("已复制到剪贴板");
}
}
function isVatSpecialReceipt(receipt) {
if (!receipt) return false;
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
if (rt === "2" || rt === "增值税专用发票" || receipt.invoiceKind === "VAT_SPECIAL") {
return true;
}
return false;
}
function isPersonalReceipt(receipt) {
return !isVatSpecialReceipt(receipt);
}
function formatReceiptType(receipt) {
if (!receipt) return "";
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
if (rt === "电子普通发票" || rt === "增值税专用发票") return rt;
return isVatSpecialReceipt(receipt) ? "增值税专用发票" : "电子普通发票";
}
function formatReceiptHeaderType(receipt) {
if (!receipt) return "";
if (isVatSpecialReceipt(receipt)) return "单位";
if (receipt.taxpayerId) return "单位";
return "个人";
}
function hasValidReceipt() {
const receipt = order.value && order.value.receipt;
if (!receipt) return false;
const content = receipt.receiptContent ? String(receipt.receiptContent).trim() : '';
const title = receipt.receiptTitle ? String(receipt.receiptTitle).trim() : '';
const taxpayerId = receipt.taxpayerId ? String(receipt.taxpayerId).trim() : '';
// 后端有时会返回占位 receipt(如 receiptContent=不开发票),这里过滤掉
if (content === '不开发票') return false;
return Boolean(content || title || taxpayerId);
}
function getInvoiceAddress(receipt) {
if (!receipt) return "";
return receipt.invoiceAddress || receipt.invoiceFileUrl || "";
}
function viewReceiptInvoice(receipt) {
const invoiceAddress = getInvoiceAddress(receipt);
if (!invoiceAddress) {
Message.warning("暂无发票附件");
return;
}
window.open(invoiceAddress, "_blank");
}
function goodsDetail(skuId, goodsId) {
// 跳转商品详情
let routeUrl = router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routeUrl.href, "_blank");
}
// 跳转店铺首页
function shopPage(id) {
router.push({
path: "/merchant",
query: { id: id },
});
}
async function getDetail() {
// 获取订单详情
orderDetail(route.query.sn).then(async (res) => {
if (res.success) {
order.value = res.result;
const receiptId =
(res.result.order && res.result.order.receiptId) ||
res.result.receiptId ||
(res.result.receipt && res.result.receipt.id);
if (receiptId) {
const receiptRes = await receiptDetail(receiptId);
if (receiptRes && receiptRes.success && receiptRes.result) {
order.value["receipt"] = receiptRes.result;
}
}
},
isVatSpecialReceipt (receipt) {
if (!receipt) return false;
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
if (rt === "2" || rt === "增值税专用发票" || receipt.invoiceKind === "VAT_SPECIAL") {
return true;
progressList.value = (res.result.orderLogs || []).slice().sort((a, b) => {
return new Date(a.createTime).getTime() - new Date(b.createTime).getTime();
});
if (order.value.order.deliveryMethod === 'LOGISTICS') {
getOrderPackage(order.value.order.sn);
}
return false;
},
isPersonalReceipt (receipt) {
return !this.isVatSpecialReceipt(receipt);
},
formatReceiptType (receipt) {
if (!receipt) return "";
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
if (rt === "电子普通发票" || rt === "增值税专用发票") return rt;
return this.isVatSpecialReceipt(receipt) ? "增值税专用发票" : "电子普通发票";
},
formatReceiptHeaderType (receipt) {
if (!receipt) return "";
if (this.isVatSpecialReceipt(receipt)) return "单位";
if (receipt.taxpayerId) return "单位";
return "个人";
},
hasValidReceipt () {
const receipt = this.order && this.order.receipt;
if (!receipt) return false;
const content = receipt.receiptContent ? String(receipt.receiptContent).trim() : '';
const title = receipt.receiptTitle ? String(receipt.receiptTitle).trim() : '';
const taxpayerId = receipt.taxpayerId ? String(receipt.taxpayerId).trim() : '';
// 后端有时会返回占位 receipt(如 receiptContent=不开发票),这里过滤掉
if (content === '不开发票') return false;
return Boolean(content || title || taxpayerId);
},
getInvoiceAddress (receipt) {
if (!receipt) return "";
return receipt.invoiceAddress || receipt.invoiceFileUrl || "";
},
viewReceiptInvoice (receipt) {
const invoiceAddress = this.getInvoiceAddress(receipt);
if (!invoiceAddress) {
Message.warning("暂无发票附件");
return;
}
});
}
function getOrderPackage(sn) {
getPackage(sn).then(res => {
if (res.success) {
orderPackage.value = Array.isArray(res.result) ? res.result : [];
if (!orderPackage.value.length) {
traces();
}
window.open(invoiceAddress, "_blank");
},
goodsDetail(skuId, goodsId) {
// 跳转商品详情
let routeUrl = this.$router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routeUrl.href, "_blank");
},
// 跳转店铺首页
shopPage(id) {
this.$router.push({
path: "/merchant",
query: { id: id },
});
},
async getDetail() {
// 获取订单详情
orderDetail(this.$route.query.sn).then(async (res) => {
if (res.success) {
this.order = res.result;
const receiptId =
(res.result.order && res.result.order.receiptId) ||
res.result.receiptId ||
(res.result.receipt && res.result.receipt.id);
if (receiptId) {
const receiptRes = await receiptDetail(receiptId);
if (receiptRes && receiptRes.success && receiptRes.result) {
this.order["receipt"] = receiptRes.result;
}
}
this.progressList = (res.result.orderLogs || []).slice().sort((a, b) => {
return new Date(a.createTime).getTime() - new Date(b.createTime).getTime();
});
if (this.order.order.deliveryMethod === 'LOGISTICS') {
this.getOrderPackage(this.order.order.sn);
}
}
});
},
getOrderPackage(sn) {
getPackage(sn).then(res => {
if (res.success) {
this.orderPackage = Array.isArray(res.result) ? res.result : [];
if (!this.orderPackage.length) {
this.traces();
}
} else {
this.traces();
}
}).catch(() => {
this.traces();
});
},
traces() {
// 物流信息
getTraces(this.$route.query.sn).then((res) => {
if (res.success) {
this.logistics = res.result;
}
});
},
logisticsList() {
this.logisticsModal = true;
this.packageTraceList = this.orderPackage;
// getTracesList(this.order.order.sn).then((res) => {
// if (res.success && res.result != null) {
// this.packageTraceList = res.result;
// }
// });
},
received(sn) {
// 确认收货
sureReceived(sn).then((res) => {
if (res.success) {
Message.success("确认收货成功");
this.getDetail();
}
});
},
goPay(sn) {
// 去支付
this.$router.push({
path: "/payment",
query: { orderType: "ORDER", sn },
});
},
applyAfterSale(sn) {
// 申请售后
this.$router.push({ name: "ApplyAfterSale", query: { sn: sn } });
},
comment(sn, goodsIndex) {
// 评价
this.$router.push({
path: "/home/addEval",
query: { sn, index: goodsIndex },
});
},
complain(sn, goodsIndex) {
// 投诉
this.$router.push({ name: "Complain", query: { sn, index: goodsIndex } });
},
handleCancelOrder(sn) {
// 取消订单
this.cancelParams.orderSn = sn;
afterSaleReason("CANCEL").then((res) => {
if (res.success) {
this.cancelReason = res.result;
this.cancelAvail = true;
this.cancelParams.reason = this.cancelReason[0].reason;
}
});
},
sureCancel() {
// 确定取消
cancelOrder(this.cancelParams).then((res) => {
if (res.success) {
Message.success("取消订单成功");
this.getDetail();
this.cancelAvail = false;
}
});
},
},
mounted() {
this.getDetail();
},
};
} else {
traces();
}
}).catch(() => {
traces();
});
}
function traces() {
// 物流信息
getTraces(route.query.sn).then((res) => {
if (res.success) {
logistics.value = res.result;
}
});
}
function logisticsList() {
logisticsModal.value = true;
packageTraceList.value = orderPackage.value;
// getTracesList(this.order.order.sn).then((res) => {
// if (res.success && res.result != null) {
// this.packageTraceList = res.result;
// }
// });
}
function received(sn) {
// 确认收货
sureReceived(sn).then((res) => {
if (res.success) {
Message.success("确认收货成功");
getDetail();
}
});
}
function goPay(sn) {
// 去支付
router.push({
path: "/payment",
query: { orderType: "ORDER", sn },
});
}
function applyAfterSale(sn) {
// 申请售后
router.push({ name: "ApplyAfterSale", query: { sn: sn } });
}
function comment(sn, goodsIndex) {
// 评价
router.push({
path: "/home/addEval",
query: { sn, index: goodsIndex },
});
}
function complain(sn, goodsIndex) {
// 投诉
router.push({ name: "Complain", query: { sn, index: goodsIndex } });
}
function handleCancelOrder(sn) {
// 取消订单
cancelParams.orderSn = sn;
afterSaleReason("CANCEL").then((res) => {
if (res.success) {
cancelReason.value = res.result;
cancelAvail.value = true;
cancelParams.reason = cancelReason.value[0].reason;
}
});
}
function sureCancel() {
// 确定取消
cancelOrder(cancelParams).then((res) => {
if (res.success) {
Message.success("取消订单成功");
getDetail();
cancelAvail.value = false;
}
});
}
onMounted(() => {
getDetail();
});
</script>
<style lang="scss" scoped>
.mb_10 {

View File

@@ -50,125 +50,128 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { memberCouponList } from "@/api/member.js";
export default {
name: "Coupons",
data() {
return {
statusNameList: [
// 优惠券状态
"未使用",
"已使用",
"已过期",
],
statusList: ["NEW", "USED", "EXPIRE"], // 优惠券状态
loading: false, // 列表加载状态
params: {
// 请求参数
pageNumber: 1,
pageSize: 20,
memberCouponStatus: "NEW",
},
total: 0, // 优惠券总数
list: [], // 优惠券列表
};
},
computed: {
activeStatusIndex() {
const index = this.statusList.indexOf(this.params.memberCouponStatus);
return index > -1 ? index : 0;
},
},
methods: {
getList() {
// 获取优惠券列表
this.loading = true;
memberCouponList(this.params)
.then((res) => {
this.loading = false;
if (res.success) {
this.list = res.result.records || [];
this.total = res.result.total || 0;
}
})
.catch(() => {
this.loading = false;
this.list = [];
this.total = 0;
});
},
// 切换优惠券状态
change(index) {
this.params.memberCouponStatus = this.statusList[index];
this.params.pageNumber = 1;
this.getList();
},
go(item) {
// 根据使用条件跳转商品列表页面
if (this.params.memberCouponStatus !== "NEW") return;
this.$router.push({
path: "/goodsList",
query: { promotionsId: item.couponId, promotionType: "COUPON" },
});
},
defineOptions({ name: "Coupons" });
changePageNum(val) {
// 分页改变页码
this.params.pageNumber = val;
this.getList();
},
const router = useRouter();
changePageSize(val) {
// 分页改变页数
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getList();
},
const statusNameList = ref([
// 优惠券状态
"未使用",
"已使用",
"已过期",
]);
const statusList = ref(["NEW", "USED", "EXPIRE"]); // 优惠券状态
const loading = ref(false); // 列表加载状态
const params = reactive({
// 请求参数
pageNumber: 1,
pageSize: 20,
memberCouponStatus: "NEW",
});
const total = ref(0); // 优惠券总数
const list = ref([]); // 优惠券列表
useScope(type, storeName) {
// 根据字段返回 优惠券适用范围
let shop = "平台";
let goods = "全部商品";
if (storeName !== "platform") shop = storeName;
switch (type) {
case "ALL":
goods = "全部商品";
break;
case "PORTION_GOODS":
goods = "部分商品";
break;
case "PORTION_GOODS_CATEGORY":
goods = "部分分类商品";
break;
const activeStatusIndex = computed(() => {
const index = statusList.value.indexOf(params.memberCouponStatus);
return index > -1 ? index : 0;
});
// 获取优惠券列表
function getList() {
loading.value = true;
memberCouponList(params)
.then((res) => {
loading.value = false;
if (res.success) {
list.value = res.result.records || [];
total.value = res.result.total || 0;
}
return `${shop}${goods}可用`;
},
formatValidity(item) {
const start = this.formatCouponTime(item && item.startTime);
const end = this.formatCouponTime(item && item.endTime);
if (start && end) return `${start} 至 ${end}`;
if (end) return `至 ${end}`;
if (start) return `${start} 起`;
return "长期有效";
},
formatCouponTime(value) {
if (!value) return "";
const text = String(value).trim();
if (/^\d{4}-\d{2}-\d{2}/.test(text)) {
return text.slice(0, 16);
}
const date = new Date(text);
if (Number.isNaN(date.getTime())) return text;
const pad = (n) => String(n).padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
},
},
mounted() {
this.getList();
},
};
})
.catch(() => {
loading.value = false;
list.value = [];
total.value = 0;
});
}
// 切换优惠券状态
function change(index) {
params.memberCouponStatus = statusList.value[index];
params.pageNumber = 1;
getList();
}
// 根据使用条件跳转商品列表页面
function go(item) {
if (params.memberCouponStatus !== "NEW") return;
router.push({
path: "/goodsList",
query: { promotionsId: item.couponId, promotionType: "COUPON" },
});
}
// 分页改变页码
function changePageNum(val) {
params.pageNumber = val;
getList();
}
// 分页改变页数
function changePageSize(val) {
params.pageNumber = 1;
params.pageSize = val;
getList();
}
// 根据字段返回 优惠券适用范围
function useScope(type, storeName) {
let shop = "平台";
let goods = "全部商品";
if (storeName !== "platform") shop = storeName;
switch (type) {
case "ALL":
goods = "全部商品";
break;
case "PORTION_GOODS":
goods = "部分商品";
break;
case "PORTION_GOODS_CATEGORY":
goods = "部分分类商品";
break;
}
return `${shop}${goods}可用`;
}
function formatValidity(item) {
const start = formatCouponTime(item && item.startTime);
const end = formatCouponTime(item && item.endTime);
if (start && end) return `${start} 至 ${end}`;
if (end) return `至 ${end}`;
if (start) return `${start} 起`;
return "长期有效";
}
function formatCouponTime(value) {
if (!value) return "";
const text = String(value).trim();
if (/^\d{4}-\d{2}-\d{2}/.test(text)) {
return text.slice(0, 16);
}
const date = new Date(text);
if (Number.isNaN(date.getTime())) return text;
const pad = (n) => String(n).padStart(2, "0");
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">

View File

@@ -23,7 +23,7 @@
</p></template>
<div>
<el-form
ref="formData"
ref="formDataRef"
label-width="100px"
:model="formData"
:rules="formValidate"
@@ -47,7 +47,7 @@
</p></template>
<div>
<el-form
ref="withdrawApplyFormData"
ref="withdrawApplyFormDataRef"
label-width="120px"
:model="withdrawApplyFormData"
:rules="withdrawApplyFormValidate"
@@ -180,229 +180,230 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { Message } from "@/utils/message";
import {getDepositLog, getMembersWallet, getRecharge, getWithdrawApply, recharge, withdrawalApply} from '@/api/member';
import {getDepositLog, getMembersWallet, getRecharge, getWithdrawApply, recharge as apiRecharge, withdrawalApply as apiWithdrawalApply} from '@/api/member';
import {withdrawalSettingVO} from "@/api/pay";
import { Edit } from '@element-plus/icons-vue';
export default {
name: 'MoneyManagement',
components: { Edit },
data() {
return {
activeWalletTab: "log",
frozenDeposit: 0, // 冻结余额
memberDeposit: 0, // 余额
defineOptions({ name: 'MoneyManagement' });
modal: false, // 余额充值
withdrawApplyModal: false, // 提现申请
formData: {
// 充值金额
price: 1
},
// 提现金额
withdrawApplyFormData: {
price: 1,
minPrice: 1,
type: '',
realName: '',
connectNumber: '',
},
// 余额日志
walletForm: {
// 搜索框初始化对象
pageNumber: 1,
pageSize: 20
},
// 充值记录
rechargeForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20 // 页面大小
},
// 提现记录
withdrawApplyForm: {
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20 // 页面大小
},
// 提现申请校验
withdrawApplyFormValidate: {
price: [
{required: true, message: '请输入大于0小于9999的合法提现金额'},
{
pattern: /^[1-9]\d{0,3}(\.\d{1,2})?$/,
message: '请输入大于0小于9999的合法提现金额',
trigger: 'change'
}
],
realName: [
{required: true, message: '请输入真实姓名'},
],
connectNumber: [
{required: true, message: '请输入第三方登录账号'},
],
},
formValidate: {
price: [
{required: true, message: '请输入大于等于1小于9999的合法充值金额'},
{
pattern: /^[1-9]\d{0,3}(\.\d{1,2})?$/,
message: '请输入大于等于1小于9999的合法充值金额',
trigger: 'change'
}
]
},
logColumnsData: {},
rechargeListData: {},
withdrawApplyColumnsListData: {}
};
},
mounted() {
this.init();
},
methods: {
// 初始化数据
init() {
getMembersWallet().then((res) => {
this.frozenDeposit = res.result.memberFrozenWallet;
this.memberDeposit = res.result.memberWallet;
});
getDepositLog(this.walletForm).then((res) => {
const router = useRouter();
const formDataRef = ref(null);
const withdrawApplyFormDataRef = ref(null);
const activeWalletTab = ref("log");
const frozenDeposit = ref(0); // 冻结余额
const memberDeposit = ref(0); // 余额
const modal = ref(false); // 余额充值
const withdrawApplyModal = ref(false); // 提现申请
const formData = reactive({
// 充值金额
price: 1
});
// 提现金额
const withdrawApplyFormData = reactive({
price: 1,
minPrice: 1,
type: '',
realName: '',
connectNumber: '',
});
// 余额日志
const walletForm = reactive({
// 搜索框初始化对象
pageNumber: 1,
pageSize: 20
});
// 充值记录
const rechargeForm = reactive({
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20 // 页面大小
});
// 提现记录
const withdrawApplyForm = reactive({
// 搜索框初始化对象
pageNumber: 1, // 当前页数
pageSize: 20 // 页面大小
});
// 提现申请校验
const withdrawApplyFormValidate = {
price: [
{required: true, message: '请输入大于0小于9999的合法提现金额'},
{
pattern: /^[1-9]\d{0,3}(\.\d{1,2})?$/,
message: '请输入大于0小于9999的合法提现金额',
trigger: 'change'
}
],
realName: [
{required: true, message: '请输入真实姓名'},
],
connectNumber: [
{required: true, message: '请输入第三方登录账号'},
],
};
const formValidate = {
price: [
{required: true, message: '请输入大于等于1小于9999的合法充值金额'},
{
pattern: /^[1-9]\d{0,3}(\.\d{1,2})?$/,
message: '请输入大于等于1小于9999的合法充值金额',
trigger: 'change'
}
]
};
const logColumnsData = ref({});
const rechargeListData = ref({});
const withdrawApplyColumnsListData = ref({});
onMounted(() => {
init();
});
// 初始化数据
function init() {
getMembersWallet().then((res) => {
frozenDeposit.value = res.result.memberFrozenWallet;
memberDeposit.value = res.result.memberWallet;
});
getDepositLog(walletForm).then((res) => {
if (res.message === 'success') {
logColumnsData.value = res.result;
}
});
}
function tabPaneChange(v) {
activeWalletTab.value = v;
if (v === 'recharge') {
getRechargeData();
}
if (v === 'log') {
init();
}
if (v === 'withdrawApply') {
getWithdrawApplyData();
}
}
// 充值记录
function getRechargeData() {
getRecharge(rechargeForm).then((res) => {
if (res.message === 'success') {
rechargeListData.value = res.result;
}
});
}
// 提现记录
function getWithdrawApplyData() {
getWithdrawApply(withdrawApplyForm).then((res) => {
if (res.message === 'success') {
withdrawApplyColumnsListData.value = res.result;
}
});
}
function rechargeStatusText (status) {
const map = { PAID: '已付款', UNPAID: '未付款', CANCEL: '已取消' }
return map[status] || status || '—'
}
function rechargeWayText (way) {
const map = { ALIPAY: '支付宝', WECHAT: '微信', BANK_TRANSFER: '线下转账' }
return map[way] || way || '—'
}
function withdrawStatusText (status) {
const map = {
APPLY: '申请中',
VIA_AUDITING: '审核通过',
SUCCESS: '提现成功',
ERROR: '提现失败'
}
return map[status] || '审核拒绝'
}
// 余额日志
function changePage(v) {
walletForm.pageNumber = v;
init();
}
function changePageSize(v) {
walletForm.pageNumber = 1;
walletForm.pageSize = v;
init();
}
// 充值记录
function rechargeChangePage(v) {
rechargeForm.pageNumber = v;
getRechargeData();
}
function rechargeChangePageSize(v) {
rechargeForm.pageNumber = 1;
rechargeForm.pageSize = v;
getRechargeData();
}
// 提现记录
function withdrawChangePage(v) {
withdrawApplyForm.pageNumber = v;
getWithdrawApplyData();
}
function withdrawChangePageSize(v) {
withdrawApplyForm.pageNumber = 1;
withdrawApplyForm.pageSize = v;
getWithdrawApplyData();
}
// 弹出在线充值框
function recharge() {
formData.price = 1;
modal.value = true;
}
// 在线充值
function rechargePrice() {
formDataRef.value.validate((valid) => {
if (valid) {
apiRecharge(formData).then((res) => {
if (res.message === 'success') {
this.logColumnsData = res.result;
}
});
},
tabPaneChange(v) {
this.activeWalletTab = v;
if (v === 'recharge') {
this.getRechargeData();
}
if (v === 'log') {
this.init();
}
if (v === 'withdrawApply') {
this.getWithdrawApplyData();
}
},
// 充值记录
getRechargeData() {
getRecharge(this.rechargeForm).then((res) => {
if (res.message === 'success') {
this.rechargeListData = res.result;
}
});
},
// 提现记录
getWithdrawApplyData() {
getWithdrawApply(this.withdrawApplyForm).then((res) => {
if (res.message === 'success') {
this.withdrawApplyColumnsListData = res.result;
}
});
},
rechargeStatusText (status) {
const map = { PAID: '已付款', UNPAID: '未付款', CANCEL: '已取消' }
return map[status] || status || '—'
},
rechargeWayText (way) {
const map = { ALIPAY: '支付宝', WECHAT: '微信', BANK_TRANSFER: '线下转账' }
return map[way] || way || '—'
},
withdrawStatusText (status) {
const map = {
APPLY: '申请中',
VIA_AUDITING: '审核通过',
SUCCESS: '提现成功',
ERROR: '提现失败'
}
return map[status] || '审核拒绝'
},
// 余额日志
changePage(v) {
this.walletForm.pageNumber = v;
this.init();
},
changePageSize(v) {
this.walletForm.pageNumber = 1;
this.walletForm.pageSize = v;
this.init();
},
// 充值记录
rechargeChangePage(v) {
this.rechargeForm.pageNumber = v;
this.getRechargeData();
},
rechargeChangePageSize(v) {
this.rechargeForm.pageNumber = 1;
this.rechargeForm.pageSize = v;
this.getRechargeData();
},
// 提现记录
withdrawChangePage(v) {
this.withdrawApplyForm.pageNumber = v;
this.getWithdrawApplyData();
},
withdrawChangePageSize(v) {
this.withdrawApplyForm.pageNumber = 1;
this.withdrawApplyForm.pageSize = v;
this.getWithdrawApplyData();
},
// 弹出在线充值框
recharge() {
this.formData.price = 1;
this.modal = true;
},
// 在线充值
rechargePrice() {
this.$refs['formData'].validate((valid) => {
if (valid) {
recharge(this.formData).then((res) => {
if (res.message === 'success') {
this.$router.push({
path: '/payment',
query: {orderType: 'RECHARGE', sn: res.result.rechargeSn}
});
}
});
}
});
},
// 申请提现弹出框
withdrawalApply() {
this.withdrawApplyModal = true;
this.withdrawApplyFormData.minPrice = 1;
this.withdrawApplyFormData.price = 1;
this.withdrawApplyFormData.type = '';
this.withdrawApplyFormData.realName = '';
this.withdrawApplyFormData.connectNumber = '';
withdrawalSettingVO().then((res) => {
if (res.code === 200) {
this.withdrawApplyFormData.minPrice = res.result.minPrice;
this.withdrawApplyFormData.type = res.result.type;
this.withdrawApplyFormData.price = 1;
}
});
},
// 提现
withdrawal() {
this.$refs['withdrawApplyFormData'].validate((valid) => {
if (valid) {
withdrawalApply(this.withdrawApplyFormData).then((res) => {
if (res && res.success) {
Message.success('提现申请成功,关注提现状态');
this.withdrawApplyModal = false;
this.init(); // 余额查询
this.getWithdrawApplyData(); // 提现记录
}
router.push({
path: '/payment',
query: {orderType: 'RECHARGE', sn: res.result.rechargeSn}
});
}
});
}
}
};
});
}
// 申请提现弹出框
function withdrawalApply() {
withdrawApplyModal.value = true;
withdrawApplyFormData.minPrice = 1;
withdrawApplyFormData.price = 1;
withdrawApplyFormData.type = '';
withdrawApplyFormData.realName = '';
withdrawApplyFormData.connectNumber = '';
withdrawalSettingVO().then((res) => {
if (res.code === 200) {
withdrawApplyFormData.minPrice = res.result.minPrice;
withdrawApplyFormData.type = res.result.type;
withdrawApplyFormData.price = 1;
}
});
}
// 提现
function withdrawal() {
withdrawApplyFormDataRef.value.validate((valid) => {
if (valid) {
apiWithdrawalApply(withdrawApplyFormData).then((res) => {
if (res && res.success) {
Message.success('提现申请成功,关注提现状态');
withdrawApplyModal.value = false;
init(); // 余额查询
getWithdrawApplyData(); // 提现记录
}
});
}
});
}
</script>
<style lang="scss" scoped>

View File

@@ -4,7 +4,7 @@
<div class="bind-section mb_20">
<div class="section-title">兑换礼品卡</div>
<el-form ref="bindForm" :model="bindForm" :rules="bindRules" inline class="bind-form">
<el-form ref="bindFormRef" :model="bindForm" :rules="bindRules" inline class="bind-form">
<el-form-item label="卡号" prop="cardNo" label-width="60px">
<el-input v-model="bindForm.cardNo" placeholder="请输入卡号" maxlength="64" style="width: 220px" />
</el-form-item>
@@ -92,7 +92,9 @@
</div>
</template>
<script>
<script setup>
import { computed, nextTick, onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { Message } from "@/utils/message";
import {
getGiftCardCashMemberCardPage,
@@ -100,194 +102,201 @@ import {
activateGiftCardCashMemberCard,
} from "@/api/promotion.js";
export default {
name: "MyGiftCards",
data() {
return {
statusTab: "AVAILABLE",
loading: false,
total: 0,
list: [],
params: {
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
memberCardStatus: "AVAILABLE",
},
bindSubmitting: false,
bindForm: {
cardNo: "",
cardSecret: "",
},
bindRules: {
cardNo: [{ required: true, message: "请输入卡号", trigger: "blur" }],
cardSecret: [{ required: true, message: "请输入兑换码", trigger: "blur" }],
},
noticeModal: false,
serviceHotline: "12221111",
activatingId: "",
};
},
computed: {
primaryActionLabel() {
if (this.statusTab === "PENDING_ACTIVATION") {
return "激活自用";
}
if (this.statusTab === "AVAILABLE") {
return "去使用";
}
return "不可用";
},
primaryActionDisabled() {
return this.statusTab === "UNAVAILABLE";
},
pendingActivateLocked() {
return this.statusTab === "PENDING_ACTIVATION" && !!this.activatingId;
},
},
mounted() {
this.getList();
},
methods: {
formatYuan(val) {
if (val == null || val === "") {
return "—";
}
const n = Number(val);
if (Number.isNaN(n)) {
return String(val);
}
return Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/\.?0+$/, "");
},
formatValidity(row) {
const t = row && row.expireTime;
if (!t) {
return "长期有效";
}
const d = new Date(t);
if (Number.isNaN(d.getTime())) {
return "长期有效";
}
if (d.getFullYear() >= 2099) {
return "长期有效";
}
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day} 到期`;
},
onStatusTab(name) {
this.statusTab = name;
this.params.memberCardStatus = name;
this.params.pageNumber = 1;
this.getList();
},
getList() {
this.loading = true;
const p = { ...this.params };
if (p.memberCardStatus) {
p.memberCardStatus = String(p.memberCardStatus);
}
getGiftCardCashMemberCardPage(p)
.then((res) => {
this.loading = false;
if (res.success && res.result) {
this.list = res.result.records || [];
this.total = res.result.total || 0;
}
})
.catch(() => {
this.loading = false;
});
},
changePage(val) {
this.params.pageNumber = val;
this.getList();
},
changePageSize(val) {
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getList();
},
resetBindForm() {
this.bindForm = { cardNo: "", cardSecret: "" };
this.$nextTick(() => {
if (this.$refs.bindForm) {
this.$refs.bindForm.resetFields();
}
});
},
submitBind() {
this.$refs.bindForm.validate((valid) => {
if (!valid) {
return;
}
this.bindSubmitting = true;
bindGiftCardCashMemberCard({
cardNo: String(this.bindForm.cardNo).trim(),
cardSecret: String(this.bindForm.cardSecret).trim(),
})
.then((res) => {
this.bindSubmitting = false;
if (res.success) {
Message.success(res.message || "兑换成功");
this.resetBindForm();
this.params.pageNumber = 1;
this.getList();
}
})
.catch(() => {
this.bindSubmitting = false;
});
});
},
onCardPrimaryAction(item) {
if (this.primaryActionDisabled) {
return;
}
if (this.statusTab === "PENDING_ACTIVATION") {
const memberCardId = this.resolveMemberCardId(item);
if (!memberCardId) {
Message.error("卡片信息异常,无法激活");
return;
}
this.activatingId = memberCardId;
activateGiftCardCashMemberCard(memberCardId)
.then((res) => {
if (res.success) {
Message.success(res.message || "激活成功");
this.getList();
}
})
.catch((err) => {
console.warn('激活礼品卡失败', err);
})
.finally(() => {
this.activatingId = "";
});
return;
}
if (this.statusTab === "AVAILABLE") {
this.$router.push({ path: "/goodsList" });
}
},
resolveMemberCardId(row) {
if (!row) {
return "";
}
return row.memberCardId != null && row.memberCardId !== ""
? String(row.memberCardId)
: row.id != null && row.id !== ""
? String(row.id)
: "";
},
isActivating(item) {
const id = this.resolveMemberCardId(item);
return !!this.activatingId && this.activatingId === id;
},
},
defineOptions({ name: "MyGiftCards" });
const router = useRouter();
const statusTab = ref("AVAILABLE");
const loading = ref(false);
const total = ref(0);
const list = ref([]);
const params = reactive({
pageNumber: 1,
pageSize: 20,
sort: "createTime",
order: "desc",
memberCardStatus: "AVAILABLE",
});
const bindSubmitting = ref(false);
const bindForm = ref({
cardNo: "",
cardSecret: "",
});
const bindRules = {
cardNo: [{ required: true, message: "请输入卡号", trigger: "blur" }],
cardSecret: [{ required: true, message: "请输入兑换码", trigger: "blur" }],
};
const noticeModal = ref(false);
const serviceHotline = ref("12221111");
const activatingId = ref("");
const bindFormRef = ref(null);
const primaryActionLabel = computed(() => {
if (statusTab.value === "PENDING_ACTIVATION") {
return "激活自用";
}
if (statusTab.value === "AVAILABLE") {
return "去使用";
}
return "不可用";
});
const primaryActionDisabled = computed(() => {
return statusTab.value === "UNAVAILABLE";
});
const pendingActivateLocked = computed(() => {
return statusTab.value === "PENDING_ACTIVATION" && !!activatingId.value;
});
onMounted(() => {
getList();
});
function formatYuan(val) {
if (val == null || val === "") {
return "—";
}
const n = Number(val);
if (Number.isNaN(n)) {
return String(val);
}
return Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/\.?0+$/, "");
}
function formatValidity(row) {
const t = row && row.expireTime;
if (!t) {
return "长期有效";
}
const d = new Date(t);
if (Number.isNaN(d.getTime())) {
return "长期有效";
}
if (d.getFullYear() >= 2099) {
return "长期有效";
}
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day} 到期`;
}
function onStatusTab(name) {
statusTab.value = name;
params.memberCardStatus = name;
params.pageNumber = 1;
getList();
}
function getList() {
loading.value = true;
const p = { ...params };
if (p.memberCardStatus) {
p.memberCardStatus = String(p.memberCardStatus);
}
getGiftCardCashMemberCardPage(p)
.then((res) => {
loading.value = false;
if (res.success && res.result) {
list.value = res.result.records || [];
total.value = res.result.total || 0;
}
})
.catch(() => {
loading.value = false;
});
}
function changePage(val) {
params.pageNumber = val;
getList();
}
function changePageSize(val) {
params.pageNumber = 1;
params.pageSize = val;
getList();
}
function resetBindForm() {
bindForm.value = { cardNo: "", cardSecret: "" };
nextTick(() => {
if (bindFormRef.value) {
bindFormRef.value.resetFields();
}
});
}
function submitBind() {
bindFormRef.value.validate((valid) => {
if (!valid) {
return;
}
bindSubmitting.value = true;
bindGiftCardCashMemberCard({
cardNo: String(bindForm.value.cardNo).trim(),
cardSecret: String(bindForm.value.cardSecret).trim(),
})
.then((res) => {
bindSubmitting.value = false;
if (res.success) {
Message.success(res.message || "兑换成功");
resetBindForm();
params.pageNumber = 1;
getList();
}
})
.catch(() => {
bindSubmitting.value = false;
});
});
}
function onCardPrimaryAction(item) {
if (primaryActionDisabled.value) {
return;
}
if (statusTab.value === "PENDING_ACTIVATION") {
const memberCardId = resolveMemberCardId(item);
if (!memberCardId) {
Message.error("卡片信息异常,无法激活");
return;
}
activatingId.value = memberCardId;
activateGiftCardCashMemberCard(memberCardId)
.then((res) => {
if (res.success) {
Message.success(res.message || "激活成功");
getList();
}
})
.catch((err) => {
console.warn('激活礼品卡失败', err);
})
.finally(() => {
activatingId.value = "";
});
return;
}
if (statusTab.value === "AVAILABLE") {
router.push({ path: "/goodsList" });
}
}
function resolveMemberCardId(row) {
if (!row) {
return "";
}
return row.memberCardId != null && row.memberCardId !== ""
? String(row.memberCardId)
: row.id != null && row.id !== ""
? String(row.id)
: "";
}
function isActivating(item) {
const id = resolveMemberCardId(item);
return !!activatingId.value && activatingId.value === id;
}
</script>
<style scoped lang="scss">

View File

@@ -38,98 +38,103 @@
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { Message, Modal } from "@/utils/message";
import { Delete } from '@element-plus/icons-vue';
import { tracksList, clearTracks, clearTracksById } from "@/api/member";
export default {
components: { Delete },
name: "MyTrack",
data() {
return {
list: [], // 我的足迹,商品列表
spinShow: false, // 控制loading是否加载
params: {
pageNumber: 1,
pageSize: 20,
order: "desc",
sort: "updateTime",
},
total: 0,
};
},
mounted() {
this.getList();
},
methods: {
goodsDetail(skuId, goodsId) {
// 跳转商品详情
let routeUrl = this.$router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routeUrl.href, "_blank");
},
// 跳转店铺首页
shopPage(id) {
this.$router.push({
path: "/merchant",
query: { id: id },
});
},
clearAll() {
// 清除全部足迹
Modal.confirm({
title: "删除",
content: "<p>确定要删除全部足迹吗?</p>",
onOk: () => {
clearTracks().then((res) => {
if (res.success) {
Message.success("删除成功");
this.getList();
}
});
},
onCancel: () => {},
});
},
clearById(id) {
// 清除全部足迹
clearTracksById(id).then((res) => {
defineOptions({ name: "MyTrack" });
const router = useRouter();
const list = ref([]); // 我的足迹,商品列表
const spinShow = ref(false); // 控制loading是否加载
const params = reactive({
pageNumber: 1,
pageSize: 20,
order: "desc",
sort: "updateTime",
});
const total = ref(0);
function goodsDetail(skuId, goodsId) {
// 跳转商品详情
let routeUrl = router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routeUrl.href, "_blank");
}
// 跳转店铺首页
function shopPage(id) {
router.push({
path: "/merchant",
query: { id: id },
});
}
function clearAll() {
// 清除全部足迹
Modal.confirm({
title: "删除",
content: "<p>确定要删除全部足迹吗?</p>",
onOk: () => {
clearTracks().then((res) => {
if (res.success) {
Message.success("删除成功");
this.getList();
getList();
}
});
},
changePageNum(val) {
// 修改页码
this.params.pageNumber = val;
this.getList();
},
changePageSize(val) {
// 修改页数
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getList();
},
getList() {
// 获取足迹列表
this.spinShow = true;
tracksList(this.params).then((res) => {
this.spinShow = false;
if (res.success && res.result.records.length) {
this.list = res.result.records.filter(item =>{
return item != null
});
this.total = res.result.total
} else {
this.list = [];
}
onCancel: () => {},
});
}
function clearById(id) {
// 清除全部足迹
clearTracksById(id).then((res) => {
if (res.success) {
Message.success("删除成功");
getList();
}
});
}
function changePageNum(val) {
// 修改页码
params.pageNumber = val;
getList();
}
function changePageSize(val) {
// 修改页数
params.pageNumber = 1;
params.pageSize = val;
getList();
}
function getList() {
// 获取足迹列表
spinShow.value = true;
tracksList(params).then((res) => {
spinShow.value = false;
if (res.success && res.result.records.length) {
list.value = res.result.records.filter(item =>{
return item != null
});
},
},
};
total.value = res.result.total
} else {
list.value = [];
}
});
}
onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">
.wrapper {

File diff suppressed because it is too large Load Diff

View File

@@ -12,13 +12,14 @@
</div>
</template>
<script>
export default {
name: 'PayDone',
mounted () {
document.querySelector('.pay-done').style.height = window.innerHeight + 'px'
}
};
<script setup>
import { onMounted } from "vue";
defineOptions({ name: 'PayDone' });
onMounted(() => {
document.querySelector('.pay-done').style.height = window.innerHeight + 'px'
});
</script>
<style scoped lang='scss'>

View File

@@ -35,109 +35,108 @@
<BaseFooter></BaseFooter>
</div>
</template>
<script>
<script setup>
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { tradeDetail, pay } from '@/api/pay.js';
import { Message, Modal } from "@/utils/message";
export default {
data () {
return {
payDetail: {},
support: [],
walletValue: 0,
qrcode: '',
endTime: 0,
endText: '订单已超时取消',
isStart: false,
now: Date.now(),
countdownTimer: null,
};
},
computed: {
countdownText () {
if (!this.isStart || !this.endTime) return '';
const diff = this.endTime - this.now;
if (diff <= 0) return this.endText;
const days = Math.floor(diff / 86400000);
const hours = Math.floor((diff % 86400000) / 3600000);
const minutes = Math.floor((diff % 3600000) / 60000);
const seconds = Math.floor((diff % 60000) / 1000);
return `${days}天${hours}小时${minutes}分钟${seconds}秒`;
},
},
methods: {
// 获取订单详情
getTradeDetail () {
const params = this.$route.query;
params.clientType = 'PC'
tradeDetail(params).then(res => {
if (res.success) {
this.payDetail = res.result;
this.endTime = this.payDetail.autoCancel
this.isStart = true
this.support = this.payDetail.support
this.walletValue = this.payDetail.walletValue
const price = Number(this.payDetail.price);
if (!Number.isNaN(price) && price <= 0) {
this.$router.replace('/payDone');
}
}
}).catch((err) => {
this.$router.push({name: 'MyOrder'});
});
},
// 支付
handlePay (way) {
// 余额支付则直接跳转
if (way === 'WALLET') {
// 如果待支付金额大于余额,则报错
if (this.payDetail.price > this.walletValue) {
Message.error('余额不足以支付当前订单,如需充值请前往会员中心');
return;
}
}
const params = this.$route.query;
params.paymentMethod = way;
params.paymentClient = 'NATIVE';
params.price = this.payDetail.price;
if (way === 'WALLET') {
Modal.confirm({
title: '支付确认',
content: '确认使用余额支付吗?',
onOk: () => {
return pay(params)
.then((res) => {
if (res.success) {
Message.success(res.message || '支付成功');
this.$router.push('/payDone');
} else {
Message.warning(res.message || '支付失败');
}
})
.catch((err) => {
// 支付失败兜底:业务错误(如 CARD_KEY_STORE_SELL_FORBIDDEN)通常已由 axios 拦截器提示
if (err?.message && !err?.data?.message) {
Message.error(err.message);
}
});
}
});
} else {
this.$router.push({path: '/qrpay', query: params});
const route = useRoute();
const router = useRouter();
const payDetail = ref({});
const support = ref([]);
const walletValue = ref(0);
const qrcode = ref('');
const endTime = ref(0);
const endText = ref('订单已超时取消');
const isStart = ref(false);
const now = ref(Date.now());
let countdownTimer = null;
const countdownText = computed(() => {
if (!isStart.value || !endTime.value) return '';
const diff = endTime.value - now.value;
if (diff <= 0) return endText.value;
const days = Math.floor(diff / 86400000);
const hours = Math.floor((diff % 86400000) / 3600000);
const minutes = Math.floor((diff % 3600000) / 60000);
const seconds = Math.floor((diff % 60000) / 1000);
return `${days}天${hours}小时${minutes}分钟${seconds}秒`;
});
// 获取订单详情
function getTradeDetail() {
const params = route.query;
params.clientType = 'PC'
tradeDetail(params).then(res => {
if (res.success) {
payDetail.value = res.result;
endTime.value = payDetail.value.autoCancel
isStart.value = true
support.value = payDetail.value.support
walletValue.value = payDetail.value.walletValue
const price = Number(payDetail.value.price);
if (!Number.isNaN(price) && price <= 0) {
router.replace('/payDone');
}
}
},
mounted () {
this.getTradeDetail();
this.countdownTimer = setInterval(() => {
this.now = Date.now();
}, 1000);
},
beforeUnmount () {
if (this.countdownTimer) clearInterval(this.countdownTimer);
},
};
}).catch((err) => {
router.push({name: 'MyOrder'});
});
}
// 支付
function handlePay(way) {
// 余额支付则直接跳转
if (way === 'WALLET') {
// 如果待支付金额大于余额,则报错
if (payDetail.value.price > walletValue.value) {
Message.error('余额不足以支付当前订单,如需充值请前往会员中心');
return;
}
}
const params = route.query;
params.paymentMethod = way;
params.paymentClient = 'NATIVE';
params.price = payDetail.value.price;
if (way === 'WALLET') {
Modal.confirm({
title: '支付确认',
content: '确认使用余额支付吗?',
onOk: () => {
return pay(params)
.then((res) => {
if (res.success) {
Message.success(res.message || '支付成功');
router.push('/payDone');
} else {
Message.warning(res.message || '支付失败');
}
})
.catch((err) => {
// 支付失败兜底:业务错误(如 CARD_KEY_STORE_SELL_FORBIDDEN)通常已由 axios 拦截器提示
if (err?.message && !err?.data?.message) {
Message.error(err.message);
}
});
}
});
} else {
router.push({path: '/qrpay', query: params});
}
}
onMounted(() => {
getTradeDetail();
countdownTimer = setInterval(() => {
now.value = Date.now();
}, 1000);
});
onBeforeUnmount(() => {
if (countdownTimer) clearInterval(countdownTimer);
});
</script>
<style scoped lang="scss">
.head-left {

View File

@@ -44,89 +44,91 @@
<BaseFooter></BaseFooter>
</div>
</template>
<script>
<script setup>
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "@/utils/message";
import { Iphone } from '@element-plus/icons-vue';
import vueQr from 'vue-qr';
import { payCallback, pay } from '@/api/pay.js';
export default {
components: { vueQr, Iphone },
data () {
return {
qrcode: '',
params: this.$route.query,
interval: null,
num: 0,
payLoading: false,
payError: '',
};
},
computed: {
payWayName () {
return this.params.paymentMethod === 'ALIPAY' ? '支付宝' : '微信';
},
},
methods: {
formatPayError (rawMessage) {
const message = (rawMessage || '').trim();
if (!message || message === '支付业务异常,请稍后重试') {
return `${this.payWayName}支付暂不可用,请稍后重试或选择其他支付方式`;
const route = useRoute();
const router = useRouter();
const qrcode = ref('');
const params = ref(route.query);
const num = ref(0);
const payLoading = ref(false);
const payError = ref('');
let interval = null;
const payWayName = computed(() => {
return params.value.paymentMethod === 'ALIPAY' ? '支付宝' : '微信';
});
function formatPayError (rawMessage) {
const message = (rawMessage || '').trim();
if (!message || message === '支付业务异常,请稍后重试') {
return `${payWayName.value}支付暂不可用,请稍后重试或选择其他支付方式`;
}
return message;
}
function clearPayPoll () {
if (interval) {
clearInterval(interval);
interval = null;
}
}
function handlePay () {
clearPayPoll();
const params = route.query;
payLoading.value = true;
payError.value = '';
qrcode.value = '';
pay(params, { skipMessage: true })
.then((res) => {
payLoading.value = false;
if (res.success && res.result) {
qrcode.value = res.result;
num.value = 0;
interval = setInterval(callback, 5000);
return;
}
return message;
},
clearPayPoll () {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
},
handlePay () {
this.clearPayPoll();
const params = this.$route.query;
this.payLoading = true;
this.payError = '';
this.qrcode = '';
pay(params, { skipMessage: true })
.then((res) => {
this.payLoading = false;
if (res.success && res.result) {
this.qrcode = res.result;
this.num = 0;
this.interval = setInterval(this.callback, 5000);
return;
}
this.payError = this.formatPayError(res.message);
Message.error(this.payError);
})
.catch((err) => {
this.payLoading = false;
this.payError = this.formatPayError(err?.message || err?.data?.message);
Message.error(this.payError);
});
},
callback () {
this.num++;
if (this.num >= 7) {
this.clearPayPoll();
}
const params = JSON.parse(JSON.stringify(this.$route.query));
delete params.paymentMethod;
delete params.paymentClient;
payCallback(params).then((res) => {
if (res.result) {
this.clearPayPoll();
this.$router.push({ path: '/payDone', query: { orderType: this.$route.query.orderType } });
}
});
},
},
mounted () {
this.handlePay();
},
beforeUnmount () {
this.clearPayPoll();
},
};
payError.value = formatPayError(res.message);
Message.error(payError.value);
})
.catch((err) => {
payLoading.value = false;
payError.value = formatPayError(err?.message || err?.data?.message);
Message.error(payError.value);
});
}
function callback () {
num.value++;
if (num.value >= 7) {
clearPayPoll();
}
const params = JSON.parse(JSON.stringify(route.query));
delete params.paymentMethod;
delete params.paymentClient;
payCallback(params).then((res) => {
if (res.result) {
clearPayPoll();
router.push({ path: '/payDone', query: { orderType: route.query.orderType } });
}
});
}
onMounted(() => {
handlePay();
});
onBeforeUnmount(() => {
clearPayPoll();
});
</script>
<style scoped lang="scss">
.head-left {

View File

@@ -93,6 +93,18 @@
</template>
<script>
export default {
name: "PointGoodsDetail",
beforeRouteEnter(to, from, next) {
window.scrollTo(0, 0);
next();
},
};
</script>
<script setup>
import { ref, computed, onMounted, nextTick } from "vue";
import { useRoute, useRouter } from "vue-router";
import { Message } from "@/utils/message";
import Search from "@/components/Search";
import { addCartGoods } from "@/api/cart.js";
@@ -104,115 +116,109 @@ import {
E_COUPON_CART_POINTS,
} from "@/constants/goodsType";
export default {
name: "PointGoodsDetail",
components: { Search },
beforeRouteEnter(to, from, next) {
window.scrollTo(0, 0);
next();
},
created() {
this.getGoodsDetail();
},
mounted() {
window.addEventListener("scroll", this.handleScroll);
},
data() {
return {
goodsMsg: {},
goodsSku: {},
isLoading: false,
onceFlag: true,
count: 1,
loading: false,
};
},
computed: {
isECouponSku() {
return isECoupon(this.goodsSku.goodsType);
},
displayStock() {
if (this.isECouponSku) {
return getECouponStock(this.goodsSku);
const route = useRoute();
const router = useRouter();
const goodsMsg = ref({});
const goodsSku = ref({});
const isLoading = ref(false);
const onceFlag = ref(true);
const count = ref(1);
const loading = ref(false);
const itemIntroGoods = ref(null);
const isECouponSku = computed(() => {
return isECoupon(goodsSku.value.goodsType);
});
const displayStock = computed(() => {
if (isECouponSku.value) {
return getECouponStock(goodsSku.value);
}
return Number(goodsSku.value.quantity) || 0;
});
const quantityMax = computed(() => {
if (isECouponSku.value) {
return getECouponMaxBuyNum(goodsSku.value);
}
const qty = Number(goodsSku.value.quantity) || 0;
return qty > 0 ? qty : 1;
});
const isOutOfStock = computed(() => {
return displayStock.value <= 0;
});
function getGoodsDetail() {
isLoading.value = true;
pointGoodsDetail(route.query.id)
.then((res) => {
isLoading.value = false;
if (res.success) {
goodsMsg.value = res.result;
goodsSku.value = res.result.goodsSku || {};
syncCountWithQuantity();
} else {
Message.error(res.message);
router.push("/");
}
return Number(this.goodsSku.quantity) || 0;
},
quantityMax() {
if (this.isECouponSku) {
return getECouponMaxBuyNum(this.goodsSku);
})
.catch(() => {
router.push("/");
});
}
function syncCountWithQuantity() {
const max = quantityMax.value;
if (max <= 0) {
count.value = 1;
return;
}
if (count.value > max) count.value = max;
if (count.value < 1) count.value = 1;
}
function pointBuy() {
const params = {
num: count.value,
skuId: goodsMsg.value.skuId,
cartType: E_COUPON_CART_POINTS,
};
loading.value = true;
addCartGoods(params)
.then((res) => {
loading.value = false;
if (res.success) {
router.push({ path: "/pay", query: { way: params.cartType } });
} else {
Message.warning(res.message);
}
const qty = Number(this.goodsSku.quantity) || 0;
return qty > 0 ? qty : 1;
},
isOutOfStock() {
return this.displayStock <= 0;
},
},
methods: {
getGoodsDetail() {
this.isLoading = true;
pointGoodsDetail(this.$route.query.id)
.then((res) => {
this.isLoading = false;
if (res.success) {
this.goodsMsg = res.result;
this.goodsSku = res.result.goodsSku || {};
this.syncCountWithQuantity();
} else {
Message.error(res.message);
this.$router.push("/");
}
})
.catch(() => {
this.$router.push("/");
});
},
syncCountWithQuantity() {
const max = this.quantityMax;
if (max <= 0) {
this.count = 1;
return;
}
if (this.count > max) this.count = max;
if (this.count < 1) this.count = 1;
},
pointBuy() {
const params = {
num: this.count,
skuId: this.goodsMsg.skuId,
cartType: E_COUPON_CART_POINTS,
};
this.loading = true;
addCartGoods(params)
.then((res) => {
this.loading = false;
if (res.success) {
this.$router.push({ path: "/pay", query: { way: params.cartType } });
} else {
Message.warning(res.message);
}
})
.catch(() => {
this.loading = false;
});
},
handleScroll() {
if (this.onceFlag) {
this.$nextTick(() => {
this.changeHeight();
});
this.onceFlag = false;
}
},
changeHeight() {
const goodsDetailCon = document.querySelector(".item-intro");
if (!goodsDetailCon || !this.$refs.itemIntroGoods) return;
let heightCss = window.getComputedStyle(goodsDetailCon).height;
heightCss = parseInt(heightCss.substr(0, heightCss.length - 2), 10) + 89;
this.$refs.itemIntroGoods.style.height = heightCss + "px";
},
},
};
})
.catch(() => {
loading.value = false;
});
}
function handleScroll() {
if (onceFlag.value) {
nextTick(() => {
changeHeight();
});
onceFlag.value = false;
}
}
function changeHeight() {
const goodsDetailCon = document.querySelector(".item-intro");
if (!goodsDetailCon || !itemIntroGoods.value) return;
let heightCss = window.getComputedStyle(goodsDetailCon).height;
heightCss = parseInt(heightCss.substr(0, heightCss.length - 2), 10) + 89;
itemIntroGoods.value.style.height = heightCss + "px";
}
getGoodsDetail();
onMounted(() => {
window.addEventListener("scroll", handleScroll);
});
</script>
<style scoped lang="scss">

View File

@@ -44,78 +44,92 @@
<BaseFooter></BaseFooter>
</div>
</template>
<script>
import {pointGoods, pointGoodsCategory} from '@/api/promotion.js'
export default {
data () {
return {
goodsList: [], // 积分商品列表
cateList: [{ // 商品分类
name: '全部分类',
id: 0
}], // 积分分类列表
params: { // 商品列表请求参数
pageNumber: 1,
pageSize: 20,
pointsGoodsCategoryId: ''
},
total: 0, // 商品总数
cateId: '' // 店铺分类id
<script setup>
import { onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { pointGoods, pointGoodsCategory } from "@/api/promotion.js";
const route = useRoute();
const router = useRouter();
const goodsList = ref([]); // 积分商品列表
const cateList = ref([{ // 商品分类
name: "全部分类",
id: 0,
}]); // 积分分类列表
// 商品列表请求参数
const params = reactive({
pageNumber: 1,
pageSize: 20,
pointsGoodsCategoryId: "",
});
const total = ref(0); // 商品总数
const cateId = ref(""); // 店铺分类id
onMounted(() => {
params.pointsGoodsCategoryId = route.query.categoryId || "";
cateId.value = route.query.categoryId || 0;
getList();
getCate();
});
/** 列表 API 返回 PointsGoods(thumbnail/goodsName 在顶层);详情 API 才有 goodsSku */
function itemThumbnail(item) {
return item?.goodsSku?.thumbnail || item?.thumbnail || "";
}
function itemGoodsName(item) {
return item?.goodsSku?.goodsName || item?.goodsName || "";
}
// 获取列表
function getList() {
pointGoods(params).then((res) => {
if (res.success) {
goodsList.value = res.result.records;
total.value = res.result.total;
}
},
mounted () {
this.params.pointsGoodsCategoryId = this.$route.query.categoryId || ''
this.cateId = this.$route.query.categoryId || 0
this.getList()
this.getCate()
},
methods: {
/** 列表 API 返回 PointsGoods(thumbnail/goodsName 在顶层);详情 API 才有 goodsSku */
itemThumbnail (item) {
return item?.goodsSku?.thumbnail || item?.thumbnail || ''
},
itemGoodsName (item) {
return item?.goodsSku?.goodsName || item?.goodsName || ''
},
getList () { // 获取列表
pointGoods(this.params).then(res => {
if (res.success) {
this.goodsList = res.result.records
this.total = res.result.total
}
})
},
getCate () { // 获取分类
pointGoodsCategory(this.params).then(res => {
if (res.success) {
this.cateList.push(...res.result.records)
}
})
},
selectCate (item) { // 选择商品分类
let cateId = item.value === 0 ? '' : item.value
this.params.pointsGoodsCategoryId = cateId
this.getList()
this.$router.push({query: {categoryId: cateId}})
},
goGoodsDetail (id) { // 跳转商品详情
this.$router.push({path: '/pointGoodsDetail', query: {id}})
// let routerUrl = this.$router.resolve({
// path: '/pointGoodsDetail',
// query: {id}
// })
// window.open(routerUrl.href, '_blank')
},
changePageNum (val) { // 修改页码
this.params.pageNumber = val;
this.getList()
},
changePageSize (val) { // 修改页数
this.params.pageNumber = 1;
this.params.pageSize = val;
this.getList()
});
}
// 获取分类
function getCate() {
pointGoodsCategory(params).then((res) => {
if (res.success) {
cateList.value.push(...res.result.records);
}
}
});
}
// 选择商品分类
function selectCate(item) {
let cateId = item.value === 0 ? "" : item.value;
params.pointsGoodsCategoryId = cateId;
getList();
router.push({ query: { categoryId: cateId } });
}
// 跳转商品详情
function goGoodsDetail(id) {
router.push({ path: "/pointGoodsDetail", query: { id } });
// let routerUrl = router.resolve({
// path: '/pointGoodsDetail',
// query: {id}
// })
// window.open(routerUrl.href, '_blank')
}
// 修改页码
function changePageNum(val) {
params.pageNumber = val;
getList();
}
// 修改页数
function changePageSize(val) {
params.pageNumber = 1;
params.pageSize = val;
getList();
}
</script>
<style lang="scss" scoped>

View File

@@ -61,121 +61,127 @@
<BaseFooter />
</div>
</template>
<script>
<script setup>
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { seckillByDay } from "@/api/promotion";
export default {
data () {
return {
list: [], // 秒杀时段列表
goodsList: [], // 商品列表
interval: null, // 定时器
currIndex: 0, // 当前时间段的下标
currTime: 0, // 当前显示的倒计时
diffSeconds: 0, // 倒计时时间戳
nowHour: new Date().getHours(), // 当前小时数
};
},
beforeUnmount () {
clearInterval(this.interval);
},
watch: {
currIndex (val) {
clearInterval(this.interval);
this.interval = null;
this.nowHour = new Date().getHours();
this.countDown(val);
this.goodsList = this.list[val].seckillGoodsList;
},
diffSeconds (val) {
const hours = Math.floor(val / 3600);
// 当前秒数 / 60,向下取整
// 获取到所有分钟数 3600 / 60 = 60分钟
// 对60取模,超过小时数的分钟数
const minutes = Math.floor(val / 60) % 60;
// 当前的秒数 % 60,获取到 超过小时数、分钟数的秒数(秒数)
const seconds = val % 60;
this.currTime =
filteTime(hours) + ":" + filteTime(minutes) + ":" + filteTime(seconds);
if (val <= 0) {
clearInterval(this.interval);
this.interval = null;
}
function filteTime (time) {
if (time < 10) {
return "0" + time;
} else {
return time;
}
}
},
},
methods: {
getListByDay () {
// 当天秒杀活动
seckillByDay().then((res) => {
if (res.success) {
this.list = res.result;
this.goodsList = this.list[0].seckillGoodsList;
this.countDown(this.currIndex);
}
});
},
goGoodsDetail (skuId, goodsId) {
// 跳转商品详情
let routeUrl = this.$router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routeUrl.href, "_blank");
},
// 已售占比。没有库存时按售罄展示
salePercent (item) {
if (!item.quantity || item.quantity <= 0) return 100;
return Math.min(100, Math.ceil((item.salesNum / item.quantity) * 100));
},
// 场次已开始,且销量达到库存
isSoldOut (item) {
return this.nowHour >= this.list[this.currIndex].timeLine && item.quantity <= item.salesNum;
},
// 场次已开始且未售罄才可抢
canBuy (item) {
return this.nowHour >= this.list[this.currIndex].timeLine && !this.isSoldOut(item);
},
// 按钮文案:未开始 / 已售罄 / 立即抢购
buyText (item) {
if (this.nowHour < this.list[this.currIndex].timeLine) return "即将开始";
if (this.isSoldOut(item)) return "已售罄";
return "立即抢购";
},
countDown (currIndex) {
// 倒计时
// 0点时间戳
let zeroTime = new Date(new Date().toLocaleDateString()).getTime();
let currTime = new Date().getTime();
let actTime = 0;
let nowHour = new Date().getHours(); // 当前小时数
if (this.list[currIndex].timeLine > nowHour) {
// 活动未开始
actTime = zeroTime + this.list[currIndex].timeLine * 3600 * 1000;
} else if (this.list[currIndex].timeLine <= nowHour) {
// 活动进行中
if (currIndex === this.list.length - 1) {
// 如果是最后一个活动,直到24点结束
actTime = zeroTime + 24 * 3600 * 1000;
} else {
actTime = zeroTime + this.list[currIndex + 1].timeLine * 3600 * 1000;
}
}
this.diffSeconds = Math.floor((actTime - currTime) / 1000);
this.interval = setInterval(() => {
this.diffSeconds--;
}, 1000);
},
},
mounted () {
this.getListByDay();
},
};
const router = useRouter();
const list = ref([]); // 秒杀时段列表
const goodsList = ref([]); // 商品列表
let interval = null; // 定时器
const currIndex = ref(0); // 当前时间段的下标
const currTime = ref(0); // 当前显示的倒计时
const diffSeconds = ref(0); // 倒计时时间戳
const nowHour = ref(new Date().getHours()); // 当前小时数
watch(currIndex, (val) => {
clearInterval(interval);
interval = null;
nowHour.value = new Date().getHours();
countDown(val);
goodsList.value = list.value[val].seckillGoodsList;
});
watch(diffSeconds, (val) => {
const hours = Math.floor(val / 3600);
// 当前秒数 / 60,向下取整
// 获取到所有分钟数 3600 / 60 = 60分钟
// 对60取模,超过小时数的分钟数
const minutes = Math.floor(val / 60) % 60;
// 当前的秒数 % 60,获取到 超过小时数、分钟数的秒数(秒数)
const seconds = val % 60;
currTime.value =
filteTime(hours) + ":" + filteTime(minutes) + ":" + filteTime(seconds);
if (val <= 0) {
clearInterval(interval);
interval = null;
}
function filteTime (time) {
if (time < 10) {
return "0" + time;
} else {
return time;
}
}
});
function getListByDay () {
// 当天秒杀活动
seckillByDay().then((res) => {
if (res.success) {
list.value = res.result;
goodsList.value = list.value[0].seckillGoodsList;
countDown(currIndex.value);
}
});
}
function goGoodsDetail (skuId, goodsId) {
// 跳转商品详情
let routeUrl = router.resolve({
path: "/goodsDetail",
query: { skuId, goodsId },
});
window.open(routeUrl.href, "_blank");
}
// 已售占比。没有库存时按售罄展示
function salePercent (item) {
if (!item.quantity || item.quantity <= 0) return 100;
return Math.min(100, Math.ceil((item.salesNum / item.quantity) * 100));
}
// 场次已开始,且销量达到库存
function isSoldOut (item) {
return nowHour.value >= list.value[currIndex.value].timeLine && item.quantity <= item.salesNum;
}
// 场次已开始且未售罄才可抢
function canBuy (item) {
return nowHour.value >= list.value[currIndex.value].timeLine && !isSoldOut(item);
}
// 按钮文案:未开始 / 已售罄 / 立即抢购
function buyText (item) {
if (nowHour.value < list.value[currIndex.value].timeLine) return "即将开始";
if (isSoldOut(item)) return "已售罄";
return "立即抢购";
}
function countDown (currIndex) {
// 倒计时
// 0点时间戳
let zeroTime = new Date(new Date().toLocaleDateString()).getTime();
let currTime = new Date().getTime();
let actTime = 0;
let nowHour = new Date().getHours(); // 当前小时数
if (list.value[currIndex].timeLine > nowHour) {
// 活动未开始
actTime = zeroTime + list.value[currIndex].timeLine * 3600 * 1000;
} else if (list.value[currIndex].timeLine <= nowHour) {
// 活动进行中
if (currIndex === list.value.length - 1) {
// 如果是最后一个活动,直到24点结束
actTime = zeroTime + 24 * 3600 * 1000;
} else {
actTime = zeroTime + list.value[currIndex + 1].timeLine * 3600 * 1000;
}
}
diffSeconds.value = Math.floor((actTime - currTime) / 1000);
interval = setInterval(() => {
diffSeconds.value--;
}, 1000);
}
onMounted(() => {
getListByDay();
});
onBeforeUnmount(() => {
clearInterval(interval);
});
</script>
<style lang="scss" scoped>
@import "../../assets/styles/goodsList.scss";

View File

@@ -1,7 +1,7 @@
<template>
<div class="company-msg">
<el-form
ref="firstForm"
ref="firstFormRef"
:model="form"
:rules="rules"
label-width="140px"
@@ -190,7 +190,8 @@
<multipleMap ref="map" @callback="getAddress" />
</div>
</template>
<script>
<script setup>
import { onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import { Message, Notice } from "@/utils/message";
import { Delete, View } from '@element-plus/icons-vue';
import { applyFirst } from '@/api/shopentry';
@@ -200,261 +201,259 @@ import storage from '@/plugins/storage';
import { buyerUrl } from '@/plugins/request.js';
const FIRST_APPLY_DRAFT_KEY = 'shopEntryFirstDraft';
export default {
components: { multipleMap, View, Delete },
emits: ['change'],
props: {
content: {
default: () => ({}),
type: Object
}
},
data () {
return {
action: buyerUrl + '/buyer/common/upload/file', // 上传地址
accessToken: {}, // 验证token
visible: false, // 预览图片
loading: false, // 加载状态
previewPicture: '', // 预览图片url
form: { // 表单数据
companyName: '',
companyAddressIdPath: '',
companyAddressPath: '',
companyAddress: '',
employeeNum: '',
companyPhone: '',
registeredCapital: '',
linkName: '',
linkPhone: '',
companyEmail: '',
licenseNum: '',
scope: '',
legalName: '',
legalId: '',
legalPhoto: [],
licencePhoto: []
},
rules: { // 验证规则
companyName: [{ required: true, message: '请填写公司信息' }],
companyAddressIdPath: [{ required: true, message: '请选择公司所在地' }],
companyAddress: [{ required: true, message: '请填写公司详细地址' }],
employeeNum: [
{ required: true, message: '请填写公司员工总数' },
{ pattern: RegExp.integer, message: '只能填写正整数' }
],
registeredCapital: [
{ required: true, message: '请填写公司注册资金' },
{ pattern: RegExp.integer, message: '只能填写正整数' }
],
linkName: [{ required: true, message: '请填写联系人姓名' }],
linkPhone: [
{ required: true, message: '请填写联系人电话' },
{ pattern: RegExp.mobile, message: '请填写正确的号码' }
],
companyPhone: [
{ required: true, message: '请填写公司电话' },
{ pattern: RegExp.mobile, message: '请填写正确的号码' }
],
companyEmail: [
{ required: true, message: '请填写电子邮箱' },
{ type: 'email', message: '请输入正确的邮箱' }
],
licenseNum: [
{ required: true, message: '请填写营业执照号' },
{ pattern: RegExp.licenseNum, message: '请输入正确的营业执照号' }
],
scope: [{ required: true, message: '请填写营业执照所示经营范围' }],
legalPhoto: [{ required: true, message: '请上传法人身份证照片' }],
licencePhoto: [{ required: true, message: '请上传营业执照' }],
legalName: [{ required: true, message: '请输入法人姓名' }],
legalId: [
{ required: true, message: '请输入法人证件号' },
{ pattern: RegExp.IDCard, message: '请输入正确的证件号' }
]
},
uploadLoading1: false, // 上传loading
uploadLoading: false, // 上传loading
contentInitialized: false
};
},
methods: {
// 获取店铺地址
getAddress(val){
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
this.form['companyAddressIdPath'] = ids
this.form['companyAddressPath'] = paths
}else{
this.form['companyAddressIdPath'] = val.data.addrId
this.form['companyAddressPath'] = val.data.addr
}
},
// 下一步
next () {
this.$refs.firstForm.validate((valid) => {
if (valid) {
this.loading = true;
let params = JSON.parse(JSON.stringify(this.form));
params.legalPhoto = this.form.legalPhoto.toString();
params.licencePhoto = this.normalizeLicencePhoto(this.form.licencePhoto);
applyFirst(params)
.then((res) => {
this.loading = false;
if (res.success) {
sessionStorage.removeItem(FIRST_APPLY_DRAFT_KEY);
this.$emit('change', 1);
}
})
.catch(() => {
this.loading = false;
});
}
});
},
// 上传之前
beforeUpload (file) {
this.uploadLoading = true;
if (this.form.licencePhoto.length >= 1) {
Message.warning('最多上传一张图片')
this.uploadLoading = false;
return false;
}
return this.validateUploadFile(file);
},
// 上传之前
beforeUpload1 (file) {
this.uploadLoading1 = true;
if (this.form.legalPhoto.length >= 2) {
Message.warning('最多上传两张图片')
this.uploadLoading1 = false;
return false;
}
return this.validateUploadFile(file);
},
validateUploadFile (file) {
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
const isAllowedType = allowedTypes.includes(file.type);
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isAllowedType) {
this.uploadLoading = false;
this.uploadLoading1 = false;
Message.warning('上传文件格式不正确');
return false;
}
if (!isLt2M) {
this.uploadLoading = false;
this.uploadLoading1 = false;
Message.warning('文件大小不能超过2M');
return false;
}
return true;
},
// 上传成功回调
handleSuccess (res, file) {
this.uploadLoading = false;
this.form.licencePhoto = [res.result];
},
// 上传成功回调
handleSuccess1 (res, file) {
this.uploadLoading1 = false;
this.form.legalPhoto.push(res.result);
},
// 上传失败
uploadErr () {
this.uploadLoading = false;
this.uploadLoading1 = false;
},
// 上传失败回调
handleFormatError (file) {
this.uploadLoading = false;
this.uploadLoading1 = false;
Notice.warning({
title: 'The file format is incorrect',
desc: '上传文件格式不正确'
});
},
// 上传大小限制
handleMaxSize (file) {
this.uploadLoading = false;
this.uploadLoading1 = false;
Notice.warning({
title: 'Exceeding file size limit',
desc: '文件大小不能超过2M'
});
},
// 图片查看
handleView (item) {
this.previewPicture = item;
this.visible = true;
},
// 删除图片
handleRemove (index, listName) {
this.form[listName].splice(index, 1);
},
normalizeLicencePhoto (value) {
if (Array.isArray(value)) {
return value.length ? value[0] : '';
}
return value ? String(value).split(',').filter(Boolean)[0] || '' : '';
},
initFormFromContent(content) {
if (!content || !Object.keys(content).length) return;
this.form = JSON.parse(JSON.stringify(content));
this.form.legalPhoto = content.legalPhoto
? String(content.legalPhoto).split(',').filter(Boolean)
: [];
this.form.licencePhoto = content.licencePhoto
? String(content.licencePhoto).split(',').filter(Boolean).slice(0, 1)
: [];
this.contentInitialized = true;
},
restoreDraft() {
const draft = sessionStorage.getItem(FIRST_APPLY_DRAFT_KEY);
if (!draft) return;
try {
const parsed = JSON.parse(draft);
this.form = {
legalPhoto: [],
licencePhoto: [],
...parsed,
};
this.form.legalPhoto = Array.isArray(parsed.legalPhoto) ? parsed.legalPhoto : [];
this.form.licencePhoto = Array.isArray(parsed.licencePhoto)
? parsed.licencePhoto.slice(0, 1)
: [];
} catch (e) {
sessionStorage.removeItem(FIRST_APPLY_DRAFT_KEY);
}
},
saveDraft() {
sessionStorage.setItem(FIRST_APPLY_DRAFT_KEY, JSON.stringify(this.form));
}
},
watch: {
content: {
immediate: true,
handler(val) {
if (this.contentInitialized || !val || !Object.keys(val).length) return;
this.initFormFromContent(val);
}
}
},
mounted () {
this.accessToken.accessToken = storage.getItem('accessToken');
if (Object.keys(this.content).length) {
this.initFormFromContent(this.content);
} else {
this.restoreDraft();
}
},
beforeUnmount() {
this.saveDraft();
const emit = defineEmits(['change']);
const props = defineProps({
content: {
default: () => ({}),
type: Object
}
});
const firstFormRef = ref(null);
const map = ref(null);
const action = buyerUrl + '/buyer/common/upload/file'; // 上传地址
const accessToken = reactive({}); // 验证token
const visible = ref(false); // 预览图片
const loading = ref(false); // 加载状态
const previewPicture = ref(''); // 预览图片url
const form = ref({ // 表单数据
companyName: '',
companyAddressIdPath: '',
companyAddressPath: '',
companyAddress: '',
employeeNum: '',
companyPhone: '',
registeredCapital: '',
linkName: '',
linkPhone: '',
companyEmail: '',
licenseNum: '',
scope: '',
legalName: '',
legalId: '',
legalPhoto: [],
licencePhoto: []
});
const rules = { // 验证规则
companyName: [{ required: true, message: '请填写公司信息' }],
companyAddressIdPath: [{ required: true, message: '请选择公司所在地' }],
companyAddress: [{ required: true, message: '请填写公司详细地址' }],
employeeNum: [
{ required: true, message: '请填写公司员工总数' },
{ pattern: RegExp.integer, message: '只能填写正整数' }
],
registeredCapital: [
{ required: true, message: '请填写公司注册资金' },
{ pattern: RegExp.integer, message: '只能填写正整数' }
],
linkName: [{ required: true, message: '请填写联系人姓名' }],
linkPhone: [
{ required: true, message: '请填写联系人电话' },
{ pattern: RegExp.mobile, message: '请填写正确的号码' }
],
companyPhone: [
{ required: true, message: '请填写公司电话' },
{ pattern: RegExp.mobile, message: '请填写正确的号码' }
],
companyEmail: [
{ required: true, message: '请填写电子邮箱' },
{ type: 'email', message: '请输入正确的邮箱' }
],
licenseNum: [
{ required: true, message: '请填写营业执照号' },
{ pattern: RegExp.licenseNum, message: '请输入正确的营业执照号' }
],
scope: [{ required: true, message: '请填写营业执照所示经营范围' }],
legalPhoto: [{ required: true, message: '请上传法人身份证照片' }],
licencePhoto: [{ required: true, message: '请上传营业执照' }],
legalName: [{ required: true, message: '请输入法人姓名' }],
legalId: [
{ required: true, message: '请输入法人证件号' },
{ pattern: RegExp.IDCard, message: '请输入正确的证件号' }
]
};
const uploadLoading1 = ref(false); // 上传loading
const uploadLoading = ref(false); // 上传loading
const contentInitialized = ref(false);
// 获取店铺地址
function getAddress(val){
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
form.value['companyAddressIdPath'] = ids
form.value['companyAddressPath'] = paths
}else{
form.value['companyAddressIdPath'] = val.data.addrId
form.value['companyAddressPath'] = val.data.addr
}
}
// 下一步
function next () {
firstFormRef.value.validate((valid) => {
if (valid) {
loading.value = true;
let params = JSON.parse(JSON.stringify(form.value));
params.legalPhoto = form.value.legalPhoto.toString();
params.licencePhoto = normalizeLicencePhoto(form.value.licencePhoto);
applyFirst(params)
.then((res) => {
loading.value = false;
if (res.success) {
sessionStorage.removeItem(FIRST_APPLY_DRAFT_KEY);
emit('change', 1);
}
})
.catch(() => {
loading.value = false;
});
}
});
}
// 上传之前
function beforeUpload (file) {
uploadLoading.value = true;
if (form.value.licencePhoto.length >= 1) {
Message.warning('最多上传一张图片')
uploadLoading.value = false;
return false;
}
return validateUploadFile(file);
}
// 上传之前
function beforeUpload1 (file) {
uploadLoading1.value = true;
if (form.value.legalPhoto.length >= 2) {
Message.warning('最多上传两张图片')
uploadLoading1.value = false;
return false;
}
return validateUploadFile(file);
}
function validateUploadFile (file) {
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
const isAllowedType = allowedTypes.includes(file.type);
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isAllowedType) {
uploadLoading.value = false;
uploadLoading1.value = false;
Message.warning('上传文件格式不正确');
return false;
}
if (!isLt2M) {
uploadLoading.value = false;
uploadLoading1.value = false;
Message.warning('文件大小不能超过2M');
return false;
}
return true;
}
// 上传成功回调
function handleSuccess (res, file) {
uploadLoading.value = false;
form.value.licencePhoto = [res.result];
}
// 上传成功回调
function handleSuccess1 (res, file) {
uploadLoading1.value = false;
form.value.legalPhoto.push(res.result);
}
// 上传失败
function uploadErr () {
uploadLoading.value = false;
uploadLoading1.value = false;
}
// 上传失败回调
function handleFormatError (file) {
uploadLoading.value = false;
uploadLoading1.value = false;
Notice.warning({
title: 'The file format is incorrect',
desc: '上传文件格式不正确'
});
}
// 上传大小限制
function handleMaxSize (file) {
uploadLoading.value = false;
uploadLoading1.value = false;
Notice.warning({
title: 'Exceeding file size limit',
desc: '文件大小不能超过2M'
});
}
// 图片查看
function handleView (item) {
previewPicture.value = item;
visible.value = true;
}
// 删除图片
function handleRemove (index, listName) {
form.value[listName].splice(index, 1);
}
function normalizeLicencePhoto (value) {
if (Array.isArray(value)) {
return value.length ? value[0] : '';
}
return value ? String(value).split(',').filter(Boolean)[0] || '' : '';
}
function initFormFromContent(content) {
if (!content || !Object.keys(content).length) return;
form.value = JSON.parse(JSON.stringify(content));
form.value.legalPhoto = content.legalPhoto
? String(content.legalPhoto).split(',').filter(Boolean)
: [];
form.value.licencePhoto = content.licencePhoto
? String(content.licencePhoto).split(',').filter(Boolean).slice(0, 1)
: [];
contentInitialized.value = true;
}
function restoreDraft() {
const draft = sessionStorage.getItem(FIRST_APPLY_DRAFT_KEY);
if (!draft) return;
try {
const parsed = JSON.parse(draft);
form.value = {
legalPhoto: [],
licencePhoto: [],
...parsed,
};
form.value.legalPhoto = Array.isArray(parsed.legalPhoto) ? parsed.legalPhoto : [];
form.value.licencePhoto = Array.isArray(parsed.licencePhoto)
? parsed.licencePhoto.slice(0, 1)
: [];
} catch (e) {
sessionStorage.removeItem(FIRST_APPLY_DRAFT_KEY);
}
}
function saveDraft() {
sessionStorage.setItem(FIRST_APPLY_DRAFT_KEY, JSON.stringify(form.value));
}
watch(
() => props.content,
(val) => {
if (contentInitialized.value || !val || !Object.keys(val).length) return;
initFormFromContent(val);
},
{ immediate: true }
);
onMounted(() => {
accessToken.accessToken = storage.getItem('accessToken');
if (Object.keys(props.content).length) {
initFormFromContent(props.content);
} else {
restoreDraft();
}
});
onBeforeUnmount(() => {
saveDraft();
});
</script>
<style lang="scss" scoped>
h4 {

View File

@@ -40,60 +40,58 @@
</el-form>
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { applySecond } from '@/api/shopentry';
export default {
emits: ['change'],
props: {
content: {
default: () => ({}),
type: Object
}
},
data () {
return {
loading: false, // 加载状态
form: {}, // 表单数据
rules: { // 验证规则
settlementBankAccountName: [
{ required: true, message: '请填写银行开户名称' }
],
settlementBankAccountNum: [
{ required: true, message: '请填写银行账号' }
],
settlementBankBranchName: [
{ required: true, message: '请填写开户银行支行名称' }
],
settlementBankJointName: [
{ required: true, message: '请填写支行联行号' }
]
}
};
},
methods: {
// 下一步
next () {
this.$refs.secondForm.validate((valid) => {
if (valid) {
this.loading = true;
applySecond(this.form)
.then((res) => {
this.loading = false;
if (res.success) this.$emit('change', 2);
})
.catch(() => {
this.loading = false;
});
}
});
}
},
mounted () {
if (this.content && Object.keys(this.content).length) {
this.form = JSON.parse(JSON.stringify(this.content));
}
const emit = defineEmits(['change']);
const props = defineProps({
content: {
default: () => ({}),
type: Object
}
});
const secondForm = ref(null);
const loading = ref(false); // 加载状态
const form = reactive({}); // 表单数据
const rules = { // 验证规则
settlementBankAccountName: [
{ required: true, message: '请填写银行开户名称' }
],
settlementBankAccountNum: [
{ required: true, message: '请填写银行账号' }
],
settlementBankBranchName: [
{ required: true, message: '请填写开户银行支行名称' }
],
settlementBankJointName: [
{ required: true, message: '请填写支行联行号' }
]
};
// 下一步
function next() {
secondForm.value.validate((valid) => {
if (valid) {
loading.value = true;
applySecond(form)
.then((res) => {
loading.value = false;
if (res.success) emit('change', 2);
})
.catch(() => {
loading.value = false;
});
}
});
}
onMounted(() => {
if (props.content && Object.keys(props.content).length) {
Object.assign(form, JSON.parse(JSON.stringify(props.content)));
}
});
</script>
<style lang="scss" scoped>
h4 {

View File

@@ -56,139 +56,134 @@
</el-dialog>
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { agreement, applyStatus } from "@/api/shopentry";
import firstApply from "./FirstApply";
import secondApply from "./SecondApply";
import thirdApply from "./ThirdApply";
export default {
components: {
firstApply,
secondApply,
thirdApply,
},
data() {
return {
currentIndex: 0, // 当前步骤
showAgreement: false, // 协议显示
agreementCon: "", // 协议内容
checked: false, // 选中协议
firstData: {}, // 第一步数据
secondData: {}, // 第二步数据
thirdData: {}, // 第三步数据
storeDisable: "", // APPLY OPEN 开店中 CLOSED 关闭 REFUSED 拒绝 APPLYING 申请中,审核
initLoading: true,
agreementAccepted: false, // 是否已同意入驻协议
};
},
methods: {
confirmAgreement() {
this.agreementAccepted = true;
this.showAgreement = false;
sessionStorage.setItem("shopEntryAgreementAccepted", "1");
},
assignApplyData(data) {
const first = [
"companyAddressPath",
"companyAddress",
"companyAddressIdPath",
"companyEmail",
"companyName",
"employeeNum",
"companyPhone",
"legalId",
"legalName",
"licencePhoto",
"legalPhoto",
"licenseNum",
"linkName",
"linkPhone",
"registeredCapital",
"scope",
];
const second = [
"settlementBankAccountName",
"settlementBankAccountNum",
"settlementBankBranchName",
"settlementBankJointName",
];
const third = [
"goodsManagementCategory",
"storeCenter",
"storeDesc",
"storeLogo",
"storeName",
"storeAddressIdPath",
"storeAddressPath",
"storeAddressDetail",
];
this.storeDisable = data.storeDisable || "";
Object.assign(this.firstData, Object.fromEntries(first.map((key) => [key, data[key]])));
Object.assign(this.secondData, Object.fromEntries(second.map((key) => [key, data[key]])));
Object.assign(this.thirdData, Object.fromEntries(third.map((key) => [key, data[key]])));
},
getArticle() {
// 入驻协议:库中无 STORE_REGISTER 文章时 result 为 null,不能直接读 content
agreement().then((res) => {
this.agreementCon = res.result?.content || "";
});
},
getData(status) {
applyStatus().then((res) => {
if (!res.success) {
return;
}
if (!res.result) {
if (status === "init" && !this.agreementAccepted) {
this.showAgreement = true;
}
return;
}
const currentIndex = ref(0); // 当前步骤
const showAgreement = ref(false); // 协议显示
const agreementCon = ref(""); // 协议内容
const checked = ref(false); // 选中协议
const firstData = reactive({}); // 第一步数据
const secondData = reactive({}); // 第二步数据
const thirdData = reactive({}); // 第三步数据
const storeDisable = ref(""); // APPLY OPEN 开店中 CLOSED 关闭 REFUSED 拒绝 APPLYING 申请中,审核
const initLoading = ref(true);
const agreementAccepted = ref(false); // 是否已同意入驻协议
this.assignApplyData(res.result);
if (status === "init") {
this.agreementAccepted = true;
sessionStorage.setItem("shopEntryAgreementAccepted", "1");
if (this.storeDisable === "APPLY") {
this.currentIndex = 0;
} else {
this.currentIndex = 3;
}
}
}).finally(() => {
if (status === "init") {
this.initLoading = false;
}
});
},
refreshApplyData() {
return applyStatus().then((res) => {
if (res.success && res.result) {
this.assignApplyData(res.result);
}
}).catch((err) => {
console.warn('获取入驻申请状态失败', err);
});
},
// 下一步:先拉最新申请数据,再切步骤,避免第三步挂载后再改 content 触发校验
async nextPage(step) {
if (typeof step !== "number") return;
if (step > 0 && step <= 3) {
await this.refreshApplyData();
function confirmAgreement() {
agreementAccepted.value = true;
showAgreement.value = false;
sessionStorage.setItem("shopEntryAgreementAccepted", "1");
}
function assignApplyData(data) {
const first = [
"companyAddressPath",
"companyAddress",
"companyAddressIdPath",
"companyEmail",
"companyName",
"employeeNum",
"companyPhone",
"legalId",
"legalName",
"licencePhoto",
"legalPhoto",
"licenseNum",
"linkName",
"linkPhone",
"registeredCapital",
"scope",
];
const second = [
"settlementBankAccountName",
"settlementBankAccountNum",
"settlementBankBranchName",
"settlementBankJointName",
];
const third = [
"goodsManagementCategory",
"storeCenter",
"storeDesc",
"storeLogo",
"storeName",
"storeAddressIdPath",
"storeAddressPath",
"storeAddressDetail",
];
storeDisable.value = data.storeDisable || "";
Object.assign(firstData, Object.fromEntries(first.map((key) => [key, data[key]])));
Object.assign(secondData, Object.fromEntries(second.map((key) => [key, data[key]])));
Object.assign(thirdData, Object.fromEntries(third.map((key) => [key, data[key]])));
}
function getArticle() {
// 入驻协议:库中无 STORE_REGISTER 文章时 result 为 null,不能直接读 content
agreement().then((res) => {
agreementCon.value = res.result?.content || "";
});
}
function getData(status) {
applyStatus().then((res) => {
if (!res.success) {
return;
}
if (!res.result) {
if (status === "init" && !agreementAccepted.value) {
showAgreement.value = true;
}
this.currentIndex = step;
},
},
created() {
this.agreementAccepted = sessionStorage.getItem("shopEntryAgreementAccepted") === "1";
},
mounted() {
this.getData("init");
this.getArticle();
},
};
return;
}
assignApplyData(res.result);
if (status === "init") {
agreementAccepted.value = true;
sessionStorage.setItem("shopEntryAgreementAccepted", "1");
if (storeDisable.value === "APPLY") {
currentIndex.value = 0;
} else {
currentIndex.value = 3;
}
}
}).finally(() => {
if (status === "init") {
initLoading.value = false;
}
});
}
function refreshApplyData() {
return applyStatus().then((res) => {
if (res.success && res.result) {
assignApplyData(res.result);
}
}).catch((err) => {
console.warn('获取入驻申请状态失败', err);
});
}
// 下一步:先拉最新申请数据,再切步骤,避免第三步挂载后再改 content 触发校验
async function nextPage(step) {
if (typeof step !== "number") return;
if (step > 0 && step <= 3) {
await refreshApplyData();
}
currentIndex.value = step;
}
// 初始化(原 created 钩子逻辑)
agreementAccepted.value = sessionStorage.getItem("shopEntryAgreementAccepted") === "1";
onMounted(() => {
getData("init");
getArticle();
});
</script>
<style lang="scss" scoped>
.content {

View File

@@ -1,6 +1,6 @@
<template>
<div class="person-msg">
<el-form ref="thirdForm" :model="form" :rules="rules" label-width="140px">
<el-form ref="thirdFormRef" :model="form" :rules="rules" label-width="140px">
<h4>基础信息</h4>
<el-form-item prop="storeName" label="店铺名称">
<el-input
@@ -92,7 +92,8 @@
<multipleMap ref="map" @callback="getAddress" />
</div>
</template>
<script>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { Message } from "@/utils/message";
import { Delete, View } from '@element-plus/icons-vue';
import { applyThird } from '@/api/shopentry';
@@ -105,188 +106,184 @@ import { buyerUrl } from '@/plugins/request.js';
import multipleMap from "@/components/map/multiple-map";
export default {
emits: ['change'],
props: {
content: {
default: () => ({}),
type: Object
}
},
components: { multipleMap, View, Delete },
data () {
return {
loading: false, // 加载状态
uploadLoading: false, // 上传加载状态
action: buyerUrl + '/buyer/common/upload/file', // 上传地址
accessToken: {}, // 验证token
previewPicture: '', // 预览图片
const emit = defineEmits(['change']);
visible: false, // 图片预览
form: { // 表单数据
storeLogo: [],
goodsManagementCategory: [],
storeName: "",
storeAddressDetail: "",
storeDesc: "",
storeAddressIdPath: "",
storeAddressPath: "",
storeCenter: "",
},
rules: { // 验证规则
goodsManagementCategory: [
{ required: true, message: '请选择店铺经营类目' }
],
storeName: [{ required: true, message: '请填写店铺名称' }],
storeLogo: [{ required: true, message: '请上传店铺logo' }],
storeDesc: [{ required: true, message: '请填写店铺简介' }],
storeCenter: [{ required: true, message: '请选择店铺位置' }],
storeAddressIdPath: [{ required: true, message: '请选择店铺位置' }],
storeAddressDetail: [{ required: true, message: '请输入店铺详细地址' }]
},
categoryList: [] // 分类数据
};
},
methods: {
// 下一步
next () {
this.$refs.thirdForm.validate((valid) => {
if (valid) {
this.loading = true;
let params = JSON.parse(JSON.stringify(this.form));
params.storeLogo = this.form.storeLogo.toString();
params.goodsManagementCategory = this.form.goodsManagementCategory.toString();
applyThird(params)
.then((res) => {
this.loading = false;
if (res.success) this.$emit('change', 3);
})
.catch(() => {
this.loading = false;
});
}
});
},
// 上传之前
beforeUpload (file) {
this.uploadLoading = true;
if (this.form.storeLogo.length >= 1) {
Message.warning('最多上传一张图片')
this.uploadLoading = false;
return false;
}
return this.validateUploadFile(file);
},
validateUploadFile (file) {
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
const isAllowedType = allowedTypes.includes(file.type);
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isAllowedType) {
this.uploadLoading = false;
Message.warning('上传文件格式不正确');
return false;
}
if (!isLt2M) {
this.uploadLoading = false;
Message.warning('文件大小不能超过2M');
return false;
}
return true;
},
// 上传成功回调
handleSuccess (res, file) {
this.uploadLoading = false;
this.form.storeLogo = [res.result];
},
// 上传失败
uploadErr () {
this.uploadLoading = false;
},
// 查看图片
handleView (item) {
this.previewPicture = item;
this.visible = true;
},
// 删除图片
handleRemove (index, listName) {
this.form[listName].splice(index, 1);
},
// 选择坐标回调
getAddress (val) {
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
this.form["storeAddressPath"] = paths;
this.form["storeAddressIdPath"] = ids;
this.form.storeCenter = val.data[val.data.length - 1].center
}else{
this.form["storeAddressPath"] = val.data.addr;
this.form["storeAddressIdPath"] = val.data.addrId;
this.form['storeCenter'] = val.data.position.lng + ',' + val.data.position.lat
;
}
},
// 获取商品分类
getCategoryList () {
getCategory(0).then((res) => {
if (res.success) {
this.categoryList = res.result || [];
this.syncCategorySelection();
}
});
},
normalizeCategoryId (id) {
return id == null ? '' : String(id);
},
syncCategorySelection () {
if (!Array.isArray(this.form.goodsManagementCategory)) {
this.form.goodsManagementCategory = [];
return;
}
this.form.goodsManagementCategory = this.form.goodsManagementCategory.map(
(id) => this.normalizeCategoryId(id)
);
},
createEmptyForm () {
return {
storeLogo: [],
goodsManagementCategory: [],
storeName: "",
storeAddressDetail: "",
storeDesc: "",
storeAddressIdPath: "",
storeAddressPath: "",
storeCenter: "",
};
},
initFormFromContent (content) {
const src = content && Object.keys(content).length
? JSON.parse(JSON.stringify(content))
: {};
this.form = {
...this.createEmptyForm(),
...src,
};
this.form.storeLogo = src.storeLogo
? String(src.storeLogo).split(",").filter(Boolean)
: [];
this.form.goodsManagementCategory = src.goodsManagementCategory
? String(src.goodsManagementCategory).split(",").filter(Boolean)
: [];
["storeName", "storeAddressDetail", "storeDesc", "storeAddressIdPath", "storeAddressPath", "storeCenter"].forEach((key) => {
if (this.form[key] == null) this.form[key] = "";
});
},
},
created () {
this.initFormFromContent(this.content);
},
mounted () {
this.accessToken.accessToken = storage.getItem('accessToken');
this.getCategoryList();
const props = defineProps({
content: {
default: () => ({}),
type: Object
}
});
const thirdFormRef = ref(null);
const loading = ref(false); // 加载状态
const uploadLoading = ref(false); // 上传加载状态
const action = buyerUrl + '/buyer/common/upload/file'; // 上传地址
const accessToken = reactive({}); // 验证token
const previewPicture = ref(''); // 预览图片
const visible = ref(false); // 图片预览
const form = ref({ // 表单数据
storeLogo: [],
goodsManagementCategory: [],
storeName: "",
storeAddressDetail: "",
storeDesc: "",
storeAddressIdPath: "",
storeAddressPath: "",
storeCenter: "",
});
const rules = { // 验证规则
goodsManagementCategory: [
{ required: true, message: '请选择店铺经营类目' }
],
storeName: [{ required: true, message: '请填写店铺名称' }],
storeLogo: [{ required: true, message: '请上传店铺logo' }],
storeDesc: [{ required: true, message: '请填写店铺简介' }],
storeCenter: [{ required: true, message: '请选择店铺位置' }],
storeAddressIdPath: [{ required: true, message: '请选择店铺位置' }],
storeAddressDetail: [{ required: true, message: '请输入店铺详细地址' }]
};
const categoryList = ref([]); // 分类数据
// 下一步
function next () {
thirdFormRef.value.validate((valid) => {
if (valid) {
loading.value = true;
let params = JSON.parse(JSON.stringify(form.value));
params.storeLogo = form.value.storeLogo.toString();
params.goodsManagementCategory = form.value.goodsManagementCategory.toString();
applyThird(params)
.then((res) => {
loading.value = false;
if (res.success) emit('change', 3);
})
.catch(() => {
loading.value = false;
});
}
});
}
// 上传之前
function beforeUpload (file) {
uploadLoading.value = true;
if (form.value.storeLogo.length >= 1) {
Message.warning('最多上传一张图片')
uploadLoading.value = false;
return false;
}
return validateUploadFile(file);
}
function validateUploadFile (file) {
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
const isAllowedType = allowedTypes.includes(file.type);
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isAllowedType) {
uploadLoading.value = false;
Message.warning('上传文件格式不正确');
return false;
}
if (!isLt2M) {
uploadLoading.value = false;
Message.warning('文件大小不能超过2M');
return false;
}
return true;
}
// 上传成功回调
function handleSuccess (res, file) {
uploadLoading.value = false;
form.value.storeLogo = [res.result];
}
// 上传失败
function uploadErr () {
uploadLoading.value = false;
}
// 查看图片
function handleView (item) {
previewPicture.value = item;
visible.value = true;
}
// 删除图片
function handleRemove (index, listName) {
form.value[listName].splice(index, 1);
}
// 选择坐标回调
function getAddress (val) {
if(val.type === 'select'){
const paths = val.data.map(item => item.name).join(',')
const ids = val.data.map(item => item.id).join(',')
form.value["storeAddressPath"] = paths;
form.value["storeAddressIdPath"] = ids;
form.value.storeCenter = val.data[val.data.length - 1].center
}else{
form.value["storeAddressPath"] = val.data.addr;
form.value["storeAddressIdPath"] = val.data.addrId;
form.value['storeCenter'] = val.data.position.lng + ',' + val.data.position.lat
;
}
}
// 获取商品分类
function getCategoryList () {
getCategory(0).then((res) => {
if (res.success) {
categoryList.value = res.result || [];
syncCategorySelection();
}
});
}
function normalizeCategoryId (id) {
return id == null ? '' : String(id);
}
function syncCategorySelection () {
if (!Array.isArray(form.value.goodsManagementCategory)) {
form.value.goodsManagementCategory = [];
return;
}
form.value.goodsManagementCategory = form.value.goodsManagementCategory.map(
(id) => normalizeCategoryId(id)
);
}
function createEmptyForm () {
return {
storeLogo: [],
goodsManagementCategory: [],
storeName: "",
storeAddressDetail: "",
storeDesc: "",
storeAddressIdPath: "",
storeAddressPath: "",
storeCenter: "",
};
}
function initFormFromContent (content) {
const src = content && Object.keys(content).length
? JSON.parse(JSON.stringify(content))
: {};
form.value = {
...createEmptyForm(),
...src,
};
form.value.storeLogo = src.storeLogo
? String(src.storeLogo).split(",").filter(Boolean)
: [];
form.value.goodsManagementCategory = src.goodsManagementCategory
? String(src.goodsManagementCategory).split(",").filter(Boolean)
: [];
["storeName", "storeAddressDetail", "storeDesc", "storeAddressIdPath", "storeAddressPath", "storeCenter"].forEach((key) => {
if (form.value[key] == null) form.value[key] = "";
});
}
initFormFromContent(props.content);
onMounted(() => {
accessToken.accessToken = storage.getItem('accessToken');
getCategoryList();
});
</script>
<style lang="scss" scoped>
h4 {

View File

@@ -55,7 +55,9 @@
</div>
</template>
<script>
<script setup>
import { computed, onBeforeUnmount, onMounted } from "vue";
import { useRouter } from "vue-router";
import {
Bell,
ChatDotRound,
@@ -78,6 +80,12 @@ import {
import menuList from "./menu";
import Storage from "@/plugins/storage.js";
import { refreshUnreadMessage } from "@/utils/unreadMessage";
import { useAppStore } from "@/stores/app";
defineOptions({ name: "Home" });
const router = useRouter();
const appStore = useAppStore();
const MENU_ICONS = {
Bell,
@@ -99,44 +107,35 @@ const MENU_ICONS = {
Warning,
};
export default {
components: { House, User },
name: "Home",
data() {
return {
menuList,
};
},
computed: {
userInfo() {
if (Storage.getItem("userInfo")) {
return JSON.parse(Storage.getItem("userInfo"));
}
return {};
},
unreadMsg() {
return Number(this.$store.state.unreadMsg) || 0;
},
unreadLabel() {
return this.unreadMsg > 99 ? "99+" : String(this.unreadMsg);
},
},
mounted() {
document.body.classList.add("buyer-user-center");
refreshUnreadMessage();
},
beforeUnmount() {
document.body.classList.remove("buyer-user-center");
},
methods: {
menuIcon(name) {
return MENU_ICONS[name] || Goods;
},
onSelect(name) {
this.$router.push({ name });
},
},
};
const userInfo = computed(() => {
if (Storage.getItem("userInfo")) {
return JSON.parse(Storage.getItem("userInfo"));
}
return {};
});
const unreadMsg = computed(() => appStore.unreadMsg);
const unreadLabel = computed(() => {
return unreadMsg.value > 99 ? "99+" : String(unreadMsg.value);
});
function menuIcon(name) {
return MENU_ICONS[name] || Goods;
}
function onSelect(name) {
router.push({ name });
}
onMounted(() => {
document.body.classList.add("buyer-user-center");
refreshUnreadMessage();
});
onBeforeUnmount(() => {
document.body.classList.remove("buyer-user-center");
});
</script>
<style scoped lang="scss">