This commit is contained in:
2026-03-06 13:41:22 +08:00
commit f39c6a705f
394 changed files with 159599 additions and 0 deletions

View File

@ -0,0 +1,175 @@
<template>
<uni-swipe-action :key="data.id" class="address-item-container">
<uni-swipe-action-item
class="address-item-content"
:right-options="{}"
:auto-close="false"
@click="$emit('jumpToDetails', data)"
>
<view
class="flex-row-start address-item"
>
<view class="address-item-clickable" @click="$emit('toDetails', data)">
<view class="address-content">
<view class="flex-row-start">
<text
class="fs-20 PingFangSC-Regular default-tip"
v-if="data.is_default"
>默认</text
>
<text class="fs-30 app-fc-main PingFangSC-Medium address-user">
{{ data.name || "" }}
</text>
<text class="fs-30 app-fc-main PingFangSC-Medium">
{{ data.phone || "" }}
</text>
</view>
<view
class="app-fc-normal fs-26 PingFangSC-Regular app-text-ellipse address-name"
>
{{ data.address || "" }}
</view>
</view>
</view>
<view class="address-right-content">
<view class="switch-container" @tap.stop="handleSwitchClick">
<switch
:checked="data.is_default"
@change="onDefaultChange"
color="#FF19A0"
class="default-switch"
/>
</view>
<image
class="address-edit-icon"
src="@/static/images/address_edit.png"
@click.stop="$emit('toDetails', data)"
/>
</view>
</view>
<view slot="right" class="flex-center address-right">
<image
class="address-delete"
src="@/static/images/mine_delete.png"
@click.stop="$emit('delete', data)"
/>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</template>
<script>
export default {
props: {
data: {
type: Object,
default: () => {},
},
},
data() {
return {};
},
methods: {
handleSwitchClick(e) {
console.log('handleSwitchClick 触发');
// 手动触发开关切换
const newValue = !this.data.is_default;
this.emitSetDefault(newValue);
},
onDefaultChange(e) {
console.log('onDefaultChange 触发:', e, e.detail, this.data);
// 阻止事件冒泡到父元素,避免触发编辑
if (e && e.stopPropagation) {
e.stopPropagation();
}
// 获取新的开关状态
const newValue = e.detail?.value !== undefined ? e.detail.value : (e.detail?.checked !== undefined ? e.detail.checked : !this.data.is_default);
console.log('新的开关状态:', newValue);
this.emitSetDefault(newValue);
},
emitSetDefault(newValue) {
// 发送设置默认地址事件
const newData = {
...this.data,
is_default: newValue ? 1 : 0
};
console.log('发送 setDefault 事件:', newData);
this.$emit('setDefault', newData);
}
},
};
</script>
<style lang="scss" scoped>
.address-item-container {
.address-item {
width: calc(100vw - 32rpx * 2);
padding: 40rpx 30rpx;
background: #fff;
border-radius: 30rpx 30rpx 30rpx 30rpx;
box-sizing: border-box;
margin-bottom: 20rpx;
margin-left: 32rpx;
margin-right: 32rpx;
.address-right-content {
display: flex;
align-items: center;
gap: 20rpx;
flex-shrink: 0;
}
.switch-container {
display: flex;
align-items: center;
padding: 10rpx;
margin: -10rpx;
}
.default-switch {
transform: scale(0.8);
transform-origin: right center;
}
.address-edit-icon {
width: 48rpx;
height: 48rpx;
}
.address-item-clickable {
flex: 1;
display: flex;
flex-direction: column;
}
.address-content {
flex: 1;
overflow: hidden;
.default-tip {
padding: 6rpx;
color: #40ae36;
background: rgba(64, 174, 54, 0.2);
margin-right: 16rpx;
}
.address-name {
margin-top: 12rpx;
}
.address-user {
margin-right: 16rpx;
}
}
}
.address-right {
padding: 0 32rpx 0 0;
.address-delete {
width: 88rpx;
height: 88rpx;
}
}
}
</style>

View File

@ -0,0 +1,407 @@
<template>
<view class="address-edit">
<view class="edit-form">
<form-cell
title="姓名"
placeholderText="请输入姓名"
:value="addressInfo.name"
:showRightArrow="false"
@onChange="(value) => onChange(value, 'name')"
/>
<form-cell
title="手机号"
placeholderText="请输入手机号"
:value="addressInfo.phone"
:showRightArrow="false"
@onChange="(value) => onChange(value, 'phone')"
/>
<form-cell
title="省市区"
type="checkAddress"
:value="regionText"
:defaultRegion="[provinceIndex, cityIndex, areaIndex]"
@onChange="onRegionChange"
/>
<form-cell
title="详细地址"
placeholderText="请输入详细地址"
:value="addressInfo.address"
:showRightArrow="false"
:noBorder="true"
@onChange="(value) => onChange(value, 'address')"
/>
</view>
<view class="edit-form default">
<form-cell
title="设为默认地址"
type="custom"
class="default-wrapper"
:showRightArrow="false"
:noBorder="true"
>
<view class="flex-row-end default-cell" slot="right">
<switch
:checked="isDefault"
@change="onSwitchChange"
color="#FF19A0"
class="default-switch"
/>
</view>
</form-cell>
</view>
<view class="flex-center edit-bottom">
<view
class="flex-center PingFangSC-Semibold fs-30 confirm-btn"
@click="save"
>
保存
</view>
<view class="bottom-safe-area"></view>
</view>
</view>
</template>
<script>
import FormCell from "@/components/FormCell.vue";
import { createAddress, updateAddress, getAddressInfo } from "../../../api/address";
import { pcaData } from "@/api/areas.js";
import appConfig from "../../../constants/app.config";
const QQMapWX = require("./static/qqmap-wx-jssdk.min.js");
var qqmapsdk;
export default {
components: {
FormCell,
},
data() {
return {
addressInfo: {
address_id: 0,
name: "",
phone: "",
address: "",
province: "",
provinceId: "",
city: "",
cityId: "",
area: "",
areaId: "",
},
isDefault: false,
provinceIndex: 0,
cityIndex: 0,
areaIndex: 0,
regionList: pcaData,
isAdd: true,
id: 0,
lastSaveTime: 0, // 上次保存时间戳
};
},
computed: {
userInfo() {
return this.$store.state?.user?.userInfo || {};
},
regionText() {
const parts = [
this.addressInfo.province,
this.addressInfo.city,
this.addressInfo.area
].filter(item => item && item.trim());
return parts.join(' ');
}
},
watch: {
"addressInfo.provinceId": {
async handler(val) {
this.provinceIndex = this.regionList.findIndex(
(v) => v.area_id === val
);
},
deep: true,
},
"addressInfo.cityId": {
handler(val) {
const citytList = this.regionList?.[this.provinceIndex]?.children || [];
this.cityIndex = citytList.findIndex((v) => v.area_id === val);
},
},
"addressInfo.areaId": {
handler(val) {
const citytList = this.regionList?.[this.provinceIndex]?.children || [];
const areaList = citytList?.[this.cityIndex]?.children || [];
this.areaIndex = areaList.findIndex((v) => v.area_id === val);
},
},
},
onLoad(options) {
const { isAdd, id } = options;
this.isAdd = !!isAdd;
if (id) {
this.id = id;
this.addressInfo.address_id = id;
this.getInfo(id);
}
qqmapsdk = new QQMapWX({
key: appConfig.tencentMapKey,
});
},
methods: {
onChange(value, key) {
this.addressInfo[key] = value;
},
onSwitchChange(e) {
this.isDefault = e.detail.value;
},
onRegionChange(data) {
const [province, city, area] = data;
this.addressInfo.province = province.label;
this.addressInfo.provinceId = province.value;
this.addressInfo.city = city.label;
this.addressInfo.cityId = city.value;
this.addressInfo.area = area.label;
this.addressInfo.areaId = area.value;
},
getInfo(id) {
uni.showLoading({ title: "加载中..." });
getAddressInfo(id).then((res) => {
uni.hideLoading();
const info = res.info || res.data || res;
// 填充地址信息
this.addressInfo.address_id = info.address_id || info.id || id;
this.addressInfo.name = info.name || info.recipient_name || '';
this.addressInfo.phone = info.phone || '';
this.addressInfo.address = info.address || info.full_address || '';
this.addressInfo.province = info.province || info.sheng || '';
this.addressInfo.provinceId = info.province_id;
this.addressInfo.city = info.city || info.shi || '';
this.addressInfo.cityId = info.city_id;
this.addressInfo.area = info.district || info.area || info.qu || '';
this.addressInfo.areaId = info.district_id;
// 设置默认地址状态
this.isDefault = !!info.is_default || !!info.isDefault;
// 设置省市区索引(用于地区选择器)
if (this.addressInfo.provinceId) {
this.provinceIndex = this.regionList.findIndex(
(v) => v.area_id === this.addressInfo.provinceId
);
}
if (this.addressInfo.cityId && this.provinceIndex >= 0) {
const cityList = this.regionList[this.provinceIndex]?.children || [];
this.cityIndex = cityList.findIndex(
(v) => v.area_id === this.addressInfo.cityId
);
}
if (this.addressInfo.areaId && this.cityIndex >= 0) {
const cityList = this.regionList[this.provinceIndex]?.children || [];
const areaList = cityList[this.cityIndex]?.children || [];
this.areaIndex = areaList.findIndex(
(v) => v.area_id === this.addressInfo.areaId
);
}
}).catch((err) => {
uni.hideLoading();
console.error('获取地址详情失败:', err);
uni.showToast({
title: '获取地址信息失败',
icon: 'none'
});
});
},
// 获取经纬度
getLatLotData() {},
save() {
// 防抖处理1秒内只能调用1次接口
const now = Date.now();
if (now - this.lastSaveTime < 1000) {
uni.showToast({
title: '操作过于频繁,请稍后再试',
icon: 'none',
duration: 1500
});
return;
}
// 更新上次保存时间
this.lastSaveTime = now;
const {
province = "",
city = "",
area = "",
address = "",
} = this.addressInfo;
// const addressText = province + city + area + address
// uni.request({
// url: `https://apis.map.qq.com/ws/geocoder/v1/?address=${addressText}&key=${appConfig.tencentMapKey}`,
// method: 'GET',
// success: (res) => {
// console.log('地址转经纬度----->', res?.data)
// }
// })
qqmapsdk.geocoder({
address: province + city + area + address,
sig: appConfig.tencentSecret,
success: (res) => {
console.log("解析地址成功----->", res);
const { lat, lng } = res?.result?.location || {};
this.editAddress(lat, lng);
},
fail: (error) => {
console.error("解析地址失败----->", error);
this.editAddress();
},
});
return;
},
editAddress(l) {
const addressParam = {
user_id: this.userInfo.userID,
// address_id: this.addressInfo.address_id,
recipient_name: this.addressInfo.name.trim(),
phone: this.addressInfo.phone.trim(),
is_default: this.isDefault,
province: this.addressInfo.province,
province_id: this.addressInfo.provinceId,
city: this.addressInfo.city,
city_id: this.addressInfo.cityId,
district: this.addressInfo.area,
district_id: this.addressInfo.areaId,
full_address: this.addressInfo.address.trim(),
region_id: 1
};
console.log("addressParam", addressParam);
const error = Object.keys(addressParam).some((v) => {
if (!addressParam[v] && v !== "address_id" && v !== "is_default") {
if (v === "sheng_id") {
uni.showToast({ icon: "none", title: "请选择省市区" });
return true;
}
if (v === "phone") {
uni.showToast({ icon: "none", title: "请输入手机号" });
return true;
}
if (v === "name") {
uni.showToast({ icon: "none", title: "请输入姓名" });
return true;
}
if (v === "address") {
uni.showToast({ icon: "none", title: "请输入详细地址" });
return true;
}
}
if (v === "phone" && !/^1[0-9]{10}$/.test(addressParam[v])) {
uni.showToast({ icon: "none", title: "手机号有误" });
return true;
}
});
if (error) {
return;
}
// 如果是编辑模式,需要添加 address_id
if (!this.isAdd && this.addressInfo.address_id) {
addressParam.id = this.addressInfo.address_id;
}
uni.showLoading({ title: "处理中..." });
// 根据 isAdd 标志决定调用创建还是更新接口
const savePromise = this.isAdd
? createAddress(addressParam)
: updateAddress(addressParam);
savePromise.then((r) => {
uni.hideLoading();
uni.showToast({ icon: "success", title: "保存成功" });
// const eventChannel = this.getOpenerEventChannel();
// eventChannel.emit("refreshAddress");
uni.navigateBack();
}).catch((err) => {
uni.hideLoading();
console.error('保存地址失败:', err);
uni.showToast({
title: err?.msg || err?.message || '保存失败,请稍后重试',
icon: 'none'
});
});
},
},
};
</script>
<style lang="scss" scoped>
.address-edit {
min-height: 100vh;
width: 100%;
box-sizing: border-box;
background: #F7F8FA;
padding: 20rpx 20rpx 0;
display: flex;
flex-direction: column;
.edit-form {
background: #fff;
border-radius: 16rpx;
padding: 0 24rpx;
margin-bottom: 20rpx;
&.default {
margin-top: 20rpx;
}
}
.default-wrapper {
::v-deep {
.form-cell .form-label {
color: #3D3D3D;
}
}
.default-cell {
width: 100%;
.default-switch {
transform: scale(0.8);
transform-origin: right center;
}
}
}
.edit-bottom {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #fff;
border-radius: 32rpx 32rpx 0 0;
padding: 24rpx 24rpx 0;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
box-sizing: border-box;
.confirm-btn {
width: 100%;
margin: auto;
margin-bottom: 24rpx;
border-radius: 100rpx;
background: #FF19A0;
color: #fff;
padding: 32rpx 0rpx;
text-align: center;
font-size: 32rpx;
font-weight: 600;
}
.bottom-safe-area {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}
}
</style>

View File

@ -0,0 +1,160 @@
<template>
<view class="address-container">
<list-page-temp
class="address-list-inner"
:getDataPromise="getAddressList"
:reloadFlag="reloadFlag"
:requestData="requestData"
>
<template v-slot:item="{ data }">
<address-item
:data="data"
@toDetails="toDetails(data)"
@delete="deleteAction"
@refresh="handleRefresh"
/>
</template>
<view slot="bottom" class="place-view"></view>
</list-page-temp>
<view class="flex-center address-list-bottom">
<view class="flex-row-center address-add" @click="jumpToAdd">
<text class="fs-60 app-fc-white PingFangSC-Semibold add-icon">+</text>
<text class="fs-30 app-fc-white PingFangSC-Semibold add-icon">
添加地址
</text>
</view>
</view>
<pop-up-modal
v-if="showModal"
content="确定要删除该地址信息吗?"
@confirm="deleteAddress(data)"
@cancel="showModal = false"
/>
</view>
</template>
<script>
import AddressItem from "./components/AddressItem.vue";
import PopUpModal from "../../../components/PopUpModal.vue";
import { delAddress, getAddressList } from "../../../api/address";
export default {
components: {
AddressItem,
PopUpModal,
},
data() {
return {
reloadFlag: 0,
requestData: {},
deleteData: {},
showModal: false,
isSelectAddress: false, // 从订单页跳转过来选择地址的标识
selectAddressId: "", // 选中的地址id
};
},
computed: {
userInfo() {
return this.$store.state?.user?.userInfo || {};
}
},
watch: {
userInfo: {
immediate: true,
handler(newVal) {
if (newVal && newVal.userID) {
this.requestData = {
user_id: newVal.userID
};
}
}
}
},
onShow() {
this.reloadFlag = Math.random();
},
onLoad(options) {
const { typeSelect, addressId } = options;
this.isSelectAddress = typeSelect;
this.selectAddressId = addressId;
},
methods: {
getAddressList,
toDetails(data) {
if (this.isSelectAddress) {
this.selectAddressId = data?.id || "";
uni.navigateBack();
uni.$emit('selectAddress', data)
return;
}
uni.navigateTo({
url: `/pages/client/address/edit?id=${data?.id || ""}`,
});
},
jumpToAdd() {
uni.navigateTo({
url: `/pages/client/address/edit?isAdd=1`,
});
},
deleteAction(data) {
this.deleteData = data;
this.showModal = true;
},
deleteAddress() {
this.showModal = false;
console.log("this.deleteData", this.deleteData);
delAddress(this.deleteData.id).then(() => {
this.reloadFlag = Math.random();
});
},
handleRefresh() {
// 刷新地址列表
this.reloadFlag = Math.random();
},
},
};
</script>
<style lang="scss" scoped>
.address-container {
height: 100%;
width: 100%;
box-sizing: border-box;
background: #fbf8fc;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
padding-top: 20rpx;
.address-list-inner {
height: 100%;
}
.place-view {
height: 90rpx;
}
.address-list-bottom {
position: fixed;
bottom: 0;
bottom: constant(safe-area-inset-bottom);
bottom: env(safe-area-inset-bottom);
left: 0;
width: 100%;
.address-add {
width: 630rpx;
height: 90rpx;
background: $app_color_main;
border-radius: 45rpx 45rpx 45rpx 45rpx;
.add-icon {
margin-right: 20rpx;
line-height: 60rpx;
}
}
}
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,395 @@
<template>
<view class="loginContainer">
<view class="body">
<image class="login-ground-img" :src="`${imgPrefix}loginGroundImg.png`" mode="widthFix" />
<view class="btnConent">
<button v-show="!checked" class="loginBtn" @click="unCheckAndGetPhoneNumber">
手机快捷登录
</button>
<button v-show="checked" class="loginBtn" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
手机快捷登录
</button>
<view class="notLoginBtn" @click="goBack">
暂不登录
</view>
</view>
</view>
<view class="footer">
<view class="radioWrapper">
<view class="checkbox-wrapper" @click.stop="changeChecked">
<view class="checkbox" :class="{ 'checkbox-checked': checked }">
<image v-if="checked" class="check-icon" :src="require('@/static/images/y.png')" />
</view>
<!-- <view v-if="uncheckMessageDialog" class="tooltip">
<view class="tooltip-content">请先勾选同意后再进行登录</view>
<view class="tooltip-arrow"></view>
</view> -->
</view>
<text class="radioText">
请阅读并同意
<text class="color" @click.stop="ptfwxy">帮宠到家平台服务协议</text>
<text class="color" @click.stop="ysxy">隐私协议</text>
</text>
</view>
<view class="bottom-safe-area" />
</view>
</view>
</template>
<script>
import appConfig from "@/constants/app.config";
import { login, getPhone } from "@/api/login";
import { imgPrefix } from "@/utils/common";
export default {
data() {
return {
appConfig,
imgPrefix,
checked: false,
uncheckMessageDialog: false,
};
},
mounted() { },
methods: {
ptfwxy() {
this.jumpTo("/pages/richText/index?code=ptfwxy");
},
ysxy() {
this.jumpTo("/pages/richText/index?code=ysxy");
},
goBack() {
this.loginAction();
},
jumpTo(url) {
uni.navigateTo({
url,
});
},
changeChecked() {
uni.requestSubscribeMessage({
tmplIds: ['QoTeQwj4xw2UQMK5jI67MzAVOo6og76oqZ7BDIJW7cE', 'GPWlTkaNbi7JqvxltLKuZZMtKedSZfEKlirV7yOUu-0'],// TEMPLATE_ID替换为选用的模版id
success(res) {
uni.hideLoading()
if (res['QoTeQwj4xw2UQMK5jI67MzAVOo6og76oqZ7BDIJW7cE'] === 'accept') {
// setSubscribeStatus(true, '已订阅')
uni.showToast({
title: '订阅成功',
icon: 'success',
})
}
if (res['GPWlTkaNbi7JqvxltLKuZZMtKedSZfEKlirV7yOUu-0'] === 'accept') {
// setSubscribeStatus(true, '已订阅')
uni.showToast({
title: '订阅成功',
icon: 'success',
})
}
},
fail() {
uni.showToast({
title: '订阅失败',
icon: 'error',
})
},
complete: () => {
// isSubscribing = false
},
})
this.checked = !this.checked;
},
unCheckAndGetPhoneNumber() {
uni.showToast({
title: '请先勾选,同意后再进行登录',
icon: 'none',
duration: 2000
});
},
async getPhoneNumber(e) {
// console.log(e,'?')
// return
// 检查是否有 code
if (!e.detail.code) {
uni.showToast({
title: "获取手机号失败,请重试",
icon: "none",
});
return;
}
uni.showLoading({
title: "获取手机号中...",
icon: "none",
mask: true,
});
try {
// 登录前先记录是否有邀请码(referrerID),用于登录后导航判断
const hasReferrer =
this.$store.state?.user && this.$store.state.user.referrerID;
// 拿着code获取手机号
const codeRes = await getPhone(e.detail.code);
const phone = codeRes?.data?.phoneNumber || "";
if (!phone) {
uni.hideLoading();
uni.showToast({
title: "获取手机号失败,请重试",
icon: "none",
});
return;
}
uni.showLoading({
title: "登录中...",
icon: "none",
mask: true,
});
// 手机号登录
login(phone)
.then((res) => {
uni.hideLoading();
this.$store.dispatch("user/setToken", res?.data?.token || "");
this.$store.dispatch("user/setUserInfo", res?.data || {});
// 如果是带邀请码(referrerID)的登录,统一跳转到首页
if (hasReferrer) {
uni.reLaunch({
url: "/pages/client/index/index",
});
return;
}
var pages = getCurrentPages();
if (pages.length === 1) {
uni.reLaunch({
url: "/pages/client/index/index",
});
return;
}
uni.navigateBack();
})
.catch((err) => {
uni.hideLoading();
uni.showToast({
title: err || "登录失败,请重试",
icon: "none",
});
});
} catch (error) {
uni.hideLoading();
console.error("获取手机号失败:", error);
// 检查是否是 access_token 相关错误
const errorMsg = error?.message || error || "";
if (errorMsg.includes("access_token") || errorMsg.includes("invalid credential")) {
uni.showToast({
title: "服务暂时不可用,请稍后重试",
icon: "none",
duration: 3000,
});
} else {
uni.showToast({
title: errorMsg || "获取手机号失败,请重试",
icon: "none",
});
}
}
},
loginAction() {
if (!this.checked) {
uni.showModal({
title: '提示',
content: '请先勾选,同意后再进行登录',
confirmText: '确认',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
// 自动勾选协议
this.checked = true;
// 继续执行登录
this.loginAction();
}
}
});
return;
}
// 登录前先记录是否有邀请码(referrerID),用于登录后导航判断
const hasReferrer =
this.$store.state?.user && this.$store.state.user.referrerID;
uni.showLoading({
title: "加载中...",
icon: "none",
mask: true,
});
login()
.then((res) => {
this.$store.dispatch("user/setToken", res?.data?.token || "");
this.$store.dispatch("user/setUserInfo", res?.data || {});
// 如果是带邀请码(referrerID)的登录,统一跳转到首页
if (hasReferrer) {
uni.reLaunch({
url: "/pages/client/index/index",
});
return;
}
var pages = getCurrentPages();
if (pages.length === 1) {
uni.reLaunch({
url: "/pages/client/index/index",
});
return;
}
uni.navigateBack();
})
.finally(() => {
uni.hideLoading();
});
},
},
};
</script>
<style lang="scss" scoped>
.loginContainer {
height: 100vh;
background-color: #f7f8fa;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 20rpx;
.body {
background-color: #fff;
padding: 20rpx;
border-radius: 16rpx;
margin: 0 20rpx;
.login-ground-img {
width: 100%;
}
.btnConent {
padding: 0rpx 20rpx;
padding-bottom: 100rpx;
margin-top: 30rpx;
.loginBtn {
color: #fff;
background-color: #ff19a0;
border-radius: 300rpx;
padding: 26rpx 0rpx;
text-align: center;
font-size: 32rpx;
border: none;
width: 100%;
&::after {
border: none;
}
}
.notLoginBtn {
background-color: #fff;
color: #FF19A0;
border: 1rpx solid #FF19A0;
border-radius: 300rpx;
padding: 26rpx 0rpx;
text-align: center;
font-size: 32rpx;
margin-top: 60rpx;
}
}
}
.footer {
.radioWrapper {
margin-bottom: 20rpx;
display: flex;
justify-content: center;
align-items: center;
position: relative;
.checkbox-wrapper {
position: relative;
display: flex;
align-items: center;
margin-right: 8rpx;
.checkbox {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid #999999;
display: flex;
align-items: center;
justify-content: center;
background: #FFFFFF;
transition: all 0.3s ease;
flex-shrink: 0;
&.checkbox-checked {
border-color: #FF19A0;
background: #FF19A0;
}
.check-icon {
width: 20rpx;
height: 20rpx;
}
}
.tooltip {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
margin-bottom: 12rpx;
background: rgba(0, 0, 0, 0.85);
border-radius: 12rpx;
padding: 16rpx 24rpx;
white-space: nowrap;
z-index: 100;
.tooltip-content {
font-size: 24rpx;
color: #FFFFFF;
line-height: 1.4;
}
.tooltip-arrow {
position: absolute;
bottom: -12rpx;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 12rpx solid transparent;
border-right: 12rpx solid transparent;
border-top: 12rpx solid rgba(0, 0, 0, 0.85);
}
}
}
.radioText {
font-size: 22rpx;
color: #9b939a;
.color {
color: #FF19A0;
font-size: 22rpx;
}
}
}
}
.bottom-safe-area {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}
</style>

View File

@ -0,0 +1,224 @@
<template>
<view class="butler-edit-container">
<view class="edit-content">
<view class="edit-top">
<image class="edit-bg" src="https://activity.wagoo.live/butler.png" />
<view class="top-inner">
<view class="fs-44 app-font-bold app-fc-mark"> 申请成为小哇</view>
<view class="fs-28 top-text"> 期待您的加入 </view>
</view>
</view>
<view class="edit-inner">
<view class="flex-row-start edit-cell">
<text class="fs-32 app-fc-main cell-title">姓名</text>
<input
class="fs-32 app-fc-main cell-input"
placeholder-class="fs-32 cell-placeholder"
placeholder="请输入姓名"
:value="butlerApplyInfo.name"
@input="nameChange"
/>
</view>
<view class="flex-row-start edit-cell">
<text class="fs-32 app-fc-main cell-title">手机号</text>
<input
class="fs-32 app-fc-main cell-input"
placeholder-class="fs-32 cell-placeholder"
placeholder="请输入手机号"
type="number"
:value="butlerApplyInfo.phone"
@input="phoneChange"
/>
</view>
<view
class="fs-30 flex-center app-fc-white request-btn"
@click="submit"
>
立即申请
</view>
</view>
</view>
<success-modal
v-if="showModal"
@ok="confirmModal"
:title="
butlerApplyInfo.status === 1
? '申请处理中'
: butlerApplyInfo.status === 2
? '您已申请成功'
: butlerApplyInfo.status === 3
? '您已提交申请'
: '提交成功'
"
:message="
butlerApplyInfo.status === 1
? '请耐心等待工作人员联系'
: butlerApplyInfo.status === 2
? ''
: butlerApplyInfo.status === 3
? '审核失败'
: '请耐心等待工作人员联系'
"
/>
</view>
</template>
<script>
import { applyKeeper, applyKeeperDetail } from "@/api/user";
import SuccessModal from "@/components/SuccessModal.vue";
export default {
components: { SuccessModal },
data() {
return {
showModal: false,
butlerApplyInfo: {
name: "",
phone: "",
status: "",
},
};
},
comments: {
SuccessModal,
},
mounted() {
this.getApplyKeeperDetails();
},
methods: {
// 获取管家信息
getApplyKeeperDetails() {
applyKeeperDetail().then((res) => {
this.butlerApplyInfo = res?.info || {};
// 1.已提交 2.已处理
if (
this.butlerApplyInfo.status === 1 ||
this.butlerApplyInfo.status === 2
) {
this.showModal = true;
}
});
},
nameChange(e) {
this.butlerApplyInfo.name = e.detail.value?.trim?.() || "";
},
phoneChange(e) {
this.butlerApplyInfo.phone = e.detail.value?.trim?.() || "";
},
confirmModal() {
this.showModal = false;
uni.navigateBack();
},
submit() {
if (!this.butlerApplyInfo.name) {
uni.showToast({
title: "请填写姓名",
icon: "none",
});
return;
}
if (!this.butlerApplyInfo.phone) {
uni.showToast({
title: "请填写手机号",
icon: "none",
});
return;
}
if (!/^1[0-9]{10}$/.test(this.butlerApplyInfo.phone)) {
uni.showToast({
title: "手机号有误",
icon: "none",
});
return;
}
uni.showLoading({
title: "处理中",
ico: "none",
mask: true,
});
applyKeeper({
name: this.butlerApplyInfo.name,
phone: this.butlerApplyInfo.phone,
}).then((res) => {
uni.hideLoading();
this.showModal = true;
});
},
},
};
</script>
<style lang="scss" scoped>
.butler-edit-container {
height: 100%;
background: #fbf8fc;
padding: 20rpx;
box-sizing: border-box;
.edit-top {
width: 100%;
height: 180rpx;
position: relative;
.edit-bg {
width: 100%;
height: 180rpx;
}
.top-inner {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 36rpx 0 36rpx 100rpx;
.top-text {
color: #3d3d3d;
margin-top: 2rpx;
}
}
}
.edit-content {
background: #fff;
border-radius: 30rpx;
.edit-inner {
padding: 40rpx 32rpx;
box-sizing: border-box;
.edit-cell {
box-sizing: border-box;
padding: 0 44rpx;
height: 92rpx;
background: #f5f5f5;
border-radius: 92rpx;
margin-bottom: 40rpx;
.cell-title {
width: 140rpx;
}
.cell-input {
flex: 1;
}
.cell-placeholder {
color: #acacac;
}
}
}
.request-btn {
width: 360rpx;
height: 90rpx;
background: $app_color_main;
border-radius: 90rpx;
margin: 60rpx auto 0;
}
}
}
</style>

View File

@ -0,0 +1,300 @@
<template>
<view class="flex-row-start cart-item" @click.stop="jumpToDetails">
<view class="flex-center cart-icon-wrapper">
<image class="cart-icon" :src="
(data.mode === 'pay' && data.is_select === 1) ||
(data.mode === 'delete' && data.deleteSelect)
? require('@/static/images/cart_checked.png')
: require('@/static/images/unchecked.png')
" @click.stop="selectAction" />
</view>
<view class="flex-row-start cart-inner">
<image class="goods-img" :src="data.product_pic" mode="aspectFill" />
<view class="goods-content">
<view>
<view class="text-multi-ellipse fs-24 app-fc-main app-font-bold goods-name">
{{ data.product_name || "" }}
</view>
<view class="fs-22 app-fc-cancel cart-shuxing">
{{ data.price_name || "" }}{{ data.price_name ? ";" : ""
}}{{ data.shuxing_name || "" }}
</view>
<!-- <view class="flex-row-start">
<text class="fs-20 sale-btn" v-for="(label, i) in labelList" :key="i"
:class="[i === 0 ? 'sale-today app-fc-mark' : 'app-fc-white']">
{{ label }}
</text>
</view> -->
</view>
<view>
<text class="fs-24 app-fc-mark">
¥
<text class="fs-28 app-font-bold app-fc-mark">
{{ Number(data.product_price) || 0 }}
</text>
<!-- <text class="fs-20" style="margin-left: 4rpx;color: #FF19A0;">到手价</text>
<text class="fs-20 origin-price">
¥{{ Number(data.item_price) || 0 }}
</text> -->
</text>
<!-- <view class="fs-20 good-salenum">
已售{{ data.xiaoliang || 0 }}
</view> -->
</view>
<view class="flex-row-end nums-list">
<view class="nums-btn nums-btn-decrease" @click.stop="decrease">
<text class="nums-btn-text">-</text>
</view>
<view class="nums-display">
<text class="fs-24 nums-text">{{ data.number }}</text>
</view>
<view class="nums-btn nums-btn-add" @click.stop="add">
<text class="nums-btn-text">+</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
updateCartNum
} from "@/api/shop";
import {
updateCartSelect
} from "../../../../api/shop";
export default {
props: {
mode: {
type: String, // 'pay', 'delete'
default: "pay",
},
cartData: {
type: Object,
default: () => {},
},
},
data() {
return {
data: {},
};
},
computed: {
labelList() {
return (this.data?.label || "").split(",").filter((v) => !!v);
},
},
mounted() {
this.data = {
...this.cartData
};
},
watch: {
cartData: {
handler(val) {
this.data = {
...val
};
},
deep: true,
},
},
methods: {
jumpToDetails() {
// console.log(this.data,'--=')
uni.navigateTo({
url: `/pages/client/shop/details?product_id=${this.data.product_id}`,
});
},
decrease() {
if (this.data.number === 1) {
uni.showToast({
icon: "none",
title: "宝贝数量不能再减少了"
});
return;
}
updateCartNum({
cart_id: this.data.cart_id,
number: this.data.number - 1,
})
.then(() => {
this.data.number -= 1;
this.$forceUpdate();
this.$emit("changeList");
})
.catch((err) => {
uni.showToast({
icon: "none",
title: err
});
});
},
add() {
if (this.data.number === this.data.kucun) {
uni.showToast({
icon: "none",
title: "宝贝库存不够了"
});
return;
}
updateCartNum({
cart_id: this.data.cart_id,
number: this.data.number + 1,
})
.then(() => {
this.data.number += 1;
this.$forceUpdate();
this.$emit("changeList");
})
.catch((err) => {
uni.showToast({
icon: "none",
title: err
});
});
},
selectAction() {
if (this.data.mode === "pay") {
const value = this.data.is_select === 1 ? 2 : 1;
updateCartSelect({
cart_id: this.data.cart_id,
is_select: value,
})
.then(() => {
this.data.is_select = value;
this.$emit("changeList");
})
.catch((err) => {
uni.showToast({
icon: "none",
title: err
});
});
} else {
this.data.deleteSelect = !this.data.deleteSelect;
this.$forceUpdate();
this.$emit("deleteSelectChange", this.data);
}
},
},
};
</script>
<style lang="scss" scoped>
.cart-item {
width: calc(100vw - 40rpx);
box-sizing: border-box;
align-items: stretch;
background: #fff;
border-radius: 40rpx;
margin: auto;
padding: 20rpx;
.cart-icon-wrapper {
width: 80rpx;
height: 100%;
align-self: center;
.cart-icon {
width: 36rpx;
height: 36rpx;
}
}
.cart-inner {
flex: 1;
overflow: hidden;
.goods-img {
border-radius: 20rpx;
width: 104rpx;
height: 104rpx;
}
.goods-content {
flex: 1;
overflow: hidden;
margin-left: 24rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: stretch;
position: relative;
gap: 20rpx;
}
.sale-btn {
padding: 3rpx 4rpx;
background: $app_color_main;
border-radius: 4rpx;
border: 1px solid $app_color_main;
margin-right: 10rpx;
margin-top: 4rpx;
}
.sale-today {
background: transparent;
}
.origin-price {
color: #726E71;
text-decoration: line-through;
margin-left: 8rpx;
}
.good-salenum {
color: #9b939a;
margin-top: 10rpx;
}
.nums-list {
position: absolute;
bottom: 0;
right: 0;
display: flex;
align-items: center;
overflow: hidden;
background: #fff;
.nums-btn {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
background: #F5F5F5;
border-radius: 8rpx;
.nums-btn-text {
font-size: 32rpx;
color: #3D3D3D;
font-weight: 500;
line-height: 1;
}
}
.nums-display {
width: 60rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
.nums-text {
text-align: center;
line-height: 1;
font-size: 28rpx;
}
}
}
}
}
</style>

View File

@ -0,0 +1,295 @@
<template>
<view class="cart-list-container">
<view class="flex-row-between cart-list-header">
<text class="fs-24 app-fc-normal">
{{ mode === "pay" ? "请勾选结算" : "请勾选删除" }}
</text>
<text class="fs-24 app-fc-main" @click="changeMode">
{{ mode === "pay" ? "管理" : "取消" }}
</text>
</view>
<view class="cart-list-inner">
<scroll-view class="list-template-wrapper" :scroll-y="true" :refresher-enabled="true"
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh">
<view class="list-content">
<view v-for="(item, index) in cartList" :key="item.id" :class="{ left: index % 2 === 0 }"
class="flex-column-start news-item">
<cart-item :cartData="{...item, mode: mode}" @changeList="reloadList"
@deleteSelectChange="deleteSelectChange" />
</view>
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
</view>
<view class="place-view"></view>
</scroll-view>
</view>
<view class="flex-row-between cart-list-bottom">
<view class="flex-row-start" @click="payAll">
<image class="select-icon" :src="
isAllSelect
? require('@/static/images/cart_checked.png')
: require('@/static/images/unchecked.png')
" />
<text class="fs-24 app-fc-main">全选</text>
</view>
<view v-if="mode === 'pay'" class="flex-row-end">
<text class="fs-24 app-fc-main">
合计
<text class="fs-48 totalPrice">{{ payPrice }}</text>
</text>
<view class="flex-center fs-30 app-fc-white pay-btn" @click="goBuy">
去结算
</view>
</view>
<view v-if="mode === 'delete'" class="flex-center fs-30 app-fc-mark pay-btn delete"
@click="deleteCartAction">
删除
</view>
</view>
</view>
</template>
<script>
import CartItem from "./components/CartItem.vue";
import ListPageTemp from "@/components/ListPageTemp.vue";
import {
getCartList,
updateCartSelect,
deleteCart
} from "@/api/shop";
export default {
components: {
CartItem,
ListPageTemp,
},
data() {
return {
cartList: [],
requestData: {},
mode: "pay", //pay/delete
payPrice: 0,
total: 0,
refreshTriggered: false,
p: 1,
num: 999,
isLoading: true,
};
},
computed: {
isAllSelect() {
return (
this.cartList.length > 0 &&
!this.cartList.some(
(item) =>
(this.mode === "pay" && item.is_select !== 1) ||
(this.mode === "delete" && !item.deleteSelect)
)
);
},
},
mounted() {
this.getList()
},
methods: {
getCartList,
onRefresh() {
if (this.refreshTriggered) return;
this.refreshTriggered = true;
this.p = 1;
this.total = 0;
this.getList();
},
changeMode() {
if (this.mode === "pay") {
this.mode = "delete";
} else {
this.mode = "pay";
}
},
getList() {
this.isLoading = true
getCartList({
p: this.p,
num: this.num
})
.then((res) => {
this.payPrice = res.data.total_amount
console.log( res.data,'???')
this.cartList = (res?.data.list || []).map(v => {
return {
...v,
deleteSelect: !!v.deleteSelect,
}
});
this.total = res?.count || 0;
})
.finally(() => {
this.isLoading = false;
this.refreshTriggered = false;
uni.stopPullDownRefresh();
});
},
reloadList() {
this.getList();
},
deleteSelectChange(data) {
const index = this.cartList.findIndex(
(item) => item.cart_id === data.cart_id
);
this.cartList[index]["deleteSelect"] = data.deleteSelect;
this.$forceUpdate();
},
deleteCartAction() {
uni.showModal({
title: "提示",
content: "您确定要删除么?",
success: (res) => {
if (res.confirm) {
console.log(this.cartList,'this.cartList')
const deletList = this.cartList
.filter((item) => item.deleteSelect)
.map((v) => v.cart_id);
if (!deletList.length) {
uni.showToast({
icon: "none",
title: "请选择要删除的商品"
});
return;
}
deleteCart({
cart_id: this.isAllSelect ? 0 : deletList.join(","),
})
.then(() => {
this.reloadList();
})
.catch((err) => {
uni.showToast({
icon: "none",
title: err
});
});
}
},
});
},
payAll() {
if (this.mode === "delete") {
const val = !this.isAllSelect;
this.cartList.forEach((item) => {
item.deleteSelect = val;
});
} else if (this.mode === "pay") {
updateCartSelect({
cart_id: 0,
is_select: this.isAllSelect ? 2 : 1,
})
.then(() => {
this.reloadList();
})
.catch((err) => {
uni.showToast({
icon: "none",
title: err
});
});
}
},
goBuy() {
const selectList = this.cartList.filter((item) => item.is_select === 1);
uni.navigateTo({
url: "/pages/client/order/create?type=cart",
success: (res) => {
// 通过eventChannel向被打开页面传送数据
res.eventChannel.emit("createOrder", {
goodList: selectList,
payPrice:this.payPrice
});
},
});
},
},
};
</script>
<style lang="scss" scoped>
.cart-list-container {
height: 100%;
width: 100%;
box-sizing: border-box;
background: #fbf8fc;
padding-bottom: 0;
padding-top: 20rpx;
overflow: hidden;
.list-template-wrapper {
height: 100%;
width: 100%;
.list-content {
width: 100%;
}
.news-item {
width: 100%;
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
}
.cart-list-header {
margin: 0 20rpx 20rpx;
}
.cart-list-inner {
height: 100%;
}
.place-view {
height: 164rpx;
}
.cart-list-bottom {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
padding-left: 32rpx;
padding-right: 32rpx;
padding-top: 20rpx;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
background: #fff;
.select-icon {
width: 36rpx;
height: 36rpx;
margin-right: 16rpx;
}
.pay-btn {
width: 220rpx;
height: 92rpx;
background: $app_color_main;
border-radius: 92rpx;
margin-left: 20rpx;
border: 3rpx solid transparent;
&.delete {
background: transparent;
border: 3rpx solid $app_color_main;
}
}
}
}
.totalPrice {
color: #FF19A0;
}
</style>

View File

@ -0,0 +1,123 @@
<template>
<view class="all-category-modal" @click.stop="$emit('close')">
<view class="category-modal-content">
<text class="fs-36 app-fc-main">全部分类</text>
<view class="flex-row-start category-list">
<view
class="flex-center category-item"
:class="{ active: selectCategoryId === item.id }"
v-for="item in list"
:key="item.id"
@click.stop="$emit('change', item)"
>
<image
class="category-item-icon"
:src="item.type_pic"
mode="aspectFit"
/>
<text class="fs-20 app-text-ellipse category-item-name">
{{ item.name }}
</text>
</view>
</view>
</view>
<view
class="flex-row-center category-modal-close"
@click.stop="$emit('close')"
>
<text class="fs-24 app-fc-cancel">点击收起</text>
<image
class="arrow-icon"
src="@/static/images/arrow_up.png"
mode="widthFix"
/>
</view>
</view>
</template>
<script>
export default {
props: {
list: {
type: Array,
default: () => [],
},
selectCategoryId: {
type: String | Number,
default: "",
},
},
data() {
return {};
},
methods: {},
};
</script>
<style lang="scss" scoped>
.all-category-modal {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
.category-modal-content {
background: #fff;
padding: 30rpx;
.category-list {
flex-wrap: wrap;
align-items: flex-start;
padding-top: 30rpx;
max-height: 35vh;
overflow-x: hidden;
overflow-y: auto;
.category-item {
width: 20%;
margin-bottom: 24rpx;
.category-item-icon {
width: 92rpx;
height: 92rpx;
background: #fee9f3;
border-radius: 92rpx;
border: 4rpx solid #fee9f3;
margin-bottom: 10rpx;
}
.category-item-name {
padding: 0 22rpx;
box-sizing: border-box;
max-width: 140rpx;
height: 36rpx;
border-radius: 36rpx;
color: $app_fc_main;
}
&.active {
.category-item-icon {
border: 4rpx solid $app_color_main;
}
.category-item-name {
background: $app_color_main;
color: #fff;
}
}
}
}
}
.category-modal-close {
background: #ffffff;
border-radius: 0rpx 0rpx 40rpx 40rpx;
height: 94rpx;
.arrow-icon {
width: 20rpx;
height: 20rpx;
}
}
}
</style>

View File

@ -0,0 +1,180 @@
<template>
<view class="flex-row-start goods-item" @click.stop="$emit('clickCard', data)">
<image class="goods-img" :src="data.product_pic" mode="aspectFill" />
<view class="goods-content">
<view class="text-multi-ellipse fs-28 app-fc-main app-font-bold goods-name">
{{ data.product_name || "" }}
</view>
<view class="flex-row-start label">
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
<view class="fs-20 app-fc-main label-name">{{data.sales}}人买过</view>
</view>
<view class="price-row">
<view class="flex-row-start">
<text class="fs-28 price-text">
¥
<text class="fs-28">{{data.prices[0].original_price || 0 }}</text>
</text>
<text class="fs-20 price-label">到手价</text>
</view>
<text class="fs-24 origin-price" v-if="minPrice.price_shichang">
¥{{ minPrice.price_shichang || 0 }}
</text>
</view>
<image
class="add-cart-icon"
:class="{ 'add-cart-icon-animate': isAnimating }"
:src="`${imgPrefix}mall-addCar.png`"
@click.stop="$emit('addToCar', data)"
/>
</view>
</view>
</template>
<script>
import { imgPrefix } from '@/utils/common';
export default {
props: {
index: {
type: Number,
default: 0,
},
data: {
type: Object,
default: () => {},
},
},
data() {
return {
imgPrefix,
isAnimating: false,
};
},
computed: {
// minPrice() {
// let minPrice = {};
// let minPriceValue = 0;
// this.data.price_list.map((v) => {
// if (!minPriceValue || minPriceValue > +v.price) {
// minPriceValue = +v.price;
// minPrice = { ...v };
// }
// });
// return minPrice;
// },
labelList() {
return this.data.label.split(",").filter((v) => !!v);
},
},
mounted() {},
methods: {
// 触发添加购物车动画
triggerAddCartAnimation() {
this.isAnimating = true;
setTimeout(() => {
this.isAnimating = false;
}, 600);
},
},
};
</script>
<style lang="scss" scoped>
.goods-item {
background: #fff;
border-radius: 40rpx;
width: 100%;
padding: 20rpx;
box-sizing: border-box;
margin-bottom: 20rpx;
position: relative;
border-bottom: 1rpx solid #F5F5F5;
.goods-img {
border-radius: 20rpx;
width: 160rpx;
height: 160rpx;
}
.goods-content {
flex: 1;
overflow: hidden;
margin-left: 20rpx;
}
.goods-name {
margin: 0 0 12rpx;
}
.label {
background-color: #ffecf3;
display: inline-flex;
align-items: center;
border-radius: 4rpx;
margin-bottom: 12rpx;
.hot-icon {
width: 28rpx;
height: 28rpx;
margin-right: 6rpx;
}
.label-name {
padding: 4rpx 8rpx;
color: #FF19A0;
}
}
.price-row {
margin-top: 12rpx;
display: flex;
flex-direction: column;
}
.price-text {
color: #3D3D3D;
}
.price-label {
color: #999;
margin-left: 8rpx;
}
.origin-price {
color: #999;
margin-top: 8rpx;
}
.add-cart-icon {
width: 44rpx;
height: 44rpx;
position: absolute;
bottom: 20rpx;
right: 20rpx;
transition: transform 0.3s ease;
&.add-cart-icon-animate {
animation: addCartBounce 0.6s ease;
}
}
}
@keyframes addCartBounce {
0% {
transform: scale(1) rotate(0deg);
}
25% {
transform: scale(1.2) rotate(-10deg);
}
50% {
transform: scale(1.3) rotate(10deg);
}
75% {
transform: scale(1.1) rotate(-5deg);
}
100% {
transform: scale(1) rotate(0deg);
}
}
</style>

View File

@ -0,0 +1,652 @@
<template>
<view class="flex-column-start category-container">
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="navbar-content">
<view class="back-btn" @click="handleBack">
<view class="back-icon"></view>
</view>
<view class="search-input-wrapper" :style="{ width: searchInputWidth + 'rpx' }">
<input class="search-input" type="text" placeholder="搜索商品" v-model="searchKeyword"
@confirm="handleSearch" @focus="handleSearchFocus" />
</view>
</view>
</view>
<!-- <view class="flex-row-start category-list">
<view
class="flex-center category-item"
:class="{ active: selectCategoryId === item.id }"
v-for="item in categoryList.slice(0, 6)"
:key="item.id"
@click="changeCategory(item)"
>
<image
class="category-item-icon"
:src="selectCategoryId === item.id ? item.select_type_pic : item.type_pic"
mode="aspectFit"
/>
<text class="flex-center fs-20 app-text-ellipse category-item-name">{{
item.name
}}</text>
</view>
<view class="flex-center open-cell" @click="showAllCategory = true">
<text class="fs-24 app-fc-main app-font-bold"></text>
<text class="fs-24 app-fc-main app-font-bold"></text>
<image class="open-icon" src="@/static/images/open_icon.png" />
</view>
</view> -->
<view class="flex-row-start category-content">
<!-- 左侧一级分类列表 -->
<view v-if="categoryList.length" class="category-list-left">
<view class="flex-center category-item-left" :class="{
'category-item-left-active': item.id === selectCategoryId,
}" v-for="(item, index) in categoryList" :key="index" @click="changeCateg(item)">
<text class="fs-28 app-fc-main app-text-ellipse category-item-left-name">
{{ item.type_name }}
</text>
<view v-if="selectCategoryId === item.id" class="border-line"></view>
</view>
</view>
<!-- 右侧内容区域 -->
<view class="category-right-wrapper">
<!-- 右侧上方二级分类标签 -->
<!-- <view v-if="subCategoryList.length" class="sub-category-tabs">
<scroll-view class="sub-category-tabs-scroll" scroll-x>
<view class="sub-category-tab-item" :class="{
'sub-category-tab-item-active': !selectSubCategoryId || selectSubCategoryId === '',
}" @click="changeSubCategory({ id: '', name: '全部' })">
全部
</view>
<view class="sub-category-tab-item" :class="{
'sub-category-tab-item-active': item.id === selectSubCategoryId,
}" v-for="(item, index) in subCategoryList" :key="index" @click="changeSubCategory(item)">
{{ item.name }}
</view>
</scroll-view>
</view> -->
<!-- 右侧下方商品列表 -->
<scroll-view class="category-right" scroll-y :refresher-enabled="true"
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
<view class="goods-list">
<good-item v-for="good in goodsList" :key="good.product_id" :ref="`goodItem_${good.product_id}`"
:data="good" @addToCar="addToCar" @clickCard="jumpToDetail" />
</view>
</scroll-view>
</view>
</view>
<view class="add-car-view">
<image class="add-car-icon" :src="`${imgPrefix}mall-shopCar.png`" @click="jumpToCart" />
<view v-if="cartShowCount" class="fs-20 app-fc-white flex-center cart-count"
:class="{ 'cart-count-bounce': cartCountBounce }">
{{ cartShowCount }}
</view>
</view>
<add-goods-modal v-if="showModal" :data="addGoodInfo" optText="加入购物车" @change="(val) => (showModal = val)"
@optAction="optAction" />
<category-modal v-if="showAllCategory" :list="categoryList" :selectCategoryId="selectCategoryId"
@close="showAllCategory = false" @change="changeCategory" />
</view>
</template>
<script>
import {
getGoodsClassify,
addCart,
getCartList
} from "@/api/shop";
import {
imgPrefix
} from "@/utils/common";
import GoodItem from "./components/GoodItem.vue";
import AddGoodsModal from "@/components/goods/AddGoodsModal.vue";
import CategoryModal from "./components/CategoryModal.vue";
import {
getGoodsListData
} from "../../../api/shop";
export default {
components: {
GoodItem,
AddGoodsModal,
CategoryModal,
},
data() {
const systemInfo = uni.getSystemInfoSync();
// 计算搜索输入框宽度,避免被胶囊按钮遮挡
let searchInputWidth = 500; // 默认宽度
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
if (menuButtonInfo && menuButtonInfo.left) {
// 屏幕宽度转换为 rpx (750rpx 对应 375px)
const screenWidthRpx = 750;
const leftPadding = 32; // navbar-content 左侧 padding
const backBtnWidth = 60; // 返回按钮宽度
const backBtnMargin = 20; // 返回按钮右边距
// 右侧需要留出的空间:屏幕宽度(px) - 胶囊按钮左边距(px) + 额外间距(px),然后转换为 rpx
const rightSpacePx = systemInfo.windowWidth - menuButtonInfo.left + 20;
const rightSpaceRpx = (rightSpacePx / systemInfo.windowWidth) * screenWidthRpx;
searchInputWidth = screenWidthRpx - leftPadding - backBtnWidth - backBtnMargin - rightSpaceRpx;
}
} catch (e) {
console.log('获取胶囊按钮信息失败', e);
}
return {
statusBarHeight: systemInfo.statusBarHeight || 0,
imgPrefix,
searchInputWidth: searchInputWidth,
searchKeyword: '',
id: "",
categoryList: [],
selectCategoryId: "",
selectSubCategoryId: "",
showModal: false,
addGoodInfo: null,
showAllCategory: false,
goodsList: [],
total: 0,
page: 1,
size: 10,
refreshTriggered: false,
isLoading: false,
petOrderId: '',
petOrderAddressId: '',
cartCount: 0,
cartCountBounce: false,
};
},
computed: {
cartShowCount() {
return this.cartCount > 9 ? "9+" : this.cartCount;
},
selectCategory() {
return (
this.categoryList.find((item) => item.id === this.selectCategoryId) || {}
);
},
subCategoryList() {
return this.selectCategory?.children || [];
},
selectSubCategory() {
return (
this.subCategoryList.find(
(item) => item.id === this.selectSubCategoryId
) || {}
);
},
},
mounted() {
this.getCategoryList();
},
onLoad(options) {
const {
id,
petOrderId = '',
addressId = ''
} = options;
this.selectCategoryId = +id;
this.petOrderId = petOrderId
this.petOrderAddressId = addressId
},
onShow() {
this.getCartListData();
},
watch: {
selectCategoryId(val) {
if (val && !this.selectSubCategoryId) {
this.getShopList();
}
},
subCategoryList(list) {
// 当二级分类列表变化时,默认选择"全部"(空字符串)
this.selectSubCategoryId = "";
},
selectSubCategoryId(id) {
this.getShopList();
},
},
methods: {
// 分类列表
getCategoryList(parent_id = -1) {
getGoodsClassify({
parent_id,
p: 1,
num: 999
}).then((res) => {
this.categoryList = res?.data || [];
if (!this.selectCategoryId) {
this.selectCategoryId = this.categoryList[0]?.id || "";
}
});
},
changeCateg(item) {
this.changeId = item.id
this.selectCategoryId = item.id;
this.showAllCategory = false;
// console.log(item,'--')
},
// 商品列表
getShopList() {
getGoodsListData({
type: this.changeId,
p: this.page,
num: this.size,
keyword: "",
is_tui: 0,
})
.then((res) => {
const list = res?.data || [];
this.goodsList =
this.page === 1 ? list : [...this.goodsList, ...list];
this.total = res?.count || 0;
})
.finally(() => {
this.isLoading = false;
this.refreshTriggered = false;
});
},
changeCategory(data) {
console.log(data, '--=')
this.selectCategoryId = data.id;
this.showAllCategory = false;
},
changeSubCategory(data) {
this.selectSubCategoryId = data.id;
},
// 加入购物车
addCartAction(data) {
addCart(data)
.then(() => {
uni.showToast({
title: "已加入购物车!",
icon: "none"
});
this.getCartListData();
// 触发购物车数量徽章动画
this.triggerCartCountAnimation();
})
.catch((err) => {
uni.showToast({
title: err || "加入购物车失败!",
icon: "none"
});
});
},
optAction(data) {
console.log(data, '??')
const {
product_id,
type,
prices,
product_name,
product_pic,
number
} = data;
this.addCartAction({
item_id: product_id,
item_type: type,
price_id: prices?.[0]?.price_id,
price_desc: prices?.[0]?.price_desc,
item_name: product_name,
item_price: prices?.[0]?.actual_price,
number,
item_pic: product_pic,
is_select: 1
});
this.showModal = false;
// 触发对应商品项的动画
this.triggerGoodItemAnimation(product_id);
},
// 触发商品项的添加购物车动画
triggerGoodItemAnimation(goodsId) {
this.$nextTick(() => {
const ref = this.$refs[`goodItem_${goodsId}`];
if (ref && ref[0]) {
ref[0].triggerAddCartAnimation();
}
});
},
// 触发购物车数量徽章动画
triggerCartCountAnimation() {
this.cartCountBounce = true;
setTimeout(() => {
this.cartCountBounce = false;
}, 600);
},
addToCar(good) {
this.showModal = true;
this.addGoodInfo = {
...good
};
},
// 购物车列表
getCartListData() {
getCartList({
p: 1,
num: 999
}).then((res) => {
this.cartCount = (res?.data.list || []).reduce(
(total, prev) => total + prev.number,
0
);
});
},
jumpToCart() {
uni.navigateTo({
url: `/pages/client/cart/index`,
});
},
jumpToDetail(details) {
console.log(details, '??')
uni.navigateTo({
url: `/pages/client/shop/details?product_id=${details.product_id}&petOrderId=${this.petOrderId}&petOrderAddressId=${this.petOrderAddressId}`,
});
},
onRefresh() {
this.refreshTriggered = true;
this.page = 1;
this.size = 10;
this.total = 0;
this.getShopList();
},
onLoadMore() {
if (!this.isLoading && this.total > this.goodsList.length) {
this.page++;
this.getShopList();
}
},
handleBack() {
uni.navigateBack();
},
handleSearch() {
// 处理搜索逻辑
this.page = 1;
this.getShopList();
},
handleSearchFocus() {
// 可以跳转到搜索页面
// uni.navigateTo({
// url: '/pages/client/search/index'
// });
},
},
};
</script>
<style lang="scss" scoped>
.category-container {
height: 100%;
align-items: stretch;
position: relative;
padding-top: calc(var(--status-bar-height, 0px) + 88rpx + 20rpx);
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background: #ff19a0;
border-radius: 0px 0px 16px 16px;
.status-bar {
background: #ff19a0;
}
.navbar-content {
display: flex;
align-items: center;
padding: 0 32rpx;
height: 88rpx;
box-sizing: border-box;
.back-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
.back-icon {
width: 20rpx;
height: 20rpx;
border-left: 3rpx solid #fff;
border-bottom: 3rpx solid #fff;
transform: rotate(45deg);
}
}
.search-input-wrapper {
height: 64rpx;
background: #fff;
border-radius: 32rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
box-sizing: border-box;
.search-input {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333;
}
}
}
}
.category-list {
position: relative;
overflow: hidden;
padding: 20rpx 24rpx;
box-sizing: border-box;
background: #fff;
min-height: 176rpx;
margin-bottom: 20rpx;
.category-item {
.category-item-icon {
width: 90rpx;
height: 90rpx;
background: #fee9f3;
border-radius: 90rpx;
border: 4rpx solid #fee9f3;
margin-bottom: 12rpx;
}
.category-item-name {
padding: 0 22rpx;
box-sizing: border-box;
max-width: 140rpx;
height: 36rpx;
border-radius: 36rpx;
color: $app_fc_main;
line-height: 36rpx;
}
&.active {
.category-item-icon {
border: 4rpx solid $app_color_main;
}
.category-item-name {
background: $app_color_main;
color: #fff;
}
}
}
.open-cell {
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 72rpx;
background: #fff;
.open-icon {
width: 27rpx;
height: 23rpx;
margin-top: 10rpx;
}
}
}
.category-content {
flex: 1;
overflow: hidden;
padding-top: 50rpx;
// 左侧:一级分类列表
.category-list-left {
width: 180rpx;
height: 100%;
overflow: auto;
background: #f7f8fa;
.category-item-left {
text-align: center;
width: 100%;
height: 110rpx;
position: relative;
background: #f7f8fa;
.category-item-left-name {
color: #3D3D3D;
}
&.category-item-left-active {
background-color: #fff;
.category-item-left-name {
color: $app_fc_mark;
}
.border-line {
position: absolute;
top: 28rpx;
bottom: 28rpx;
left: 0;
width: 10rpx;
border-radius: 28rpx;
background: $app_color_main;
}
}
}
}
// 右侧内容区域
.category-right-wrapper {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
background: #fff;
// 二级分类标签
.sub-category-tabs {
flex-shrink: 0;
background: #fff;
padding: 28rpx 0;
.sub-category-tabs-scroll {
white-space: nowrap;
display: flex;
align-items: center;
padding: 0 32rpx;
.sub-category-tab-item {
display: inline-block;
padding: 12rpx 20rpx;
margin-right: 16rpx;
font-size: 24rpx;
color: #3D3D3D;
white-space: nowrap;
background: #F5F5F5;
border-radius: 32rpx;
flex-shrink: 0;
&.sub-category-tab-item-active {
color: #FF19A0;
background: #FFECF3;
font-weight: normal;
}
}
}
}
// 商品列表区域
.category-right {
flex: 1;
height: 100%;
}
}
}
}
.add-car-view {
position: fixed;
bottom: 35vh;
right: 20rpx;
background-color: #fff;
border-radius: 50%;
padding: 20rpx;
box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
.add-car-icon {
width: 48rpx;
height: 48rpx;
}
.cart-count {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 30rpx;
height: 30rpx;
border-radius: 30rpx;
background: #FF19A0;
z-index: 10;
border: 1px solid #FFFFFF;
transition: transform 0.3s ease;
&.cart-count-bounce {
animation: cartCountBounce 0.6s ease;
}
}
}
@keyframes cartCountBounce {
0% {
transform: scale(1);
}
25% {
transform: scale(1.3);
}
50% {
transform: scale(1.1);
}
75% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
</style>

View File

@ -0,0 +1,110 @@
<template>
<scroll-view
:scroll-y="true"
class="collect-list-container"
:refresher-enabled="true"
:refresher-triggered="refreshTriggered"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
>
<view class="collect-list-content">
<view class="goods-list-item left">
<good-item
v-for="(good, i) in leftColumnGoods"
:index="2 * i"
:key="good.shoucang_id"
:data="good"
@addToCar="addToCar"
/>
</view>
<view class="goods-list-item">
<good-item
v-for="(good, i) in rightColumnGoods"
:index="2 * i + 1"
:key="good.shoucang_id"
:data="good"
@addToCar="addToCar"
/>
</view>
</view>
</scroll-view>
</template>
<script>
import { collectList } from "@/api/shop";
import GoodItem from "../shop/components/GoodItem.vue";
export default {
components: { GoodItem },
data() {
return {
goodsList: [],
p: 1,
num: 10,
total: 0,
refreshTriggered: false,
isLoading: false,
};
},
computed: {
leftColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 0);
},
rightColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 1);
},
},
onShow() {
this.onRefresh();
},
methods: {
// 获取商品列表
getGoodsList() {
if (this.isLoading) return;
this.isLoading = true;
collectList({ p: this.p, num: this.num })
.then((res) => {
const list = res?.info || [];
this.goodsList = this.p === 1 ? list : [...this.goodsList, ...list];
this.total = res?.count || 0;
})
.finally(() => {
this.isLoading = false;
this.refreshTriggered = false;
});
},
onRefresh() {
if (this.refreshTriggered) return;
this.refreshTriggered = true;
this.p = 1;
this.num = 10;
this.total = 0;
this.getGoodsList();
},
},
};
</script>
<style lang="scss" scoped>
.collect-list-container {
height: 100%;
background: #f7f8fa;
.collect-list-content {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
height: 100%;
padding: 24rpx 32rpx;
box-sizing: border-box;
}
.goods-list-item {
flex: 1;
&.left {
margin-right: 20rpx;
}
}
}
</style>

View File

@ -0,0 +1,256 @@
<template>
<view class="community-item">
<view class="flex-row-start item-title">
<image class="item-avator" :src="data.head_pic" />
<text class="fs-32 app-fc-main app-font-bold item-nickname">
{{ data.nick_name || "" }}
</text>
<text class="fs-22 item-time">
{{ time }}
</text>
</view>
<view class="wash-info" v-if="data.old_list.length">
<text class="fs-28 app-fc-main app-font-bold">洗护前</text>
<view class="wash-imgs-wrapper">
<view class="flex-row-start">
<image
class="wash-img"
:class="{'wash-img-right': index % 2 === 2}"
:src="item"
v-for="(item, index) in data.old_list"
:key="index"
@click="preview(index, data.old_list)"
mode="aspectFill"
/>
</view>
</view>
</view>
<view class="wash-info wash-info-new" v-if="data.new_list.length">
<text class="fs-28 app-fc-main app-font-bold">洗护后</text>
<view class="wash-imgs-wrapper">
<view class="flex-row-start">
<image
class="wash-img"
:class="{'wash-img-right': index % 2 === 2}"
:src="item"
v-for="(item, index) in data.new_list"
:key="index"
@click="preview(index, data.new_list)"
mode="aspectFill"
/>
</view>
</view>
</view>
<view
v-if="showDeletBtn"
class="flex-center fs-30 app-fc-white delet-btn"
@click="$emit('delete', data)"
>
一键删除
</view>
<view v-if="showOptBtns" class="flex-row-start opt-btns">
<view class="flex-row-start opt-btn">
<image class="opt-icon" :src="require('../static/see.png')" />
<text class="fs-30 opt-text">{{ cellData.see || 0 }}</text>
</view>
<view class="flex-row-start opt-btn" @click="zanComunity">
<image
class="opt-icon"
:src="
cellData.zan_id
? require('../static/zan_active.png')
: require('../static/zan.png')
"
/>
<text class="fs-30 opt-text">{{ cellData.zan || 0 }}</text>
</view>
<button class="share-wrapper" open-type="share" @click="share">
<view class="flex-row-start opt-btn">
<image class="opt-icon" :src="require('../static/share.png')" />
<text class="fs-30 opt-text">{{ cellData.share || 0 }}</text>
</view>
</button>
</view>
</view>
</template>
<script>
import moment from "moment";
import { zanCommunity, getCommunityDetail } from "@/api/community";
import appConfig from "../../../../constants/app.config";
export default {
props: {
data: {
type: Object,
default: () => {},
},
showDeletBtn: {
type: Boolean,
default: false,
},
showOptBtns: {
type: Boolean,
default: false,
},
},
data() {
return {
cellData: {},
};
},
computed: {
time() {
return moment(this.data.add_time * 1000).format("YYYY/MM/DD");
},
},
mounted() {},
watch: {
data: {
handler(val) {
this.cellData = { ...val };
},
immediate: true,
deep: true,
},
},
methods: {
preview(index, list = []) {
uni.previewImage({
urls: list,
current: index,
});
this.getDetail();
},
// 圈子详情
getDetail(is_see = 1, is_share = 0) {
getCommunityDetail({
chongquan_id: this.data.chongquan_id,
is_see,
is_share,
}).then((res) => {
this.cellData = { ...this.cellData, ...res?.info };
this.$emit("update");
this.$forceUpdate();
});
},
// 点赞
zanComunity() {
zanCommunity({
chongquan_id: this.data.chongquan_id,
type: this.cellData.zan_id ? 2 : 1,
}).then(() => {
this.$emit("update");
});
},
// 转发
share() {
this.$emit("share", {
title: appConfig.appShareName,
path: `/pages/client/index/index?communityId=${this.data.chongquan_id}`,
communityId: this.cellData.chongquan_id,
});
},
},
};
</script>
<style lang="scss" scoped>
.community-item {
width: calc(100vw - 32rpx * 2);
background: #fff;
border-radius: 40rpx;
padding: 32rpx 24rpx;
box-sizing: border-box;
margin-bottom: 28rpx;
.item-title {
margin-bottom: 24rpx;
.item-avator {
width: 60rpx;
height: 60rpx;
border-radius: 60rpx;
}
.item-nickname {
flex: 1;
margin: 0 20rpx;
}
.item-time {
color: #999;
}
}
.wash-info {
padding: 24rpx;
border-radius: 20rpx;
box-sizing: border-box;
&.wash-info-new {
margin-top: 24rpx;
}
.wash-imgs-wrapper {
width: 100%;
overflow-x: auto;
overflow-y: scroll;
.wash-img {
width: calc((100vw - 32rpx * 2 - 24rpx * 4 - 20rpx * 2) / 3);
height: calc((100vw - 32rpx * 2 - 24rpx * 4 - 20rpx * 2) / 3);
border-radius: 20rpx;
margin-right: 20rpx;
margin-top: 24rpx;
flex-shrink: 0;
}
.wash-img-right {
margin-right: 0;
}
}
}
.delet-btn {
height: 92rpx;
width: 100%;
border-radius: 92rpx;
background: $app_color_main;
margin-top: 24rpx;
}
.share-wrapper {
margin: 0;
background: transparent;
padding: 0;
&::after {
border: none;
}
}
.opt-btns {
margin-top: 32rpx;
.opt-btn {
margin-right: 48rpx;
.opt-icon {
width: 36rpx;
height: 36rpx;
margin-right: 4rpx;
}
.opt-text {
color: #726e71;
}
}
}
}
</style>

View File

@ -0,0 +1,140 @@
<template>
<view class="flex-column-start community-list-container">
<nav-bar title="Wagoo" />
<scroll-view
class="list-template-wrapper"
:scroll-y="!disableScroll"
:refresher-enabled="true"
:refresher-triggered="refreshTriggered"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
>
<view class="list-content">
<view
v-for="(item, index) in list"
:key="item[idKey]"
:class="{ left: index % 2 === 0 }"
class="flex-column-start news-item"
>
<community-item
:data="item"
:showOptBtns="true"
@share="(val) => $emit('share', val)"
@update="getAllList"
/>
</view>
<uni-load-more
v-if="isLoading || (!isLoading && total && total === list.length)"
:status="isLoading ? 'loading' : 'nomore'"
></uni-load-more>
</view>
</scroll-view>
</view>
</template>
<script>
import { getCommunityList } from "@/api/community";
import CommunityItem from "./components/CommunityItem.vue";
export default {
components: {
CommunityItem,
},
data() {
return {
reloadFlag: 0,
list: [],
isLoading: false,
total: 0,
list: [],
refreshTriggered: false,
p: 1,
num: 10,
};
},
watch: {
reloadFlag: {
handler(value) {
if (value) {
this.p = 1;
this.num = 10;
this.total = 0;
this.getList();
}
},
immediate: true,
},
},
methods: {
getCommunityList,
onShowFun() {
this.reloadFlag = Math.random();
},
onRefresh() {
if (this.refreshTriggered) return;
this.refreshTriggered = true;
this.p = 1;
this.num = 10;
this.total = 0;
this.getList();
},
onLoadMore() {
if (!this.isLoading && this.total > this.list.length) {
this.p++;
this.getList();
}
},
getList() {
if (this.isLoading) return;
this.isLoading = true;
const data = Object.assign({}, { p: this.p, num: this.num });
getCommunityList(data)
.then((res) => {
const list =
this.p === 1
? res?.info || []
: [...this.list, ...(res?.info || [])];
this.list = list;
this.total = res?.count || 0;
})
.finally(() => {
this.isLoading = false;
this.refreshTriggered = false;
uni.stopPullDownRefresh();
});
},
getAllList() {
getCommunityList({
p: 1,
num: this.list.length,
}).then((res) => {
this.list = res?.info || [];
});
},
},
};
</script>
<style lang="scss" scoped>
.community-list-container {
height: 100%;
align-items: stretch;
.list-template-wrapper {
padding: 20rpx 32rpx;
box-sizing: border-box;
background: #f7f8fa;
width: 100%;
flex: 1;
overflow: hidden;
.list-content {
width: 100%;
}
.news-item {
width: 100%;
}
}
}
</style>

View File

@ -0,0 +1,80 @@
<template>
<view class="community-list-container">
<list-page-temp
:getDataPromise="getMyCommunityList"
:reloadFlag="reloadFlag"
:requestData="{}"
>
<template v-slot:item="{ data }">
<community-item
:data="data"
:showDeletBtn="true"
@delete="deleteAction"
/>
</template>
</list-page-temp>
<pop-up-modal
v-if="showDeleteModal"
content="确定要删除该条圈子吗?"
@confirm="deleteCommunity"
@cancel="showDeleteModal = false"
/>
</view>
</template>
<script>
import { getMyCommunityList, deleteCommunity } from "@/api/community";
import CommunityItem from "./components/CommunityItem.vue";
import PopUpModal from "@/components/PopUpModal.vue";
export default {
components: {
CommunityItem,
PopUpModal,
},
data() {
return {
deleteInfo: null,
showDeleteModal: false,
reloadFlag: 0,
};
},
onShow() {
this.reloadFlag = Math.random();
},
methods: {
getMyCommunityList,
deleteAction(data) {
this.showDeleteModal = true;
this.deleteInfo = { ...data };
},
deleteCommunity() {
uni.showLoading({
title: "删除中",
icon: "none",
mask: true,
});
deleteCommunity(this.deleteInfo.chongquan_id).then(() => {
this.reloadFlag = Math.random();
uni.hideLoading();
uni.showToast({
title: "删除成功",
icon: "none",
});
this.showDeleteModal = false
});
},
},
};
</script>
<style lang="scss" scoped>
.community-list-container {
height: 100%;
padding: 20rpx 32rpx;
background: #f7f8fa;
box-sizing: border-box;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

View File

@ -0,0 +1,81 @@
<template>
<view class="coupon-list-container">
<list-page-temp
:getDataPromise="getCouponData"
:reloadFlag="reloadFlag"
:requestData="{ is_lingqu: 1 }"
>
<template v-slot:item="{ data }">
<coupon-item
:data="data"
optBtnText="领取"
@useCoupon="receiveCoupon"
/>
</template>
<view slot="bottom" class="place-view"></view>
</list-page-temp>
</view>
</template>
<script>
import ListPageTemp from "@/components/ListPageTemp";
import CouponItem from "@/components/coupon/CouponItem";
import { getCouponData, receiveCoupon } from "@/api/coupon";
export default {
data() {
return {
reloadFlag: 0,
};
},
components: {
ListPageTemp,
CouponItem,
},
onShow() {
this.reloadFlag = Math.random();
},
methods: {
getCouponData,
receiveCoupon(data) {
uni.showLoading({
title: "领取中",
icon: 'none'
});
receiveCoupon(data.coupon_id)
.then(() => {
uni.hideLoading();
uni.showToast({
icon: "none",
title: "领取成功",
});
this.reloadFlag = Math.random();
})
.catch((err) => {
uni.hideLoading();
uni.showToast({
icon: "none",
title: err || "领取失败",
});
});
},
},
};
</script>
<style lang="scss" scoped>
.coupon-list-container {
height: 100%;
padding: 20rpx 32rpx 0;
background: #f7f8fa;
::v-deep {
.coupon-price {
color: $app_fc_alarm;
}
.coupon-item .item-bottom .circle {
background: #f7f8fa;
}
}
}
</style>

View File

@ -0,0 +1,344 @@
<template>
<view class="flex-column-start order-list-container">
<tabs-list :list="tabsList" :isScroll="false" :currentIndex="curTabIndex" flexClass="flex-row-around"
@change="onTabChange" />
<view class="order-list-content">
<scroll-view
class="coupon-scroll"
scroll-y
:refresher-enabled="true"
:refresher-triggered="refreshTriggered"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore">
<view class="coupon-list" v-if="couponList.length > 0">
<coupon-item
v-for="(item, index) in couponList"
:key="item.distribution_id || index"
:data="item"
:showOptBtn="false"
:disabled="item.use_status === 2"
:showCountDown="item.nearDisabled && item.daojishi"
:past="curTabIndex === 2"
@useCoupon="handleUseCoupon" />
</view>
<view v-else-if="!isLoading" class="empty-container">
<image class="empty-image" mode="widthFix" src="https://activity.wagoo.live/empty.png" />
<!-- <text class="empty-text">暂无优惠券</text> -->
</view>
<view v-if="isLoading && couponList.length === 0" class="loading-container">
<text class="loading-text">加载中...</text>
</view>
<view v-if="hasMore && couponList.length > 0" class="load-more">
<text class="load-more-text">{{ isLoading ? '加载中...' : '上拉加载更多' }}</text>
</view>
<view v-if="!hasMore && couponList.length > 0" class="no-more">
<text class="no-more-text">没有更多了</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import TabsList from "@/components/TabsList.vue";
import CouponItem from "@/components/coupon/CouponItem";
import {
userCoupolistwo
} from "../../../api/login";
export default {
components: {
TabsList,
CouponItem,
},
data() {
return {
curTabIndex: 0,
user_id: '',
couponList: [],
isLoading: false,
refreshTriggered: false,
page: 1,
pageSize: 10,
total: 0,
};
},
computed: {
tabsList() {
return [{
name: "未使用",
id: 1,
},
{
name: "已使用",
id: 2,
},
{
name: "已过期",
id: 3,
},
];
},
hasMore() {
return this.total > this.couponList.length;
},
},
options: {
styleIsolation: "shared",
},
onShow() {
this.refreshList();
},
onLoad() {
const value = uni.getStorageSync("userInfo");
this.user_id = value?.user_id || '';
this.getCouponList();
},
methods: {
onTabChange(item, index) {
this.curTabIndex = index;
this.refreshList();
},
refreshList() {
this.page = 1;
this.couponList = [];
this.total = 0;
this.getCouponList();
},
onRefresh() {
if (this.refreshTriggered) return;
this.refreshTriggered = true;
this.refreshList();
},
onLoadMore() {
if (this.isLoading || !this.hasMore) return;
this.page++;
this.getCouponList();
},
handleUseCoupon(couponData) {
// 跳转到 index 页面并切换到预约页面
uni.redirectTo({
url: '/pages/client/index/index?activePageId=reservationPage',
success: () => {
// 如果 redirectTo 后页面已加载,也可以再触发一次确保切换
setTimeout(() => {
uni.$emit("changeTabBar", {
pageId: 'reservationPage'
});
}, 200);
}
});
},
getCouponList() {
if (this.isLoading) return;
this.isLoading = true;
const params = {
p: this.page,
num: this.pageSize,
status: this.tabsList[this.curTabIndex].id,
type: 0,
user_id: this.user_id,
};
userCoupolistwo(params)
.then((res) => {
// 处理接口返回的数据结构
const list = Array.isArray(res?.data)
? res.data
: (res?.data?.list || []);
// 处理列表数据,添加额外字段
const processedList = list.map(item => ({
...item,
nearDisabled: this.curTabIndex === 2,
}));
if (this.page === 1) {
this.couponList = processedList;
} else {
this.couponList = [...this.couponList, ...processedList];
}
// 获取总数
this.total = res?.count ?? res?.data?.count ?? 0;
})
.catch((err) => {
console.error('获取优惠券列表失败', err);
uni.showToast({
title: err || '获取列表失败',
icon: 'none',
});
})
.finally(() => {
this.isLoading = false;
this.refreshTriggered = false;
uni.stopPullDownRefresh();
});
},
},
};
</script>
<style lang="scss" scoped>
.order-list-container {
height: 100%;
align-items: stretch;
justify-content: flex-start;
background: #ffecf3;
::v-deep {
.list-wrapper {
padding: 0 100rpx;
}
}
.coupon-header {
padding: 36rpx 40rpx;
background: #fff;
border-radius: 30rpx;
box-sizing: border-box;
width: 686rpx;
margin-top: 24rpx;
margin-left: 32rpx;
.coupon-header-text {
flex: 1;
}
.header-icon {
width: 56rpx;
height: 56rpx;
margin-right: 24rpx;
}
.header-btn {
width: 132rpx;
height: 52rpx;
background: $app_fc_alarm;
border-radius: 52rpx;
}
}
.order-list-content {
flex: 1;
overflow: hidden;
padding-left: 32rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.coupon-scroll {
height: 100%;
width: 100%;
}
.coupon-list {
display: flex;
flex-direction: column;
gap: 24rpx;
padding: 20rpx 0;
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 400rpx auto 0;
padding: 0 40rpx;
}
.empty-image {
width: 160px;
height: 174px;
display: block;
margin-bottom: 24rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
text-align: center;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
.load-more {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0;
}
.load-more-text {
font-size: 24rpx;
color: #999;
}
.no-more {
display: flex;
justify-content: center;
align-items: center;
padding: 40rpx 0;
}
.no-more-text {
font-size: 24rpx;
color: #999;
}
::v-deep {
.coupon-price {
color: $app_fc_alarm;
}
}
.use-icon {
width: 100rpx;
height: 100rpx;
}
}
.serviceVoucher {
width: 100%;
height: 200rpx;
position: fixed;
display: flex;
justify-content: center;
align-items: center;
left: 0;
bottom: 0;
background: #fff;
.view {
border: 1px solid #FF19A0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 90%;
height: 48px;
border-radius: 200rpx;
.c {
color: #FF19A0;
font-family: PingFang SC;
font-size: 32rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,123 @@
<template>
<view class="flex-column-start order-list-container">
<tabs-list
:list="tabsList"
:isScroll="false"
:currentIndex="curTabIndex"
flexClass="flex-row-around"
@change="onTabChange"
/>
<view class="order-list-content">
<list-page-temp
:requestData="{ status: tabsList[curTabIndex].id }"
:getDataPromise="getMyServiceCouponList"
:reloadFlag="reloadFlag"
>
<template v-slot:item="{ data }">
<service-coupon-item
:data="data"
:showOptBtn="false"
:disabled="data.status === 3 || data.status === 4"
:showOrderBtn="data.status === 2"
@jumpToDetails="jumpToDetails"
>
<image
v-if="data.status === 3"
slot="status"
class="use-icon"
src="./static/coupon_used.png"
/>
</service-coupon-item>
</template>
</list-page-temp>
</view>
</view>
</template>
<script>
import TabsList from "@/components/TabsList.vue";
import ListPageTemp from "@/components/ListPageTemp.vue";
import ServiceCouponItem from "@/components/coupon/ServiceCouponItem";
import { getMyServiceCouponList } from "@/api/coupon";
export default {
components: {
TabsList,
ListPageTemp,
ServiceCouponItem,
},
data() {
return {
curTabIndex: 0,
reloadFlag: 0,
};
},
computed: {
tabsList() {
return [
{
name: "待使用",
id: 2,
},
{
name: "已使用",
id: 3,
},
{
name: "已过期",
id: 4,
},
];
},
},
options: {
styleIsolation: "shared",
},
onShow() {
this.reloadFlag = Math.random();
},
methods: {
getMyServiceCouponList,
onTabChange(item, index) {
this.curTabIndex = index;
},
jumpToDetails(data) {
uni.navigateTo({
url: `/pages/client/service/details?id=${data.fuwuquan_id}`
})
}
},
};
</script>
<style lang="scss" scoped>
.order-list-container {
height: 100%;
align-items: stretch;
justify-content: flex-start;
::v-deep {
.list-wrapper {
padding: 0 100rpx;
}
}
.order-list-content {
flex: 1;
overflow: hidden;
padding-left: 32rpx;
::v-deep {
.coupon-price {
color: $app_fc_alarm;
}
}
.use-icon {
width: 100rpx;
height: 100rpx;
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,745 @@
<template>
<view class="home-page">
<scroll-view class="homeContainer" scroll-y :show-scrollbar="false" :enhanced="true">
<view class="swiperWrapper">
<swiper indicator-dots="true" autoplay="true" interval="3000" duration="500" circular="true"
class="swiper">
<swiper-item v-for="(item, index) in swiperDataList" :key="index">
<image class="swiper-img" :src="item.image || `${imgPrefix}home-ground.png`" mode="aspectFill" />
</swiper-item>
</swiper>
<view class="userInfoWrapper">
<view class="userInfo" @click="handleUserInfo">
<image class="avatar-img"
:src="userInfo.avatar ? userInfo.avatar : `${imgPrefix}defaultHeadImg.png`"
mode="aspectFill" />
<view class="userContent">
<view class="userName">
{{ userInfo.username ? userInfo.username : "嗨,你好呀" }}
</view>
<view class="userTips" v-if="!userInfo.userID">
登陆享受更多精彩内容
</view>
<view v-else class="user-membership-row">
<view class="vipWrapper">
<image class="lableImg" :src="`${imgPrefix}home-vipLabel.png`" />
v{{ userInfo.vipLevel ? userInfo.vipLevel : 0 }}会员
</view>
<view
v-if="userInfo.membershipTier && userInfo.membershipTier > 0"
class="membership-tier-badge"
:class="{
'membership-tier-1': userInfo.membershipTier === 1,
'membership-tier-2': userInfo.membershipTier === 2,
'membership-tier-3': userInfo.membershipTier === 3
}"
>
<text class="membership-tier-text">{{ getMembershipTierText(userInfo.membershipTier) }}</text>
</view>
</view>
</view>
</view>
<view class="loginBtn" @click="toLogin" v-if="!userInfo.userID">
注册/登陆
</view>
<view class="loginBtn flexClass" v-else @click="toCouponList">
<view class="couponText">{{ userInfo.couponCount ? userInfo.couponCount : 0 }}张优惠券</view>
<image :src="`${imgPrefix}home-rightWhite Arrow.png`" class="discountCoupon" />
</view>
</view>
<view class="shadowBackground" />
</view>
<view class="menuBody">
<view class="firstMenu">
<view class="itemWrapper" @click="toReservation">
<text class="titlWrapper" style="transform: translateY(-4rpx);">你们在哪我们去哪</text>
<view class="content">预约洗护</view>
<view class="tips">随时随地上车洗澡</view>
<view class="itemImg">
<image class="menu-img-lg" :src="`${imgPrefix}home-menuBath.png`" mode="aspectFill" />
</view>
</view>
<view class="line" />
<view class="itemWrapper" @click="toDogTraining">
<text class="titlWrapper">狗狗训练</text>
<view class="content">上门服务</view>
<view class="tips">上门训犬寄养喂猫遛狗</view>
<view class="itemImg">
<image class="menu-img-sm" :src="`${imgPrefix}home-dogTraining.png`" mode="aspectFill" />
</view>
</view>
</view>
<view class="secondMenu">
<scroll-view class="scrollWrapper" :scroll-x="secondMenuItemList.length > 4" enable-flex
:scroll-with-animation="false"
@scroll="onSecondMenuScroll">
<view class="secondMenuInner" :class="{ 'no-scroll': secondMenuItemList.length <= 4 }">
<view class="itemWrapper" v-for="(item, i) in secondMenuItemList" :key="i"
@click="handleNav(item)">
<view class="imgWrapper">
<image class="second-icon" :src="item.img" mode="aspectFill" />
</view>
<view class="itemTitle">
{{ item.title }}
</view>
<view class="itemTips">
{{ item.tips }}
</view>
</view>
</view>
</scroll-view>
<view class="custom-indicator" v-if="secondMenuItemList.length > 4">
<view class="itemLine" :class="{ active: secondMenuIndicatorIndex === 0 }"></view>
<view class="itemLine" :class="{ active: secondMenuIndicatorIndex === 1 }"></view>
</view>
</view>
<view class="thirdMenu">
<view class="itemWrapper" @click="toPublicBenefit">
<view>
<view class="title">公益助力</view>
<view class="tips">帮我找个家</view>
</view>
<image class="third-icon" :src="`${imgPrefix}home-publicBenefit.png`" mode="aspectFill" />
</view>
<view class="itemWrapper" @click="toJoin">
<view>
<view class="title">加盟咨询</view>
<view class="tips">立即加盟咨询</view>
</view>
<image class="third-icon" :src="`${imgPrefix}home-joinIn.png`" mode="aspectFill" />
</view>
</view>
<view class="serviceMenu" @click="jumpToWeChat">
<view class="left">
<image class="service-icon" :src="`${imgPrefix}supportStaff.png`" mode="aspectFill" />
<view class="content">
<view class="title">在线客服</view>
<view class="tips">遇到什么问题您尽管说哦~</view>
</view>
</view>
<view>
<image :src="`${imgPrefix}right-arrow.png`" class="rightArrow" />
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import {
imgPrefix,
jumpToWeChat,
showLoginConfirmModal
} from "@/utils/common";
import {
userWllet
} from "../../../api/login";
export default {
name: "HomePage",
data() {
return {
imgPrefix,
couponCount: 1,
swiperDataList: [
{
image: `${imgPrefix}banner1.png`
},
{
image: `${imgPrefix}bannerShare.png`
}
],
secondMenuItemList: [{
title: "邀请有礼",
tips: "邀请好友得好礼",
img: `${imgPrefix}home-invite .png`,
naviUrl: "/pages/client/recharge/membership-code",
params: {
yaoqing_code: (userInfo) => userInfo?.userID || '',
member_id: (userInfo) => userInfo?.userID || ''
}
},
{
title: "领券中心",
tips: "领取更多优惠券",
img: `${imgPrefix}home-getCoupon.png`,
naviUrl: "/pages/client/coupon/get-list",
},
{
title: "充值有礼",
tips: "最高返赠1650元",
img: `${imgPrefix}home-recharge.png`,
naviUrl: '/pages/client/recharge/index',
params: {
user_id: (userInfo) => userInfo?.userID || '',
}
},
{
title: "开通会员",
tips: "服务享8折",
img: `${imgPrefix}home-openVip.png`,
naviUrl: "/pages/richText/member-interests"
},
],
walletInfo: {}, // 钱包信息
secondMenuIndicatorIndex: 0, // 第二排菜单指示器当前页 0=第一页 1=第二页
};
},
computed: {
couponCountText() {
return `${this.couponCount}张优惠券`;
},
userInfo() {
return this.$store.state?.user?.userInfo || {};
}
},
created() {
this.buyService()
},
methods: {
toLogin() {
uni.navigateTo({
url: "/pages/client/auth/index",
});
},
// 统一的 token 检查方法,未登录时弹窗让用户自主选择
async checkTokenAndExecute(callback) {
const token = uni.getStorageSync('token');
if (!token) {
const willLogin = await showLoginConfirmModal();
if (!willLogin) return;
// 用户选择去登录,跳转后不执行 callback
return;
}
if (typeof callback === 'function') {
callback();
}
},
async handleUserInfo() {
const token = uni.getStorageSync('token');
if (token) {
uni.navigateTo({
url: "/pages/client/mine/userInfo",
});
} else {
await showLoginConfirmModal();
}
},
toReservation() {
this.checkTokenAndExecute(() => {
// 直接调用父组件的 handleTabChange 方法来切换 TabBar
// 这是最可靠的方式,因为 home 组件是 index 组件的子组件
if (this.$parent && typeof this.$parent.handleTabChange === 'function') {
this.$parent.handleTabChange(["reservationPage"]);
}
});
},
toDogTraining() {
uni.navigateTo({
url: '/pageHome/service/index'
});
},
// 我的钱包
buyService() {
const value = JSON.parse(uni.getStorageSync('vuex'));
console.log(value, '??')
this.userId = value.user.userInfo.userID
this.nick_name = value.user.userInfo.username
// this.yaoqing_code = value.user.userInfo.yaoqing_code
// console.log(value,'--')
userWllet(value.user.userInfo.userID).then((res) => {
uni.setStorage({ //存入Storage
key: 'userInfo',
data: { //存的数据(可以是多条)
'user_id': res.data.user_id,
'wallet_id': res.data.id
}
})
});
},
onSecondMenuScroll(e) {
const scrollLeft = e.detail.scrollLeft || 0;
const sysInfo = uni.getSystemInfoSync();
const windowWidth = sysInfo.windowWidth || 375;
// 5项一屏4个可滚动范围=1项宽≈windowWidth/4过半即第二页
const maxScroll = windowWidth / 4;
this.secondMenuIndicatorIndex = scrollLeft >= maxScroll * 0.5 ? 1 : 0;
},
getMembershipTierText(tier) {
const tierMap = {
1: '黄金会员',
2: '白金会员',
3: '黑金会员'
};
return tierMap[tier] || '';
},
handleNav(item) {
this.checkTokenAndExecute(() => {
// 健康顾问暂未开放
if (item.title === '健康顾问') {
uni.showToast({
title: '暂未开放',
icon: 'none',
duration: 2000
});
return;
}
if (item.naviUrl) {
let url = item.naviUrl;
// 如果有 params 参数,进行拼接
if (item.params && typeof item.params === 'object') {
const params = [];
for (const key in item.params) {
const value = item.params[key];
// 支持函数形式,动态获取值(可接收 userInfo 和 walletInfo
const paramValue = typeof value === 'function'
? value(this.userInfo, this.walletInfo)
: value;
if (paramValue !== undefined && paramValue !== null) {
params.push(`${key}=${encodeURIComponent(paramValue)}`);
}
}
if (params.length > 0) {
url += (url.includes('?') ? '&' : '?') + params.join('&');
}
}
uni.navigateTo({
url: url,
});
}
});
},
toCouponList() { // 前往优惠券
uni.navigateTo({
url: "/pages/client/coupon/list",
});
},
toPublicBenefit() {
this.checkTokenAndExecute(() => {
uni.navigateTo({
url: '/pageHome/welfare/index'
});
});
},
toJoin() {
this.checkTokenAndExecute(() => {
uni.navigateTo({
url: '/pageHome/franchise/index'
});
});
},
jumpToWeChat() {
this.checkTokenAndExecute(() => {
jumpToWeChat();
});
},
},
};
</script>
<style lang="scss" scoped>
.home-page {
height: 100vh;
background-color: #ffecf3;
}
.homeContainer {
height: calc(100vh - #{$app_tabbar_height + 36});
background-color: #ffecf3;
overflow: scroll;
.avatar-img {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
}
.menu-img-lg {
width: 160rpx;
height: 160rpx;
}
.menu-img-sm {
width: 120rpx;
height: 164rpx;
}
.second-icon,
.third-icon,
.service-icon {
width: 96rpx;
height: 96rpx;
}
.service-icon {
width: 66rpx;
height: 66rpx;
}
.swiperWrapper {
position: relative;
.swiper {
height: 552rpx;
width: 100%;
.swiper-img {
width: 100%;
height: 100%;
}
}
.userInfoWrapper {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
position: absolute;
bottom: -5%;
left: 50%;
transform: translateX(-50%);
width: calc(100% - 40rpx);
padding: 32rpx 28rpx;
box-sizing: border-box;
border-radius: 16rpx;
z-index: 10;
.userInfo {
display: flex;
align-items: center;
.userContent {
margin-left: 16rpx;
.userName {
font-weight: 500;
font-size: 28rpx;
}
.userTips {
font-size: 20rpx;
color: #808080;
margin-top: 8rpx;
}
.user-membership-row {
display: flex;
align-items: center;
gap: 16rpx;
margin-top: 16rpx;
flex-wrap: wrap;
}
.vipWrapper {
display: inline-flex;
align-items: center;
background-color: #ff19a0;
padding: 4rpx 8rpx;
border-radius: 50px;
font-size: 20rpx;
color: #fff;
text-align: center;
.lableImg {
width: 24rpx;
height: 20rpx;
margin-right: 4rpx;
}
}
.membership-tier-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4rpx 8rpx;
border-radius: 50px;
font-size: 20rpx;
font-weight: 500;
}
.membership-tier-1 {
background-color: #EDCA69;
}
.membership-tier-1 .membership-tier-text {
color: #754410;
font-size: 20rpx;
}
.membership-tier-2 {
background-color: #CECECE;
}
.membership-tier-2 .membership-tier-text {
color: #3D3D3D;
font-size: 20rpx;
}
.membership-tier-3 {
background-color: #321500;
}
.membership-tier-3 .membership-tier-text {
color: #CBAD78;
font-size: 20rpx;
}
}
}
.loginBtn {
background-color: #ff19a0;
border-radius: 218px;
color: #fff;
font-size: 23rpx;
padding: 16rpx 24rpx;
display: flex;
align-items: center;
.couponText {
color: #fff;
font-size: 24rpx;
}
.discountCoupon {
width: 11rpx;
height: 18rpx;
margin-left: 8rpx;
}
}
}
.shadowBackground {
position: absolute;
left: 0;
bottom: -20rpx;
z-index: 8;
width: 100%;
height: 72rpx;
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #ffecf3 64%);
}
}
.menuBody {
margin-top: 32rpx;
padding: 20rpx;
.firstMenu {
display: flex;
background-color: #fff;
border-radius: 16rpx;
justify-content: space-between;
align-items: center;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.04);
.itemWrapper {
flex: 1;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
position: relative;
}
.line {
width: 2rpx;
background-color: #e8e8e8;
height: 280rpx;
}
.titlWrapper {
background-color: #ff19a0;
color: #fff;
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 0px 0px 12rpx 12rpx;
display: inline-block;
}
.content {
font-size: 36rpx;
font-weight: 500;
margin-top: 24rpx;
}
.tips {
font-size: 20rpx;
margin-top: 16rpx;
color: #808080;
}
.itemImg {
margin: auto;
margin-top: 48rpx;
margin-bottom: 32rpx;
}
}
.secondMenu {
margin-top: 20rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.04);
.scrollWrapper {
width: 100%;
height: auto; /* 由内容撑开;小程序 scroll-view 横向滚动时默认可能不随内容计算高度 */
min-height: 220rpx; /* 兜底:约等于 padding-top + 图标 + 标题 + 副标题 */
display: flex;
align-items: flex-start; /* 不拉伸子项,高度由内容决定 */
white-space: nowrap;
padding-top: 24rpx;
padding-bottom: 24rpx;
box-sizing: border-box;
.secondMenuInner {
display: flex;
flex-wrap: nowrap;
flex-shrink: 0;
width: 937.5rpx; /* 5 * 187.5 一屏4个共5项 */
&.no-scroll {
width: 100%; /* 四项时一屏排满,不滚动 */
.itemWrapper {
flex: 1;
width: 0; /* 均分宽度,避免被 menuBody padding 裁切 */
min-width: 0;
}
}
}
.itemWrapper {
width: 187.5rpx; /* 750/4 一屏固定4个 */
flex-shrink: 0;
text-align: center;
.imgWrapper {
display: inline-block;
margin: auto;
}
.itemTitle {
font-size: 24rpx;
font-weight: 500;
margin-top: 8rpx;
}
.itemTips {
font-size: 20rpx;
color: #808080;
margin-top: 8rpx;
}
}
}
.custom-indicator {
display: flex;
margin-top: 12rpx;
justify-content: center;
margin-bottom: 20rpx;
gap: 12rpx;
.itemLine {
background-color: #ededed;
width: 20rpx;
height: 6rpx;
border-radius: 170px;
&.active {
background-color: #ff19a0;
}
}
}
}
.thirdMenu {
display: flex;
gap: 16rpx;
margin-top: 20rpx;
.itemWrapper {
flex: 1;
background-color: #fff;
border-radius: 16rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 24rpx;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.04);
.title {
font-size: 24rpx;
font-weight: 500;
}
.tips {
font-size: 20rpx;
color: #808080;
margin-top: 16rpx;
}
}
}
.serviceMenu {
display: flex;
background-color: #fff;
margin-top: 20rpx;
padding: 20rpx 24rpx;
align-items: center;
justify-content: space-between;
border-radius: 16rpx;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.04);
.left {
display: flex;
align-items: center;
}
.rightArrow {
width: 11rpx;
height: 18rpx;
}
.content {
margin-left: 16rpx;
.title {
font-size: 28rpx;
font-weight: 500;
}
.tips {
font-size: 22rpx;
margin-top: 8rpx;
color: #808080;
}
}
}
}
}
.flexClass {
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,255 @@
<template>
<view class="index-container">
<view class="tab-page-wrapper">
<!-- 使用 v-if 替代 v-show只渲染当前页面以提升性能 -->
<home
v-if="activePageId === 'homePage'"
ref="homePage"
/>
<mine
v-if="activePageId === 'minePage'"
ref="minePage"
/>
<shop
v-if="activePageId === 'shopPage'"
ref="shopPage"
/>
<pet-profile
v-if="activePageId === 'filesPage'"
ref="filesPage"
/>
<reservation
v-show="activePageId === 'reservationPage'"
ref="reservationPage"
/>
</view>
<tab-bar
:activePageId="activePageId"
@changeTab="handleTabChange"
class="tab-bar-view"
/>
</view>
</template>
<script>
import TabBar from "@/components/TabBar.vue";
import Home from "../home/index.vue";
import Mine from "../mine/index.vue";
import Shop from "../shop/index.vue";
import PetProfile from "../pet-profile/index.vue";
import Reservation from "../../../page-reser/reservation/index.vue";
import { getUserInfo } from "../../../api/user";
import appConfig from "@/constants/app.config";
import { getCommunityDetail } from "../../../api/community";
export default {
name: "IndexPage",
components: {
TabBar,
Home,
Mine,
Shop,
PetProfile,
Reservation,
},
data() {
return {
activePageId: "homePage",
shareInfo: {
title: appConfig.appShareName,
path: "/pages/client/index/index",
},
getUserInfoPromise: null, // 用于防止重复调用 getUserInfo
};
},
methods: {
/**
* 处理 Tab 切换
* @param {Array} params - [pageId] 页面ID数组
* @param {*} otherInfo - 其他信息(如订单状态等)
*/
async handleTabChange([pageId], otherInfo) {
if (this.activePageId === pageId) {
return; // 避免重复切换
}
// 需要登录的页面列表(除了首页)
const needLoginPages = ['filesPage'];
// 如果需要登录且没有 token弹窗让用户自主选择
if (needLoginPages.includes(pageId)) {
const token = uni.getStorageSync('token');
if (!token) {
const { showLoginConfirmModal } = await import('@/utils/common');
const willLogin = await showLoginConfirmModal();
if (!willLogin) return;
return;
}
}
this.activePageId = pageId;
// 切换到 mine 页面时,调用 getUserInfo 获取最新用户信息
if (pageId === 'minePage') {
this.getUserInfo();
}
this.$nextTick(() => {
this.triggerPageShow(pageId, otherInfo);
});
},
/**
* 触发页面 onShow 方法
* @param {String} pageId - 页面ID
* @param {*} otherInfo - 其他信息
*/
triggerPageShow(pageId, otherInfo) {
// Reservation 组件不需要触发更新
const pageRef = this.$refs[pageId];
if (pageRef && typeof pageRef.onShowFun === "function") {
pageRef.onShowFun(otherInfo);
}
},
/**
* 获取用户信息(带防重复调用机制)
*/
getUserInfo() {
// 如果正在调用中,直接返回之前的 Promise
if (this.getUserInfoPromise) {
return this.getUserInfoPromise;
}
// 创建新的 Promise 并保存
this.getUserInfoPromise = getUserInfo()
.then((res) => {
this.$store.dispatch("user/setUserInfo", res?.data || {});
this.getUserInfoPromise = null; // 调用完成后清空
return res;
})
.catch((err) => {
console.error("获取用户信息失败:", err);
this.getUserInfoPromise = null; // 调用失败后清空
throw err;
});
return this.getUserInfoPromise;
},
/**
* 获取圈子详情
* @param {String|Number} id - 圈子ID
*/
getCommunityDetail(id) {
if (!id) return;
getCommunityDetail({
chongquan_id: id,
is_see: 0,
is_share: 1,
});
},
/**
* 处理分享成功事件
* @param {String|Number} communityId - 圈子ID
*/
handleShareSuccess(communityId) {
if (communityId) {
this.getCommunityDetail(communityId);
}
},
},
onLoad(option) {
console.log("首页 onLoad", option);
// 处理二维码扫描的 referrerID写入 vuexApp.onLaunch 也会处理一次,这里兜底)
if (option?.referrerID) {
this.$store.dispatch('user/setReferrerID', Number(option.referrerID) || 0);
}
// 处理页面跳转参数
if (option?.activePageId) {
const targetPageId = option.activePageId;
// 如果目标页面与当前页面不同,触发切换
if (this.activePageId !== targetPageId) {
this.handleTabChange([targetPageId]);
} else {
this.activePageId = targetPageId;
}
}
// 处理分享宠圈
if (option?.communityId) {
this.getCommunityDetail(option.communityId);
}
// 监听外部切换 TabBar 事件
uni.$on("changeTabBar", this.handleExternalTabChange);
},
onShow() {
this.getUserInfo();
this.$nextTick(() => {
this.triggerPageShow(this.activePageId);
});
},
onShareAppMessage(res) {
// 来自页面内分享按钮
if (res.from === "button") {
// 延迟触发分享成功事件
setTimeout(() => {
console.log("分发分享成功事件");
uni.$emit("shareSuccess", this.shareInfo?.communityId);
}, 3000);
return {
title: this.shareInfo.title,
path: this.shareInfo.path,
imageUrl: this.shareInfo.imageUrl,
};
}
return {
...this.shareInfo,
};
},
/**
* 处理外部切换 TabBar 事件
* @param {Object} pageInfo - 页面信息 {pageId, orderState}
*/
handleExternalTabChange(pageInfo) {
const { pageId, orderState } = pageInfo || {};
console.log(111)
if (pageId) {
this.handleTabChange([pageId], orderState);
}
},
destroyed() {
// 清理事件监听
uni.$off("changeTabBar", this.handleExternalTabChange);
uni.$off("shareSuccess", this.handleShareSuccess);
},
};
</script>
<style lang="scss" scoped>
.index-container {
height: 100vh;
display: flex;
flex-direction: column;
background: #fff;
.tab-page-wrapper {
flex: 1;
width: 100%;
box-sizing: border-box;
overflow: hidden;
padding-bottom: calc(#{$app_tabbar_height});
}
.tab-bar-view {
flex-shrink: 0;
}
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<view class="about-us">
<!-- <view class="flex-row-center">
<image class="logo-icon" :src="logoImg" />
</view> -->
<view
class="fs-32 app-fc-main SourceHanSansCN-Regular about-us-inner"
v-html="content"
></view>
</view>
</template>
<script>
import { getArticleDetail } from "../../../api/article";
import { ARTICLE_TYPE_ABOUT_US } from "@/constants/app.business";
export default {
data() {
return {
logoImg: "",
content: "",
id: ARTICLE_TYPE_ABOUT_US
};
},
methods: {
getDetail(id) {
getArticleDetail(id).then(
res => {
this.logoImg = res.data.article_thumb
this.content = res.data.content
}
)
}
},
onLoad(option) {
this.getDetail(this.id);
},
};
</script>
<style lang="scss" scoped>
.about-us {
height: 100%;
width: 100%;
padding: 0 0 20rpx;
box-sizing: border-box;
background: #fff;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.logo-icon {
width: 216rpx;
height: 216rpx;
border-radius: 42rpx;
margin: 34rpx 0;
}
.about-us-inner {
line-height: 72rpx;
padding: 0 32rpx 20rpx;
}
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<view class="article-container">
<view class="article-content" v-html="content"></view>
</view>
</template>
<script>
import { getArticleDetail } from "../../../api/article";
import { ARTICLE_TYPE_HELP } from "@/constants/app.business";
export default {
data() {
return {
id: ARTICLE_TYPE_HELP,
content: "",
};
},
mounted() {},
onLoad(option) {
this.getDetail(this.id);
},
methods: {
getDetail(id) {
getArticleDetail(id).then(
res => {
this.content = res.data.content
}
)
}
},
};
</script>
<style lang="scss" scoped>
.article-container {
height: 100%;
width: 100%;
box-sizing: border-box;
background: #fff;
padding-bottom: 20rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
padding-top: 20rpx;
.article-content {
padding: 0 32rpx;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,332 @@
<template>
<view class="mine-user-edit">
<view class="edit-content">
<view class="flex-row-between edit-cell">
<text class="title">头像</text>
<button class="flex-row-end user-avator" open-type="chooseAvatar" @chooseavatar="chooseavatar">
<image class="avator-icon" :src="userInfo.head_pic_url" />
<!-- <image class="arrow-icon" :src="`${imgPrefix}right-arrow.png`" /> -->
</button>
</view>
<view class="flex-row-between edit-cell">
<text class="title">昵称</text>
<input class="fs-24 app-fc-main SourceHanSansCN-Medium edit-input" type="nickname"
placeholder-class="fs-24 app-fc-normal SourceHanSansCN-Regular app-text-right"
:value="userInfo.nick_name" placeholder="点击输入昵称" @change="
(e) =>
onChange(e.detail.value && e.detail.value.trim(), 'nick_name')
" />
</view>
<form-cell title="出生年月" type="checkDate" placeholderText="选择出生日期" :value="userInfo.birthday"
:defaultDate="userInfo.birthday" @onChange="(value) => onChange(value, 'birthday')" />
<form-cell title="性别" type="custom" :showRightArrow="false" :noBorder="true">
<view class="flex-row-end edit-sex" slot="right">
<view class="flex-center fs-24 app-fc-main SourceHanSansCN-Medium sex-item"
:class="[userInfo.sex === 1 ? 'active man' : '']" @click="onChange(1, 'sex')">
<image class="sex-item-img"
:src="userInfo.sex === 1 ? `${imgPrefix}whiteMale.png` : `${imgPrefix}record-maleImg.png`">
</image>
</view>
<view class="flex-center fs-24 app-fc-main SourceHanSansCN-Medium sex-item"
:class="[userInfo.sex === 2 ? 'active women' : '']" @click="onChange(2, 'sex')">
<image class="sex-item-img"
:src="userInfo.sex === 2 ? `${imgPrefix}whiteFemale.png` : `${imgPrefix}record-femaleImg.png`">
</image>
</view>
</view>
</form-cell>
</view>
<view class="footer">
<view class="footerBtn" @click="onsubmit()">
保存
</view>
<view class="bottom-safe-area"></view>
</view>
</view>
</template>
<script>
import FormCell from "../../../components/FormCell.vue";
import {
updateUserInfo,
getUserInfo
} from "../../../api/user";
import appConfig from "../../../constants/app.config";
import {
imgPrefix
} from "@/utils/common";
import {
uploadImageToOSS_PUT
} from "@/utils/oss";
export default {
components: {
FormCell,
},
data() {
return {
userInfo: {
head_pic_url: "",
head_pic: "",
nick_name: "",
birthday: "",
sex: 0,
},
appConfig,
imgPrefix
};
},
onShow() {
// 直接从 store缓存中获取用户信息并映射到表单字段
const storeUserInfo = this.$store.state?.user?.userInfo || {};
if (storeUserInfo && Object.keys(storeUserInfo).length > 0) {
// 将 gender 转换为 sex: "male" -> 1, "female" -> 2, 其他 -> 0
let sex = 0;
if (storeUserInfo.gender === 'male') {
sex = 1;
} else if (storeUserInfo.gender === 'female') {
sex = 2;
} else if (storeUserInfo.sex) {
// 兼容旧的 sex 字段
sex = storeUserInfo.sex;
}
// 格式化生日:将 ISO 8601 格式 (2006-01-02T00:00:00+08:00) 转换为 YYYY-MM-DD 格式
let birthday = storeUserInfo.birthday || '';
if (birthday) {
// 如果是 ISO 8601 格式,提取日期部分
if (birthday.includes('T')) {
birthday = birthday.split('T')[0];
}
// 确保格式为 YYYY-MM-DD
const dateMatch = birthday.match(/^(\d{4})-(\d{2})-(\d{2})/);
if (!dateMatch) {
// 如果不是标准格式,尝试转换
const date = new Date(birthday);
if (!isNaN(date.getTime())) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
birthday = `${year}-${month}-${day}`;
}
}
}
this.userInfo = {
head_pic_url: storeUserInfo.avatar || storeUserInfo.head_pic_url || storeUserInfo.head_pic || '',
head_pic: storeUserInfo.avatar || storeUserInfo.head_pic || storeUserInfo.head_pic_url || '',
nick_name: storeUserInfo.nickname || storeUserInfo.nick_name || storeUserInfo.username || '',
birthday: birthday,
sex: sex,
};
}
},
methods: {
// 保存
onsubmit() {
const {
head_pic,
nick_name,
birthday,
sex
} = this.userInfo;
if (!head_pic || !nick_name) {
uni.showToast({
icon: "none",
title: "请将昵称和头像填写完整",
});
return;
}
// 将 sex 转换为 gender: 1 -> "male", 2 -> "female", 其他 -> "other"
let gender = "other";
if (sex === 1) {
gender = "male";
} else if (sex === 2) {
gender = "female";
}
uni.showLoading({
title: "处理中..."
});
// 构建请求参数
const params = {
username: nick_name,
gender: gender,
birthday: birthday,
};
// 如果头像地址不是 https 开头,才传给后端
if (head_pic && !head_pic.startsWith('https://')) {
params.avatar = head_pic;
}
updateUserInfo(params).then(() => {
uni.hideLoading();
uni.navigateBack();
}).catch((err) => {
uni.hideLoading();
console.error('更新用户信息失败:', err);
});
},
// 更新头像
async chooseavatar(e) {
const {
avatarUrl
} = e.detail;
uni.showLoading({
title: "上传中..."
});
try {
const { url, objectKey } = await uploadImageToOSS_PUT(avatarUrl);
console.log(url, objectKey, 'url, objectKey')
this.userInfo.head_pic_url = url; // 同时更新 head_pic_url因为模板使用的是这个字段
this.userInfo.head_pic = objectKey;
this.$forceUpdate();
uni.hideLoading();
} catch (error) {
console.error('头像上传失败:', error);
uni.hideLoading();
uni.showToast({
title: error?.message || "头像上传失败",
icon: "none"
});
}
},
onChange(value, key) {
this.userInfo[key] = value;
this.$forceUpdate();
},
},
};
</script>
<style lang="scss">
.mine-user-edit {
background-color: #ffecf3;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
.edit-content {
background-color: #fff;
border-radius: 16rpx;
padding: 0rpx 24rpx;
width: calc(100vw - 40rpx);
margin: 20rpx auto;
box-sizing: border-box;
.edit-cell {
border-bottom: 1rpx solid #ececec;
height: 110rpx;
width: 100%;
box-sizing: border-box;
.title {
color: #3D3D3D;
font-size: 24rpx;
.required {
color: #FF19A0;
}
}
.avator-icon {
width: 80rpx;
height: 80rpx;
border-radius: 80rpx;
background: gray;
}
.arrow-icon {
width: 11rpx;
height: 18rpx;
}
.edit-input {
height: 100%;
text-align: right;
flex: 1;
margin-left: 30rpx;
font-size: 24rpx;
}
}
.edit-sex {
width: 100%;
}
.sex-item {
width: 120rpx;
height: 64rpx;
background: #f9f7f9;
border-radius: 16rpx 16rpx 16rpx 16rpx;
margin-left: 50rpx;
&-img {
width: 28rpx;
height: 28rpx;
}
&.active {
color: #fff;
&.man {
background: #FF19A0;
}
&.women {
background: #FF19A0;
}
}
}
}
.user-avator {
margin: 0;
background: #fff;
border: 0;
padding: 0;
border-color: transparent;
padding-left: 100rpx;
&::after {
border: none;
}
}
.footer {
background-color: #fff;
border-radius: 32rpx 32rpx 0px 0px;
.footerBtn {
color: #fff;
background-color: #FF19A0;
width: calc(100% - 48rpx);
margin: auto;
margin-bottom: 24rpx;
margin-top: 12rpx;
border-radius: 100px;
text-align: center;
padding: 32rpx 0rpx;
}
.bottom-safe-area {
padding-bottom: constant(safe-area-inset-bottom);
/* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom);
/* 兼容 iOS >= 11.2 */
}
}
}
</style>

View File

@ -0,0 +1,750 @@
<template>
<view class="welfare-page">
<!-- Tab 导航 -->
<view class="tab-bar">
<view class="tab-item" :class="{ 'tab-active': activeTab === 'certificates' }" @click="switchTab('certificates')">
<text class="tab-text">荣誉证书</text>
<view class="tab-line" :class="{ 'tab-line-active': activeTab === 'certificates' }"></view>
</view>
<view class="tab-item" :class="{ 'tab-active': activeTab === 'adoptions' }" @click="switchTab('adoptions')">
<text class="tab-text">领养记录</text>
<view class="tab-line" :class="{ 'tab-line-active': activeTab === 'adoptions' }"></view>
</view>
<view class="tab-item" :class="{ 'tab-active': activeTab === 'applications' }" @click="switchTab('applications')">
<text class="tab-text">申请记录</text>
<view class="tab-line" :class="{ 'tab-line-active': activeTab === 'applications' }"></view>
</view>
</view>
<!-- 荣誉证书内容 -->
<view v-show="activeTab === 'certificates'" class="tab-content">
<view class="certificate-grid" :class="{ 'empty-container': !certificateList || certificateList.length === 0 }">
<template v-if="certificateList && certificateList.length > 0">
<view v-for="(item, index) in certificateList" :key="item.id || index" class="certificate-card">
<image :src="item.certificate_url" mode="aspectFit" class="certificate-image" @error="handleImageError" />
</view>
</template>
<view v-else class="certificate-placeholder">
<image :src="`${imgPrefix}certificatePlaceholder.png`" mode="aspectFit" class="placeholder-image" />
<text class="placeholder-text">暂无内容</text>
</view>
</view>
</view>
<!-- 领养记录内容 -->
<view v-show="activeTab === 'adoptions'" class="tab-content">
<view class="record-list">
<view v-for="(item, index) in adoptionList" :key="item.id || index" class="record-item"
@click="() => viewAdoptionDetail(item)">
<image class="record-pet-image" :src="item.pet_image_url || getDefaultPetImage()" mode="aspectFill"></image>
<view class="record-info">
<text class="record-pet-name">{{ item.pet_nickname || '未知' }}</text>
<text class="record-time">{{ formatTime(item.created_at) }}</text>
</view>
<view class="record-status" :class="{
'status-approved': item.status === 'approved' || item.status === 1,
'status-rejected': item.status === 'rejected' || item.status === 0,
'status-pending': !(item.status === 'approved' || item.status === 1 || item.status === 'rejected' || item.status === 0)
}">
<text class="status-text">{{ getStatusText(item.status) }}</text>
</view>
</view>
<view v-if="adoptionList.length === 0" class="empty-state">
<text class="empty-text">暂无领养记录</text>
</view>
</view>
</view>
<!-- 申请记录内容 -->
<view v-show="activeTab === 'applications'" class="tab-content">
<view class="application-list-content">
<view v-for="(item, index) in applicationList" :key="item.id || index" class="adopt-card"
@click="() => viewApplicationDetail(item)">
<view class="adopt-card-content">
<!-- 左侧图片 -->
<view class="pet-image-wrapper">
<image class="pet-image" :src="getPetImage(item)" mode="aspectFill" />
</view>
<!-- 右侧信息 -->
<view class="pet-info">
<!-- 名称性别年龄 -->
<view class="pet-header">
<text class="pet-name">{{ item.pet_nickname || '未知' }}</text>
<image v-if="item.gender" class="pet-gender"
:src="item.gender === 'male' ? imgPrefix + 'record-maleImg.png' : imgPrefix + 'record-femaleImg.png'"
mode="aspectFit" />
<text v-if="item.gender" class="pet-divider">|</text>
<text v-if="item.pet_age" class="pet-age">{{ item.pet_age + '个月' }}</text>
<view class="status-label" :class="{
'status-pending': item.status === 1,
'status-reviewing': item.status === 2,
'status-completed': item.status === 3
}">
<text class="status-label-text">{{ getApplicationStatusText(item.status) }}</text>
</view>
</view>
<!-- 描述文字 -->
<view class="pet-description" v-if="item.description">
<text class="description-text">{{ item.description }}</text>
</view>
<!-- 状态标签 -->
<view class="pet-tags">
<view class="tag" :class="item.is_immunized === 1 ? 'tag-completed' : 'tag-pending'">
<text class="tag-text">{{ item.is_immunized === 1 ? '已免疫' : '未免疫' }}</text>
</view>
<view class="tag" :class="item.is_sterilized === 1 ? 'tag-completed' : 'tag-pending'">
<text class="tag-text">{{ item.is_sterilized === 1 ? '已驱虫' : '未驱虫' }}</text>
</view>
<view class="tag" :class="item.is_dewormed === 1 ? 'tag-completed' : 'tag-pending'">
<text class="tag-text">{{ item.is_dewormed === 1 ? '已绝育' : '未绝育' }}</text>
</view>
</view>
</view>
</view>
<!-- 撤销申请按钮待处理状态时显示 -->
<view v-if="item.status === 1" class="cancel-application-btn" @click.stop="() => handleCancelApplication(item)">
<text class="cancel-application-text">撤销申请</text>
</view>
</view>
<view v-if="applicationList.length === 0" class="empty-state">
<text class="empty-text">暂无申请记录</text>
</view>
</view>
</view>
<!-- 撤销申请确认弹窗 -->
<pop-up-modal v-if="isShowCancelApplicationModal" content="确定要撤销申请吗?" @confirm="confirmCancelApplication"
@cancel="isShowCancelApplicationModal = false" />
</view>
</template>
<script>
import { getCertificates as getCertificatesApi } from '@/api/user';
import { getMyApplications, cancelPetApply } from '@/api/common';
import { imgPrefix } from '@/utils/common';
import moment from 'moment';
import PopUpModal from '@/components/PopUpModal.vue';
export default {
name: 'MyWelfare',
components: {
PopUpModal
},
data() {
return {
imgPrefix,
activeTab: 'certificates', // 'certificates', 'adoptions', 'applications'
certificateList: [],
adoptionList: [],
applicationList: [],
applicationPage: 1,
applicationPageSize: 10,
isShowCancelApplicationModal: false,
cancelApplicationInfo: null
};
},
computed: {
userName() {
const userInfo = this.$store.state?.user?.userInfo || {};
return userInfo.username || userInfo.nickname || '用户';
}
},
onLoad(options) {
const tab = (options && options.tab) || '';
if (tab === 'applications') {
this.activeTab = 'applications';
this.loadApplications();
} else {
this.loadCertificates();
}
},
onShow() {
// 每次页面显示时,如果当前是证书 tab重新加载数据
if (this.activeTab === 'certificates') {
this.loadCertificates();
}
},
methods: {
switchTab(tab) {
this.activeTab = tab;
if (tab === 'certificates') {
// 切换到证书 tab 时,重新加载数据
this.loadCertificates();
} else if (tab === 'adoptions' && this.adoptionList.length === 0) {
this.loadAdoptions();
} else if (tab === 'applications' && this.applicationList.length === 0) {
this.loadApplications();
}
},
loadCertificates() {
uni.showLoading({
title: '加载中...',
mask: true
});
getCertificatesApi().then((res) => {
uni.hideLoading();
// this.certificateList = res.data || [];
console.log('证书列表数据:', this.certificateList);
}).catch((err) => {
uni.hideLoading();
console.error('获取证书列表失败:', err);
this.certificateList = [];
uni.showToast({
title: err || '获取证书列表失败',
icon: 'none'
});
});
},
handleImageError(e) {
console.error('图片加载失败:', e);
},
loadAdoptions() {
// TODO: 调用领养记录接口
this.adoptionList = [];
},
loadApplications() {
uni.showLoading({
title: '加载中...',
mask: true
});
getMyApplications({
page: this.applicationPage,
page_size: this.applicationPageSize
}).then((res) => {
uni.hideLoading();
this.applicationList = res.data || [];
console.log('申请记录列表数据:', this.applicationList);
}).catch((err) => {
uni.hideLoading();
console.error('获取申请记录失败:', err);
this.applicationList = [];
uni.showToast({
title: err || '获取申请记录失败',
icon: 'none'
});
});
},
viewAdoptionDetail(item) {
uni.navigateTo({
url: `/pageHome/welfare/pet-detail?id=${item.pet_id || item.id}`
});
},
viewApplicationDetail(item) {
const adoptionId = item.adoption_id || item.id;
if (!adoptionId) return;
uni.navigateTo({
url: `/pageHome/welfare/pet-detail?adoption_id=${adoptionId}&from=applications`,
success: (res) => {
res.eventChannel.on('refreshApplications', () => {
this.loadApplications();
});
}
});
},
handleCancelApplication(item) {
// 显示撤销申请确认弹窗
if (!item) {
console.error('撤销申请item 未定义');
uni.showToast({
title: "数据错误",
icon: "none",
});
return;
}
console.log('撤销申请信息:', item);
this.cancelApplicationInfo = item;
this.isShowCancelApplicationModal = true;
},
confirmCancelApplication() {
// 确认撤销申请
const cancelInfo = this.cancelApplicationInfo;
if (!cancelInfo) {
this.isShowCancelApplicationModal = false;
uni.showToast({
title: "数据错误",
icon: "none",
});
return;
}
const adoptionId = cancelInfo.adoption_id || cancelInfo.id;
if (!adoptionId) {
uni.showToast({
title: "缺少必要参数",
icon: "none",
});
this.isShowCancelApplicationModal = false;
this.cancelApplicationInfo = null;
return;
}
this.isShowCancelApplicationModal = false;
uni.showLoading({
title: "正在撤销申请",
mask: true,
});
cancelPetApply({
adoption_id: adoptionId
})
.then(() => {
uni.hideLoading();
this.cancelApplicationInfo = null;
uni.showToast({
title: "撤销成功",
icon: "success",
});
// 刷新申请记录列表
this.loadApplications();
})
.catch((err) => {
uni.hideLoading();
this.cancelApplicationInfo = null;
uni.showToast({
title: err || "撤销申请失败",
icon: "none",
});
});
},
getDefaultCertificateImage(index) {
// 返回默认证书图片路径
const defaultImages = [
'welfare-cert-1.png',
'welfare-cert-2.png',
'welfare-cert-3.png'
];
return `${this.imgPrefix}${defaultImages[index % defaultImages.length]}`;
},
getCertificateTitle(index) {
const titles = [
'爱心猫粮证书',
'爱心狗粮证书',
'投喂达人证书',
'为爱续航证书',
'予爱同行证书',
'以爱为名证书',
'自在摇曳证书',
'万物生长证书',
'生生不息证书',
'哇们爱宠官证书',
'拆蛋专家证书',
'哇们爱心使者证书'
];
return titles[index] || '荣誉证书';
},
getDefaultPetImage() {
return `${this.imgPrefix}home-head.png`;
},
getPetImage(item) {
// 如果有图片,使用图片
if (item.pet_image_url) {
return item.pet_image_url;
}
return 'https://qcloud.dpfile.com/pc/YqyJiv76-rg2R6TRfCBcanAGqub6NNziEZdnXFdG23T2y5o8aoN_P8m9D9Q8eeWh.jpg';
},
formatTime(time) {
if (!time) return '';
return moment(time).format('YYYY-MM-DD HH:mm');
},
getStatusClass(status) {
// 根据状态返回样式类
if (status === 'approved' || status === 1) {
return 'status-approved';
} else if (status === 'rejected' || status === 0) {
return 'status-rejected';
}
return 'status-pending';
},
getStatusText(status) {
// 根据状态返回文本
if (status === 'approved' || status === 1) {
return '已通过';
} else if (status === 'rejected' || status === 0) {
return '已拒绝';
}
return '审核中';
},
getApplicationStatusText(status) {
// 申请记录状态文本
if (status === 1) {
return '待处理';
} else if (status === 2) {
return '审核中';
} else if (status === 3) {
return '已完成';
}
return '未知';
}
}
};
</script>
<style lang="scss" scoped>
.welfare-page {
min-height: 100vh;
background-color: #f7f8fa;
position: relative;
display: flex;
flex-direction: column;
}
.tab-bar {
display: flex;
align-items: center;
justify-content: space-around;
background-color: #fff;
padding: 0 20rpx;
border-bottom: 1rpx solid #f0f0f0;
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx 0;
position: relative;
.tab-text {
font-size: 28rpx;
color: #666;
transition: color 0.3s;
}
.tab-line {
width: 24rpx;
height: 10rpx;
background-color: transparent;
border-radius: 100px;
margin-top: 16rpx;
transition: background-color 0.3s;
}
&.tab-active {
.tab-text {
color: #FF19A0;
font-weight: 500;
}
.tab-line-active {
background-color: #FF19A0;
}
}
}
}
.tab-content {
padding: 20rpx;
min-height: calc(100vh - 200rpx);
flex: 1;
overflow-y: auto;
box-sizing: border-box;
&[v-show="false"] {
display: none;
}
}
// 证书网格样式
.certificate-grid {
background-color: #fff;
padding: 20rpx;
display: flex;
flex-wrap: wrap;
&.empty-container {
min-height: calc(100vh - 200rpx);
height: 100%;
}
.certificate-card {
flex: 0 0 calc(100% / 3);
display: flex;
justify-content: center;
}
.certificate-image {
width: 200rpx;
height: 200rpx;
}
.certificate-placeholder {
width: 100%;
min-height: 400rpx;
background-color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx 20rpx;
}
.placeholder-image {
width: 213.15px;
height: 160px;
margin-bottom: 20rpx;
}
.placeholder-text {
font-size: 28rpx;
color: #999;
}
}
// 申请记录列表样式(按照 adopt-list 的 adopt-card 样式)
.application-list-content {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.adopt-card {
width: 100%;
background: #fff;
border-radius: 24rpx;
padding: 24rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.adopt-card-content {
display: flex;
flex-direction: row;
gap: 16rpx;
}
.pet-image-wrapper {
width: 200rpx;
height: 200rpx;
flex-shrink: 0;
border-radius: 16rpx;
overflow: hidden;
background-color: #f0f0f0;
}
.pet-image {
width: 100%;
height: 100%;
}
.pet-info {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.pet-header {
display: flex;
align-items: center;
flex-wrap: wrap;
margin-bottom: 16rpx;
gap: 8rpx;
}
.pet-name {
font-size: 28rpx;
color: #3D3D3D;
}
.pet-gender {
width: 28rpx;
height: 28rpx;
margin-left: 4rpx;
}
.pet-divider {
font-size: 24rpx;
color: #999;
margin: 0 8rpx;
}
.pet-age {
font-size: 24rpx;
color: #3D3D3D;
}
.status-label {
margin-left: auto;
padding: 4rpx 12rpx;
border-radius: 8rpx;
display: inline-flex;
align-items: center;
justify-content: center;
&.status-pending {
background-color: #FEF6FF;
.status-label-text {
color: #FF19A0;
font-size: 20rpx;
}
}
&.status-reviewing {
background-color: #FEF6FF;
.status-label-text {
color: #FF19A0;
font-size: 20rpx;
}
}
&.status-completed {
background-color: #E8F5E9;
.status-label-text {
color: #4CAF50;
font-size: 20rpx;
}
}
.status-label-text {
font-size: 20rpx;
line-height: 1;
}
}
.pet-description {
margin-bottom: 16rpx;
flex: 1;
}
.description-text {
font-size: 20rpx;
color: #9B939A;
line-height: 1.6;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
.pet-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
align-items: center;
}
.tag {
padding: 8rpx 16rpx;
border-radius: 8rpx;
display: inline-flex;
align-items: center;
justify-content: center;
}
.tag-pending {
background-color: #E5E5E5;
}
.tag-pending .tag-text {
color: #9B939A;
font-size: 20rpx;
}
.tag-completed {
background-color: #FF19A0;
}
.tag-completed .tag-text {
color: #fff;
font-size: 20rpx;
}
.tag-text {
font-size: 20rpx;
line-height: 1;
}
.cancel-application-btn {
margin-top: 0;
padding: 12rpx 18rpx;
border-radius: 100px;
border: 1rpx solid #9B939A;
display: flex;
align-items: center;
justify-content: center;
align-self: flex-end;
.cancel-application-text {
font-size: 24rpx;
color: #272427;
}
}
// 领养记录列表样式(保留原有样式)
.record-list {
.record-item {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
.record-pet-image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
margin-right: 24rpx;
}
.record-info {
flex: 1;
display: flex;
flex-direction: column;
.record-pet-name {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-bottom: 12rpx;
}
.record-time {
font-size: 24rpx;
color: #999;
}
}
.record-status {
padding: 8rpx 20rpx;
border-radius: 20rpx;
font-size: 24rpx;
&.status-approved {
background-color: #E8F5E9;
color: #4CAF50;
}
&.status-rejected {
background-color: #FFEBEE;
color: #F44336;
}
&.status-pending {
background-color: #FFF3E0;
color: #FF9800;
}
}
}
}
.empty-state {
text-align: center;
padding: 100rpx 0;
.empty-text {
font-size: 28rpx;
color: #999;
}
}
</style>

View File

@ -0,0 +1,154 @@
<template>
<view class="news-item" @click="$emit('toDetails', data)">
<view class="news-icon-wrapper">
<view class="news-icon">
<image class="bell-icon-img" :src="`${imgPrefix}notice-tips.png`" mode="aspectFit" />
</view>
</view>
<view class="news-content-wrapper">
<view class="news-top-row">
<view class="news-title">{{ data.title || "" }}</view>
<view class="news-time">{{ time }}</view>
</view>
<view class="news-desc-row">
<view class="news-desc">{{ data.desc || "" }}</view>
<view v-if="!isRead" class="news-dot"></view>
</view>
</view>
</view>
</template>
<script>
import { imgPrefix } from '@/utils/common';
export default {
props: {
data: {
type: Object,
default: () => {},
},
},
data() {
return {
imgPrefix
};
},
computed: {
time() {
if (this.data.add_time) {
const date = new Date(this.data.add_time * 1000);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
return "";
},
isRead() {
// 假设数据中有 is_read 字段,如果没有则默认为未读
return this.data.is_read === 1 || this.data.read_status === 1;
},
},
};
</script>
<style lang="scss" scoped>
.news-item {
display: flex;
align-items: flex-start;
padding: 24rpx 32rpx;
background: #fff;
.news-icon-wrapper {
flex-shrink: 0;
margin-right: 24rpx;
.news-icon {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #FF19A0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.bell-icon-img {
width: 40rpx;
height: 40rpx;
filter: brightness(0) invert(1);
}
.no-message-text {
font-size: 20rpx;
color: #fff;
margin-top: 4rpx;
white-space: nowrap;
}
}
}
.news-content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
.news-top-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
.news-title {
flex: 1;
font-size: 28rpx;
color: #3D3D3D;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 20rpx;
}
.news-time {
flex-shrink: 0;
font-size: 24rpx;
color: #999;
}
}
.news-desc-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
.news-desc {
flex: 1;
font-size: 24rpx;
color: #999;
line-height: 36rpx;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
margin-right: 12rpx;
}
.news-dot {
flex-shrink: 0;
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #FF19A0;
margin-top: 10rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<view class="news-details">
<view class="fs-32 SourceHanSansCN-Bold app-fc-main details-title">
{{ newsInfo.desc || "" }}
</view>
<view class="fs-24 PingFangSC-Light app-fc-normal details-time">
{{ time }}
</view>
<view
class="fs-32 SourceHanSansCN-Regular details-info"
v-html="newsInfo.content"
></view>
</view>
</template>
<script>
import { getNoticeDetails } from "../../../api/notice";
export default {
data() {
return {
id: "",
newsInfo: {},
};
},
computed: {
time() {
return this.newsInfo.add_time
? new Date(this.newsInfo.add_time * 1000).format("yyyy-MM-dd hh:mm:ss")
: "";
},
},
onLoad(options) {
const { id } = options;
this.id = id;
this.getNoticeInfo();
},
methods: {
getNoticeInfo() {
getNoticeDetails(this.id).then((res) => {
this.newsInfo = res?.info || {};
uni.setNavigationBarTitle({
title: this.newsInfo.title || "",
});
});
},
},
};
</script>
<style lang="scss" scoped>
.news-details {
background: #fff;
height: 100%;
width: 100%;
box-sizing: border-box;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
padding-top: 38rpx;
.details-title {
padding:0 36rpx;
}
.details-time {
margin: 20rpx 0;
padding: 0 36rpx;
}
.details-info {
color: #726e71;
line-height: 72rpx;
padding: 0 36rpx;
}
}
</style>

View File

@ -0,0 +1,63 @@
<template>
<view class="news-list-container">
<list-page-temp class="news-list-inner" :getDataPromise="getNoticeList" :reloadFlag="reloadFlag"
:requestData="requestData" :emptyImage="`${imgPrefix}certificatePlaceholder.png`" emptyText="暂无系统消息">
<template v-slot:item="{ data }">
<news-item class="news-item-wrapper" :data="data" @toDetails="toDetails(data)" />
</template>
</list-page-temp>
</view>
</template>
<script>
import {
getNoticeList
} from "../../../api/notice";
import NewsItem from "./components/NewsItem.vue";
import { imgPrefix } from "@/utils/common";
export default {
components: {
NewsItem,
},
data() {
return {
newsList: [],
reloadFlag: 0,
requestData: {},
imgPrefix,
};
},
onShow() {
this.reloadFlag = Math.random();
},
methods: {
getNoticeList,
toDetails(data) {
uni.navigateTo({
url: `/pages/client/news/details?id=${data.notice_id}`,
});
},
},
};
</script>
<style lang="scss" scoped>
.news-list-container {
height: 100%;
width: 100%;
box-sizing: border-box;
background: #fff;
border-top: 1rpx solid #f4f4f4;
.news-list-inner {
height: 100%;
::v-deep {
.news-item {
width: 100vw;
box-sizing: border-box;
}
}
}
}
</style>

View File

@ -0,0 +1,101 @@
<template>
<view class="flex-column-start after-sale-container">
<tabs-list
:list="tabsList"
:isScroll="false"
:currentIndex="curTabIndex"
flexClass="flex-row-around"
@change="onTabChange"
/>
<view class="after-sale-content">
<list-page-temp
:getDataPromise="getShopOrderList"
:requestData="{ tui_status: tabsList[curTabIndex].id }"
:reloadFlag="reloadFlag"
>
<template v-slot:item="{ data }">
<after-sale-order-item :data="data" />
</template>
</list-page-temp>
</view>
</view>
</template>
<script>
import TabsList from "@/components/TabsList.vue";
import ListPageTemp from "@/components/ListPageTemp.vue";
import AfterSaleOrderItem from "./components/AfterSaleOrderItem.vue";
import { getShopOrderList } from '@/api/shop'
import {
SHOP_ORDER_AFTERSALE,
SHOP_ORDER_AFTERSALE_DONE,
SHOP_ORDER_AFTERSALE_REJECT,
} from "@/constants/app.business";
export default {
components: {
TabsList,
ListPageTemp,
AfterSaleOrderItem,
},
data() {
return {
curTabIndex: 0,
reloadFlag: 0,
};
},
computed: {
tabsList() {
return [
{
name: "售后中",
id: SHOP_ORDER_AFTERSALE,
},
{
name: "已退款",
id: SHOP_ORDER_AFTERSALE_DONE,
},
{
name: "已驳回",
id: SHOP_ORDER_AFTERSALE_REJECT,
},
];
},
},
options: {
styleIsolation: "shared",
},
onLoad() {
this.reloadFlag = Math.random();
},
methods: {
getShopOrderList,
onTabChange(item, index) {
this.curTabIndex = index;
},
},
};
</script>
<style lang="scss" scoped>
.after-sale-container {
height: 100%;
align-items: stretch;
justify-content: flex-start;
::v-deep {
.tabs-list {
.list-wrapper {
padding: 0 100rpx;
}
}
}
.after-sale-content {
flex: 1;
overflow: hidden;
}
}
</style>

View File

@ -0,0 +1,113 @@
<template>
<view class="order-item">
<view class="flex-row-between order-title">
<text class="fs-28 app-fc-normal">订单编号{{ data.order_no || '-' }}</text>
</view>
<view class="order-content">
<good-info
:data="data.goods_list"
@openGoodsModal="$emit('openGoodsModal')"
/>
</view>
<view class="flex-row-between order-status" @click="jumpToAfterSaleDetails">
<text
v-if="data.tui_status === SHOP_ORDER_AFTERSALE"
class="fs-28 app-fc-main"
>
{{ "等待平台确认" }}
</text>
<text
v-else-if="data.tui_status === SHOP_ORDER_AFTERSALE_DONE"
class="fs-28 app-fc-main"
>
{{ "退款成功" }}
</text>
<text
v-else-if="data.tui_status === SHOP_ORDER_AFTERSALE_REJECT"
class="fs-28 app-fc-main"
>
{{ "退款驳回" }}
</text>
<image
class="arrow-icon"
src="@/static/images/arrow_right_black.png"
mode="widthFix"
/>
</view>
</view>
</template>
<script>
import {
SHOP_ORDER_AFTERSALE,
SHOP_ORDER_AFTERSALE_DONE,
SHOP_ORDER_AFTERSALE_REJECT,
} from "@/constants/app.business";
import GoodInfo from "./GoodInfo.vue";
export default {
props: {
data: {
type: Object,
default: () => {},
},
},
components: {
GoodInfo,
},
data() {
return {
SHOP_ORDER_AFTERSALE,
SHOP_ORDER_AFTERSALE_DONE,
SHOP_ORDER_AFTERSALE_REJECT,
};
},
mounted() {},
methods: {
jumpToAfterSaleDetails() {
uni.navigateTo({
url: `/pages/client/order/details?id=${this.data.order_id}`,
});
},
},
};
</script>
<style lang="scss" scoped>
.order-item {
width: calc(100vw - 32rpx * 2);
background: #fff;
margin: 28rpx 32rpx 32rpx;
border-radius: 30rpx;
padding: 0 32rpx 32rpx;
box-sizing: border-box;
.order-title {
padding: 32rpx 0;
border-bottom: 1rpx solid #ececec;
.order-btn {
width: 104rpx;
height: 48rpx;
background: #fef6ff;
border-radius: 48rpx;
}
}
.order-status {
width: 100%;
height: 76rpx;
background: #f5f5f5;
padding: 0 32rpx 0 24rpx;
box-sizing: border-box;
border-radius: 16rpx;
.arrow-icon {
width: 24rpx;
height: 24rpx;
}
}
}
</style>

View File

@ -0,0 +1,175 @@
<template>
<view class="flex-row-start info-cell good-info" :class="{'good-info-multi' : data.length > 1}">
<template v-if="data.length === 1">
<image class="good-icon" :src="data[0].product_pic" mode="aspectFill" @click="$emit('clickGoodImg', data[0])" />
<view class="good-content" @click="$emit('clickGoodInfo', data[0])">
<view class="goods-row-first">
<view class="goods-name">{{ data[0].product_name || "" }}</view>
<text class="goods-price">¥{{ data[0].goods_price || actual_price }}</text>
</view>
<view class="goods-row-second">
<view class="goods-spec">
{{ data[0].shuxing_name || "" }}{{ data[0].shuxing_name && data[0].price_name ? ";" : "" }}{{ data[0].price_name || "" }}
</view>
<text class="goods-count">{{ data[0].number || 1 }}</text>
</view>
</view>
</template>
<template v-else>
<view class="flex-row-between good-info-more" @click="$emit('openGoodsModal')">
<view class="flex-row-start">
<image v-for="(img, i) in goodsImgs.slice(0, 3)" :key="i" class="good-img" :src="img" mode="aspectFill" />
</view>
<view class="good-info-right">
<text class="good-total-price">¥{{ actual_price }}</text>
<text class="fs-24 app-fc-normal good-num">
{{ goodsImgs.length }}
</text>
</view>
</view>
</template>
</view>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: () => [],
},
actual_price:String
},
data() {
return {};
},
computed: {
goodsImgs() {
return this.data.map((item) => item.product_pic).filter((v) => !!v);
},
totalPrice() {
return this.data.reduce((sum, item) => {
const price = parseFloat(item.goods_price || 0);
const number = parseInt(item.number || 1);
return sum + price * number;
}, 0).toFixed(2);
},
},
mounted() {
console.log(this.data,'--')
},
methods: {},
};
</script>
<style lang="scss" scoped>
.info-cell {
border-radius: 20rpx;
box-sizing: border-box;
.arrow-icon {
width: 11rpx;
height: 18rpx;
}
&.good-info {
padding-top: 20rpx;
align-items: center;
.good-icon {
width: 100rpx;
height: 100rpx;
border-radius: 8rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.good-content {
flex: 1;
min-width: 0;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 100rpx;
.goods-row-first {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8rpx;
.goods-name {
flex: 1;
font-size: 28rpx;
color: #3D3D3D;
line-height: 40rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 20rpx;
}
.goods-price {
flex-shrink: 0;
font-size: 28rpx;
color: #3D3D3D;
font-weight: 500;
}
}
.goods-row-second {
display: flex;
align-items: center;
justify-content: space-between;
.goods-spec {
flex: 1;
font-size: 24rpx;
color: #999;
margin-right: 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.goods-count {
flex-shrink: 0;
font-size: 24rpx;
color: #999;
}
}
}
}
.good-info-more {
width: 100%;
.good-img {
width: 100rpx;
height: 100rpx;
margin-right: 8rpx;
border-radius: 8rpx;
}
.good-info-right {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
.good-total-price {
font-size: 28rpx;
color: #3D3D3D;
font-weight: 500;
margin-bottom: 8rpx;
}
.good-num {
flex-shrink: 0;
}
}
}
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<select-modal
class="good-info-modal"
title="商品信息"
@close="$emit('close', false)"
>
<view class="good-info-content">
<view
class="flex-row-start info-cell good-info"
v-for="data in goods"
:key="data.goods_id"
>
<image
class="good-icon"
:src="data.goods_pic"
@click="$emit('clickGoodImg', data)"
/>
<view class="good-content" @click="$emit('clickGoodInfo', data)">
<view>
<view class="fs-32 app-fc-main app-text-ellipse app-font-bold">
{{ data.goods_name || "" }}
</view>
<view class="fs-24 app-fc-normal">
{{ data.shuxing_name || "" }}{{ data.price_name || "" }}
</view>
</view>
<view class="flex-row-between">
<text class="fs-28 app-fc-main">
实付款
<text class="fs-28 app-fc-alarm">
{{ data.goods_price || 0 }}
</text>
</text>
<text class="fs-24 app-fc-normal">x{{ data.number || 1 }}</text>
</view>
</view>
</view>
</view>
</select-modal>
</template>
<script>
import SelectModal from "@/components/select-modal.vue";
import GoodInfo from "./GoodInfo.vue";
export default {
components: {
SelectModal,
GoodInfo,
},
options: {
styleIsolation: "shared",
},
props: {
goods: {
type: Array,
default: () => [],
},
},
data() {
return {};
},
mounted() {},
methods: {},
};
</script>
<style lang="scss" scoped>
.good-info-modal {
::v-deep {
.selected-modal .model-container {
background: #fff0f5;
}
}
.good-info-content {
padding: 48rpx 32rpx 0;
max-height: 75vh;
overflow: auto;
box-sizing: border-box;
.info-cell {
background: #fff;
border-radius: 30rpx;
padding: 40rpx;
box-sizing: border-box;
margin-bottom: 32rpx;
.arrow-icon {
width: 20rpx;
height: 24rpx;
}
&.good-info {
padding: 20rpx 48rpx 20rpx 20rpx;
align-items: stretch;
.good-icon {
width: 160rpx;
height: 160rpx;
border-radius: 20rpx;
margin-right: 24rpx;
}
.good-content {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: stretch;
}
}
}
}
}
</style>

View File

@ -0,0 +1,392 @@
<template>
<view class="order-item">
<view class="flex-row-between order-title" @click="jumpToDetails">
<text class="fs-24 app-fc-normal">
订单编号{{ data.order_no || "-" }}
</text>
<!-- 待支付状态显示倒计时横幅 -->
<view v-if="data.status === SHOP_ORDER_UNPAY && !data.tui_status && countDownTime > 0"
class="order-status-banner">
<view class="status-banner-left">
<text class="status-text">等待付款</text>
</view>
<view class="status-banner-right">
<text class="countdown-text">{{ formatCountdown(countDownTime) }}</text>
</view>
</view>
<!-- 其他状态显示原有样式 -->
<view v-else-if="
([
SHOP_ORDER_CANCEL,
SHOP_ORDER_UNPAY,
SHOP_ORDER_UNSLIVER,
SHOP_ORDER_UNRECEIVE,
SHOP_ORDER_DONE,
SHOP_ORDER_UNREMARK,
].includes(data.status) &&
!data.tui_status) ||
[SHOP_ORDER_AFTERSALE_REJECT].includes(data.tui_status)
" class="flex-center fs-24 order-btn" :class="[
![SHOP_ORDER_DONE, SHOP_ORDER_CANCEL, SHOP_ORDER_UNREMARK].includes(
data.status
)
? 'app-fc-mark confirm'
: 'cancel',
]">
{{ orderStatus }}
</view>
<view v-if="
[SHOP_ORDER_AFTERSALE, SHOP_ORDER_AFTERSALE_DONE].includes(
data.tui_status
)
" class="flex-center fs-24 order-btn cancel">
{{ refundOrderStatus }}
</view>
</view>
<view class="order-content" :class="{ 'split-border': showStatusBtn }" @click="jumpToDetails">
<good-info :data="data.items" :actual_price="data.actual_price" />
<view v-if="data.type && data.type !== 1" class="fs-24 app-fc-mark order-type">
随车订单
</view>
</view>
<view v-if="showStatusBtn" class="order-btns">
<!-- 待支付 -->
<template v-if="[SHOP_ORDER_UNPAY].includes(data.status)">
<view class="flex-center fs-24 app-fc-main cancel-order-btn" @click.stop="$emit('cancelOrder', data)">
取消订单
</view>
<view class="order-btns-right">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服
</view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm" @click.stop="$emit('pay', data)">
立即支付
</view>
</view>
</template>
<!-- 待发货 -->
<template v-if="[SHOP_ORDER_UNSLIVER].includes(data.status)">
<view class="flex-center fs-24 app-fc-main cancel-order-btn" @click.stop="$emit('cancelOrder', data)">
取消订单
</view>
<view class="order-btns-right">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服
</view> -->
<!-- <view
class="flex-center fs-24 app-fc-main status-btn"
@click.stop="$emit('afterSale', data)"
>
申请售后
</view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm"
@click.stop="$emit('remindSilver', data)">
提醒发货
</view>
</view>
</template>
<!-- 待收货 -->
<template v-if="[SHOP_ORDER_UNRECEIVE].includes(data.status)">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服
</view> -->
<view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('afterSale', data)">
申请售后
</view>
<!-- 随车订单不显示物流 -->
<!-- <view v-if="data.pay_type !== pay_type_BYCAR" class="flex-center fs-24 app-fc-main status-btn"
@click.stop="$emit('checkSliver', data)">
查看物流
</view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm"
@click.stop="$emit('confirmSliver', data)">
确认收货
</view>
</template>
<!-- 已签收未评价 -->
<template v-if="[SHOP_ORDER_UNREMARK].includes(data.status)">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服
</view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm" @click.stop="$emit('remark', data)">
立即评价
</view>
</template>
<!-- 已完成 -->
<template v-if="[SHOP_ORDER_DONE].includes(data.status)">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服
</view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm "
style="visibility: hidden;"
>
</view>
</template>
<template v-if="[SHOP_ORDER_DONE].includes(data.status)">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服
</view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm"
@click.stop="$emit('remarkDetails', data)">
查看评价
</view>
</template>
</view>
</view>
</template>
<script>
import PopUpModal from "@/components/PopUpModal.vue";
import GoodInfo from "./GoodInfo.vue";
import {
SHOP_ORDER_STATUS,
SHOP_ORDER_UNPAY,
SHOP_ORDER_UNSLIVER,
SHOP_ORDER_UNRECEIVE,
SHOP_ORDER_DONE,
SHOP_ORDER_CANCEL,
SHOP_ORDER_AFTERSALE,
SHOP_ORDER_AFTERSALE_DONE,
SHOP_ORDER_AFTERSALE_REJECT,
SHOP_ORDER_AFTERSALE_STATUS,
SHOP_ORDER_UNREMARK,
pay_type_ADDRESS,
pay_type_BYCAR,
pay_type_BYPET
} from "@/constants/app.business";
export default {
props: {
data: {
type: Object,
default: () => {},
},
},
data() {
return {
pay_type_ADDRESS,
pay_type_BYCAR,
pay_type_BYPET,
SHOP_ORDER_STATUS,
SHOP_ORDER_UNPAY,
SHOP_ORDER_UNSLIVER,
SHOP_ORDER_UNRECEIVE,
SHOP_ORDER_DONE,
SHOP_ORDER_CANCEL,
SHOP_ORDER_AFTERSALE,
SHOP_ORDER_AFTERSALE_DONE,
SHOP_ORDER_AFTERSALE_REJECT,
SHOP_ORDER_UNREMARK,
showCancelModal: false,
countDownTime: 0,
countDownTimer: null,
};
},
components: {
GoodInfo,
PopUpModal,
},
options: {
styleIsolation: "shared",
},
computed: {
orderStatus() {
return SHOP_ORDER_STATUS[this.data.status] || "";
},
refundOrderStatus() {
return SHOP_ORDER_AFTERSALE_STATUS[this.data.tui_status] || "";
},
showStatusBtn() {
return (
([
SHOP_ORDER_UNPAY,
SHOP_ORDER_UNSLIVER,
SHOP_ORDER_UNRECEIVE,
SHOP_ORDER_DONE,
SHOP_ORDER_UNREMARK,
].includes(this.data.status) &&
!this.data.tui_status) || [SHOP_ORDER_AFTERSALE_REJECT].includes(this.data.tui_status)
);
},
},
watch: {
showCancelModal(val) {
this.$emit("disableScroll", val);
},
'data.daojishi'(newVal) {
if (this.data.status === this.SHOP_ORDER_UNPAY && newVal) {
this.countDownTime = newVal;
this.startCountDown();
}
},
'data.status'(newVal) {
if (newVal === this.SHOP_ORDER_UNPAY && this.data.daojishi) {
this.countDownTime = this.data.daojishi;
this.startCountDown();
} else {
this.stopCountDown();
}
},
},
mounted() {
// console.log(this.data,'--=')
if (this.data.status === SHOP_ORDER_UNPAY && this.data.daojishi) {
this.countDownTime = this.data.daojishi;
this.startCountDown();
}
},
beforeDestroy() {
this.stopCountDown();
},
methods: {
startCountDown() {
this.stopCountDown();
if (this.countDownTime > 0) {
this.countDownTimer = setInterval(() => {
if (this.countDownTime > 0) {
this.countDownTime--;
} else {
this.stopCountDown();
// 倒计时结束,可以触发刷新
this.$emit('countdownEnd', this.data);
}
}, 1000);
}
},
stopCountDown() {
if (this.countDownTimer) {
clearInterval(this.countDownTimer);
this.countDownTimer = null;
}
},
formatCountdown(seconds) {
const hour = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds - hour * 3600) / 60);
return `${hour}小时${minutes}分钟`;
},
orderCancel() {
this.showCancelModal = false;
},
cancel() {
this.showCancelModal = false;
},
jumpToDetails() {
this.$emit('jumpToDetails', this.data)
},
},
};
</script>
<style lang="scss" scoped>
.order-item {
width: calc(100vw - 40rpx);
background: #fff;
margin: 20rpx;
border-radius: 30rpx;
box-sizing: border-box;
padding: 20rpx;
margin-bottom: 0;
.order-title {
padding-bottom: 20rpx;
border-bottom: 1rpx solid #ececec;
.order-btn {
width: 104rpx;
height: 48rpx;
border-radius: 48rpx;
&.confirm {
background: #fef6ff;
}
&.cancel {
background: #f7f7f7;
color: #afa5ae;
}
}
.order-status-banner {
display: flex;
align-items: center;
border-radius: 48rpx;
overflow: hidden;
.status-banner-left {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background: #FF19A0;
padding: 8rpx;
flex-shrink: 0;
border-radius: 10px 0px 10px 10px;
.status-text {
font-size: 20rpx;
color: #FFFFFF;
}
}
.status-banner-right {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background: #FFF0F8;
padding: 8rpx;
flex-shrink: 0;
.countdown-text {
font-size: 20rpx;
color: #FF19A0;
}
}
}
}
.order-btns {
padding-top: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
.cancel-order-btn {
font-size: 24rpx;
color: #9B939A;
padding: 16rpx 0;
}
.order-btns-right {
display: flex;
align-items: center;
}
.status-btn {
padding: 16rpx 20rpx;
border-radius: 64rpx;
border: 1px solid #9b939a;
margin-left: 20rpx;
&.confirm {
color: $app_color_main;
border: 1px solid $app_color_main;
}
}
}
::v-deep {
.good-info-multi {
padding-top: 20rpx;
}
}
}
</style>

View File

@ -0,0 +1,261 @@
<template>
<select-modal title="物流信息" @close="$emit('close')">
<view class="sliver-info-container">
<view class="sliver-info-content">
<view v-if="isLoading" class="flex-center">
<uni-load-more status="loading" />
</view>
<template v-else>
<view class="flex-row-start sliver-info-title">
<!-- <image class="sliver-icon" :src="sliverInfo.logo" /> -->
<text class="fs-24 app-fc-main sliver-text">
{{ sliverInfo.name }} {{ orderData.kuaidi_no }}
</text>
<text class="fs-24 app-fc-main" @click="copySliverId">复制</text>
</view>
<view
class="flex-row-start sliver-item"
:class="{
first: index === sliverList.length - 1,
last: index === 0,
}"
v-for="(item, index) in sliverList"
:key="index"
>
<view class="flex-center icon-wrapper">
<view class="icon-circle"></view>
<view v-if="index === 0" class="icon-circle-bg"></view>
<view
v-if="index < sliverList.length - 1"
class="flex-1 icon-line"
></view>
</view>
<view class="flex-1 flex-column-start item-info">
<text>
<!-- index === 0 || index > 0 && item.status !== sliverList[index - 1].status -->
<text class="fs-32 app-fc-main app-font-bold info-status">
{{ item.status }}
</text>
<text class="fs-24 info-time ali-puhui-regular">{{ item.time }}</text>
</text>
<text class="fs-24 app-fc-main info-desc">{{
item.context
}}</text>
</view>
</view>
</template>
</view>
<view
class="flex-center app-fc-white fs-30 confirm-btn"
@click.stop="$emit('close')"
>
确定
</view>
</view>
</select-modal>
</template>
<script>
import selectModal from "@/components/select-modal.vue";
import { getSliverInfo } from "@/api/shop";
import moment from "moment";
export default {
props: {
orderId: {
type: String | Number,
default: "",
},
orderInfo: {
type: Object,
default: () => {},
},
},
components: { selectModal },
data() {
return {
isLoading: true,
sliverList: [],
sliverInfo: {},
orderData: {},
};
},
computed: {
defaultSliver() {
return [
{
status: "已发货",
time: moment(this.orderInfo.fahuo_time * 1000).format(
"YYYY-MM-DD HH:mm:ss"
),
context: "包裹正在等待揽收",
},
{
status: "已下单",
time: moment(this.orderInfo.add_time * 1000).format(
"YYYY-MM-DD HH:mm:ss"
),
context: "商品已经下单",
},
];
},
},
mounted() {
this.getSliverInfo();
},
methods: {
formatStatus(status) {
switch (status) {
case "揽收":
return "已揽件";
case "在途":
return "运输中";
case "派件":
return "派送中";
case "签收":
return "已签收";
case "已发货":
return "已发货";
case "已下单":
return "已下单";
default:
return "";
}
},
getSliverInfo() {
getSliverInfo(this.orderId)
.then((res) => {
this.sliverList = [
...(res?.info?.wuliu_list || []),
...this.defaultSliver,
].map((v) => ({
...v,
status: this.formatStatus(v.status),
}));
this.orderData = res?.info?.order_info || {};
this.sliverInfo = res?.info?.express_info || {};
})
.finally(() => {
this.isLoading = false;
});
},
copySliverId() {
uni.setClipboardData({
data: this.orderData.kuaidi_no,
success: function () {
uni.showToast({
title: "复制成功",
icon: "success",
});
},
});
},
},
};
</script>
<style lang="scss" scoped>
.sliver-info-container {
padding: 30rpx 56rpx 0 48rpx;
box-sizing: border-box;
.sliver-info-content {
height: 55vh;
overflow-y: auto;
border-bottom: 1px solid #f5f5f5;
.sliver-info-title {
margin-bottom: 50rpx;
.sliver-icon {
width: 56rpx;
height: 56rpx;
border-radius: 56rpx;
background: gray;
}
.sliver-text {
margin-right: 14rpx;
flex: 1;
}
}
.sliver-item {
align-items: stretch;
padding-left: 8rpx;
&.last {
.icon-wrapper .icon-circle {
background: $app_color_main;
}
}
&.first {
.item-info {
min-height: unset;
}
.icon-wrapper {
justify-content: flex-start;
}
}
.icon-wrapper {
position: relative;
.icon-circle {
width: 24rpx;
height: 24rpx;
background: #d4ced1;
border-radius: 24rpx;
}
.icon-line {
width: 2rpx;
background: #dddddd;
}
.icon-circle-bg {
position: absolute;
top: -8rpx;
left: -8rpx;
width: 40rpx;
height: 40rpx;
border-radius: 40rpx;
background: rgba(254, 1, 155, 0.3);
}
}
.item-info {
min-height: 190rpx;
padding: 0rpx 0 30rpx 24rpx;
box-sizing: border-box;
justify-content: flex-start;
margin-top: -10rpx;
.info-time {
color: #afa5ae;
// font-weight: 400;
}
.info-status {
padding-right: 20rpx;
}
.info-desc {
margin-top: 12rpx;
line-height: 1.6;
margin-left: -4rpx;
}
}
}
}
.confirm-btn {
width: 630rpx;
height: 92rpx;
background: #fe019b;
border-radius: 92rpx;
margin: 20rpx auto 10rpx;
}
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,698 @@
<template>
<view class="flex-column-start order-list-container">
<tabs-list :list="tabsList" :isScroll="true" :currentIndex="curTabIndex" @change="onTabChange" />
<view class="order-list-content">
<scroll-view class="list-template-wrapper" :scroll-y="!disableScroll" :refresher-enabled="true"
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
<view class="list-content">
<view v-for="(data, index) in list" :key="data.order_id" :class="{ left: index % 2 === 0 }"
class="flex-column-start news-item">
<order-item :data="data" @disableScroll="disableScrollAction" @cancelOrder="cancelOrderAction"
@concactService="jumpToWeChat" @confirmSliver="confirmSliver" @remindSilver="remindSliver" @pay="pay"
@afterSale="afterSale" @checkSliver="checkSliver" @remark="remark" @remarkDetails="remarkDetails"
@jumpToDetails="jumpToDetails" />
</view>
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
:status="isLoading ? 'loading' : 'nomore'"></uni-load-more>
</view>
</scroll-view>
<!-- <list-page-temp
:getDataPromise="getShopOrderList"
:requestData="{ status: tabsList[curTabIndex].id }"
:reloadFlag="reloadFlag"
:disableScroll="disableScroll"
>
<template v-slot:item="{ data }">
<order-item
:data="data"
@disableScroll="disableScrollAction"
@cancelOrder="cancelOrderAction"
@concactService="showConcact = true"
@confirmSliver="confirmSliver"
@remindSilver="remindSliver"
@pay="pay"
@afterSale="afterSale"
@checkSliver="checkSliver"
@remark="remark"
@remarkDetails="remarkDetails"
/>
</template>
</list-page-temp> -->
<view v-if="elasticLayer" class="elastic-layer" />
</view>
<!-- 右侧浮动联系客服按钮 -->
<view class="contact-float-btn" :style="{ right: contactBtnRight + 'rpx', bottom: contactBtnBottom + 'rpx' }"
@touchstart="onContactBtnTouchStart" @touchmove="onContactBtnTouchMove" @touchend="onContactBtnTouchEnd"
@click="handleContactBtnClick">
<image class="contact-icon" :src="`${imgPrefix}supportStaff.png`" />
<view class="contact-btn fs-20">
联系客服
</view>
</view>
<view class="additional-bottom" v-if="additionalBom">
<view class="recharge-method">
<view class="method-header">
<text class="payment-title">支付方式</text>
<image src="@/static/images/close.png" mode="aspectFit" class="close-icon"
@click="(additionalBom = false), (elasticLayer = false)" />
</view>
<view class="wechat" @click.stop="selectOption1('1')">
<view class="select">
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
<text class="x">微信</text>
</view>
<image v-if="selected1" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
</view>
<view class="wechat" @click.stop="selectOption2('2')">
<view class="select">
<image class="w" src="@/static/images/wallet.png" mode="widthFix" />
<text class="x">钱包支付</text>
</view>
<image v-if="selected3" class="not-selected" src="@/static/images/w.png" mode="widthFix" />
<image v-if="selected4" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
</view>
</view>
<view class="payment" @click="rechargeNow">
<text class="Z"> 支付 </text>
</view>
</view>
<pop-up-modal v-if="showCancelModal" content="确定要取消该订单吗?" @confirm="orderCancel"
@cancel="showCancelModal = false" />
<contact-modal v-if="showConcact" @close="showConcact = false" />
<pop-up-modal v-if="showSliverModal" content="是否要确认收货?" @confirm="confirmReceiveOrder"
@cancel="showSliverModal = false" />
<success-modal v-if="showRemindSliver" title="提醒成功" message="已通知商家,请耐心等待商家发货" @close="showRemindSliver = false"
@ok="showRemindSliver = false" />
<sliver-info v-if="showSliverRouteModal" :orderId="orderInfo.order_id" :orderInfo="orderInfo"
@close="showSliverRouteModal = false" />
</view>
</template>
<script>
import TabsList from "@/components/TabsList.vue";
import ListPageTemp from "@/components/ListPageTemp.vue";
import OrderItem from "./components/OrderItem.vue";
import PopUpModal from "@/components/PopUpModal.vue";
import SuccessModal from "@/components/SuccessModal.vue";
import ContactModal from "@/components/ContactModal.vue";
import SliverInfo from "./components/SliverInfo.vue";
import { walletTransaction, cancelPetOrderRefund, cancelPetOrderMall } from "../../../api/login";
import {
payOrder,
remindOrder,
getShopOrderList,
cancelOrder,
confirmOrder,
} from "@/api/shop";
import {
SHOP_ORDER_UNPAY,
SHOP_ORDER_UNSLIVER,
SHOP_ORDER_UNRECEIVE,
SHOP_ORDER_DONE,
SHOP_ORDER_CANCEL,
SHOP_ORDER_UNREMARK,
} from "@/constants/app.business";
import { jumpToWeChat, imgPrefix } from "@/utils/common";
export default {
components: {
TabsList,
ListPageTemp,
OrderItem,
PopUpModal,
ContactModal,
SuccessModal,
SliverInfo,
},
data() {
return {
selected1: true,
selected2: false,
selected3: true,
selected4: false,
recharge: true,
record: false,
WeChat: "",
wallet: "",
curTabIndex: 0,
reloadFlag: 0,
additionalBom: false,
elasticLayer: false,
disableScroll: false,
showCancelModal: false,
showConcact: false,
showRemindSliver: false,
showSliverModal: false,
showSliverRouteModal: false,
orderInfo: {},
isLoading: false,
total: 0,
list: [],
refreshTriggered: false,
p: 1,
num: 10,
imgPrefix,
// 联系客服按钮拖拽相关
contactBtnRight: 0, // 默认右侧距离始终为0
contactBtnBottom: 0, // 默认底部距离,需要计算
isDragging: false, // 是否正在拖拽
touchStartX: 0, // 触摸开始X坐标
touchStartY: 0, // 触摸开始Y坐标
initialRight: 0, // 初始右侧距离
initialBottom: 0, // 初始底部距离
};
},
computed: {
tabsList() {
return [
{
name: "全部",
id: 0,
},
{
name: "待支付",
id: SHOP_ORDER_UNPAY,
},
{
name: "待发货",
id: SHOP_ORDER_UNSLIVER,
},
{
name: "待收货",
id: SHOP_ORDER_UNRECEIVE,
},
{
name: "待评价",
id: SHOP_ORDER_UNREMARK,
},
{
name: "已取消",
id: SHOP_ORDER_CANCEL,
},
];
},
},
options: {
styleIsolation: "shared",
},
onLoad() {
this.reloadData();
// 初始化联系客服按钮位置
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
// 计算底部距离calc(35vh - 130rpx - 48rpx) 转换为rpx
// 35vh = windowHeight * 0.35转换为rpx (750rpx = windowHeight px)
const vh35InRpx = (windowHeight * 0.35 / windowHeight) * 750;
this.contactBtnBottom = vh35InRpx - 130 - 48;
},
methods: {
jumpToWeChat,
getShopOrderList,
getList() {
this.isLoading = true;
getShopOrderList({ status: this.tabsList[this.curTabIndex].id })
.then((res) => {
const list = res?.data || [];
this.list = this.p === 1 ? list : [...this.list, data];
this.total = res?.count || 0;
})
.finally(() => {
this.isLoading = false;
this.refreshTriggered = false;
uni.stopPullDownRefresh();
});
},
reloadData() {
this.page = 1;
this.total = 0;
this.getList();
},
onTabChange(item, index) {
this.curTabIndex = index;
this.reloadData();
},
disableScrollAction(val) {
this.disableScroll = val;
},
onRefresh() {
if (this.refreshTriggered) return;
this.refreshTriggered = true;
this.p = 1;
this.num = 10;
this.total = 0;
this.getList();
},
onLoadMore() {
if (!this.isLoading && this.total > this.list.length) {
this.p++;
this.getList();
}
},
orderCancel() {
uni.showLoading({
icon: "none",
title: "处理中",
mask: true,
});
const data = {
order_id: this.orderInfo.order_id,
// business_type:1
}
cancelPetOrderMall(data).then((res) => {
uni.hideLoading();
this.showCancelModal = false;
this.reloadData();
});
},
// 确认收货
confirmSliver(data) {
this.showSliverModal = true;
this.orderInfo = data;
},
// 确认收货
confirmReceiveOrder() {
uni.showLoading({
icon: "none",
title: "处理中",
mask: true,
});
confirmOrder(this.orderInfo.order_id).then((res) => {
uni.hideLoading();
this.reloadData();
this.showSliverModal = false;
});
},
selectOption1(v) {
this.WeChat = v;
this.wallet = "";
console.log(this.WeChat);
this.selected1 = false;
this.selected2 = true;
this.selected3 = true;
this.selected4 = false;
},
selectOption2(v) {
this.WeChat = "";
this.wallet = v;
console.log(this.wallet);
this.selected1 = true;
this.selected2 = false;
this.selected3 = false;
this.selected4 = true;
},
// 支付
pay(data) {
this.additionalBom = true;
this.elasticLayer = true;
this.dataList = data;
},
async rechargeNow() {
// console.log(this.wallet, "???");
if (!this.WeChat && !this.wallet) {
uni.showToast({
title: "请选择充值方式",
icon: "none",
});
return;
}
if (this.WeChat == 1) {
uni.showLoading({
icon: "none",
title: "支付中",
mask: true,
});
payOrder({
type: 4,
total_fee: Number(this.dataList.actual_price),
order_id: this.dataList.order_id,
order_no: this.dataList.order_no
}).then((res) => {
const payData = res?.data || {};
uni.requestPayment({
provider: "wxpay",
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.paySign,
success: (res) => {
uni.hideLoading();
uni.showToast({
title: "支付成功",
icon: "none",
});
this.additionalBom = false,
this.elasticLayer = false,
this.reloadData();
},
fail: (err) => {
uni.hideLoading();
uni.showToast({
title: err?.msg || "支付失败",
icon: "none",
});
},
});
});
} else if (this.wallet == 2) {
const value = (this.$store.state && this.$store.state.user && this.$store.state.user.userInfo) || {};
// console.log(data, "???");
// console.log(this.selectService,'---')
const data = {
wallet_id: value.wallet_id,
total_fee: this.dataList.actual_price,
type: 4,
order_id: this.dataList.order_id,
order_no: this.dataList.order_no
// business_type: 2,
// wallet_id: value.wallet_id,
// user_id: value.user_id,
// amount: Number(this.dataList.pay_price),
// business_id: this.dataList.order_id,
};
walletTransaction(data).then((res) => {
if (res.code == 0) {
uni.showToast({
title: "支付成功",
icon: "none",
});
this.additionalBom = false,
this.elasticLayer = false,
uni.hideLoading();
this.reloadData();
} else if (res.code == 100) {
uni.showToast({
title: res.message,
icon: "none",
});
}
});
}
// if (!this.giftLabel) {
// uni.showToast({
// title: "请选择充值金额",
// icon: "none",
// });
// return;
// }
},
// 提醒发货
remindSliver(data) {
this.showRemindSliver = true;
// remindOrder(data.order_id).then(() => {
// this.showRemindSliver = true;
// });
},
// 申请售后
afterSale() {
// 如果正在拖拽,不触发点击事件
if (this.isDragging) {
return;
}
this.jumpToWeChat();
},
// afterSale(data) {
// uni.navigateTo({
// url: `/pages/client/order/refund?orderId=${data.order_id}`,
// events: {
// refreshData: () => this.reloadData(),
// },
// });
// },
// 订单详情
jumpToDetails(data) {
uni.navigateTo({
url: `/pages/client/order/details?id=${data?.order_id}`,
events: {
refreshData: () => this.reloadData(),
},
});
},
// 查看物流
checkSliver(data) {
this.showSliverRouteModal = true;
this.orderInfo = data;
},
// 立即评价
remark(data) {
uni.navigateTo({
url: `/pages/client/order/remark?orderId=${data.order_id}`,
events: {
refreshData: () => this.reloadData(),
},
});
},
// 查看评价
remarkDetails(data) {
if (!data.pinglun_id) {
uni.showToast({
title: "暂无评价",
icon: "none",
});
return;
}
uni.navigateTo({
url: `/pages/client/remark/details?remarkId=${data.pinglun_id}`,
});
},
// 取消订单
cancelOrderAction(data) {
this.showCancelModal = true;
this.orderInfo = data;
},
// 联系客服按钮触摸开始
onContactBtnTouchStart(e) {
this.isDragging = false;
const touch = e.touches[0];
this.touchStartX = touch.clientX;
this.touchStartY = touch.clientY;
this.initialRight = this.contactBtnRight;
this.initialBottom = this.contactBtnBottom;
},
// 联系客服按钮触摸移动
onContactBtnTouchMove(e) {
const touch = e.touches[0];
const deltaX = touch.clientX - this.touchStartX;
const deltaY = touch.clientY - this.touchStartY;
// 如果移动距离超过10px认为是拖拽
if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
this.isDragging = true;
}
if (this.isDragging) {
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
// 只处理上下移动将px转换为rpx
const deltaYRpx = (deltaY / windowHeight) * 750;
// 计算新位置只改变bottomright始终保持为0
// 向上移动时deltaY为负但bottom值应该减小
let newBottom = this.initialBottom - deltaYRpx;
// 限制在屏幕范围内
// 按钮高度约120rpx
const btnHeight = 120;
newBottom = Math.max(0, Math.min(newBottom, windowHeight * 2 - btnHeight));
// right始终保持为0
this.contactBtnRight = 0;
this.contactBtnBottom = newBottom;
}
},
// 联系客服按钮触摸结束
onContactBtnTouchEnd(e) {
// 松手后right重置为0
this.contactBtnRight = 0;
this.isDragging = false;
},
// 联系客服按钮点击
handleContactBtnClick() {
// 如果正在拖拽,不触发点击事件
if (this.isDragging) {
return;
}
this.jumpToWeChat();
},
},
};
</script>
<style lang="scss" scoped>
.order-list-container {
height: 100%;
align-items: stretch;
justify-content: flex-start;
.elastic-layer {
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
z-index: 999;
}
.additional-bottom {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
z-index: 9999;
background: #fff;
border-radius: 30rpx 30rpx 0 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.recharge-method {
display: flex;
flex-direction: column;
background: #fff;
padding: 40rpx 0 30rpx;
border-radius: 30rpx;
width: 100%;
box-sizing: border-box;
.method-header {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30rpx;
position: relative;
padding: 0 30rpx;
box-sizing: border-box;
}
.payment-title {
font-family: PingFangSC;
font-size: 32rpx;
font-weight: 500;
color: #272427;
text-align: center;
flex: 1;
}
.close-icon {
width: 50rpx;
height: 50rpx;
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
}
.wechat {
width: 100%;
height: 116rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 60rpx;
box-sizing: border-box;
.select {
display: flex;
align-items: center;
.w {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
.x {
font-size: 32rpx;
color: #272427;
}
}
.not-selected {
width: 40rpx;
height: 40rpx;
}
}
}
.payment {
position: relative;
border-radius: 100rpx;
margin: auto;
width: calc(100% - 60rpx);
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #FF19A0;
background: #FF19A0;
box-sizing: border-box;
.Z {
font-family: PingFang SC;
font-size: 32rpx;
font-weight: normal;
color: #fff;
}
}
}
.order-list-content {
flex: 1;
overflow: hidden;
background: #f7f8fa;
.list-template-wrapper {
height: 100%;
width: 100%;
.list-content {
width: 100%;
}
.news-item {
width: 100%;
}
}
}
.contact-float-btn {
position: fixed;
text-align: center;
z-index: 100;
.contact-icon {
width: 66rpx;
height: 66rpx;
margin: auto;
}
.contact-btn {
color: #FFFFFF;
background-color: #FF19A0;
border-radius: 257px;
padding: 6rpx 8rpx;
transform: translateY(-8px);
}
}
}
</style>

View File

@ -0,0 +1,272 @@
<template>
<view class="flex-column-start refund-edit-container">
<view class="refund-edit-content">
<view class="refund-cell">
<form-cell
:title="'售后事项'"
type="checkPicker"
placeholderText="请选择"
:value="refundInfo.reason"
:pickerData="reasonList"
:noBorder="true"
valueKey="name"
labelKey="name"
@onChange="(value) => onChange(value, 'reason')"
/>
</view>
<view class="refund-content-cell">
<text class="fs-32 app-fc-main">申请售后原因</text>
<view class="flex-column-start remark-content">
<textarea
class="remark-input"
:v-model="refundInfo.content"
:maxlength="maxLen"
placeholder="在此输入内容......"
placeholder-style="color:#ACACAC; font-size: 28rpx;"
:auto-height="true"
@input="onRemarkChange"
/>
<view class="flex-row-end remark-count">
{{ refundInfo.content.length }}/{{ maxLen }}
</view>
</view>
</view>
<view class="refund-content-cell">
<text class="fs-32 app-fc-main">图片</text>
<uni-file-picker
limit="1"
:title="' '"
:imageStyles="{
width: '220rpx',
height: '220rpx',
'border-radius': '20rpx',
}"
:del-icon="true"
:auto-upload="false"
@select="onImgChange"
@delete="onImgDelete"
></uni-file-picker>
</view>
</view>
<view class="flex-center refund-edit-footer">
<view
class="flex-center app-fc-white fs-30 app-fc-bold refund-edit-btn"
@click="submit"
>
提交
</view>
</view>
</view>
</template>
<script>
import FormCell from "@/components/FormCell";
import appConfig from "@/constants/app.config";
import { createAfterSale } from "../../../api/shop";
export default {
components: {
FormCell,
},
data() {
return {
orderId: "",
refundInfo: {
reason: "",
content: "",
},
maxLen: 300,
reasonList: [
{
value: "",
name: "多拍/错拍/不想要了",
},
{
value: "",
name: "商家发错货",
},
{
value: "",
name: "未按约定时间发货",
},
{
value: "",
name: "填错地址/不方便收货",
},
],
imgList: [],
};
},
onLoad(options) {
this.orderId = options.orderId;
},
beforeDestroy() {
const eventChannel = this.getOpenerEventChannel();
eventChannel && eventChannel.emit('refreshData');
},
methods: {
onChange(val, key) {
this.refundInfo[key] = val;
},
onRemarkChange(e) {
this.refundInfo.content = e.mp.detail.value.slice(0, this.maxLen);
},
onImgChange(e) {
this.uploadFile(e?.tempFilePaths?.[0]);
},
onImgDelete(e) {
this.imgList.splice(e.index, 1);
},
uploadFile(url) {
uni.showLoading({
title: "图片上传中...",
icon: "none",
mask: true
})
uni.uploadFile({
url: appConfig.apiBaseUrl + "/app/upload/upload_file",
filePath: url,
name: "image",
formData: {
file_name: "app",
pic_name: +new Date(),
},
header: { token: uni.getStorageSync("token") },
success: (res) => {
const data = JSON.parse(res.data);
const { code, msg, info } = data;
const errCode = String(code);
uni.hideLoading()
if (errCode === "0" || errCode === "200") {
this.imgList.push({
fullUrl: info?.[0]?.urlname,
url: info?.[0]?.filename,
});
this.$forceUpdate();
} else {
uni.showToast({
title: msg || "上传失败",
icon: "none",
});
}
},
fail: () => {
uni.hideLoading()
uni.showToast({
title: "上传失败",
icon: "none",
});
}
});
},
submit() {
if (!this.refundInfo.reason || !this.refundInfo.reason.trim()) {
uni.showToast({
title: "请选择售后事项",
icon: "none",
});
return;
}
if (!this.refundInfo.content || !this.refundInfo.content.trim()) {
uni.showToast({
title: "请选择售后原因",
icon: "none",
});
return;
}
uni.showLoading({
title: "处理中",
icon: "none",
mask: true,
});
createAfterSale({
order_id: this.orderId,
tui_yuanyin: this.refundInfo.reason,
tui_desc: this.refundInfo.content,
tui_pic: this.imgList.map((v) => v.url).join(","),
}).then(() => {
uni.hideLoading();
uni.showToast({
title: "提交成功",
icon: "none",
});
uni.redirectTo({
url: "/pages/client/order/after-sale",
})
});
},
},
};
</script>
<style lang="scss" scoped>
.refund-edit-container {
align-items: stretch;
height: 100vh;
.refund-edit-content {
flex: 1;
padding: 20rpx 30rpx;
box-sizing: border-box;
.refund-cell {
width: 100%;
height: 116rpx;
box-sizing: border-box;
background: #fff;
margin-bottom: 20rpx;
padding: 0 40rpx;
border-radius: 30rpx;
}
.refund-content-cell {
width: 100%;
box-sizing: border-box;
background: #fff;
margin-bottom: 20rpx;
padding: 40rpx 28rpx;
border-radius: 30rpx;
}
.remark-content {
padding: 30rpx;
background: #f8f8f8;
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin: 28rpx 0;
min-height: 260rpx;
align-items: stretch;
.remark-input {
flex: 1;
}
.remark-count {
color: #acacac;
margin-top: 28rpx;
}
}
::v-deep {
.form-label {
color: $app_fc_main;
}
}
}
.refund-edit-footer {
padding: 20rpx 60rpx 10rpx;
background: #fff;
margin-bottom: 10rpx;
margin-bottom: constant(safe-area-inset-bottom);
margin-bottom: env(safe-area-inset-bottom);
.refund-edit-btn {
width: 100%;
height: 92rpx;
background: $app_color_main;
border-radius: 92rpx;
}
}
}
</style>

View File

@ -0,0 +1,417 @@
<template>
<view class="remark-page">
<scroll-view class="remark-scroll" scroll-y>
<!-- 文字评价 -->
<view class="section text-review-section">
<view class="section-title">文字评价</view>
<view class="textarea-wrap">
<textarea class="remark-textarea" :value="remark" :maxlength="remarkMaxLen" placeholder="请输入评价"
placeholder-class="textarea-placeholder" :auto-height="true" @input="onRemarkChange" />
<view class="char-count">{{ remark.length }}/{{ remarkMaxLen }}</view>
</view>
</view>
<!-- 上传图片 -->
<view class="section upload-section">
<view class="section-title">上传图片 <text class="tips">(最多可上传3张)</text></view>
<view class="image-upload-grid">
<view v-for="(item, index) in imgList" :key="index" class="image-item">
<image class="uploaded-image" :src="item.fullUrl || item.url" mode="aspectFill" @click="previewImage(item.fullUrl || item.url, imgList.map(i => i.fullUrl || i.url))" />
<view class="delete-btn" @click="deleteImage(index)">
<text class="delete-icon">×</text>
</view>
</view>
<view v-if="imgList.length < 3" class="image-item upload-placeholder" @click="chooseImage">
<text class="add-icon">+</text>
</view>
</view>
</view>
<!-- 星级评分 -->
<view class="section rating-section">
<view class="rating-row">
<text class="rating-label">描述相符</text>
<view class="star-row">
<image v-for="v in 5" :key="v" class="star-icon"
:src="starDesc >= v ? require('@/static/images/star.png') : require('@/static/images/star_dark.png')"
@click="starDesc = v" />
</view>
</view>
<view class="rating-row" style="margin-top: 40rpx;">
<text class="rating-label">服务态度</text>
<view class="star-row">
<image v-for="v in 5" :key="v" class="star-icon"
:src="starService >= v ? require('@/static/images/star.png') : require('@/static/images/star_dark.png')"
@click="starService = v" />
</view>
</view>
</view>
<!-- 洗护对比图有数据时展示 -->
<!-- <view v-if="showWashImg" class="section wash-compare-section" @click="showModal = true">
<view class="wash-compare-row">
<text class="wash-compare-label">洗护对比图</text>
<image class="arrow-icon" src="@/static/images/arrow_right_black.png" mode="aspectFit" />
</view>
</view> -->
<view class="bottom-placeholder" />
</scroll-view>
<view class="submit-footer">
<view class="submit-btn" @click="submit">提交</view>
</view>
<compare-modal v-if="showModal" :before-imgs="washBeforeImgs" :after-imgs="washAfterImgs" :select-imgs="selectImgs"
:is-can-remark="true" @close="showModal = false" @add="selectImgsChange" />
</view>
</template>
<script>
import { createRemark } from "@/api/shop";
import CompareModal from "@/components/CompareModal.vue";
import { ORDER_TYPE_GOODS } from "@/constants/app.business";
import { uploadImageToOSS_PUT } from "@/utils/oss";
export default {
components: { CompareModal },
data() {
return {
orderId: "",
remark: "",
remarkMaxLen: 300,
starDesc: 5,
starService: 5,
imgList: [],
washBeforeImgs: [],
washAfterImgs: [],
selectImgs: [],
showModal: false,
orderType: ORDER_TYPE_GOODS,
};
},
computed: {
showWashImg() {
return [...this.washBeforeImgs, ...this.washAfterImgs].length > 0;
},
},
onLoad(options) {
console.log(options, '--')
this.orderId = options.orderId || "";
if (options.orderType) this.orderType = options.orderType;
const eventChannel = this.getOpenerEventChannel();
if (eventChannel && typeof eventChannel.on === "function") {
eventChannel.on("remarkInfo", (data) => {
this.washAfterImgs = data.afterImages || [];
this.washBeforeImgs = data.beforeImages || [];
this.selectImgs = data.remarkImages || [];
});
}
},
beforeDestroy() {
const ec = this.getOpenerEventChannel();
if (ec && typeof ec.emit === "function") ec.emit("refreshData");
},
methods: {
selectImgsChange(imgs) {
this.selectImgs = imgs;
this.showModal = false;
},
onRemarkChange(e) {
const val = (e && e.detail && e.detail.value) || (e && e.mp && e.mp.detail && e.mp.detail.value) || "";
this.remark = String(val).slice(0, this.remarkMaxLen);
},
chooseImage() {
const remaining = 3 - this.imgList.length;
if (remaining <= 0) {
uni.showToast({ title: "最多只能上传3张图片", icon: "none" });
return;
}
uni.chooseImage({
count: remaining,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePaths = res.tempFilePaths || [];
if (tempFilePaths.length === 0) return;
this.uploadImages(tempFilePaths);
},
fail: (err) => {
console.error('选择图片失败', err);
}
});
},
async uploadImages(filePaths) {
uni.showLoading({ title: "图片上传中...", icon: "none", mask: true });
try {
const uploadTasks = filePaths.map(filePath => this.uploadImageToOSS(filePath));
await Promise.all(uploadTasks);
uni.hideLoading();
uni.showToast({ title: "上传成功", icon: "success" });
} catch (error) {
uni.hideLoading();
uni.showToast({ title: error?.message || "上传失败", icon: "none" });
}
},
async uploadImageToOSS(filePath) {
try {
const { url, objectKey } = await uploadImageToOSS_PUT(filePath);
this.imgList.push({
url: objectKey,
fullUrl: url,
});
this.$forceUpdate();
} catch (error) {
console.error('图片上传失败:', error);
throw error;
}
},
deleteImage(index) {
this.imgList.splice(index, 1);
this.$forceUpdate();
},
previewImage(current, urls) {
uni.previewImage({
current: current,
urls: urls || [current]
});
},
submit() {
if (!this.remark || !this.remark.trim()) {
uni.showToast({ title: "请输入评价内容", icon: "none" });
return;
}
const pics = [...this.selectImgs, ...this.imgList].map((v) => (v.url || v.objectKey || v.filename || v.fullUrl)).filter(Boolean);
if (!pics.length) {
uni.showToast({ title: "请上传图片", icon: "none" });
return;
}
uni.showLoading({ title: "提交中...", icon: "none" });
createRemark({
order_id: +this.orderId,
type: this.orderType,
imgs: pics,
content: this.remark.trim(),
description: this.starDesc || 0,
attitude: this.starService || 0,
})
.then(() => {
uni.hideLoading();
uni.$emit("createRemarkSuccess");
uni.navigateBack();
})
.catch((err) => {
uni.hideLoading();
uni.showToast({ title: err?.message || err || "提交失败", icon: "none" });
});
},
},
};
</script>
<style lang="scss" scoped>
.remark-page {
min-height: 100vh;
background: #f5f5f5;
box-sizing: border-box;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.remark-scroll {
height: 100vh;
box-sizing: border-box;
}
.section {
margin: 20rpx;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
box-sizing: border-box;
padding: 20rpx;
}
.section-title {
font-size: 24rpx;
font-weight: 500;
color: #272427;
margin-bottom: 20rpx;
.tips {
font-size: 20rpx;
color: #ACACAC;
}
}
/* 文字评价 */
.text-review-section {
.textarea-wrap {
position: relative;
padding: 20rpx;
background: #f5f5f5;
border-radius: 16rpx;
min-height: 240rpx;
}
.remark-textarea {
width: 100%;
min-height: 200rpx;
font-size: 28rpx;
color: #272427;
line-height: 1.5;
box-sizing: border-box;
}
.textarea-placeholder {
color: #acacac;
font-size: 28rpx;
}
.char-count {
position: absolute;
right: 32rpx;
bottom: 24rpx;
font-size: 24rpx;
color: #9b939a;
}
}
/* 上传图片 */
.upload-section {
.image-upload-grid {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
}
.image-item {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 16rpx;
overflow: hidden;
flex-shrink: 0;
}
.uploaded-image {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
.delete-btn {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 40rpx;
height: 40rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.delete-icon {
color: #fff;
font-size: 32rpx;
font-weight: bold;
line-height: 1;
}
.upload-placeholder {
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
border: 1rpx dashed #d0d0d0;
}
.add-icon {
font-size: 64rpx;
color: #999;
font-weight: 300;
line-height: 1;
}
}
/* 星级 */
.rating-section {
margin-top: 24rpx;
padding: 20rpx;
.rating-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.rating-label {
font-size: 24rpx;
color: #3D3D3D;
}
.star-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.star-icon {
width: 40rpx;
height: 40rpx;
}
}
/* 洗护对比图 */
.wash-compare-section {
.wash-compare-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
background: #f5f5f5;
margin: 0 24rpx 24rpx;
border-radius: 12rpx;
}
.wash-compare-label {
font-size: 28rpx;
color: #272427;
}
.arrow-icon {
width: 24rpx;
height: 24rpx;
}
}
.bottom-placeholder {
height: 180rpx;
}
.submit-footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
background: #fff;
box-sizing: border-box;
}
.submit-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 32rpx;
font-weight: 500;
color: #fff;
background: #fe019b;
border-radius: 44rpx;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,293 @@
<template>
<view class="wash-compare-page">
<!-- 小哇信息取数组第 0 -->
<view class="content-card wa-header-card" v-if="waHeader">
<view class="wa-header-section">
<image class="wa-avatar" :src="waHeader.avatar" mode="aspectFill" />
<text class="wa-name">{{ waHeader.wa_name || '小哇名字' }}</text>
<text class="order-date">{{ waHeader.updated_at || '' }}</text>
</view>
</view>
<view class="content-card" v-for="(row, rowIndex) in orderList" :key="row.order_id || rowIndex">
<!-- 头部宠物信息 -->
<view class="header-section">
<image class="pet-avatar" :src="row.pet_avatar || defaultAvatar" mode="aspectFill" />
<view class="pet-info">
<text class="pet-name">{{ row.pet_name || '--' }}</text>
<text class="pet-desc">{{ formatPetDesc(row) }}</text>
</view>
</view>
<!-- 洗护前 -->
<view class="image-section">
<text class="section-label">洗护前</text>
<view class="image-grid">
<view v-for="(item, index) in row.beforeImages" :key="index" class="image-item"
@click="previewImage(item.fullUrl || item.url, row.beforeImages.map(img => img.fullUrl || img.url))">
<image class="grid-image" :src="item.fullUrl || item.url" mode="aspectFill" />
</view>
</view>
</view>
<!-- 洗护后 -->
<view class="image-section">
<text class="section-label">洗护后</text>
<view class="image-grid">
<view v-for="(item, index) in row.afterImages" :key="index" class="image-item"
@click="previewImage(item.fullUrl || item.url, row.afterImages.map(img => img.fullUrl || img.url))">
<image class="grid-image" :src="item.fullUrl || item.url" mode="aspectFill" />
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { getServiceImgs } from "@/api/shop";
import { imgPrefix } from "@/utils/common";
import moment from "moment";
export default {
data() {
return {
orderId: "",
orderList: [],
defaultAvatar: `${imgPrefix}record_avator.png`,
};
},
computed: {
waHeader() {
return this.orderList.length > 0 ? this.orderList[0] : null;
},
},
onLoad(options) {
this.orderId = options.orderid || options.order_id || "";
if (this.orderId) {
this.getOrderData();
} else {
uni.showToast({
title: "订单ID不存在",
icon: "none",
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
},
methods: {
async getOrderData() {
uni.showLoading({
title: "加载中...",
icon: "none",
});
try {
const res = await getServiceImgs(+this.orderId);
const raw = res?.data || res?.info;
const list = Array.isArray(raw) ? raw : raw && typeof raw === "object" ? [raw] : [];
this.orderList = list.map((data) => {
let beforeList = [];
if (typeof data.json_before_service === "string" && data.json_before_service.trim()) {
const str = data.json_before_service.trim();
beforeList = str.indexOf(",") > -1
? str.split(",").map((s) => s.trim()).filter((s) => s)
: [str];
}
if (!beforeList.length && (Array.isArray(data.before_imgs) || Array.isArray(data.before))) {
beforeList = data.before_imgs || data.before || [];
}
const beforeImages = beforeList
.map((item) => ({
url: typeof item === "string" ? item : item?.url || item?.objectKey || "",
fullUrl:
typeof item === "string"
? item
: item?.fullUrl || item?.url || item?.objectKey || "",
}))
.filter((item) => item.url || item.fullUrl);
let afterList = [];
if (typeof data.json_after_service === "string" && data.json_after_service.trim()) {
const str = data.json_after_service.trim();
afterList = str.indexOf(",") > -1
? str.split(",").map((s) => s.trim()).filter((s) => s)
: [str];
}
if (!afterList.length && (Array.isArray(data.after_imgs) || Array.isArray(data.after))) {
afterList = data.after_imgs || data.after || [];
}
const afterImages = afterList
.map((item) => ({
url: typeof item === "string" ? item : item?.url || item?.objectKey || "",
fullUrl:
typeof item === "string"
? item
: item?.fullUrl || item?.url || item?.objectKey || "",
}))
.filter((item) => item.url || item.fullUrl);
return {
...data,
beforeImages,
afterImages,
};
});
} catch (error) {
console.error("获取服务图片失败:", error);
uni.showToast({
title: error?.message || "获取服务图片失败",
icon: "none",
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} finally {
uni.hideLoading();
}
},
formatPetDesc(item) {
if (!item) return "";
const parts = [];
if (item.breed_name) parts.push(item.breed_name);
const gender = (item.pet_gender || "").toLowerCase();
parts.push(gender === "male" ? "男生" : gender === "female" ? "女生" : "");
if (item.pet_age != null && item.pet_age !== "") {
const age = Number(item.pet_age);
parts.push(age >= 12 ? age / 12 + "岁" : age + "个月");
}
return parts.filter(Boolean).join("/");
},
previewImage(current, urls) {
uni.previewImage({
current: current,
urls: urls || [current],
});
},
},
};
</script>
<style lang="scss" scoped>
.wash-compare-page {
min-height: 100vh;
background: #f8f8f8;
padding: 24rpx;
box-sizing: border-box;
}
.content-card {
background: #fff;
border-radius: 20rpx;
padding: 20rpx;
margin-bottom: 24rpx;
box-sizing: border-box;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
&:last-child {
margin-bottom: 0;
}
}
.wa-header-card {
.wa-header-section {
display: flex;
align-items: center;
padding-bottom: 0;
.wa-avatar {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
margin-right: 20rpx;
flex-shrink: 0;
background: #f5f5f5;
}
.wa-name {
flex: 1;
font-size: 28rpx;
font-weight: 500;
color: #272427;
}
.order-date {
font-size: 24rpx;
color: #999;
}
}
}
.header-section {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.pet-avatar {
width: 52rpx;
height: 52rpx;
border-radius: 50%;
margin-right: 8rpx;
flex-shrink: 0;
background: #f5f5f5;
}
.pet-info {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8rpx;
}
.pet-name {
font-size: 24rpx;
font-weight: 500;
color: #333333;
line-height: 1.3;
}
.pet-desc {
font-size: 20rpx;
color: #666666;
line-height: 1.4;
}
}
.image-section {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.section-label {
display: block;
font-size: 24rpx;
color: #726E71;
margin-bottom: 8rpx;
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, 160rpx);
gap: 8rpx;
}
.image-item {
width: 160rpx;
height: 160rpx;
border-radius: 16rpx;
overflow: hidden;
background: #f5f5f5;
}
.grid-image {
width: 160rpx;
height: 160rpx;
border-radius: 16rpx;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,368 @@
<template>
<view class="home-service-order-item" @click="goToOrderDetail">
<view class="order-header">
<text class="order-number">订单编号: {{ orderInfo.order_no || orderInfo.order_id || '--' }}</text>
<view class="status-btn" :class="statusBtnClass">
<text class="status-text">{{ orderStatus }}</text>
</view>
</view>
<view class="line-view"></view>
<view class="order-content">
<!-- 宠物信息 -->
<view class="info-row">
<text class="info-label">{{ petsCountLabel }}</text>
<text class="info-value">{{ petsNames }}</text>
</view>
<!-- 服务时长 -->
<view class="info-row" >
<text class="info-label">{{ serviceDurationTypeLabel }}</text>
<text class="info-value">{{ serviceDurationLabel }}</text>
</view>
<!-- 服务地址 -->
<view class="info-row" >
<text class="info-label">服务地址</text>
<text class="info-value">{{ serviceAddress }}</text>
</view>
</view>
<!-- 底部按钮 -->
<view class="order-footer" v-if="showFooterButtons" @click.stop>
<!-- 未支付状态显示取消预约和立即支付 -->
<template v-if="isUnpay">
<view class="footer-btn cancel-btn" @click.stop="cancelOrder">
<text class="footer-btn-text">取消预约</text>
</view>
<view class="footer-btn pay-btn" @click.stop="payNow">
<text class="footer-btn-text">立即支付</text>
</view>
</template>
<!-- 已预约状态显示取消预约 -->
<view v-else-if="isReserved" class="footer-btn cancel-btn" @click.stop="cancelOrder">
<text class="footer-btn-text">取消预约</text>
</view>
<!-- 服务中状态显示查看服务图片 -->
<view v-else-if="isService" class="footer-btn service-btn" @click.stop="viewServiceImages">
<text class="footer-btn-text">查看服务图片</text>
</view>
<!-- 已取消或已完成状态显示再次预约 -->
<view v-else-if="isCancelled || isCompleted" class="book-again-btn" @click.stop="bookAgain">
<text class="book-again-text">再次预约</text>
</view>
</view>
</view>
</template>
<script>
import { orderStatusList, ORDER_STATUS_CANCELED, ORDER_STATUS_COMPLETED, ORDER_STATUS_UNPAY, ORDER_STATUS_RESERVED, ORDER_STATUS_SERVICE } from "@/pageHome/constants/home";
import { jumpToWeChat } from "@/utils/common";
export default {
name: 'HomeServiceOrderItem',
props: {
orderInfo: {
type: Object,
default: null
}
},
computed: {
orderStatus() {
// 根据订单状态返回对应的文本
const statusItem = orderStatusList.find(
(item) => `${item.value}` === `${this.orderInfo?.status}`
);
return statusItem?.label || '--';
},
isUnpay() {
return this.orderInfo?.status === ORDER_STATUS_UNPAY;
},
isReserved() {
return this.orderInfo?.status === ORDER_STATUS_RESERVED;
},
isService() {
return this.orderInfo?.status === ORDER_STATUS_SERVICE;
},
isCancelled() {
return this.orderInfo?.status === ORDER_STATUS_CANCELED;
},
isCompleted() {
return this.orderInfo?.status === ORDER_STATUS_COMPLETED || this.orderInfo?.status === 5;
},
showFooterButtons() {
// 未支付、已预约、服务中、已完成或已取消状态显示按钮
return this.isUnpay || this.isReserved || this.isService || this.isCancelled || this.isCompleted;
},
statusBtnClass() {
// 根据订单状态返回不同的样式类
if (this.orderInfo?.status === ORDER_STATUS_UNPAY) {
return 'status-unpay';
} else if (this.orderInfo?.status === ORDER_STATUS_CANCELED) {
return 'status-cancelled';
}
return '';
},
petsCountLabel() {
// 显示多少只宠物
if (this.orderInfo.home_service_pets && Array.isArray(this.orderInfo.home_service_pets) && this.orderInfo.home_service_pets.length > 0) {
return `${this.orderInfo.home_service_pets.length}只宠物`;
}
return '宠物';
},
petsNames() {
// 显示宠物名称
if (this.orderInfo.home_service_pets && Array.isArray(this.orderInfo.home_service_pets) && this.orderInfo.home_service_pets.length > 0) {
const petNames = this.orderInfo.home_service_pets.map(pet => pet.pet_name || '').filter(Boolean);
if (petNames.length > 0) {
return petNames.join('、');
}
}
return '--';
},
serviceDurationLabel() {
// 使用 home_service_slots 数组长度显示天数
if (this.orderInfo.home_service_slots && Array.isArray(this.orderInfo.home_service_slots) && this.orderInfo.home_service_slots.length > 0) {
return `${this.orderInfo.home_service_slots.length}`;
}
return '--';
},
serviceDurationTypeLabel() {
// 根据 service_type 显示不同的标签
const serviceType = this.orderInfo && this.orderInfo.home_service_order && this.orderInfo.home_service_order.service_type;
return serviceType === 'WALK' ? '遛宠时长' : '喂宠时长';
},
serviceAddress() {
// 获取服务地址
return this.orderInfo.home_service_order.address;
}
},
methods: {
goToOrderDetail() {
// 如果是未支付状态,不允许进入详情
if (this.isUnpay) {
return;
}
// 跳转到订单详情页面
const orderId = this.orderInfo?.home_service_order?.id || this.orderInfo?.order_id;
if (orderId) {
uni.navigateTo({
url: `/pageHome/service/order-detail?order_id=${orderId}&source=home_service`
});
} else {
uni.showToast({
title: '订单信息错误',
icon: 'none'
});
}
},
bookAgain() {
// 再次预约的逻辑:根据 service_type 跳转到不同的服务页面
const serviceType = this.orderInfo?.home_service_order?.service_type;
let serviceTypeParam = '';
if (serviceType === 'FEED') {
serviceTypeParam = 'feeding';
} else if (serviceType === 'WALK') {
serviceTypeParam = 'walking';
}
if (serviceTypeParam) {
uni.navigateTo({
url: `/pageHome/service/feeding?serviceType=${serviceTypeParam}`
});
} else {
// 如果 service_type 不匹配,使用默认值或提示
uni.showToast({
title: '服务类型错误',
icon: 'none'
});
}
},
cancelOrder() {
// 取消预约
this.$emit('cancel-order', this.orderInfo);
},
payNow() {
// 立即支付:跳转到订单详情页面
const orderId = this.orderInfo?.home_service_order?.id || this.orderInfo?.order_id;
if (orderId) {
uni.navigateTo({
url: `/pageHome/service/payment-confirm?orderId=${orderId}`
});
} else {
uni.showToast({
title: '订单信息错误',
icon: 'none'
});
}
},
viewServiceImages() {
// 查看服务图片,跳转到客服
jumpToWeChat();
}
}
}
</script>
<style lang="scss" scoped>
.home-service-order-item {
width: calc(100% - 40rpx);
margin: 20rpx auto;
padding: 20rpx;
border-radius: 30rpx;
background-color: #fff;
box-sizing: border-box;
}
.order-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
.order-number {
font-size: 24rpx;
color: #666;
}
.status-btn {
width: 104rpx;
height: 48rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #fef6ff;
.status-text {
font-size: 20rpx;
color: $app_fc_mark;
}
&.status-unpay {
background-color: #FEF6FF;
.status-text {
color: #FF19A0;
}
}
&.status-cancelled {
background-color: #f7f7f7;
.status-text {
color: #afa5ae;
}
}
}
}
.line-view {
width: 100%;
height: 2rpx;
background-color: #ececec;
margin-bottom: 20rpx;
}
.order-content {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.info-row {
display: flex;
align-items: center;
.info-label {
font-size: 24rpx;
color: #999;
margin-right: 20rpx;
flex-shrink: 0;
}
.info-value {
font-size: 24rpx;
color: #333;
flex: 1;
word-break: break-all;
}
}
.order-footer {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 20rpx;
.cancel-btn {
padding: 12rpx 18rpx;
border-radius: 100px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #9B939A;
margin-left: 24rpx;
.footer-btn-text {
font-size: 24rpx;
color: $app_fc_main;
}
}
.pay-btn {
padding: 12rpx 18rpx;
border-radius: 100px;
border: 2rpx solid #ff19a0;
display: flex;
align-items: center;
justify-content: center;
margin-left: 24rpx;
.footer-btn-text {
font-size: 24rpx;
color: #ff19a0;
}
}
.service-btn {
padding: 12rpx 18rpx;
border-radius: 100px;
border: 2rpx solid #ff19a0;
display: flex;
align-items: center;
justify-content: center;
margin-left: 24rpx;
.footer-btn-text {
font-size: 24rpx;
color: #ff19a0;
}
}
.book-again-btn {
padding: 12rpx 18rpx;
border-radius: 100px;
border: 2rpx solid #ff19a0;
display: flex;
align-items: center;
justify-content: center;
margin-left: 24rpx;
.book-again-text {
font-size: 24rpx;
color: #ff19a0;
}
}
// 最后一个按钮的特殊样式
>view:last-child {
border: 2rpx solid #FF19A0 !important;
text {
color: #FF19A0 !important;
}
}
}
</style>

View File

@ -0,0 +1,436 @@
<template>
<view class="home-training-order-item">
<!-- 订单头部 -->
<view class="order-header">
<text class="order-number">订单编号: {{ orderNumber }}</text>
<view class="status-btn" :class="statusBtnClass">
<text class="status-text">{{ orderStatus }}</text>
</view>
</view>
<view class="line-view"></view>
<!-- 订单内容 -->
<view class="order-content">
<!-- 宠物信息 -->
<view class="info-row pet-row">
<image class="pet-icon" :src="petIcon" mode="aspectFit" />
<text class="pet-name">{{ petName }}</text>
</view>
<!-- 服务地址 -->
<view class="info-row" v-if="serviceAddress">
<text class="info-label">服务地址</text>
<text class="info-value">{{ serviceAddress }}</text>
</view>
<!-- 服务券 -->
<!-- <view class="info-row" v-if="serviceCoupon">
<text class="info-label">服务券</text>
<text class="info-value">{{ serviceCoupon }}</text>
</view> -->
</view>
<!-- 宠物报告横幅 -->
<!-- <view class="report-banner" v-if="hasReport">
<text class="report-text">该订单宠物报告已生成</text>
<view class="view-report-btn" @click="viewReport">
<text class="view-report-text">立即查看</text>
<text class="arrow">></text>
</view>
</view> -->
<!-- 底部操作按钮 -->
<view class="order-footer" v-if="showFooterButtons">
<!-- 未支付状态显示立即支付 -->
<view v-if="isUnpay && !isPendingPay" class="footer-btn pay-btn" @click="payNow">
<text class="footer-btn-text">立即支付</text>
</view>
<!-- 待支付状态显示购买课程 -->
<view v-if="isPendingPay" class="footer-btn buy-course-btn" @click="buyCourse">
<text class="footer-btn-text">购买课程</text>
</view>
<!-- 已预约状态显示取消预约 -->
<view v-if="isReserved" class="footer-btn cancel-btn" @click="cancelOrder">
<text class="footer-btn-text">取消预约</text>
</view>
<!-- 服务中状态显示查看合同和去评价 -->
<template v-if="isService">
<view class="footer-btn view-contract-btn" @click="viewContract">
<text class="footer-btn-text">查看合同</text>
</view>
<view class="footer-btn evaluate-btn" @click="gotoEvaluate">
<text class="footer-btn-text">去评价</text>
</view>
</template>
<!-- 已完成状态显示去评价 -->
<view v-if="isCompleted" class="footer-btn evaluate-btn" @click="gotoEvaluate">
<text class="footer-btn-text">去评价</text>
</view>
</view>
</view>
</template>
<script>
import { orderStatusList, ORDER_STATUS_CANCELED, ORDER_STATUS_SERVICE, ORDER_STATUS_COMPLETED, ORDER_STATUS_RESERVED, ORDER_STATUS_SEND, ORDER_STATUS_UNPAY } from "@/pageHome/constants/home";
import { PET_TYPE_CAT } from "@/constants/app.business";
export default {
name: 'HomeTrainingOrderItem',
props: {
orderInfo: {
type: Object,
default: null
}
},
computed: {
orderNumber() {
return this.orderInfo?.order_no || this.orderInfo?.order_id || '--';
},
orderStatus() {
const statusItem = orderStatusList.find(
(item) => `${item.value}` === `${this.orderInfo?.status}`
);
return statusItem?.label || '--';
},
statusBtnClass() {
// 根据状态返回不同的样式类
if (this.orderInfo?.status === ORDER_STATUS_UNPAY) {
return 'status-unpay';
} else if (this.orderInfo?.status === ORDER_STATUS_SERVICE) {
return 'status-service';
} else if (this.orderInfo?.status === ORDER_STATUS_COMPLETED) {
return 'status-completed';
} else if (this.orderInfo?.status === ORDER_STATUS_CANCELED) {
return 'status-cancelled';
}
return '';
},
petName() {
return this.orderInfo?.home_training_order.pet_name;
},
petIcon() {
// 根据宠物类型返回图标(粉色图标)
const petType = this.orderInfo?.type || this.orderInfo?.home_training?.type;
// 使用粉色图标,可以根据实际图片路径调整
return require("@/static/images/dog.png");
},
appointmentTime() {
// 格式化预约时间 "2024-12-13 16:00-17:00"
if (this.orderInfo?.slots && Array.isArray(this.orderInfo.slots) && this.orderInfo.slots.length > 0) {
const slot = this.orderInfo.slots[0];
const date = slot.date || '';
const period = slot.period_name || '';
if (date && period) {
// 将 "16:00:00 ~ 17:00:00" 转换为 "16:00-17:00"
const formattedPeriod = period.replace(/:00/g, '').replace(/\s*~\s*/g, '-');
return `${date} ${formattedPeriod}`;
}
}
// 如果有 order_date 和 period_name
if (this.orderInfo?.order_date && this.orderInfo?.period_name) {
const formattedPeriod = this.orderInfo.period_name.replace(/:00/g, '').replace(/\s*~\s*/g, '-');
return `${this.orderInfo.order_date} ${formattedPeriod}`;
}
// 如果有 service_time 字段
if (this.orderInfo?.service_time) {
return this.orderInfo.service_time;
}
return '';
},
serviceAddress() {
return this.orderInfo?.home_training_order?.address_detail;
},
hasReport() {
// 判断是否有宠物报告(可以根据实际字段判断)
return this.orderInfo?.has_report || this.orderInfo?.report_id || false;
},
isUnpay() {
return this.orderInfo?.status === ORDER_STATUS_UNPAY;
},
isPendingPay() {
// 待支付状态,如果和未支付是同一个状态,可以根据其他字段判断
// 例如:如果未支付且需要购买课程,则显示"购买课程"按钮
// 这里暂时返回 false如果待支付是独立状态需要添加对应的状态判断
// 如果需要区分,可以检查 orderInfo 中的其他字段,比如 order_type 或 is_course_order
return this.orderInfo?.is_course_order || false;
},
isReserved() {
return this.orderInfo?.status === ORDER_STATUS_RESERVED;
},
isService() {
return this.orderInfo?.status === ORDER_STATUS_SERVICE;
},
isCompleted() {
return this.orderInfo?.status === ORDER_STATUS_COMPLETED;
},
showFooterButtons() {
// 根据订单状态决定是否显示底部按钮
return this.isUnpay || this.isPendingPay || this.isReserved || this.isService || this.isCompleted;
}
},
methods: {
viewReport() {
// 查看宠物报告
this.$emit('view-report', this.orderInfo);
},
buyGoods() {
// 随车购商品
this.$emit('buy-goods', this.orderInfo);
},
addService() {
// 添加附加项
this.$emit('add-service', this.orderInfo);
},
payNow() {
// 立即支付 - 跳转到支付确认页面
const orderId = this.orderInfo?.home_training_order?.id || this.orderInfo?.id || this.orderInfo?.order_id;
if (!orderId) {
uni.showToast({
title: '订单ID不存在',
icon: 'none'
});
return;
}
uni.navigateTo({
url: `/pageHome/service/training-order-detail?order_id=${orderId}&source=home_training`
});
},
buyCourse() {
// 购买课程
this.$emit('buy-course', this.orderInfo);
},
cancelOrder() {
// 取消预约
this.$emit('cancel-order', this.orderInfo);
},
viewContract() {
// 查看合同
this.$emit('view-contract', this.orderInfo);
},
gotoEvaluate() {
// 去评价
this.$emit('goto-evaluate', this.orderInfo);
}
}
}
</script>
<style lang="scss" scoped>
.home-training-order-item {
width: calc(100% - 40rpx);
margin: 20rpx auto;
padding: 20rpx;
border-radius: 30rpx;
background-color: #fff;
box-sizing: border-box;
}
.order-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
.order-number {
font-size: 24rpx;
color: #666;
}
.status-btn {
width: 104rpx;
height: 48rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #fef6ff;
.status-text {
font-size: 20rpx;
color: $app_fc_mark;
}
&.status-unpay {
background-color: #FEF6FF;
.status-text {
color: #FF19A0;
}
}
&.status-service {
background-color: #fef6ff;
.status-text {
color: #FF19A0;
}
}
&.status-completed {
background-color: #f7f7f7;
.status-text {
color: #afa5ae;
}
}
&.status-cancelled {
background-color: #f7f7f7;
.status-text {
color: #afa5ae;
}
}
}
}
.line-view {
width: 100%;
height: 2rpx;
background-color: #ececec;
margin-bottom: 20rpx;
}
.order-content {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.info-row {
display: flex;
align-items: center;
.info-label {
font-size: 24rpx;
color: #999;
margin-right: 20rpx;
flex-shrink: 0;
}
.info-value {
font-size: 24rpx;
color: #333;
flex: 1;
word-break: break-all;
}
}
.pet-row {
display: flex;
align-items: center;
.pet-icon {
width: 24rpx;
height: 24rpx;
}
.pet-name {
font-size: 24rpx;
}
}
.report-banner {
margin-top: 20rpx;
padding: 20rpx;
background-color: #fef6ff;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: space-between;
.report-text {
font-size: 24rpx;
color: #FF19A0;
}
.view-report-btn {
display: flex;
align-items: center;
gap: 4rpx;
.view-report-text {
font-size: 24rpx;
color: #FF19A0;
}
.arrow {
font-size: 24rpx;
color: #FF19A0;
}
}
}
.order-footer {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 20rpx;
.footer-btn {
padding: 12rpx 18rpx;
border-radius: 100px;
display: flex;
align-items: center;
justify-content: center;
margin-left: 24rpx;
.footer-btn-text {
font-size: 24rpx;
}
&.pay-btn {
border: 2rpx solid #ff19a0;
.footer-btn-text {
color: #ff19a0;
}
}
&.buy-course-btn {
border: 2rpx solid #ff19a0;
.footer-btn-text {
color: #ff19a0;
}
}
&.cancel-btn {
border: 1px solid #9B939A;
.footer-btn-text {
color: $app_fc_main;
}
}
&.view-contract-btn {
border: 2rpx solid #ff19a0;
.footer-btn-text {
color: #ff19a0;
}
}
&.evaluate-btn {
border: 2rpx solid #ff19a0;
.footer-btn-text {
color: #ff19a0;
}
}
}
// 最后一个按钮的特殊样式
>view:last-child {
border: 2rpx solid #FF19A0 !important;
text {
color: #FF19A0 !important;
}
}
}
</style>

View File

@ -0,0 +1,406 @@
<template>
<view class="order-page-container">
<scroll-view class="tab-view" scroll-x="true" :show-scrollbar="false">
<view class="tab-view-content">
<view class="tab-item" v-for="item in tabList" :key="item.value" @click.stop="selectOrderStatus(item)">
<text class="fs-28 app-fc-normal" :class="{ 'tab-text ali-puhui-bold': item.value === currentStatus }">
{{ item.label }}
</text>
<view class="tab-line" :class="{ 'tab-selected-line': item.value === currentStatus }" />
</view>
</view>
</scroll-view>
<view class="order-content">
<view class="status-view-container" v-if="isLoading">
<uni-load-more status="loading" />
</view>
<view class="status-view-container" v-if="!isLoading && orderList.length === 0">
<uni-load-more status="noMore" />
</view>
<scroll-view
class="scroll-view"
scroll-y
v-if="!isLoading && orderList.length"
refresher-enabled
:refresher-triggered="isRefresh"
@refresherrefresh="refresherAction"
@scrolltolower="loadMoreAction"
refresher-background="transparent"
>
<!-- 根据 source 字段动态渲染不同的组件 -->
<block v-for="(item, index) in orderList" :key="item.order_id || index">
<!-- source 'pet_order' 时使用 reservation 组件 -->
<reservation
v-if="item.source === 'pet_order'"
:order-info="item"
@refresh-list="refresherAction"
@cancel-order="handleCancelOrder"
/>
<!-- source 'home_service' 时使用 home-service-order-item 组件 -->
<home-service-order-item
v-else-if="item.source === 'home_service'"
:order-info="item"
@refresh-list="refresherAction"
@cancel-order="handleCancelOrder"
/>
<!-- source 'home_training' 时使用 home-training-order-item 组件 -->
<home-training-order-item
v-else-if="item.source === 'home_training'"
:order-info="item"
@refresh-list="refresherAction"
@pay-now="handlePayNow"
@buy-course="handleBuyCourse"
@cancel-order="handleCancelOrder"
@view-contract="handleViewContract"
@goto-evaluate="handleGotoEvaluate"
/>
</block>
</scroll-view>
</view>
<!-- 取消预约确认弹窗 - 放在父组件中避免被 overflow: hidden 裁剪 -->
<pop-up-modal
v-if="isShowCancelModal"
content="确定要取消预约吗?"
@confirm="confirmCancelOrder"
@cancel="isShowCancelModal = false"
/>
</view>
</template>
<script>
import Reservation from './reservation.vue';
import HomeServiceOrderItem from './home-service-order-item.vue';
import HomeTrainingOrderItem from './home-training-order-item.vue';
import { showOrderStatus } from "@/pageHome/constants/home";
import { getOrderList, cancelHomeOrder, cancelTrainingOrder } from "@/api/order";
import { cancelPetOrderRefund } from "@/api/login";
import PopUpModal from "@/components/PopUpModal.vue";
export default {
name: 'index',
components: {
Reservation,
HomeServiceOrderItem,
HomeTrainingOrderItem,
PopUpModal
},
data() {
return {
tabList: showOrderStatus,
currentStatus: 0,
isLoading: true,
orderList: [],
pageNumber: 1,
pageSize: 10,
isRefresh: false,
isLoadMore: false,
isNoMore: false,
isShowCancelModal: false,
cancelOrderInfo: null,
};
},
onLoad(options) {
// 如果从外部传入状态,设置当前状态
if (options.status !== undefined) {
this.currentStatus = parseInt(options.status) || 0;
}
},
mounted() {
uni.$on("createRemarkSuccess", this.refresherAction);
uni.$on("refreshOrderList", this.refresherAction);
this.initData();
},
destroyed() {
uni.$off("createRemarkSuccess", this.refresherAction);
uni.$off("refreshOrderList", this.refresherAction);
},
watch: {
currentStatus: {
handler(newVal, oldVal) {
if (newVal !== oldVal && oldVal !== undefined) {
this.initData();
}
},
immediate: false
}
},
methods: {
selectOrderStatus(status) {
if (this.currentStatus === status.value) {
return;
}
this.currentStatus = status.value;
},
initData() {
this.isLoading = true;
this.pageNumber = 1;
this.isRefresh = false;
this.isLoadMore = false;
this.isNoMore = false;
this.orderList = [];
this.getData();
},
refresherAction() {
if (this.isRefresh) {
return;
}
this.pageNumber = 1;
this.isRefresh = true;
this.isLoadMore = false;
this.isNoMore = false;
this.getData();
},
loadMoreAction() {
console.log(this.isNoMore)
if (this.isNoMore) {
return;
}
this.pageNumber++;
this.getData();
},
getData() {
const value = uni.getStorageSync("userInfo");
const params = {
user_id: value.user_id,
status: this.currentStatus || 0,
page: this.pageNumber,
page_size: this.pageSize
};
getOrderList(params)
.then((res) => {
let list = res?.data || [];
if (this.pageNumber === 1) {
this.orderList = list;
} else {
this.orderList = [...this.orderList, ...list];
}
this.isRefresh = false;
this.isLoading = false;
this.isLoadMore = false;
this.isNoMore = list.length < 9;
})
.catch(() => {
if (this.pageNumber !== 1) {
this.pageNumber--;
}
this.isLoading = false;
this.isRefresh = false;
this.isLoadMore = false;
});
},
handleBookAgain(orderInfo) {
// 处理再次预约的逻辑
// 可以跳转到预约页面
console.log('再次预约', orderInfo);
// 例如uni.navigateTo({ url: '/pageHome/service/feeding?orderId=' + orderInfo.order_id });
},
handleViewReport(orderInfo) {
// 处理查看宠物报告的逻辑
console.log('查看宠物报告', orderInfo);
// 可以跳转到报告详情页面
// uni.navigateTo({ url: '/pages/client/report/detail?orderId=' + orderInfo.order_id });
},
handleBuyGoods(orderInfo) {
// 处理随车购商品的逻辑
uni.navigateTo({
url: `/pages/client/category/index?petOrderId=${orderInfo.order_id}&addressId=${orderInfo.address_id}`,
});
},
handleAddService(orderInfo) {
// 处理添加附加项的逻辑
console.log('添加附加项', orderInfo);
// 可以跳转到添加附加项页面或显示弹窗
},
handleCancelOrder(orderInfo) {
// 显示取消预约确认弹窗
this.cancelOrderInfo = orderInfo;
this.isShowCancelModal = true;
},
confirmCancelOrder() {
// 确认取消订单
if (!this.cancelOrderInfo) {
this.isShowCancelModal = false;
return;
}
const orderInfo = this.cancelOrderInfo;
this.isShowCancelModal = false;
uni.showLoading({
title: "正在取消订单",
mask: true,
});
// 根据订单类型调用不同的取消接口
let cancelPromise;
if (orderInfo.source === 'home_service') {
// 上门服务订单使用 cancelHomeOrder 接口
const orderId = orderInfo.home_service_order?.id;
cancelPromise = cancelHomeOrder(orderId);
} else if (orderInfo.source === 'pet_order') {
// pet_order 类型订单使用 cancelPetOrderRefund 接口
const orderId = orderInfo.pet_order?.order_id || orderInfo.order_id;
cancelPromise = cancelPetOrderRefund({ order_id: Number(orderId) });
} else {
// 上门训练订单使用 cancelTrainingOrder 接口
const orderId = orderInfo.home_training_order?.id;
cancelPromise = cancelTrainingOrder({ order_id: Number(orderId) });
}
cancelPromise
.then((res) => {
uni.hideLoading();
// 判断是否成功result === 'success'
if (res.result === 'success') {
this.cancelOrderInfo = null;
uni.showToast({
title:"取消成功",
icon: "success",
});
// 刷新订单列表
this.refresherAction();
} else {
// 失败:弹出错误信息
this.cancelOrderInfo = null;
uni.showToast({
title: res.msg || res.message || "取消订单失败",
icon: "none",
});
}
})
.catch((err) => {
uni.hideLoading();
this.cancelOrderInfo = null;
// 捕获异常时显示错误信息
const errorMsg = err?.msg || err?.message || err || "取消订单失败,请稍后重试";
uni.showToast({
title: errorMsg,
icon: "none",
});
});
},
handlePayNow(orderInfo) {
// 处理立即支付
console.log('立即支付', orderInfo);
// TODO: 实现支付逻辑
uni.showToast({
title: '支付功能开发中',
icon: 'none'
});
},
handleBuyCourse(orderInfo) {
// 处理购买课程
console.log('购买课程', orderInfo);
// TODO: 实现购买课程逻辑
uni.showToast({
title: '购买课程功能开发中',
icon: 'none'
});
},
handleViewContract(orderInfo) {
// 处理查看合同
console.log('查看合同', orderInfo);
// TODO: 实现查看合同逻辑,可能需要跳转到合同详情页
uni.showToast({
title: '查看合同功能开发中',
icon: 'none'
});
},
handleGotoEvaluate(orderInfo) {
// 处理去评价
const orderId = orderInfo.order_id || orderInfo.home_training_order?.order_id;
const pinglunId = orderInfo.pinglun_id;
if (pinglunId) {
// 如果已有评价,跳转到评价详情
uni.navigateTo({
url: `/pages/client/remark/details?remarkId=${pinglunId}`,
});
} else {
// 跳转到评价页面
// TODO: 确认订单类型常量
uni.navigateTo({
url: `/pages/client/order/remark?orderId=${orderId}&orderType=3`, // 3 可能是 home_training 的订单类型
});
}
}
}
}
</script>
<style lang="scss" scoped>
.order-page-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f7f8fa;
}
.tab-view {
width: 100%;
height: 100rpx;
background-color: #ffffff;
white-space: nowrap;
border-top: 2rpx solid #f4f4f4;
flex-shrink: 0;
.tab-view-content {
display: inline-flex;
align-items: center;
height: 100%;
padding: 0 20rpx;
}
.tab-item {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 20rpx;
flex-shrink: 0;
white-space: nowrap;
transition: 0.2ms all;
.tab-text {
color: $app_fc_main;
white-space: nowrap;
font-size: 32rpx;
}
}
.tab-line {
width: 24rpx;
height: 10rpx;
background-color: transparent;
margin-top: 18rpx;
border-radius: 100px;
}
.tab-selected-line {
background-color: $app_color_main;
}
}
.order-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.status-view-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.scroll-view {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
padding-bottom: 32rpx;
}
</style>

View File

@ -0,0 +1,514 @@
<template>
<view class="order-container">
<order-cell
v-if="orderInfo"
:orderInfo="orderInfo.pet_order"
:isShowReport="orderInfo.has_precheck"
:key="orderInfo.order_id"
@addService="showAddService"
@callPhone="showCall"
@cancelOrder="clickCancel"
@gotoDetail="gotoDetail"
@gotoEvaluate="gotoEvaluate"
@gotoComparisonChart="gotoComparisonChart"
@showGoods="showGoods"
@payNow="handlePayNow"
/>
<view v-if="elasticLayer" class="elastic-layer" />
<view class="additional-bottom" v-if="additionalBom">
<image src="@/static/images/close.png" mode="aspectFit" class="close-icon"
@click="(additionalBom = false), (elasticLayer = false)" />
<view class="recharge-method">
<view class="wechat">
<view class="select">
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
<text class="x">微信</text>
</view>
<image v-if="selected1" class="not-selected" @click.stop="selectOption1('1')" src="@/static/images/w.png"
mode="widthFix" />
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
</view>
<view class="wechat">
<view class="select">
<image class="w" src="@/static/images/wallet.png" mode="widthFix" />
<text class="x">钱包支付</text>
</view>
<image v-if="selected3" class="not-selected" @click.stop="selectOption2('2')" src="@/static/images/w.png"
mode="widthFix" />
<image v-if="selected4" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
</view>
</view>
<view class="payment" @click="wxPayAction">
<text class="Z"> 支付 </text>
</view>
</view>
<add-service-pay-modal v-if="isShowPayModal" @close="hidePayModal" :order-info="showOrderInfo"
:new-weight="otherWeight" @paymentConfirm="paymentConfirm" />
<call-modal :phone-number="showOrderInfo.phone" v-if="isShowCallModal" @close="hideCallModal" />
<pay-success-modal v-if="isShowPaySuccessModal" @close="isShowPaySuccessModal = false" @ok="okPayAction" />
<!-- CompareModal 已移除改为跳转到新页面 -->
<success-modal v-if="showSuccessModal" title="转发成功" message="请前往宠圈查看" @ok="jumpToCommunity" />
</view>
</template>
<script>
import OrderCell from "@/components/petOrder/order-cell.vue";
import PaySuccessModal from "@/components/petOrder/pay-success-modal.vue";
import {
PRICE_DIFF_TYPE_SERVICE,
} from "@/pageHome/constants/home";
import {
addServicePay,
cancelOrder,
getOrderList,
payOrder,
} from "@/api/order";
import CallModal from "@/components/petOrder/call-modal.vue";
import AddServicePayModal from "@/components/petOrder/add-service-pay-modal.vue";
import { ORDER_TYPE_PET_SERVICE } from "@/constants/app.business";
import SuccessModal from "@/components/SuccessModal.vue";
import { walletTransaction } from "../../../api/login";
export default {
components: {
AddServicePayModal,
CallModal,
OrderCell,
PaySuccessModal,
SuccessModal,
},
props: {
orderInfo: {
type: Object,
default: null
}
},
data() {
return {
additionalBom: false,
elasticLayer: false,
selected1: true,
selected2: false,
selected3: true,
selected4: false,
recharge: true,
record: false,
WeChat: "",
wallet: "",
isShowPayModal: false,
isShowCallModal: false,
showOrderInfo: null,
otherWeight: {},
isShowPaySuccessModal: false,
showSuccessModal: false,
};
},
mounted() {
},
methods: {
jumpToCommunity() {
this.showSuccessModal = false;
uni.redirectTo({
url: "/pages/client/index/index?activePageId=communityPage",
});
},
selectOption1(v) {
this.WeChat = v;
this.wallet = "";
console.log(this.WeChat);
this.selected1 = false;
this.selected2 = true;
this.selected3 = true;
this.selected4 = false;
},
selectOption2(v) {
this.WeChat = "";
this.wallet = v;
console.log(this.wallet);
this.selected1 = true;
this.selected2 = false;
this.selected3 = false;
this.selected4 = true;
},
showAddService() {
this.showOrderInfo = this.orderInfo;
this.isShowPayModal = true;
},
hidePayModal() {
this.isShowPayModal = false;
this.showOrderInfo = null;
this.otherWeight = {};
},
showCall() {
this.showOrderInfo = this.orderInfo;
this.isShowCallModal = true;
},
hideCallModal() {
this.isShowCallModal = false;
this.showOrderInfo = null;
},
okPayAction() {
this.isShowPaySuccessModal = false;
this.$emit('refresh-list');
},
// paymentConfirm(data) {
// this.additionalBom = true
// this.elasticLayer = true
// },
paymentConfirm(data) {
console.log(data);
this.dataList = data;
addServicePay(this.dataList.order_id, this.otherWeight.weight_id)
.then((res) => {
const { code, msg } = res;
if (`${code}` === "-121") {
uni.hideLoading();
uni.showToast({
title: msg,
icon: "none",
duration: 4000,
});
this.additionalBom = false;
this.elasticLayer = false;
this.hidePayModal();
this.$emit('refresh-list');
} else {
// console.log(111,data,'--')
if (!data.needRefund) {
console.log(111)
this.additionalBom = true;
this.elasticLayer = true;
this.hidePayModal();
} else if (data.needRefund) {
// console.log(this,'--')
// console.log(222)
this.additionalBom = false;
this.elasticLayer = false;
this.isShowPayModal = false
uni.showToast({
title: "退差价成功",
icon: "none",
});
this.$emit('refresh-list');
}
// this.wxPayAction(data.needRefund, PRICE_DIFF_TYPE_SERVICE)
}
})
.catch((err) => {
uni.showToast({
title: err || "创建订单失败",
icon: "none",
});
uni.hideLoading();
});
},
wxPayAction() {
if (this.dataList.needRefund) {
uni.hideLoading();
this.hidePayModal();
this.isShowPaySuccessModal = true;
return;
}
if (this.WeChat == 1) {
payOrder(this.dataList.order_id, PRICE_DIFF_TYPE_SERVICE)
.then((res) => {
const payData = res?.info?.pay_data || {};
uni.requestPayment({
provider: "wxpay",
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.sign,
success: (res) => {
uni.hideLoading();
uni.showToast({
title: "支付成功",
icon: "none",
});
this.additionalBom = false;
this.elasticLayer = false;
this.hidePayModal();
this.$emit('refresh-list');
},
fail: (err) => {
uni.showToast({
title: err?.msg || "支付失败",
icon: "none",
});
uni.hideLoading();
},
});
})
.catch((err) => {
uni.showToast({
title: err || "支付失败",
icon: "none",
});
});
} else if (this.wallet == 2) {
const value = (this.$store.state && this.$store.state.user && this.$store.state.user.userInfo) || {};
// console.log(this.selectService,'---')
const data = {
business_type: 2,
wallet_id: value.wallet_id,
user_id: value.userID,
amount: Number(this.dataList.diffPrice),
business_id: this.dataList.order_id,
difference_amount: Number(this.dataList.diffPrice),
};
walletTransaction(data).then((res) => {
if (res.code == 200) {
uni.hideLoading();
uni.showToast({
title: "支付成功",
icon: "none",
});
this.additionalBom = false;
this.elasticLayer = false;
this.hidePayModal();
this.$emit('refresh-list');
} else if (res.code == 100) {
uni.showToast({
title: res.message,
icon: "none",
});
}
});
}
},
clickCancel() {
// 触发事件,让父组件处理弹窗显示
this.$emit('cancel-order', this.orderInfo);
},
handlePayNow(orderInfo) {
// 跳转到订单详情页面进行支付
uni.navigateTo({
url: `/page-reser/order/order-detail-page?source=${this.orderInfo.source}&order_id=${orderInfo.order_id}`,
events: {
refreshList: () => this.$emit('refresh-list'),
},
});
},
gotoDetail() {
uni.navigateTo({
url: `/pageHome/order/order-detail-page?source=${this.orderInfo.source}&order_id=${this.orderInfo.pet_order.order_id}`,
events: {
refreshList: () => this.$emit('refresh-list'),
},
});
},
aspectFillto() {
uni.redirectTo({
url: "/pages/client/index/index?activePageId=minePage",
});
},
gotoEvaluate() {
// if (this.orderInfo.pinglun_id) {
// uni.navigateTo({
// url: `/pages/client/remark/details?remarkId=${this.orderInfo.pinglun_id}`,
// });
// } else {
// }
uni.navigateTo({
url: `/pages/client/order/remark?orderId=${this.orderInfo.pet_order.order_id}`,
});
},
gotoComparisonChart() {
const orderId = this.orderInfo?.order_id;
if (!orderId) {
uni.showToast({
title: "订单ID不存在",
icon: "none",
});
return;
}
uni.navigateTo({
url: `/pages/client/order/wash-compare?orderid=${orderId}`,
});
},
showGoods() {
uni.navigateTo({
url: `/pages/client/category/index?petOrderId=${this.orderInfo.order_id}&addressId=${this.orderInfo.address_id}`,
});
},
},
};
</script>
<style lang="scss" scoped>
.order-container {
// display: flex;
// flex: 1;
// flex-direction: column;
// width: 100%;
// height: 100%;
// box-sizing: border-box;
background-color: #f7f8fa;
// padding-bottom: 32rpx;
.nav-container {
background: #fff;
height: 180rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
.reservation-order {
margin-top: 82rpx;
}
.ctFill {
width: 148rpx;
height: 57rpx;
position: absolute;
top: 102rpx;
left: 15rpx;
// border: 1px solid #000;
.home-cw {
position: absolute;
top: 17rpx;
left: 20rpx;
width: 8.5px;
height: 14.21px;
}
}
.nav-title {
font-size: 14px;
// font-weight: bold;
color: #000;
height: 100%;
}
}
.body-container {
display: flex;
flex: 1;
position: relative;
.status-view-container {
width: 100%;
height: 60%;
display: flex;
align-items: center;
justify-content: center;
}
.scroll-view {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
}
.elastic-layer {
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
z-index: 999;
}
.additional-bottom {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
z-index: 9999;
.close-icon {
width: 50rpx;
height: 50rpx;
position: absolute;
right: 23rpx;
top: 2rpx;
}
.recharge-method {
height: 541rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #fff;
margin-bottom: 30rpx;
border-radius: 30rpx;
width: 100%;
.wechat {
width: 367px;
height: 58px;
/* 自动布局 */
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
border-radius: 8px;
box-sizing: border-box;
// border: 1px solid #ececec;
margin-bottom: 30rpx;
}
.select {
display: flex;
align-items: center;
.w {
width: 68rpx;
height: 68rpx;
}
.x {
font-family: PingFangSC;
font-size: 32rpx;
font-weight: 500;
line-height: 16px;
letter-spacing: normal;
color: #3d3d3d;
display: flex;
margin-left: 24rpx;
}
}
.not-selected {
width: 36rpx;
height: 36rpx;
}
}
.payment {
position: absolute;
border-radius: 100px;
bottom: 50rpx;
left: 30rpx;
width: 343px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #FF19A0;
z-index: 10000;
.Z {
font-family: PingFang SC;
font-size: 16px;
font-weight: normal;
color: #FF19A0;
}
}
}
}
</style>

View File

@ -0,0 +1,560 @@
<template>
<view style="background: #fff">
<view class="scroll">
<view class="card-roll">
<image class="file-pet" :src="imgPrefix + 'integral-ground.png'" mode="widthFix" />
<view class="points-content">
<view class="points-top-row">
<text class="points-label">积分数量 ()</text>
<view class="points-right" @click="goToPointsDetails">
<text class="points-details-text">积分明细</text>
<image class="points-arrow" :src="imgPrefix + 'recharge-whiteArrow.png'" mode="widthFix" />
</view>
</view>
<text class="points-value">{{ totalPoints || 0 }}</text>
</view>
</view>
</view>
<view class="select-amount">
<text class="select">请选择充值金额</text>
<view class="select-recharge">
<view @click.stop="giftOrderStatus(item)" v-for="item in giftList" :key="item.id">
<view class="recharge" :class="{ recharge2: item.id === giftStatus }">
<view class="first">
<image class="integral-icon" :src="imgPrefix + 'integral-icon.png'" mode="widthFix" />
<text class="f">{{ item.points }}</text>
</view>
<text class="s">¥{{ item.amount }} </text>
</view>
</view>
</view>
<text class="select">请选择充值方式</text>
<view class="recharge-method">
<view class="wechat" @click.stop="selectOption1">
<view class="select">
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
<text class="x">微信</text>
</view>
<image v-if="selected1" class="not-selected"
src="@/static/images/w.png" mode="widthFix" />
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
</view>
</view>
<view class="remk-reservation">
<view class="rese-bom" @click="rechargeNow()">
<text class="l" style="color: #fff;">立即充值</text>
</view>
</view>
</view>
</view>
</template>
<script>
import { pointsRechargeList, walletWxpay } from '@/api/login';
import { getUserInfo } from '@/api/user';
import { imgPrefix } from '@/utils/common';
export default {
props: {
user_id: {
type: String,
default: ""
}
},
data() {
return {
imgPrefix,
giftList: [],
giftStatus: null,
giftLabel: null,
selected1: true,
selected2: false
};
},
mounted() {
this.getGiftList();
},
computed: {
totalPoints() {
return this.$store.state?.user?.userInfo.totalPoints || 0;
}
},
methods: {
getGiftList() {
pointsRechargeList().then((res) => {
this.giftList = res.data;
});
},
giftOrderStatus(item) {
// 如果点击的是已选中的项,不做处理
if (this.giftStatus === item.id) {
return;
}
// 更新选中状态
this.giftStatus = item.id;
this.giftLabel = item.amount;
},
goToPointsDetails() {
uni.navigateTo({
url: '/pages/client/recharge/points-details'
});
},
// 刷新用户信息
refreshUserInfo() {
getUserInfo().then((res) => {
if (res && res.data) {
this.$store.dispatch("user/setUserInfo", res.data);
}
}).catch((err) => {
console.error('刷新用户信息失败:', err);
});
},
selectOption1() {
this.selected1 = false;
this.selected2 = true;
},
rechargeNow() {
// 验证是否选择了充值金额
if (!this.giftStatus || !this.giftLabel) {
uni.showToast({
title: '请选择充值金额',
icon: 'none'
});
return;
}
// 验证是否选择了支付方式
if (this.selected1) {
uni.showToast({
title: '请选择支付方式',
icon: 'none'
});
return;
}
uni.showLoading({
title: '处理中...',
mask: true
});
const data = {
type: 6, // 购买积分传6
total_fee: +this.giftLabel
};
walletWxpay(data).then((res) => {
uni.hideLoading();
// 使用获取的支付参数进行支付
uni.requestPayment({
provider: 'wxpay',
timeStamp: res.data.timeStamp,
nonceStr: res.data.nonceStr,
package: res.data.package,
signType: res.data.signType,
paySign: res.data.paySign,
success: (payRes) => {
uni.showToast({
title: '支付成功',
icon: 'success'
});
// 刷新用户信息,更新 totalPoints
this.refreshUserInfo();
},
fail: (err) => {
uni.showToast({
title: '支付失败',
icon: 'none'
});
}
});
}).catch((err) => {
uni.hideLoading();
console.error('获取支付参数失败:', err);
uni.showToast({
title: err.msg || '获取支付参数失败',
icon: 'none'
});
});
}
},
};
</script>
<style lang="scss" scoped>
.scroll {
display: flex;
margin-top: 20rpx;
.card-roll {
position: relative;
margin: 0 auto;
.file-pet {
width: 710rpx;
height: 300rpx;
margin-left: 10rpx;
}
.card {
font-family: PingFangSC;
font-size: 40rpx;
color: #ffffff;
}
.points-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
padding: 32rpx 40rpx;
box-sizing: border-box;
}
.points-top-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.points-label {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
.points-value {
font-size: 32px;
font-weight: 500;
color: #fff;
}
.points-right {
display: flex;
flex-direction: row;
align-items: center;
gap: 8rpx;
}
.points-details-text {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.5);
}
.points-arrow {
width: 11rpx;
height: 18rpx;
}
.effectiveTime {
display: flex;
margin-top: 16rpx;
.surplus {
font-family: PingFang SC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #ffffff;
}
.first {
font-family: PingFang SC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #ffffff;
}
}
.recharge {
position: absolute;
left: 35rpx;
top: 185rpx;
width: 577rpx;
height: 41rpx;
display: flex;
flex-direction: column;
padding: 16rpx 24rpx;
gap: 20rpx;
// border: 1px solid #000;
border-radius: 179px;
// background: linear-gradient(263deg, #ffea7b 0%, #ece5bf 137%);
.immediately {
font-family: PingFangSC;
font-size: 28rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #3a3d55;
}
}
.discount {
position: absolute;
right: 0;
top: 0;
width: 50rpx;
height: 20rpx;
/* 自动布局 */
display: flex;
align-items: center;
padding: 8rpx 24rpx;
gap: 4rpx;
border-radius: 0 24rpx 0 24rpx;
background: linear-gradient(270deg, #ff3c4a 0%, #f4c24e 107%);
.break {
font-family: PingFangSC;
font-size: 20rpx;
font-weight: 500;
letter-spacing: normal;
color: #ffffff;
}
}
.use-record {
position: absolute;
right: 34rpx;
top: 27rpx;
display: flex;
align-items: center;
// border: 1px solid #000;
width: 142rpx;
height: 46rpx;
.record {
font-family: PingFang SC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
display: flex;
align-items: center;
letter-spacing: normal;
color: #ffffff;
margin-right: 10rpx;
}
.s {
width: 11rpx;
height: 18rpx;
}
}
}
}
.select-amount {
padding: 20rpx;
border-radius: 30rpx 30rpx 0 0;
background: #ffffff;
.select {
font-family: PingFangSC;
font-size: 32rpx;
font-weight: 500;
color: #272427;
}
.select-recharge {
display: flex;
flex-wrap: wrap;
margin-bottom: 10rpx;
justify-content: space-between;
.recharge {
width: 125rpx;
height: 76rpx;
margin: 10rpx 0;
/* 自动布局 */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 32rpx 44rpx;
gap: 16rpx;
z-index: 0;
border-radius: 16rpx;
background: #F8F8F8;
border: 1px solid #F8F8F8;
.s {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #9B939A;
}
.first {
display: flex;
align-items: center;
gap: 8rpx;
.integral-icon {
width: 24rpx;
height: 24rpx;
}
.f {
font-family: PingFangSC;
font-size: 40rpx;
font-weight: 500;
letter-spacing: normal;
color: #272427;
}
.t {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: 500;
line-height: 24rpx;
letter-spacing: normal;
color: #272427;
margin-left: 2rpx;
}
}
}
.recharge2 {
width: 125rpx;
height: 76rpx;
margin: 10rpx 0;
/* 自动布局 */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 32rpx 44rpx;
gap: 16rpx;
z-index: 0;
border-radius: 16rpx;
background: #fee9f3;
border: 1px solid #FF0097;
.s {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #FF0097;
}
.first {
.f {
font-family: PingFangSC;
font-size: 40rpx;
font-weight: 500;
letter-spacing: normal;
color: #FF0097;
}
.t {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: 500;
line-height: 24rpx;
letter-spacing: normal;
color: #FF0097;
margin-left: 2rpx;
}
}
}
}
.recharge-method {
display: flex;
flex-direction: column;
.wechat {
height: 58px;
/* 自动布局 */
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
border-radius: 8px;
box-sizing: border-box;
border: 1px solid #ececec;
margin-top: 30rpx;
}
.select {
display: flex;
align-items: center;
.w {
width: 48rpx;
height: 48rpx;
}
.x {
font-family: PingFangSC;
font-size: 30rpx;
font-weight: 500;
line-height: 16px;
letter-spacing: normal;
color: #3d3d3d;
display: flex;
margin-left: 16rpx;
}
}
.not-selected {
width: 36rpx;
height: 36rpx;
}
}
.remk-reservation {
position: fixed;
width: 100%;
bottom: 0;
left: 0;
height: 150rpx;
background: #fff;
display: flex;
padding-top: 12rpx;
// align-items: center;
justify-content: center;
box-shadow: 0px 0px 22px 0px rgba(0, 0, 0, 0.1);
border-radius: 16px 16px 0px 0px;
.rese-bom {
width: calc(100vw - 48rpx);
height: 48px;
/* 自动布局 */
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
// gap: 10px;
z-index: 0;
border-radius: 100px;
box-sizing: border-box;
/* 主色1 */
border: 1px solid #FE019B;
background-color: #FE019B;
color: #fff;
.l {
font-family: PingFang SC;
font-size: 16px;
font-weight: normal;
line-height: 16px;
text-align: center;
letter-spacing: normal;
/* 主色1 */
color: #FE019B;
}
}
}
}
</style>

View File

@ -0,0 +1,334 @@
<template>
<view class="home-share">
<image
class="home-comm"
src="https://activity.wagoo.live/home-comment.png"
/>
<view class="tab-view">
<view
class="tab-item"
v-for="item in tabList"
:key="item.value"
@click.stop="selectOrderStatus(item)"
>
<text
class="app-fc-normal"
:class="{ 'tab-text ali-puhui-bold': item.value === currentStatus }"
>
{{ item.label }}
</text>
<view
class="tab-line"
:class="{ 'tab-selected-line': item.value === currentStatus }"
/>
</view>
</view>
<view class="remark-layer">
<view class="remark-item" v-for="(item,index) in remarkList"
:key="index" >
<view class="flex-row-start remark-item-info">
<image v-if="item.head_pic" class="info-avator" :src="item.head_pic" />
<image v-else class="info-avator" src="https://activity.wagoo.live/home-head.png" />
<view class="info-center">
<view class="app-fc-main">{{ item.nick_name || "" }}</view>
<view class="remark-comment">
<view class="remak-solo">
<text class="z">超赞</text>
</view>
<view class="flex-row-start star-list">
<image
v-for="item1 in [1, 2, 3, 4, 5]"
:key="item1"
class="star-item"
:src="
item1 <= item.star
? require('@/static/images/star.png')
: require('@/static/images/star_dark.png')
"
/>
</view>
</view>
</view>
<text class="fs-24 app-fc-normal">
{{ formatTime(item.add_time) }}
</text>
</view>
<view class="remark-bottom">
<text class="fs-26 app-fc-normal">
{{ item.content || "" }}
</text>
<view class="flex-row-start remark-imgs">
<image
v-for="(item2, i) in item.pinglun_pic"
:key="item2"
class="remark-img"
:class="{ 'remark-img-right': i % 3 === 2 }"
:src="item2"
@click="preview(item2,item.pinglun_pic)"
mode="aspectFill"
/>
</view>
</view>
</view>
</view>
<view class="remk-reservation">
<view class="rese-bom" @click="
jumpTo('/pages/client/index/index?activePageId=reservationPage')
">
<text class="l">立即预约</text>
</view>
</view>
</view>
</template>
<script>
import moment from "moment";
export default {
data() {
return {
remarkList: [],
remarkId: "4",
currentStatus: "1",
tabList: [
{
value: "1",
label: "好评分享",
},
],
};
},
onLoad() {
const eventChannel = this.getOpenerEventChannel();
eventChannel.on('favorableInfo', (data) => {
this.getRemarkDetails(data);
// console.log(data,'111')
})
},
methods: {
jumpTo(url) {
uni.navigateTo({
url,
});
},
getRemarkDetails() {
this.remarkList = [];
},
formatTime(time) {
return moment(time * 1000).format("YYYY/MM/DD");
},
preview(item,v) {
uni.previewImage({
urls:v,
current: item,
});
},
selectOrderStatus(status) {
if (this.currentStatus === status.value) {
return;
}
this.currentStatus = status.value;
this.isLoading = true;
this.pageNumber = 1;
this.isRefresh = false;
this.isLoadMore = false;
this.isNoMore = false;
this.orderList = [];
// this.getData();
},
},
};
</script>
<style lang="scss" scoped>
.home-share {
width: 100%;
height: 100%;
.remk-reservation{
position: fixed;
width: 100%;
bottom: 0;
left: 0;
height:150rpx;
background: #fff;
display: flex;
padding-top: 24rpx;
// align-items: center;
justify-content: center;
.rese-bom{
width: 351px;
height: 48px;
/* 自动布局 */
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
// gap: 10px;
z-index: 0;
border-radius: 100px;
box-sizing: border-box;
/* 主色1 */
border: 1px solid #FE019B;
.l{
font-family: PingFang SC;
font-size: 16px;
font-weight: normal;
line-height: 16px;
text-align: center;
letter-spacing: normal;
/* 主色1 */
color: #FE019B;
}
}
}
.remark-layer{
padding-bottom: 160rpx;
.remark-item {
padding: 40rpx 36rpx;
background: #fff;
border-radius: 20rpx;
box-sizing: border-box;
margin: 0 auto;
width: 100%;
// height: 100%;
.remark-item-info {
margin-bottom: 20rpx;
align-items: stretch;
.info-avator {
width: 100rpx;
height: 100rpx;
border-radius: 100rpx;
}
.info-center {
flex: 1;
margin: 0 20rpx;
overflow: hidden;
.app-fc-main {
font-family: PingFangSC;
font-size: 28rpx;
font-weight: 500;
text-align: center;
letter-spacing: normal;
color: #333333;
text-align: left;
}
}
.remark-comment {
display: flex;
align-content: center;
.remak-solo {
margin: 28rpx 10rpx 0 0 ;
width: 48rpx;
height: 28rpx;
/* 自动布局 */
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 4rpx;
border-radius: 6rpx;
background: #fee9f3;
.z {
font-family: PingFangSC;
font-size: 20rpx;
font-weight: normal;
line-height: 20rpx;
letter-spacing: normal;
/* 主色1 */
color: #fe019b;
}
}
.star-item {
width: 30rpx;
height: 28rpx;
margin-right: 6rpx;
margin-top: 22rpx;
}
}
}
.remark-bottom{
// border: 1px solid #000;
width: 560rpx;
margin-left: 122rpx;
.remark-imgs {
width:560rpx;
margin-top: 20rpx;
flex-wrap: wrap;
.remark-img {
width: 85px;
height: 85px;
// width: calc((100vw - 32rpx * 2 - 24rpx * 2 - 14rpx * 2) / 3);
// height: calc((100vw - 32rpx * 2 - 24rpx * 2 - 14rpx * 2) / 3);
border-radius: 20rpx;
margin-right: 14rpx;
margin-bottom: 14rpx;
&.remark-img-right {
margin-right: 0;
}
}
}
}
}
}
.home-comm {
width: 95%;
height: 267rpx;
margin: 20rpx auto;
display: block;
}
.tab-view {
width: 95%;
height:87rpx;
display: flex;
// align-items: center;
// background-color: #ffffff;
.tab-item {
display: flex;
margin-left: 34rpx;
// flex: 1;
flex-direction: column;
align-items: center;
.app-fc-normal {
color: #666666;
font-size: 32rpx;
}
.tab-text {
font-size: 32rpx;
color: #333333;
// font-weight: bold;
// color: $app_fc_main;
}
}
.tab-line {
transform: translateX(-50%);
width: 24rpx;
height: 10rpx;
border-radius: 12rpx;
// width: 30rpx;
// height: 6rpx;
background-color: transparent;
margin-top: 8rpx;
}
.tab-selected-line {
background-color: $app_color_main;
}
}
}
</style>

View File

@ -0,0 +1,780 @@
<template>
<view class="container" @touchmove.stop.prevent>
<view class="tab-view">
<view
class="tab-item"
v-for="item in mainTabList"
:key="item.value"
@click.stop="selectMainTab(item)"
>
<text
class="fs-28 app-fc-normal"
:class="{ 'tab-text ali-puhui-bold': item.value == currentMainTab }"
>
{{ item.label }}
</text>
<view
class="tab-line"
:class="{ 'tab-selected-line': item.value == currentMainTab }"
/>
</view>
</view>
<view style="background: #fff" v-show="recharge && currentMainTab === 'balance'">
<view class="scroll">
<view class="card-roll">
<image class="file-pet" src="https://activity.wagoo.live/myRechargetwo.png" mode="widthFix" />
<view class="amount">
<text class="surplus">{{ walletList.balance }}</text>
</view>
<!-- <view class="effectiveTime">
<text class="surplus">有效期</text>
<text class="surplus">2025/07/01至2026/07/01</text>
</view> -->
<view class="recharge" @click="jumpTo('/pages/client/recharge/index')">
<!-- <text class="immediately">立即充值</text> -->
</view>
<view class="use-record" @click="
jumpTo(
`/pages/client/recharge/use-list?wallet_id=${walletList.id}&user_id=${user_id}`
)
">
<!-- <text class="record">使用记录</text> -->
<!-- <image class="s" src="@/static/images/jt.png" /> -->
</view>
<!-- <view class="discount">
<text class="break">9.8</text>
</view> -->
</view>
</view>
<view class="select-amount">
<text class="select">请选择充值金额</text>
<view class="select-recharge">
<view @click.stop="giftOrderStatus(item)" v-for="item in giftList" :key="item.id">
<view class="recharge" :class="{ recharge2: item.id === giftStatus }">
<view class="first">
<text class="f">{{ item.amount }}</text>
<text class="t"> </text>
</view>
<text class="s">{{ item.bonus_ratio }} </text>
</view>
</view>
</view>
<text class="select">请选择充值方式</text>
<view class="recharge-method">
<view class="wechat" @click.stop="selectOption1">
<view class="select">
<image class="w" src="@/static/images/wx.png" mode="widthFix" />
<text class="x">微信</text>
</view>
<image v-if="selected1" class="not-selected"
src="@/static/images/w.png" mode="widthFix" />
<image v-if="selected2" class="not-selected" src="@/static/images/y.png" mode="widthFix" />
</view>
<!-- <view class="wechat">
<view class="select">
<image class="w" src="@/static/images/zf.png" mode="widthFix" />
<text class="x">支付宝</text>
</view>
<image
v-if="selected3"
class="not-selected"
@click.stop="selectOption2"
src="@/static/images/w.png"
mode="widthFix"
/>
<image
v-if="selected4"
class="not-selected"
src="@/static/images/y.png"
mode="widthFix"
/>
</view> -->
</view>
<view class="remk-reservation">
<view class="rese-bom" @click="rechargeNow()">
<text class="l" style="color: #fff;">立即充值</text>
</view>
</view>
<!-- <view @click="rechargeNow()" class="recharge-now">
<text class="r">立即充值</text>
</view> -->
</view>
</view>
<!-- 积分组件 -->
<points-content v-show="currentMainTab === 'points'" :user_id="user_id" />
</view>
</template>
<script>
import {
rechargeStatus
} from "@/pageHome/constants/home";
import {
walletWxpay,
userWllet,
walletRechagre
} from "../../../api/login";
import {
serviceCouponCreateOrder,
serviceCouponOrderPay,
} from "../../../api/coupon";
import PointsContent from "./components/points-content.vue";
export default {
components: {
PointsContent,
},
data() {
return {
tabList: rechargeStatus,
mainTabList: [
{
value: "balance",
label: "余额",
},
{
value: "points",
label: "积分",
},
],
currentMainTab: "balance",
giftLabel: "",
giftList: [],
walletList: {},
paymentList: {},
currentStatus: "1",
giftStatus: "",
selectedOption: "",
selected1: true,
selected2: false,
selected3: true,
selected4: false,
recharge: true,
record: false,
balance: "",
user_id: ""
};
},
onLoad(options) {
this.user_id = options.user_id;
// 如果传递了 tab 参数,自动选中对应的标签
if (options.tab === 'points') {
this.currentMainTab = 'points';
}
this.buyService(options.user_id);
this.walletRechagre()
},
methods: {
async rechargeNow() {
if (!this.giftLabel) {
uni.showToast({
title: "请选择充值金额",
icon: "none",
});
return;
}
if (this.selected1) {
uni.showToast({
title: "请选择充值方式",
icon: "none",
});
return;
}
try {
const data = {
wallet_id: this.walletList.id,
user_id: +this.user_id,
total_fee: this.giftLabel,
type: 1
};
walletWxpay(data).then((res) => {
// 使用获取的支付参数进行支付
uni.requestPayment({
timeStamp: res.data.timeStamp, // 确保这些字段都正确
nonceStr: res.data.nonceStr,
package: res.data.package,
signType: res.data.signType,
paySign: res.data.paySign,
success: (res) => {
this.buyService(this.user_id);
// console.log('支付成功:', res);
},
fail: (err) => {
console.error("支付失败:", err);
},
});
});
} catch (error) {
console.error("捕获到的错误:", error);
}
},
buyService(user_id) {
userWllet(user_id).then((res) => {
this.walletList = res.data;
});
},
walletRechagre() {
walletRechagre().then((res) => {
this.giftList = res.data
});
},
selectOption1() {
this.selected1 = false;
this.selected2 = true;
this.selected3 = true;
this.selected4 = false;
},
selectOption2() {
this.selected1 = true;
this.selected2 = false;
this.selected3 = false;
this.selected4 = true;
},
giftOrderStatus(status) {
// console.log(status, "-=-");
if (this.giftStatus === status.id) {
return;
}
this.giftStatus = status.id;
this.giftLabel = status.amount;
},
jumpTo(url) {
uni.navigateTo({
url,
});
},
selectMainTab(item) {
if (this.currentMainTab === item.value) {
return;
}
this.currentMainTab = item.value;
},
selectOrderStatus(status) {
if (status.value == 1) {
(this.recharge = true), (this.record = false);
} else {
(this.recharge = false), (this.record = true);
}
if (this.currentStatus === status.value) {
return;
}
this.currentStatus = status.value;
},
},
};
</script>
<style lang="scss" scoped>
.container {
width: 746rpx;
height: 100vh;
background: #fff;
.tab-view {
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
background-color: #ffffff;
.tab-item {
display: flex;
magin-bottom: 20rpx;
padding: 20rpx 0;
flex-direction: column;
align-items: center;
flex: 1;
.tab-text {
color: $app_fc_main;
}
}
.tab-line {
width: 24rpx;
height: 10rpx;
border-radius: 12rpx;
background-color: transparent;
margin-top: 8rpx;
}
.tab-selected-line {
background-color: $app_color_main;
}
}
.scroll {
display: flex;
margin-top: 20rpx;
.card-roll {
position: relative;
margin: 0 auto;
.file-pet {
width: 710rpx;
height: 300rpx;
margin-left: 10rpx;
}
.card {
font-family: PingFangSC;
font-size: 40rpx;
color: #ffffff;
}
.amount {
position: absolute;
left: 40rpx;
top: 82rpx;
.surplus {
font-family: PingFangSC;
font-size: 32px;
font-weight: 500;
line-height: 32px;
letter-spacing: normal;
color: #ffffff;
}
}
.effectiveTime {
display: flex;
margin-top: 16rpx;
.surplus {
font-family: PingFang SC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #ffffff;
}
.first {
font-family: PingFang SC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #ffffff;
}
}
.recharge {
position: absolute;
left: 35rpx;
top: 185rpx;
width: 577rpx;
height: 41rpx;
display: flex;
flex-direction: column;
padding: 16rpx 24rpx;
gap: 20rpx;
// border: 1px solid #000;
border-radius: 179px;
// background: linear-gradient(263deg, #ffea7b 0%, #ece5bf 137%);
.immediately {
font-family: PingFangSC;
font-size: 28rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #3a3d55;
}
}
.discount {
position: absolute;
right: 0;
top: 0;
width: 50rpx;
height: 20rpx;
/* 自动布局 */
display: flex;
align-items: center;
padding: 8rpx 24rpx;
gap: 4rpx;
border-radius: 0 24rpx 0 24rpx;
background: linear-gradient(270deg, #ff3c4a 0%, #f4c24e 107%);
.break {
font-family: PingFangSC;
font-size: 20rpx;
font-weight: 500;
letter-spacing: normal;
color: #ffffff;
}
}
.use-record {
position: absolute;
right: 34rpx;
top: 27rpx;
display: flex;
align-items: center;
// border: 1px solid #000;
width: 142rpx;
height: 46rpx;
.record {
font-family: PingFang SC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
display: flex;
align-items: center;
letter-spacing: normal;
color: #ffffff;
margin-right: 10rpx;
}
.s {
width: 11rpx;
height: 18rpx;
}
}
}
}
.select-amount {
padding: 20rpx;
border-radius: 30rpx 30rpx 0 0;
background: #ffffff;
.select {
font-family: PingFangSC;
font-size: 32rpx;
font-weight: 500;
color: #272427;
}
.select-recharge {
display: flex;
flex-wrap: wrap;
margin-bottom: 10rpx;
justify-content: space-between;
.recharge {
width: 125rpx;
height: 76rpx;
margin: 10rpx 0rpx;
/* 自动布局 */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 32rpx 44rpx;
gap: 16rpx;
z-index: 0;
border-radius: 16rpx;
background: #F8F8F8;
border: 1px solid #F8F8F8;
.s {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #272427;
}
.first {
display: flex;
align-items: center;
.f {
font-family: PingFangSC;
font-size: 40rpx;
font-weight: 500;
line-height: 36rpx;
letter-spacing: normal;
color: #272427;
}
.t {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: 500;
line-height: 24rpx;
letter-spacing: normal;
color: #272427;
margin-left: 2rpx;
}
}
}
.recharge2 {
width: 125rpx;
height: 76rpx;
margin: 10rpx 0rpx;
/* 自动布局 */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 32rpx 44rpx;
gap: 16rpx;
z-index: 0;
border-radius: 16rpx;
background: #fee9f3;
border: 1px solid #FF0097;
.s {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #FF0097;
}
.first {
.f {
font-family: PingFangSC;
font-size: 40rpx;
font-weight: 500;
line-height: 36rpx;
letter-spacing: normal;
color: #FF0097;
}
.t {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: 500;
line-height: 24rpx;
letter-spacing: normal;
color: #FF0097;
margin-left: 2rpx;
}
}
}
}
.recharge-method {
display: flex;
flex-direction: column;
.wechat {
height: 58px;
/* 自动布局 */
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
border-radius: 8px;
box-sizing: border-box;
border: 1px solid #ececec;
margin-top: 30rpx;
}
.select {
display: flex;
align-items: center;
.w {
width: 48rpx;
height: 48rpx;
}
.x {
font-family: PingFangSC;
font-size: 30rpx;
font-weight: 500;
line-height: 16px;
letter-spacing: normal;
color: #3d3d3d;
display: flex;
margin-left: 16rpx;
}
}
.not-selected {
width: 36rpx;
height: 36rpx;
}
}
.remk-reservation {
position: fixed;
width: 100%;
bottom: 0;
left: 0;
height: 150rpx;
background: #fff;
display: flex;
padding-top: 12rpx;
// align-items: center;
justify-content: center;
box-shadow: 0px 0px 22px 0px rgba(0, 0, 0, 0.1);
border-radius: 16px 16px 0px 0px;
.rese-bom {
width: calc(100vw - 48rpx);
height: 48px;
/* 自动布局 */
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
// gap: 10px;
z-index: 0;
border-radius: 100px;
box-sizing: border-box;
/* 主色1 */
border: 1px solid #FE019B;
background-color: #FE019B;
color: #fff;
.l {
font-family: PingFang SC;
font-size: 16px;
font-weight: normal;
line-height: 16px;
text-align: center;
letter-spacing: normal;
/* 主色1 */
color: #FE019B;
}
}
}
// .recharge-now {
// position: fixed;
// bottom: 100rpx;
// left: 20rpx;
// // margin: 133rpx 0 0 7rpx;
// width: 89%;
// height: 48rpx;
// /* 自动布局 */
// display: flex;
// flex-direction: column;
// justify-content: center;
// align-items: center;
// padding: 24rpx;
// gap: 20rpx;
// align-self: stretch;
// z-index: 0;
// border-radius: 200rpx;
// border: 1px solid #fe019b;
// /* 主色1 */
// // background: #fe019b;
// .r {
// font-family: PingFang SC;
// font-size: 32rpx;
// font-weight: normal;
// line-height: 32rpx;
// text-align: center;
// letter-spacing: normal;
// /* 主色1 */
// color: #fe019b;
// }
// }
}
.recharge-record {
margin: 32rpx auto;
width: 654rpx;
height: 1127rpx;
/* 自动布局 */
display: flex;
flex-direction: column;
// align-items: center;
padding: 0 28rpx 28rpx 28rpx;
gap: 32rpx;
border-radius: 30rpx 30rpx 0 0;
background: #ffffff;
.card-layer {
height: 106rpx;
border-bottom: 1px solid #ececec;
&:last-of-type {
border-bottom: none;
}
.card {
display: flex;
justify-content: space-between;
margin-top: 30rpx;
.k {
font-family: PingFangSC;
font-size: 28rpx;
font-weight: 500;
line-height: 28rpx;
letter-spacing: normal;
color: #3d3d3d;
}
}
.time {
display: flex;
justify-content: space-between;
margin-top: 30rpx;
.time-t {
display: flex;
.t {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #9b939a;
}
.i {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #9b939a;
margin-left: 6rpx;
}
}
.t {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #9b939a;
}
}
}
}
.occupation-map {
margin: 250rpx auto;
width: 160px;
height: 379.5rpx;
/* 自动布局 */
display: flex;
flex-direction: column;
padding: 0;
gap: 40rpx;
.service-head-img {
width: 320rpx;
height: 299.5rpx;
}
.z {
font-family: PingFang SC;
font-size: 32rpx;
font-weight: normal;
line-height: 40rpx;
text-align: center;
letter-spacing: normal;
color: #726e71;
}
}
}
</style>

View File

@ -0,0 +1,240 @@
<template>
<view class="send-friends">
<image class="diamond" src="https://activity.wagoo.live/poster.png" mode="widthFix" />
<view class="my-friend">
<text class="y">新人专属福利</text>
<view class="my-reward">
<text class="m">首单更</text> <text class="j">优惠</text>
</view>
<view class="my-login" @click="LoginNow">
<text class="d">立即登录领取</text>
</view>
<view class="my-step">
<view class="c" /> <text class="step">新人专享劵</text>
<view class="c" />
</view>
</view>
<view class="order-list-content">
<list-page-temp :requestData="{ use_status: tabsList[curTabIndex].id, type: 0 }"
:getDataPromise="getOwnCouponData" :reloadFlag="reloadFlag"
:listExtraFields="{ nearDisabled: curTabIndex === 2 }">
<template v-slot:item="{ data }">
<coupon-item :data="data" :showOptBtn="false" :disabled="data.use_status === 2"
:showCountDown="data.nearDisabled && data.daojishi">
<!-- <image
v-if="data.use_status === 2"
slot="status"
class="use-icon"
src="./static/coupon_used.png"
/> -->
</coupon-item>
</template>
</list-page-temp>
</view>
</view>
</template>
<script>
import TabsList from "@/components/TabsList.vue";
import ListPageTemp from "@/components/ListPageTemp.vue";
import CouponItem from "@/components/coupon/CouponItem";
import { getOwnCouponData } from "@/api/coupon";
export default {
components: {
TabsList,
ListPageTemp,
CouponItem,
},
computed: {
tabsList() {
return [
{
name: "",
id: 1,
},
];
},
},
onLoad(options) {
if (options?.referrerID) {
this.$store.dispatch('user/setReferrerID', Number(options.referrerID) || 0);
}
},
data() {
return {
curTabIndex: 0,
reloadFlag: 0,
userId: ''
}
},
options: {
styleIsolation: "shared",
},
onShow() {
this.reloadFlag = Math.random();
},
methods: {
getOwnCouponData,
onTabChange(item, index) {
this.curTabIndex = index;
},
LoginNow() {
uni.navigateTo({
url: `/pages/client/auth/index`
});
},
jumpTo(url) {
uni.navigateTo({
url,
});
},
},
}
</script>
<style lang="scss" scoped>
.send-friends {
width: 100%;
height: 100%;
.diamond {
width: 100%;
height: 676rpx;
}
.my-friend {
margin-top: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
height: 140rpx;
gap: 32rpx;
.y {
font-family: Alibaba PuHuiTi 2.0;
font-size: 24px;
font-weight: 500;
line-height: 24px;
text-align: center;
letter-spacing: normal;
color: #3D3D3D;
}
.my-reward {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.j {
font-family: PingFang SC;
font-size: 16px;
font-weight: normal;
line-height: 16px;
text-align: center;
letter-spacing: normal;
/* 首单更 */
color: #FE019B
}
}
.m {
font-family: PingFang SC;
font-size: 16px;
font-weight: normal;
line-height: 16px;
text-align: center;
letter-spacing: normal;
/* 首单更 */
color: #3D3D3D
}
.my-login {
width: 207px;
height: 36px;
/* 自动布局 */
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
gap: 4px;
z-index: 0;
border-radius: 100px;
/* 主色1 */
background: #FE019B;
.d {
font-family: PingFangSC;
font-size: 16px;
font-weight: 500;
line-height: 16px;
text-align: center;
letter-spacing: normal;
color: #FFFFFF;
}
}
.my-step {
display: flex;
align-items: center;
justify-content: center;
margin-top: 26rpx;
.c {
width: 120rpx;
height: 0;
z-index: 0;
border-top: 1rpx solid #333333;
}
.step {
width: 138rpx;
height: 28rpx;
z-index: 1;
font-family: PingFang SC;
font-size: 28rpx;
font-weight: normal;
line-height: 28rpx;
letter-spacing: normal;
color: #333333;
margin: 0 30rpx;
}
}
}
.order-list-content {
flex: 1;
overflow: hidden;
padding-left: 32rpx;
margin-top: 190rpx;
::v-deep {
.coupon-price {
color: $app_fc_alarm;
}
.coupon-item {
.circle {
background: #f7f8fa;
}
}
}
.use-icon {
width: 100rpx;
height: 100rpx;
}
}
}
</style>

View File

@ -0,0 +1,384 @@
<template>
<view class="send-welfare">
<image class="diamond" src="https://activity.wagoo.live/poster.png" mode="widthFix" />
<view class="my-friend">
<view class="my-reward">
<text class="y">邀请好友获得</text> <text class="j">奖励</text>
</view>
<text class="m">每成功邀1个新用户达成任务,可重复获得奖励</text>
</view>
<view class="share-Pictures">
<image
class="container"
src="https://activity.wagoo.live/container.png"
mode="widthFix"
/>
<button open-type="share" class="left" />
<!-- <view @click="jumpTo('/pages/client/recharge/share-pictures')" class="right" /> -->
</view>
<view class="my-achievements">
<view class="my-title">
<view class="my-step">
<view class="c" /> <text class="step">我的成就</text> <view class="c"
/></view>
<view class="my-invite">
<view class="invite">
<text class="y">已成功邀请</text>
<text class="people">{{orderInfo.user_count}} </text>
</view>
<!-- <view class="invite">
<text class="y">已获得积分</text>
<text class="people">{{orderInfo.points}} </text>
</view> -->
<view class="invite">
<text class="y">已获得优惠券</text>
<text class="people">{{orderInfo.coupon_count}} </text>
</view>
</view>
</view>
</view>
<view class="invitation-record">
<view class="my-title">
<view class="my-step">
<view class="c" /> <text class="step">邀请记录</text> <view class="c"
/></view>
<!-- <view v-for="(item, index) in orderInfo.users"
:key="index"> -->
<view class="drop-down" v-if="orderInfo.users && orderInfo.users.length" >
<view class="my-invite" v-for="(item, index) in orderInfo.users"
:key="index">
<view class="left">
<image
:src="item.head_pic"
mode="widthFix"
class="head-img"
/>
<view class="my-name">
<text class="y">{{item.nick_name}}</text>
<!-- <text class="y">ID: 101067</text> -->
</view>
</view>
<text class="right">{{item.add_time}}</text>
</view>
</view>
<text v-else class="invite-ser">空空如也,赶快去邀请吧</text>
<!-- </view> -->
</view>
</view>
<view class="bottom" />
</view>
</template>
<script>
import { userShare } from '../../../api/login';
export default {
data() {
return {
orderInfo: {}
};
},
computed: {
userInfo() {
return this.$store.state?.user?.userInfo || {};
},
memberId() {
const u = this.userInfo;
return (u && (u.userID || u.user_id)) ? String(u.userID || u.user_id) : '';
}
},
onLoad() {
this.getData(this.memberId);
},
onShareAppMessage(e) {
// console.log(e,'-=-=-')
if (e.from === 'button') {
// let obj = e.target.dataset.obj // 获取 button 组件 自定义的data-obj值
// console.log(obj, '查看分享的输出');
return {
title: 'wagoo', // 标题
imageUrl:'https://activity.wagoo.live/poster.png', // 封面图
path: `pages/client/recharge/invite-friends?referrerID=${this.memberId}` // 地址以及参数
};
}
},
methods: {
getData(member_id) {
userShare(member_id).then((res) => {
this.orderInfo = res?.data;
}).catch(() => {
})
},
jumpTo(url) {
uni.navigateTo({
url,
});
},
},
};
</script>
<style lang="scss" scoped>
.send-welfare {
width: 100%;
height: 100%;
.diamond {
width: 100%;
height: 676rpx;
}
.my-friend{
margin-top:30rpx;
display: flex;
flex-direction: column;
align-items: center;
height: 140rpx;
gap: 32rpx;
.my-reward{
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.y{
font-family: Alibaba PuHuiTi 2.0;
font-size: 48rpx;
font-weight: 500;
line-height: 48rpx;
text-align: center;
letter-spacing: normal;
/* 邀请好友得 */
color: #3D3D3D
}
.j{
font-family: Alibaba PuHuiTi 2.0;
font-size: 48rpx;
font-weight: 500;
line-height: 48rpx;
text-align: center;
letter-spacing: normal;
/* 邀请好友得 */
color: #C13030
}
}
.m{
font-family: Alibaba PuHuiTi 2.0;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
text-align: center;
letter-spacing: normal;
color: #3D3D3D;
}
}
.share-Pictures{
position: relative;
.container {
width: 750rpx;
height: 384px;
}
.left{
width: 90%;
height: 85rpx;
border-radius: 100rpx;
position: absolute;
left: 32rpx;
top: 7rpx;
background: none;
border: none;
}
wx-button:after{
border: none;
}
.right{
width: 332rpx;
height: 85rpx;
border-radius: 100rpx;
position: absolute;
right: 18rpx;
top: 7rpx;
}
}
.my-achievements {
margin: 20rpx auto;
width: 656rpx;
height: 248rpx;
/* 自动布局 */
display: flex;
flex-direction: column;
align-items: center;
padding: 32rpx 24rpx;
gap: 32rpx;
z-index: 2;
border-radius: 24rpx;
background: #ffffff;
.my-title {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.my-step {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 50rpx;
.c {
width: 120rpx;
height: 0;
z-index: 0;
border-top: 1rpx solid #333333;
}
.step {
width: 117rpx;
height: 28rpx;
z-index: 1;
font-family: PingFang SC;
font-size: 28rpx;
font-weight: normal;
line-height: 28rpx;
letter-spacing: normal;
color: #333333;
margin: 0 30rpx;
}
}
.my-invite {
margin-top: 30rrpx;
display: flex;
justify-content: space-around;
.invite {
display: flex;
flex-direction: column;
gap: 40rpx;
.y {
font-family: PingFangSC;
font-size: 28rpx;
font-weight: normal;
line-height:28rpx;
text-align: center;
letter-spacing: normal;
color: #3d3d3d;
}
.people {
font-family: PingFangSC;
font-size: 36rpx;
font-weight: normal;
line-height: 36rpx;
text-align: center;
letter-spacing: normal;
/* 错误色/错误色 */
color: #ea0000;
}
}
}
}
}
.invitation-record {
margin: 20rpx auto;
width:656rpx;
height: 248rpx;
/* 自动布局 */
display: flex;
flex-direction: column;
align-items: center;
padding: 32rpx 24rpx;
gap: 32rpx;
z-index: 2;
border-radius: 24rpx;
background: #ffffff;
.my-title {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.my-step {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
.c {
width: 120rpx;
height: 0;
z-index: 0;
border-top: 0.5px solid #333333;
}
.step {
width: 112rpx;
height: 28rpx;
z-index: 1;
font-family: PingFang SC;
font-size: 28rpx;
font-weight: normal;
line-height: 28rpx;
letter-spacing: normal;
color: #333333;
margin: 0 30rpx;
}
}
.drop-down {
height: 280rpx;
overflow: auto;
.my-invite {
margin-top: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.head-img {
width: 56rpx;
height: 56rpx;
z-index: 0;
box-sizing: border-box;
border: 0 solid #ffffff;
border-radius: 50%;
}
.my-name {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
.y {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #9b939a;
margin-left: 10rpx;
}
}
}
.right {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #9b939a;
}
}
}
.invite-ser{
margin: 100rpx auto;
display: block;
color: #707070;
font-size: 24rpx;
}
}
}
.bottom {
height: 50rpx;
visibility: hidden;
}
}
</style>

View File

@ -0,0 +1,917 @@
<template>
<view>
<view class="tab-view">
<view
class="tab-item"
v-for="item in tabList"
:key="item.value"
@click.stop="selectOrderStatus(item)"
>
<text
class="fs-34 app-fc-normal"
:class="{ 'tab-text ali-puhui-bold': item.value == currentStatus }"
>
{{ item.label }}
</text>
<view
class="tab-line"
:class="{ 'tab-selected-line': item.value == currentStatus }"
/>
</view>
</view>
<view class="scroll-white" v-if="scrollLeft">
<view class="scroll" v-for="(item , index) in platinum"
:key="index">
<image
src="https://activity.wagoo.live/member2.png"
mode="widthFix"
class="service-head-img"
@click="cardDetails(item,1)"
/>
<!-- <image
@click="jumpTo(`/pages/richText/member-interests`)"
src="https://activity.wagoo.live/xf-1.png"
mode="widthFix"
class="head-img"
/> -->
<image v-if="item.bounding_pets[0].pet_avatar" :src="item.bounding_pets[0].pet_avatar" mode="widthFix" class="add-img" />
<image
v-else
@click="clickElasticLayer(item)"
src="https://activity.wagoo.live/bd-3.png"
mode="widthFix"
class="add-img"
/>
<view class="number">
<text class="n">编号</text>
<text class="n">{{item.card_number.substring(0, 16)}}</text>
</view>
<view class="immediately">
<text class="n">有效期至:</text>
<text class="n">{{item.expired_date.substring(0, 10)}}</text>
</view>
<view class="startDate">
<text class="n">购卡日期:</text>
<text class="n">{{item.effective_date.substring(0, 10)}}</text>
</view>
<!-- <view class="card-roll">
</view> -->
<!-- <view class="recharge" @click="jumpTo('/pages/client/recharge/index')">
<text class="immediately">立即充值</text>
</view> -->
</view>
<view class="scroll" v-for="(item , index) in blackGold"
:key="index">
<image
src="https://activity.wagoo.live/member1.png"
mode="widthFix"
class="service-head-img"
@click="cardDetails(item,2)"
/>
<!-- <image
@click="jumpTo(`/pages/richText/member-interests`)"
src="https://activity.wagoo.live/xf-3.png"
mode="widthFix"
class="head-img"
/> -->
<view class="imgList" >
<view class="img" v-for="(it, inx) in item.bounding_pets"
:key="inx">
<image
:src="it.pet_avatar"
mode="aspectFill"
class="add-i"
/>
</view>
<image
v-if="item.bounding_pets.length <=5"
src="https://activity.wagoo.live/bd-1.png"
mode="widthFix"
class="add-img2"
@click="selectelaList(item)"
/>
</view>
<view class="number1">
<text class="n">编号</text>
<text class="n">{{item.card_number.substring(0, 16)}}</text>
</view>
<view class="immediately1">
<text class="n">有效期至:</text>
<text class="n">{{item.expired_date.substring(0, 10)}}</text>
</view>
<view class="startDate1">
<text class="n">购卡日期:</text>
<text class="n">{{item.effective_date.substring(0, 10)}}</text>
</view>
<!-- <text class="number1">{{item.card_number.substring(0, 8)}}</text>
<text class="immediately1">{{item.effective_date.substring(0, 10)}}</text>
<text class="startDate1">{{item.expired_date.substring(0, 10)}}</text> -->
<!-- <view class="card-roll">
</view> -->
<!-- <view class="recharge" @click="jumpTo('/pages/client/recharge/index')">
<text class="immediately">立即充值</text>
</view> -->
</view>
<view class="scroll" v-for="(item, index) in gold"
:key="index">
<image
src="https://activity.wagoo.live/member3.png"
mode="widthFix"
class="service-head-img"
@click="cardDetails(item,3)"
/>
<!-- <image
@click="jumpTo(`/pages/richText/member-interests`)"
src="https://activity.wagoo.live/xf-2.png"
mode="widthFix"
class="head-img"
/> -->
<image
v-if="item.bounding_pets[0].pet_avatar"
:src="item.bounding_pets[0].pet_avatar"
mode="widthFix"
class="add-img"
/>
<image
v-else
src="https://activity.wagoo.live/bd-2.png"
mode="widthFix"
class="add-img"
@click="clickElasticLayer2(item)"
/>
<view class="number">
<text class="n">编号</text>
<text class="n">{{item.card_number.substring(0, 16)}}</text>
</view>
<view class="immediately">
<text class="n">有效期至:</text>
<text class="n">{{item.expired_date.substring(0, 10)}}</text>
</view>
<view class="startDate">
<text class="n">购卡日期:</text>
<text class="n">{{item.effective_date.substring(0, 10)}}</text>
</view>
<!-- <text class="number">{{item.card_number.substring(0, 8)}}</text>
<text class="immediately">{{item.effective_date.substring(0, 10)}}</text>
<text class="startDate">{{item.expired_date.substring(0, 10)}}</text> -->
<!-- <view class="card-roll">
</view> -->
<!-- <view class="recharge" @click="jumpTo('/pages/client/recharge/index')">
<text class="immediately">立即充值</text>
</view> -->
</view>
</view>
<!-- <view class="occupation-bom"> -->
<view
class="occupation-map"
v-if="false"
@click="jumpTo('/pages/client/index/index?activePageId=shopPage')"
>
<image
src="https://activity.wagoo.live/membership-card.png"
mode="widthFix"
class="service-head-img"
/>
<!-- <text class="z">暂无内容</text> -->
<!-- <view class="card-purchase">
<text class="l">立即购卡</text>
</view> -->
</view>
<view
@click="elasticLayer = false"
v-if="elasticLayer"
class="elastic-layer"
/>
<view v-if="elasticLayer" class="select-pet">
<view class="select-pet-header">
<text class="s">选择宠物</text>
<image
src="@/static/images/close.png"
mode="aspectFit"
class="close-icon"
@click="elasticLayer = false"
/>
</view>
<scroll-view class="select-pet-scroll" scroll-y>
<view
class="select-bom"
@click="addPets(item,index)"
v-for="(item, index) in dataList"
:key="index"
>
<view
:class="[
item.id == selectPetid ? 'select-item2' : 'select-item',
]"
>
<view class="select-left">
<image
class="diamond"
:src="item.avatar"
mode="aspectFit"
/>
<text class="a">{{ item.name }}</text>
</view>
</view>
</view>
</scroll-view>
<view class="add-animal">
<view class="right" @click="switchimgLoad">
<text class="a" style="color: #fff;">绑定</text>
</view>
</view>
</view>
<!-- </view> -->
<!-- 黑金多选 -->
<view
@click="elasticLayer2 = false"
v-if="elasticLayer2"
class="elastic-layer"
/>
<view v-if="elasticLayer2" class="select-pet">
<view class="select-pet-header">
<text class="s">选择宠物</text>
<image
src="@/static/images/close.png"
mode="aspectFit"
class="close-icon"
@click="elasticLayer2 = false"
/>
</view>
<scroll-view class="select-pet-scroll" scroll-y>
<view
class="select-bom"
@click="selectAction(item, index)"
v-for="(item, index) in dataList"
:key="index"
>
<view :class="[item.delete == 2 ? 'select-item2' : 'select-item']">
<view class="select-left">
<image
class="diamond"
:src="item.avatar"
mode="aspectFit"
/>
<text class="a">{{ item.name }}</text>
</view>
</view>
</view>
</scroll-view>
<view class="add-animal">
<view class="right" @click="switchimgLoad2">
<text class="a" style="color: #fff;">绑定</text>
</view>
</view>
</view>
</view>
</template>
<script>
import {
userHoldrlist,
bindPets,
petBinding
} from "../../../api/login";
import { getPetList } from "@/api/common";
export default {
data() {
return {
platinum:[],
petList:[],
gold:[],
dataList:[],
data: [],
blackGold:[],
selectId: [],
selectList:[],
currentStatus: "0",
elasticLayer: false,
elasticLayer2: false,
scrollLeft:true,
haerImg: "",
haerImg2: "",
selectPetid: "",
additionalBom: false,
tabList: [
{
value: "0",
label: "生效中",
},
{
value: "1",
label: "已失效",
},
// ,
// {
// value: "2",
// label: "服务记录",
// },
// {
// value: '1',
// label: '健康记录',
// }
],
};
},
onLoad(options) {
this.user_id = options.user_id
// this.getData();
this.getuserHoldrlist()
},
methods: {
jumpTo(url) {
uni.navigateTo({
url,
});
},
cardDetails(item,v){
// @click="jumpTo(`/pages/client/recharge/recharge-details?catId=1`)"
// console.log(item,v,'--')
uni.navigateTo({
url: `/pages/client/recharge/recharge-details?catId=${v}&userList=${encodeURIComponent(JSON.stringify(item))}`,
});
},
// 绑定接口
geBindin() {
console.log(this.bindPets1,'--?')
const data = {
// user_id:this.bindPets1.user_id,
instance_id:this.bindPets1.id,
user_nickname:this.bindPets1.user_nickname,
card_type:this.bindPets1.card_type,
card_number:this.bindPets1.card_number,
pets:this.petList
// pet_id:this.pet_id,
// pet_name:this.pet_name,
// pet_avatar:this.pet_avatar
// Pet List
}
petBinding(data).then((res) => {
if(res.code == 0){
uni.showToast({
title: "绑定成功",
icon: "none",
});
this.selectId = []
this.petList = []
this.selectPetid = ''
console.log(111222)
// this.getData();
this.getuserHoldrlist()
}
// console.log( this.gold,'--')
// this.butlerApplyInfo = res?.info || {};
});
},
// 卡包列表接口
getuserHoldrlist() {
const data = {
user_id:this.user_id
}
userHoldrlist(data).then((res) => {
this.platinum = res.data.active.白金
this.gold = res.data.active.黄金
this.blackGold = res.data.active.黑金
// console.log( this.gold,'--')
// this.butlerApplyInfo = res?.info || {};
});
},
clickElasticLayer(item) {
this.bindPets1 = item
this.v = 1;
this.elasticLayer = true;
const data = {
user_id:this.user_id,
card_number:item.card_number
}
bindPets(data)
.then((res) => {
this.dataList = res.data
})
},
clickElasticLayer2(item) {
this.bindPets1 = item
this.v = 2;
this.elasticLayer = true;
const data = {
user_id:this.user_id,
card_number:item.card_number
}
bindPets(data)
.then((res) => {
this.dataList = res.data
})
},
addPets(item,index) {
console.log(item, "--");
this.switchList = item;
this.petList = item
this.selectPetid = item.id;
},
switchimgLoad() {
// console.log(this.v,'--')
if (this.v == 1) {
this.geBindin()
console.log(this.bindPets1,'??')
// this.haerImg = this.switchList.chongwu_pic;
this.elasticLayer = false;
} else if (this.v == 2) {
this.geBindin()
// this.haerImg2 = this.switchList.chongwu_pic;
// console.log(this.switchList.chongwu_pic,'--')
this.elasticLayer = false;
}
},
switchimgLoad2(){
this.geBindin()
this.elasticLayer2 = false;
// this.selectList = this.selectId
// this.elasticLayer2 = false;
// uni.showToast({
// title: "绑定成功",
// icon: "none",
// });
},
selectelaList(item){
this.elasticLayer2 = true;
this.bindPets1 = item
const data = {
user_id:this.user_id,
card_number:item.card_number
}
bindPets(data)
.then((res) => {
this.dataList = res.data
})
},
selectAction(item, index) {
// console.log(item,index,'--=')
if (item.delete == 2) {
item.delete = 1;
//取消选中时删除数组中的值
for (var i = 0; i < this.selectId.length; i++) {
if (this.selectId[i] === item) {
this.selectId.splice(i, 1);
}
}
console.log("选中的值1", this.selectId);
// this.seleId = this.selectId[index].item_id
} else {
item.delete = 2;
this.selectId.push(item);
this.petList = this.selectId
// this.seleIdx = item.services[index].idx;
// console.log(this.seleIdx, "--");
console.log("选中的值2", this.selectId);
}
},
// getData() {
// .catch(() => {});
// },
selectOrderStatus(status) {
// console.log(status, "--===-");
if (this.currentStatus === status.value) {
return;
}
this.currentStatus = status.value;
if (status.value == 0) {
this.scrollLeft = true
} else if (status.value == 1) {
this.scrollLeft = false
}
},
},
};
</script>
<style lang="scss" scoped>
.tab-view {
width: 100%;
height: 100rpx;
display: flex;
align-items: center;
justify-content: space-around;
background-color: #ffffff;
.tab-item {
display: flex;
margin-left: 40rpx;
// flex: 1;
flex-direction: column;
align-items: center;
.tab-text {
// font-weight: bold;
color: $app_fc_main;
}
}
.tab-line {
// transform: translateX(-50%);
width: 24rpx;
height: 10rpx;
border-radius: 12rpx;
// width: 30rpx;
// height: 6rpx;
background-color: transparent;
margin-top: 8rpx;
}
.tab-selected-line {
background-color: $app_color_main;
}
}
.scroll-white {
display: flex;
justify-content: center;
flex-direction: column;
align-content: center;
padding-bottom: 30rpx;
.scroll {
display: flex;
margin-top: 20rpx;
justify-content: center;
// justify-content: space-between;
position: relative;
.imgList {
height: 92rpx;
width: 500rpx;
// border: 1px solid #fff;
position: absolute;
display: flex;
align-items: center;
left:55rpx;
bottom: 84rpx;
.img{
display: flex;
margin-left: 20rpx;
.add-i{
width: 44px;
height: 44px;
border-radius: 50%;
}
}
.add-img2 {
width: 44px;
height: 44px;
border-radius: 50%;
margin-left: 20rpx;
}
}
.service-head-img {
width:96%;
height: 155px;
}
.head-img {
position: absolute;
right: 84rpx;
top: 76rpx;
width: 44px;
height: 44px;
}
.number {
position: absolute;
// border: 1px solid #000;
right:48rpx;
top: 30rpx;
display: flex;
align-items: center;
.n{
font-family: PingFangSC;
font-size: 12px;
color: #644d29;
margin-left: 5rpx;
}
}
.add-img {
position: absolute;
left:51rpx;
top: 124rpx;
width: 44px;
height: 44px;
border-radius: 50%;
}
.immediately {
position: absolute;
right:42rpx;
bottom: 33rpx;
display: flex;
align-items: center;
.n{
font-family: PingFangSC;
font-size: 12px;
color: #644d29;
margin-left: 5rpx;
}
}
.startDate {
position: absolute;
left:46rpx;
bottom: 30rpx;
display: flex;
align-items: center;
.n{
font-family: PingFangSC;
font-size: 12px;
color: #644d29;
margin-left: 5rpx;
}
}
.immediately1 {
position: absolute;
right:42rpx;
bottom: 33rpx;
display: flex;
align-items: center;
.n{
font-family: PingFangSC;
font-size: 12px;
color: #CBAD78;
margin-left: 5rpx;
}
}
.number1 {
position: absolute;
// border: 1px solid #000;
right:48rpx;
top: 30rpx;
display: flex;
align-items: center;
.n{
font-family: PingFangSC;
font-size: 12px;
color: #CBAD78;
margin-left: 5rpx;
}
}
.startDate1 {
position: absolute;
left:46rpx;
bottom: 30rpx;
display: flex;
align-items: center;
.n{
font-family: PingFangSC;
font-size: 12px;
color: #CBAD78;
margin-left: 5rpx;
}
}
// .startDate1 {
// position: absolute;
// left: 179rpx;
// bottom: 30rpx;
// font-family: PingFangSC;
// font-size: 12px;
// color: #CBAD78;
// }
}
}
.occupation-map {
height: 1004px;
display: flex;
justify-content: center;
margin-top: 30rpx;
.service-head-img {
width: 351px;
height: 502px;
}
.z {
font-family: PingFang SC;
font-size: 32rpx;
font-weight: normal;
line-height: 40rpx;
text-align: center;
letter-spacing: normal;
color: #726e71;
}
.card-purchase {
width: 96px;
height: 37px;
/* 自动布局 */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 8px 16px;
gap: 10px;
z-index: 2;
border-radius: 20px;
/* 主色1 */
background: #fe019b;
.l {
font-family: PingFang SC;
font-size: 16px;
font-weight: normal;
line-height: 21px;
text-align: center;
letter-spacing: normal;
color: #ffffff;
}
}
}
.elastic-layer {
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
z-index: 999;
}
.select-pet {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
max-height: 70vh;
background: #fff;
border-radius: 20px 20px 0px 0px;
z-index: 99999;
display: flex;
flex-direction: column;
overflow: hidden;
}
.select-pet-header {
flex-shrink: 0;
position: relative;
padding: 30rpx 0 50rpx;
.s {
font-family: PingFang SC;
font-size: 18px;
color: #3e4055;
display: flex;
justify-content: center;
margin: 0;
}
.close-icon {
width: 50rpx;
height: 50rpx;
position: absolute;
right: 23rpx;
top: 24rpx;
}
}
.select-pet-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
}
.select-pet .select-bom {
display: flex;
justify-content: center;
flex-direction: column;
margin-top: 30rpx;
.select-item {
width: 351px;
height: 56px;
border-radius: 8px;
opacity: 1;
/* 自动布局 */
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0 8px 12px;
box-sizing: border-box;
border: 1px solid rgba(0, 0, 0, 0.1);
margin: auto;
.select-left {
display: flex;
align-items: center;
.diamond {
width: 44px;
height: 44px;
border-radius: 50%;
}
.a {
margin-left: 20rpx;
font-family: PingFang SC;
font-size: 16px;
color: #000;
}
}
.operate {
display: flex;
.operate-item {
display: flex;
align-items: center;
margin-right: 40rpx;
.diamond {
width: 20rpx;
height: 20rpx;
margin: 0 20rpx 4rpx 0;
}
.a {
font-family: PingFangSC;
font-size: 12px;
color: #808080;
}
}
}
}
.select-item2 {
width: 351px;
height: 56px;
border-radius: 8px;
opacity: 1;
/* 自动布局 */
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0 8px 12px;
margin: auto;
box-sizing: border-box;
border: 1px solid #FF19A0;
.select-left {
display: flex;
align-items: center;
.diamond {
width: 44px;
height: 44px;
border-radius: 50%;
}
.a {
margin-left: 20rpx;
font-family: PingFang SC;
font-size: 16px;
}
}
.operate {
display: flex;
.operate-item {
display: flex;
align-items: center;
margin-right: 40rpx;
.diamond {
width: 20rpx;
height: 20rpx;
margin: 0 20rpx 4rpx 0;
}
.a {
font-family: PingFangSC;
font-size: 12px;
color: #808080;
}
}
}
}
}
.select-pet .add-animal {
flex-shrink: 0;
display: flex;
justify-content: center;
margin-top: 30rpx;
padding-bottom: 60rpx;
.right {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
width: 343px;
height: 48px;
border-radius: 100px;
margin-left: 30rpx;
background-color: #FF19A0;
color: #fff;
}
}
</style>

View File

@ -0,0 +1,231 @@
<template>
<view class="points-details-container">
<!-- 标签栏 -->
<view class="tabs-container">
<view
class="tab-item"
:class="{ active: activeTab === 0 }"
@click="switchTab(0)"
>
<text class="tab-text">全部</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 1 }"
@click="switchTab(1)"
>
<text class="tab-text">已获取</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 2 }"
@click="switchTab(2)"
>
<text class="tab-text">已使用</text>
</view>
</view>
<!-- 列表内容 -->
<scroll-view class="list-container" scroll-y>
<view
class="list-item"
v-for="(item, index) in pointsList"
:key="index"
>
<view class="item-left">
<text class="item-type">{{ item.type || '未知' }}</text>
<text class="item-time">{{ item.time || '' }}</text>
</view>
<view class="item-right">
<text
class="item-points"
:class="{ positive: item.points_change > 0, negative: item.points_change < 0 }"
>
{{ item.points_change > 0 ? '+' : '' }}{{ item.points_change }}
</text>
</view>
</view>
<!-- 空状态 -->
<view v-if="pointsList.length === 0" class="empty-state">
<text class="empty-text">暂无积分明细</text>
</view>
</scroll-view>
</view>
</template>
<script>
import { getPointsRecords } from '@/api/login';
export default {
data() {
return {
activeTab: 0, // 0: 全部, 1: 已获取, 2: 已使用
pointsList: []
};
},
onLoad() {
this.getPointsList();
},
methods: {
switchTab(index) {
if (this.activeTab === index) {
return;
}
this.activeTab = index;
this.getPointsList();
},
// 获取积分明细列表
getPointsList() {
uni.showLoading({
title: '加载中...',
mask: true
});
getPointsRecords({
status: this.activeTab // 0: 全部, 1: 已获取, 2: 已使用
}).then((res) => {
uni.hideLoading();
// 处理返回的数据
this.pointsList = (res?.data || res?.info || []).map(item => {
return {
type: item.type || item.description || '未知',
time: item.created_at || item.time || '',
points_change: item.points_change || item.amount || 0
};
});
}).catch((err) => {
uni.hideLoading();
uni.showToast({
title: err.msg || '获取数据失败',
icon: 'none'
});
this.pointsList = [];
});
}
}
};
</script>
<style lang="scss" scoped>
.points-details-container {
width: 100%;
min-height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
// 标签栏
.tabs-container {
display: flex;
background-color: #fff;
padding: 0 32rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 0;
position: relative;
.tab-text {
font-size: 28rpx;
color: #666;
transition: all 0.3s;
}
&.active {
.tab-text {
color: #FF19A0;
font-weight: 500;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 24rpx;
height: 10rpx;
background-color: #FF19A0;
border-radius: 100px;
}
}
}
// 列表容器
.list-container {
flex: 1;
padding: 20rpx;
box-sizing: border-box;
border-radius: 24rpx;
}
.list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
.item-left {
display: flex;
flex-direction: column;
gap: 24rpx;
flex: 1;
}
.item-type {
font-size: 28rpx;
font-weight: 500;
color: #3D3D3D;
}
.item-time {
font-size: 24rpx;
color: #9B939A;
}
.item-right {
display: flex;
align-items: center;
}
.item-points {
font-size: 28rpx;
font-weight: 500;
&.positive {
color: #FF19A0;
}
&.negative {
color: #9B939A;
}
}
// 空状态
.empty-state {
display: flex;
align-items: center;
justify-content: center;
padding: 120rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
</style>

View File

@ -0,0 +1,411 @@
<template>
<view class="rech-details">
<view class="scroll-white" v-if="true">
<view class="scroll" v-if="catId == 1">
<image
src="https://activity.wagoo.live/member2.png"
mode="widthFix"
class="service-head-img"
/>
<!-- <image
@click="jumpTo(`/pages/richText/member-interests`)"
src="https://activity.wagoo.live/xf-1.png"
mode="widthFix"
class="head-img"
/> -->
<!-- <text class="number">{{item.card_number.substring(0, 8)}}</text>
<text class="immediately1">{{item.effective_date.substring(0, 10)}}</text>
<text class="startDate1">{{item.expired_date.substring(0, 10)}}</text> -->
<image
:src="userList.bounding_pets[0].pet_avatar"
mode="widthFix"
class="add-img"
/>
<view class="number1">
<text class="n">编号</text>
<text class="n">{{ userList.card_number.substring(0, 8) }}</text>
</view>
<view class="immediately1">
<text class="n">有效期至:</text>
<text class="n">{{ userList.expired_date.substring(0, 10) }}</text>
</view>
<view class="startDate1">
<text class="n">购卡日期:</text>
<text class="n">{{ userList.effective_date.substring(0, 10) }}</text>
</view>
<!-- <text class="number">{{userList.card_number.substring(0, 8)}}</text>
<text class="immediately">{{userList.effective_date.substring(0, 10)}}</text>
<text class="startDate">{{userList.expired_date.substring(0, 10)}}</text> -->
</view>
<view class="scroll" v-if="catId == 2">
<image
src="https://activity.wagoo.live/member1.png"
mode="widthFix"
class="service-head-img"
/>
<!-- <image
@click="jumpTo(`/pages/richText/member-interests`)"
src="https://activity.wagoo.live/xf-3.png"
mode="widthFix"
class="head-img"
/> -->
<view class="imgList">
<view
class="img"
v-for="(item, index) in userList.bounding_pets"
:key="index"
>
<image :src="item.pet_avatar" mode="aspectFill" class="add-i" />
</view>
</view>
<view class="number">
<text class="n">编号</text>
<text class="n">{{ userList.card_number.substring(0, 8) }}</text>
</view>
<view class="immediately">
<text class="n">有效期至:</text>
<text class="n">{{ userList.effective_date.substring(0, 10) }}</text>
</view>
<view class="startDate">
<text class="n">购卡日期:</text>
<text class="n">{{ userList.effective_date.substring(0, 10) }}</text>
</view>
<!-- <text class="number1">{{ userList.card_number.substring(0, 8) }}</text>
<text class="immediately1">{{
userList.effective_date.substring(0, 10)
}}</text>
<text class="startDate1">{{
userList.expired_date.substring(0, 10)
}}</text> -->
</view>
<view class="scroll" v-if="catId == 3">
<image
src="https://activity.wagoo.live/member3.png"
mode="widthFix"
class="service-head-img"
/>
<!-- <image
@click="jumpTo(`/pages/richText/member-interests`)"
src="https://activity.wagoo.live/xf-2.png"
mode="widthFix"
class="head-img"
/> -->
<image
:src="userList.bounding_pets[0].pet_avatar"
mode="widthFix"
class="add-img"
/>
<view class="number">
<text class="n1">编号</text>
<text class="n1">{{ userList.card_number.substring(0, 8) }}</text>
</view>
<view class="immediately">
<text class="n1">有效期至:</text>
<text class="n1">{{ userList.expired_date.substring(0, 10) }}</text>
</view>
<view class="startDate">
<text class="n1">购卡日期:</text>
<text class="n1">{{ userList.effective_date.substring(0, 10) }}</text>
</view>
<!-- <text class="number">{{ userList.card_number.substring(0, 8) }}</text>
<text class="immediately">{{ userList.effective_date.substring(0, 10) }}</text>
<text class="startDate">{{ userList.expired_date.substring(0, 10) }}</text> -->
</view>
</view>
<view class="PetContent">
<view class="PetItem">
<text class="c1">已绑定宠物</text>
<view class="pt-item">
<view class="c2" v-for="(item, index) in userList.bounding_pets" :key="index">
<text
v-if="userList.bounding_pets && userList.bounding_pets.length >1 "
>{{ item.pet_name }},
</text>
<text
v-else
>{{ item.pet_name }}
</text>
</view>
</view>
</view>
<view class="PetItem">
<text class="c1">洗护折扣权益</text>
<text class="c2">{{ userList.discount/10 }}</text>
</view>
<view class="PetItem">
<text class="c1">入会礼-减免券</text>
<text class="c2">已发放</text>
</view>
<view class="PetItem">
<text class="c1">定制浴液</text>
<text class="c2">生效中</text>
</view>
<view class="PetItem">
<text class="c1">专属会员活动</text>
<text class="c2">生效中</text>
</view>
<view class="PetItem">
<text class="c1">免调度费</text>
<text class="c2">{{ userList.fee_exemption[0].fee_value }}</text>
</view>
<view class="PetItem">
<text class="c1">公益勋章</text>
<text class="c2">生效中</text>
</view>
<view class="PetItem">
<text class="c1">专属影集</text>
<text class="c2">生效中</text>
</view>
<view class="PetItem">
<text class="c1">Al宠物档案</text>
<text class="c2">生效中</text>
</view>
<!-- <view class="PetItem">
<text class="c1">积分加速</text>
<text class="c2">生效中</text>
</view> -->
<view class="PetItem">
<text class="c1">专属丝带</text>
<text class="c2">生效中</text>
</view>
<view class="PetItem">
<text class="c1">免夜间费</text>
<text class="c2">{{ userList.fee_exemption[0].fee_value }}</text>
</view>
<view class="PetItem">
<text class="c1">专属客服</text>
<text class="c2">生效中</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
catId: "",
userList: "",
};
},
onLoad(options) {
this.userList = JSON.parse(decodeURIComponent(options.userList));
console.log(this.userList, "--");
this.catId = options.catId;
},
methods: {
jumpTo(url) {
uni.navigateTo({
url,
});
},
},
};
</script>
<style lang="scss" scoped>
.rech-details {
margin: 28rpx auto;
width: 96%;
// height: 1234rpx;
border-radius: 24rpx;
// background: #ffffff;
.scroll-white {
display: flex;
justify-content: center;
flex-direction: column;
align-content: center;
.scroll {
display: flex;
// margin-top: 20rpx;
justify-content: center;
// justify-content: space-between;
position: relative;
.imgList {
height: 92rpx;
width: 500rpx;
// border: 1px solid #fff;
position: absolute;
display: flex;
align-items: center;
left: 37rpx;
bottom: 84rpx;
.img {
display: flex;
margin-left: 30rpx;
.add-i {
width: 44px;
height: 44px;
border-radius: 50%;
}
}
.add-img2 {
width: 44px;
height: 44px;
border-radius: 50%;
margin-left: 20rpx;
}
}
.service-head-img {
width: 98%;
height: 155px;
}
.head-img {
position: absolute;
right: 66rpx;
top: 76rpx;
width: 44px;
height: 44px;
}
.number1 {
font-family: PingFangSC;
font-size: 12px;
color: #cbad78;
position: absolute;
right: 48rpx;
top: 30rpx;
.n {
font-family: PingFangSC;
font-size: 12px;
color: #3d3d3d;
}
}
.add-img {
position: absolute;
left:51rpx;
top: 124rpx;
width: 44px;
height: 44px;
border-radius: 50%;
}
.immediately1 {
position: absolute;
right: 60rpx;
bottom: 30rpx;
font-family: PingFangSC;
font-size: 12px;
color: #cbad78;
.n {
font-family: PingFangSC;
font-size: 12px;
color: #3d3d3d;
}
.n1{
font-family: PingFangSC;
font-size: 12px;
color: #644D29;
}
}
.startDate1 {
position: absolute;
left: 49rpx;
bottom: 30rpx;
font-family: PingFangSC;
font-size: 12px;
color: #cbad78;
.n {
font-family: PingFangSC;
font-size: 12px;
color: #3d3d3d;
}
.n1{
font-family: PingFangSC;
font-size: 12px;
color: #644D29;
}
}
.immediately {
position: absolute;
right: 60rpx;
bottom: 30rpx;
font-family: PingFangSC;
font-size: 12px;
color: #644d29;
.n{
font-family: PingFangSC;
font-size: 12px;
color: #CBAD78;
margin-left: 5rpx;
}
.n1{
font-family: PingFangSC;
font-size: 12px;
color: #644D29;
}
}
.number {
font-family: PingFangSC;
font-size: 12px;
color: #644d29;
position: absolute;
right: 48rpx;
top: 30rpx;
.n {
font-family: PingFangSC;
font-size: 12px;
color: #CBAD78;
}
.n1{
font-family: PingFangSC;
font-size: 12px;
color: #644D29;
}
}
.startDate {
position: absolute;
left:34rpx;
bottom: 30rpx;
font-family: PingFangSC;
font-size: 12px;
color: #644d29;
.n{
font-family: PingFangSC;
font-size: 12px;
color: #CBAD78;
margin-left: 5rpx;
}
.n1{
font-family: PingFangSC;
font-size: 12px;
color: #644D29;
}
}
}
}
.PetContent {
background: #fff;
// width: 351px;
height: 100%;
margin: 30rpx auto;
border-radius: 12px;
.PetItem {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
// margin-top: 30rpx;
height: 120rpx;
border-bottom: 1px solid #ececec;
.c1 {
font-family: PingFangSC;
font-size: 14px;
color: #686868;
}
.pt-item{
display: flex;
align-items: center;
.c2 {
font-family: PingFangSC;
font-size: 14px;
color: #040404;
}
}
}
}
}
</style>

View File

@ -0,0 +1,405 @@
<template>
<view class="share-img">
<image
class="diamond"
src="https://activity.wagoo.live/generate -images.png"
mode="widthFix"
/>
<view class="share-layer">
<image
class="share-cat"
src="https://activity.wagoo.live/share-cat.png"
mode="widthFix"
/>
<view @click="shareLeft" class="left" />
<view @click="shareRight" class="right" />
<view @click="shareBottom" class="bottom" />
</view>
<view class="share-bg" v-if="shareDog">
<image
class="share-img"
src="https://activity.wagoo.live/share-dog2.png"
mode="widthFix"
/>
<view class="share-bottom">
<button class="share-friend" @click="FenXiang">
<image
class="diamond"
src="https://activity.wagoo.live/share-friend.png"
mode="widthFix"
/>
<text class="f"> 发送给朋友 </text>
</button>
<view @click="shareToFriends" class="share-friend">
<image
class="diamond"
src="https://activity.wagoo.live/share-moments.png"
mode="widthFix"
/>
<text class="f"> 分享到朋友圈 </text>
</view>
<view class="share-friend">
<image
class="diamond"
src="https://activity.wagoo.live/share-collect.png"
mode="widthFix"
/>
<text class="f"> 收藏 </text>
</view>
<view class="share-friend">
<image
class="diamond"
src="https://activity.wagoo.live/share-save.png"
mode="widthFix"
/>
<text class="f"> 保存图片 </text>
</view>
<image
@click="shareDog = false"
class="close"
src="https://activity.wagoo.live/share.delete.png"
mode="widthFix"
/>
</view>
</view>
<!-- <canvas id="qrcode" canvas-id="qrcode" style="width: 200px;height: 200px;"></canvas> -->
</view>
</template>
<script>
// import UQRCode from 'uqrcodejs';
export default {
data() {
return {
shareDog: false,
str: "https://activity.wagoo.live/generate -images.png",
};
},
onLoad() {
// console.log(1122);
},
methods: {
initMenu() {
uni.showShareMenu({
withShareTicket: true,
//设置下方的Menus菜单才能够让发送给朋友与分享到朋友圈两个按钮可以点击
menus: ["shareAppMessage", "shareTimeline"],
});
},
FenXiang() {
let _this = this;
console.log("分享图片路径url", _this.str);
wx.downloadFile({
url: _this.str,
success: (res) => {
wx.showShareImageMenu({
path: res.tempFilePath,
});
console.log(res.tempFilePath);
},
fail: (err) => {
console.log("错误信息", err);
},
});
},
// 发送给朋友
onShareAppMessage(res) {
// 此处的distSource为分享者的部分信息需要传递给其他人
return {
title: "#wagoo",
// path:'/pages/home/home'
};
},
//分享到朋友圈
onShareTimeline(res) {
return {
title: "#wagoo",
// path:'/pages/home/home'
};
},
shareBklink() {
uni.navigateBack({
delta: 1,
});
},
shareLeft() {
let _this = this;
console.log("分享图片路径url", _this.str);
wx.downloadFile({
url: _this.str,
success: (res) => {
wx.showShareImageMenu({
path: res.tempFilePath,
});
console.log(res.tempFilePath);
},
fail: (err) => {
console.log("错误信息", err);
},
});
// console.log(111);
// this.shareDog = true;
},
shareRight() {
uni.getSetting({
success(res) {
if (res.authSetting["scope.writePhotosAlbum"]) {
// 已授权,直接保存图片
uni.downloadFile({
url: "https://activity.wagoo.live/generate -images.png",
success: (res) => {
if (res.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: function () {
uni.showToast({
title: "保存成功",
duration: 2000,
});
},
fail: function () {
uni.showToast({
title: "保存失败,请稍后重试",
icon: "none",
});
},
});
}
},
});
} else if (res.authSetting["scope.writePhotosAlbum"] === false) {
// 用户已拒绝授权,提示用户授权
uni.showModal({
title: "提示",
content: "您未授权保存图片到相册,是否前往设置页面进行授权?",
success: function (res) {
if (res.confirm) {
uni.openSetting({
success: function (res) {
if (res.authSetting["scope.writePhotosAlbum"]) {
uni.downloadFile({
url: "https://activity.wagoo.live/home_service_head.png",
success: (res) => {
if (res.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: function () {
uni.showToast({
title: "保存成功",
duration: 2000,
});
},
fail: function () {
uni.showToast({
title: "保存失败,请稍后重试",
icon: "none",
});
},
});
}
},
});
}
},
});
} else if (res.cancel) {
uni.showToast({
title: "您取消了授权",
icon: "none",
duration: 2000,
});
}
},
});
} else {
// 用户第一次调用,调用授权接口
uni.authorize({
scope: "scope.writePhotosAlbum",
success() {
uni.downloadFile({
url: "https://activity.wagoo.live/home_service_head.png",
success: (res) => {
if (res.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: function () {
uni.showToast({
title: "保存成功",
duration: 2000,
});
},
fail: function () {
uni.showToast({
title: "保存失败,请稍后重试",
icon: "none",
});
},
});
}
},
});
},
fail() {
uni.showToast({
title: "授权失败,请稍后重试",
icon: "none",
duration: 2000,
});
},
});
}
},
});
},
shareBottom() {
uni.navigateBack({
delta: 1,
});
},
jumpTo(url) {
uni.navigateTo({
url,
});
},
},
};
</script>
<style lang="scss" scoped>
.share-img {
.nav-container {
background: #fff;
height: 180rpx;
position: relative;
.nav-title {
font-size: 14px;
padding-top: 42rpx;
// font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
color: #000;
height: 100%;
}
.sk {
position: absolute;
left: 13rpx;
top: 92rpx;
width: 80rpx;
height: 80rpx;
// border: 1px solid #000;
.right-arrow {
position: absolute;
left: 18rpx;
top: 24rpx;
width: 8.5px;
height: 14.21px;
}
}
}
.diamond {
width: 100%;
height: 100%;
}
.share-layer {
position: relative;
// height: 325rpx;
.share-cat {
width: 100%;
height: 100%;
position: fixed;
bottom: 0;
left: 0;
}
.left {
width: 100rpx;
height: 156rpx;
border-radius: 50%;
// border: 1px solid #000;
position: absolute;
left: 225rpx;
bottom: 45rpx;
}
.right {
width: 100rpx;
height: 156rpx;
border-radius: 50%;
// border: 1px solid #000;
position: absolute;
right: 225rpx;
bottom: 45rpx;
}
.bottom {
width: 100%;
height: 107rpx;
// border: 1px solid #000;
position: absolute;
left: 0;
bottom: -105rpx;
}
}
.share-bg {
background: rgba(0, 0, 0, 0.85);
position: fixed;
top: 0;
height: 100%;
width: 100%;
z-index: 9999;
.share-img {
margin: 204rpx auto;
display: block;
width: 261px;
height: 464.23px;
}
.share-bottom {
position: fixed;
left: 0;
bottom: 77rpx;
width: 698rpx;
height: 153px;
/* 自动布局 */
display: flex;
justify-content: space-around;
// flex-direction: column;
// align-items: center;
padding: 0 26rpx 26rpx 26rpx;
// gap: 33px;
.share-friend {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 200rpx;
width: 160rpx;
.diamond {
width: 56px;
height: 56px;
}
.f {
font-family: PingFangSC;
font-size: 12px;
font-weight: 500;
line-height: 12px;
text-align: center;
letter-spacing: normal;
color: #ffffff;
margin-top: 50rpx;
}
}
.close {
width: 32px;
height: 32px;
position: absolute;
left: 346rpx;
bottom: 5rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,146 @@
<template>
<view class="rech-details">
<view class="rech-layer">
<view class="rech-amount">
<text class="c" v-if="objNew.third_party_sn">充值金额</text>
<text class="c" v-else-if="objNew.transaction_sn">消费金额</text>
<view class="rech-add">
<text v-if="objNew.third_party_sn" class="m">+</text>
<text v-else-if="objNew.transaction_sn" class="m">-</text>
<text class="m">{{objNew.amount}}</text>
</view>
</view>
<view class="rech-bill">
<view class="rech-row">
<text class="c">交易描述</text>
<text class="m">{{objNew.description}}</text>
</view>
<view v-if="!objNew.third_party_sn" class="rech-row">
<text class="c">订单id</text>
<text class="m">{{objNew.id}}</text>
</view>
<view v-if="!objNew.third_party_sn" class="rech-row">
<text class="c">业务类型</text>
<text class="m">{{objNew.related_business_type}}</text>
</view>
<view class="rech-row">
<text class="c">交易流水号</text>
<text class="m">{{objNew.business_sn}}</text>
</view>
<view v-if="objNew.third_party_sn " class="rech-row">
<text class="c">第三方流水号</text>
<text class="m">{{objNew.third_party_sn}}</text>
</view>
<view v-if="objNew.third_party_sn " class="rech-row">
<text class="c">充值方式</text>
<text v-if="objNew.payment_method == 1" class="m">微信</text>
<text v-else class="m">支付宝</text>
</view>
<view class="rech-row">
<text class="c">交易时间</text>
<text class="m">{{objNew.created_at}}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
objNew:{}
};
},
onLoad(options){
this.objNew=JSON.parse(options.obj)
},
methods: {},
};
</script>
<style lang="scss" scoped>
.rech-details {
margin: 28rpx auto;
width: 700rpx;
height: 1234rpx;
border-radius: 24rpx;
background: #ffffff;
.rech-layer {
padding-top: 80rpx;
.rech-amount {
margin: 0 auto;
width: 206rpx;
height: 120rpx;
/* 自动布局 */
display: flex;
flex-direction: column;
align-items: center;
padding: 0;
gap: 24rpx;
z-index: 0;
.c {
font-family: PingFangSC;
font-size: 32rpx;
font-weight: normal;
line-height: 48rpx;
display: flex;
align-items: center;
letter-spacing: normal;
color: #333333;
}
.rech-add{
display: flex;
align-items: center;
}
.m {
font-family: PingFangSC;
font-size: 48rpx;
font-weight: 500;
line-height: 48rpx;
display: flex;
align-items: center;
letter-spacing: normal;
color: #272426;
}
}
.rech-bill {
height: 460rpx;
/* 自动布局 */
display: flex;
flex-direction: column;
// justify-content: center;
// align-items: center;
padding: 60rpx 24rpx;
gap: 40rpx;
z-index: 1;
.rech-row {
display: flex;
justify-content: space-between;
.c {
display: block;
width: 180rpx;
font-family: PingFangSC;
font-size: 30rpx;
font-weight: normal;
line-height: 30rpx;
letter-spacing: normal;
color: #9b939a;
}
.m {
font-family: PingFangSC;
font-size: 30rpx;
font-weight: 500;
line-height: 30rpx;
text-align: right;
letter-spacing: normal;
color: #272427;
}
}
}
}
}
</style>

View File

@ -0,0 +1,367 @@
<template>
<view class="carDate">
<view class="layer">
<view class="filter-data">
<view class="x1"
>{{ dateStringData }}
</view>
<xp-picker :yearRange=[1950,null] :history="true" mode="ym" style="color:#8f9090" @confirm="confirm">选择时间</xp-picker>
<!-- <xp-picker ref="picker"
mode="ym"
@confirm="confirm" /> -->
<!-- <text v-else class="x" @tap="show">筛选日期 </text> -->
<image v-if="!dateStringData" class="selected" src="@/static/images/sx.png" mode="widthFix" />
</view>
<view>
<form-cell
class="newData2"
type="checkPicker"
placeholderText="全部类型"
:value="recordInfo.weight_id"
:pickerData="weightListFilters"
valueKey="weight_id"
labelKey="weight_name"
:noBorder="true"
@onChange="(value) => onChange(value, 'weight_id')"
/>
<image class="selected2" src="@/static/images/sx.png" mode="widthFix" />
</view>
</view>
<view class="recharge-record" v-if="remarkList.length">
<view
v-for="(item, index) in remarkList"
:key="index"
class="card-layer"
@click="jumpView(item)"
>
<view class="card">
<text class="k">{{ item.description }}</text>
<view class="rech-add">
<text v-if="item.third_party_sn || item.description == '消费退款' " class="m">+</text>
<text v-else-if="item.transaction_sn" class="m">-</text>
<text class="k">{{ item.amount }}</text>
</view>
</view>
<view class="time">
<view class="time-t">
<text class="t">{{ item.created_at }}</text>
</view>
<text class="t">余额{{ item.current_balance }}</text>
</view>
</view>
</view>
<view class="occupation-map" v-else>
<image
src="https://activity.wagoo.live/no-information.png"
mode="widthFix"
class="service-head-img"
/>
<text class="z">暂无内容</text>
</view>
</view>
</template>
<script>
import { rechargeWallet } from "../../../api/login";
import FormCell from "@/components/FormCell.vue";
import PopUpModal from "@/components/PopUpModal.vue";
export default {
components: {
FormCell,
PopUpModal,
},
data() {
return {
// todayTimestamp: Number(new Date()),
remarkList: [],
weightListFilters: [
{
type: 0,
weight_id: 0,
weight_name: "全部类型",
},
{
type: 1,
weight_id: 1,
weight_name: "充值",
},
{
type: 2,
weight_id: 2,
weight_name: "消费",
},
],
// value: Number(new Date()),
timestamp: "",
dateStringData: "",
dateStData:"",
// maxDate: new Date(),
recordInfo: {
chongwu_id: null,
name: "",
weight_id: "",
chongwu_pic_url: "",
chongwu_pic: "",
jianhuren_name: "",
jianhuren_no: "",
pinzhong_name: "",
birthday: "",
daojia_time: "",
age: 1,
pinzhong_id: "",
maofa: "",
user_id: "",
wallet_id: "",
data: "",
typeL: "",
},
};
},
onLoad(options) {
this.user_id = options.user_id;
this.wallet_id = options.wallet_id;
this.myrechargeWallet();
},
methods: {
// show() {
// console.log(this.$refs.picker,'---')
// // this.$refs.picker.show()
// },
confirm(e) {
this.dateStringData = e.value
// console.log(e,'--')
// const date = new Date(e.value); // 时间戳通常是秒需要乘以1000转换成毫秒
// const year = date.getFullYear();
// const month = date.getMonth() + 1; // 月份是从0开始的所以需要+1
// this.dateStData = `${year}-${month < 10 ? "0" + month : month}`;
// console.log(this.dateStData,'---')
this.myrechargeWallet();
},
myrechargeWallet() {
const data = {
wallet_id: +this.wallet_id,
user_id: +this.user_id,
type: this.type,
date: this.dateStringData,
};
rechargeWallet(data).then((res) => {
// console.log(res.data,'---')
this.remarkList = res?.data;
// console.log( this.remarkList,'---==')
});
},
jumpView(item) {
uni.navigateTo({
url: `/pages/client/recharge/use-details?obj=${JSON.stringify(item)}`,
});
console.log(item);
},
jumpTo(url) {
uni.navigateTo({
url,
});
},
onChange(value, key) {
if (key == "birthday") {
this.data = value;
this.myrechargeWallet();
} else if (key == "weight_id") {
this.type = value;
this.myrechargeWallet();
}
// console.log(key, "--");
// console.log(value,'=-=-')
if (key === "type" && this.recordInfo[key] !== value) {
this.recordInfo.type = value;
// this.getRecordTypeList();
// this.getRecordWeightList();
}
if (key === "age" && this.recordInfo[key] !== value) {
this.recordInfo.birthday = "";
}
// if (key === "birthday") {
// this.recordInfo.age = Math.ceil(
// moment(moment().format("YYYY-MM")).diff(
// moment(moment(value).format("YYYY-MM")),
// "years",
// true
// )
// );
// }
this.recordInfo[key] = value;
this.$forceUpdate();
},
},
};
</script>
<style lang="scss" scoped>
.carDate {
// ::v-deep .form-cell{
// justify-content: flex-start;
// }
.layer {
position: relative;
height: 80rpx;
background: #fff;
margin-top: 6rpx;
.filter-data {
position: absolute;
top: 22rpx;
left: 50rpx;
width: 300rpx;
::v-deep .xp-h-full{
color: #8f9090;
}
.x {
color: #8f9090;
}
.x1{
margin: 0 0 0 14rpx;
color: #8f9090;
position: absolute;
left: 115rpx;
top: 3rpx;
}
.selected {
position: absolute;
top: 18rpx;
left: 123rpx;
width: 6px;
height: 3.5px;
}
}
.selected2 {
position: absolute;
top: 38rpx;
right: 50rpx;
width: 6px;
height: 3.5px;
}
.newData {
display: flex;
justify-content: flex-start;
background: #fff;
margin-top: 5rpx;
::v-deep {
.form-cell-wrapper .form-cell {
width: 190rpx;
border: none;
.flex-center {
flex: none;
}
.form-arrow {
display: none;
}
}
}
}
.newData2 {
display: flex;
justify-content: flex-start;
// background: #fff;
margin-top: 5rpx;
::v-deep {
.form-cell-wrapper .form-cell {
width: 180rpx;
border: none;
position: absolute;
right: 58rpx;
top: -3rpx;
.flex-center {
flex: none;
}
.form-arrow {
display: none;
}
}
}
}
}
.recharge-record {
margin: 20rpx auto;
width: 654rpx;
height: 100%;
/* 自动布局 */
display: flex;
flex-direction: column;
// align-items: center;
padding: 28rpx;
gap: 32rpx;
border-radius: 30rpx;
background: #ffffff;
.card-layer {
height: 106rpx;
border-bottom: 1px solid #ececec;
// display: flex;
// position: relative;
z-index: 2;
&:last-of-type {
border-bottom: none;
}
.card {
display: flex;
justify-content: space-between;
// margin-top: 30rpx;
.k {
font-family: PingFangSC;
font-size: 28rpx;
font-weight: 500;
line-height: 28rpx;
letter-spacing: normal;
color: #3d3d3d;
}
}
.time {
display: flex;
justify-content: space-between;
margin: 20rpx 0 20rpx;
.time-t {
display: flex;
.t {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #9b939a;
}
.i {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #9b939a;
margin-left: 6rpx;
}
}
.t {
font-family: PingFangSC;
font-size: 24rpx;
font-weight: normal;
line-height: 24rpx;
letter-spacing: normal;
color: #9b939a;
}
}
}
}
.occupation-map {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
</style>

View File

@ -0,0 +1,175 @@
<template>
<view class="score">
<text class="health">健康记录分析</text>
<view class="view-files">
<image class="selected" :src="chongwu_pic_url" mode="widthFix" />
<text class="name"> {{name}}综合评分 </text>
<view class="fraction"> <text class="p">{{remarkList.total}}</text><text class="f"></text></view>
</view>
<view class="charts-box">
<qiun-data-charts
type="radar"
:opts="opts"
:chartData="remarkList"
/>
</view>
</view>
</template>
<script>
export default {
data(){
return{
chongwu_id:'',
userId:'',
chartData: {},
remarkList:{},
name:'',
//您可以通过修改 config-ucharts.js 文件中下标为 ['radar'] 的节点来配置全局默认参数,如都是默认参数,此处可以不传 opts 。实际应用过程中 opts 只需传入与全局默认参数中不一致的【某一个属性】即可实现同类型的图表显示不同的样式,达到页面简洁的需求。
opts: {
color: ["#1890FF","#91CB74","#FAC858","#EE6666","#73C0DE","#3CA272","#FC8452","#9A60B4","#ea7ccc"],
padding: [5,5,5,5],
dataLabel: false,
enableScroll: false,
legend: {
show: true,
position: "bottom",
lineHeight: 25
},
extra: {
radar: {
gridType: "circle",
gridColor: "#CCCCCC",
gridCount: 3,
opacity: 0.2,
max: 100,
labelShow: true
}
}
}
}
},
onLoad(options){
// console.log(options,'---')
this.chongwu_id = options.chongwu_id
this.userId = options.userId
this.name = options.name
this.mypetradarChart()
},
methods: {
mypetradarChart() {
this.remarkList = {};
},
// getServerData(){
// //模拟从服务器获取数据时的延时
// setTimeout(() => {
// //模拟服务器返回数据,如果数据格式和标准格式不同,需自行按下面的格式拼接
// let res = {
// categories: ["洗护","标题","健康","日常","咨询","清洁"],
// series: [
// {
// name: "日常记录",
// data: [30,50,74,38,87,64]
// },
// // {
// // name: "成交量2",
// // data: [90,21,46,35,83,69]
// // }
// ]
// };
// this.chartData = JSON.parse(JSON.stringify(res));
// }, 500);
// }
},
// onReady() {
// this.mypetradarChart();
// },
}
</script>
<style lang="scss" scoped>
.score{
width: 351px;
height: 468.5px;
display: flex;
flex-direction: column;
/* 自动布局 */
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 0px;
gap: 24rpx;
border-radius: 24rpx;
background: #FFFFFF;
margin: 20rpx auto;
.charts-box {
width: 100%;
height: 300px;
}
.health{
font-family: PingFangSC;
font-size: 28rpx;
font-weight: 500;
line-height: 28rpx;
letter-spacing: normal;
color: #272427;
}
.view-files{
width: 100%;
padding-left: 50rpx;
display: flex;
align-items: center;
.selected{
width:88rpx;
height: 88rpx;
z-index: 0;
border-radius:50%;
}
.name{
font-family: PingFang SC;
font-size: 32rpx;
font-weight: normal;
line-height: 32rpx;
text-align: center;
letter-spacing: normal;
color: #272427;
margin-left:20rpx;
}
.fraction{
display: flex;
justify-content: flex-end; /* 如果需要,也可以将内容靠右对齐 */
align-items: flex-end;
.p{
// margin-left:6rpx;
font-family: PingFang SC;
font-size: 48rpx;
font-weight: normal;
line-height: 48rpx;
text-align: center;
letter-spacing: normal;
/* 主色1 */
color: #FE019B;
}
.f{
margin-bottom:6rpx;
font-family: PingFangSC;
font-size: 12px;
font-weight: normal;
line-height: 12px;
text-align: center;
letter-spacing: normal;
/* 主色1 */
color: #FE019B;
}
}
}
}
</style>

View File

@ -0,0 +1,284 @@
<template>
<view class="breed-modal-container">
<view class="modal-mask" @click="close"></view>
<view class="model-content">
<view class="flex-row-start modal-title">
<view class="title-icon"></view>
<view class="app-fc-main PingFangSC-Bold fs-36 title-name">
宠物品种
</view>
<image
class="title-icon"
src="@/static/images/close_icon.png"
@click="close"
/>
</view>
<view class="breed-list">
<scroll-view
scroll-y
:scroll-into-view="`item_${breedIndex}`"
class="breed-list-wrapper"
@scroll="onScroll"
>
<view
class="flex-row-start breed-item"
v-for="(breed, i) in data"
:key="breed.id"
:id="`item_${i}`"
@click="save(breed)"
>
<image class="breed-icon" :src="breed.image_url" />
<text
class="fs-32 SourceHanSansCN-Regular app-text-ellipse breed-name"
>
{{ breed.name }}
</text>
<view class="split-line"></view>
</view>
</scroll-view>
</view>
<view class="breed-bottom"></view>
<view class="flex-column-start letters-list">
<view
class="flex-center letters-item"
v-for="(letter, i) in lettersList"
:key="i"
@click="setPostion(letter)"
>
<view
class="flex-center fs-20 letters-item-text"
:class="letter === curTab ? 'active' : ''"
>
{{ letter }}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import appConfig from "../../../../constants/app.config";
export default {
props: {
data: {
type: Array,
default: () => [],
},
value: {
type: String | Number,
default: "",
},
},
data() {
return {
appConfig,
curTab: "",
curBreedId: "",
indicatorStyle: `height: 60px;border: 0;`,
};
},
computed: {
lettersList() {
const letters = [];
for (let i = 65; i <= 90; i++) {
letters.push(String.fromCharCode(i));
}
return letters;
},
breedIndex() {
const index = this.data.findIndex(
(v) => v.id === this.curBreedId
);
return index < 0 ? 0 : index;
},
},
watch: {
value: {
handler(val) {
const breed = this.data.find((v) => v.id === val);
this.curBreedId = breed?.id || this.data?.[0]?.id;
this.curTab = (breed?.alpha_code || this.data?.[0]?.alpha_code || "").toUpperCase();
},
immediate: true,
},
data(list) {
if (list.length) {
this.curBreedId = list?.[0]?.id;
this.curTab = list?.[0]?.alpha_code?.toUpperCase();
}
},
},
methods: {
close() {
this.$emit("close");
},
save(breed) {
this.curBreedId = breed.id;
this.curTab = (breed.alpha_code || "").toUpperCase();
this.$emit("confirm", this.curBreedId);
},
onScroll(e) {
const top = e.detail.scrollTop;
const itemHeight = 44 + 10 * 2;
const index = Math.floor(top / itemHeight);
const breed = this.data[index];
this.curTab = (breed.alpha_code || "").toUpperCase();
},
setPostion(letter) {
this.curTab = letter;
const index = this.data.findIndex(
(v) => (v?.alpha_code || "").toUpperCase() === letter
);
this.curBreedId = this.data?.[index]?.id;
},
},
};
</script>
<style lang="scss" scoped>
.breed-modal-container {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.6);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
.modal-mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100vw;
}
.model-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100vw;
height: 80vh;
overflow: hidden;
padding-bottom: 20rpx;
background: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
box-sizing: border-box;
.modal-title {
padding: 50rpx 52rpx;
.title-icon {
width: 52rpx;
height: 52rpx;
}
.title-name {
flex: 1;
text-align: center;
}
}
.breed-list {
flex: 1;
box-sizing: border-box;
height: calc(80vh - 180rpx); // 添加固定高度,减去标题和底部的高度
.breed-list-wrapper {
height: 100%;
}
.breed-item {
padding: 10px 52rpx;
box-sizing: border-box;
position: relative;
.breed-icon {
width: 44px;
height: 44px;
border-radius: 44px;
}
.breed-name {
flex: 1;
margin: 0 20rpx;
color: #726e71;
}
.split-line {
position: absolute;
bottom: 0;
left: 52rpx;
right: 52rpx;
height: 1px;
background: #f3f4f7;
}
}
}
.breed-bottom {
height: 60rpx;
width: 100%;
}
}
.confirm-btn {
width: 630rpx;
height: 90rpx;
border-radius: 90rpx;
background: $app_color_main;
border-radius: 45rpx 45rpx 45rpx 45rpx;
color: #fff;
}
.letters-list {
position: absolute;
top: 108rpx;
right: 20rpx;
height: 80vh;
width: 60rpx;
z-index: 2;
overflow: auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
.letters-item {
height: 34rpx;
.letters-item-text {
text-align: center;
width: 24rpx;
height: 24rpx;
color: #3e4055;
&.active {
width: 24rpx;
height: 24rpx;
border-radius: 24rpx;
background: $app_color_main;
color: #fff;
}
}
}
}
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<uni-swipe-action :key="data.id" class="record-item-container">
<uni-swipe-action-item
:right-options="{}"
:auto-close="false"
@change="change()"
@click="toDetails()"
>
<view class="flex-row-start pet-item">
<image
class="pet-icon"
:src="data.avatar"
mode="aspectFit"
/>
<text class="fs-32 app-fc-main text-multi-ellipse pet-name">
{{ data.name }}
</text>
<text class="fs-28 SourceHanSansCN-Regular pet-edit" @click="toDetails">
编辑
</text>
</view>
<view slot="right" class="flex-center pet-right">
<image
class="pet-delete"
src="@/static/images/mine_delete.png"
@click.stop="deleteClick"
/>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</template>
<script>
import appConfig from "@/constants/app.config";
export default {
props: {
data: {
type: Object,
default: () => {},
},
},
data() {
return {
appConfig,
};
},
mounted() {
},
methods: {
toDetails() {
this.$emit("toDetails", this.data);
},
deleteClick() {
this.$emit("deleteClick", this.data);
},
change() {
this.$emit("change", this.data);
},
},
};
</script>
<style lang="scss" scoped>
.record-item-container {
.pet-item {
padding: 44rpx 40rpx;
background: #ffffff;
border-radius: 30rpx;
margin-bottom: 32rpx;
.pet-icon {
width: 80rpx;
height: 80rpx;
flex-shrink: 0;
border-radius: 80rpx;
}
.pet-edit {
color: $app_fc_mark;
flex-shrink: 0;
}
.pet-name {
margin: 0 24rpx;
flex: 1;
}
}
.pet-right {
padding: 0 0 0 32rpx;
.pet-delete {
width: 88rpx;
height: 88rpx;
}
}
}
</style>

View File

@ -0,0 +1,747 @@
<template>
<view class="record-edit">
<view class="record-edit-content" :class="{ 'disable-scroll': showBreedModal }">
<view class="tips">请完善您的宠物信息</view>
<!-- <view class="flex-row-center avator-wrapper">
<button
class="avator-inner"
open-type="chooseAvatar"
@chooseavatar="changeAvator"
>
<image
class="avator-ic"
src="https://activity.wagoo.live/zxj.png"
mode="aspectFit"
/>
<image
v-if="recordInfo.chongwu_pic_url"
class="avator-icon"
:src="recordInfo.chongwu_pic_url"
mode="aspectFit"
/>
<image
v-else
class="avator-icon"
src="https://activity.wagoo.live/wagoodeft.png"
/>
</button>
</view> -->
<view class="edit-form">
<form-cell title="宠物头像" type="custom" :showRightArrow="false">
<view class="uploadImgWrapper" slot="right">
<button class="upload-btn" open-type="chooseAvatar" @chooseavatar="changeAvator">
<image v-if="recordInfo.avatar" class="uploaded-img" :src="recordInfo.avatar"
mode="aspectFill" />
<image v-else :src="`${imgPrefix}record-cameraImg.png`" class="img" />
</button>
</view>
</form-cell>
<form-cell :isRequired='true' title="宠物姓名" placeholderText="点击输入姓名" :value="recordInfo.name"
:showRightArrow="false" @onChange="(value) => onChange(value, 'name')" />
<view>
<form-cell :title="'宠物类别'" type="custom" :showRightArrow="false" :isRequired='true'>
<view class="flex-row-end edit-sex" slot="right">
<view class="flex-row-center opt-item opt-item-type"
:class="[recordInfo.type === 1 ? 'active' : '', id ? 'disabled' : '']"
@click="!id && onChange(1, 'type')">
<image class="icon"
:src="recordInfo.type === 1 ? `${imgPrefix}record-selectedDog.png` : `${imgPrefix}record-dogImg.png`" />
<text class="text"></text>
</view>
<view class="flex-row-center opt-item opt-item-type"
:class="[recordInfo.type === 2 ? 'active' : '', id ? 'disabled' : '']"
@click="!id && onChange(2, 'type')">
<image class="icon"
:src="recordInfo.type === 2 ? `${imgPrefix}record-selectedCat.png` : `${imgPrefix}record-carImg.png`" />
<text class="text"></text>
</view>
</view>
</form-cell>
<form-cell type="custom" :title="'宠物品种'" placeholderText="选择宠物品种" :showRightArrow="!id"
:isRequired='true' @clickAction="!id && (showBreedModal = true)">
<view class="flex-row-end edit-sex" slot="right">
<text class="fs-26" v-if="breedName">{{ breedName }}</text>
<text class="fs-26 place-text" v-else>选择宠物品种</text>
</view>
</form-cell>
<form-cell :title="'宠物性别'" type="custom" :showRightArrow="false" :isRequired='true'>
<view class="flex-row-end edit-sex" slot="right">
<view class="opt-item"
:class="[recordInfo.gender === 'male' ? 'active' : '', id ? 'disabled' : '']"
@click="!id && (recordInfo.gender = 'male')">
<image class="icon"
:src="recordInfo.gender === 'male' ? `${imgPrefix}whiteMale.png` : `${imgPrefix}record-maleImg.png`" />
</view>
<view class="opt-item"
:class="[recordInfo.gender === 'female' ? 'active' : '', id ? 'disabled' : '']"
style="margin-left: 32rpx;" @click="!id && (recordInfo.gender = 'female')">
<image class="icon"
:src="recordInfo.gender === 'female' ? `${imgPrefix}whiteFemale.png` : `${imgPrefix}record-femaleImg.png`" />
</view>
</view>
</form-cell>
<form-cell :title="'是否绝育'" type="custom" :showRightArrow="false" :isRequired='true'>
<view class="flex-row-end edit-sex" slot="right">
<view class="flex-center fs-28 app-fc-main SourceHanSansCN-Medium opt-item"
:class="[recordInfo.is_neutered == 1 ? 'active' : '', id ? 'disabled' : '']"
@click="!id && (recordInfo.is_neutered = '1')">
</view>
<view class="flex-center fs-28 app-fc-main SourceHanSansCN-Medium opt-item"
:class="[recordInfo.is_neutered == 2 ? 'active' : '', id ? 'disabled' : '']"
@click="!id && (recordInfo.is_neutered = '2')">
</view>
</view>
</form-cell>
<form-cell :title="'出生年月'" type="checkDate" placeholderText="选择出生日期" :value="recordInfo.birthday"
:defaultDate="recordInfo.birthday || ''" :disabled="!!id"
@onChange="(value) => onChange(value, 'birthday')" :isRequired='true' />
<form-cell :title="'宠物年龄'" type="checkPicker" placeholderText="选择宠物年龄" :value="recordInfo.age"
:pickerData="ageList" valueKey="value" labelKey="name" :disabled="!!id"
@onChange="(value) => onChange(value, 'age')" :isRequired='true' />
<form-cell v-if="recordInfo.type == 2" :title="'宠物毛发'" type="custom" :showRightArrow="false"
:isRequired='true'>
<view class="flex-row-end edit-sex" slot="right">
<view class="flex-center fs-28 app-fc-main SourceHanSansCN-Medium opt-item"
:class="[recordInfo.hair == 1 ? 'active' : '', id ? 'disabled' : '']"
@click="!id && changeHair(1)">
长毛
</view>
<view class="flex-center fs-28 app-fc-main SourceHanSansCN-Medium opt-item"
:class="[recordInfo.hair == 2 ? 'active' : '', id ? 'disabled' : '']"
@click="!id && changeHair(2)">
短毛
</view>
</view>
</form-cell>
<form-cell :isRequired='true' :title="'宠物体重'" type="checkPicker" placeholderText="选择宠物体重"
:value="recordInfo.weight_id" :pickerData="weightList" valueKey="id" labelKey="weight_name"
:noBorder="true" :disabled="!!id" @onChange="(value) => onChange(value, 'weight_id')" />
</view>
</view>
<view class="place-view"></view>
</view>
<view class="flex-row-center edit-bottom">
<!-- <view
v-if="id"
class="flex-center PingFangSC-Semibold fs-30 confirm-btn delete"
@click="showModal = true"
>
删除
</view> -->
<view class="flex-center PingFangSC-Semibold fs-30 confirm-btn" @click="save">
保存
</view>
</view>
<pop-up-modal v-if="showModal" content="确定要删除该档案信息吗?" @confirm="deleteRecord" @cancel="showModal = false" />
<!-- 品种modal -->
<BreedModal v-if="showBreedModal" :data="breedList" :value="recordInfo.breed_id" @confirm="onBreedChange"
@close="showBreedModal = false" />
</view>
</template>
<script>
import moment from "moment";
import {
delRecord,
editRecord,
updatePet,
getRecordInfo,
getRecordTypeList,
getRecordWeightList,
} from "../../../api/record";
import appConfig from "../../../constants/app.config";
import {
PET_IS_STERILIZE_YES,
PET_SEX_MALE,
PET_TYPE_DOG,
} from "@/constants/app.business";
import {
imgPrefix
} from "@/utils/common.js";
import {
uploadImageToOSS_PUT
} from "@/utils/oss";
import FormCell from "@/components/FormCell.vue";
import PopUpModal from "@/components/PopUpModal.vue";
import BreedModal from "./components/BreedModal.vue";
export default {
components: {
FormCell,
PopUpModal,
BreedModal,
},
data() {
return {
id: "",
recordInfo: {
id: null,
name: "",
weight_id: "",
avatar: "",
chongwu_pic: "",
// jianhuren_name: "",
jianhuren_no: "",
type: PET_TYPE_DOG, // 1狗 2猫
gender: "male", // 性别 "male" 公 "female" 母
birthday: "",
age: 1,
is_neutered: PET_IS_STERILIZE_YES, // 是否绝育 1.是 2.否
breed_id: "",
hair: 1,
},
breedList: [],
weightList: [],
showModal: false,
showBreedModal: false,
appConfig,
imgPrefix,
};
},
computed: {
ageList() {
return Array.from({
length: 20
}, (v, k) => ({
value: k + 1,
name: `${k + 1}`,
}));
},
breedName() {
return (
this.breedList.find(
(v) => v.id === this.recordInfo.breed_id
)?.name || ""
);
},
userInfo() {
return this.$store.state?.user?.userInfo || {};
},
},
onLoad(options) {
// console.log()
console.log(options, '--?')
const {
id,
type
} = options;
// this.recordInfo.jianhuren_name = this.userInfo.nick_name;
// if (type) {
// this.recordInfo.type = parseInt(type);
// }
if (id) {
this.id = id;
this.recordInfo.id = id;
this.getInfo(id);
} else {
this.getRecordTypeList();
this.getRecordWeightList();
}
},
methods: {
moment,
// 更新头像
async changeAvator(e) {
const {
avatarUrl
} = e.detail;
uni.showLoading({
title: "上传中..."
});
try {
const { url, objectKey } = await uploadImageToOSS_PUT(avatarUrl);
console.log(url, objectKey, 'url, objectKey');
this.recordInfo.avatar = url; // 完整URL
this.recordInfo.chongwu_pic = objectKey; // 对象键
this.$forceUpdate();
uni.hideLoading();
uni.showToast({
title: "上传成功",
icon: "success",
});
} catch (error) {
console.error('头像上传失败:', error);
uni.hideLoading();
uni.showToast({
title: error?.message || "头像上传失败",
icon: "none"
});
}
},
onChange(value, key) {
if (key === "type" && this.recordInfo[key] !== value) {
this.recordInfo.type = value;
this.getRecordTypeList();
this.getRecordWeightList();
this.recordInfo.breed_id = null;
this.recordInfo.weight_id = null;
}
// 移除年龄和生日的自动关联逻辑,让用户独立选择
this.recordInfo[key] = value;
this.$forceUpdate();
},
changeHair(value) {
this.recordInfo.hair = value;
this.recordInfo.weight_id = "";
this.getRecordWeightList();
this.$forceUpdate();
},
onBreedChange(val) {
this.recordInfo.breed_id = val;
this.showBreedModal = false;
},
getRecordTypeList() {
const param = {
species: this.recordInfo.type
};
getRecordTypeList(param).then((res) => {
this.breedList = res.data;
});
},
getRecordWeightList() {
const param = {
species: this.recordInfo.type,
hair: this.recordInfo.type === PET_TYPE_DOG ? 1 : this.recordInfo.hair
};
getRecordWeightList(param).then((res) => {
this.weightList = res.data;
});
},
getInfo(id) {
getRecordInfo(id, this.userInfo.userID).then((res) => {
// 合并数据,确保保留所有字段
const data = { ...res.data };
// 如果后端返回的是 false 或 2转换为 2
if (data.is_neutered === true || data.is_neutered === 1) {
data.is_neutered = 1;
} else if (data.is_neutered === false || data.is_neutered === 2) {
data.is_neutered = 2;
}
this.recordInfo = data;
// 使用 $nextTick 确保数据更新后再调用
this.$nextTick(() => {
this.getRecordTypeList();
this.getRecordWeightList();
});
});
},
save() {
// 必填字段校验
if (!this.recordInfo.name || this.recordInfo.name.trim() === '') {
uni.showToast({
icon: 'none',
title: '请输入宠物姓名'
});
return;
}
if (!this.recordInfo.type) {
uni.showToast({
icon: 'none',
title: '请选择宠物类别'
});
return;
}
if (!this.recordInfo.breed_id) {
uni.showToast({
icon: 'none',
title: '请选择宠物品种'
});
return;
}
if (!this.recordInfo.gender) {
uni.showToast({
icon: 'none',
title: '请选择宠物性别'
});
return;
}
if (!this.recordInfo.is_neutered) {
uni.showToast({
icon: 'none',
title: '请选择是否绝育'
});
return;
}
if (!this.recordInfo.birthday) {
uni.showToast({
icon: 'none',
title: '请选择出生年月'
});
return;
}
if (!this.recordInfo.age) {
uni.showToast({
icon: 'none',
title: '请选择宠物年龄'
});
return;
}
if (!this.recordInfo.weight_id) {
uni.showToast({
icon: 'none',
title: '请选择宠物体重'
});
return;
}
// 如果是猫,需要校验毛发
if (this.recordInfo.type === 2 && !this.recordInfo.hair) {
uni.showToast({
icon: 'none',
title: '请选择宠物毛发'
});
return;
}
const params = {
user_id: this.userInfo.userID,
name: this.recordInfo.name,
type: this.recordInfo.type, // 1狗 2猫
breed_id: this.recordInfo.breed_id,
gender: this.recordInfo.gender, // "male" 或 "female"
weight_id: this.recordInfo.weight_id,
hair: this.recordInfo.hair ? this.recordInfo.hair : 1,
age: this.recordInfo.age || 1,
birthday: this.recordInfo.birthday,
is_neutered: this.recordInfo.is_neutered == 1 ? true : false,
};
// 如果有 id说明是更新添加 id 字段
const isEdit = !!this.recordInfo.id;
if (isEdit) {
params.pet_id = this.recordInfo.id;
}
// 处理头像字段
if (isEdit) {
// 编辑模式:如果 chongwu_pic 存在,使用它;否则使用 avatar
if (this.recordInfo.chongwu_pic) {
params.avatar = this.recordInfo.chongwu_pic;
} else if (this.recordInfo.avatar) {
params.avatar = this.recordInfo.avatar;
}
} else {
// 添加模式:直接使用 chongwu_pic
if (this.recordInfo.chongwu_pic) {
params.avatar = this.recordInfo.chongwu_pic;
}
}
uni.showLoading({
title: "保存中..."
});
// 根据是否有 id 判断是新增还是更新
const savePromise = this.recordInfo.id ? updatePet(params) : editRecord(params);
savePromise
.then((r) => {
uni.hideLoading();
uni.showToast({
icon: "none",
title: "保存成功"
});
// 通过 eventChannel 触发事件(如果页面通过 events 参数传递)
const eventChannel = this.getOpenerEventChannel();
if (eventChannel) {
eventChannel.emit("addPetSuccess");
}
// 触发全局刷新事件
uni.$emit('refreshPetList');
uni.navigateBack();
})
.catch((err) => {
uni.hideLoading();
uni.showToast({
icon: "none",
title: err || "保存失败",
});
});
},
deleteRecord() {
this.showModal = false;
uni.showLoading({
title: "删除中..."
});
delRecord(this.recordInfo.id)
.then(() => {
uni.navigateBack();
})
.catch((err) => {
uni.hideLoading();
uni.showToast({
title: err || "删除失败",
icon: "none",
});
});
},
},
};
</script>
<style lang="scss" scoped>
.record-edit {
height: 100%;
width: 100%;
box-sizing: border-box;
background: #ffecf3;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.record-edit-content {
height: 100%;
&.disable-scroll {
overflow: hidden;
}
.tips {
padding: 20rpx 0rpx;
text-align: center;
color: #3D3D3D;
font-size: 24rpx;
}
}
.avator-wrapper {
padding: 40rpx 0 60rpx;
display: flex;
justify-content: center;
.avator-inner {
width: 180rpx;
height: 180rpx;
position: relative;
margin: 0;
background: transparent;
border: 0;
padding: 0;
&::after {
border: none;
}
.avator-ic {
position: absolute;
right: 0;
top: 121rpx;
width: 60rpx;
height: 60rpx;
}
.avator-icon {
width: 180rpx;
height: 180rpx;
border-radius: 180rpx;
}
.avator-upload {
position: absolute;
bottom: 0;
right: 0;
width: 60rpx;
height: 60rpx;
}
}
}
.edit-form {
background: #fff;
border-radius: 16rpx;
padding: 0 24rpx;
width: calc(100vw - 40rpx);
margin: auto;
box-sizing: border-box;
&.default {
margin-top: 32rpx;
}
.uploadImgWrapper {
background-color: #a2a2a2;
border-radius: 218px;
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
margin-left: auto;
.upload-btn {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: transparent;
border: none;
display: flex;
align-items: center;
justify-content: center;
&::after {
border: none;
}
.img {
width: 32rpx;
height: 28rpx;
}
.uploaded-img {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
}
}
.opt-item {
width: 104rpx;
height: 56rpx;
background: #f8f8f8;
border-radius: 8rpx;
margin-left: 32rpx;
color: #3D3D3D;
font-size: 24rpx;
display: flex;
align-items: center;
justify-content: center;
&:first-child {
margin-left: 0;
}
&.disabled {
opacity: 0.5;
pointer-events: none;
}
&.active {
color: #fff;
background: #FF19A0;
&.man {
background: #FF19A0;
}
&.women {
background: #FF19A0;
}
.item-text {
color: #fff;
}
}
&.opt-item-type {
width: auto;
padding: 0rpx 8rpx;
background: #f8f8f8;
border-radius: 8rpx;
margin-left: 0;
display: flex;
align-items: center;
justify-content: center;
&:not(:first-child) {
margin-left: 32rpx;
}
.icon {
width: 56rpx;
height: 56rpx;
}
.text {
margin-left: 8rpx;
font-size: 24rpx;
color: #3D3D3D;
}
&.active {
background: #FF19A0;
color: #fff;
.text {
color: #fff;
}
}
}
.icon {
width: 28rpx;
height: 28rpx;
}
}
.edit-sex {
width: 100%;
}
.place-text {
color: #8f9090;
}
.place-view {
width: 100%;
height: 186rpx;
}
.edit-bottom {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #fff;
border-radius: 32rpx 32rpx 0px 0px;
padding-top: 12rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
.confirm-btn {
width: calc(100% - 48rpx);
margin: auto;
margin-bottom: 24rpx;
height: auto;
border-radius: 100px;
background: #FF19A0;
color: #fff;
padding: 32rpx 0rpx;
text-align: center;
font-size: 32rpx;
&.delete {
background: #e7e2e7;
color: #9b939a;
margin-right: 70rpx;
}
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,240 @@
<template>
<view class="remark-compare-container">
<view class="remark-compare-content">
<view class="fs-32 app-fc-main compare-title">
洗护前
<text class="fs-20 compare-tips">最多可上传9张</text>
</view>
<uni-file-picker
limit="9"
:imageStyles="{
width: '196rpx',
height: '196rpx',
'border-radius': '20rpx',
}"
:del-icon="true"
:auto-upload="false"
@select="onImgChange"
@delete="onImgDelete"
>
<view class="flex-center add-wrapper">
<image class="add-icon" src="./static/add_icon.png" />
<text class="fs-24 add-text">添加图片</text>
</view>
</uni-file-picker>
<view class="fs-32 app-fc-main compare-title compare-after">
洗护后
<text class="fs-20 compare-tips">最多可上传9张</text>
</view>
<uni-file-picker
limit="9"
:imageStyles="{
width: '196rpx',
height: '196rpx',
'border-radius': '20rpx',
}"
:del-icon="true"
:auto-upload="false"
@select="onAfterImgChange"
@delete="onAfterImgDelete"
>
<view class="flex-center add-wrapper">
<image class="add-icon" src="./static/add_icon.png" />
<text class="fs-24 add-text">添加图片</text>
</view>
</uni-file-picker>
</view>
<view class="place-view"></view>
<view class="flex-center confirm-footer">
<view class="flex-center fs-30 app-fc-white confirm-btn" @click="submit">
上传
</view>
</view>
</view>
</template>
<script>
import appConfig from "../../../constants/app.config";
export default {
data() {
return {
beforeImgList: [],
afterImgList: [],
orderId: "",
};
},
onLoad(options) {
this.orderId = options?.orderId || "";
},
methods: {
onImgChange(e) {
uni.showLoading({
title: "图片上传中...",
icon: "none",
mask: true,
});
const promiseList = [];
(e?.tempFilePaths || []).map((path, i) => {
promiseList.push(this.uploadFile(path, "before", i));
});
Promise.all(promiseList).finally(() => {
uni.hideLoading();
});
},
onImgDelete(e) {
this.beforeImgList.splice(e.index, 1);
},
onAfterImgChange(e) {
const promiseList = [];
(e?.tempFilePaths || []).map((path, i) => {
promiseList.push(this.uploadFile(path, "after", i));
});
Promise.all(promiseList).finally(() => {
uni.hideLoading();
});
},
onAfterImgDelete(e) {
this.afterImgList.splice(e.index, 1);
},
uploadFile(url, type, index) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: appConfig.apiBaseUrl + "/app/upload/upload_file",
filePath: url,
name: "image",
formData: {
file_name: "app",
pic_name: +new Date() + '_' + index,
},
header: { token: uni.getStorageSync("token") },
success: (res) => {
const data = JSON.parse(res.data);
const { code, msg, info } = data;
const errCode = String(code);
if (errCode === "0" || errCode === "200") {
if (type === "after") {
this.afterImgList.push({
fullUrl: info?.[0]?.urlname,
url: info?.[0]?.filename,
});
} else {
this.beforeImgList.push({
fullUrl: info?.[0]?.urlname,
url: info?.[0]?.filename,
});
}
this.$forceUpdate();
resolve();
} else {
uni.showToast({
title: msg || "上传失败",
icon: "none",
});
reject();
}
},
fail: () => {
uni.showToast({
title: "上传失败",
icon: "none",
});
reject();
},
});
});
},
submit() {
if (!this.beforeImgList.length) {
uni.showToast({
title: "请上传洗护前图片",
icon: "none",
});
return;
}
if (!this.afterImgList.length) {
uni.showToast({
title: "请上传洗护后图片",
icon: "none",
});
return;
}
uni.showToast({
icon: "none",
title: "接口开发中",
});
},
},
};
</script>
<style lang="scss" scoped>
.remark-compare-container {
height: 100%;
padding: 20rpx 32rpx 0;
padding: 20rpx 32rpx constant(safe-area-inset-bottom);
padding: 20rpx 32rpx env(safe-area-inset-bottom);
background: #f7f8fa;
.remark-compare-content {
background: #fff;
border-radius: 30rpx;
padding: 52rpx 28rpx 40rpx;
box-sizing: border-box;
.compare-tips {
color: #726e71;
}
.compare-title {
margin-bottom: 28rpx;
}
.add-wrapper {
background: #f8f9fa;
width: 196rpx;
height: 196rpx;
border-radius: 20rpx;
.add-icon {
width: 30rpx;
height: 30rpx;
margin-bottom: 20rpx;
}
.add-text {
color: #d2d4dd;
}
}
.compare-after {
margin-top: 40rpx;
}
}
.confirm-footer {
position: fixed;
bottom: 0;
left: 0;
width: 100vw;
background: #fff;
padding: 20rpx 0 0;
padding: 20rpx 0 constant(safe-area-inset-bottom);
padding: 20rpx 0 env(safe-area-inset-bottom);
}
.place-view {
height: 166rpx;
}
.confirm-btn {
width: 630rpx;
height: 90rpx;
border-radius: 90rpx;
background: $app_color_main;
border-radius: 45rpx 45rpx 45rpx 45rpx;
color: #fff;
}
}
</style>

View File

@ -0,0 +1,42 @@
<template>
<view class="remark-details">
<remark-item :data="data" />
</view>
</template>
<script>
import RemarkItem from "@/components/goods/RemarkItem.vue";
import { getRemarkDetails } from "../../../api/shop";
export default {
data() {
return {
data: {},
remarkId: "",
};
},
components: {
RemarkItem,
},
onLoad(options) {
const { remarkId = "" } = options;
this.remarkId = remarkId;
this.getRemarkDetails();
},
methods: {
getRemarkDetails() {
getRemarkDetails(this.remarkId).then((res) => {
this.data = res?.info;
});
},
},
};
</script>
<style lang="scss" scoped>
.remark-details {
padding: 20rpx 0;
box-sizing: border-box;
background: #f7f8fa;
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<view class="remark-list-container">
<list-page-temp
class="remark-list-inner"
:getDataPromise="getRemarkList"
:reloadFlag="reloadFlag"
:requestData="requestData"
>
<template v-slot:item="{ data }">
<remark-item :data="data" />
</template>
<view slot="bottom" class="place-view"></view>
</list-page-temp>
</view>
</template>
<script>
import RemarkItem from "@/components/goods/RemarkItem.vue";
import ListPageTemp from "@/components/ListPageTemp.vue";
import { getRemarkList } from "@/api/shop";
export default {
props: {
// 1.商品订单 2.服务券订单
type: {
type: Number,
default: 1,
},
guanlian_id: {
type: String | Number,
default: "",
},
},
components: {
RemarkItem,
ListPageTemp,
},
data() {
return {
reloadFlag: 0,
remarkList: [],
requestData: {
type: this.type,
guanlian_id: this.guanlian_id,
},
};
},
onShow() {
this.reloadFlag = Math.random();
},
onLoad(options) {
const { type, shopId } = options;
if (type) {
this.requestData.type = type;
}
if (shopId) {
this.requestData.guanlian_id = shopId;
}
},
mounted() {},
methods: {
getRemarkList,
},
};
</script>
<style lang="scss" scoped>
.remark-list-container {
height: 100%;
width: 100%;
box-sizing: border-box;
background: #fbf8fc;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
padding-top: 20rpx;
.remark-list-inner {
height: 100%;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

View File

@ -0,0 +1,527 @@
<template>
<view class="screening-details">
<uni-load-more v-if="isLoading" status="loading" :show-text="false" />
<scroll-view v-if="!isLoading" class="scroll-view" scroll-y>
<!-- 宠物信息 -->
<view class="section pet-info-section">
<view class="section-title pet-info-title">宠物信息</view>
<view class="info-list">
<view class="info-item">
<text class="info-label">报告编号</text>
<text class="info-value">{{ reportData.order_no || '--' }}</text>
</view>
<view class="info-item">
<text class="info-label">宠物昵称</text>
<text class="info-value">{{ petInfo.name || '--' }}</text>
</view>
<view class="info-item">
<text class="info-label">种类</text>
<text class="info-value">{{ getSpeciesName(petInfo.species) }}</text>
</view>
<view class="info-item">
<text class="info-label">品种</text>
<text class="info-value">{{ petInfo.breed_name || '--' }}</text>
</view>
<view class="info-item">
<text class="info-label">年龄</text>
<text class="info-value">{{ `${petInfo.age}` || '--' }}</text>
</view>
<view class="info-item">
<text class="info-label">性别</text>
<text class="info-value">{{ petInfo.gender || '--' }}</text>
</view>
<view class="info-item">
<text class="info-label">宠物体重</text>
<text class="info-value">{{ petInfo.weight_name || '--' }}</text>
</view>
<view class="info-item">
<text class="info-label">报告时间</text>
<text class="info-value">{{ reportData.report_time || '--' }}</text>
</view>
</view>
</view>
<!-- 检测图片 -->
<view class="section detection-images-section">
<view class="section-title detection-title">预检结果</view>
<view class="detection-list">
<view v-for="(item, index) in reportData.precheck_json" :key="index" class="detection-item">
<view class="detection-item-header">
<text class="detection-item-title">
{{ index + 1 }}. {{ getPartLabel(item.name) }}{{ item.radioSelected === 'abnormal' ? '不正常' : '正常' }}
</text>
</view>
<view class="detection-item-content" v-if="getDetectionItemContent(item)">
<text class="detection-text">{{ getDetectionItemContent(item) }}</text>
</view>
<view class="detection-images">
<image v-for="(img, imgIndex) in (item.beforeImgList || [])" :key="img"
class="detection-image" :src="img" mode="aspectFill"
@click="previewImage(img, item.beforeImgList)" />
</view>
</view>
</view>
</view>
<!-- 部位诊断 -->
<!-- <view class="section part-diagnosis-section">
<view class="section-title part-diagnosis-title">部位诊断</view>
<view class="diagnosis-list">
<view
v-for="(item, index) in partDiagnosisList"
:key="index"
class="diagnosis-item"
>
<view class="diagnosis-item-title">{{ item.part_name }}</view>
<view class="diagnosis-item-content">
<text class="diagnosis-text">{{ item.content || '正文正文...' }}</text>
</view>
</view>
</view>
</view> -->
<!-- 综合诊断 -->
<!-- <view class="section comprehensive-diagnosis-section">
<view class="section-title comprehensive-title">综合诊断</view>
<view class="diagnosis-content">
<text class="diagnosis-text">{{ comprehensiveDiagnosis || '正文正文...' }}</text>
</view>
</view> -->
<!-- 诊断依据 -->
<!-- <view class="section diagnosis-basis-section">
<view class="section-title diagnosis-basis-title">诊断依据</view>
<view class="diagnosis-content">
<text class="diagnosis-text">{{ diagnosisBasis || '正文正文...' }}</text>
</view>
</view> -->
<!-- 治疗建议 -->
<!-- <view class="section treatment-section">
<view class="section-title treatment-title">治疗建议</view>
<view class="diagnosis-content">
<text class="diagnosis-text">{{ treatmentSuggestions || '正文正文...' }}</text>
</view>
</view> -->
<!-- 护理建议 -->
<!-- <view class="section care-section">
<view class="section-title care-title">护理建议</view>
<view class="diagnosis-content">
<text class="diagnosis-text">{{ careSuggestions || '正文正文...' }}</text>
</view>
</view> -->
<!-- 康复建议 -->
<!-- <view class="section recovery-section">
<view class="section-title recovery-title">康复建议</view>
<view class="diagnosis-content">
<text class="diagnosis-text">{{ recoverySuggestions || '正文正文...' }}</text>
</view>
</view> -->
<!-- 健康管理 -->
<!-- <view class="section health-management-section">
<view class="section-title health-management-title">健康管理</view>
<view class="diagnosis-content">
<text class="diagnosis-text">{{ healthManagement || '正文正文...' }}</text>
</view>
</view> -->
<!-- 底部安全区域 -->
<view class="bottom-safe-area"></view>
</scroll-view>
</view>
</template>
<script>
import { getPetPrecheckRecords } from "@/api/order";
import moment from "moment/moment";
export default {
name: 'ScreeningDetails',
data() {
return {
orderId: '',
petId: '',
isLoading: true,
reportData: {},
detectionList: [],
partDiagnosisList: [],
comprehensiveDiagnosis: '',
diagnosisBasis: '',
treatmentSuggestions: '',
careSuggestions: '',
recoverySuggestions: '',
healthManagement: '',
petInfo: {},
state: [
{ value: 'eye', label: '眼睛' },
{ value: 'ear', label: '耳朵' },
{ value: 'nose', label: '鼻子' },
{ value: 'cavity', label: '口腔' },
{ value: 'skin', label: '皮肤' },
{ value: 'joint', label: '关节' },
{ value: 'anus', label: '肛门' },
{ value: 'genitals', label: '生殖器' },
{ value: 'nail', label: '指甲' },
{ value: 'whole', label: '整体' }
],
};
},
onLoad(options) {
if (options.orderId) {
this.orderId = options.orderId;
}
if (options.petId) {
this.petId = options.petId;
}
if (this.orderId && this.petId) {
this.getReportDetails();
} else {
uni.showToast({
title: '参数错误',
icon: 'none'
});
}
},
methods: {
getReportDetails() {
this.isLoading = true;
getPetPrecheckRecords({
pet_id: this.petId.split(',').map(Number),
order_id: parseInt(this.orderId)
})
.then((res) => {
this.isLoading = false;
if (res.code === 0 && res.data) {
const raw = res.data;
let data = raw;
if (Array.isArray(raw) && raw.length > 0) {
const petIdStr = String(this.petId);
data = raw.find(function(item) {
const pid = (item.list && item.list.pet_id) != null ? item.list.pet_id : (item.pet && item.pet.pet_id);
return pid != null && String(pid) === petIdStr;
});
if (!data) data = raw[0];
}
if (data) this.processReportData(data);
} else {
uni.showToast({
title: res?.msg || '获取报告失败',
icon: 'none'
});
}
})
.catch((err) => {
this.isLoading = false;
console.error('获取预检报告失败:', err);
uni.showToast({
title: err?.msg || err?.message || '获取报告失败',
icon: 'none'
});
});
},
processReportData(data) {
const list = data.list;
const reportItem = (Array.isArray(list) ? list[0] : list) || {};
const pet = data.pet || {};
// 处理 submit_time使用 moment 格式化
let formattedTime = '--';
if (reportItem.submit_time) {
// 如果是时间戳(数字),使用 unix 转换
if (typeof reportItem.submit_time === 'number') {
formattedTime = moment.unix(reportItem.submit_time).format("YYYY-MM-DD HH:mm:ss");
} else {
// 如果是字符串,直接解析
formattedTime = moment(reportItem.submit_time).format("YYYY-MM-DD HH:mm:ss");
}
}
this.reportData = {
...reportItem,
report_time: formattedTime
};
this.petInfo = {
...pet,
gender: this.formatGender(pet.gender)
};
},
formatGender(val) {
if (!val) return '--';
const v = String(val).toLowerCase();
if (v === 'male') return '男生';
if (v === 'female') return '女生';
return val;
},
getSpeciesName(species) {
if (species === 1) return '狗';
if (species === 2) return '猫';
return '--';
},
getPartLabel(name) {
if (!name) return '--';
const found = this.state.find(v => v.value === name);
return found ? found.label : name;
},
// 拼接 selectList 的 name以、隔开最后拼接 inputContent
getDetectionItemContent(item) {
const names = (item.selectList || []).map(s => s && s.name).filter(Boolean).join('、');
const input = (item.inputContent || '').trim();
if (names && input) return names + '、' + input;
if (names) return names;
return input;
},
previewImage(current, urls) {
uni.previewImage({
current: current,
urls: urls
});
}
}
};
</script>
<style lang="scss" scoped>
.screening-details {
width: 100%;
height: 100vh;
background-color: #f5f5f5;
box-sizing: border-box;
.scroll-view {
width: 100%;
height: 100%;
}
.section {
width: calc(100% - 40rpx);
margin: 0 20rpx 20rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
box-sizing: border-box;
.section-title {
width: 100%;
padding: 16rpx 20rpx;
font-size: 24rpx;
box-sizing: border-box;
}
.detection-title {
background-color: #ffecd9;
color: #FF7F00;
}
.part-diagnosis-title {
background-color: #e8f5e9;
color: #333;
}
.comprehensive-title {
background-color: #e3f2fd;
color: #333;
}
.diagnosis-basis-title {
background-color: #ffecf3;
color: #333;
}
.treatment-title {
background-color: #e3f2fd;
color: #333;
}
.care-title {
background-color: #ffecf3;
color: #333;
}
.recovery-title {
background-color: #e3f2fd;
color: #333;
}
.health-management-title {
background-color: #ffecf3;
color: #333;
}
}
// 宠物信息部分
.pet-info-section {
margin: 20rpx;
border-radius: 16rpx;
overflow: hidden;
background-color: #fff;
.pet-info-title {
background-color: #FFE7E8;
color: #EC6D71;
border-radius: 16rpx 16rpx 0 0;
}
.info-list {
padding: 0 32rpx 0;
box-sizing: border-box;
background-color: #fff;
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10rpx 0;
&:first-child {
padding-top: 20rpx;
}
&:last-child {
border-bottom: none;
padding-bottom: 20rpx;
}
.info-label {
font-size: 24rpx;
color: #9B939A;
}
.info-value {
font-size: 24rpx;
color: #272427;
font-weight: 500;
}
}
}
}
// 检测图片部分
.detection-images-section {
.detection-list {
padding: 0 32rpx 32rpx;
box-sizing: border-box;
.detection-item {
padding: 20rpx 0;
&:last-child {
border-bottom: none;
}
.detection-item-header {
margin-bottom: 20rpx;
.detection-item-title {
font-size: 24rpx;
color: #272427;
font-weight: 500;
}
}
.detection-item-content {
margin-bottom: 20rpx;
.detection-text {
font-size: 24rpx;
color: #272427;
line-height: 1.6;
}
}
.detection-images {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-top: 20rpx;
.detection-image {
width: 200rpx;
height: 200rpx;
border-radius: 20rpx;
background-color: #f5f5f5;
flex-shrink: 0;
}
.detection-image-placeholder {
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.placeholder-text {
font-size: 24rpx;
color: #999;
}
}
}
}
}
}
// 部位诊断部分
.part-diagnosis-section {
.diagnosis-list {
padding: 0 32rpx 32rpx;
box-sizing: border-box;
.diagnosis-item {
padding: 32rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.diagnosis-item-title {
font-size: 30rpx;
color: #333;
font-weight: 500;
margin-bottom: 16rpx;
}
.diagnosis-item-content {
.diagnosis-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
}
}
}
}
// 综合诊断和其他建议部分
.comprehensive-diagnosis-section,
.diagnosis-basis-section,
.treatment-section,
.care-section,
.recovery-section,
.health-management-section {
.diagnosis-content {
padding: 0 32rpx 32rpx;
box-sizing: border-box;
.diagnosis-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
}
}
.bottom-safe-area {
width: 100%;
height: constant(safe-area-inset-bottom);
height: env(safe-area-inset-bottom);
min-height: 0;
}
}
</style>

View File

@ -0,0 +1,223 @@
<template>
<view class="pet-list-page">
<view class="record-list">
<view
class="record-item"
v-for="(item, index) in petsList"
:key="item.pet_id"
@click="selectPet(item)"
>
<view class="record-content">
<view class="pet-info">
<text class="pet-name">{{ item.pet_name || item.name || '--' }}</text>
<text class="pet-gender">{{ formatGender(item.sex || item.gender) }}</text>
</view>
<view class="divider"></view>
<view class="order-info">
<view class="info-row">
<text class="label">订单编号</text>
<text class="value">{{ item.order_no || '--' }}</text>
</view>
<view class="info-row">
<text class="label">车牌号</text>
<text class="value">{{ item.license_plate || '--' }}</text>
</view>
<view class="info-row">
<text class="label">预约时间</text>
<text class="value">{{ item.reservation_time || item.created_at || orderCreatedAt || '--' }}</text>
</view>
</view>
</view>
<view class="status-badge">
<text class="status-text">预检</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'ScreeningPetList',
data() {
return {
orderId: '',
orderCreatedAt: '',
petsList: []
};
},
onLoad(options) {
this.orderId = options.orderId || '';
try {
this.orderCreatedAt = options.created_at ? decodeURIComponent(options.created_at) : '';
} catch (e) {
this.orderCreatedAt = options.created_at || '';
}
try {
let list = options.petsList ? JSON.parse(decodeURIComponent(options.petsList)) : [];
console.log(list, 'list')
if (!Array.isArray(list)) list = [];
this.petsList = list.map(function(p) {
if (typeof p === 'object' && p !== null) return p;
return { pet_id: p, id: p, chongwu_id: p, pet_name: '宠物' };
});
} catch (e) {
this.petsList = [];
}
},
methods: {
formatGender(val) {
if (!val) return '--';
const v = String(val).toLowerCase();
if (v === 'male' || v === '男') return '男生';
if (v === 'female' || v === '女') return '女生';
return val;
},
getPetAvatar(pet) {
const url = pet.chongwu_pic || pet.pet_avatar || pet.avatar || pet.pet_image_url;
return url || 'https://activity.wagoo.live/record_avator.png';
},
selectPet(pet) {
const petId = pet.pet_id || pet.id || pet.chongwu_id;
if (!this.orderId || !petId) {
uni.showToast({ title: '参数错误', icon: 'none' });
return;
}
uni.navigateTo({
url: `/pages/client/screening/details?orderId=${this.orderId}&petId=${petId}`
});
}
}
};
</script>
<style lang="scss" scoped>
.pet-list-page {
min-height: 100vh;
background: #f7f8fa;
padding: 24rpx;
}
.record-list {
margin: 0 auto;
background: #fff;
padding: 20rpx;
padding-top: 20rpx;
box-sizing: border-box;
border-radius: 16rpx;
.record-item {
display: flex;
background: #fff;
border-radius: 24rpx;
margin-bottom: 24rpx;
overflow: hidden;
&:last-child {
margin-bottom: 0;
}
position: relative;
border: 2rpx solid #FF19A0;
box-sizing: border-box;
cursor: pointer;
.record-content {
flex: 1;
padding: 32rpx 88rpx 32rpx 32rpx;
display: flex;
align-items: center;
gap: 32rpx;
min-width: 0;
position: relative;
z-index: 3;
.pet-info {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
flex-shrink: 0;
width: 100rpx;
.pet-name {
font-size: 24rpx;
color: #272427;
line-height: 1.3;
}
.pet-gender {
font-size: 24rpx;
color: #9B939A;
line-height: 1.3;
}
}
.divider {
width: 2rpx;
height: 88rpx;
background: #ececec;
flex-shrink: 0;
}
.order-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 20rpx;
align-items: flex-start;
min-width: 0;
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16rpx;
width: 100%;
.label {
font-size: 24rpx;
color: #9B939A;
flex-shrink: 0;
}
.value {
font-size: 24rpx;
color: #272427;
text-align: right;
font-weight: 500;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.status-badge {
width: 60rpx;
background: #FF19A0;
display: flex;
align-items: center;
justify-content: center;
writing-mode: vertical-rl;
text-orientation: upright;
position: absolute;
right: 0;
top: 0;
bottom: 0;
z-index: 2;
border-radius: 0 22rpx 22rpx 0;
.status-text {
font-size: 28rpx;
color: #fff;
font-weight: 500;
letter-spacing: 4rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,398 @@
<template>
<view class="shop-details-container">
<view class="banner-swiper" v-if="bannerList.length">
<swiper
class="swiper-wrapper"
circular
:indicator-dots="false"
:autoplay="true"
@change="bannerChange"
>
<swiper-item v-for="(banner, i) in bannerList" :key="i">
<image class="swiper-img" :src="banner" mode="aspectFill" />
</swiper-item>
</swiper>
<view class="flex-row-center dot-box">
<view
v-for="(banner, index) in bannerList"
:key="index"
class="dot-item"
:class="{ active: bannerIndex === index }"
></view>
</view>
</view>
<view class="good-info">
<view class="info-cell">
<view class="flex-row-between">
<view class="">
<text class="app-fc-mark fs-24"></text>
<text class="app-fc-mark fs-44">{{ details.price || 0 }}</text>
<text class="fs-24 origin-price">
¥{{ details.old_price || 0 }}
</text>
</view>
<view class="flex-row-end">
<text class="fs-22 app-fc-cancel">
已售 {{ details.xiaoliang || 0 }}
</text>
<view class="fs-22 app-fc-cancel split-line">|</view>
<text class="fs-22 app-fc-cancel">
剩余{{ details.kucun || 0 }}
</text>
</view>
</view>
<view class="fs-36 app-font-bold app-fc-main good-name">
{{ details.name || "" }}
</view>
<view class="flex-row-start">
<text
class="fs-20 sale-btn"
v-for="(label, i) in labelList"
:key="i"
:class="[i > 0 ? 'sale-today app-fc-mark' : 'app-fc-white']"
>
{{ label }}
</text>
</view>
</view>
<view class="info-cell">
<view class="flex-row-start cell-inner">
<text class="fs-26 postage-info">使用区间</text>
<text class="fs-26 app-fc-main flex-1">
{{ details.weight_name || "-" }}
</text>
</view>
<view class="flex-row-start cell-inner">
<text class="fs-26 postage-info">有效时间</text>
<text class="fs-26 app-fc-main flex-1">
{{ details.youxiao_time || "-" }}
</text>
</view>
<view class="flex-row-start cell-inner" @click="showGuideModal = true">
<text class="fs-26 postage-info">使用规则</text>
<text class="fs-26 app-fc-main flex-1 app-text-ellipse">
{{ details.desc || "-" }}
</text>
<image
class="arrow-icon"
:src="require('@/static/images/arrow_right_black.png')"
mode="widthFix"
/>
</view>
</view>
<view class="info-cell" v-if="remarkList.length">
<view class="width-fill flex-row-between">
<text class="fs-28 postage-info">
用户评价
<text class="fs-24 app-fc-normal"> ({{ remarkCount }}) </text>
</text>
<view class="flex-row-end" @click="jumpToRemark">
<text class="fs-26 app-fc-main">查看全部</text>
<image
class="arrow-icon"
:src="require('@/static/images/arrow_right_black.png')"
/>
</view>
</view>
<view class="remark-list">
<remark-item v-for="item in remarkList" :key="item.id" :data="item" />
</view>
</view>
</view>
<view class="fs-36 app-fc-normal rich-text-title"> 服务详情 </view>
<view class="rich-text-content" v-html="details.content"></view>
<view class="place-view"></view>
<view class="flex-row-between details-bottom">
<text class="fs-30 app-fc-main">
<text class="fs-48 app-fc-main app-font-bold">{{ payPrice }}</text>
</text>
<view
class="flex-center fs-30 app-fc-white app-font-bold opt-btn"
@click="createOrder"
>
立即购买
</view>
</view>
<use-guide-modal
v-if="showGuideModal"
title="使用规则"
:content="details.desc"
@ok="showGuideModal = false"
/>
<success-modal
v-if="showSuccessModal"
title="购买成功"
ok-text="立即预约"
@ok="paySuccessAction"
/>
</view>
</template>
<script>
import { getServiceCouponDetail } from "@/api/coupon";
import { getRemarkList } from "../../../api/shop";
import { ORDER_TYPE_PET_SERVICE } from '@/constants/app.business';
import {
serviceCouponCreateOrder,
serviceCouponOrderPay,
} from "../../../api/coupon";
import UseGuideModal from "@/components/coupon/UseGuideModal.vue";
import SuccessModal from "@/components/SuccessModal";
import RemarkItem from "@/components/goods/RemarkItem";
export default {
components: {
UseGuideModal,
SuccessModal,
RemarkItem
},
data() {
return {
couponId: "",
details: {},
bannerList: [],
bannerIndex: 0,
remarkList: [],
showGuideModal: false,
showSuccessModal: false,
remarkCount : 0
};
},
computed: {
labelList() {
return (this.details?.label || "").split(",").filter((v) => !!v);
},
payPrice() {
return +this.details.price || 0;
},
},
onLoad(options) {
this.couponId = options.id;
this.getDetails();
this.getRemarkListData();
},
methods: {
// 获取服务券详情
getDetails() {
getServiceCouponDetail(this.couponId).then((res) => {
this.details = res?.info || {};
this.bannerList = (this.details.fuwuquan_pic || []).split(',').filter(v => !!v);
});
},
// 获取评论列表
getRemarkListData() {
getRemarkList({ p: 1, num: 1, type: ORDER_TYPE_PET_SERVICE, guanlian_id: this.couponId }).then(
(res) => {
this.remarkList = res?.info || [];
this.remarkCount = res?.count || 0;
}
);
},
// 轮播图
bannerChange(e) {
this.bannerIndex = e.detail.current;
},
stripTags(html = "") {
return html.replace(/<[^>]+>/g, "");
},
jumpToRemark() {
uni.navigateTo({
url: `/pages/client/remark/list?shopId=${this.couponId}&type=${ORDER_TYPE_PET_SERVICE}`,
});
},
// 下单
createOrder() {
uni.showLoading({
title: "处理中",
icon: "none",
mask: true,
});
serviceCouponCreateOrder(this.couponId).then((res) => {
this.pay(res?.info);
});
},
// 支付
pay(orderId) {
serviceCouponOrderPay(orderId).then((res) => {
const payData = res?.info?.pay_data || {};
uni.requestPayment({
provider: "wxpay",
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.sign,
success: (res) => {
uni.hideLoading();
this.showSuccessModal = true;
},
fail: (err) => {
uni.hideLoading();
uni.showToast({
title: err?.msg || "支付失败",
icon: "none",
});
},
});
});
},
paySuccessAction(){
this.showSuccessModal = false;
uni.redirectTo({
url: '/pageHome/reservation/index'
})
}
},
};
</script>
<style lang="scss" scoped>
.shop-details-container {
height: 100%;
background: #f7f8fa;
.banner-swiper {
position: relative;
width: 100%;
height: 750rpx;
.swiper-wrapper {
width: 100%;
height: 750rpx;
.swiper-img {
width: 100%;
height: 100%;
}
}
.dot-box {
position: absolute;
bottom: 30rpx;
left: 0;
width: 100%;
.dot-item {
width: 12rpx;
height: 12rpx;
border-radius: 12rpx;
background: rgba(255, 255, 255, 0.6);
margin-right: 12rpx;
&.active {
width: 28rpx;
height: 12rpx;
background: #ffffff;
}
}
}
}
.good-info {
margin: 24rpx 32rpx;
.info-cell {
background: #fff;
border-radius: 20rpx;
padding: 36rpx 24rpx;
margin-bottom: 24rpx;
.split-line {
margin: 0 10rpx;
}
}
.cell-inner {
padding: 12rpx 0;
.info-arrow-icon {
width: 40rpx;
height: 20rpx;
}
}
.origin-price {
color: #726e71;
text-decoration: line-through;
margin-left: 18rpx;
line-height: 44rpx;
}
.good-name {
margin: 30rpx 0;
}
.sale-btn {
padding: 6rpx 12rpx;
background: $app_color_main;
border-radius: 4rpx;
border: 1rpx solid $app_color_main;
margin-right: 12rpx;
margin-bottom: 24rpx;
&.sale-today {
background: transparent;
}
}
.postage-info {
color: #9b939a;
margin-right: 32rpx;
}
.arrow-icon {
width: 30rpx;
height: 18rpx;
margin-left: 20rpx;
}
.remark-list {
border-top: 2rpx solid #ebebeb;
margin-top: 36rpx;
::v-deep {
.remark-item {
width: 100%;
margin: 0;
padding: 40rpx 0;
}
}
}
}
.rich-text-title {
margin: 32rpx 0 24rpx;
text-align: center;
}
.rich-text-content {
background: #fff;
padding: 24rpx;
}
.place-view {
height: 190rpx;
}
.details-bottom {
position: fixed;
bottom: 0;
padding-bottom: 20rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
padding-top: 20rpx;
padding-left: 40rpx;
padding-right: 36rpx;
left: 0;
width: 100vw;
background: #fff;
box-sizing: border-box;
.opt-btn {
width: 260rpx;
height: 92rpx;
background: $app_color_main;
border-radius: 92rpx;
}
}
}
</style>

View File

@ -0,0 +1,124 @@
<template>
<view class="flex-center coupon-modal">
<image
class="coupon-title-icon"
src="'https://activity.wagoo.live/coupon_modal_title.png'"
mode="widthFix"
/>
<view class="coupon-modal-content">
<view
v-for="item in couponList"
:key="item.coupon_id"
class="flex-row-start coupon-item"
>
<view class="flex-row-center item-left">
<text class="fs-60 app-font-bold app-fc-white">
{{ item.card_money || 0 }}
</text>
<text class="fs-32 price-unit app-fc-white"></text>
</view>
<view class="flex-column-start item-right">
<text v-if="item.type === 1" class="fs-36 app-fc-main app-font-bold">
{{ item.name }}
</text>
<text class="fs-24 app-fc-main app-font-bold coupon-text">
{{ item.type === 1 ? "商品优惠券" : item.name }}
</text>
</view>
</view>
</view>
<image
class="get-icon"
:src="require('../static/coupon_modal_get.png')"
@click="$emit('getCoupon')"
/>
<image
class="close-icon"
:src="require('../static/coupon_modal_close.png')"
@click="$emit('close')"
/>
</view>
</template>
<script>
export default {
props: {
couponList: {
type: Array,
default: () => [],
},
},
data() {
return {};
},
mounted() {},
methods: {},
};
</script>
<style lang="scss" scoped>
.coupon-modal {
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
z-index: 99999;
.coupon-title-icon {
width: 620rpx;
height: 232rpx;
padding-right: 30px;
position: relative;
z-index: 1;
}
.coupon-modal-content {
position: relative;
z-index: 0;
width: 596rpx;
max-height: 520rpx;
overflow-y: auto;
padding: 82rpx 32rpx 40rpx;
box-sizing: border-box;
background: #FDC8DA;
background-size: 100% 100%;
border-radius: 40rpx;
margin-top: -42rpx;
.coupon-item {
background: #ffffff;
border-radius: 30rpx 30rpx 30rpx 30rpx;
height: 160rpx;
margin-bottom: 32rpx;
.item-left {
width: 208rpx;
height: 100%;
margin-right: 32rpx;
background: #FD269C;
border-radius: 30rpx 30rpx 30rpx 30rpx;
}
.price-unit {
margin-left: 6rpx;
}
.coupon-text {
// color: #726e71;
}
}
}
.get-icon {
width: 300rpx;
height: 80rpx;
margin: 52rpx 0;
}
.close-icon {
width: 80rpx;
height: 80rpx;
}
}
</style>

View File

@ -0,0 +1,158 @@
<template>
<view class="goods-item" @click.stop="jumpToDetails">
<image class="goods-img" :src="data.product_pic" mode="aspectFill" />
<view class=" fs-24 app-fc-main goods-name">
{{ data.product_name || "" }}
</view>
<view class="flex-row-start label">
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
<view class="fs-20 app-fc-main label-name">{{data.sales}}人买过</view>
</view>
<view class="flex-row-start" style="margin-top: 12rpx;">
<text class="fs-28" style="color: #3D3D3D;">
¥
<text class="fs-28">{{
data.prices[0].actual_price || 0
}}</text>
</text>
<!-- <text class="fs-24 origin-price">
¥{{ minPrice.price_shichang || 0 }}
</text> -->
</view>
<!-- <text class="fs-20 good-salenum">已售{{ data.xiaoliang || 0 }}</text> -->
<!-- <image class="add-cart-icon" src="@/static/images/cart-icon.png" @click.stop="$emit('addToCar', data)" /> -->
</view>
</template>
<script>
import {
imgPrefix
} from '@/utils/common';
export default {
props: {
index: {
type: Number,
default: 0,
},
data: {
type: Object,
default: () => {},
},
},
data() {
return {
imgPrefix,
};
},
computed: {
// minPrice() {
// let minPrice = {};
// let minPriceValue = 0;
// this.data.price_list.map((v) => {
// if (!minPriceValue || minPriceValue > +v.price) {
// minPriceValue = +v.price;
// minPrice = {
// ...v
// };
// }
// });
// return minPrice;
// },
labelList() {
return (this.data?.label || "").split(",").filter((v) => !!v);
},
},
mounted() {},
methods: {
jumpToDetails() {
uni.navigateTo({
url: `/pages/client/shop/details?product_id=${this.data.product_id}`,
});
},
},
};
</script>
<style lang="scss" scoped>
.goods-item {
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
box-sizing: border-box;
margin-bottom: 22rpx;
position: relative;
width: 100%;
min-width: 0;
overflow: hidden;
.goods-img {
display: block;
width: 100%;
max-width: 100%;
height: 320rpx;
border-radius: 16rpx;
background: #f5f5f5;
}
.goods-name {
margin: 20rpx 0;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
word-wrap: break-word;
word-break: break-all;
}
.label {
background-color: #ffecf3;
display: inline-flex;
border-radius: 4rpx;
.hot-icon {
width: 28rpx;
height: 28rpx;
}
.label-name {
padding: 4rpx;
color: #FF19A0;
}
}
.sale-btn {
padding: 4rpx;
background: $app_color_main;
border-radius: 4rpx;
border: 1rpx solid $app_color_main;
margin-right: 10rpx;
margin-bottom: 24rpx;
}
.sale-today {
background: transparent;
}
.origin-price {
color: #726e71;
text-decoration: line-through;
margin-left: 16rpx;
}
.good-salenum {
color: #9b939a;
}
.add-cart-icon {
width: 44rpx;
height: 44rpx;
position: absolute;
bottom: 26rpx;
right: 20rpx;
}
}
</style>

View File

@ -0,0 +1,739 @@
<template>
<view class="shop-details-container">
<view class="banner-swiper">
<swiper class="swiper-wrapper" circular :indicator-dots="false" :autoplay="true" @change="bannerChange">
<swiper-item v-for="(banner, i) in details.product_pic_json" :key="i">
<image class="swiper-img" :src="banner.photos_image" />
</swiper-item>
</swiper>
<view class="flex-row-center dot-box">
<view v-for="(banner, index) in bannerList" :key="index" class="dot-item"
:class="{ active: bannerIndex === index }"></view>
</view>
</view>
<view class="good-info">
<view class="info-cell">
<view class="flex-row-start label">
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
<view class="fs-20 app-fc-main label-name">{{details.sales}}人买过</view>
</view>
<view class="flex-row-between" style="margin-top: 20rpx">
<view class="">
<text class="app-fc-mark fs-28"></text>
<text class="app-fc-mark fs-28">{{picList[0].actual_price || 0 }}</text>
<!-- <text class="fs-24 origin-price">
¥{{ minPrice.price_shichang || 0 }}
</text> -->
</view>
<!-- <text class="fs-22 app-fc-cancel">
已售 {{ details.xiaoliang || 0 }}
</text> -->
</view>
<view class="fs-29 good-name">
{{ details.product_name || "" }}
</view>
<!-- <view class="flex-row-start">
<text class="fs-20 sale-btn" v-for="(label, i) in labelList" :key="i"
:class="[i > 0 ? 'sale-today app-fc-mark' : 'app-fc-white']">
{{ label }}
</text>
</view> -->
</view>
<view class="info-cell shipping-info-cell">
<view class="selected-row" @click="showSpecModal">
<text class="selected-label">已选</text>
<text class="selected-value">{{ selectedSpec || "请选择规格" }}</text>
<image class="arrow-right-icon" :src="require('./static/arrow_right.png')" />
</view>
<view class="shipping-row">
<text class="shipping-label">运费</text>
<text class="shipping-value">{{ sliverFee ? `¥${sliverFee}` : "待下单时候确认" }}</text>
</view>
<view class="service-features">
<view class="feature-item">
<image :src="`${imgPrefix}mall-detailTips.png`" class="mallPng"></image>
<!-- <view class="check-icon"></view> -->
<text class="feature-text">快递发货</text>
</view>
<view class="feature-item" style="margin-left: 20rpx;">
<!-- <view class="check-icon"></view> -->
<image :src="`${imgPrefix}mall-detailTips.png`" class="mallPng"></image>
<text class="feature-text">七天无理由退货</text>
</view>
</view>
</view>
<view class="info-cell" v-if="remarkList.length">
<view class="width-fill flex-row-between">
<text class="fs-28 postage-info">
用户评价
<text class="fs-24 app-fc-normal">
({{ details.pingjia_num }})
</text>
</text>
<view class="flex-row-end" @click="jumpToRemark">
<text class="fs-26 app-fc-main">查看全部</text>
<image class="arrow-icon" :src="require('./static/arrow_right.png')" />
</view>
</view>
<view class="remark-list">
<remark-item v-for="item in remarkList" :key="item.id" :data="item" />
</view>
</view>
</view>
<view class="fs-24 app-fc-normal rich-text-title"> 商品详情 </view>
<view class="rich-text-content" v-html="details.content"></view>
<view class="place-view"></view>
<view class="flex-row-between details-bottom">
<view class="flex-row-start">
<view class="flex-center opt-item" @click="collectAction">
<image class="opt-icon" :src="
details.shoucang_id
? `${imgPrefix}shopDetail-collect_done.png`
: `${imgPrefix}shopDetail-collect.png`
" mode="widthFix" />
<text class="fs-20 app-fc-main">收藏</text>
</view>
<view class="flex-center opt-item service" @click="jumpToWeChat">
<image class="opt-icon" :src="`${imgPrefix}shopDetail-contact.png`" mode="widthFix" />
<text class="fs-20 app-fc-main">客服</text>
</view>
<view class="flex-center opt-item service cart-item-wrapper" @click="jumpToCart">
<view class="cart-icon-wrapper">
<image class="opt-icon" :src="`${imgPrefix}shopDetail-car.png`" mode="widthFix" />
<view v-if="cartCount > 0" class="cart-badge">
<text class="cart-badge-text">{{ cartCount > 99 ? '99+' : cartCount }}</text>
</view>
</view>
<text class="fs-20 app-fc-main">购物车</text>
</view>
</view>
<view class="flex-row-start opt-btns">
<view class="flex-center fs-30 app-fc-white opt-btn opt-btn-cart" @click="addToCart">
加入购物车
</view>
<view class="flex-center fs-30 app-fc-white opt-btn" @click="goBuy">
立即购买
</view>
</view>
</view>
<add-goods-modal v-if="showModal" :optText="type === 'cart' ? '加入购物车' : '立即购买'" :data="details"
@optAction="optAction" @change="(val) => (showModal = val)" />
<contact-modal v-if="showConcactModal" @close="showConcactModal = false" />
</view>
</template>
<script>
import {
addCart,
getGoodsDetail,
collectShop,
getCartList
} from "@/api/shop";
import {
imgPrefix
} from '@/utils/common';
import RemarkItem from "@/components/goods/RemarkItem.vue";
import AddGoodsModal from "@/components/goods/AddGoodsModal.vue";
import ContactModal from "@/components/ContactModal.vue";
import {
getRemarkList
} from "../../../api/shop";
import {
jumpToWeChat,
showLoginConfirmModal
} from "@/utils/common";
export default {
components: {
RemarkItem,
AddGoodsModal,
ContactModal,
},
data() {
return {
imgPrefix,
bannerIndex: 0,
picList:[],
bannerList: [],
details: {},
showModal: false,
type: "", // cart/gobuy
goodId: "",
showConcactModal: false,
remarkList: [],
petOrderId: "",
petOrderAddressId: "",
selectedSpec: "", // 已选规格
cartCount: 0, // 购物车商品数量
};
},
computed: {
minPrice() {
let minPrice = {};
let minPriceValue = 0;
(this.details?.price_list || []).map((v) => {
if (!minPriceValue || minPriceValue > +v.price) {
minPriceValue = +v.price;
minPrice = {
...v
};
}
});
return minPrice;
},
labelList() {
return (this.details?.label || "").split(",").filter((v) => !!v);
},
sliverFee() {
return +this.details?.yunfei || 0;
},
},
onLoad(options) {
this.product_id = options.product_id
// console.log(this.product_id ,'??')
const {
id,
petOrderId = "",
petOrderAddressId = ""
} = options;
this.goodId = id;
this.petOrderId = petOrderId;
this.petOrderAddressId = petOrderAddressId;
this.getDetails(this.product_id);
// this.getRemarkListData();
this.getCartCount();
},
onShow() {
// 页面显示时更新购物车数量
this.getCartCount();
},
methods: {
jumpToWeChat,
// 跳转购物车
jumpToCart() {
uni.redirectTo({
url: '/pages/client/cart/index'
});
},
// 获取购物车数量
getCartCount() {
getCartList({
p: 1,
num: 999
}).then((res) => {
const cartList = res?.info || [];
// 计算购物车商品总数量
this.cartCount = cartList.reduce((total, item) => {
return total + (item.number || 0);
}, 0);
}).catch(() => {
this.cartCount = 0;
});
},
// 获取商品详情
getDetails(id) {
getGoodsDetail({
product_id: Number(id)
}).then((res) => {
this.details = res.data || {};
// console.log(this.details,'--')
this.picList = res.data?.prices
// console.log(this.picList,'??')
this.bannerList = this.details?.pic_list || [];
// 默认选择第一个规格
this.setDefaultSpec();
});
},
// 设置默认规格
setDefaultSpec() {
const priceList = this.details?.price_list || [];
const shuxingList = this.details?.shuxing_list || [];
if (priceList.length > 0) {
const firstPrice = priceList[0];
const firstShuxing = shuxingList.length > 0 ? shuxingList[0] : null;
// 使用 price_name 或 shuxing_name
const specName = firstPrice.price_name || firstShuxing?.name || "";
this.selectedSpec = specName ? `${specName}*1` : "";
}
},
// 获取评论列表
// getRemarkListData() {
// getRemarkList({
// p: 1,
// num: 1,
// type: 1,
// guanlian_id: this.goodId
// }).then(
// (res) => {
// this.remarkList = res?.info || [];
// }
// );
// },
// 跳转评论列表
jumpToRemark() {
uni.navigateTo({
url: `/pages/client/remark/list?shopId=${this.goodId}`,
});
},
bannerChange(e) {
this.bannerIndex = e.detail.current;
},
addToCart() {
const {
goods_id,
price_list,
shuxing_list
} = this.details;
// 只有一个规格时,直接加入购物车
// if (price_list?.length === 1 && shuxing_list.length === 1) {
// this.addCartAction({
// goods_id,
// price_id: price_list?.[0]?.price_id,
// number: 1,
// shuxing_name: shuxing_list?.[0]?.name,
// });
// return;
// }
this.showModal = true;
this.type = "cart";
},
// 加入购物车
addCartAction({number}) {
const data = {
item_id:this.details.id,
item_type:this.details.type,
price_id:this.details.prices[0].id,
price_desc:this.details.prices[0].price_desc,
item_name:this.details.product_name,
item_price:this.details.prices[0].actual_price,
number:number,
item_pic:this.details.product_pic,
is_select:1,
property:this.details.property_name,
}
// console.log(this.details,'??')
addCart(data).then(() => {
uni.showToast({
title: "已加入购物车!",
icon: "none"
});
this.showModal = false;
// 更新购物车数量
this.getCartCount();
})
.catch((err) => {
uni.showToast({
title: err || "加入购物车失败!",
icon: "none"
});
});
},
optAction({ goods_id, shuxing_name, price_id, number}) {
// if (kucun <= 0) {
// uni.showToast({
// title: "该商品已售罄",
// icon: "none",
// });
// return;
// }
// 更新已选规格显示
const priceInfo = (this.details?.price_list || []).find(
(v) => v.price_id === price_id
);
if (priceInfo) {
this.selectedSpec = `${priceInfo.price_name || shuxing_name}*${number}`;
}
if (this.type === "cart") {
// 加入购物车
this.addCartAction({
goods_id,
price_id,
number,
shuxing_name,
});
} else {
// 立即购买
this.jumpToOrder({
goods_id,
shuxing_name,
price_id,
number
});
}
},
// 显示规格选择modal
showSpecModal() {
this.showModal = true;
this.type = "cart";
},
goBuy() {
const {
goods_id,
price_list,
shuxing_list
} = this.details;
// 只有一个规格时,直接加入购物车
// if (price_list?.length === 1 && shuxing_list.length === 1) {
// this.jumpToOrder({ goods_id, shuxing_name, price_id, 1 })
// return;
// }
this.showModal = true;
this.type = "gobuy";
},
// 立即购买
jumpToOrder({
goods_id,
shuxing_name,
price_id,
number
}) {
// console.log(this.details,'--?')
// const petList = {
// original_price:this.details.prices[0].actual_price,
// actual_price:this.details.prices[0].original_price,
// reduction_amount:0,
// // pay_amount:, //数量乘实际单价
// // note:''
// }
// console.log(petList,'=-=')
console.log(number, 'number')
uni.navigateTo({
url: `/pages/client/order/create`,
success: (res) => {
// 通过eventChannel向被打开页面传送数据
const priceInfo = (this.details?.prices || []).find(
(v) => price_id === v.id
);
res.eventChannel.emit("createOrder", {
goodList: [{
...this.details,
goods_id,
shuxing_name,
price_id,
number,
goods_name: this.details.goods_name,
price_name: priceInfo?.price_name,
goods_price: +(+priceInfo?.actual_price * number).toFixed(2),
}, ],
});
},
});
},
// 收藏
async collectAction() {
const token = uni.getStorageSync('token');
if (!token) {
const willLogin = await showLoginConfirmModal();
if (!willLogin) return;
return;
}
collectShop({
goods_id: this.details.goods_id,
type: this.details.shoucang_id ? 2 : 1,
})
.then(() => {
this.getDetails();
})
.catch((err) => {
uni.showToast({
title: err || "收藏失败!",
icon: "none"
});
});
},
},
};
</script>
<style lang="scss" scoped>
.shop-details-container {
height: 100%;
position: relative;
.banner-swiper {
position: relative;
width: 100%;
height: 750rpx;
.swiper-wrapper {
width: 100%;
height: 750rpx;
.swiper-img {
width: 100%;
height: 100%;
border-radius: 40rpx;
}
}
.dot-box {
position: absolute;
bottom: 30rpx;
left: 0;
width: 100%;
.dot-item {
width: 12rpx;
height: 12rpx;
border-radius: 12rpx;
background: rgba(255, 255, 255, 0.6);
margin-right: 12rpx;
&.active {
width: 28rpx;
height: 12rpx;
background: #ffffff;
}
}
}
}
.good-info {
margin: 20rpx;
.info-cell {
background: #fff;
border-radius: 20rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.origin-price {
color: #726e71;
text-decoration: line-through;
margin-left: 18rpx;
line-height: 44rpx;
}
.good-name {
margin-top: 16rpx;
}
.sale-btn {
padding: 6rpx 12rpx;
background: $app_color_main;
border-radius: 4rpx;
border: 1rpx solid $app_color_main;
margin-right: 12rpx;
margin-bottom: 24rpx;
&.sale-today {
background: transparent;
}
}
.postage-info {
color: #9b939a;
margin-right: 32rpx;
}
.arrow-icon {
width: 11rpx;
height: 18rpx;
margin-left: 20rpx;
}
.shipping-info-cell {
.selected-row {
display: flex;
align-items: center;
margin-bottom: 24rpx;
cursor: pointer;
.selected-label {
font-size: 24rpx;
color: #999;
margin-right: 12rpx;
}
.selected-value {
flex: 1;
font-size: 24rpx;
color: #3D3D3D;
}
.arrow-right-icon {
width: 11rpx;
height: 18rpx;
flex-shrink: 0;
}
}
.shipping-row {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.shipping-label {
font-size: 24rpx;
color: #999;
margin-right: 12rpx;
}
.shipping-value {
font-size: 24rpx;
color: #3D3D3D;
}
}
.service-features {
background: #F5F5F5;
border-radius: 12rpx;
padding: 12rpx 20rpx;
display: flex;
align-items: center;
border-radius: 339px;
.feature-item {
display: flex;
align-items: center;
.mallPng {
width: 20rpx;
height: 20rpx;
}
.feature-text {
font-size: 20rpx;
color: #3D3D3D;
margin-left: 4rpx;
}
}
}
}
.remark-list {
border-top: 2rpx solid #ebebeb;
margin-top: 36rpx;
::v-deep {
.remark-item {
width: 100%;
margin: 0;
padding: 40rpx 0;
}
}
}
}
.rich-text-title {
margin: 32rpx 0 24rpx;
text-align: center;
}
.rich-text-content {
background: #fff;
padding: 24rpx;
}
.place-view {
height: 190rpx;
}
.details-bottom {
position: fixed;
bottom: 0;
padding-bottom: 20rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
padding-top: 20rpx;
padding-left: 40rpx;
padding-right: 36rpx;
left: 0;
width: 100vw;
background: #fff;
box-sizing: border-box;
.opt-item {
&.service {
margin-left: 36rpx;
}
.opt-icon {
width: 33rpx;
height: 33rpx;
margin-bottom: 8rpx;
}
&.cart-item-wrapper {
.cart-icon-wrapper {
position: relative;
display: inline-block;
}
.cart-badge {
position: absolute;
top: -16rpx;
right: -16rpx;
min-width: 32rpx;
height: 32rpx;
background: #FF19A0;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8rpx;
box-sizing: border-box;
.cart-badge-text {
color: #FFFFFF;
font-size: 20rpx;
font-weight: 500;
line-height: 1;
}
}
}
}
.opt-btns {
width: 376rpx;
height: 76rpx;
border-radius: 92rpx;
overflow: hidden;
.opt-btn {
width: 188rpx;
height: 100%;
background: $app_color_main;
font-size: 28rpx;
&.opt-btn-cart {
background: #F8C142;
}
}
}
}
}
.label {
background-color: #ffecf3;
display: inline-flex;
border-radius: 4rpx;
.hot-icon {
width: 28rpx;
height: 28rpx;
}
.label-name {
padding: 4rpx;
color: #FF19A0;
}
}
</style>

View File

@ -0,0 +1,749 @@
<template>
<view class="flex-column-start shop-container">
<view class="shop-header">
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="header-content">
<view class="search-bar" :style="{ width: searchBarWidth + 'rpx' }" @click="jumpToSearch">
<text class="search-icon">🔍</text>
<text class="search-placeholder">猫猫主粮</text>
</view>
<view class="category-tabs-wrapper">
<!-- 左侧固定综合 -->
<view class="category-tab fixed-left" :class="{ active: activeCategoryIndex === 0 }"
@click="switchCategory(0)">
综合
</view>
<!-- 中间可滚动动态分类数据 -->
<scroll-view class="category-tabs-scroll" scroll-x>
<view class="category-tab" v-for="(tab, index) in classifyList" :key="index"
:class="{ active: activeCategoryIndex === tab.id }" @click="switchCategory(tab.id)">
{{ tab.type_name }}
</view>
</scroll-view>
<!-- 右侧固定分类 -->
<view class="category-tab fixed-right"
:class="{ active: activeCategoryIndex === classifyList.length + 1 }" @click="jumpToSearch">
<image class="category-icon" :src="`${imgPrefix}mall-category.png`" />
分类
</view>
</view>
</view>
</view>
<scroll-view class="shop-content" :style="{ paddingTop: headerHeight + 'rpx' }" scroll-y
:refresher-enabled="true" :refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh"
@scrolltolower="onLoadMore">
<view>
<image class="info-avator" src="https://activity.wagoo.live/bannerMall.png" />
</view>
<!-- <view class="banner-swiper">
<swiper class="swiper-wrapper" circular :indicator-dots="false" :autoplay="true" @change="bannerChange">
<swiper-item v-for="banner in bannerList" :key="banner.ad_id" @click="jumpTo(banner.ad_url)">
<image class="swiper-img" :src="banner.thumb" />
</swiper-item>
</swiper>
<view class="flex-row-center dot-box">
<view v-for="(banner, index) in bannerList" :key="banner.ad_id" class="dot-item"
:class="{ active: bannerIndex === index }"></view>
</view>
</view> -->
<!-- <view class="flex-row-start shop-classify">
<view
class="flex-center classify-item"
v-for="classify in classifyList"
:key="classify.id"
@click="jumpToCategory(classify)"
>
<image class="classify-img" :src="classify.select_type_pic" />
<text class="app-fc-main fs-26 app-font-bold">{{
classify.name
}}</text>
</view>
</view> -->
<!-- <view
v-if="couponList.length"
class="flex-row-start coupon-view"
@click="showCouponModal = true"
>
<image
class="coupon-icon"
src="https://activity.wagoo.live/new_account_coupon.png"
/>
<text class="flex-1 app-fc-mark fs-24 app-text-ellipse">
您有{{
couponList.length === 1
? `一张${couponList[0].card_money || 0}`
: "多张"
}}优惠券待领取
</text>
<image
class="coupon-arrow"
src="@/static/images/arrow_mark.png"
mode="heightFix"
/>
</view> -->
<view class="recommand-goods-wrapper">
<!-- <view class="fs-36 app-font-bold app-fc-main recommand-title">
推荐商品
</view> -->
<view class="goods-list">
<view class="goods-list-item left">
<good-item v-for="(good, i) in leftColumnGoods" :index="2 * i" :key="2 * i" :data="good"
@addToCar="addToCar" />
</view>
<view class="goods-list-item">
<good-item v-for="(good, i) in rightColumnGoods" :index="2 * i + 1" :key="2 * i + 1"
:data="good" @addToCar="addToCar" />
</view>
</view>
<view v-if="isLoadingGoods" class="loading-wrapper flex-center">
<uni-load-more status="loading" :show-text="false" />
</view>
</view>
</scroll-view>
<view class="add-car-view">
<image class="add-car-icon" :src="`${imgPrefix}mall-shopCar.png`" @click="jumpToCart" />
</view>
<view v-if="cartShowCount" class="fs-20 app-fc-white flex-center cart-count">
{{ cartShowCount }}
</view>
<view class="contact-icon-view">
<image class="contact-icon" :src="`${imgPrefix}supportStaff.png`" @click="jumpToWeChat" />
<view class="contact-btn fs-20">
联系客服
</view>
</view>
<add-goods-modal v-if="showModal" :data="addGoodInfo" optText="加入购物车" @change="(val) => (showModal = val)"
@optAction="optAction" />
<coupon-modal v-if="showCouponModal" :couponList="couponList" @close="onCouponModalClose"
@getCoupon="getCoupon" />
<contact-modal v-if="showContactModal" @close="showContactModal = false" />
</view>
</template>
<script>
import {
imgPrefix
} from '@/utils/common';
import GoodItem from "./components/GoodItem.vue";
import AddGoodsModal from "@/components/goods/AddGoodsModal.vue";
import CouponModal from "./components/CouponModal.vue";
import ContactModal from "@/components/ContactModal.vue";
import {
getGoodsClassify,
getGoodsListData
} from "@/api/shop";
import {
getCouponData
} from "@/api/coupon";
import {
addCart,
getCartList
} from "../../../api/shop";
import {
getImageList
} from "../../../api/common";
import {
jumpToWeChat
} from "@/utils/common";
export default {
components: {
GoodItem,
AddGoodsModal,
CouponModal,
ContactModal,
},
data() {
const systemInfo = uni.getSystemInfoSync();
// 获取胶囊按钮位置信息,计算搜索栏宽度
// 默认宽度750rpx (标准屏幕宽度) - 32rpx (左侧padding) - 右侧到胶囊的距离
let searchBarWidth = 686;
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
if (menuButtonInfo && menuButtonInfo.left) {
// 屏幕宽度转换为 rpx (750rpx 对应 375px)
const screenWidthRpx = 750;
const leftPadding = 32; // header-content 左侧 padding
// 右侧需要留出的空间:屏幕宽度(px) - 胶囊按钮左边距(px) + 额外间距(px),然后转换为 rpx
const rightSpacePx = systemInfo.windowWidth - menuButtonInfo.left + 20;
const rightSpaceRpx = (rightSpacePx / systemInfo.windowWidth) * screenWidthRpx;
searchBarWidth = screenWidthRpx - leftPadding - rightSpaceRpx;
}
} catch (e) {
console.log('获取胶囊按钮信息失败', e);
}
// 计算 shop-header 的总高度
// status-bar 高度(px转rpx) + header-content 高度
// header-content: 20rpx(上padding) + 72rpx(搜索栏) + 24rpx(margin-bottom) + 约40rpx(分类标签) + 20rpx(下padding)
const statusBarHeightRpx = (systemInfo.statusBarHeight || 0) * 2; // px转rpx
const headerContentHeight = 20 + 72 + 10 + 40 + 20; // 约176rpx
const headerHeight = statusBarHeightRpx + headerContentHeight;
return {
statusBarHeight: systemInfo.statusBarHeight || 0,
headerHeight: headerHeight,
searchBarWidth: searchBarWidth,
activeCategoryIndex: 0,
// categoryTabs: ['综合', '宠物食品', '宠物洗护', '宠物用品', '宠物健康'],
refreshTriggered: false,
bannerList: [{
id: 1,
image: 'https://activity.wagoo.live/mine_pet.png',
}
],
classifyList: [],
bannerIndex: 0,
goodsList: [],
isLoadingGoods: false,
goodsTotal: 0,
goodPage: 1,
goodSize: 10,
couponList: [],
showModal: false,
addGoodInfo: null,
showCouponModal: false,
unGetCouponList: [], // 待领取的优惠券列表
showContactModal: false,
cartCount: 0,
imgPrefix
};
},
computed: {
leftColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 0);
},
rightColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 1);
},
cartShowCount() {
return this.cartCount > 9 ? "9+" : this.cartCount;
},
},
// watch: {
// showCouponModal(val) {
// if (val) {
// this.getCouponList();
// }
// },
// },
mounted() {
// this.getCouponList();
},
methods: {
jumpToWeChat,
onShowFun() {
this.initData();
},
initData() {
this.getGoodsCategory();
// this.getCouponList();
this.goodPage = 1;
this.getGoodsList();
// this.getBannerList();
this.getCartListData();
},
bannerChange(e) {
this.bannerIndex = e.detail.current;
},
onRefresh() {
if (this.refreshTriggered) return;
this.refreshTriggered = true;
// 请求商品列表/轮播图/商品分类/优惠券
this.initData();
},
onLoadMore() {
if (!this.isLoadingGoods && this.goodsTotal > this.goodsList.length) {
this.goodPage++;
this.getGoodsList();
}
},
// 获取商品列表
getGoodsList() {
if (this.isLoadingGoods) return;
this.isLoadingGoods = true;
const params = {
type: this.activeCategoryIndex
}
getGoodsListData(params)
.then((res) => {
const list = res?.data || [];
this.goodsList =
this.goodPage === 1 ? list : [...this.goodsList, ...list];
this.goodsTotal = res?.count || 0;
})
.finally(() => {
this.isLoadingGoods = false;
this.refreshTriggered = false;
});
},
// 获取商品分类
getGoodsCategory() {
getGoodsClassify()
.then((res) => {
this.classifyList = res?.data || [];
})
.finally(() => {
this.refreshTriggered = false;
});
},
// 获取待领取的新人优惠券
getCouponList() {
getCouponData({
p: 1,
num: 9999,
is_lingqu: 1,
is_xinren: 1,
type: 1,
})
.then((res) => {
this.couponList = res?.info || [];
})
.finally(() => {
this.refreshTriggered = false;
});
},
// 加入购物车
addCartAction({
goods_id,
price_id,
number,
shuxing_name
}) {
addCart({
goods_id,
price_id,
number,
shuxing_name,
})
.then(() => {
uni.showToast({
title: "已加入购物车!",
icon: "none"
});
this.getCartListData();
})
.catch((err) => {
uni.showToast({
title: err || "加入购物车失败!",
icon: "none"
});
});
},
// 轮播图片列表
// getBannerList() {
// getImageList(3).then((res) => {
// this.bannerList = res?.info || [];
// });
// },
// 购物车列表
getCartListData() {
getCartList({
p: 1,
num: 999
}).then((res) => {
this.cartCount = (res?.data.list || []).reduce(
(total, prev) => total + prev.number,
0
);
console.log(this.cartCount,'--=?')
});
},
optAction(data) {
const {
goods_id,
price_id,
number,
shuxing_name,
kucun
} = data;
if (kucun <= 0) {
uni.showToast({
title: "该商品已售罄",
icon: "none",
});
return;
}
this.addCartAction({
goods_id,
price_id,
number,
shuxing_name
});
this.showModal = false;
},
addToCar(good) {
const {
goods_id,
price_list,
shuxing_list
} = good;
if (price_list?.length === 1 && shuxing_list.length === 1) {
if (price_list?.[0]?.kucun <= 0) {
uni.showToast({
title: "该商品已售罄",
icon: "none",
});
return;
}
this.addCartAction({
goods_id,
price_id: price_list?.[0]?.price_id,
number: 1,
shuxing_name: shuxing_list?.[0]?.name,
});
return;
}
this.showModal = true;
this.addGoodInfo = {
...good
};
},
jumpToCategory(category) {
uni.navigateTo({
url: `/pages/client/category/index?id=${category.id}`,
});
},
jumpToCart() {
uni.navigateTo({
url: `/pages/client/cart/index`,
});
},
jumpTo(url) {
console.log(1111, url)
uni.navigateTo({
url
})
},
onCouponModalClose() {
this.showCouponModal = false;
this.getCouponList();
},
getCoupon() {
this.showCouponModal = false;
uni.navigateTo({
url: "/pages/client/coupon/get-list",
});
},
jumpToSearch() {
// 跳转到搜索页面
uni.navigateTo({
url: '/pages/client/category/index'
});
},
switchCategory(_id) {
this.activeCategoryIndex = _id;
// 可以根据分类切换商品列表
this.goodPage = 1;
this.getGoodsList();
},
},
};
</script>
<style lang="scss" scoped>
.shop-container {
width: 100%;
height: 100%;
align-items: stretch;
background: #f7f8fa;
.shop-header {
background: #ff19a0;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
border-radius: 0px 0px 16px 16px;
.status-bar {
background: #ff19a0;
}
.header-content {
padding: 20rpx 32rpx;
background: #ff19a0;
padding-top: 0;
border-radius: 0px 0px 16px 16px;
.search-bar {
background: #fff;
border-radius: 100rpx;
height: 72rpx;
display: flex;
align-items: center;
padding: 0 24rpx;
margin-bottom: 24rpx;
box-sizing: border-box;
max-width: 100%;
.search-icon {
font-size: 32rpx;
color: #999;
margin-right: 16rpx;
}
.search-placeholder {
flex: 1;
font-size: 28rpx;
color: #999;
}
.search-actions {
display: flex;
align-items: center;
gap: 16rpx;
.action-icon {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #333;
border: 1rpx solid #eee;
}
}
}
.category-tabs-wrapper {
display: flex;
align-items: center;
width: 100%;
.category-tab {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
white-space: nowrap;
transition: all 0.3s;
&.active {
color: #fff;
font-weight: bold;
}
&.fixed-left {
padding: 0 24rpx;
flex-shrink: 0;
}
&.fixed-right {
margin-left: 16rpx;
flex-shrink: 0;
position: relative;
color: #fff;
&::before {
content: '';
position: absolute;
left: -16rpx;
top: 50%;
transform: translateY(-50%);
width: 1rpx;
height: 24rpx;
background: rgba(255, 255, 255, 0.5);
}
}
.category-icon {
width: 20rpx;
height: 17rpx;
margin-right: 6rpx;
}
}
.category-tabs-scroll {
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
.category-tab {
display: inline-block;
padding: 0 24rpx;
}
}
}
}
}
.shop-content {
background: #ffecf3;
padding: 20rpx;
box-sizing: border-box;
flex: 1;
overflow: hidden;
.info-avator{
width: 100%;
height: 260rpx;
}
.banner-swiper {
position: relative;
width: 100%;
height: 260rpx;
.swiper-wrapper {
width: 100%;
height: 260rpx;
.swiper-img {
width: 100%;
height: 100%;
border-radius: 40rpx;
}
}
.dot-box {
position: absolute;
bottom: 12rpx;
left: 0;
width: 100%;
.dot-item {
width: 8rpx;
height: 8rpx;
border-radius: 8rpx;
background: rgba(255, 255, 255, 0.6);
margin-right: 8rpx;
&.active {
width: 20rpx;
height: 8rpx;
background: #ffffff;
}
}
}
}
.shop-classify {
margin: 52rpx 0 44rpx;
.classify-item {
width: 25%;
.classify-img {
width: 100rpx;
height: 100rpx;
margin-bottom: 22rpx;
border-radius: 100rpx;
}
}
}
.coupon-view {
width: 100%;
height: 56rpx;
background: #fee9f3;
border-radius: 16rpx;
padding-right: 30rpx;
box-sizing: border-box;
margin-bottom: 40rpx;
.coupon-icon {
height: 100%;
width: 186rpx;
margin-right: 16rpx;
}
.coupon-arrow {
width: 10rpx;
height: 14rpx;
margin-left: 16rpx;
}
}
.recommand-goods-wrapper {
.recommand-title {
margin-bottom: 10rpx;
}
.goods-list {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
margin-top: 16rpx;
.goods-list-item {
flex: 1;
min-width: 0;
&.left {
margin-right: 20rpx;
}
}
}
.loading-wrapper {
padding: 10rpx 0;
}
}
}
.add-car-view {
position: fixed;
bottom: 35vh;
right: 20rpx;
background-color: #fff;
border-radius: 50%;
padding: 20rpx;
box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: center;
align-items: center;
.add-car-icon {
width: 48rpx;
height: 48rpx;
}
}
.contact-icon-view {
position: fixed;
bottom: calc(35vh - 130rpx - 48rpx);
right: 20rpx;
text-align: center;
.contact-icon {
width: 66rpx;
height: 66rpx;
margin: auto;
}
.contact-btn {
color: #FFFFFF;
background-color: #FF19A0;
border-radius: 257px;
padding: 6rpx 8rpx;
transform: translateY(-8px);
}
}
.cart-count {
position: fixed;
bottom: calc(35vh + 88rpx - 20rpx);
right: 20rpx;
width: 30rpx;
height: 30rpx;
border-radius: 30rpx;
background: #FF19A0;
z-index: 10;
border: 1px solid #FFFFFF;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB