项目初始化

This commit is contained in:
jerry
2025-01-21 01:46:34 +08:00
parent 364021b042
commit 48153e7761
962 changed files with 172070 additions and 0 deletions

305
pages/user/address/edit.vue Normal file
View File

@@ -0,0 +1,305 @@
<!-- 收货地址的新增/编辑 -->
<template>
<s-layout :title="state.model.id ? '编辑地址' : '新增地址'">
<uni-forms
ref="addressFormRef"
v-model="state.model"
:rules="rules"
validateTrigger="bind"
labelWidth="160"
labelAlign="left"
border
:labelStyle="{ fontWeight: 'bold' }"
>
<view class="bg-white form-box ss-p-x-30">
<uni-forms-item name="name" label="收货人" class="form-item">
<uni-easyinput
v-model="state.model.name"
placeholder="请填写收货人姓名"
:inputBorder="false"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
/>
</uni-forms-item>
<uni-forms-item name="mobile" label="手机号" class="form-item">
<uni-easyinput
v-model="state.model.mobile"
type="number"
placeholder="请输入手机号"
:inputBorder="false"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item
name="areaName"
label="省市区"
@tap="state.showRegion = true"
class="form-item"
>
<uni-easyinput
v-model="state.model.areaName"
disabled
:inputBorder="false"
:styles="{ disableColor: '#fff', color: '#333' }"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
placeholder="请选择省市区"
>
<template v-slot:right>
<uni-icons type="right" />
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item
name="detailAddress"
label="详细地址"
:formItemStyle="{ alignItems: 'flex-start' }"
:labelStyle="{ lineHeight: '5em' }"
class="textarea-item"
>
<uni-easyinput
:inputBorder="false"
type="textarea"
v-model="state.model.detailAddress"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
placeholder="请输入详细地址"
clearable
/>
</uni-forms-item>
</view>
<view class="ss-m-y-20 bg-white ss-p-x-30 ss-flex ss-row-between ss-col-center default-box">
<view class="default-box-title"> 设为默认地址 </view>
<su-switch style="transform: scale(0.8)" v-model="state.model.defaultStatus" />
</view>
</uni-forms>
<su-fixed bottom :opacity="false" bg="" placeholder :noFixed="false" :index="10">
<view class="footer-box ss-flex-col ss-row-between ss-p-20">
<view class="ss-m-b-20">
<button class="ss-reset-button save-btn ui-Shadow-Main" @tap="onSave">保存</button>
</view>
<button v-if="state.model.id" class="ss-reset-button cancel-btn" @tap="onDelete">
删除
</button>
</view>
</su-fixed>
<!-- 省市区弹窗 -->
<su-region-picker
:show="state.showRegion"
@cancel="state.showRegion = false"
@confirm="onRegionConfirm"
/>
</s-layout>
</template>
<script setup>
import { ref, reactive, unref } from 'vue';
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import _ from 'lodash-es';
import { mobile } from '@/sheep/validate/form';
import AreaApi from '@/sheep/api/system/area';
import AddressApi from '@/sheep/api/member/address';
const addressFormRef = ref(null);
const state = reactive({
showRegion: false,
model: {
name: '',
mobile: '',
detailAddress: '',
defaultStatus: false,
areaName: '',
},
rules: {},
});
const rules = {
name: {
rules: [
{
required: true,
errorMessage: '请输入收货人姓名',
},
],
},
mobile,
detailAddress: {
rules: [
{
required: true,
errorMessage: '请输入详细地址',
},
],
},
areaName: {
rules: [
{
required: true,
errorMessage: '请选择您的位置',
},
],
},
};
// 确认选择地区
const onRegionConfirm = (e) => {
state.model.areaName = `${e.province_name} ${e.city_name} ${e.district_name}`;
state.model.areaId = e.district_id;
state.showRegion = false;
};
// 获得地区数据
const getAreaData = () => {
if (_.isEmpty(uni.getStorageSync('areaData'))) {
AreaApi.getAreaTree().then((res) => {
if (res.code === 0) {
uni.setStorageSync('areaData', res.data);
}
});
}
};
// 保存收货地址
const onSave = async () => {
// 参数校验
const validate = await unref(addressFormRef)
.validate()
.catch((error) => {
console.log('error: ', error);
});
if (!validate) {
return;
}
// 提交请求
const formData = {
...state.model,
};
const { code } =
state.model.id > 0
? await AddressApi.updateAddress(formData)
: await AddressApi.createAddress(formData);
if (code === 0) {
sheep.$router.back();
}
};
// 删除收货地址
const onDelete = () => {
uni.showModal({
title: '提示',
content: '确认删除此收货地址吗?',
success: async function (res) {
if (!res.confirm) {
return;
}
const { code } = await AddressApi.deleteAddress(state.model.id);
if (code === 0) {
sheep.$router.back();
}
},
});
};
onLoad(async (options) => {
// 获得地区数据
getAreaData();
// 情况一:基于 id 获得收件地址
if (options.id) {
let { code, data } = await AddressApi.getAddress(options.id);
if (code !== 0) {
return;
}
state.model = data;
}
// 情况二:微信导入
if (options.data) {
let data = JSON.parse(options.data);
const areaData = uni.getStorageSync('areaData');
const findAreaByName = (areas, name) => areas.find((item) => item.name === name);
let provinceObj = findAreaByName(areaData, data.province_name);
let cityObj = provinceObj ? findAreaByName(provinceObj.children, data.city_name) : undefined;
let districtObj = cityObj ? findAreaByName(cityObj.children, data.district_name) : undefined;
let areaId = (districtObj || cityObj || provinceObj).id;
state.model = {
...state.model,
areaId,
areaName: [data.province_name, data.city_name, data.district_name]
.filter(Boolean)
.join(' '),
defaultStatus: false,
detailAddress: data.address,
mobile: data.mobile,
name: data.consignee,
};
}
});
</script>
<style lang="scss" scoped>
:deep() {
.uni-forms-item__label .label-text {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
}
.uni-easyinput__content-input {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
padding-left: 0 !important;
}
.uni-easyinput__content-textarea {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
margin-top: 8rpx !important;
}
.uni-icons {
font-size: 40rpx !important;
}
.is-textarea-icon {
margin-top: 22rpx;
}
.is-disabled {
color: #333333;
}
}
.default-box {
width: 100%;
box-sizing: border-box;
height: 100rpx;
.default-box-title {
font-size: 28rpx;
color: #333333;
line-height: normal;
}
}
.footer-box {
.save-btn {
width: 710rpx;
height: 80rpx;
border-radius: 40rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: $white;
}
.cancel-btn {
width: 710rpx;
height: 80rpx;
border-radius: 40rpx;
background: var(--ui-BG);
}
}
</style>

165
pages/user/address/list.vue Normal file
View File

@@ -0,0 +1,165 @@
<!-- 收件地址列表 -->
<template>
<s-layout title="收货地址" :bgStyle="{ color: '#FFF' }">
<view v-if="state.list.length">
<s-address-item
hasBorderBottom
v-for="item in state.list"
:key="item.id"
:item="item"
@tap="onSelect(item)"
/>
</view>
<su-fixed bottom placeholder>
<view class="footer-box ss-flex ss-row-between ss-p-20">
<!-- 微信小程序和微信H5 -->
<button
v-if="['WechatMiniProgram', 'WechatOfficialAccount'].includes(sheep.$platform.name)"
@tap="importWechatAddress"
class="border ss-reset-button sync-wxaddress ss-m-20 ss-flex ss-row-center ss-col-center"
>
<text class="cicon-weixin ss-p-r-10" style="color: #09bb07; font-size: 40rpx"></text>
导入微信地址
</button>
<button
class="add-btn ss-reset-button ui-Shadow-Main"
@tap="sheep.$router.go('/pages/user/address/edit')"
>
新增收货地址
</button>
</view>
</su-fixed>
<s-empty
v-if="state.list.length === 0 && !state.loading"
text="暂无收货地址"
icon="/static/data-empty.png"
/>
</s-layout>
</template>
<script setup>
import { onBeforeMount, reactive } from 'vue';
import { onShow, onLoad } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import { isEmpty } from 'lodash-es';
import AreaApi from '@/sheep/api/system/area';
import AddressApi from '@/sheep/api/member/address';
const state = reactive({
list: [], // 地址列表
loading: true,
openType: '', // 页面打开类型
});
// 选择收货地址
const onSelect = (addressInfo) => {
if (state.openType !== 'select'){ // 不作为选择组件时阻断操作
return
}
uni.$emit('SELECT_ADDRESS', {
addressInfo,
});
sheep.$router.back();
};
// 导入微信地址
function importWechatAddress() {
let wechatAddress = {};
// #ifdef MP
uni.chooseAddress({
success: (res) => {
wechatAddress = {
consignee: res.userName,
mobile: res.telNumber,
province_name: res.provinceName,
city_name: res.cityName,
district_name: res.countyName,
address: res.detailInfo,
region: '',
is_default: false,
};
if (!isEmpty(wechatAddress)) {
sheep.$router.go('/pages/user/address/edit', {
data: JSON.stringify(wechatAddress),
});
}
},
fail: (err) => {
console.log('%cuni.chooseAddress,调用失败', 'color:green;background:yellow');
},
});
// #endif
// #ifdef H5
sheep.$platform.useProvider('wechat').jssdk.openAddress({
success: (res) => {
wechatAddress = {
consignee: res.userName,
mobile: res.telNumber,
province_name: res.provinceName,
city_name: res.cityName,
district_name: res.countryName,
address: res.detailInfo,
region: '',
is_default: false,
};
if (!isEmpty(wechatAddress)) {
sheep.$router.go('/pages/user/address/edit', {
data: JSON.stringify(wechatAddress),
});
}
},
});
// #endif
}
onLoad((option) => {
if (option.type) {
state.openType = option.type;
}
});
onShow(async () => {
state.list = (await AddressApi.getAddressList()).data;
state.loading = false;
});
onBeforeMount(() => {
if (!!uni.getStorageSync('areaData')) {
return;
}
// 提前加载省市区数据
AreaApi.getAreaTree().then((res) => {
if (res.code === 0) {
uni.setStorageSync('areaData', res.data);
}
});
});
</script>
<style lang="scss" scoped>
.footer-box {
.add-btn {
flex: 1;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 80rpx;
font-size: 30rpx;
font-weight: 500;
line-height: 80rpx;
color: $white;
position: relative;
z-index: 1;
}
.sync-wxaddress {
flex: 1;
line-height: 80rpx;
background: $white;
border-radius: 80rpx;
font-size: 30rpx;
font-weight: 500;
color: $dark-6;
margin-right: 18rpx;
}
}
</style>

View File

@@ -0,0 +1,221 @@
<template>
<su-fixed alway :bgStyles="{ background: '#fff' }" :val="0" noNav opacity :placeholder="false">
<su-status-bar />
<view
class="ui-bar ss-flex ss-col-center ss-row-between ss-p-x-20"
:style="[{ height: sys_navBar - sys_statusBar + 'px' }]"
>
<!-- -->
<view class="icon-box ss-flex">
<view class="icon-button icon-button-left ss-flex ss-row-center" @tap="onClickLeft">
<text :class="tabOpacityVal > 0.4 ? 'black' : ''" class="sicon-back" v-if="hasHistory" />
<text :class="tabOpacityVal > 0.4 ? 'black' : ''" class="sicon-home" v-else />
</view>
<view class="line"></view>
<view class="icon-button icon-button-right ss-flex ss-row-center" @tap="onClickRight">
<text :class="tabOpacityVal > 0.4 ? 'black' : ''" class="sicon-more" />
</view>
</view>
<!-- -->
<view class="detail-tab-card ss-flex-1" :style="[{ opacity: tabOpacityVal }]">
<view class="tab-box ss-flex ss-col-center ss-row-around">
<view
class="tab-item ss-flex-1 ss-flex ss-row-center ss-col-center"
v-for="item in tabList"
:key="item.value"
@tap="onTab(item)"
>
<view class="tab-title" :class="currentTab === item.value ? 'cur-tab-title' : ''">
{{ item.label }}
</view>
<view v-show="currentTab === item.value" class="tab-line"></view>
</view>
</view>
</view>
<!-- #ifdef MP -->
<view :style="[capsuleStyle]"></view>
<!-- #endif -->
</view>
</su-fixed>
</template>
<script>
import sheep from '@/sheep';
import { showMenuTools, closeMenuTools } from '@/sheep/hooks/useModal';
export default {
components: {
},
props: {
title: {
type: String,
default: '',
},
//滚动条滚动距离
scrollTop: {
type: [Number, String],
default: 0
},
},
data() {
return {
sys_navBar: sheep.$platform.navbar,
sys_statusBar: sheep.$platform.device.statusBarHeight,
hasHistory: sheep.$router.hasHistory(),
tabList: [
{
label: '礼物墙',
value: 0,
},
{
label: '动态',
value: 1,
},
],
capsuleStyle: {
width: sheep.$platform.capsule.width + 'px',
height: sheep.$platform.capsule.height + 'px',
},
}
},
computed: {
tabOpacityVal() {
return this.scrollTop > sheep.$platform.navbar ? 1 : this.scrollTop * 0.01;;
},
currentTab() {
return sheep.$store('sys').userTabIndex;
},
},
methods: {
onClickLeft() {
if (this.hasHistory) {
sheep.$router.back();
} else {
sheep.$router.go('/pages/tabbar/index');
}
},
onClickRight() {
showMenuTools();
},
onTab(e) {
this.$emit('onTab', e.value);
},
}
}
</script>
<style lang="scss" scoped>
.icon-box {
box-shadow: 0px 0px 4rpx rgba(51, 51, 51, 0.08), 0px 4rpx 6rpx 2rpx rgba(102, 102, 102, 0.12);
border-radius: 30rpx;
width: 134rpx;
height: 56rpx;
margin-left: 8rpx;
border: 1px solid rgba(#fff, 0.4);
.line {
width: 2rpx;
height: 24rpx;
background: #e5e5e7;
}
.sicon-back {
font-size: 32rpx;
color: #fff;
}
.sicon-home {
font-size: 32rpx;
color: #fff;
}
.sicon-more {
font-size: 32rpx;
color: #fff;
}
.black {
color: #000;
}
.icon-button {
width: 67rpx;
height: 56rpx;
&-left:hover {
background: rgba(0, 0, 0, 0.16);
border-radius: 30rpx 0px 0px 30rpx;
}
&-right:hover {
background: rgba(0, 0, 0, 0.16);
border-radius: 0px 30rpx 30rpx 0px;
}
}
}
.left-box {
position: relative;
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
.circle {
position: absolute;
left: 0;
top: 0;
width: 60rpx;
height: 60rpx;
background: rgba(#fff, 0.6);
border: 1rpx solid #ebebeb;
border-radius: 50%;
box-sizing: border-box;
z-index: -1;
}
}
.right {
position: relative;
width: 60rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
.circle {
position: absolute;
left: 0;
top: 0;
width: 60rpx;
height: 60rpx;
background: rgba(#ffffff, 0.6);
border: 1rpx solid #ebebeb;
box-sizing: border-box;
border-radius: 50%;
z-index: -1;
}
}
.detail-tab-card {
width: 50%;
.tab-item {
height: 80rpx;
position: relative;
z-index: 11;
.tab-title {
font-size: 30rpx;
}
.cur-tab-title {
font-weight: $font-weight-bold;
}
.tab-line {
width: 60rpx;
height: 6rpx;
border-radius: 6rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 10rpx;
background-color: var(--ui-BG-Main);
z-index: 12;
}
}
}
</style>

View File

@@ -0,0 +1,316 @@
<template>
<view v-show="showPop" class="gift-box">
<scroll-view style="height: 100%;" scroll-y>
<view class="top-box">
<view class="title">选择礼物</view>
<view class="btn-box" @click="close">
<u-icon name="close"></u-icon>
<view class="text">取消</view>
</view>
</view>
<view class="list-box">
<view v-for="(item,index) in dataList" class="gift" @click="sendGift(item)">
<view class="img-box">
<img class="img" :src="item.img"></img>
<view class="tag" v-if="item.giftType == 1">
<text>特效</text>
<text v-if="item.tag">·{{item.tag}}</text>
</view>
<view class="tag" v-if="item.giftType == 0 && item.tag">
<text>{{item.tag}}</text>
</view>
</view>
<view class="name">{{item.name}}</view>
<view class="price">{{ fen2yuan(item.money) }} 钻石</view>
</view>
</view>
</scroll-view>
</view>
<view class="svga-box" :class="giftFlag ? 'svga-show': 'svga-hide'">
<c-svga ref="cSvgaRef" :canvasId='canvasId' :src="src" :loops='0' :auto-play="false" @frame='onFrame' @finished='onFinished' @percentage='onPercentage' @loaded='onLoaded'></c-svga>
<view class="close-btn">
<view class="bottom-box">
<view class="title">{{gift.name}}</view>
<view class="price">{{ fen2yuan(gift.money) }} 钻石</view>
<view class="btn-box">
<view class="btn" @click="cannel">取消</view>
<view class="btn active" @click="ok">确定</view>
</view>
</view>
</view>
</view>
</template>
<script>
import sheep from '@/sheep';
export default {
components: {
},
props: {
modelValue: {
type: Object,
default: {}
},
showPop: {
type: Boolean,
default: false
},
dataList: {
type: Array,
default: []
},
},
emits: ["close", "update:modelValue"],
data() {
return {
giftFlag: false,
src: '',
canvasId: 'myCanvas2',
gift: {},
}
},
methods: {
close() {
this.$emit('close');
},
sendGift(e) {
this.gift = e;
if(e.giftType == 0){
// 普通礼物不播放
this.close();
this.$emit('update:modelValue', this.gift);
return;
}
this.src = e.pic;
this.giftFlag = true;
},
onFinished() {
this.giftFlag = false;
console.log('动画停止播放时回调');
},
onFrame(frame) {//动画播放至某帧后回调
// console.log(frame);
},
onPercentage(percentage) { //动画播放至某进度后回调
// console.log(percentage);
},
onLoaded() {
this.$refs.cSvgaRef.call('setContentMode', 'AspectFill');
console.log('加载完成');
this.$refs.cSvgaRef.call('startAnimation');
},
closeSvga() {
this.src = "";
this.$refs.cSvgaRef.call('stopAnimation');
this.giftFlag = false;
},
fen2yuan(price) {
var f = 0;
var p = (price / 100.0).toFixed(0);
var p1 = (price / 100.0).toFixed(1);
var p2 = (price / 100.0).toFixed(2);
if(p*100 == price){
f = 0;
}else if(p1*100 == price){
f = 1;
}else if(p2*100 == price){
f = 2;
}
return (price / 100.0).toFixed(f)
},
cannel() {
this.closeSvga();
},
ok() {
this.closeSvga();
this.close();
this.$emit('update:modelValue', this.gift);
},
}
}
</script>
<style lang="scss" scoped>
.gift-box {
background-color: #fff;
position: absolute;
top: 200rpx;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
padding: 15px;
border-top-right-radius: 20rpx;
border-top-left-radius: 20rpx;
padding-bottom: 0;
.top-box {
display: flex;
justify-content: space-between;
align-items: center;
position: fixed;
left: 0;
right: 0;
background-color: #fff;
top: 200rpx;
padding: 0 15px;
border-top-right-radius: 20rpx;
border-top-left-radius: 20rpx;
height: 110rpx;
z-index: 1;
.title {
font-size: 28rpx;
}
.btn-box {
background-color: var(--ui-BG-Main);
padding: 7px 15px;
font-size: 24rpx;
border-radius: 40px;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
.text {
margin-left: 5px;
}
}
}
.list-box {
display: flex;
flex-wrap: wrap;
padding-top: 60rpx;
.gift {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 20rpx 0px;
.img-box {
position: relative;
.img{
max-width: 160rpx;
height: 160rpx;
}
.tag {
position: absolute;
top: 0;
right: 0;
font-size: 16rpx;
color: #fff;
background-color: var(--ui-BG-Main);
border-radius: 40px;
padding: 2rpx 8rpx;
}
}
.name {
font-size: 24rpx;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 160rpx;
margin-top: 2px;
margin-bottom: 7px;
}
.price {
font-size: 20rpx;
color: var(--ui-BG-Main);
}
}
}
}
.svga-box {
position: fixed;
top: 0;
left: 0;
z-index: 999999999;
width: 100%;
height: 100%;
background-color: black;
.close-btn {
color: #fff;
position: absolute;
z-index: 999999999;
padding: 5px 10px;
left: 0;
right: 0;
bottom: 100rpx;
.bottom-box {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
.title {
font-size: 28rpx;
margin-bottom: 5px;
}
.price {
font-size: 24rpx;
}
.btn-box {
display: flex;
align-items: center;
margin-top: 10px;
.btn {
border: 1px solid #fff;
font-size: 28rpx;
padding: 20rpx 110rpx;
border-radius: 40px;
margin: 15px;
color: #fff;
}
.active {
border: 1px solid var(--ui-BG-Main);
background-color: var(--ui-BG-Main);
}
}
}
}
}
.svga-hide {
/* #ifdef MP */
transform: translate(-100%, 0);
/* #endif */
/* #ifndef MP */
display: none;
/* #endif */
}
.svga-show {
/* #ifdef MP */
transform: translate(0, 0);
/* #endif */
/* #ifndef MP */
display: block;
/* #endif */
}
</style>

View File

@@ -0,0 +1,445 @@
<template>
<tui-bottom-popup :zIndex="1002" :maskZIndex="1001" :show="popupShow" @close="hiddenPopup">
<view class="order-box">
<view class="avatar-box">
<u-image width="140" height="140" border-radius="20" :src="clerk.avatar"></u-image>
<view class="close-span" @click="hiddenPopup">
<u-icon name="close-circle" color="#98a2a1" size="50"></u-icon>
</view>
</view>
<scroll-view style="height: 700rpx;" scroll-y>
<view class="page-box">
<view class="form-box">
<view class="input-box">
<view class="tag-box">
<view class="name">赠送类型</view>
</view>
<view class="tab-span">
<!-- <view @click="changeTab(0)" class="btn" :class="form.rewardType == 0 ? 'active' : ''">赠送</view> -->
<view @click="changeTab(1)" class="btn" :class="form.rewardType == 1 ? 'active' : ''">赠礼</view>
</view>
</view>
<view class="input-box" v-if="form.rewardType == 0">
<view class="tag-box">
<view class="name">打赏金额</view>
</view>
<view class="input-span">
<u-input v-model="payMoney" type="number" :placeholder-style="`fontSize: 24rpx; color: #cbced5;`" placeholder="请输入赠送金额"></u-input>
</view>
</view>
<view class="input-box" v-if="form.rewardType == 1">
<view class="tag-box">
<view class="name">赠送礼物</view>
</view>
<view class="select-span" @click="openGift">
<view v-if="gift.id > 0">已选{{gift.name}}</view>
<view v-else>点击选择礼物</view>
<u-icon name="arrow-right"></u-icon>
</view>
</view>
<view class="input-box" v-if="gift.id > 0">
<view class="tag-box">
<view class="name">礼物数量</view>
</view>
<view class="step-span">
<img class="img" :src="gift.img"></img>
<tui-numberbox iconBgColor="var(--ui-BG-Main)" iconColor="#fff" backgroundColor="#fff" :min="1" :value="form.count" @change="change"></tui-numberbox>
</view>
</view>
<view class="input-box">
<view class="textarea-box">
<view class="name">心动留言</view>
</view>
<view class="textarea-span">
<u-input v-model="form.msg" type="textarea" :placeholder-style="`fontSize: 24rpx; color: #cbced5;`" placeholder="请输入心动留言"></u-input>
</view>
</view>
<view class="input-box" v-if="isPass">
<view class="tag-box">
<view class="name">您的微信号</view>
</view>
<view class="input-span">
<u-input v-model="form.weixin" :placeholder-style="`fontSize: 24rpx; color: #cbced5;`" placeholder="请输入正确的微信号"></u-input>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="bottom-box2" v-if="form.rewardType == 0">
<view class="btn-box">
<view class="pay-btn" @click="confirm">立即赠送</view>
</view>
</view>
<view class="bottom-box" v-if="form.rewardType == 1">
<view class="price-box">
<text>总价</text>
<text class="price">{{ fen2yuan(gift.money*form.count) }}</text>
<text>钻石</text>
</view>
<view class="btn-box">
<view class="pay-btn" @click="confirm">立即赠送</view>
</view>
</view>
</view>
</tui-bottom-popup>
<gift-list :showPop="giftFlag" :dataList="giftList" v-model="gift" @close="closeGift"></gift-list>
</template>
<script>
import tuiBottomPopup from "@/components/thorui/tui-bottom-popup/tui-bottom-popup.vue"
import tuiNumberbox from "@/components/thorui/tui-numberbox/tui-numberbox.vue"
import GiftList from '@/pages/user/detail/components/giftList.vue';
import RewardApi from '@/sheep/api/worker/reward';
import sheep from '@/sheep';
export default {
components: {
tuiBottomPopup,
tuiNumberbox,
GiftList,
},
props: {
},
data() {
return {
popupShow: false,
gift: {
id: -1,
money: 0,
},
giftFlag: false,
giftList: [],
payMoney: '',
form: {
rewardType: 1,
count: 1,
giftId: -1,
money: '',
weixin: '',
msg: '',
workerUserId: 0,
payMoney: '',
},
}
},
computed: {
clerk() {
return sheep.$store('sys').user;
},
userInfo() {
return sheep.$store('user').userInfo;
},
isPass() {
return sheep.$store('user').tradeConfig.weixinEnabled;
},
},
methods: {
//调用此方法显示弹层
showPopup() {
this.form.weixin = this.userInfo.weixin;
RewardApi.getGiftList().then(res => {
this.giftList = res.data;
this.popupShow = true
});
},
showGiftPopup(e) {
this.gift = e;
this.form.rewardType = 1;
this.form.weixin = this.userInfo.weixin;
RewardApi.getGiftList().then(res => {
this.giftList = res.data;
this.popupShow = true
});
},
hiddenPopup() {
this.popupShow = false
},
change(e) {
this.form.count = e.value
},
changeTab(e) {
this.form.rewardType = e;
this.gift = {
id: -1,
money: 0,
};
},
fen2yuan(price) {
var f = 0;
var p = (price / 100.0).toFixed(0);
var p1 = (price / 100.0).toFixed(1);
var p2 = (price / 100.0).toFixed(2);
if(p*100 == price){
f = 0;
}else if(p1*100 == price){
f = 1;
}else if(p2*100 == price){
f = 2;
}
return (price / 100.0).toFixed(f)
},
yuan2fen(price) {
return (price * 100.0).toFixed(0)
},
openGift() {
this.giftFlag = true;
},
closeGift() {
this.form.count = 1;
this.giftFlag = false;
},
confirm() {
if(this.isPass && !this.form.weixin){
sheep.$helper.toast('请输入正确的微信');
return;
}
if(this.form.rewardType == 0){
this.form.payMoney = this.yuan2fen(this.payMoney);
if(this.form.payMoney < 1){
sheep.$helper.toast('请输入打赏金额');
return;
}
}else{
this.form.giftId = this.gift.id;
if(this.form.giftId < 1){
sheep.$helper.toast('请选择打赏礼物');
return;
}
}
this.form.workerUserId = this.clerk.id;
RewardApi.createRewardOrder(this.form).then(res => {
// 跳转到支付页面
this.$u.route({
url: 'pages/pay/reward/index',
params: {
id: res.data.payOrderId,
}
});
});
},
}
}
</script>
<style lang="scss" scoped>
.avatar-box {
padding: 10px 0;
display: flex;
justify-content: center;
align-items: center;
.close-span {
position: absolute;
right: 5px;
top: 5px;
width: 70rpx;
height: 70rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
.page-box {
padding: 15px;
padding-bottom: 10px;
.form-box {
margin-bottom: 10px;
.tag-box {
display: flex;
align-items: center;
width: 180rpx;
.name {
font-size: 24rpx;
color: #333;
margin-left: 5px;
}
}
.textarea-box {
height: 20px;
display: flex;
width: 180rpx;
.name {
font-size: 24rpx;
color: #333;
margin-left: 5px;
}
}
.input-box {
display: flex;
margin-bottom: 20px;
align-items: center;
.input-span {
background-color: #f6f6f6;
margin-left: 15px;
display: flex;
flex: 1;
padding: 0 12px;
border-radius: 10rpx;
}
.select-span {
font-size: 24rpx;
background-color: #f6f6f6;
margin-left: 15px;
display: flex;
flex: 1;
padding: 12px 12px;
border-radius: 10rpx;
color: #aaaaaa;
justify-content: space-between;
}
.tab-span {
display: flex;
flex: 1;
padding: 0 12px;
align-items: center;
.btn {
background-color: #f6f6f6;
font-size: 24rpx;
padding: 7px 0;
border-radius: 40px;
min-width: 120rpx;
display: flex;
justify-content: center;
align-items: center;
margin-right: 15px;
}
.active {
color: #fff;
background-color: var(--ui-BG-Main);
}
}
.textarea-span {
background-color: #f6f6f6;
margin-left: 15px;
flex: 1;
padding: 2px 12px;
border-radius: 10px;
}
.step-span {
display: flex;
justify-content: space-between;
flex: 1;
padding-left: 12px;
align-items: center;
.img {
width: 80rpx;
height: 80rpx;
border-radius: 100%;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
}
}
}
}
}
.bottom-box {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
box-shadow: 0 0 6px 0 #ccc;
.price-box {
color: #fb932c;
font-size: 28rpx;
width: 50%;
display: flex;
justify-content: center;
align-items: center;
.price {
margin-right: 5px;
}
}
.btn-box {
width: 50%;
padding-left: 15px;
.pay-btn {
background-color: var(--ui-BG-Main);
color: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 40px;
font-size: 28rpx;
height: 70rpx;
}
}
}
.bottom-box2 {
display: flex;
justify-content: center;
align-items: center;
padding: 15px;
box-shadow: 0 0 6px 0 #ccc;
.price-box {
color: #fb932c;
font-size: 28rpx;
width: 50%;
display: flex;
justify-content: center;
align-items: center;
.price {
margin-right: 5px;
}
}
.btn-box {
width: 50%;
.pay-btn {
background-color: var(--ui-BG-Main);
color: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 40px;
font-size: 28rpx;
height: 70rpx;
}
}
}
</style>

View File

@@ -0,0 +1,286 @@
<!-- 虚拟列表演示(不使用内置列表)(vue) -->
<!-- 写法较简单在页面中对当前需要渲染的虚拟列表数据进行for循环在vue3中兼容性良好 -->
<!-- 在各平台兼容性请查阅https://z-paging.zxlee.cn/module/virtual-list.html -->
<template>
<view class="content">
<!-- 如果页面中的cell高度是固定不变的则不需要设置cell-height-mode如果页面中高度是动态改变的则设置cell-height-mode="dynamic" -->
<!-- 原先的v-model修改为@virtualListChange="virtualListChange"并赋值处理后的虚拟列表 -->
<z-paging ref="paging" :auto="false" :show-loading-more-no-more-view="showLoad" :show-default-loading-more-text="showLoad" use-virtual-list :force-close-inner-list="true" :paging-style="{ paddingTop: 0 + 'px', paddingBottom: paddingBottom + 'rpx' }" cell-height-mode="dynamic" @scroll="scroll" @virtualListChange="virtualListChange" @query="queryList">
<!-- 需要固定在顶部不滚动的view放在slot="top"的view中如果需要跟着滚动则不要设置slot="top" -->
<template #top>
<nav-bar :title="title" :scrollTop="scrollTop" @onTab="change" @initNav="initNav"></nav-bar>
</template>
<view>
<user-box :clerk="user" :paddingTop="paddingTop"></user-box>
<sticky-box :trendNum="total" @change="change"></sticky-box>
</view>
<!-- :id="`zp-id-${item.zp_index}`":key="item.zp_index" 必须写必须写 -->
<!-- 这里for循环的index不是数组中真实的index了请使用item.zp_index获取真实的index -->
<view class="data-box">
<star-list v-if="currentTab == 0" @openGift="openGift" :virtualList="giftList"></star-list>
<post-list v-if="currentTab == 1" :virtualList="virtualList"></post-list>
</view>
<template #bottom>
<view v-if="isGift" class="bottom-box">
<view class="order" @click="doGift">赠礼</view>
<view class="gift" @click="chat">密聊</view>
</view>
<view v-else class="bottom-box">
<view class="order-btn" @click="chat">密聊</view>
</view>
</template>
</z-paging>
<gift-popup ref="giftPopup"></gift-popup>
</view>
</template>
<script>
import UserBox from '@/pages/user/detail/components/userBox.vue';
import StickyBox from '@/pages/user/detail/components/stickyBox.vue';
import StarList from '@/pages/user/detail/components/starList.vue';
import PostList from '@/pages/user/detail/components/postList.vue';
import NavBar from '@/pages/user/detail/components/navBar.vue';
import GiftPopup from '@/pages/user/detail/components/giftPopup.vue';
import UserApi from '@/sheep/api/member/user';
import TrendApi from '@/sheep/api/worker/trend';
import RewardApi from '@/sheep/api/worker/reward';
import ImConversationApi from '@/sheep/api/im/memberConversation';
import { WxaSubscribeTemplate } from '@/sheep/util/const';
import { showAuthModal } from '@/sheep/hooks/useModal';
import dayjs from 'dayjs';
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
import sheep from '@/sheep';
export default {
components: {
UserBox,
StickyBox,
StarList,
PostList,
NavBar,
GiftPopup,
},
props: {
title: {
type: String,
default: '',
}
},
data() {
return {
// 虚拟列表数组,通过@virtualListChange监听获得最新数组
virtualList: [],
scrollTop: 0,
paddingTop: 0,
paddingBottom: 0,
height: 0,
user: {},
userId: 0,
giftList: [],
total: 0,
}
},
computed: {
currentTab() {
return sheep.$store('sys').userTabIndex;
},
showLoad() {
return sheep.$store('sys').userTabIndex > 1;
},
isLogin: {
get() {
return sheep.$store('user').isLogin;
},
},
userInfo: {
get() {
return sheep.$store('user').userInfo;
},
},
isGift() {
return sheep.$store('user').tradeConfig.giftEnabled;
},
},
methods: {
initNav(e) {
this.height = e.height;
this.paddingTop = this.height;
},
initData(options) {
this.userId = options.id;
this.getUserInfo();
RewardApi.getRewardGiftList({
workerUserId: options.id,
}).then(res => {
this.giftList = res.data;
});
},
getUserInfo() {
UserApi.getUserInfoById(this.userId).then(res => {
this.user = res.data;
this.user.time = this.showDayTime(this.user.updateTime);
sheep.$store('sys').setUser(this.user);
this.$refs.paging.reload();
});
},
showDayTime(datetime) {
if (!datetime) return "";
return dayjs(datetime).fromNow();
},
scroll(e) {
this.scrollTop = e.detail.scrollTop;
},
// 监听虚拟列表数组改变并赋值给virtualList进行重新渲染
virtualListChange(vList) {
this.virtualList = vList;
},
queryList(pageNo, pageSize) {
// 组件加载时会自动触发此方法,因此默认页面加载时会自动触发,无需手动调用
// 这里的pageNo和pageSize会自动计算好直接传给服务器即可
// 模拟请求服务器获取分页数据,请替换成自己的网络请求
const params = {
pageNo: pageNo,
pageSize: pageSize,
queryType: '2',
workerUserId: this.userId,
}
TrendApi.getTrendPage(params).then(res => {
this.total = res.data.total;
// 将请求的结果数组传递给z-paging
this.$refs.paging.complete(res.data.list);
}).catch(res => {
// 如果请求失败写this.$refs.paging.complete(false);
// 注意每次都需要在catch中写这句话很麻烦z-paging提供了方案可以全局统一处理
// 在底层的网络请求抛出异常时写uni.$emit('z-paging-error-emit');即可
this.$refs.paging.complete(false);
})
},
change(e) {
sheep.$store('sys').setUserTabIndex(e);
this.scrollTop = 0;
this.$refs.paging.reload();
},
chat() {
const isLogin = sheep.$store('user').isLogin;
if(!isLogin) {
showAuthModal();
return;
}
const userInfo = sheep.$store('user').userInfo;
// 如果用户已经有头像和昵称,不要每次登录都要重新上传头像。
if(userInfo.visible) {
// #ifdef MP-WEIXIN
this.subscribeMessage();
// #endif
ImConversationApi.createMemberConversation({
userId: this.userId,
groupType: 1,
lastMessageContentType: 1,
}).then(res => {
if(res.data){
sheep.$router.go('/pages/im/index',{groupId: res.data, receiveUserId: this.userId});
}
});
return;
}
// 触发小程序授权信息弹框
// #ifdef MP-WEIXIN
showAuthModal('mpAuthorization');
// #endif
// #ifndef MP-WEIXIN
showAuthModal('h5Authorization');
// #endif
},
subscribeMessage() {
const event = [WxaSubscribeTemplate.UNREAD_MESSAGE];
sheep.$platform.useProvider('wechat').subscribeMessage(event, () => {
// 订阅后记录一下订阅状态
uni.removeStorageSync(WxaSubscribeTemplate.UNREAD_MESSAGE);
uni.setStorageSync(WxaSubscribeTemplate.UNREAD_MESSAGE, '已订阅');
});
},
doGift() {
if(this.isLogin){
this.$refs.giftPopup.showPopup();
}else{
showAuthModal();
}
},
openGift(e) {
if(this.isLogin){
this.$refs.giftPopup.showGiftPopup(e);
}else{
showAuthModal();
}
},
}
}
</script>
<style lang="scss" scoped>
.data-box {
padding: 0 15px;
}
.bottom-box {
display: flex;
justify-content: center;
align-items: center;
padding: 10px 30px;
box-shadow: 0 0 6px 0 #ccc;
.order {
background-color: var(--ui-BG-Main);
padding: 10px;
border-top-left-radius: 40px;
border-bottom-left-radius: 40px;
color: #fff;
width: 50%;
display: flex;
justify-content: center;
align-items: center;
height: 88rpx;
font-size: 28rpx;
}
.order-btn {
background-color: var(--ui-BG-Main);
padding: 10px;
border-radius: 40px;
color: #fff;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
height: 88rpx;
font-size: 28rpx;
}
.gift {
background-color: #eef3f2;
padding: 10px;
border-top-right-radius: 40px;
border-bottom-right-radius: 40px;
width: 50%;
display: flex;
justify-content: center;
align-items: center;
height: 88rpx;
color: #aaa;
font-size: 28rpx;
}
}
</style>

View File

@@ -0,0 +1,89 @@
<template>
<view>
<tui-navigation-bar @init="initNavigation" @change="opacityChange" :scrollTop="scrollTop" backgroundColor="#fff" color="#333">
<detailNavbar @onTab="onTab" :scrollTop="scrollTop" />
</tui-navigation-bar>
</view>
</template>
<script>
import tuiNavigationBar from "@/components/thorui/tui-navigation-bar/tui-navigation-bar.vue";
import detailNavbar from '@/pages/user/detail/components/detail-navbar.vue';
export default {
components: {
tuiNavigationBar,
detailNavbar,
},
props: {
title: {
type: String,
default: '',
},
//滚动条滚动距离
scrollTop: {
type: [Number, String],
default: 0
},
},
data() {
return {
top: 0, //标题图标距离顶部距离
opacity: 0,
height: 0,
}
},
methods: {
initNavigation(e) {
this.height = e.height;
this.opacity = e.opacity;
this.top = e.top;
this.$emit('initNav', e);
},
opacityChange(e) {
this.opacity = e.opacity;
},
onTab(e) {
this.$emit('onTab', e);
},
}
}
</script>
<style lang="scss" scoped>
.content-bpx {
color: #fff;
display: flex;
align-items: center;
height: 44px;
justify-content: center;
.left-box {
display: flex;
position: absolute;
left: 0;
}
.nickname {
position: absolute;
width: 100px;
display: flex;
justify-content: center;
align-items: center;
height: 44px;
font-size: 15px;
font-weight: bolder;
color: #1f2122;
}
.set-btn {
padding: 0 15px;
height: 44px;
display: flex;
align-items: center;
}
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<view class="photo-box">
<scroll-view scroll-x>
<view class="scroll-box">
<view class="img-box" @click="showImage(item)" v-for="(item,index) in dataList">
<u-image border-radius="20" width="170rpx" height="170rpx" :src="item"></u-image>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
components: {
},
props: {
dataList: {
type: Array,
default: [],
},
},
data() {
return {
}
},
methods: {
showImage(src) {
uni.previewImage({
indicator: "none",
current: src,
urls: this.dataList,
});
},
}
}
</script>
<style lang="scss" scoped>
.photo-box {
.scroll-box {
display: flex;
}
.img-box {
margin-right: 15px;
}
}
</style>

View File

@@ -0,0 +1,362 @@
<template>
<view>
<view class="item" :id="`zp-id-${item.zp_index}`" :key="item.zp_index" v-for="(item,index) in orderList">
<view class="card">
<view class="right">
<view class="card-header">
<view class="box3">
<view class="box1">
<view class="tag-list">
<!-- <view class="tag">Ta爱玩王者荣耀</view> -->
<view class="tag">发布于 {{item.createTimeStr}}</view>
</view>
</view>
</view>
</view>
<view class="card-content">
<view class="text-box">
<rich-text :nodes="item.content"></rich-text>
</view>
</view>
<view class="image-box" v-if="item.fileType == 0">
<img-box @preAd="preAd" :ad="item.adStatus" :file="item.file"></img-box>
</view>
<view class="voice-box" v-if="item.fileType == 1">
<voice-play :sec="item.seconds" @tap.stop="playAudio(item)" :isPlay="item.id == playId"></voice-play>
</view>
<view class="video-box" v-if="item.fileType == 2">
<video-box :file="item.file"></video-box>
</view>
<view class="topic-list">
<view class="topic">
<u-icon name="map-fill" size="40" color="#3cc9a4"></u-icon>
<view class="tag-text">{{item.city}}</view>
<u-icon name="arrow-right" size="24" color="#3cc9a4"></u-icon>
</view>
</view>
<view class="card-footer">
<!-- <view class="toolbar">
<u-icon color="#f880ab" name="presentfill" size="38" custom-prefix="iconfont"></u-icon>
<view class="toolbar-text">3</view>
</view> -->
<!-- <view class="toolbar">
<u-icon name="pinglun" size="38" custom-prefix="iconfont"></u-icon>
<view class="toolbar-text">22</view>
</view> -->
<view class="toolbar" @click="thumb(item)">
<u-icon v-if="item.like" name="thumb-up-fill" color="var(--ui-BG-Main)" size="40"></u-icon>
<u-icon v-else name="thumb-up" size="40"></u-icon>
<view class="toolbar-text" v-if="item.likeNum > 0">{{item.likeNum}}</view>
<view class="text" v-else>点赞</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import ImgBox from '@/pages/tabbar/components/trend/imgBox.vue';
import VideoBox from '@/pages/tabbar/components/trend/videoBox.vue';
import VoicePlay from '@/pages/tabbar/components/trend/voicePlay.vue';
import adVideoUtils from '@/sheep/util/adVideoUtils';
import sheep from '@/sheep';
import TrendApi from '@/sheep/api/worker/trend';
const audio = uni.createInnerAudioContext();
export default {
components: {
ImgBox,
VideoBox,
VoicePlay,
},
props: {
virtualList: {
type: Array,
default: [],
},
},
data() {
return {
playId: null,
}
},
computed: {
orderList() {
this.virtualList.forEach((order) => order.createTimeStr = sheep.$helper.timeFormat(order.createTime, 'yyyy-mm-dd hh:MM'));
return this.virtualList;
},
tradeConfig() {
return sheep.$store('user').tradeConfig;
},
},
methods: {
playAudio(e) {
if(this.playId == e.id){
this.playId = null;
audio.stop();
return;
}
this.playId = e.id;
//语音自然播放结束
audio.onEnded((res) => {
this.playId = null;
});
audio.src = e.file;
audio.play();
},
thumb(e) {
TrendApi.createTrendLike({
trendId: e.id,
}).then((res) => {
if(res) {
if(e.like){
e.like = false;
e.likeNum = e.likeNum-1;
sheep.$helper.toast('取消点赞');
}else{
e.like = true;
e.likeNum = e.likeNum+1;
sheep.$helper.toast('点赞成功');
}
}
});
},
preAd(run) {
adVideoUtils.videoAdInit(this.tradeConfig.adUnitId);
adVideoUtils.videoAdShow()
.then((res) => {
if (res) {
run(true);
} else {
console.log('广告提前退出')
run(false);
}
})
.catch((err) => {
console.log('广告加载失败')
run(false);
});
},
fans(e) {
TrendApi.createClerkFans({
workerClerkId: e.workerClerkId,
}).then((res) => {
if(res){
if(e.fans){
sheep.$helper.toast('取消收藏');
e.fans = false;
}else{
sheep.$helper.toast('收藏成功');
e.fans = true;
}
}
});
},
detail(e) {
this.$u.route({
url: 'pages/clerk/detail/index',
params: {
id: e.workerClerkId,
}
});
},
}
}
</script>
<style lang="scss" scoped>
.item {
display: flex;
flex-direction: column;
}
.card {
padding: 30rpx 20rpx;
border-bottom: 1rpx solid #f5f5f5;
display: flex;
.right {
display: flex;
flex-direction: column;
flex: 1;
}
}
.avatar-img{
margin-right: 5px;
width: 110rpx;
height: 110rpx;
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 8rpx;
.img {
width: 100%;
height: 100%;
border-radius: 100%;
}
}
.avatar{
margin-right: 5px;
width: 110rpx;
height: 110rpx;
position: relative;
display: flex;
justify-content: center;
align-items: center;
.img {
width: 75%;
height: 75%;
border-radius: 100%;
}
.gif {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
}
}
.card-header {
display: flex;
align-items: center;
.box1 {
.tag-list {
display: flex;
align-items: center;
font-size: 22rpx;
margin-top: 4rpx;
.tag::after {
content: '·';
padding: 0 10rpx; /* 添加一些间隔 */
}
}
.tag-list .tag:last-child::after {
content: unset;
}
}
.box2 {
display: flex;
align-items: center;
line-height: 40rpx;
.nickname {
font-size: 28rpx;
color: #333333;
}
.sex-box {
display: flex;
align-items: center;
justify-content: center;
background-color: #d06b8d1f;
border-radius: 20px;
font-size: 20rpx;
padding: 4rpx 10rpx;
color: #d06b8d;
line-height: 24rpx;
margin: 0 5px;
.age {
margin-left: 2px;
}
}
.nan {
background-color: #007aff1a;
color: #007aff;
}
}
.box3 {
display: flex;
justify-content: space-between;
flex: 1;
align-items: center;
}
.fans {
width: 60rpx;
display: flex;
justify-content: flex-end;
height: 60rpx;
align-items: center;
color: var(--ui-BG-Main);
font-size: 24rpx;
padding-right: 3px;
}
}
.card-content {
margin-top: 20rpx;
margin-bottom: 10rpx;
.text-box {
font-size: 26rpx;
line-height: 180%;
white-space: pre-wrap;
letter-spacing: 2rpx;
}
}
.topic-list {
display: flex;
align-items: center;
margin-top: 10px;
.topic {
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
margin-right: 20rpx;
color: var(--ui-BG-Main);
font-weight: bold;
.tag-text {
margin-left: 2px;
}
}
}
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 30rpx;
.toolbar {
display: flex;
align-items: center;
justify-content: flex-end;
color: #666666;
width: 80rpx;
height: 40rpx;
flex: 1;
}
.toolbar-text {
font-size: 26rpx;
margin-left: 5px;
}
.text {
font-size: 24rpx;
margin-left: 5px;
}
}
</style>

View File

@@ -0,0 +1,310 @@
<template>
<view>
<view v-if="isGift" class="star-box">
<view class="title-box">
<view class="title">礼物墙</view>
<view class="num">(已点亮{{starNum}}/{{total}})</view>
</view>
<view class="list-box">
<view @click="sendGift(item)" v-for="(item,index) in virtualList" :key="item.id" class="gift">
<view class="img-box">
<img class="img" :class="item.playStatus == 0 ? 'hui' : ''" :src="item.img"></img>
<view class="tag" v-if="item.giftType == 1">
<text>特效</text>
<text v-if="item.tag">·{{item.tag}}</text>
</view>
<view class="tag" v-if="item.giftType == 0 && item.tag">
<text>{{item.tag}}</text>
</view>
</view>
<view class="name">{{item.name}}</view>
<view class="price">x {{item.playNum}}</view>
<view class="btn" @tap.stop="openGift(item)" v-if="item.playStatus == 0">点亮</view>
<view class="btn" @tap.stop="openGift(item)" v-if="item.playStatus == 1">赠送</view>
<view class="btn" v-if="item.playStatus == 2">已下架</view>
</view>
</view>
</view>
<view class="svga-box" :class="giftFlag ? 'svga-show': 'svga-hide'">
<c-svga ref="cSvgaRef" :canvasId='canvasId' :src="src" :loops='0' :auto-play="false" @frame='onFrame' @finished='onFinished' @percentage='onPercentage' @loaded='onLoaded'></c-svga>
<view class="close-btn" @click="closeSvga">
<view class="bottom-box">
<view class="title">{{gift.name}}</view>
<view class="price">{{ fen2yuan(gift.money) }} 钻石</view>
<view class="btn-box">
<view class="btn" @click="cannel">取消</view>
<view v-if="gift.playStatus == 0" class="btn active" @click="ok">点亮</view>
<view v-if="gift.playStatus == 1" class="btn active" @click="ok">赠送</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import sheep from '@/sheep';
export default {
components: {
},
props: {
virtualList: {
type: Array,
default: [],
},
},
data() {
return {
giftFlag: false,
src: '',
canvasId: 'myCanvas',
gift: {},
}
},
computed: {
starNum() {
var count = 0;
this.virtualList.forEach(e => {
if(e.playStatus > 0){
count ++;
}
});
return count;
},
total() {
return this.virtualList.length;
},
isGift() {
return sheep.$store('user').tradeConfig.giftEnabled;
},
},
methods: {
sendGift(e) {
this.gift = e;
if(e.giftType == 0){
// 普通礼物不播放
this.$emit('openGift', this.gift);
return;
}
this.src = e.pic;
this.giftFlag = true;
},
onFinished() {
this.giftFlag = false;
console.log('动画停止播放时回调');
},
onFrame(frame) {//动画播放至某帧后回调
// console.log(frame);
},
onPercentage(percentage) { //动画播放至某进度后回调
// console.log(percentage);
},
onLoaded() {
this.$refs.cSvgaRef.call('setContentMode', 'AspectFill');
console.log('加载完成');
this.$refs.cSvgaRef.call('startAnimation');
},
closeSvga() {
this.src = "";
this.$refs.cSvgaRef.call('stopAnimation');
this.giftFlag = false;
},
fen2yuan(price) {
var f = 0;
var p = (price / 100.0).toFixed(0);
var p1 = (price / 100.0).toFixed(1);
var p2 = (price / 100.0).toFixed(2);
if(p*100 == price){
f = 0;
}else if(p1*100 == price){
f = 1;
}else if(p2*100 == price){
f = 2;
}
return (price / 100.0).toFixed(f)
},
cannel() {
this.closeSvga();
},
ok() {
this.closeSvga();
this.$emit('openGift', this.gift);
},
openGift(e) {
this.$emit('openGift', e);
},
}
}
</script>
<style lang="scss" scoped>
.star-box {
padding: 30rpx 20rpx;
.title-box {
display: flex;
align-items: center;
.title {
font-size: 28rpx;
margin-right: 10px;
}
.num {
font-size: 24rpx;
color: var(--ui-BG-Main);
}
}
.list-box {
display: flex;
flex-wrap: wrap;
padding-top: 30rpx;
.gift {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 20rpx 0px;
.img-box {
position: relative;
.img{
max-width: 150rpx;
height: 150rpx;
}
.hui {
filter: grayscale(100%);
}
.tag {
position: absolute;
top: 0;
right: 0;
font-size: 16rpx;
color: #fff;
background-color: var(--ui-BG-Main);
border-radius: 40px;
padding: 2rpx 8rpx;
}
}
.name {
font-size: 24rpx;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 150rpx;
margin-top: 2px;
margin-bottom: 7px;
}
.price {
font-size: 20rpx;
color: var(--ui-BG-Main);
margin-bottom: 7px;
}
.btn {
border: 1px solid var(--ui-BG-Main);
border-radius: 40px;
color: var(--ui-BG-Main);
padding: 8rpx 20rpx;
font-size: 24rpx;
}
}
}
}
.svga-box {
position: fixed;
top: 0;
left: 0;
z-index: 999999999;
width: 100%;
height: 100%;
background-color: black;
.close-btn {
color: #fff;
position: absolute;
z-index: 999999999;
padding: 5px 10px;
left: 0;
right: 0;
bottom: 100rpx;
.bottom-box {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
.title {
font-size: 28rpx;
margin-bottom: 5px;
}
.price {
font-size: 24rpx;
}
.btn-box {
display: flex;
align-items: center;
margin-top: 10px;
.btn {
border: 1px solid #fff;
font-size: 28rpx;
padding: 20rpx 110rpx;
border-radius: 40px;
margin: 15px;
color: #fff;
}
.active {
border: 1px solid var(--ui-BG-Main);
background-color: var(--ui-BG-Main);
}
}
}
}
}
.svga-hide {
/* #ifdef MP */
transform: translate(-100%, 0);
/* #endif */
/* #ifndef MP */
display: none;
/* #endif */
}
.svga-show {
/* #ifdef MP */
transform: translate(0, 0);
/* #endif */
/* #ifndef MP */
display: block;
/* #endif */
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<view>
<view class="sticky-box">
<view>
<tui-tabs :tabs="tabList" badgeBgColor="var(--ui-BG-Main)" selectedColor="var(--ui-BG-Main)" sliderBgColor="var(--ui-BG-Main)" :currentTab="currentTab" @change="change"></tui-tabs>
</view>
</view>
</view>
</template>
<script>
import tuiTabs from "@/components/thorui/tui-tabs/tui-tabs.vue"
import sheep from '@/sheep';
export default {
components: {
tuiTabs,
},
props: {
trendNum: {
type: Number,
default: 0,
},
},
data() {
return {
}
},
computed: {
tabList() {
var tabs = [{
name: '礼物墙'
},{
name: '动态',
num: this.trendNum,
}];
return tabs;
},
currentTab() {
return sheep.$store('sys').userTabIndex;
},
},
created() {
},
methods: {
change(e) {
this.$emit('change', e.index);
},
}
}
</script>
<style lang="scss" scoped>
.sticky-box {
background-color: #fff;
padding: 5px 0;
}
</style>

View File

@@ -0,0 +1,308 @@
<template>
<view class="user-box" :style="`background-size: contain;background-image: url(${clerk.avatar});`">
<view class="info-box" :style="{ paddingTop: paddingTop + 20 + 'px'}">
<view class="avatar-box">
<view class="avatar-span">
<u-image width="100%" height="100%" shape="circle" :src="clerk.avatar"></u-image>
<!-- <view class="fans-btn" @click="fans(clerk)">
<u-icon v-if="clerk.isFans" color="var(--ui-BG-Main)" name="checkmark-circle-fill" size="46"></u-icon>
<u-icon v-else color="var(--ui-BG-Main)" name="plus-circle-fill" size="46"></u-icon>
</view> -->
</view>
<view class="right-box">
<view class="nickname-box">
<view class="nickname">{{clerk.nickname}}</view>
<view class="icon">
<u-icon name="wode_duanwei" size="36" custom-prefix="iconfont"></u-icon>
<text>L1</text>
</view>
</view>
<view class="badge-box">
<view v-if="clerk.hasQrcode" class="weixin-btn" @click="getWeixin(clerk)">
<u-icon name="weixin-fill" size="28" color="#fff"></u-icon>
</view>
</view>
</view>
</view>
<view class="sex-box">
<view class="span" v-if="clerk.sex == 2">
<u-icon name="ziyuan2" color="#f898c5" size="34" custom-prefix="iconfont"></u-icon>
</view>
<view class="span" v-if="clerk.sex == 1">
<u-icon name="ziyuan3" color="#0081ff" size="34" custom-prefix="iconfont"></u-icon>
</view>
<view class="span">{{clerk.age}}</view>
<view class="span">{{clerk.city}}</view>
</view>
<view class="sign-box">{{clerk.intro}}</view>
<view class="voice-box">
<!-- <view class="voice-span">
<voice-play :sec="clerk.soundTime" :src="clerk.sound"></voice-play>
</view> -->
<view class="span">已注册{{clerk.day}}</view>
<span class="span" v-if="clerk.hasQrcode">可交换名片</span>
</view>
<view class="tag-list">
<view class="tag-box">
<span class="tag" v-for="(categoryName,index) in clerk.categoryNameList">{{categoryName}}</span>
</view>
</view>
<!-- <view class="tags-box">
<view class="span">搞笑</view>
<view class="span">风骚走位</view>
<view class="span">乖巧懂事🍑 极致体验</view>
</view> -->
<photo-box v-if="clerk.photo" :dataList="clerk.photo.split(',')"></photo-box>
</view>
</view>
</template>
<script>
import PhotoBox from '@/pages/user/detail/components/photoBox.vue';
import sheep from '@/sheep';
import UserApi from '@/sheep/api/member/user';
import TrendApi from '@/sheep/api/worker/trend';
export default {
components: {
PhotoBox,
},
props: {
paddingTop: {
type: Number,
default: 0,
},
clerk: {
type: Object,
default: {},
},
},
data() {
return {
photo: 'http://cos.duopei.feiniaowangluo.com/34/clerk/1707236152609pJKyvtedS9.jpg,http://cos.duopei.feiniaowangluo.com/34/clerk/1720278125689P34fP9MU1c.jpg?imageView2/1/w/200/h/200',
}
},
methods: {
fans(e) {
TrendApi.createClerkFans({
workerClerkId: e.id,
}).then((res) => {
if(res){
if(e.isFans){
sheep.$helper.toast('取消关注');
e.isFans = false;
}else{
sheep.$helper.toast('关注成功');
e.isFans = true;
}
}
});
},
getWeixin(e) {
var that = this;
uni.showModal({
title: '交换名片',
content: '确认交换名片吗?',
success: function (res) {
if (res.confirm) {
that.queryWeixin(e);
}
},
});
},
queryWeixin(e) {
UserApi.getQrcodeByUserId(e.id).then(res => {
if(res.data){
uni.previewImage({
current: 0, //预览图片的下标
urls: [res.data], //预览图片的地址,必须要数组形式,如果不是数组形式就转换成数组形式就可以
indicator: 'number',
loop: true
});
}
});
},
}
}
</script>
<style lang="scss" scoped>
.info-box {
backdrop-filter: blur(10px);
background-color: rgba(0, 0, 0, 0.5);
padding: 15px;
.avatar-box {
display: flex;
align-items: center;
.avatar-span {
border-radius: 100%;
width: 140rpx;
height: 140rpx;
border: 2px solid #fff;
position: relative;
.fans-btn {
position: absolute;
right: 0;
bottom: 0;
}
}
.right-box {
color: #fff;
display: flex;
justify-content: space-between;
flex: 1;
align-items: center;
margin-left: 10px;
.nickname {
font-size: 34rpx;
font-weight: bold;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 320rpx;
}
.icon {
background-color: #ff5ebd;
display: flex;
justify-content: center;
align-items: center;
border-radius: 40px;
font-weight: bold;
color: #fff;
font-size: 22rpx;
line-height: 22rpx;
padding: 5px 5px;
margin-top: 10px;
width: 110rpx;
}
.online {
border-bottom: 1px solid #fff;
font-size: 28rpx;
}
}
}
.sex-box {
color: #fff;
display: flex;
align-items: center;
line-height: 30px;
.span {
margin-right: 5px;
font-size: 26rpx;
}
}
.sign-box {
color: #cfcfcf;
font-size: 28rpx;
margin-top: 10px;
margin-bottom: 10px;
}
.voice-box {
display: flex;
flex-wrap: wrap;
align-items: center;
.voice-span {
margin-bottom: 10px;
margin-right: 10px;
}
.span {
padding: 10rpx 20rpx;
background-color: rgba(132, 167, 164, .81);
border-radius: 20px;
font-size: 24rpx;
margin-bottom: 10px;
margin-right: 10px;
color: #fff;
}
}
.tags-box {
display: flex;
flex-wrap: wrap;
align-items: center;
margin: 5px 0;
.span {
font-size: 20rpx;
color: #fff;
background-color: #000000;
border-radius: 999px;
padding: 3px 10px;
margin: 0 10px 10px 0;
}
}
.tag-list {
display: flex;
margin: 5px 0;
}
.tag-box {
display: inline-block;
align-items: center;
margin-bottom: 5px;
background-color: #eef2f26e;
border-radius: 3px;
color: #fff;
font-size: 12px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-width: 100%;
padding: 5px 0;
.tag {
white-space: nowrap;
padding: 5px 5px;
position: relative;
}
.tag:after {
content: ' ';
border-left: 1px solid #fff;
display: inline-block;
height: 10px;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 0;
}
.tag:last-child:after{
display: none;
}
}
}
.weixin-btn {
display: flex;
align-items: center;
background-color: #19be6b;
justify-content: center;
border-radius: 40px;
color: #fff;
font-size: 22rpx;
padding: 5px;
.text {
margin-left: 2px;
}
}
</style>

View File

@@ -0,0 +1,61 @@
<template>
<view class="container page-app theme-light main-green font-1">
<layout ref="skill" title="选人下单">
</layout>
<s-auth-modal />
<s-menu-tools />
</view>
</template>
<script>
import layout from '@/pages/user/detail/components/layout.vue';
import sheep from '@/sheep';
export default {
components: {
layout,
},
data() {
return {
}
},
// 分享小程序
onShareAppMessage(res) {
return {
title: this.shareInfo.title,
imageUrl: this.shareInfo.image,
};
},
onShareTimeline() {
return {
title: this.shareInfo.title,
imageUrl: this.shareInfo.image,
}
},
onLoad(options) {
this.$nextTick(() => {
this.$refs.skill.initData(options);
});
},
computed: {
shareInfo() {
return sheep.$platform.share.getShareInfo();
},
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.container {
background-color: #fff;
height: calc(100vh);
padding-bottom: env(safe-area-inset-bottom);
}
</style>

View File

@@ -0,0 +1,233 @@
<!-- 我的商品收藏 -->
<template>
<s-layout title="商品收藏">
<view class="cart-box ss-flex ss-flex-col ss-row-between">
<!-- 头部 -->
<view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
<view class="header-left ss-flex ss-col-center ss-font-26">
<text class="goods-number ui-TC-Main ss-flex">{{ state.pagination.total }}</text> 件商品
</view>
<view class="header-right">
<button
v-if="state.editMode && state.pagination.total"
class="ss-reset-button"
@tap="state.editMode = false"
>
取消
</button>
<button
v-if="!state.editMode && state.pagination.total"
class="ss-reset-button ui-TC-Main"
@tap="state.editMode = true"
>
编辑
</button>
</view>
</view>
<!-- 内容 -->
<view class="cart-content">
<view
class="goods-box ss-r-10 ss-m-b-14"
v-for="item in state.pagination.list"
:key="item.id"
>
<view class="ss-flex ss-col-center">
<label
class="check-box ss-flex ss-col-center ss-p-l-10"
v-if="state.editMode"
@tap="onSelect(item.spuId)"
>
<radio
:checked="state.selectedCollectList.includes(item.spuId)"
color="var(--ui-BG-Main)"
style="transform: scale(0.8)"
@tap.stop="onSelect(item.spuId)"
/>
</label>
<s-goods-item
:title="item.spuName"
:img="item.picUrl"
:price="item.price"
priceColor="#FF3000"
:titleWidth="400"
@tap="
sheep.$router.go('/pages/goods/index', {
id: item.spuId,
})
"
/>
</view>
</view>
</view>
<!-- 底部 -->
<su-fixed bottom :val="0" placeholder v-show="state.editMode">
<view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom">
<view class="footer-left ss-flex ss-col-center">
<label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll">
<radio
:checked="state.selectAll"
color="var(--ui-BG-Main)"
style="transform: scale(0.7)"
@tap.stop="onSelectAll"
/>
<view> 全选 </view>
</label>
</view>
<view class="footer-right">
<button
class="ss-reset-button ui-BG-Main-Gradient pay-btn ss-font-28 ui-Shadow-Main"
@tap="onCancel"
>
取消收藏
</button>
</view>
</view>
</su-fixed>
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
@tap="loadMore"
/>
<s-empty v-if="state.pagination.total === 0" text="暂无收藏" icon="/static/collect-empty.png" />
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import _ from 'lodash-es';
import FavoriteApi from '@/sheep/api/product/favorite';
import { resetPagination } from '@/sheep/util';
const sys_navBar = sheep.$platform.navbar;
const state = reactive({
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 6,
},
loadStatus: '',
editMode: false,
selectedCollectList: [], // 选中的 SPU 数组
selectAll: false,
});
async function getData() {
state.loadStatus = 'loading';
const { code, data } = await FavoriteApi.getFavoritePage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
// 单选选中
const onSelect = (spuId) => {
if (!state.selectedCollectList.includes(spuId)) {
state.selectedCollectList.push(spuId);
} else {
state.selectedCollectList.splice(state.selectedCollectList.indexOf(spuId), 1);
}
state.selectAll = state.selectedCollectList.length === state.pagination.list.length;
};
// 全选
const onSelectAll = () => {
state.selectAll = !state.selectAll;
if (!state.selectAll) {
state.selectedCollectList = [];
} else {
state.selectedCollectList = state.pagination.list.map((item) => item.spuId);
}
};
async function onCancel() {
if (!state.selectedCollectList) {
return;
}
// 取消收藏
for (const spuId of state.selectedCollectList) {
await FavoriteApi.deleteFavorite(spuId);
}
// 清空选择 + 重新加载
state.editMode = false;
state.selectedCollectList = [];
state.selectAll = false;
resetPagination(state.pagination);
await getData();
}
// 加载更多
function loadMore() {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getData();
}
onReachBottom(() => {
loadMore();
});
onLoad(() => {
getData();
});
</script>
<style lang="scss" scoped>
.cart-box {
.cart-header {
height: 70rpx;
background-color: #f6f6f6;
width: 100%;
position: fixed;
left: 0;
top: v-bind('sys_navBar') rpx;
z-index: 1000;
box-sizing: border-box;
}
.cart-footer {
height: 100rpx;
background-color: #fff;
.pay-btn {
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
padding: 0 40rpx;
min-width: 200rpx;
}
}
.cart-content {
width: 100%;
margin-top: 70rpx;
padding: 0 20rpx;
box-sizing: border-box;
.goods-box {
background-color: #fff;
&:last-child {
margin-bottom: 40rpx;
}
}
}
}
</style>

308
pages/user/goods-log.vue Normal file
View File

@@ -0,0 +1,308 @@
<!-- 商品浏览记录 -->
<template>
<s-layout title="我的足迹" :bgStyle="{ color: '#f2f2f2' }">
<view class="cart-box ss-flex ss-flex-col ss-row-between">
<!-- 头部 -->
<view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
<view class="header-left ss-flex ss-col-center ss-font-26">
<text class="goods-number ui-TC-Main ss-flex">
{{ state.pagination.total }}
</text>
件商品
</view>
<view class="header-right">
<button
v-if="state.editMode && state.pagination.total"
class="ss-reset-button"
@tap="state.editMode = false"
>
取消
</button>
<button
v-if="!state.editMode && state.pagination.total"
class="ss-reset-button ui-TC-Main"
@tap="state.editMode = true"
>
编辑
</button>
</view>
</view>
<!-- 内容 -->
<view class="cart-content">
<view
class="goods-box ss-r-10 ss-m-b-14"
v-for="item in state.pagination.list"
:key="item.id"
>
<view class="ss-flex ss-col-center">
<label
class="check-box ss-flex ss-col-center ss-p-l-10"
v-if="state.editMode"
@tap="onSelect(item.spuId)"
>
<radio
:checked="state.selectedSpuIdList.includes(item.spuId)"
color="var(--ui-BG-Main)"
style="transform: scale(0.8)"
@tap.stop="onSelect(item.spuId)"
/>
</label>
<s-goods-item
:title="item.spuName"
:img="item.picUrl"
:price="item.price"
:skuText="item.introduction"
priceColor="#FF3000"
:titleWidth="400"
@tap="
sheep.$router.go('/pages/goods/index', {
id: item.spuId,
})
"
>
</s-goods-item>
</view>
</view>
</view>
<!-- 底部 -->
<su-fixed bottom :val="0" placeholder v-show="state.editMode">
<view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom">
<view class="footer-left ss-flex ss-col-center">
<label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll">
<radio
:checked="state.selectAll"
color="var(--ui-BG-Main)"
style="transform: scale(0.7)"
@tap.stop="onSelectAll"
/>
<view>全选</view>
</label>
</view>
<view class="footer-right ss-flex">
<button
:class="[
'ss-reset-button pay-btn ss-font-28 ',
{
'ui-BG-Main-Gradient': state.selectedSpuIdList.length > 0,
'ui-Shadow-Main': state.selectedSpuIdList.length > 0,
},
]"
@tap="onDelete"
>
删除足迹
</button>
<button
class="ss-reset-button ui-BG-Main-Gradient pay-btn ss-font-28 ui-Shadow-Main ml-2"
@tap="onClean"
>
清空
</button>
</view>
</view>
</su-fixed>
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
@tap="loadMore"
/>
<s-empty
v-if="state.pagination.total === 0"
text="暂无浏览记录"
icon="/static/collect-empty.png"
/>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import _ from 'lodash-es';
import SpuHistoryApi from '@/sheep/api/product/history';
import { cloneDeep } from '@/sheep/helper/utils';
const sys_navBar = sheep.$platform.navbar;
const pagination = {
list: [],
pageNo: 1,
total: 1,
pageSize: 10,
};
const state = reactive({
pagination: cloneDeep(pagination),
loadStatus: '',
editMode: false,
selectedSpuIdList: [],
selectAll: false,
});
async function getList() {
state.loadStatus = 'loading';
const { code, data } = await SpuHistoryApi.getBrowseHistoryPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
// 单选选中
const onSelect = (id) => {
if (!state.selectedSpuIdList.includes(id)) {
state.selectedSpuIdList.push(id);
} else {
state.selectedSpuIdList.splice(state.selectedSpuIdList.indexOf(id), 1);
}
state.selectAll = state.selectedSpuIdList.length === state.pagination.list.length;
};
// 全选
const onSelectAll = () => {
state.selectAll = !state.selectAll;
if (!state.selectAll) {
state.selectedSpuIdList = [];
} else {
state.pagination.list.forEach((item) => {
if (state.selectedSpuIdList.includes(item.spuId)) {
state.selectedSpuIdList.splice(state.selectedSpuIdList.indexOf(item.spuId), 1);
}
state.selectedSpuIdList.push(item.spuId);
});
}
};
// 删除足迹
async function onDelete() {
if (state.selectedSpuIdList.length <= 0) {
return;
}
const { code } = await SpuHistoryApi.deleteBrowseHistory(state.selectedSpuIdList);
if (code === 0) {
reload();
}
}
// 清空
async function onClean() {
const { code } = await SpuHistoryApi.cleanBrowseHistory();
if (code === 0) {
reload();
}
}
function reload() {
state.editMode = false;
state.selectedSpuIdList = [];
state.selectAll = false;
state.pagination = pagination;
getList();
}
// 加载更多
function loadMore() {
if (state.loadStatus !== 'noMore') {
state.pagination.pageNo += 1;
getList();
}
}
onReachBottom(() => {
loadMore();
});
onLoad(() => {
getList();
});
</script>
<style lang="scss" scoped>
.cart-box {
.cart-header {
height: 70rpx;
background-color: #f6f6f6;
width: 100%;
position: fixed;
left: 0;
top: v-bind('sys_navBar') rpx;
z-index: 1000;
box-sizing: border-box;
}
.cart-footer {
height: 100rpx;
background-color: #fff;
.pay-btn {
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
padding: 0 40rpx;
min-width: 200rpx;
}
}
.cart-content {
width: 100%;
padding: 0 20rpx;
box-sizing: border-box;
margin-top: 70rpx;
.goods-box {
background-color: #fff;
&:last-child {
margin-bottom: 40rpx;
}
}
}
}
.title-card {
padding: 36rpx 0 46rpx 20rpx;
.img-box {
width: 164rpx;
height: 164rpx;
.order-img {
width: 164rpx;
height: 164rpx;
}
}
.check-box {
height: 100%;
}
.title-text {
font-size: 28rpx;
font-weight: 500;
color: #333333;
}
.params-box {
.params-title {
height: 38rpx;
background: #f4f4f4;
border-radius: 2rpx;
font-size: 24rpx;
font-weight: 400;
color: #666666;
}
}
.price-text {
color: $red;
font-family: OPPOSANS;
}
}
</style>

View File

@@ -0,0 +1,275 @@
<template>
<s-layout title="选择自提门店" :bgStyle="{ color: '#FFF' }">
<view class="storeBox" ref="container">
<view class="storeBox-box" v-for="(item, index) in state.storeList" :key="index" @tap="checked(item)">
<view class="store-img">
<image :src="item.logo" class="img" />
</view>
<view class="store-cent-left">
<view class="store-name">{{ item.name }}</view>
<view class="store-address line1">
{{ item.areaName }}{{ ', ' + item.detailAddress }}
</view>
</view>
<view class="row-right ss-flex-col ss-col-center">
<view>
<!-- #ifdef H5 -->
<a class="store-phone" :href="'tel:' + item.phone">
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</a>
<!-- #endif -->
<!-- #ifdef MP -->
<view class="store-phone" @click="call(item.phone)">
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</view>
<!-- #endif -->
</view>
<view class="store-distance ss-flex ss-row-center" @tap.stop="showMaoLocation(item)">
<text class="addressTxt" v-if="item.distance">距离{{ item.distance.toFixed(2) }}千米</text>
<text class="addressTxt" v-else>查看地图</text>
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</view>
</view>
</view>
</view>
</s-layout>
</template>
<script setup>
import DeliveryApi from '@/sheep/api/trade/delivery';
import { onMounted, reactive } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import sheep from '@/sheep';
const LONGITUDE = 'user_longitude';
const LATITUDE = 'user_latitude';
const state = reactive({
loaded: false,
loading: false,
storeList: [],
system_store: {},
locationShow: false,
user_latitude: 0,
user_longitude: 0,
});
const call = (phone) => {
uni.makePhoneCall({
phoneNumber: phone,
});
};
const selfLocation = () => {
// #ifdef H5
const jsWxSdk = sheep.$platform.useProvider('wechat').jsWxSdk;
if (jsWxSdk.isWechat()) {
jsWxSdk.getLocation((res) => {
console.log(res);
state.user_latitude = res.latitude;
state.user_longitude = res.longitude;
uni.setStorageSync(LATITUDE, res.latitude);
uni.setStorageSync(LONGITUDE, res.longitude);
getList();
});
} else {
// #endif
uni.getLocation({
type: 'gcj02',
success: (res) => {
try {
state.user_latitude = res.latitude;
state.user_longitude = res.longitude;
uni.setStorageSync(LATITUDE, res.latitude);
uni.setStorageSync(LONGITUDE, res.longitude);
} catch {
}
getList();
},
complete: () => {
getList();
},
});
// #ifdef H5
}
// #endif
};
const showMaoLocation = (e) => {
// #ifdef H5
const jsWxSdk = sheep.$platform.useProvider('wechat').jsWxSdk;
if (jsWxSdk.isWechat()) {
jsWxSdk.openLocation({
latitude: Number(e.latitude),
longitude: Number(e.longitude),
name: e.name,
address: `${e.areaName}-${e.detailAddress}`
});
} else {
// #endif
uni.openLocation({
latitude: Number(e.latitude),
longitude: Number(e.longitude),
name: e.name,
address: `${e.areaName}-${e.detailAddress}`,
success: function() {
console.log('success');
},
});
// #ifdef H5
}
// #endif
};
/**
* 选中门店
*/
const checked = (addressInfo) => {
uni.$emit('SELECT_PICK_UP_INFO', {
addressInfo,
});
sheep.$router.back();
};
/**
* 获取门店列表数据
*/
const getList = async () => {
if (state.loading || state.loaded) {
return;
}
state.loading = true;
const { data, code } = await DeliveryApi.getDeliveryPickUpStoreList({
latitude: state.user_latitude,
longitude: state.user_longitude,
});
if (code !== 0) {
return;
}
state.loading = false;
state.storeList = data;
};
onMounted(() => {
if (state.user_latitude && state.user_longitude) {
getList();
} else {
selfLocation();
getList();
}
});
onLoad(() => {
try {
state.user_latitude = uni.getStorageSync(LATITUDE);
state.user_longitude = uni.getStorageSync(LONGITUDE);
} catch (e) {
// error
}
});
</script>
<style lang="scss" scoped>
.line1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap
}
.geoPage {
position: fixed;
width: 100%;
height: 100%;
top: 0;
z-index: 10000;
}
.storeBox {
width: 100%;
background-color: #fff;
padding: 0 30rpx;
}
.storeBox-box {
width: 100%;
height: auto;
display: flex;
align-items: center;
padding: 23rpx 0;
justify-content: space-between;
border-bottom: 1px solid #eee;
}
.store-cent {
display: flex;
align-items: center;
width: 80%;
}
.store-cent-left {
//width: 45%;
flex: 2;
}
.store-img {
flex: 1;
width: 120rpx;
height: 120rpx;
border-radius: 6rpx;
margin-right: 22rpx;
}
.store-img .img {
width: 100%;
height: 100%;
}
.store-name {
color: #282828;
font-size: 30rpx;
margin-bottom: 22rpx;
font-weight: 800;
}
.store-address {
color: #666666;
font-size: 24rpx;
}
.store-phone {
width: 50rpx;
height: 50rpx;
color: #fff;
border-radius: 50%;
display: block;
text-align: center;
line-height: 48rpx;
background-color: #e83323;
margin-bottom: 22rpx;
text-decoration: none;
}
.store-distance {
font-size: 22rpx;
color: #e83323;
}
.iconfont {
font-size: 20rpx;
}
.row-right {
flex: 2;
//display: flex;
//flex-direction: column;
//align-items: flex-end;
//width: 33.5%;
}
</style>

475
pages/user/info.vue Normal file
View File

@@ -0,0 +1,475 @@
<!-- 用户信息 -->
<template>
<s-layout title="用户信息" class="set-userinfo-wrap">
<uni-forms
:model="state.model"
:rules="state.rules"
labelPosition="left"
border
class="form-box"
>
<!-- 头像 -->
<view class="ss-flex ss-row-center ss-col-center ss-p-t-60 ss-p-b-60 bg-white">
<view class="header-box-content">
<su-image
class="content-img"
isPreview
:current="0"
:src="state.model?.avatar"
:height="160"
:width="160"
:radius="80"
mode="scaleToFill"
/>
<view class="avatar-action">
<button class="ss-reset-button avatar-action-btn" @tap="onChooseAvatar">修改</button>
</view>
</view>
</view>
<view class="bg-white ss-p-x-30">
<!-- <view class="form-item">
<view class="label">昵称</view>
<view class="input-box">
<view>{{state.model.nickname}}</view>
</view>
</view> -->
<!-- 昵称 + 性别 -->
<uni-forms-item name="nickname" label="昵称">
<uni-easyinput
v-model="state.model.nickname"
placeholder="设置昵称"
:inputBorder="false"
:placeholderStyle="placeholderStyle"
/>
</uni-forms-item>
<uni-forms-item name="sex" label="性别">
<view class="ss-flex ss-col-center ss-h-100">
<radio-group @change="onChangeGender" class="ss-flex ss-col-center">
<label class="radio" v-for="item in sexRadioMap" :key="item.value">
<view class="ss-flex ss-col-center ss-m-r-32">
<radio
:value="item.value"
color="var(--ui-BG-Main)"
style="transform: scale(0.8)"
:checked="parseInt(item.value) === state.model?.sex"
/>
<view class="gender-name">{{ item.name }}</view>
</view>
</label>
</radio-group>
</view>
</uni-forms-item>
<!-- <view class="form-item" @tap="onChangeMobile">
<view class="label">手机号</view>
<view class="input-box">
<view v-if="userInfo.mobile">{{userInfo.mobile}}</view>
<view v-else class="placeholder">请绑定手机号</view>
<view class="ss-flex ss-col-center">
<su-radio class="ss-flex" v-if="userInfo.mobile" :modelValue="true" />
<button v-else class="ss-reset-button ss-flex ss-col-center ss-row-center">
<text class="_icon-forward" style="color: #bbbbbb; font-size: 26rpx"></text>
</button>
</view>
</view>
</view>
<view class="form-item" @tap="onSetPassword">
<view class="label">登录密码</view>
<view class="input-box">
<view v-if="userInfo.password">{{userInfo.password}}</view>
<view v-else class="placeholder">点击修改登录密码</view>
<view class="ss-flex ss-col-center">
<su-radio class="ss-flex" v-if="userInfo.password" :modelValue="true" />
<button v-else class="ss-reset-button ss-flex ss-col-center ss-row-center">
<text class="_icon-forward" style="color: #bbbbbb; font-size: 26rpx"></text>
</button>
</view>
</view>
</view>
-->
</view>
</uni-forms>
<!-- 当前社交平台的绑定关系只处理 wechat 微信场景 -->
<!-- <view v-if="sheep.$platform.name !== 'H5'">
<view class="title-box ss-p-l-30">第三方账号绑定</view>
<view class="account-list ss-flex ss-row-between">
<view v-if="'WechatOfficialAccount' === sheep.$platform.name" class="ss-flex ss-col-center">
<image
class="list-img"
:src="sheep.$url.static('/static/img/shop/platform/WechatOfficialAccount.png')"
/>
<text class="list-name">微信公众号</text>
</view>
<view v-if="'WechatMiniProgram' === sheep.$platform.name" class="ss-flex ss-col-center">
<image
class="list-img"
:src="sheep.$url.static('/static/img/shop/platform/WechatMiniProgram.png')"
/>
<text class="list-name">微信小程序</text>
</view>
<view v-if="'App' === sheep.$platform.name" class="ss-flex ss-col-center">
<image
class="list-img"
:src="sheep.$url.static('/static/img/shop/platform/wechat.png')"
/>
<text class="list-name">微信开放平台</text>
</view>
<view class="ss-flex ss-col-center">
<view class="info ss-flex ss-col-center" v-if="state.thirdInfo">
<image class="avatar ss-m-r-20" :src="sheep.$url.cdn(state.thirdInfo.avatar)" />
<text class="name">{{ state.thirdInfo.nickname }}</text>
</view>
<view class="bind-box ss-m-l-20">
<button
v-if="state.thirdInfo.openid"
class="ss-reset-button relieve-btn"
@tap="unBindThirdOauth"
>
解绑
</button>
<button v-else class="ss-reset-button bind-btn" @tap="bindThirdOauth">绑定</button>
</view>
</view>
</view>
</view> -->
<su-fixed bottom placeholder bg="none">
<view class="footer-box ss-p-20">
<button class="ss-rest-button logout-btn ui-Shadow-Main" @tap="onSubmit">保存</button>
</view>
</su-fixed>
<qrcode-modal />
</s-layout>
</template>
<script setup>
import qrcodeModal from '@/components/qrcode-modal/qrcode-modal.vue';
import { computed, reactive, ref, onBeforeMount } from 'vue';
import sheep from '@/sheep';
import { clone } from 'lodash-es';
import { showAuthModal } from '@/sheep/hooks/useModal';
import FileApi from '@/sheep/api/infra/file';
import UserApi from '@/sheep/api/member/user';
const clerk = ref();
const state = reactive({
model: {}, // 个人信息
rules: {},
thirdInfo: {}, // 社交用户的信息
});
const placeholderStyle = 'color:#BBBBBB;font-size:28rpx;line-height:normal';
const sexRadioMap = [
{
name: '男',
value: '1',
},
{
name: '女',
value: '2',
},
];
const userInfo = computed(() => sheep.$store('user').userInfo);
// 选择性别
function onChangeGender(e) {
state.model.sex = e.detail.value;
}
// 修改手机号
const onChangeMobile = () => {
showAuthModal('changeMobile');
};
function onChooseAvatar() {
uni.chooseImage({
count: 1, //默认9
sourceType: ['album', 'camera'],
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
success: (res) => {
for (let i = 0; i < res.tempFilePaths.length; i++) {
uni.getImageInfo({
src: res.tempFilePaths[i],
success: (image) => {
uploadAvatar(image.path);
}
});
}
}
});
}
// 上传头像文件
async function uploadAvatar(tempUrl) {
if (!tempUrl) {
return;
}
let { data } = await FileApi.uploadFile(tempUrl);
state.model.avatar = data;
}
// 修改密码
function onSetPassword() {
showAuthModal('changePassword');
}
// 绑定第三方账号
async function bindThirdOauth() {
let result = await sheep.$platform.useProvider('wechat').bind();
if (result) {
await getUserInfo();
}
}
// 解绑第三方账号
function unBindThirdOauth() {
uni.showModal({
title: '解绑提醒',
content: '解绑后您将无法通过微信登录此账号',
cancelText: '再想想',
confirmText: '确定',
success: async function (res) {
if (!res.confirm) {
return;
}
const result = await sheep.$platform.useProvider('wechat').unbind(state.thirdInfo.openid);
if (result) {
await getUserInfo();
}
},
});
}
// 保存信息
async function onSubmit() {
const { code } = await UserApi.updateUser({
avatar: state.model.avatar,
nickname: state.model.nickname,
sex: state.model.sex,
});
if (code === 0) {
await getUserInfo();
}
}
// 获得用户信息
const getUserInfo = async () => {
// 个人信息
const userInfo = await sheep.$store('user').getInfo();
state.model = clone(userInfo);
// 获得社交用户的信息
if (sheep.$platform.name !== 'H5') {
const result = await sheep.$platform.useProvider('wechat').getInfo();
state.thirdInfo = result || {};
}
};
onBeforeMount(() => {
getUserInfo();
});
</script>
<style lang="scss" scoped>
:deep() {
.uni-file-picker {
border-radius: 50%;
}
.uni-file-picker__container {
margin: -14rpx -12rpx;
}
.file-picker__progress {
height: 0 !important;
}
.uni-list-item__content-title {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
}
.uni-icons {
font-size: 40rpx !important;
}
.is-disabled {
color: #333333;
}
}
:deep(.disabled) {
opacity: 1;
}
.gender-name {
font-size: 28rpx;
font-weight: 500;
line-height: normal;
color: #333333;
}
.title-box {
font-size: 28rpx;
font-weight: 500;
color: #666666;
line-height: 100rpx;
}
.logout-btn {
width: 710rpx;
height: 80rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 40rpx;
font-size: 30rpx;
font-weight: 500;
color: $white;
}
.radio-dark {
filter: grayscale(100%);
filter: gray;
opacity: 0.4;
}
.content-img {
border-radius: 50%;
}
.header-box-content {
position: relative;
width: 160rpx;
height: 160rpx;
overflow: hidden;
border-radius: 50%;
background-color: #f6f6f6;
}
.avatar-action {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 0;
z-index: 1;
width: 160rpx;
height: 46rpx;
background: rgba(#000000, 0.3);
.avatar-action-btn {
width: 160rpx;
height: 46rpx;
font-weight: 500;
font-size: 24rpx;
color: #ffffff;
}
}
// 绑定项
.account-list {
background-color: $white;
height: 100rpx;
padding: 0 20rpx;
.list-img {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
.list-name {
font-size: 28rpx;
color: #333333;
}
.info {
.avatar {
width: 38rpx;
height: 38rpx;
border-radius: 50%;
overflow: hidden;
}
.name {
font-size: 28rpx;
font-weight: 400;
color: $dark-9;
}
}
.bind-box {
width: 100rpx;
height: 50rpx;
line-height: normal;
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
.bind-btn {
width: 100%;
height: 100%;
border-radius: 25rpx;
background: #f4f4f4;
color: #999999;
}
.relieve-btn {
width: 100%;
height: 100%;
border-radius: 25rpx;
background: var(--ui-BG-Main-opacity-1);
color: var(--ui-BG-Main);
}
}
}
.list-border {
font-size: 28rpx;
font-weight: 400;
color: #333333;
border-bottom: 2rpx solid #eeeeee;
}
image {
width: 100%;
height: 100%;
}
.form-item {
display: flex;
align-items: center;
line-height: 110rpx;
border-top: 1px #eee solid;
.label {
width: 140rpx;
justify-content: flex-start;
}
.input-box {
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
.placeholder {
color:#BBBBBB;
font-size:28rpx;
line-height:normal
}
}
}
</style>

382
pages/user/vip.vue Normal file
View File

@@ -0,0 +1,382 @@
<template>
<s-layout title="我的特权">
<view class="page" v-if="isPass">
<view class="vip-box">
<view class="vip-name">VIP尊享会员</view>
<view class="vip-tip-box">
<u-avatar size="50" :src="userInfo.avatar"></u-avatar>
<view class="vip-tip" v-if="userInfo.isVip">VIP会员({{showDayTime(userInfo.vipEndTime)}}到期)</view>
<view class="vip-tip" v-else>暂未激活VIP会员</view>
</view>
</view>
<view class="price-tab-box">
<view class="price-tab" v-for="(item, index) in dataList" :key="index">
<view @click="changeTab(index)" class="price-tab-border" :class="[currentIndex==index ? 'active' : '']">
<view class="price-texta">{{item.num}}{{item.unit}}</view>
<view class="price-textb">{{item.month}}/{{item.tip}}</view>
<view class="price-textc">{{item.pay}}</view>
<view class="tab-band" v-if="item.band">新人优惠</view>
</view>
</view>
</view>
<view class="vip-title">
VIP特权
</view>
<!-- <view class="vip-info">
<view class="vip-info-icon">
<u-icon name="star-fill" color="#ffd32f" size="36"></u-icon>
</view>
<view>
<view class="vip-info-title">拥有申请置顶推荐特权</view>
<view class="vip-info-text">每天可以免费申请置顶推荐</view>
</view>
</view> -->
<view class="vip-info">
<view class="vip-info-icon">
<u-icon name="edit-pen-fill" color="#ffd32f" size="36"></u-icon>
</view>
<view>
<view class="vip-info-title">每天3次发布动态无广告</view>
<view class="vip-info-text">每天免费发布3次无广告增加曝光</view>
</view>
</view>
<view class="vip-info">
<view class="vip-info-icon">
<u-icon name="weixin-fill" color="#ffd32f" size="36"></u-icon>
</view>
<view>
<view class="vip-info-title">每天5次交换名片</view>
<view class="vip-info-text">每天可以免费交换5次名片</view>
</view>
</view>
<view class="vip-info">
<view class="vip-info-icon">
<u-icon name="chat-fill" color="#ffd32f" size="36"></u-icon>
</view>
<view>
<view class="vip-info-title">打招呼聊天免费</view>
<view class="vip-info-text">打招呼聊天发送消息无限制</view>
</view>
</view>
<view class="vip-info">
<view class="vip-info-icon">
<u-icon name="star-fill" color="#ffd32f" size="36"></u-icon>
</view>
<view>
<view class="vip-info-title">每天3次首页置顶</view>
<view class="vip-info-text">每天可以免费置顶3次</view>
</view>
</view>
<view class="vip-info">
<view class="vip-info-icon">
<u-icon name="map-fill" color="#ffd32f" size="40"></u-icon>
</view>
<view>
<view class="vip-info-title">可查看同城的用户</view>
<view class="vip-info-text">离得近奔现的概率更高</view>
</view>
</view>
<view v-if="platform == 'android'">
<view class="btn-box" v-if="userInfo.isVip">
<view class="buy-btn">已开通</view>
</view>
<view class="btn-box" v-else>
<view class="btn-border" @click="payHandle">
<view class="buy-btn">{{pay.pay}} 立即购买</view>
<view class="btn-tip">每天不到{{pay.day}}</view>
</view>
<!-- <view class="btn-border" @click="goPay">
<view class="pay-btn">去充值趣豆</view>
</view> -->
</view>
</view>
<view v-else>
<view class="btn-box" v-if="userInfo.isVip">
<view class="buy-btn">已开通</view>
</view>
<view class="btn-box" v-else>
<view class="btn-border" @click="payHot">
<view class="buy-btn">{{pay.pay}} 购买会员</view>
<view class="btn-tip">每天不到{{pay.day}}</view>
</view>
<!-- <view class="btn-border" @click="goPay">
<view class="pay-btn">去充值趣豆</view>
</view> -->
</view>
</view>
</view>
</s-layout>
</template>
<script>
import sheep from '@/sheep';
import PayPointApi from '@/sheep/api/pay/point';
import dayjs from 'dayjs';
export default {
data() {
return {
currentIndex: 1,
dataList: [
{
band: false,
num: 1,
month: 5.7,
pay: 40,
day: 5.7,
unit: '个周',
tip: '天',
},
{
band: true,
num: 1,
month: 2.5,
pay: 80,
day: 2.5,
unit: '个月',
tip: '天',
},
{
band: false,
num: 6,
month: 40,
pay: 240,
day: 1.2,
unit: '个月',
tip: '月',
}
],
pay: {
},
my: 'false',
platform: 'android',
}
},
onLoad(option) {
this.my = option.my;
this.pay = this.dataList[this.currentIndex];
var that = this;
uni.getSystemInfo({
success: function(e) {
that.platform = e.platform;
}
});
sheep.$store('user').getInfo();
},
computed: {
userInfo: {
get() {
return sheep.$store('user').userInfo;
},
},
isPass() {
return sheep.$store('user').tradeConfig.weixinEnabled;
},
},
methods: {
changeTab(index) {
this.currentIndex = index;
this.pay = this.dataList[index];
},
payHot() {
uni.showModal({
title: '温馨提示',
content: '苹果用户请联系客服充值客服微信rbtnet',
showCancel: false,
success: function(res) {
if (res.confirm) {
uni.setClipboardData({
data: 'rbtnet' ,
success: function (res) {
uni.showToast({
title: '复制成功',
});
}
});
}
}
});
},
goPay() {
this.$u.route({
url: 'pages/me/coin/pay',
params: {
my: 'true',
}
});
},
payHandle() {
var coinNum = 100*this.pay.pay;
PayPointApi.createPointRecharge({
payPrice: coinNum,
}).then(res => {
sheep.$router.go('/pages/pay/vip/index', {
id: res.data.payOrderId,
orderType: 'recharge',
});
});
},
showDayTime(datetime) {
if (!datetime) return "";
return dayjs(datetime).format('YYYY-MM-DD HH:mm:ss');
},
}
}
</script>
<style lang="scss" scoped>
.page {
padding: 15px;
padding-bottom: 150px;
background-color: #fff;
}
.vip-box {
padding: 15px;
background-image: linear-gradient(180deg, #ffd32f, #db854e);
align-items: center;
justify-content: center;
display: flex;
flex-direction: column;
border-radius: 10px;
margin-bottom: 15px;
}
.vip-name {
color: #fff;
font-size: 32rpx;
font-weight: bold;
margin-bottom: 50rpx;
}
.vip-tip {
color: #fff;
font-size: 24rpx;
margin-left: 5px;
}
.price-tab-box {
display: flex;
}
.price-tab {
display: flex;
flex: 1;
padding-right: 5px;
}
.price-tab .active {
border: 8rpx solid #e09049;
color: #e09049;
}
.price-tab-box .price-tab:last-child {
padding-right: 0;
}
.price-tab-border {
border: 8rpx solid #f5e8e8;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 20px 0;
border-radius: 10px;
color: #858585;
position: relative;
}
.price-texta {
font-size: 36rpx;
margin-bottom: 5px;
font-weight: bold;
}
.price-textb {
font-size: 30rpx;
margin-bottom: 5px;
}
.price-textc {
font-size: 36rpx;
}
.vip-title {
font-size: 28rpx;
color: #9e9696ed;
padding: 15px 0;
font-weight: bold;
}
.vip-info {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.vip-info-icon {
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid #f5e8e8;
padding: 15px;
border-radius: 10px;
}
.vip-info-title {
font-size: 30rpx;
margin-bottom: 5px;
font-weight: bold;
}
.vip-info-text {
font-size: 26rpx;
color: #858585;
}
.btn-box {
position: fixed;
bottom: 20px;
left: 0;
right: 0;
padding: 15px;
}
.buy-btn {
display: flex;
align-items: center;
justify-content: center;
background-color: #ffd32f;
padding: 15px;
border-radius: 10px;
font-size: 30rpx;
font-weight: bold;
color: #fff;
}
.pay-btn {
display: flex;
align-items: center;
justify-content: center;
background-color: #18b566;
padding: 15px;
border-radius: 10px;
font-size: 30rpx;
font-weight: bold;
color: #fff;
}
.btn-border {
position: relative;
margin-top: 30rpx;
}
.tab-band {
position: absolute;
background-color: #efc93c;
top: -10px;
border-radius: 5px;
padding: 5px 10px;
font-size: 22rpx;
color: #fff;
}
.vip-tip-box {
display: flex;
align-items: center;
}
.btn-tip {
position: absolute;
background-color: #efa3fa;
top: -10px;
right: -3px;
border: 3px solid #fff;
border-radius: 10px;
padding: 3px 5px;
font-size: 22rpx;
color: #fff;
}
</style>

379
pages/user/wallet/money.vue Normal file
View File

@@ -0,0 +1,379 @@
<!-- 我的钱包 -->
<template>
<s-layout class="wallet-wrap" title="我的钻石">
<!-- 钱包卡片 -->
<view class="header-box ss-flex ss-row-center ss-col-center">
<view class="card-box ui-BG-Main ui-Shadow-Main">
<view class="card-head ss-flex ss-col-center">
<view class="card-title ss-m-r-10">钻石余额</view>
<view
@tap="state.showMoney = !state.showMoney"
class="ss-eye-icon"
:class="state.showMoney ? 'cicon-eye' : 'cicon-eye-off'"
/>
</view>
<view class="ss-flex ss-row-between ss-col-center ss-m-t-64">
<view class="money-num">{{
state.showMoney ? fen2yuan(userWallet.balance) : '*****'
}}</view>
<button class="ss-reset-button topup-btn" @tap="sheep.$router.go('/pages/pay/recharge')">
充值
</button>
</view>
</view>
</view>
<su-sticky>
<!-- 统计 -->
<view class="filter-box ss-p-x-30 ss-flex ss-col-center ss-row-between">
<uni-datetime-picker
v-model="state.data"
type="daterange"
@change="onChangeTime"
:end="state.today"
>
<button class="ss-reset-button date-btn">
<text>{{ dateFilterText }}</text>
<text class="cicon-drop-down ss-seldate-icon"></text>
</button>
</uni-datetime-picker>
<view class="total-box">
<view class="ss-m-b-10">总收入{{ fen2yuan(state.summary.totalIncome) }}</view>
<view>总支出{{ fen2yuan(state.summary.totalExpense) }}</view>
</view>
</view>
<su-tabs
:list="tabMaps"
@change="onChange"
:scrollable="false"
:current="state.currentTab"
></su-tabs>
</su-sticky>
<s-empty v-if="state.pagination.total === 0" text="暂无数据" icon="/static/data-empty.png" />
<!-- 钱包记录 -->
<view v-if="state.pagination.total > 0">
<view
class="wallet-list ss-flex border-bottom"
v-for="item in state.pagination.list"
:key="item.id"
>
<view class="list-content">
<view class="title-box ss-flex ss-row-between ss-m-b-20">
<text class="title ss-line-1">
{{ item.title }}
</text>
<view class="money">
<text v-if="item.price >= 0" class="add">+{{ fen2yuan(item.price) }}</text>
<text v-else class="minus">{{ fen2yuan(item.price) }}</text>
</view>
</view>
<text class="time">
{{ sheep.$helper.timeFormat(state.createTime, 'yyyy-mm-dd hh:MM:ss') }}
</text>
</view>
</view>
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
/>
</s-layout>
</template>
<script setup>
import { computed, reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import dayjs from 'dayjs';
import _ from 'lodash-es';
import PayWalletApi from '@/sheep/api/pay/wallet';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import { resetPagination } from '@/sheep/util';
const headerBg = sheep.$url.css('/static/img/shop/user/wallet_card_bg.png');
// 数据
const state = reactive({
showMoney: false,
date: [], // 筛选的时间段
currentTab: 0,
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 8,
},
summary: {
totalIncome: 0,
totalExpense: 0,
},
loadStatus: '',
today: '',
});
const tabMaps = [
{
name: '全部',
value: '',
},
{
name: '收入',
value: '1',
},
{
name: '支出',
value: '2',
},
];
const userWallet = computed(() => sheep.$store('user').userWallet);
// 格式化时间段
const dateFilterText = computed(() => {
if (state.date[0] === state.date[1]) {
return state.date[0];
} else {
return state.date.join('~');
}
});
// 获得钱包记录分页
async function getLogList() {
state.loadStatus = 'loading';
const { data, code } = await PayWalletApi.getWalletTransactionPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
type: tabMaps[state.currentTab].value,
'createTime[0]': state.date[0] + ' 00:00:00',
'createTime[1]': state.date[1] + ' 23:59:59',
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
// 获得钱包统计
async function getSummary() {
const { data, code } = await PayWalletApi.getWalletTransactionSummary({
createTime: [state.date[0] + ' 00:00:00', state.date[1] + ' 23:59:59'],
});
if (code !== 0) {
return;
}
state.summary = data;
}
onLoad(() => {
state.today = dayjs().format('YYYY-MM-DD');
state.date = [state.today, state.today];
getLogList();
getSummary();
// 刷新钱包的缓存
sheep.$store('user').getWallet();
});
// 处理 tab 切换
function onChange(e) {
state.currentTab = e.index;
// 重新加载列表
resetPagination(state.pagination);
getLogList();
getSummary();
}
// 处理时间筛选
function onChangeTime(e) {
state.date[0] = e[0];
state.date[1] = e[e.length - 1];
// 重新加载列表
resetPagination(state.pagination);
getLogList();
getSummary();
}
onReachBottom(() => {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getLogList();
});
</script>
<style lang="scss" scoped>
// 钱包
.header-box {
background-color: $white;
padding: 30rpx;
.card-box {
width: 100%;
min-height: 300rpx;
padding: 40rpx;
background-size: 100% 100%;
border-radius: 30rpx;
overflow: hidden;
position: relative;
z-index: 1;
box-sizing: border-box;
&::after {
content: '';
display: block;
width: 100%;
height: 100%;
z-index: 2;
position: absolute;
top: 0;
left: 0;
background: v-bind(headerBg) no-repeat;
pointer-events: none;
}
.card-head {
color: $white;
font-size: 30rpx;
}
.ss-eye-icon {
font-size: 40rpx;
color: $white;
}
.money-num {
font-size: 70rpx;
line-height: 70rpx;
font-weight: 500;
color: $white;
font-family: OPPOSANS;
}
.reduce-num {
font-size: 26rpx;
font-weight: 400;
color: $white;
}
.topup-btn {
width: 120rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 30px;
font-size: 26rpx;
font-weight: 500;
background-color: $white;
color: var(--ui-BG-Main);
}
}
}
// 筛选
.filter-box {
height: 114rpx;
background-color: $bg-page;
.total-box {
font-size: 24rpx;
font-weight: 500;
color: $dark-9;
}
.date-btn {
background-color: $white;
line-height: 54rpx;
border-radius: 27rpx;
padding: 0 20rpx;
font-size: 24rpx;
font-weight: 500;
color: $dark-6;
.ss-seldate-icon {
font-size: 50rpx;
color: $dark-9;
}
}
}
.tabs-box {
background: $white;
border-bottom: 2rpx solid #eeeeee;
}
// tab
.wallet-tab-card {
.tab-item {
height: 80rpx;
position: relative;
.tab-title {
font-size: 30rpx;
}
.cur-tab-title {
font-weight: $font-weight-bold;
}
.tab-line {
width: 60rpx;
height: 6rpx;
border-radius: 6rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 2rpx;
background-color: var(--ui-BG-Main);
}
}
}
// 钱包记录
.wallet-list {
padding: 30rpx;
background-color: #ffff;
.head-img {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
background: $gray-c;
}
.list-content {
justify-content: space-between;
align-items: flex-start;
flex: 1;
.title {
font-size: 28rpx;
color: $dark-3;
width: 400rpx;
}
.time {
color: $gray-c;
font-size: 22rpx;
}
}
.money {
font-size: 28rpx;
font-weight: bold;
font-family: OPPOSANS;
.add {
color: var(--ui-BG-Main);
}
.minus {
color: $dark-3;
}
}
}
</style>

323
pages/user/wallet/score.vue Normal file
View File

@@ -0,0 +1,323 @@
<!-- 我的积分 -->
<template>
<s-layout class="wallet-wrap" title="我的积分">
<!-- 钱包卡片 -->
<view class="header-box ss-flex ss-row-center ss-col-center">
<view class="card-box ui-BG-Main ui-Shadow-Main">
<view class="card-head ss-flex ss-col-center">
<view class="card-title ss-m-r-10">剩余积分</view>
<view
@tap="state.showMoney = !state.showMoney"
class="ss-eye-icon"
:class="state.showMoney ? 'cicon-eye' : 'cicon-eye-off'"
/>
</view>
<view class="ss-flex ss-row-between ss-col-center ss-m-t-64">
<view class="money-num">{{
state.showMoney ? (userInfo.point || 0) : '*****'
}}</view>
<button class="ss-reset-button topup-btn" @tap="sheep.$router.go('/pages/point/recharge')">
充值
</button>
</view>
</view>
</view>
<!-- tab -->
<su-sticky>
<!-- 统计 -->
<view class="filter-box ss-p-x-30 ss-flex ss-col-center ss-row-between">
<uni-datetime-picker
v-model="state.date"
type="daterange"
@change="onChangeTime"
:end="state.today"
>
<button class="ss-reset-button date-btn">
<text>{{ dateFilterText }}</text>
<text class="cicon-drop-down ss-seldate-icon"></text>
</button>
</uni-datetime-picker>
<!-- TODO 芋艿钱包可优化 -->
<!-- <view class="total-box">-->
<!-- <view class="ss-m-b-10">总收入{{ state.pagination.income }}</view>-->
<!-- <view>总支出{{ -state.pagination.expense }}</view>-->
<!-- </view>-->
</view>
<su-tabs
:list="tabMaps"
@change="onChange"
:scrollable="false"
:current="state.currentTab"
></su-tabs>
</su-sticky>
<!-- list -->
<view class="list-box">
<view v-if="state.pagination.total > 0">
<view
class="list-item ss-flex ss-col-center ss-row-between"
v-for="item in state.pagination.list"
:key="item.id"
>
<view class="ss-flex-col">
<view class="name"
>{{ item.title }}{{ item.description ? ' - ' + item.description : '' }}</view
>
<view class="time">{{
sheep.$helper.timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss')
}}</view>
</view>
<view class="add" v-if="item.point > 0">+{{ item.point }}</view>
<view class="minus" v-else>{{ item.point }}</view>
</view>
</view>
<s-empty v-else text="暂无数据" icon="/static/data-empty.png" />
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
@tap="onLoadMore"
/>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { computed, reactive } from 'vue';
import _ from 'lodash-es';
import dayjs from 'dayjs';
import PointApi from '@/sheep/api/member/point';
import { resetPagination } from '@/sheep/util';
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const userInfo = computed(() => sheep.$store('user').userInfo);
const sys_navBar = sheep.$platform.navbar;
const state = reactive({
showMoney: true,
currentTab: 0,
pagination: {
list: 0,
total: 0,
pageSize: 20,
pageNo: 1,
},
loadStatus: '',
date: [],
today: '',
});
const tabMaps = [
{
name: '全部',
value: 'all',
},
{
name: '收入',
value: 'true',
},
{
name: '支出',
value: 'false',
},
];
const dateFilterText = computed(() => {
if (state.date[0] === state.date[1]) {
return state.date[0];
} else {
return state.date.join('~');
}
});
async function getLogList() {
state.loadStatus = 'loading';
let { code, data } = await PointApi.getPointRecordPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
addStatus: state.currentTab > 0 ? tabMaps[state.currentTab].value : undefined,
'createTime[0]': state.date[0] + ' 00:00:00',
'createTime[1]': state.date[1] + ' 23:59:59',
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
onLoad(() => {
state.today = dayjs().format('YYYY-MM-DD');
state.date = [state.today, state.today];
getLogList();
// 刷新积分缓存
sheep.$store('user').getInfo();
});
function onChange(e) {
state.currentTab = e.index;
resetPagination(state.pagination);
getLogList();
}
function onChangeTime(e) {
state.date[0] = e[0];
state.date[1] = e[e.length - 1];
resetPagination(state.pagination);
getLogList();
}
function onLoadMore() {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getLogList();
}
onReachBottom(() => {
onLoadMore();
});
</script>
<style lang="scss" scoped>
// 钱包
.header-box {
background-color: $white;
padding: 30rpx;
.card-box {
width: 100%;
min-height: 300rpx;
padding: 40rpx;
background-size: 100% 100%;
border-radius: 30rpx;
overflow: hidden;
position: relative;
z-index: 1;
box-sizing: border-box;
&::after {
content: '';
display: block;
width: 100%;
height: 100%;
z-index: 2;
position: absolute;
top: 0;
left: 0;
background: v-bind(headerBg) no-repeat;
pointer-events: none;
}
.card-head {
color: $white;
font-size: 30rpx;
}
.ss-eye-icon {
font-size: 40rpx;
color: $white;
}
.money-num {
font-size: 70rpx;
line-height: 70rpx;
font-weight: 500;
color: $white;
font-family: OPPOSANS;
}
.reduce-num {
font-size: 26rpx;
font-weight: 400;
color: $white;
}
.topup-btn {
width: 120rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 30px;
font-size: 26rpx;
font-weight: 500;
background-color: $white;
color: var(--ui-BG-Main);
}
}
}
// 筛选
.filter-box {
height: 114rpx;
background-color: $bg-page;
.total-box {
font-size: 24rpx;
font-weight: 500;
color: $dark-9;
}
.date-btn {
background-color: $white;
line-height: 54rpx;
border-radius: 27rpx;
padding: 0 20rpx;
font-size: 24rpx;
font-weight: 500;
color: $dark-6;
.ss-seldate-icon {
font-size: 50rpx;
color: $dark-9;
}
}
}
.list-box {
.list-item {
background: #fff;
border-bottom: 1rpx solid #dfdfdf;
padding: 30rpx;
.name {
font-size: 28rpx;
font-weight: 500;
color: rgba(102, 102, 102, 1);
line-height: 28rpx;
margin-bottom: 20rpx;
}
.time {
font-size: 24rpx;
font-weight: 500;
color: rgba(196, 196, 196, 1);
line-height: 24px;
}
.add {
font-size: 30rpx;
font-weight: 500;
color: #e6b873;
}
.minus {
font-size: 30rpx;
font-weight: 500;
color: $dark-3;
}
}
}
</style>