第一次提交

This commit is contained in:
gxwebsoft
2023-08-04 13:04:21 +08:00
commit 7a73b38bd5
764 changed files with 166417 additions and 0 deletions
+212
View File
@@ -0,0 +1,212 @@
<template>
<view class="container" :style="appThemeStyle">
<!-- 标题 -->
<view class="page-title">收货地址</view>
<!-- 表单组件 -->
<view class="form-wrapper">
<u-form :model="form" ref="uForm" label-width="140rpx">
<u-form-item label="姓名" prop="name">
<u-input v-model="form.name" placeholder="请输入收货人姓名" />
</u-form-item>
<u-form-item label="电话" prop="phone">
<u-input v-model="form.phone" placeholder="请输入收货人手机号" />
</u-form-item>
<u-form-item label="地区" prop="region">
<select-region ref="sRegion" v-model="form.region" />
</u-form-item>
<u-form-item label="详细地址" prop="detail" :border-bottom="false">
<u-input v-model="form.detail" placeholder="街道门牌、楼层等信息" />
</u-form-item>
</u-form>
</view>
<!-- 操作按钮 -->
<view class="footer">
<view class="btn-wrapper">
<!-- #ifdef MP-WEIXIN -->
<view class="btn-item btn-item-wechat" @click="chooseAddress()">选择微信收货地址</view>
<!-- #endif -->
<view class="btn-item btn-item-main" :class="{ disabled }" @click="handleSubmit()">保存</view>
</view>
</view>
</view>
</template>
<script>
import SelectRegion from '@/components/select-region/select-region'
import { isMobile } from '@/utils/verify'
import * as AddressApi from '@/api/address'
// 表单字段元素
const form = {
name: '',
phone: '',
region: [],
detail: ''
}
// 表单验证规则
const rules = {
name: [{
required: true,
message: '请输入姓名',
trigger: ['blur', 'change']
}],
phone: [{
required: true,
message: '请输入手机号',
trigger: ['blur', 'change']
}, {
// 自定义验证函数
validator: (rule, value, callback) => {
// 返回true表示校验通过,返回false表示不通过
return isMobile(value)
},
message: '手机号码不正确',
// 触发器可以同时用blur和change
trigger: ['blur'],
}],
region: [{
required: true,
message: '请选择省市区',
trigger: ['blur', 'change'],
type: 'array'
}],
detail: [{
required: true,
message: '请输入详细地址',
trigger: ['blur', 'change']
}],
}
export default {
components: {
SelectRegion
},
data() {
return {
form,
rules,
// 按钮禁用
disabled: false
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {},
// 必须要在onReady生命周期,因为onLoad生命周期组件可能尚未创建完毕
onReady() {
this.$refs.uForm.setRules(this.rules)
},
methods: {
// 选择微信地址
// #ifdef MP-WEIXIN
chooseAddress() {
const app = this
const { form, $refs } = this
uni.chooseAddress({
success(res) {
const names = $refs.sRegion.getOptionItemByNames(res)
form.name = res.userName
form.phone = res.telNumber
form.detail = res.detailInfo
form.region = names.length > 0 ? names : []
},
fail({ errMsg }) {
app.$toast(errMsg)
console.error('获取微信地址失败:', errMsg)
}
})
},
// #endif
// 表单提交
handleSubmit() {
const app = this
if (app.disabled) {
return false
}
app.$refs.uForm.validate(valid => {
if (valid) {
app.disabled = true
AddressApi.add(app.form)
.then(result => {
app.$toast(result.message)
uni.navigateBack()
})
.finally(() => app.disabled = false)
}
})
}
}
}
</script>
<style>
page {
background: #f7f8fa;
}
</style>
<style lang="scss" scoped>
.page-title {
width: 94%;
margin: 0 auto;
padding-top: 40rpx;
font-size: 28rpx;
color: rgba(69, 90, 100, 0.6);
}
.form-wrapper {
margin: 20rpx auto 20rpx auto;
padding: 0 40rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
background: #fff;
}
/* 底部操作栏 */
.footer {
margin-top: 80rpx;
.btn-wrapper {
height: 100%;
// display: flex;
// align-items: center;
padding: 0 20rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 86rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
.btn-item-wechat {
background: #0ba90b;
margin-bottom: 26rpx;
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
// 禁用按钮
&.disabled {
opacity: 0.6;
}
}
}
</style>
+307
View File
@@ -0,0 +1,307 @@
<template>
<view class="container" :style="appThemeStyle">
<view class="addres-list">
<view class="address-item" v-for="(item, index) in list" :key="index">
<view class="contacts">
<text class="name">{{ item.name }}</text>
<text class="phone">{{ item.phone }}</text>
</view>
<view class="address">
<text class="region" v-for="(region, idx) in item.region" :key="idx">{{ region }}</text>
<text class="detail">{{ item.detail }}</text>
</view>
<view class="line"></view>
<view class="item-option">
<view class="_left">
<label class="item-radio" @click.stop="handleSetDefault(item.address_id)">
<radio class="radio" :color="appTheme.mainBg" :checked="item.address_id == defaultId"></radio>
<text class="text">{{ item.address_id == defaultId ? '默认' : '选择' }}</text>
</label>
</view>
<view class="_right">
<view class="events">
<view class="event-item" @click="handleUpdate(item.address_id)">
<text class="iconfont icon-edit"></text>
<text class="title">编辑</text>
</view>
<view class="event-item" @click="handleRemove(item.address_id)">
<text class="iconfont icon-delete"></text>
<text class="title">删除</text>
</view>
</view>
</view>
</view>
</view>
</view>
<empty v-if="!list.length" :isLoading="isLoading" tips="亲,暂无收货地址" />
<!-- 底部操作按钮 -->
<view class="footer-fixed">
<view class="btn-wrapper">
<view class="btn-item btn-item-main" @click="handleCreate()">添加新地址</view>
</view>
</view>
</view>
</template>
<script>
import * as AddressApi from '@/api/address'
import Empty from '@/components/empty'
export default {
components: {
Empty
},
data() {
return {
//当前页面参数
options: {},
// 正在加载
isLoading: true,
// 收货地址列表
list: [],
// 默认收货地址
defaultId: null
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 当前页面参数
this.options = options
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
// 获取页面数据
this.getPageData()
},
methods: {
// 获取页面数据
getPageData() {
const app = this
app.isLoading = true
Promise.all([app.getDefaultId(), app.getAddressList()])
.then(() => {
// 列表排序把默认收货地址放到最前
app.onReorder()
})
.finally(() => app.isLoading = false)
},
// 获取收货地址列表
getAddressList() {
const app = this
return new Promise((resolve, reject) => {
AddressApi.list()
.then(result => {
app.list = result.data.list
resolve(result)
})
.catch(reject)
})
},
// 获取默认的收货地址
getDefaultId() {
return new Promise((resolve, reject) => {
const app = this
AddressApi.defaultId()
.then(result => {
app.defaultId = result.data.defaultId
resolve(result)
})
.catch(reject)
})
},
// 列表排序把默认收货地址放到最前
onReorder() {
const app = this
app.list.sort(item => {
return item.address_id == app.defaultId ? -1 : 1
})
},
/**
* 添加新地址
*/
handleCreate() {
this.$navTo('pages/address/create')
},
/**
* 编辑地址
* @param {int} addressId 收货地址ID
*/
handleUpdate(addressId) {
this.$navTo('pages/address/update', { addressId })
},
/**
* 删除收货地址
* @param {int} addressId 收货地址ID
*/
handleRemove(addressId) {
const app = this
uni.showModal({
title: "提示",
content: "您确定要删除当前收货地址吗?",
success({ confirm }) {
confirm && app.onRemove(addressId)
}
});
},
/**
* 确认删除收货地址
* @param {int} addressId 收货地址ID
*/
onRemove(addressId) {
const app = this
AddressApi.remove(addressId)
.then(result => {
app.getPageData()
})
},
/**
* 设置为默认地址
* @param {Object} addressId
*/
handleSetDefault(addressId) {
const app = this
AddressApi.setDefault(addressId)
.then(result => {
app.defaultId = addressId
app.options.from === 'checkout' && uni.navigateBack()
})
}
}
}
</script>
<style lang="scss" scoped>
.addres-list {
padding-top: 20rpx;
// 设置ios刘海屏底部横线安全区域
padding-bottom: calc(constant(safe-area-inset-bottom) + 140rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
}
// 项目内容
.address-item {
margin: 0 auto 20rpx auto;
padding: 30rpx 40rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0 rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
background: #fff;
}
.contacts {
font-size: 30rpx;
margin-bottom: 16rpx;
.name {
margin-right: 16rpx;
}
}
.address {
font-size: 28rpx;
.region {
margin-right: 10rpx;
}
}
.line {
margin: 20rpx 0;
border-bottom: 1rpx solid #f3f3f3;
}
.item-option {
display: flex;
justify-content: space-between;
height: 48rpx;
// 单选框
.item-radio {
font-size: 28rpx;
.radio {
vertical-align: middle;
transform: scale(0.76)
}
.text {
vertical-align: middle;
}
}
// 操作
.events {
display: flex;
align-items: center;
line-height: 48rpx;
.event-item {
font-size: 28rpx;
margin-right: 26rpx;
color: #4c4c4c;
&:last-child {
margin-right: 0;
}
.title {
margin-left: 8rpx;
}
}
}
}
// 底部操作栏
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
// 设置ios刘海屏底部横线安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.btn-wrapper {
height: 120rpx;
padding: 0 40rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 86rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 1rpx 5rpx 0 rgba(0, 0, 0, 0.05);
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
}
}
</style>
+249
View File
@@ -0,0 +1,249 @@
<template>
<view class="container" :style="appThemeStyle">
<!-- 标题 -->
<view class="page-title">收货地址</view>
<!-- 表单组件 -->
<view class="form-wrapper">
<u-form :model="form" ref="uForm" label-width="140rpx">
<u-form-item label="姓名" prop="name">
<u-input v-model="form.name" placeholder="请输入收货人姓名" />
</u-form-item>
<u-form-item label="电话" prop="phone">
<u-input v-model="form.phone" placeholder="请输入收货人手机号" />
</u-form-item>
<u-form-item label="地区" prop="region">
<select-region ref="sRegion" v-model="form.region" />
</u-form-item>
<u-form-item label="详细地址" prop="detail" :border-bottom="false">
<u-input v-model="form.detail" placeholder="街道门牌、楼层等信息" />
</u-form-item>
</u-form>
</view>
<!-- 操作按钮 -->
<view class="footer">
<view class="btn-wrapper">
<!-- #ifdef MP-WEIXIN -->
<view class="btn-item btn-item-wechat" @click="chooseAddress()">选择微信收货地址</view>
<!-- #endif -->
<view class="btn-item btn-item-main" :class="{ disabled }" @click="handleSubmit()">保存</view>
</view>
</view>
</view>
</template>
<script>
import SelectRegion from '@/components/select-region/select-region'
import { isMobile } from '@/utils/verify'
import * as AddressApi from '@/api/address'
// 表单验证规则
const rules = {
name: [{
required: true,
message: '请输入姓名',
trigger: ['blur', 'change']
}],
phone: [{
required: true,
message: '请输入手机号',
trigger: ['blur', 'change']
}, {
// 自定义验证函数
validator: (rule, value, callback) => {
// 返回true表示校验通过,返回false表示不通过
return isMobile(value)
},
message: '手机号码不正确',
// 触发器可以同时用blur和change
trigger: ['blur'],
}],
region: [{
required: true,
message: '请选择省市区',
trigger: ['blur', 'change'],
type: 'array'
}],
detail: [{
required: true,
message: '请输入详细地址',
trigger: ['blur', 'change']
}],
}
export default {
components: {
SelectRegion
},
data() {
return {
form: {
name: '',
phone: '',
region: [],
detail: ''
},
rules,
// 加载中
isLoading: true,
// 按钮禁用
disabled: false,
// 当前收货地址ID
addressId: null
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({ addressId }) {
// 当前收货地址ID
this.addressId = addressId
// 获取当前记录详情
this.getDetail()
},
// 必须要在onReady生命周期,因为onLoad生命周期组件可能尚未创建完毕
onReady() {
this.$refs.uForm.setRules(this.rules)
},
methods: {
// 获取当前记录详情
getDetail() {
const app = this
AddressApi.detail(app.addressId)
.then(result => {
const detail = result.data.detail
app.createFormData(detail)
})
},
// 选择微信地址
// #ifdef MP-WEIXIN
chooseAddress() {
const { form, $refs } = this
uni.chooseAddress({
success(res) {
const names = $refs.sRegion.getOptionItemByNames(res)
form.name = res.userName
form.phone = res.telNumber
form.detail = res.detailInfo
form.region = names.length > 0 ? names : []
},
fail({ errMsg }) {
app.$toast(errMsg)
console.error('获取微信地址失败:', errMsg)
}
})
},
// #endif
// 生成默认的表单数据
createFormData(detail) {
const { form } = this
form.name = detail.name
form.phone = detail.phone
form.detail = detail.detail
form.region = this.createRegion(detail)
},
createRegion(detail) {
return [{
label: detail.region.province,
value: detail.province_id
}, {
label: detail.region.city,
value: detail.city_id
}, {
label: detail.region.region,
value: detail.region_id
}]
},
// 表单提交
handleSubmit() {
const app = this
if (app.disabled) {
return false
}
app.$refs.uForm.validate(valid => {
if (valid) {
app.disabled = true
AddressApi.edit(app.addressId, app.form)
.then(result => {
app.$toast(result.message)
uni.navigateBack()
})
.finally(() => app.disabled = false)
}
})
}
}
}
</script>
<style>
page {
background: #f7f8fa;
}
</style>
<style lang="scss" scoped>
.page-title {
width: 94%;
margin: 0 auto;
padding-top: 40rpx;
font-size: 28rpx;
color: rgba(69, 90, 100, 0.6);
}
.form-wrapper {
margin: 20rpx auto 20rpx auto;
padding: 0 40rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
background: #fff;
}
/* 底部操作栏 */
.footer {
margin-top: 80rpx;
.btn-wrapper {
height: 100%;
// display: flex;
// align-items: center;
padding: 0 20rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 86rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
.btn-item-wechat {
background: #0ba90b;
margin-bottom: 26rpx;
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
// 禁用按钮
&.disabled {
opacity: 0.6;
}
}
}
</style>
+107
View File
@@ -0,0 +1,107 @@
<template>
<view v-if="!isLoading" class="container b-f p-b">
<view class="article-title">
<text class="f-32">{{ detail.title }}</text>
</view>
<view class="article-little dis-flex flex-x-between m-top10">
<view class="article-little__left">
<text class="article-views f-24 col-8">{{ detail.show_views }}次浏览</text>
</view>
<view class="article-little__right">
<text class="article-views f-24 col-8">{{ detail.view_time }}</text>
</view>
</view>
<view class="article-content m-top20">
<mp-html :content="detail.content" />
</view>
<!-- 快捷导航 -->
<shortcut />
</view>
</template>
<script>
import Shortcut from '@/components/shortcut'
import * as ArticleApi from '@/api/article'
export default {
components: {
Shortcut
},
data() {
return {
// 当前文章ID
articleId: null,
// 加载中
isLoading: true,
// 当前文章详情
detail: null
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 记录文章ID
this.articleId = options.articleId
// 获取文章详情
this.getArticleDetail()
},
methods: {
// 获取文章详情
getArticleDetail() {
const app = this
app.isLoading = true
ArticleApi.detail(app.articleId)
.then(result => {
app.detail = result.data.detail
})
.finally(() => app.isLoading = false)
}
},
/**
* 分享当前页面
*/
onShareAppMessage() {
const app = this
// 构建页面参数
const params = app.$getShareUrlParams({ articleId: app.articleId });
return {
title: app.detail.title,
path: "/pages/article/detail?" + params
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
const app = this
// 构建页面参数
const params = app.$getShareUrlParams({ articleId: app.articleId });
return {
title: app.detail.title,
path: "/pages/article/detail?" + params
}
}
}
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
padding: 20rpx;
background: #fff;
}
.article-content {
font-size: 28rpx;
}
</style>
+251
View File
@@ -0,0 +1,251 @@
<template>
<view class="container" :style="appThemeStyle">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption" @up="upCallback">
<!-- tab栏 -->
<u-tabs :list="tabList" :is-scroll="true" :current="curTab" :active-color="appTheme.mainBg" :duration="0.2" @change="onChangeTab" />
<!-- 文章列表 -->
<view class="article-list">
<view class="article-item" :class="[`show-type__${item.show_type}`]" v-for="(item, index) in articleList.data" :key="index"
@click="onTargetDetail(item.article_id)">
<!-- 小图模式 -->
<block v-if="item.show_type == 10">
<view class="article-item__left flex-box">
<view class="article-item__title">
<text class="twoline-hide">{{ item.title }}</text>
</view>
<view class="article-item__footer m-top10">
<text class="article-views f-24 col-8">{{ item.show_views }}次浏览</text>
</view>
</view>
<view class="article-item__image">
<image class="image" mode="widthFix" :src="item.image_url"></image>
</view>
</block>
<!-- 大图模式 -->
<block v-if="item.show_type == 20">
<view class="article-item__title">
<text class="twoline-hide">{{ item.title }}</text>
</view>
<view class="article-item__image m-top20">
<image class="image" mode="widthFix" :src="item.image_url"></image>
</view>
<view class="article-item__footer m-top10">
<text class="article-views f-24 col-8">{{ item.show_views }}次浏览</text>
</view>
</block>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import * as ArticleApi from '@/api/article'
import * as CategoryApi from '@/api/article/category'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
const pageSize = 15
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
// 选项卡列表
tabList: [],
// 当前选项
curTab: 0,
// 文章列表
articleList: getEmptyPaginateObj(),
// 上拉加载配置
upOption: {
// 首次自动执行
auto: true,
// 每页数据的数量; 默认10
page: { size: pageSize },
// 数量要大于3条才显示无更多数据
noMoreSize: 3,
}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const app = this
// 获取文章分类数据
app.getCategoryList(options.categoryId)
},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
// 设置列表数据
app.getArticleList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// 获取文章分类数据
getCategoryList(categoryId) {
CategoryApi.list().then(result => {
this.setTabList(result.data.list, categoryId)
})
},
// 设置选项卡数据
setTabList(categoryList, categoryId) {
const app = this
app.tabList = [{ value: 0, name: '全部' }]
categoryList.forEach(item => {
app.tabList.push({ value: item.category_id, name: item.name })
})
if (categoryId > 0) {
const findIndex = app.tabList.findIndex(item => item.value == categoryId)
app.curTab = findIndex > -1 ? findIndex : 0
}
},
/**
* 获取文章列表
* @param {Number} pageNo 页码
*/
getArticleList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
ArticleApi.list({ categoryId: app.getTabValue(), page: pageNo }, { load: false })
.then(result => {
// 合并新数据
const newList = result.data.list
app.articleList.data = getMoreListData(newList, app.articleList, pageNo)
resolve(newList)
})
.catch(reject)
})
},
// 切换标签项
onChangeTab(index) {
// 设置当前选中的标签
this.curTab = index
// 刷新订单列表
this.onRefreshList()
},
// 获取当前标签项的值
getTabValue() {
const app = this
return app.tabList.length ? app.tabList[app.curTab].value : 0
},
// 刷新列表数据
onRefreshList() {
this.articleList = getEmptyPaginateObj()
setTimeout(() => this.mescroll.resetUpScroll(), 120)
},
// 跳转文章详情页
onTargetDetail(articleId) {
this.$navTo('pages/article/detail', { articleId })
}
},
/**
* 分享当前页面
*/
onShareAppMessage() {
return {
title: '文章首页',
path: "/pages/article/index?" + this.$getShareUrlParams()
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
return {
title: '文章首页',
path: "/pages/article/index?" + this.$getShareUrlParams()
}
}
}
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
}
// 文章列表
.article-list {
padding-top: 20rpx;
line-height: 1;
background: #f7f7f7;
}
.article-item {
margin-bottom: 20rpx;
padding: 30rpx;
background: #fff;
&:last-child {
margin-bottom: 0;
}
.article-item__title {
max-height: 74rpx;
font-size: 28rpx;
line-height: 38rpx;
color: #333;
}
.article-item__image .image {
display: block;
}
}
// 小图模式
.show-type__10 {
display: flex;
.article-item__left {
padding-right: 20rpx;
}
.article-item__title {
// min-height: 72rpx;
}
.article-item__image .image {
width: 240rpx;
}
}
// 大图模式
.show-type__20 .article-item__image .image {
width: 100%;
}
</style>
+167
View File
@@ -0,0 +1,167 @@
<template>
<goods-sku-popup :value="value" @input="onChangeValue" border-radius="20" :localdata="goodsInfo" :mode="skuMode" :maskCloseAble="true"
:priceColor="appTheme.mainBg" :buyNowBackgroundColor="appTheme.mainBg" :addCartColor="appTheme.viceText" :addCartBackgroundColor="appTheme.viceBg"
:activedStyle="{ color: appTheme.mainBg, borderColor: appTheme.mainBg, backgroundColor: activedBtnBackgroundColor }"
@open="openSkuPopup" @close="closeSkuPopup" @buy-now="buyNow" buyNowText="立即砍价" :maxBuyNum="1" />
</template>
<script>
import { hex2rgba } from '@/utils/color'
import * as TaskApi from '@/api/bargain/task'
import GoodsSkuPopup from '@/components/goods-sku-popup'
export default {
components: {
GoodsSkuPopup
},
model: {
prop: 'value',
event: 'input'
},
props: {
// true 组件显示 false 组件隐藏
value: {
Type: Boolean,
default: false
},
// 模式 1:都显示 2:只显示购物车 3:只显示立即购买
skuMode: {
type: Number,
default: 1
},
// 砍价活动详情
active: {
type: Object,
default: {}
},
// 商品详情信息
goods: {
type: Object,
default: {}
}
},
data() {
return {
goodsInfo: {}
}
},
computed: {
// 规格按钮选中时的背景色
activedBtnBackgroundColor() {
return hex2rgba(this.appTheme.mainBg, 0.1)
}
},
created() {
const app = this
const { goods } = app
app.goodsInfo = {
_id: goods.goods_id,
name: goods.goods_name,
goods_thumb: goods.goods_image,
sku_list: app.getSkuList(),
spec_list: app.getSpecList()
}
},
methods: {
// 监听组件显示隐藏
onChangeValue(val) {
this.$emit('input', val)
},
// 整理商品SKU列表
getSkuList() {
const app = this
const { goods: { goods_name, goods_image, skuList } } = app
const skuData = []
skuList.forEach(item => {
skuData.push({
_id: item.id,
goods_sku_id: item.goods_sku_id,
goods_id: item.goods_id,
goods_name: goods_name,
image: item.image_url ? item.image_url : goods_image,
price: item.goods_price * 100,
stock: item.stock_num,
spec_value_ids: item.spec_value_ids,
sku_name_arr: app.getSkuNameArr(item.spec_value_ids)
})
})
return skuData
},
// 获取sku记录的规格值列表
getSkuNameArr(specValueIds) {
const app = this
const defaultData = ['默认']
const skuNameArr = []
if (specValueIds) {
specValueIds.forEach((valueId, groupIndex) => {
const specValueName = app.getSpecValueName(valueId, groupIndex)
skuNameArr.push(specValueName)
})
}
return skuNameArr.length ? skuNameArr : defaultData
},
// 获取指定的规格值名称
getSpecValueName(valueId, groupIndex) {
const app = this
const { goods: { specList } } = app
const res = specList[groupIndex].valueList.find(specValue => {
return specValue.spec_value_id == valueId
})
return res.spec_value
},
// 整理规格数据
getSpecList() {
const { goods: { specList } } = this
const defaultData = [{ name: '默认', list: [{ name: '默认' }] }]
const specData = []
specList.forEach(group => {
const children = []
group.valueList.forEach(specValue => {
children.push({ name: specValue.spec_value })
})
specData.push({
name: group.spec_name,
list: children
})
})
return specData.length ? specData : defaultData
},
// sku组件 开始-----------------------------------------------------------
openSkuPopup() {
// console.log("监听 - 打开sku组件")
},
closeSkuPopup() {
// console.log("监听 - 关闭sku组件")
},
// 立即购买
buyNow(selectShop) {
const app = this
TaskApi.partake({
activeId: app.active.active_id,
goodsSkuId: selectShop.goods_sku_id
})
.then(result => {
// 跳转到砍价任务详情页
const taskId = result.data.taskId
this.$navTo('pages/bargain/task', { taskId })
})
// 隐藏当前弹窗
this.onChangeValue(false)
}
}
}
</script>
<style lang="scss" scoped>
</style>
+413
View File
@@ -0,0 +1,413 @@
<template>
<view v-show="!isLoading" class="container" :style="appThemeStyle">
<!-- 商品图片轮播 -->
<SlideImage v-if="!isLoading" :video="goods.video" :videoCover="goods.videoCover" :images="goods.goods_images" />
<!-- 商品信息 -->
<view v-if="!isLoading" class="goods-info m-top20">
<!-- 价格销量 -->
<view class="info-item info-item__top dis-flex flex-x-between flex-y-end">
<view class="block-left dis-flex flex-y-center">
<view class="active-tag">
<text>限时砍价</text>
</view>
<!-- 砍价底价 -->
<text class="floor-price__samll"></text>
<text class="floor-price">{{ active.floor_price }}</text>
<!-- 商品原价 -->
<text class="original-price">{{ goods.goods_price_min }}</text>
</view>
<view class="block-right dis-flex">
<!-- 销量 -->
<view class="goods-sales">
<text>已砍成{{ active.active_sales }}</text>
</view>
</view>
</view>
<!-- 标题分享 -->
<view class="info-item info-item__name dis-flex flex-y-center">
<view class="goods-name flex-box">
<text class="twoline-hide">{{ goods.goods_name }}</text>
</view>
<view class="goods-share__line"></view>
<view class="goods-share">
<button class="share-btn dis-flex flex-dir-column" @click="onShowShareSheet()">
<text class="share__icon iconfont icon-fenxiang"></text>
<text class="f-24">分享</text>
</button>
</view>
</view>
<!-- 商品卖点 -->
<view v-if="goods.selling_point" class="info-item info-item_selling-point">
<text>{{ goods.selling_point }}</text>
</view>
<!-- 活动倒计时 -->
<view v-if="active.is_end == false" class="info-item info-item_status info-item_countdown dis-flex flex-y-center">
<text class="countdown-icon iconfont icon-naozhong"></text>
<text>距离活动结束</text>
<text class="m-r-10">还剩</text>
<count-down :date="active.end_time" separator="zh" theme="text" />
</view>
<!-- 活动已结束 -->
<view v-if="active.is_end == true" class="info-item info-item_status info-item_end">
<text class="countdown-icon iconfont icon-naozhong"></text>
<text>砍价活动已结束下次记得早点来哦~</text>
</view>
</view>
<!-- 砍价玩法 -->
<view class="bargain-rules m-top20 b-f" @click="handleShowRules()">
<view class="item-title dis-flex">
<view class="block-left flex-box">
<text>砍价玩法</text>
</view>
<view class="block-right">
<text class="show-more col-9">查看规则</text>
</view>
</view>
<!-- 砍价步骤 -->
<view class="rule-simple dis-flex flex-x-around">
<view class="simple-item dis-flex flex-dir-column flex-y-center">
<view class="i-number dis-flex flex-x-center flex-y-center">
<text class="f-30">1</text>
</view>
<view class="i-text f-28">点击砍价</view>
</view>
<view class="simple-item dis-flex flex-dir-column flex-y-center">
<view class="i-number dis-flex flex-x-center flex-y-center">
<text class="f-30">2</text>
</view>
<view class="i-text f-28">找人帮砍</view>
</view>
<view class="simple-item dis-flex flex-dir-column flex-y-center">
<view class="i-number dis-flex flex-x-center flex-y-center">
<text class="f-30">3</text>
</view>
<view class="i-text f-28">砍到最低</view>
</view>
<view class="simple-item dis-flex flex-dir-column flex-y-center">
<view class="i-number dis-flex flex-x-center flex-y-center">
<text class="f-30">4</text>
</view>
<view class="i-text f-28">优惠购买</view>
</view>
</view>
</view>
<!-- 选择商品规格 -->
<view v-if="goods.spec_type == 20" class="goods-choice m-top20 b-f" @click="onShowSkuPopup()">
<view class="spec-list">
<view class="flex-box">
<text class="col-8">选择</text>
<text class="spec-name" v-for="(item, index) in goods.specList" :key="index">{{ item.spec_name }}</text>
</view>
<view class="f-26 col-9 t-r">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
</view>
<!-- 商品服务 -->
<!-- <Service v-if="!isLoading" :goods-id="goodsId" /> -->
<!-- 商品SKU弹窗 -->
<SkuPopup v-if="!isLoading" v-model="showSkuPopup" :skuMode="skuMode" :active="active" :goods="goods" />
<!-- 商品评价 -->
<Comment v-if="!isLoading" :goods-id="goodsId" :limit="2" />
<!-- 商品描述 -->
<view v-if="!isLoading" class="goods-content m-top20">
<view class="item-title b-f">
<text>商品描述</text>
</view>
<block v-if="goods.content != ''">
<view class="goods-content__detail b-f">
<mp-html :content="goods.content" />
</view>
</block>
<empty v-else tips="亲,暂无商品描述" />
</view>
<!-- 底部选项卡 -->
<view class="footer-fixed">
<view class="footer-container">
<!-- 导航图标 -->
<view class="foo-item-fast">
<!-- 首页 -->
<view class="fast-item fast-item--home" @click="onTargetHome">
<view class="fast-icon">
<text class="iconfont icon-shouye"></text>
</view>
<view class="fast-text">
<text>首页</text>
</view>
</view>
<!-- 客服 -->
<customer-btn v-if="isShowCustomerBtn">
<view class="fast-item">
<view class="fast-icon">
<text class="iconfont icon-kefu1"></text>
</view>
<view class="fast-text">
<text>客服</text>
</view>
</view>
</customer-btn>
<!-- 购物车 (客服按钮不显示时) -->
<view v-if="!isShowCustomerBtn" class="fast-item fast-item--cart" @click="onTargetCart">
<view v-if="cartTotal > 0" class="fast-badge fast-badge--fixed">{{ cartTotal > 99 ? '99+' : cartTotal }}
</view>
<view class="fast-icon">
<text class="iconfont icon-gouwuche"></text>
</view>
<view class="fast-text">
<text>购物车</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="foo-item-btn">
<view class="btn-wrapper">
<view v-if="active.is_start && !active.is_end" class="btn-item btn--main" @click="handleMainBtn(3)">
<text>{{ isPartake? '继续砍价' : '立即砍价' }}</text>
</view>
<button v-else class="btn-item btn--gray">
<text>{{ active.is_end ? '活动已结束' : '活动未开启' }}</text>
</button>
</view>
</view>
</view>
</view>
<!-- 分享菜单 -->
<share-sheet v-model="showShareSheet" :shareTitle="goods.goods_name" :shareImageUrl="goods.goods_image" :posterApiCall="posterApiCall" :posterApiParam="{ activeId }" />
<!-- 砍价规则弹窗 -->
<u-modal v-if="!isLoading" v-model="showRules" title="砍价规则">
<scroll-view style="height: 610rpx;" :scroll-y="true">
<view class="pops-content">
<text>{{ setting.rulesDesc }}</text>
</view>
</scroll-view>
</u-modal>
</view>
</template>
<script>
import { getSceneData } from '@/core/app'
import ShareSheet from '@/components/share-sheet'
import CustomerBtn from '@/components/customer-btn'
import SkuPopup from './components/SkuPopup'
import SlideImage from '../../goods/components/SlideImage'
import Comment from '../../goods/components/Comment'
// import Service from '../../goods/components/Service'
import CountDown from '@/components/countdown'
import * as GoodsApi from '@/api/goods'
import * as CartApi from '@/api/cart'
import * as ActiveApi from '@/api/bargain/active'
import SettingModel from '@/common/model/Setting'
export default {
components: {
ShareSheet,
CustomerBtn,
// Shortcut,
SlideImage,
SkuPopup,
Comment,
// Service,
CountDown
},
data() {
return {
// 正在加载
isLoading: true,
// 显示/隐藏SKU弹窗
showSkuPopup: false,
// 模式 1:都显示 2:只显示购物车 3:只显示立即购买
skuMode: 3,
// 显示/隐藏分享菜单
showShareSheet: false,
// 显示砍价规则
showRules: false,
// 获取商品海报图api方法
posterApiCall: ActiveApi.poster,
// 当前活动ID
activeId: null,
// 当前商品ID
goodsId: null,
// 活动详情
active: {},
// 商品详情
goods: {},
// 砍价设置
setting: null,
// 标记当前用户是否正在参与
isPartake: null,
// 砍价任务ID (当前用户参与的话才有值)
taskId: null,
// 购物车总数量
cartTotal: 0,
// 是否显示在线客服按钮
isShowCustomerBtn: false
}
},
/**
* 生命周期函数--监听页面加载
*/
async onLoad(options) {
// 记录query参数
this.onRecordQuery(options)
// 加载页面数据
this.onRefreshPage()
// 是否显示在线客服按钮
this.isShowCustomerBtn = await SettingModel.isShowCustomerBtn()
},
methods: {
// 记录query参数
onRecordQuery(query) {
const scene = getSceneData(query)
this.activeId = query.activeId ? parseInt(query.activeId) : parseInt(scene.aid)
this.goodsId = query.goodsId ? parseInt(query.goodsId) : parseInt(scene.gid)
},
// 刷新页面数据
onRefreshPage() {
const app = this
app.isLoading = true
Promise.all([app.getActiveDetail(), app.getGoodsDetail(), app.getCartTotal()])
.then(() => app.isLoading = false)
},
// 获取砍价活动详情
getActiveDetail() {
const app = this
return new Promise((resolve, reject) => {
ActiveApi.detail(app.activeId)
.then(result => {
app.active = result.data.active
app.setting = result.data.setting
app.isPartake = result.data.isPartake
app.taskId = result.data.taskId
resolve(result)
})
.catch(reject)
})
},
// 获取商品信息
getGoodsDetail() {
const app = this
return new Promise((resolve, reject) => {
GoodsApi.detail(app.goodsId, false)
.then(result => {
app.goods = result.data.detail
resolve(result)
})
.catch(reject)
})
},
// 获取购物车总数量
getCartTotal() {
const app = this
return new Promise((resolve, reject) => {
CartApi.total()
.then(result => {
app.cartTotal = result.data.cartTotal
resolve(result)
})
.catch(reject)
})
},
/**
* 显示/隐藏SKU弹窗
*/
onShowSkuPopup() {
this.showSkuPopup = !this.showSkuPopup
},
// 显示隐藏分享菜单
onShowShareSheet() {
this.showShareSheet = !this.showShareSheet
},
// 显示砍价规则
handleShowRules() {
this.showRules = true
},
// 跳转到首页
onTargetHome(e) {
this.$navTo('pages/index/index')
},
// 跳转到购物车页
onTargetCart() {
this.$navTo('pages/cart/index')
},
// 点击主按钮
handleMainBtn() {
const app = this
// 发起新的砍价任务
if (!app.isPartake) {
return app.onShowSkuPopup()
}
// 已发起砍价则跳转到砍价任务详情页
app.$navTo('pages/bargain/task', { taskId: app.taskId })
},
},
/**
* 分享当前页面
*/
onShareAppMessage() {
// 构建页面参数
const app = this
const params = app.$getShareUrlParams({
activeId: app.activeId,
goodsId: app.goodsId
})
return {
title: app.goods.goods_name,
path: `/pages/bargain/goods/index?${params}`
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
// 构建页面参数
const app = this
const params = app.$getShareUrlParams({
activeId: app.activeId,
goodsId: app.goodsId
})
return {
title: app.goods.goods_name,
path: `/pages/bargain/goods/index?${params}`
}
},
}
</script>
<style>
page {
background: #fafafa;
}
</style>
<style lang="scss" scoped>
@import "./style.scss";
</style>
+298
View File
@@ -0,0 +1,298 @@
.container {
// 设置ios刘海屏底部横线安全区域
// 110 - 18 + 4
padding-bottom: calc(constant(safe-area-inset-bottom) + 98rpx + 6rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 98rpx + 6rpx);
}
/* 商品信息 */
.goods-info {
background: #fff;
padding: 25rpx 30rpx;
}
.info-item__top {
min-height: 40rpx;
margin-bottom: 20rpx;
}
.info-item__top .active-tag {
color: #fff;
background: $main-bg;
padding: 4rpx 10rpx;
border-radius: 15rpx;
font-size: 26rpx;
text-align: center;
margin-right: 15rpx;
}
.floor-price__samll {
font-size: 26rpx;
line-height: 1;
color: $main-bg;
margin-bottom: -10rpx;
}
/* 商品价 */
.floor-price {
color: $main-bg;
margin-right: 15rpx;
font-size: 42rpx;
line-height: 1;
}
.original-price {
font-size: 26rpx;
line-height: 1;
text-decoration: line-through;
color: #959595;
margin-bottom: -6rpx;
}
.goods-sales {
font-size: 24rpx;
color: #959595;
}
.info-item__name .goods-name {
font-size: 30rpx;
}
/* 商品分享 */
.goods-share__line {
border-left: 1rpx solid #f4f4f4;
height: 60rpx;
margin: 0 30rpx;
}
.goods-share .share-btn {
line-height: normal;
padding: 0;
background: none;
border-radius: 0;
box-shadow: none;
font-size: 8pt;
border: none;
color: #191919;
}
.goods-share .share-btn::after {
border: none;
}
.goods-share .share__icon {
font-size: 40rpx;
margin-bottom: 5rpx;
}
/* 商品卖点 */
.info-item_selling-point {
margin-top: 8rpx;
font-size: 24rpx;
color: #808080;
}
// 选择商品规格
.goods-choice {
padding: 26rpx 30rpx;
font-size: 28rpx;
.spec-list {
display: flex;
align-items: center;
.spec-name {
margin-right: 10rpx;
}
}
}
/* 商品详情 */
.goods-content .item-title {
padding: 26rpx 30rpx;
font-size: 28rpx;
}
/* 底部操作栏 */
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
display: flex;
z-index: 11;
box-shadow: 0 -4rpx 40rpx 0 rgba(151, 151, 151, 0.24);
background: #fff;
// 设置ios刘海屏底部横线安全区域
padding-bottom: env(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.footer-container {
width: 100%;
display: flex;
height: 98rpx;
}
// 快捷菜单
.foo-item-fast {
box-sizing: border-box;
width: 256rpx;
line-height: 1;
display: flex;
justify-content: center;
align-items: center;
.fast-item {
position: relative;
padding: 4rpx 10rpx;
line-height: 1;
// text-align: center;
.fast-icon {
margin-bottom: 6rpx;
}
&--home {
margin-right: 30rpx;
}
&--cart {
.fast-icon { padding-left: 3px; }
}
// 角标
.fast-badge {
display: inline-block;
box-sizing: border-box;
min-width: 16px;
padding: 0 3px;
color: #fff;
font-weight: 500;
font-size: 12px;
font-family: -apple-system-font, Helvetica Neue, Arial, sans-serif;
line-height: 1.2;
text-align: center;
background-color: #ee0a24;
border: 1px solid #fff;
border-radius: 999px;
}
.fast-badge--fixed {
position: absolute;
top: 0;
right: 0;
transform-origin: 100%
}
.fast-icon {
font-size: 46rpx;
}
.fast-text {
font-size: 24rpx;
}
}
}
// 操作按钮
.foo-item-btn {
flex: 1;
.btn-wrapper {
height: 100%;
display: flex;
align-items: center;
}
// 立即砍价
.btn-item {
flex: 1;
height: 100%;
font-size: 30rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 0;
&.btn--main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
}
&.btn--gray {
background-color: #ccc;
color: #fff;
}
}
}
// 活动状态
.info-item_status {
margin-top: 20rpx;
padding: 15rpx 20rpx;
font-size: 24rpx;
border-radius: 6rpx;
}
.info-item_status .countdown-icon {
font-size: 28rpx;
margin-right: 15rpx;
}
// 活动倒计时
.info-item_countdown {
background: #f0f9ff;
color: #8f8f8f;
}
.info-item_countdown .countdown-icon {
color: #1397d8;
}
// 活动已结束
.info-item_end {
background: #ccc;
color: #fff;
}
// 砍价玩法
.bargain-rules {
padding: 20rpx 0;
font-size: 29rpx;
.item-title {
padding: 0 30rpx;
}
.rule-simple {
margin-top: 35rpx;
color: #737373;
}
.i-number {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-bottom: 15rpx;
border: 1rpx dashed #c0c0c0;
}
}
// 砍价规则(弹窗)
.pops-content {
padding: 30rpx 48rpx;
font-size: 28rpx;
line-height: 44rpx;
text-align: left;
color: #606266;
min-height: 320rpx;
max-height: 640rpx;
box-sizing: border-box;
}
+479
View File
@@ -0,0 +1,479 @@
<template>
<view class="container" :style="appThemeStyle">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption" @up="upCallback">
<!-- 砍价会场 -->
<view v-if="curTab == 0" class="bargain-hall">
<!-- 商品列表 -->
<view class="goods-item" v-for="(item, index) in activeList.data" :key="index">
<view class="goods-item--container dis-flex" @click="onTargetActive(item)">
<!-- 商品图片 -->
<view class="goods-image">
<image class="image" :src="item.goods.goods_image"></image>
</view>
<view class="goods-info">
<!-- 商品名称 -->
<view class="goods-name">
<text class="twoline-hide">{{ item.goods.goods_name }}</text>
</view>
<!-- 参与的用户头像 -->
<view v-if="item.helpsCount > 0" class="peoples dis-flex">
<view class="user-list dis-flex">
<view class="user-item-avatar" v-for="(help, hIdx) in item.helpList" :key="hIdx">
<avatar-image :url="help.user.avatar_url" :width="36" />
</view>
</view>
<view class="people__text">
<text>{{ item.helpsCount }}人正在砍价</text>
</view>
</view>
<!-- 商品原价 -->
<view class="goods-price">
<text>{{ item.goods.goods_price_min }}</text>
</view>
<!-- 砍价低价 -->
<view class="floor-price">
<text class="small">最低</text>
<text class="big">{{ item.floor_price }}</text>
</view>
<!-- 操作按钮 -->
<view class="opt-touch">
<view class="touch-btn">
<text>立即参加</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 我的砍价 -->
<view v-if="curTab == 1" class="bargain-hall">
<!-- 商品列表 -->
<view class="goods-item" v-for="(item, index) in myList.data" :key="index">
<view class="goods-item--container dis-flex" @click="onTargetTask(item.task_id)">
<!-- 商品图片 -->
<view class="goods-image">
<image class="image" :src="item.goods.goods_image"></image>
</view>
<view class="goods-info">
<!-- 商品名称 -->
<view class="goods-name">
<text class="twoline-hide">{{ item.goods.goods_name }}</text>
</view>
<!-- 砍价进度 -->
<view class="task-rate">
<block v-if="item.status == true">
<text>已砍</text>
<text class="col-m">{{ item.cut_money }}</text>
<text></text>
<text>只差</text>
<text class="col-m">{{ item.surplus_money }}</text>
<text></text>
</block>
<block v-if="item.is_floor">
<text>已砍至最低</text>
<text class="col-m">{{ item.floor_price }}</text>
<text></text>
</block>
</view>
<!-- 任务状态 -->
<view class="task-status dis-flex flex-y-center">
<!-- 倒计时 -->
<view v-if="item.status == true" class="count-down dis-flex flex-y-center">
<text class="m-r-6">剩余</text>
<count-down :date="item.end_time" separator="colon" theme="custom" />
</view>
<view v-if="item.status == false" class="task-status__text">
<text class="col-m">{{ item.is_buy ? '砍价成功' : '已结束' }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view v-if="item.status == true" class="opt-touch">
<view class="touch-btn">
<text>继续砍价</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 底部选项卡 -->
<view class="footer-fixed">
<view class="footer-container">
<!-- 砍价会场 -->
<view class="tabbar-item flex-box" :class="{ active: curTab == 0 }">
<view class="tabbar-item-content dis-flex flex-x-center flex-y-center" @click="onChangeTab(0)">
<view class="tabbar-item-icon">
<text class="iconfont icon-shangcheng"></text>
</view>
<view class="tabbar-item-name">
<text>砍价会场</text>
</view>
</view>
</view>
<!-- 分割线 -->
<view class="tabbar-item__divider">
<view class="divider-line"></view>
</view>
<!-- 我的砍价 -->
<view class="tabbar-item flex-box" :class="{ active: curTab == 1 }">
<view class="tabbar-item-content dis-flex flex-x-center flex-y-center" @click="onChangeTab(1)">
<view class="tabbar-item-icon">
<text class="iconfont icon-sy-yh"></text>
</view>
<view class="tabbar-item-name">
<text>我的砍价</text>
</view>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import AvatarImage from '@/components/avatar-image'
import CountDown from '@/components/countdown'
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import * as ActiveApi from '@/api/bargain/active'
import * as TaskApi from '@/api/bargain/task'
const pageSize = 15
export default {
components: {
MescrollBody,
AvatarImage,
CountDown
},
mixins: [MescrollMixin],
data() {
return {
// 是否正在加载中
isLoading: true,
// 当前tab索引
curTab: 0,
// 砍价会场商品列表
activeList: getEmptyPaginateObj(),
// 我的砍价列表
myList: getEmptyPaginateObj(),
// 上拉加载配置
upOption: {
// 首次自动执行
auto: true,
// 每页数据的数量; 默认10
page: { size: pageSize },
// 数量要大于3条才显示无更多数据
noMoreSize: 3,
}
}
},
watch: {
curTab(val) {
// 设置页面标题
uni.setNavigationBarTitle({ title: val == 0 ? '砍价会场' : '我的砍价' })
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 设置当前tab索引
if (options.tab) {
this.curTab = options.tab
}
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
// 设置列表数据
app.getListData(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// 获取列表数据(根据当前选项卡判断调用的方法)
getListData(pageNo) {
const apiFuc = {
0: this.getActiveList,
1: this.getMyList
}
return apiFuc[this.curTab](pageNo)
},
// 获取砍价活动列表
getActiveList(pageNo) {
const app = this
return new Promise((resolve, reject) => {
ActiveApi.list({ page: pageNo }, { load: false })
.then(result => {
// 合并新数据
const newList = result.data.list
app.activeList.data = getMoreListData(newList, app.activeList, pageNo)
resolve(newList)
})
.catch(reject)
})
},
// 获取我的砍价列表
getMyList(pageNo) {
const app = this
return new Promise((resolve, reject) => {
TaskApi.list({ page: pageNo }, { load: false })
.then(result => {
// 合并新数据
const newList = result.data.list
app.myList.data = getMoreListData(newList, app.myList, pageNo)
resolve(newList)
})
.catch(reject)
})
},
// 切换当前选项卡
onChangeTab(key = 0) {
const app = this
// 记录选项卡索引
app.curTab = key
// 刷新列表数据
app.activeList = getEmptyPaginateObj()
app.myList = getEmptyPaginateObj()
app.mescroll.resetUpScroll()
},
// 跳转到砍价商品详情页
onTargetActive(item) {
this.$navTo('pages/bargain/goods/index', { activeId: item.active_id, goodsId: item.goods_id })
},
// 跳转到砍价任务详情
onTargetTask(taskId) {
this.$navTo('pages/bargain/task', { taskId })
},
},
/**
* 分享当前页面
*/
onShareAppMessage() {
// 构建页面参数
const params = this.$getShareUrlParams()
return {
title: '砍价专区',
path: `/pages/bargain/index?${params}`
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
// 构建页面参数
const params = this.$getShareUrlParams()
return {
title: '砍价专区',
path: `/pages/bargain/index?${params}`
}
},
}
</script>
<style lang="scss" scoped>
.container {
// 设置ios刘海屏底部横线安全区域
// 110 - 18 + 4
padding-bottom: calc(constant(safe-area-inset-bottom) + 96rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 96rpx);
}
.bargain-hall {
padding-top: 20rpx;
}
// 砍价商品
.goods-item {
margin-bottom: 20rpx;
background: #fff;
padding: 20rpx 16rpx;
&:last-child {
margin-bottom: 0;
}
.goods-image {
.image {
display: block;
width: 220rpx;
height: 220rpx;
}
}
.goods-info {
width: 498rpx;
padding-top: 8rpx;
margin-left: 15rpx;
position: relative;
.goods-name {
font-size: 28rpx;
min-height: 60rpx;
}
// 正在参与的用户
.peoples {
margin-top: 15rpx;
.user-list {
margin-right: 10rpx;
.user-item-avatar {
margin-left: -8rpx;
&:first-child {
margin-left: 0;
}
}
}
.people__text {
font-size: 24rpx;
color: #818181;
}
}
// 商品原价
.goods-price {
margin-top: 15rpx;
color: #818181;
font-size: 25rpx;
text-decoration: line-through;
}
// 砍价底价
.floor-price {
color: $main-bg;
.small {
font-size: 24rpx;
}
.big {
font-size: 32rpx;
}
}
}
// 砍价进度
.task-rate {
font-size: 25rpx;
color: #a4a4a4;
margin-top: 15rpx;
}
}
/* 我的砍价 */
// 砍价状态
.task-status {
margin-top: 32rpx;
height: 58rpx;
}
.task-status__text {
font-size: 26rpx;
}
// 倒计时
.count-down {
font-size: 25rpx;
}
// 立即参加按钮
.opt-touch {
position: absolute;
bottom: 0;
right: 10rpx;
}
.touch-btn {
color: #fff;
font-size: 28rpx;
background: #d3a975;
border-radius: 30rpx;
padding: 10rpx 28rpx;
}
// 底部选项卡
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
box-shadow: 0 -4rpx 40rpx 0 rgba(151, 151, 151, 0.24);
background: #fff;
// 设置ios刘海屏底部横线安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.footer-container {
display: flex;
align-items: center;
height: 96rpx;
}
.tabbar-item {
font-size: 30rpx;
// height: 42rpx;
&.active {
.tabbar-item-content {
color: $main-bg;
}
}
.tabbar-item-icon {
margin-right: 15rpx;
}
}
// 分割线
.tabbar-item__divider {
padding: 22rpx 0;
}
.divider-line {
width: 1rpx;
height: 62rpx;
background: #ddd;
}
</style>
+838
View File
@@ -0,0 +1,838 @@
<template>
<view v-if="!isLoading" class="container">
<!-- 顶部操作栏 -->
<view class="header dis-flex flex-x-between">
<view class="item-touch" @click="$navTo('pages/index/index')">
<text>返回首页</text>
</view>
<view class="item-touch" @click="handleShowRules()">
<text>玩法详情</text>
</view>
</view>
<view class="content">
<!-- 砍价信息 -->
<view class="infos-wrap">
<view class="infos-top">
<view class="infos-img">
<avatar-image :url="task.user.avatar_url" :width="104" />
</view>
<view class="infos-name">
<text>{{ task.user.nick_name }}</text>
</view>
</view>
<view class="infos-mask">
<view class="infos-prompt" v-if="active.prompt_words">
<text>{{ active.prompt_words }}</text>
</view>
<!-- 商品信息 -->
<view class="infos-item" @click="$navTo('pages/bargain/goods/index', { activeId, goodsId: goods.goods_id })">
<view class="infos-item-img">
<image class="image" :src="goodsSkuInfo.goods_image ? goodsSkuInfo.goods_image : goods.goods_image"></image>
</view>
<view class="infos-item-info">
<view class="infos-item-name">
<text class="twoline-hide">{{ goods.goods_name }}</text>
</view>
<view class="infos-item-stock">
<view class="stock-widget">
<text>仅剩</text>
<text class="stock-num">{{ goodsSkuInfo.stock_num }}</text>
<text></text>
</view>
</view>
<view class="infos-item-price dis-flex flex-y-end">
<text class="price1 col-m">底价¥</text>
<text class="price2 col-m">{{ task.floor_price }}</text>
<text class="price3">¥{{ task.goods_price }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 分割线 -->
<view class="connect">
<view class="connect-ring bgf-ring--left">
<text class="line"></text>
</view>
<view class="connect-ring bgf-ring--right">
<text class="line"></text>
</view>
</view>
<!-- 砍价进度 -->
<view class="bargain-wrap">
<!-- 已砍数目 -->
<view class="bargain-info">
<view v-if="task.status" class="bargain-ing">
<block v-if="!task.is_floor">
<text>已砍</text>
<text class="focal col-m">{{ task.cut_money }}</text>
<text>还差</text>
<text class="focal col-m">{{ task.surplus_money }}</text>
<text></text>
</block>
<block v-else>
<text>已砍至最低</text>
<text class="focal col-m">{{ task.floor_price }}</text>
<text>砍价成功</text>
</block>
</view>
<view v-else class="bargain-ing">
<text class="col-9">该砍价任务已结束</text>
</view>
</view>
<!-- 砍价进度条 -->
<view class="bgn__process m-top30">
<view class="bgn__process-bottom">
<view class="bgn__process-process process--ani" :style="{ width: `${task.bargain_rate}%` }"></view>
</view>
</view>
<!-- 操作按钮 -->
<view class="btn-container m-top30 dis-flex flex-x-center">
<!-- 立即购买 -->
<view v-if="showBuyBtn" class="btn-item btn-item__buy" :class="{ complete: task.is_floor }" @click="handleBuyNow()">
<text>立即购买</text>
</view>
<!-- 分享给朋友 -->
<button v-if="showShareBtn" open-type="share" class="btn-normal" @click="handleShareBtn()">
<view class="btn-item btn-item__main">
<text>邀请好友砍价</text>
</view>
</button>
<!-- 砍一刀操作 -->
<view v-if="showCatBtn" class="btn-item btn-item__main btn-item-long" @click="handleHelpCut()">
<text>帮TA砍一刀</text>
</view>
<!-- 查看其他砍价活动 -->
<view v-if="showOtherBtn" class="btn-item btn-item__main btn-item-long" @click="$navTo('pages/bargain/index')">
<text>查看其他砍价活动</text>
</view>
</view>
<!-- 到期时间 -->
<view class="bargain-p" v-if="task.status">
<view class="bargain-people dis-flex flex-x-center flex-y-center">
<text>活动还剩</text>
<count-down :date="active.end_time" separator="zh" theme="text" />
<text>结束快来砍价吧~</text>
</view>
</view>
</view>
<!-- 好友助力榜 -->
<view class="records-container" v-if="helpList.length">
<view class="records">
<view class="records-back"></view>
<view class="records-content">
<view class="records-h2">
<text>好友助力榜</text>
</view>
<view class="friend-help">
<view class="records-item" v-for="(help, idx) in helpList" :key="idx">
<view class="records-left">
<avatar-image :url="help.user.avatar_url" :width="70" />
<text class="nick-name">{{ help.user.nick_name }}</text>
</view>
<view class="records-right">
<text class="bold m-r-6">帮砍了</text>
<text class="red">¥{{ help.cut_money }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 砍价规则弹窗 -->
<u-modal v-if="!isLoading" v-model="showRules" title="砍价规则">
<scroll-view style="height: 610rpx;" :scroll-y="true">
<view class="pops-content">
<text>{{ setting.rulesDesc }}</text>
</view>
</scroll-view>
</u-modal>
</view>
</template>
<script>
import { getCurrentPage, buildUrL } from '@/core/app'
import AvatarImage from '@/components/avatar-image'
import CountDown from '@/components/countdown'
import SettingModel from '@/common/model/Setting'
import * as GoodsApi from '@/api/goods'
import * as TaskApi from '@/api/bargain/task'
import * as ActiveApi from '@/api/bargain/active'
export default {
components: {
AvatarImage,
CountDown
},
data() {
return {
// 是否正在加载中
isLoading: true,
taskId: undefined, // 砍价任务ID
activeId: undefined, // 砍价活动ID
task: {}, // 砍价任务详情
active: {}, // 活动详情
goods: {}, // 商品详情
goodsSkuInfo: {}, // 商品SKU信息
helpList: [], // 好友助力榜
isCreater: false, // 是否为当前砍价任务的发起人
isCut: false, // 当前是否已砍
setting: {}, // 砍价规则
showRules: false, // 显示砍价规则
disabled: false, // 按钮禁用状态
showBuyBtn: false, // 立即购买
showShareBtn: false, // 邀请好友砍价
showCatBtn: false, // 帮TA砍一刀
showOtherBtn: false, // 查看其他砍价活动
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.taskId = options.taskId
// 刷新页面数据
this.onRefreshPage()
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
methods: {
// 刷新页面数据
onRefreshPage() {
const app = this
app.isLoading = true
// 获取砍价任务详情
app.getTaskDetail()
.then(result => {
Promise.all([
app.getActiveDetail(),
app.getGoodsBasic(),
app.getGoodsSku(),
app.getHelpList()
])
.then(() => app.initShowBtn())
.finally(() => app.isLoading = false)
})
},
// 获取砍价任务详情
getTaskDetail() {
const app = this
return new Promise((resolve, reject) => {
TaskApi.detail(app.taskId)
.then(result => {
app.task = result.data.taskInfo
app.activeId = app.task.active_id
app.isCreater = result.data.isCreater
app.isCut = result.data.isCut
app.setting = result.data.setting
resolve(result)
})
.catch(reject)
})
},
// 获取砍价活动详情
getActiveDetail() {
const app = this
return new Promise((resolve, reject) => {
ActiveApi.detail(app.activeId)
.then(result => {
app.active = result.data.active
resolve(result)
})
.catch(reject)
})
},
// 获取商品信息
getGoodsBasic() {
const app = this
const goodsId = app.task.goods_id
return new Promise((resolve, reject) => {
GoodsApi.basic(goodsId, false)
.then(result => {
app.goods = result.data.detail
resolve(result)
})
.catch(reject)
})
},
// 获取商品SKU信息
getGoodsSku() {
const app = this
const goodsId = app.task.goods_id
const goodsSkuId = app.task.goods_sku_id
return new Promise((resolve, reject) => {
GoodsApi.skuInfo(goodsId, goodsSkuId)
.then(result => {
app.goodsSkuInfo = result.data.skuInfo
resolve(result)
})
.catch(reject)
})
},
// 获取砍价活动详情
getHelpList() {
const app = this
return new Promise((resolve, reject) => {
TaskApi.helpList(app.taskId)
.then(result => {
app.helpList = result.data.list
resolve(result)
})
.catch(reject)
})
},
// 初始化:显示操作按钮
initShowBtn() {
const app = this
// 立即购买
const showBuyBtn = app.isCreater && !app.task.is_buy && app.task.status && (!app.active.is_floor_buy ||
app.task.is_floor)
// 帮砍一刀
const showCatBtn = !app.isCreater && !app.isCut && !app.task.is_floor && app.task.status
// 邀请好友砍价
const showShareBtn = !showCatBtn && !app.task.is_floor && app.task.status
// 查看其他砍价活动
const showOtherBtn = !showBuyBtn && !showShareBtn && !showCatBtn
app.showBuyBtn = showBuyBtn
app.showCatBtn = showCatBtn
app.showShareBtn = showShareBtn
app.showOtherBtn = showOtherBtn
},
// 显示砍价规则
handleShowRules() {
this.showRules = true
},
// 立即购买
handleBuyNow() {
// 跳转到结算页
const app = this
app.$navTo('pages/checkout/index', {
mode: 'bargain',
taskId: app.taskId
})
},
// 帮砍一刀
handleHelpCut() {
const app = this
app.disabled = true
TaskApi.helpCut(app.taskId)
.then(result => {
app.$toast(result.message)
setTimeout(() => app.onRefreshPage(), 1800)
})
.finally(() => app.disabled = false)
},
// 点击分享按钮
handleShareBtn() {
// #ifndef MP
this.handleCopyLink()
// #endif
},
// 复制当前页面链接
handleCopyLink() {
const app = this
app.getShareUrl().then(shareUrl => {
// 复制到剪贴板
uni.setClipboardData({
data: shareUrl,
success: () => app.$toast('复制链接成功,快去发送给朋友吧'),
fail: err => app.$toast('复制失败')
})
})
},
// 获取分享链接 (H5外链)
getShareUrl() {
const { path, query } = getCurrentPage()
return new Promise((resolve, reject) => {
// 获取h5站点地址
SettingModel.h5Url(true)
.then(baseUrl => {
// 生成完整的分享链接
const shareUrl = buildUrL(baseUrl, path, query)
resolve(shareUrl)
})
})
},
},
/**
* 分享当前页面
*/
onShareAppMessage() {
// 构建页面参数
const app = this
const params = app.$getShareUrlParams({ taskId: app.taskId })
return {
title: app.active.share_title,
path: `/pages/bargain/task?${params}`
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
// 构建页面参数
const app = this
const params = app.$getShareUrlParams({ taskId: app.taskId })
return {
title: app.active.share_title,
path: `/pages/bargain/task?${params}`
}
},
}
</script>
<style>
page {
height: 100%;
}
</style>
<style lang="scss" scoped>
.container {
background: url('https://si.geilicdn.com/img-310900000167962321710a026860-unadjust_750_686.png') top no-repeat,
linear-gradient(90deg, #fea044, #f9565d 63%, #e63378);
background-size: 100% auto;
min-height: 100%;
}
/* 头部区域 */
.header {
padding: 30rpx 30rpx;
.item-touch {
color: #fff;
font-size: 24rpx;
padding: 7rpx 20rpx;
background: rgba(0, 0, 0, 0.17);
border-radius: 22rpx;
}
}
/* 内容区域 */
.content {
position: relative;
box-sizing: border-box;
padding: 10rpx 30rpx 60rpx 30rpx;
}
/* 砍价信息 */
.infos-wrap {
background: #fff;
box-shadow: 0 4rpx 40rpx 0 rgba(151, 151, 151, 0.24);
padding: 0 30rpx 40rpx;
border-radius: 16rpx;
}
.infos-top {
position: relative;
top: -42rpx;
margin-bottom: -22rpx;
}
.infos-img {
width: 120rpx;
height: 120rpx;
padding: 8rpx;
background: #fff;
overflow: hidden;
margin: 0 auto;
border-radius: 50%;
}
.infos-name {
margin: 8rpx auto 0;
width: 80%;
font-size: 26rpx;
color: #9a9a9a;
text-align: center;
line-height: 32rpx;
}
.infos-prompt {
text-align: center;
font-size: 30rpx;
color: #222;
line-height: 48rpx;
margin-bottom: 30rpx;
}
.infos-item {
margin-top: 40rpx;
display: flex;
}
.infos-item-img {
flex: none;
width: 180rpx;
height: 180rpx;
.image {
width: 100%;
height: 100%;
}
}
.infos-item-info {
margin-left: 25rpx;
flex: auto;
}
.infos-item-name {
font-size: 28rpx;
color: #404040;
line-height: 40rpx;
height: 80rpx;
}
.infos-item-stock {
.stock-widget {
display: inline-block;
min-width: 100rpx;
padding: 0 20rpx;
background-image: linear-gradient(-90deg, #fe9c3f, #fb6253 99%);
border-radius: 40rpx;
height: 40rpx;
font-size: 24rpx;
color: #fff;
line-height: 40rpx;
margin-top: 6rpx;
.stock-num {
margin: 0 6rpx;
}
}
}
.infos-item-price {
font-size: 0;
margin-top: 8rpx;
.price1 {
font-size: 24rpx;
line-height: 32rpx;
}
.price2 {
margin-left: 4rpx;
font-size: 36rpx;
line-height: 40rpx;
}
.price3 {
margin-left: 10rpx;
font-size: 24rpx;
color: #9a9a9a;
line-height: 32rpx;
text-decoration: line-through;
}
}
/* 分割线 */
.connect {
position: relative;
height: 20rpx;
}
.connect-ring {
position: absolute;
top: -28rpx;
height: 76rpx;
width: 20rpx;
padding: 8rpx 6rpx;
box-sizing: border-box;
&:after,
&:before {
content: "";
position: absolute;
z-index: 6;
left: 0;
height: 20rpx;
width: 20rpx;
border-radius: 20rpx;
}
.line {
z-index: 8;
display: block;
height: 100%;
width: 100%;
background: #fff;
border-radius: 20rpx;
}
&.bgf-ring--left {
left: 20rpx;
&:before {
top: 0;
background: #f4914e;
}
&:after {
bottom: 0;
background: #f4914e;
}
}
&.bgf-ring--right {
right: 20rpx;
&:before {
top: 0;
background: #e03e71;
}
&:after {
bottom: 0;
background: #e03e71;
}
}
}
/* 砍价进度 */
.bargain-wrap {
position: relative;
background: #fff;
padding: 40rpx 30rpx 30rpx;
box-shadow: 0 4rpx 40rpx 0 rgba(144, 52, 52, 0.1);
border-radius: 16rpx;
}
.bargain-info {
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #404040;
line-height: 40rpx;
.focal {
margin: 0 5rpx;
}
}
.bargain-p {
margin-top: 40rpx;
min-height: 32rpx;
}
.bargain-people {
font-size: 24rpx;
color: #9a9a9a;
text-align: center;
line-height: 32rpx;
}
/* 进度条 */
.bgn__process {
position: relative;
padding: 30rpx 0;
}
.bgn__process-bottom {
z-index: 1;
overflow: hidden;
background-image: linear-gradient(0deg, #f0f2f7, #e8ebf3);
}
.bgn__process-bottom,
.bgn__process-process {
position: relative;
height: 30rpx;
border-radius: 30rpx;
}
.bgn__process-process {
background-image: linear-gradient(90deg, #ffc108, #fde586);
background: #ffc108;
&.process--ani {
&:after {
content: "";
position: absolute;
width: 100%;
height: 32rpx;
top: 50%;
left: 0;
margin-top: -16rpx;
}
}
}
/* 操作按钮 */
.btn-container {
.btn-item {
color: #fff;
height: 80rpx;
font-size: 30rpx;
border-radius: 15rpx;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
}
.btn-item__buy {
width: 280rpx;
margin-right: 30rpx;
background-image: linear-gradient(90deg, #fa9e1b, #fe5b1b);
box-shadow: #fe5b1b 0 22rpx 48rpx -22rpx;
&.complete {
width: 360rpx;
animation: btn_anim 0.9s linear infinite;
transform-origin: center;
}
}
.btn-item__main {
width: 320rpx;
background-image: linear-gradient(90deg, #fe316c, #fd584e);
box-shadow: #fd584e 0 22rpx 48rpx -22rpx;
animation: btn_anim 0.9s linear infinite;
transform-origin: center;
}
.btn-item-long {
max-width: 400rpx;
}
/* 按钮动画 */
@keyframes btn_anim {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
40% {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
}
/* 好友助力榜 */
.records-container {
margin-top: 44rpx;
}
.records {
position: relative;
color: #404040;
box-shadow: 0 4rpx 40rpx 0 rgba(0, 0, 0, 0.08);
}
.records-back {
position: absolute;
left: -14rpx;
right: -14rpx;
top: -14rpx;
height: 28rpx;
border-radius: 28rpx;
z-index: 1;
background: #cb272d;
}
.records-content {
position: relative;
z-index: 2;
background: #fff;
padding: 40rpx 30rpx;
}
.records-h2 {
display: flex;
justify-content: space-between;
height: 60rpx;
align-items: center;
font-weight: 700;
font-size: 34rpx;
line-height: 48rpx;
}
.friend-help {
overflow: hidden;
padding: 40rpx 0 20rpx;
transition: max-height 0.6s ease-out;
}
.records-left,
.records-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10rpx;
}
.records-left {
.nick-name {
display: inline-block;
margin-left: 14rpx;
}
}
.records-left .nick-name,
.records-right {
font-size: 28rpx;
line-height: 40rpx;
}
.records-right {
display: flex;
justify-content: center;
align-items: center;
.red {
color: #e53a40;
}
}
// 砍价规则(弹窗)
.pops-content {
padding: 30rpx 48rpx;
font-size: 28rpx;
line-height: 44rpx;
text-align: left;
color: #606266;
min-height: 320rpx;
max-height: 640rpx;
box-sizing: border-box;
}
</style>
+516
View File
@@ -0,0 +1,516 @@
<template>
<view class="container" :style="appThemeStyle">
<!-- 页面顶部 -->
<view v-if="list.length" class="head-info">
<view class="cart-total">
<text></text>
<text class="active">{{ total }}</text>
<text>件商品</text>
</view>
<view class="cart-edit" @click="handleToggleMode">
<view v-if="mode == 'normal'" class="normal">
<text class="icon iconfont icon-bianji"></text>
<text>编辑</text>
</view>
<view v-if="mode == 'edit'" class="edit">
<text>完成</text>
</view>
</view>
</view>
<!-- 购物车商品列表 -->
<view v-if="list.length" class="cart-list">
<view class="cart-item" v-for="(item, index) in list" :key="index">
<label class="item-radio" @click.stop="handleCheckItem(item.id)">
<radio class="radio" :color="appTheme.mainBg" :checked="inArray(item.id, checkedIds)" />
</label>
<view class="goods-image" @click="onTargetGoods(item.goods_id)">
<image class="image" :src="item.goods.goods_image" mode="scaleToFill"></image>
</view>
<view class="item-content">
<view class="goods-title" @click="onTargetGoods(item.goods_id)">
<text class="twoline-hide">{{ item.goods.goods_name }}</text>
</view>
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in item.goods.skuInfo.goods_props" :key="idx">
<text>{{ props.value.name }}</text>
</view>
</view>
<view class="item-foot">
<view class="goods-price">
<text class="unit"></text>
<text class="value">{{ item.goods.skuInfo.goods_price }}</text>
</view>
<view class="stepper">
<u-number-box :min="1" :value="item.goods_num" :step="1" @change="onChangeStepper($event, item)" />
</view>
</view>
</view>
</view>
</view>
<!-- 购物车数据为空 -->
<empty v-if="!list.length" :isLoading="isLoading" :custom-style="{ padding: '180rpx 50rpx' }" tips="您的购物车是空的, 快去逛逛吧">
<view slot="slot" class="empty-ipt" @click="onTargetIndex">
<text>去逛逛</text>
</view>
</empty>
<!-- 商品推荐 -->
<recommended />
<!-- 底部操作栏 -->
<view v-if="list.length" class="footer-fixed">
<label class="all-radio" @click="handleCheckAll">
<radio class="radio" :color="appTheme.mainBg" :checked="checkedIds.length > 0 && checkedIds.length === list.length" />
<text>全选</text>
</label>
<view class="total-info">
<text>合计</text>
<view class="goods-price">
<text class="unit"></text>
<text class="value">{{ totalPrice }}</text>
</view>
</view>
<view class="cart-action">
<view class="btn-wrapper">
<!-- dev:下面的disabled条件使用checkedIds.join方式判断 -->
<!-- dev:通常情况下vue项目使用checkedIds.length更合理, 但是length属性在微信小程序中不起作用 -->
<view v-if="mode == 'normal'" class="btn-item btn-main" :class="{ disabled: checkedIds.join() == '' }" @click="handleOrder()">
<text>去结算 {{ checkedIds.length > 0 ? `(${checkedIds.length})` : '' }}</text>
</view>
<view v-if="mode == 'edit'" class="btn-item btn-main" :class="{ disabled: !checkedIds.length }" @click="handleDelete()">
<text>删除</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import Empty from '@/components/empty'
import Recommended from '@/components/recommended'
import { inArray, arrayIntersect, debounce } from '@/utils/util'
import { checkLogin, setCartTotalNum, setCartTabBadge } from '@/core/app'
import * as CartApi from '@/api/cart'
const CartIdsIndex = 'CartIds'
export default {
components: {
Empty,
Recommended
},
data() {
return {
inArray,
// 正在加载
isLoading: true,
// 当前模式: normal正常 edit编辑
mode: 'normal',
// 购物车商品列表
list: [],
// 购物车商品总数量
total: null,
// 选中的商品ID记录
checkedIds: [],
// 选中的商品总金额
totalPrice: '0.00'
}
},
watch: {
// 监听选中的商品
checkedIds: {
handler(val) {
// 计算合计金额
this.onCalcTotalPrice()
// 记录到缓存中
uni.setStorageSync(CartIdsIndex, val)
},
immediate: false
},
// 监听购物车商品总数量
total(val) {
// 缓存并设置角标
setCartTotalNum(val)
setCartTabBadge()
}
},
/**
* 生命周期函数--监听页面显示
*/
onShow(options) {
// 获取购物车商品列表
checkLogin() ? this.getCartList() : this.isLoading = false
// 获取缓存中的选中记录
this.checkedIds = uni.getStorageSync(CartIdsIndex)
},
methods: {
// 计算合计金额 (根据选中的商品)
onCalcTotalPrice() {
const app = this
// 选中的商品记录
const checkedList = app.list.filter(item => inArray(item.id, app.checkedIds))
// 计算总金额
let tempPrice = 0;
checkedList.forEach(item => {
// 商品单价, 为了方便计算先转换单位为分 (整数)
const unitPrice = item.goods.skuInfo.goods_price * 100
tempPrice += unitPrice * item.goods_num
})
app.totalPrice = (tempPrice / 100).toFixed(2)
},
// 获取购物车商品列表
getCartList() {
const app = this
app.isLoading = true
CartApi.list()
.then(result => {
app.list = result.data.list
app.total = result.data.cartTotal
// 清除checkedIds中无效的ID
app.onClearInvalidId()
})
.finally(() => app.isLoading = false)
},
// 清除checkedIds中无效的ID
onClearInvalidId() {
const app = this
const listIds = app.list.map(item => item.id)
app.checkedIds = arrayIntersect(listIds, app.checkedIds)
},
// 切换当前模式
handleToggleMode() {
this.mode = this.mode == 'normal' ? 'edit' : 'normal'
},
// 监听步进器更改事件
onChangeStepper({ value }, item) {
// 这里是组织首次启动时的执行
if (item.goods_num == value) return
// 记录一个节流函数句柄
if (!item.debounceHandle) {
item.oldValue = item.goods_num
item.debounceHandle = debounce(this.onUpdateCartNum, 500)
}
// 更新商品数量
item.goods_num = value
// 提交更新购物车数量 (节流)
item.debounceHandle(item, item.oldValue, value)
},
// 提交更新购物车数量
onUpdateCartNum(item, oldValue, newValue) {
const app = this
CartApi.update(item.goods_id, item.goods_sku_id, newValue)
.then(result => {
// 更新商品数量
app.total = result.data.cartTotal
// 重新计算合计金额
app.onCalcTotalPrice()
// 清除节流函数句柄
item.debounceHandle = null
})
.catch(err => {
// 还原商品数量
item.goods_num = oldValue
setTimeout(() => app.$toast(err.errMsg), 10)
})
},
// 跳转到商品详情页
onTargetGoods(goodsId) {
this.$navTo('pages/goods/detail', { goodsId })
},
// 点击去逛逛按钮, 跳转到首页
onTargetIndex() {
this.$navTo('pages/index/index')
},
// 选中商品
handleCheckItem(cartId) {
const { checkedIds } = this
const index = checkedIds.findIndex(id => id === cartId)
index < 0 ? checkedIds.push(cartId) : checkedIds.splice(index, 1)
},
// 全选事件
handleCheckAll() {
const { checkedIds, list } = this
this.checkedIds = checkedIds.length === list.length ? [] : list.map(item => item.id)
},
// 结算选中的商品
handleOrder() {
const app = this
if (app.checkedIds.length) {
const cartIds = app.checkedIds.join()
app.$navTo('pages/checkout/index', { mode: 'cart', cartIds })
}
},
// 删除选中的商品弹窗事件
handleDelete() {
const app = this
if (!app.checkedIds.length) {
return false
}
uni.showModal({
title: '友情提示',
content: '您确定要删除该商品吗?',
showCancel: true,
success({ confirm }) {
// 确认删除
confirm && app.onClearCart()
}
})
},
// 确认删除商品
onClearCart() {
const app = this
CartApi.clear(app.checkedIds)
.then(result => {
app.getCartList()
app.handleToggleMode()
})
}
}
}
</script>
<style>
page {
background: #f5f5f5;
}
</style>
<style lang="scss" scoped>
.container {
padding-bottom: 120rpx;
}
// 页面顶部
.head-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4rpx 30rpx;
// background-color: #fff;
height: 80rpx;
.cart-total {
font-size: 28rpx;
color: #333;
.active {
color: $main-bg;
margin: 0 2rpx;
}
}
.cart-edit {
padding-left: 20rpx;
.icon {
margin-right: 12rpx;
}
.edit {
color: $main-bg;
}
}
}
// 购物车列表
.cart-list {
padding: 0 16rpx 0 16rpx;
}
.cart-item {
background: #fff;
border-radius: 12rpx;
display: flex;
align-items: center;
padding: 30rpx 16rpx;
margin-bottom: 24rpx;
.item-radio {
width: 56rpx;
height: 80rpx;
margin-right: 10rpx;
display: flex;
justify-content: center;
align-items: center;
.radio {
transform: scale(0.76)
}
}
.goods-image {
width: 200rpx;
height: 200rpx;
.image {
display: block;
width: 100%;
height: 100%;
border-radius: 8rpx;
}
}
.item-content {
flex: 1;
padding-left: 24rpx;
.goods-title {
font-size: 28rpx;
max-height: 76rpx;
}
.goods-props {
margin-top: 14rpx;
height: 40rpx;
color: #ababab;
font-size: 24rpx;
overflow: hidden;
.goods-props-item {
display: inline-block;
margin-right: 14rpx;
padding: 4rpx 16rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
width: auto;
}
}
.item-foot {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20rpx;
.goods-price {
vertical-align: bottom;
color: $main-bg;
.unit {
font-size: 24rpx;
}
.value {
font-size: 32rpx;
}
}
}
}
}
// 空数据按钮
.empty-ipt {
margin: 0 auto;
width: 250rpx;
height: 70rpx;
font-size: 32rpx;
text-align: center;
color: #fff;
border-radius: 50rpx;
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
display: flex;
justify-content: center;
align-items: center;
}
// 底部操作栏
.footer-fixed {
display: flex;
align-items: center;
height: 96rpx;
background: #fff;
padding: 0 30rpx;
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
.all-radio {
width: 140rpx;
display: flex;
align-items: center;
.radio {
margin-bottom: -4rpx;
transform: scale(0.76)
}
}
.total-info {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 30rpx;
.goods-price {
vertical-align: bottom;
color: $main-bg;
.unit {
font-size: 24rpx;
}
.value {
font-size: 32rpx;
}
}
}
.cart-action {
width: 200rpx;
.btn-wrapper {
height: 100%;
display: flex;
align-items: center;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 72rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
// 去结算按钮
.btn-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
// 禁用按钮
&.disabled {
opacity: 0.6;
}
}
}
}
</style>
+297
View File
@@ -0,0 +1,297 @@
<template>
<view class="container" :style="appThemeStyle">
<!-- 一级分类 -->
<scroll-view class="cate-left" :scroll-y="true" :style="{ height: `${scrollHeight}px` }">
<text class="type-nav" :class="{ selected: curIndex == -1 }" @click="handleSelectNav(-1)">全部</text>
<text class="type-nav" :class="{ selected: curIndex == index }" v-for="(item, index) in list" :key="index"
@click="handleSelectNav(index)">{{ item.name }}</text>
</scroll-view>
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption" :bottombar="false" @up="upCallback">
<view class="cate-content">
<!-- 商品列表 -->
<view class="goods-list">
<view class="goods-item--container" v-for="(item, index) in goodsList.data" :key="index">
<view class="goods-item" @click="onTargetGoods(item.goods_id)">
<!-- 商品图片 -->
<view class="goods-item_left">
<image class="image" :src="item.goods_image"></image>
</view>
<view class="goods-item_right">
<!-- 商品标题 -->
<view class="goods-name">
<text class="twoline-hide">{{ item.goods_name }}</text>
</view>
<!-- 商品信息 -->
<view class="goods-item_desc">
<view class="desc_footer">
<view class="item-prices oneline-hide">
<text class="price_x">¥{{ item.goods_price_min }}</text>
<text v-if="item.line_price_min > 0" class="price_y">¥{{ item.line_price_min }}</text>
</view>
<add-cart-btn v-if="setting.showAddCart" :btnStyle="setting.cartStyle" @click="handleAddCart(item)" />
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 加入购物车组件 -->
<AddCartPopup ref="AddCartPopup" @addCart="onUpdateCartTabBadge" />
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import { getEmptyPaginateObj, getMoreListData, setCartTabBadge } from '@/core/app'
import { PageCategoryStyleEnum } from '@/common/enum/store/page/category'
import Empty from '@/components/empty'
import AddCartBtn from '@/components/add-cart-btn'
import AddCartPopup from '@/components/add-cart-popup'
import { rpx2px } from '@/utils/util'
import * as GoodsApi from '@/api/goods'
const pageSize = 15
export default {
components: {
MescrollBody,
Empty,
AddCartBtn,
AddCartPopup
},
mixins: [MescrollMixin],
props: {
// 分类列表
list: {
type: Array,
default: []
},
// 分类设置
setting: {
type: Object,
default: () => {}
},
},
data() {
return {
// 枚举类
PageCategoryStyleEnum,
// 列表高度
scrollHeight: 0,
// 一级分类:指针
curIndex: -1,
// 商品列表
goodsList: getEmptyPaginateObj(),
// 上拉加载配置
upOption: {
// 首次自动执行
auto: true,
// 每页数据的数量; 默认10
page: { size: pageSize },
// 数量要大于3条才显示无更多数据
noMoreSize: 3,
// 返回顶部
toTop: { right: 30, bottom: 48, zIndex: 9 }
}
}
},
created() {
// 设置分类列表高度
this.setListHeight()
},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
// 设置列表数据
app.getGoodsList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
/**
* 获取商品列表
* @param {Number} pageNo 页码
*/
getGoodsList(pageNo = 1) {
const app = this
const categoryId = app.curIndex > -1 ? app.list[app.curIndex].category_id : 0
return new Promise((resolve, reject) => {
GoodsApi.list({ categoryId, page: pageNo }, { load: false })
.then(result => {
const newList = result.data.list
app.goodsList.data = getMoreListData(newList, app.goodsList, pageNo)
app.goodsList.last_page = newList.last_page
resolve(newList)
})
.catch(reject)
})
},
// 设置列表内容的高度
setListHeight() {
const { windowHeight } = uni.getSystemInfoSync()
this.scrollHeight = windowHeight - rpx2px(96)
},
// 一级分类:选中分类
handleSelectNav(index) {
this.curIndex = index
this.onRefreshList()
},
// 刷新列表数据
onRefreshList() {
this.goodsList = getEmptyPaginateObj()
setTimeout(() => this.mescroll.resetUpScroll(), 120)
},
// 跳转至商品列表页
onTargetGoods(goodsId) {
this.$navTo('pages/goods/detail', { goodsId })
},
// 点击加入购物车
handleAddCart(item) {
this.$refs.AddCartPopup.handle(item)
},
// 更新购物车角标
onUpdateCartTabBadge() {
console.log('onUpdateCartTabBadge')
setCartTabBadge()
},
}
}
</script>
<style lang="scss" scoped>
.container {
padding-left: 173rpx;
}
// 分类内容
.cate-content {
z-index: 1;
background: #fff;
padding-top: 96rpx;
}
// 一级分类+二级分类 20
.cate-left {
width: 173rpx;
height: 100%;
background: #f8f8f8;
color: #444;
position: fixed;
top: calc(96rpx + var(--window-top));
left: var(--window-left);
bottom: var(--window-bottom);
}
// 左侧一级分类
.type-nav {
position: relative;
height: 90rpx;
z-index: 10;
display: block;
font-size: 26rpx;
display: flex;
justify-content: center;
align-items: center;
&.selected {
background: #fff;
border-right: none;
font-size: 28rpx;
color: $main-bg
}
}
// 商品列表
.goods-list {
background: #fff;
}
.goods-item {
padding: 28rpx 22rpx;
display: flex;
}
.goods-item_left {
position: relative;
background: #fff;
margin-right: 20rpx;
.image {
display: block;
width: 180rpx;
height: 180rpx;
}
}
.goods-item_right {
position: relative;
flex: 1;
.goods-name {
display: block;
width: 100%;
min-height: 68rpx;
font-size: 28rpx;
line-height: 1.3;
color: #333;
}
}
.goods-item_desc {
margin-top: 20rpx;
.people {
margin-right: 14rpx;
}
.desc_footer {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
right: 0rpx;
bottom: 0rpx;
min-height: 44rpx;
.item-prices {
padding-right: 6rpx;
.price_x {
margin-right: 14rpx;
color: $main-bg;
font-size: 28rpx;
}
.price_y {
color: #999;
text-decoration: line-through;
font-size: 24rpx;
}
}
}
}
</style>
+110
View File
@@ -0,0 +1,110 @@
<template>
<view class="primary">
<!-- 一级分类(大图) 10 -->
<view v-if="list.length > 0 && display == PageCategoryStyleEnum.ONE_LEVEL_BIG.value" class="cate-content">
<view class="cate-wrapper cate_style__10">
<view class="cate-item" v-for="(item, index) in list" :key="index" @click="onTargetGoodsList(item.category_id)">
<image v-if="item.image" class="image" mode="widthFix" :src="item.image.preview_url"></image>
</view>
</view>
</view>
<!-- 一级分类(小图) 11 -->
<view v-if="list.length > 0 && display == PageCategoryStyleEnum.ONE_LEVEL_SMALL.value" class="cate-content">
<view class="cate-wrapper cate_style__11">
<view class="cate-item" v-for="(item, index) in list" :key="index" @click="onTargetGoodsList(item.category_id)">
<image v-if="item.image" class="image" mode="widthFix" :src="item.image.preview_url"></image>
<view class="cate-name">{{ item.name }}</view>
</view>
</view>
</view>
<empty v-if="!list.length" :tips="'亲,暂无商品分类' + display" />
</view>
</template>
<script>
import { PageCategoryStyleEnum } from '@/common/enum/store/page/category'
import Empty from '@/components/empty'
export default {
components: {
Empty
},
props: {
// 分类页样式
display: {
type: Number,
default: 10
},
// 分类列表
list: {
type: Array,
default: []
}
},
data() {
return {
// 枚举类
PageCategoryStyleEnum,
}
},
methods: {
// 跳转至商品列表页
onTargetGoodsList(categoryId) {
this.$navTo('pages/goods/list', { categoryId })
}
}
}
</script>
<style lang="scss" scoped>
// 分类内容
.cate-content {
z-index: 1;
background: #fff;
padding-top: 96rpx;
.cate-wrapper {
padding: 0 20rpx 20rpx 20rpx;
box-sizing: border-box;
}
}
// 一级分类(大图) 10
.cate_style__10 .cate-item {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.image {
display: block;
width: 100%;
height: auto;
}
}
// 一级分类(小图) 11
.cate_style__11 .cate-item {
float: left;
padding: 25rpx;
width: 33.3333%;
text-align: center;
box-sizing: border-box;
.image {
display: block;
width: 100%;
height: 33vw;
margin-bottom: 12rpx;
}
.cate-name {
font-size: 28rpx;
color: #555;
}
}
</style>
+180
View File
@@ -0,0 +1,180 @@
<template>
<view v-if="list.length > 0" class="secondary" :style="appThemeStyle">
<!-- 二级分类 20 -->
<view class="cate-content">
<!-- 左侧 一级分类 -->
<scroll-view class="cate-left" :scroll-y="true" :style="{ height: `${scrollHeight}px` }">
<text class="type-nav" :class="{ selected: curIndex == index }" v-for="(item, index) in list" :key="index"
@click="handleSelectNav(index)">{{ item.name }}</text>
</scroll-view>
<!-- 右侧 二级分类 -->
<scroll-view class="cate-right" :scroll-top="scrollTop" :scroll-y="true" :style="{ height: `${scrollHeight}px` }">
<view v-if="list[curIndex]" class="cate-right-cont">
<view class="cate-two-box">
<view class="cate-cont-box">
<view class="flex-three" v-for="(item, idx) in list[curIndex].children" :key="idx" @click="onTargetGoodsList(item.category_id)">
<view class="cate-img-padding">
<view v-if="item.image" class="cate-img">
<image class="image" mode="scaleToFill" :src="item.image.preview_url"></image>
</view>
</view>
<text class="name oneline-hide">{{ item.name }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<empty v-if="!list.length" />
</view>
</template>
<script>
import { PageCategoryStyleEnum } from '@/common/enum/store/page/category'
import Empty from '@/components/empty'
import { rpx2px } from '@/utils/util'
export default {
components: {
Empty
},
props: {
// 分类列表
list: {
type: Array,
default: []
},
},
data() {
return {
// 枚举类
PageCategoryStyleEnum,
// 列表高度
scrollHeight: 0,
// 一级分类:指针
curIndex: 0,
// 内容区竖向滚动条位置
scrollTop: 0,
}
},
created() {
// 设置分类列表高度
this.setListHeight()
},
methods: {
// 设置列表内容的高度
setListHeight() {
const { windowHeight } = uni.getSystemInfoSync()
this.scrollHeight = windowHeight - rpx2px(96)
},
// 一级分类:选中分类
handleSelectNav(index) {
this.curIndex = index
this.scrollTop = 0
},
// 跳转至商品列表页
onTargetGoodsList(categoryId) {
this.$navTo('pages/goods/list', { categoryId })
}
}
}
</script>
<style lang="scss" scoped>
// 分类内容
.cate-content {
display: flex;
z-index: 1;
background: #fff;
padding-top: 96rpx;
}
// 一级分类+二级分类 20
.cate-left {
height: 100%;
display: flex;
flex-direction: column;
flex: 0 0 23%;
background: #f8f8f8;
color: #444;
}
.cate-right {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
.cate-right-cont {
width: 100%;
display: flex;
flex-flow: row wrap;
align-content: flex-start;
padding-top: 15rpx;
.cate-two-box {
width: 100%;
padding: 0 10px;
}
}
}
// 左侧一级分类
.type-nav {
position: relative;
height: 90rpx;
z-index: 10;
display: block;
font-size: 26rpx;
display: flex;
justify-content: center;
align-items: center;
&.selected {
background: #fff;
color: $main-bg;
border-right: none;
font-size: 28rpx;
}
}
// 右侧二级分类
.cate-cont-box {
margin-bottom: 30rpx;
padding-bottom: 10rpx;
background: #fff;
overflow: hidden;
.name {
display: block;
padding-bottom: 30rpx;
text-align: center;
font-size: 26rpx;
color: #444444;
}
.cate-img-padding {
padding: 16rpx 16rpx 4rpx 16rpx;
}
.cate-img {
position: relative;
width: 100%;
padding-top: 100%;
.image {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
border-radius: 10rpx;
}
}
}
</style>
+166
View File
@@ -0,0 +1,166 @@
<template>
<view class="container">
<!-- 搜索框 -->
<search class="search" tips="搜索商品" @event="$navTo('pages/search/index')" />
<!-- 一级分类 -->
<primary v-if="setting.style == PageCategoryStyleEnum.ONE_LEVEL_BIG.value || setting.style == PageCategoryStyleEnum.ONE_LEVEL_SMALL.value"
:display="setting.style" :list="list" />
<!-- 二级分类 -->
<secondary v-if="setting.style == PageCategoryStyleEnum.TWO_LEVEL.value" :list="list" />
<!-- 分类+商品 -->
<commodity v-if="setting.style == PageCategoryStyleEnum.COMMODITY.value" ref="mescrollItem" :list="list" :setting="setting" />
</view>
</template>
<script>
import MescrollCompMixin from '@/components/mescroll-uni/mixins/mescroll-comp'
import { setCartTabBadge } from '@/core/app'
import SettingKeyEnum from '@/common/enum/setting/Key'
import { PageCategoryStyleEnum } from '@/common/enum/store/page/category'
import SettingModel from '@/common/model/Setting'
import * as CategoryApi from '@/api/category'
import Empty from '@/components/empty'
import Search from '@/components/search'
import Primary from './components/primary'
import Secondary from './components/secondary'
import Commodity from './components/commodity'
// 最后一次刷新时间
let lastRefreshTime;
export default {
components: {
Search,
Empty,
Primary,
Secondary,
Commodity
},
mixins: [MescrollCompMixin],
data() {
return {
// 枚举类
PageCategoryStyleEnum,
// 分类列表
list: [],
// 分类模板设置
setting: {},
// 正在加载中
isLoading: true
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad() {
// 加载页面数据
this.onRefreshPage()
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
// 每间隔5分钟自动刷新一次页面数据
const curTime = new Date().getTime()
if ((curTime - lastRefreshTime) > 5 * 60 * 1000) {
this.onRefreshPage()
}
},
methods: {
// 刷新页面
onRefreshPage() {
// 记录刷新时间
lastRefreshTime = new Date().getTime()
// 获取页面数据
this.getPageData()
// 更新购物车角标
setCartTabBadge()
},
// 获取页面数据
getPageData() {
const app = this
app.isLoading = true
Promise.all([
// 获取分类模板设置
// 优化建议: 可以将此处的false改为true 启用缓存
SettingModel.data(false),
// 获取分类列表
CategoryApi.list()
])
.then(result => {
// 初始化分类模板设置
app.initSetting(result[0])
// 初始化分类列表数据
app.initCategory(result[1])
})
.finally(() => app.isLoading = false)
},
/**
* 初始化分类模板设置
* @param {Object} result
*/
initSetting(setting) {
this.setting = setting[SettingKeyEnum.PAGE_CATEGORY_TEMPLATE.value]
},
/**
* 初始化分类列表数据
* @param {Object} result
*/
initCategory(result) {
this.list = result.data.list
},
},
/**
* 设置分享内容
*/
onShareAppMessage() {
const app = this
return {
title: _this.templet.shareTitle,
path: '/pages/category/index?' + app.$getShareUrlParams()
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
const app = this
return {
title: _this.templet.shareTitle,
path: '/pages/category/index?' + app.$getShareUrlParams()
}
}
}
</script>
<style>
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
// 搜索框
.search {
position: fixed;
top: var(--window-top);
left: var(--window-left);
right: var(--window-right);
z-index: 9;
padding-bottom: 20rpx;
}
</style>
+367
View File
@@ -0,0 +1,367 @@
<template>
<view>
<!-- 消息列表 -->
<view class="shop-list" v-if="msg">
<view class="item" v-for="(item,index) in msg.choices" :key="index">
<!-- <image :src="item.logo" mode="aspectFit"></image> -->
<view class="info">
<!-- <text class="title">{{item.merchantName}}</text> -->
<text class="desc">{{ item.message.content }}</text>
<!-- <view class="tag">
<view class="mr12">
<u-tag size="mini" plain text="租车站"></u-tag>
</view>
<view class="mr12">
<u-tag size="mini" type="warning" plain text="换点站"></u-tag>
</view>
<view class="mr12">
<u-tag size="mini" type="success" plain text="买车站"></u-tag>
</view>
</view> -->
<!-- <text class="distance">距离4.2km</text> -->
</view>
</view>
</view>
<!-- 定位按钮 -->
<!-- <view v-if="!isAuthor" class="widget-location dis-flex flex-x-center flex-y-center" @click="onAuthorize()">
<text class="iconfont icon-locate"></text>
</view> -->
<!-- <empty v-if="list.length == 0" :isLoading="isLoading" tips="亲,暂无消息" /> -->
<!-- <view v-if="msg">
<view v-for="(item,index) in msg.choices" :key="index">
{{ item.message }}
</view>
</view> -->
<view class="footer">
<view class="send-box">
<input class="input" type="text" v-model="text" />
<button @click="onSubmit" class="submit-text">提交</button>
</view>
</view>
</view>
</template>
<script>
import store from '@/store/index.js'
import storage from '../../utils/storage'
import {
ACCESS_TOKEN,
USER_ID
} from '@/store/mutation-types'
import {
pageMerchant
} from '@/websoft/api/merchant.js'
import { chat } from '@/websoft/api/chatgpt.js'
import Empty from '@/components/empty'
import {
userId
} from '../../config'
import login from '../../websoft/api/login'
export default {
components: {
// Search,
Empty
},
data() {
return {
title: '国内馆',
// 正在加载中
isLoading: false,
// 是否授权了定位权限
isAuthor: true,
// 当前选择的门店ID
selectedId: null,
// 订单列表数据
list: [],
text: '',
msg: null
}
},
onLoad() {
const app = this
if (!store.getters.userId) {
console.log("未登录1: ");
return false;
}
// 获取用户坐标
app.getLocation(res => {
app.getShopList(res.longitude, res.latitude)
})
},
onShow() {
const app = this
if (app.list.length == 0) {
app.getShopList(app.longitude, app.latitude)
}
},
methods: {
// 获取门店列表
getShopList(longitude, latitude) {
const app = this
},
// 获取用户坐标
// 参考文档:https://uniapp.dcloud.io/api/location/location?id=getlocation
getLocation(callback) {
const app = this
uni.getLocation({
success: callback,
fail() {
app.$toast('获取定位失败,请点击右下角按钮重新尝试定位')
app.isAuthor = false
}
})
},
// 授权启用定位权限
onAuthorize() {
const app = this
// #ifdef MP
uni.openSetting({
success(res) {
if (res.authSetting['scope.userLocation']) {
console.log('定位权限授权成功')
app.isAuthor = true
setTimeout(() => {
// 获取用户坐标
app.getLocation((res) => {
app.getShopList(res.longitude, res.latitude)
})
}, 1000)
}
}
})
// #endif
// #ifdef H5
// 获取用户坐标
app.getLocation((res) => {
app.getShopList(res.longitude, res.latitude)
})
// #endif
},
navTo(merchantId) {
const navTo = uni.$u.route()
navTo('pages/merchant/detail', {
merchantId
})
},
/**
* 选择门店
*/
onSelectedShop(merchantId, merchantCode) {
uni.$u.route('pages/merchant/detail', {
merchantId,
merchantCode
})
},
changeCity() {
this.$toast('切换区域')
},
onSubmit() {
const app = this
chat({
tenantId: 6,
content: app.text
}).then(res => {
console.log("res: ",res);
app.msg = res.data
}).catch(err => {
if(err.code == 1){
app.$error("您的免费额度已用完")
}
console.log("err: ",err);
})
pageMerchant({}).then(res => {
console.log("res: ",res);
})
}
}
}
</script>
<style lang="scss" scoped>
.bg {
background-color: #F5F5F8
}
.shop-list {
.item {
padding: 20rpx;
margin: 20rpx auto;
background-color: #ffffff;
width: 660rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
image {
width: 180rpx;
height: 180rpx;
margin-right: 40rpx;
background-color: #f9f9f9;
}
.info {
display: flex;
flex-direction: column;
.title {
font-size: 38rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.desc {
font-size: 28rpx;
}
.tag {
font-size: 28rpx;
display: flex;
.mr12 {
margin-right: 12rpx;
}
}
.distance {
font-size: 28rpx;
}
}
}
}
.header-box {
padding: 20rpx 36rpx 0 36rpx;
display: flex;
justify-content: space-between;
.location {
display: flex;
justify-content: center;
align-items: center;
color: #999999;
image {
width: 60rpx;
height: 60rpx
}
}
.u-subsection {
width: 260rpx;
margin-top: 7rpx
}
}
.search-wrapper {
display: flex;
width: 80%;
height: 64rpx
}
// 搜索输入框
.search-input {
width: 90%;
background: #fff;
border-radius: 10rpx 0 0 10rpx;
box-sizing: border-box;
overflow: hidden;
.search-input-wrapper {
display: flex;
.left {
display: flex;
width: 60rpx;
justify-content: center;
align-items: center;
.search-icon {
display: block;
color: #b4b4b4;
font-size: 28rpx
}
}
.right {
flex: 1;
input {
font-size: 28rpx;
height: 64rpx;
display: flex;
.input-placeholder {
color: #aba9a9
}
}
}
}
}
.shop-avatar {
width: 200rpx;
margin-right: 24rpx
}
// 搜索按钮
.search-button {
width: 25%;
box-sizing: border-box;
.button {
height: 64rpx;
font-size: 28rpx;
border-radius: 0 5px 5px 0;
background: #2C71C7
}
}
.shop-info {
width: 100%;
margin-left: 20rpx;
.title {
font-size: 38rpx;
font-weight: bold;
padding-bottom: 10rpx;
}
.about {
display: flex;
.mr12 {
margin-right: 12rpx;
}
}
}
.u-body-item {
align-items: stretch !important
}
.footer {
width: 750rpx;
position: absolute;
bottom: 100rpx;
.send-box {
width: 700rpx;
margin: auto;
height: 90rpx;
display: flex;
justify-content: space-around;
border-radius: 20rpx;
.submit-text{ padding: 0 30rpx;}
.input{
width: 512rpx;
height: 82rpx;
}
}
}
</style>
+601
View File
@@ -0,0 +1,601 @@
<template>
<view>
<view v-if="!isLoading && order" class="container" :style="appThemeStyle">
<!-- 订单信息 -->
<view class="order-info">
<!-- 支付剩余时间 -->
<view class="order-countdown">
<text class="m-r-6">剩余时间</text>
<count-down :date="expirationTime" separator="zh" theme="text" />
</view>
<!-- 付款金额 -->
<view class="order-amount">
<text class="unit"></text>
<text class="amount">{{ order.totalPrice }}</text>
</view>
</view>
<!-- 支付方式 -->
<view class="payment-method">
<view v-for="(item, index) in methods" :key="index" class="pay-item dis-flex flex-x-between"
@click="handleSelectPayType(index)">
<view class="item-left dis-flex flex-y-center">
<view class="item-left_icon" :class="[item.method]">
<text class="iconfont" :class="[item.icon]"></text>
</view>
<view class="item-left_text">
<text>{{ item.method }}</text>
</view>
<view v-if="item.method === '余额支付'" class="user-balance">
<text>(可用{{ personal.balance }})</text>
</view>
</view>
<view class="item-right col-m" v-if="curPaymentItem && curPaymentItem.method == item.method">
<text class="iconfont icon-check"></text>
</view>
</view>
</view>
<!-- 确认按钮 -->
<view class="footer-fixed">
<view class="btn-wrapper">
<view class="btn-item btn-item-main" :class="{ disabled }" @click="handleSubmit()">确认支付</view>
</view>
</view>
<!-- 支付确认弹窗 -->
<!-- #ifdef H5 -->
<u-modal v-if="tempUnifyData" v-model="showConfirmModal" title="支付确认" show-cancel-button confirm-text="已完成支付"
:confirm-color="appTheme.mainBg" negative-top="100" :asyncClose="true"
@confirm="onTradeQuery(tempUnifyData.outTradeNo, tempUnifyData.method)">
<view class="modal-content">
<text>请在{{ PayMethodClientNameEnum[tempUnifyData.method] }}内完成支付如果您已经支付成功请点击已完成支付按钮</text>
</view>
</u-modal>
<!-- #endif -->
</view>
<empty v-else :isLoading="isLoading" tips="订单不存在" />
</view>
</template>
<script>
import storage from '@/utils/storage'
import {
inArray,
urlEncode
} from '@/utils/util'
import {
Alipay,
Wechat
} from '@/core/payment'
import CountDown from '@/components/countdown'
import {
PayMethodEnum
} from '@/common/enum/payment'
import {
PayStatusEnum
} from '@/common/enum/order'
import {
getOrder,
setPayStatus
} from '@/websoft/api/order.js'
import {
pagePayment,
alipay,
payQuery,
balance
} from '@/websoft/api/payment.js'
import {
dateFormat
} from '@/utils/util.js'
import Empty from '@/components/empty'
import * as CashierApi from '@/api/cashier'
import { getUser } from '@/websoft/api/user.js'
// 支付方式对应的图标
const PayMethodIconEnum = {
[PayMethodEnum.WECHAT.value]: 'icon-wechat-pay',
[PayMethodEnum.ALIPAY.value]: 'icon-alipay',
[PayMethodEnum.BALANCE.value]: 'icon-balance-pay'
}
// 支付方式的终端名称
const PayMethodClientNameEnum = {
[PayMethodEnum.WECHAT.value]: '微信',
[PayMethodEnum.ALIPAY.value]: '支付宝'
}
export default {
components: {
CountDown,
Empty
},
data() {
return {
// 加载中
isLoading: true,
// 确认按钮禁用
disabled: false,
// 枚举类
PayMethodEnum,
PayMethodIconEnum,
PayMethodClientNameEnum,
// 当前选中的支付方式
curPaymentItem: {
method: '余额支付'
},
// 当前订单ID
orderId: null,
// 当前结算订单信息
order: null,
// 订单过期时间
expirationTime: new Date(),
// 个人信息
personal: {
balance: '0.00'
},
// 当前客户端的支付方式列表(后端根据platform判断)
methods: [
{
id: 1,
method: '余额支付',
icon: 'icon-balance-pay'
},
{
id: 2,
method: '支付宝',
icon: 'icon-alipay'
}
],
// 支付确认弹窗
showConfirmModal: false,
// #ifdef H5
// 当前微信支付信息 (临时数据, 仅用于H5端)
tempUnifyData: {
outTradeNo: '',
method: ''
},
// #endif
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({
orderId
}) {
// 记录订单ID
this.orderId = Number(orderId)
// 获取收银台信息
this.getCashierInfo()
},
methods: {
// 获取收银台信息
getCashierInfo() {
const app = this
const {
orderId
} = this
app.isLoading = true
// 加载订单
getOrder(orderId)
.then(res => {
app.order = res.data
app.isLoading = false
// 处理订单过期时间(*1小时)
var expirationTime = new Date().getTime() + 60 * 60 * 1000 * 0.20
app.expirationTime = dateFormat('YYYY-mm-dd HH:MM:SS', new Date(expirationTime))
})
.catch(e => {
console.log("e: ", e);
})
// 查询余额
getUser().then(res => app.personal = res.data)
// pagePayment({}).then(res => {
// app.methods = res.data.list
// })
// CashierApi.orderInfo(app.orderId, { client: app.platform })
// .then(result => {
// app.order = result.data.order
// console.log("app.order: ",app.order);
// app.personal = result.data.personal
// app.methods = result.data.paymentMethods
// app.isLoading = false
// app.setDefaultPayType()
// app.checkOrderPayStatus()
// // #ifdef H5
// // 判断当前页面来源于浏览器返回
// this.performance()
// // #endif
// })
},
// 设置默认的支付方式
setDefaultPayType() {
const app = this
if (app.disabled) return
const defaultIndex = app.methods.findIndex(item => item.is_default == true)
defaultIndex > -1 && app.handleSelectPayType(defaultIndex)
},
// 判断当前订单是否为已支付
checkOrderPayStatus() {
const app = this
if (app.order.pay_status == PayStatusEnum.SUCCESS.value) {
app.$toast('恭喜您,订单已付款成功')
app.onSuccessNav()
}
},
// 选择支付方式
handleSelectPayType(index) {
this.curPaymentItem = this.methods[index]
console.log("this.curPaymentItem: ", this.curPaymentItem);
},
// 判断当前页面来源于浏览器返回
// #ifdef H5
performance() {
const app = this
// 判断订单状态, 异步回调会将订单状态变为已支付, 那么就不需要让用户手动查单了
if (app.order.pay_status == PayStatusEnum.PENDING.value) {
app.alipayPerformance()
app.wechatPerformance()
}
},
// H5端支付宝支付完成跳转回当前页面时触发
alipayPerformance() {
const app = this
app.tempUnifyData = Alipay.performance()
if (app.tempUnifyData) {
app.onTradeQuery(app.tempUnifyData.outTradeNo, app.tempUnifyData.method)
}
},
// H5端微信支付完成或返回时触发
wechatPerformance() {
const app = this
app.tempUnifyData = Wechat.performance(app.orderId)
if (app.tempUnifyData) {
app.showConfirmModal = true
}
},
// #endif
// 确认支付
handleSubmit() {
const app = this
const {
orderId
} = app.order
// 判断是否选择了支付方式
if (!app.curPaymentItem) {
app.$toast('您还没有选择支付方式')
return
}
// 按钮禁用
if (app.disabled) return
// app.disabled = true
// .js
console.log("curPaymentItem: ",app.curPaymentItem);
if(app.curPaymentItem.method == '余额支付'){
balance(orderId).then(result => app.onSubmitCallback(result)).catch(err => app.$error(err.message))
}
if(app.curPaymentItem.method == '支付宝'){
alipay(orderId).then(result => app.onSubmitCallback(result)).catch(err => app.$error(err.message))
}
// // 提交到后端API
// CashierApi.orderPay(app.orderId, {
// method: app.curPaymentItem.method,
// client: app.platform,
// extra: app.getExtraAsUnify(app.curPaymentItem.method)
// })
// .then(result => app.onSubmitCallback(result))
// .finally(err => setTimeout(() => app.disabled = false, 10))
},
// 获取第三方支付的扩展参数
getExtraAsUnify(method) {
if (method === PayMethodEnum.ALIPAY.value) {
return Alipay.extraAsUnify()
}
if (method === PayMethodEnum.WECHAT.value) {
return Wechat.extraAsUnify()
}
return {}
},
// 订单提交成功后回调
onSubmitCallback(result) {
const app = this
const method = app.curPaymentItem.method
const tradeNO = result.data
console.log("result订单提交成功后回调: ", tradeNO);
// 余额支付
if (method === '余额支付') {
app.onShowSuccess(result)
}
// 发起支付宝支付
if (method === '支付宝') {
my.tradePay({
// 调用统一收单交易创建接口(alipay.trade.create),获得返回字段支付宝交易号trade_no
tradeNO: tradeNO,
success: (res) => {
console.log("res11: ", res);
if (res.resultCode == "9000") {
payQuery(app.orderId).then(result => {
if(result.code == 0) {
app.$success('支付成功')
app.$navTo('pages/order/index')
}
})
// setPayStatus({
// orderId: app.orderId,
// payStatus: 20,
// payMethod: 20
// }).then(res => {
// console.log("updateOrder: ",res);
// app.$navTo('pages/order/index')
// })
}
if (res.resultCode == "6001") {
app.$navTo('pages/order/index')
}
// my.alert({
// content: JSON.stringify(res),
// });
},
fail: (res) => {
my.alert({
content: JSON.stringify(res),
});
}
});
// Alipay.payment(paymentData)
// .then(res => app.onPaySuccess(res))
// .catch(err => app.onPayFail(err))
}
// 发起微信支付
if (method === PayMethodEnum.WECHAT.value) {
console.log('paymentData', paymentData)
Wechat.payment({
orderKey: app.orderId,
...paymentData
})
.then(res => app.onPaySuccess(res))
.catch(err => app.onPayFail(err))
}
},
// 订单支付成功的回调方法
// 这里只是前端支付api返回结果success,实际订单是否支付成功 以后端的查单和异步通知为准
onPaySuccess({
res,
option: {
isRequireQuery,
outTradeNo,
method
}
}) {
const app = this
// 判断是否需要主动查单
// isRequireQuery为true代表需要主动查单
if (isRequireQuery) {
app.onTradeQuery(outTradeNo, method)
return true
}
this.onShowSuccess(res)
},
// 显示支付成功信息并页面跳转
onShowSuccess({
message
}) {
this.$toast(message || '订单支付成功')
this.onSuccessNav()
},
// 订单支付失败
onPayFail(err) {
console.log('onPayFail', err)
const errMsg = err.message || '订单未支付'
this.$error(errMsg)
},
// 已完成支付按钮事件: 请求后端查单
onTradeQuery(outTradeNo, method) {
const app = this
// 交易查询
// 查询第三方支付订单是否付款成功
CashierApi.tradeQuery({
outTradeNo,
method,
client: app.platform
})
.then(result => result.data.isPay ? app.onShowSuccess(result) : app.onPayFail(result))
.finally(() => app.showConfirmModal = false)
},
// 支付成功后的跳转
onSuccessNav() {
// 相应全局事件订阅: 刷新上级页面数据
uni.$emit('syncRefreshOrder', true)
// 获取上级页面
const pages = getCurrentPages()
const lastPage = pages.length < 2 ? null : pages[pages.length - 2]
const backRoutes = [
'pages/order/index',
'pages/order/detail'
]
setTimeout(() => {
if (lastPage && inArray(lastPage.route, backRoutes)) {
uni.navigateBack()
} else {
this.$navTo('pages/order/index', {}, 'redirectTo')
}
}, 1200)
},
}
}
</script>
<style>
page {
background: #F4F4F4;
}
</style>
<style lang="scss" scoped>
.container {
background-color: #F4F4F4;
}
// 订单信息
.order-info {
padding: 80rpx 0;
text-align: center;
.order-countdown {
display: flex;
justify-content: center;
font-size: 26rpx;
color: #666666;
margin-bottom: 20rpx;
}
.order-amount {
margin: 0 auto;
max-width: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fb0f07;
.unit {
font-size: 30rpx;
margin-bottom: -18rpx;
}
.amount {
font-size: 56rpx;
}
}
}
// 支付方式
.payment-method {
width: 94%;
margin: 0 auto 20rpx auto;
padding: 0 40rpx;
background-color: #ffffff;
border-radius: 20rpx;
.pay-item {
padding: 26rpx 0;
font-size: 28rpx;
border-bottom: 1rpx solid rgb(248, 248, 248);
&:last-child {
border-bottom: none;
}
.item-left_icon {
margin-right: 20rpx;
font-size: 44rpx;
&.wechat {
color: #00c800;
}
&.alipay {
color: #009fe8;
}
&.balance {
color: #ff9700;
}
}
.item-left_text {
font-size: 28rpx;
}
.item-right {
font-size: 32rpx;
}
.user-balance {
margin-left: 20rpx;
font-size: 26rpx;
}
}
}
// 支付确认弹窗
.modal-content {
padding: 40rpx 48rpx;
font-size: 30rpx;
line-height: 50rpx;
text-align: left;
color: #606266;
// height: 620rpx;
box-sizing: border-box;
}
// 底部操作栏
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
box-shadow: 0 -4rpx 40rpx 0 rgba(151, 151, 151, 0.24);
background: #fff;
// 设置ios刘海屏底部横线安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.btn-wrapper {
height: 120rpx;
display: flex;
align-items: center;
padding: 0 40rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 80rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
// 禁用按钮
&.disabled {
opacity: 0.6;
}
}
}
</style>
+378
View File
@@ -0,0 +1,378 @@
<template>
<view>
<view class="goods-info">
<view class="goods">
<image v-if="record.image" :src="record.image" class="equipment-image" mode="aspectFit"></image>
<image v-else src="../../static/goods/battery.png" class="equipment-image" mode="aspectFit"></image>
<view class="info">
<text class="goods-name">{{ record.equipmentName }}</text>
<text class="goods-desc">归属门店{{ record.merchantName }}</text>
<text class="goods-desc">电池型号{{ record.batteryModel }}</text>
<text class="selling-point">{{ record.sellingPoint }}</text>
</view>
</view>
</view>
<view class="order">
<block v-if="record.equipmentCategory === '10'">
<view class="title">
<text>合计总金额</text>
<text>{{ record.batteryPrice + record.batteryInsurance }}</text>
</view>
<!-- <u-cell-group>
<u-cell-item title="电池租金" :border-bottom="false" hover-class="none" :value="record.batteryRent"></u-cell-item>
<u-cell-item title="电池押金" :border-bottom="false" hover-class="none" :value="record.batteryDeposit"></u-cell-item>
<u-cell-item title="电池保险" :border-bottom="false" hover-class="none" :value="record.batteryInsurance"></u-cell-item>
</u-cell-group> -->
</block>
<block v-if="record.equipmentCategory === '20'">
<view class="title">
<text>合计总金额</text>
<text>{{ (record.downPayment).toFixed(2) }}</text>
</view>
<view class="fenqi">
<view class="item">
<view class="name">销售价格</view>
{{ record.batteryPrice }}
</view>
<view class="item">
<view class="name">首付款</view>
{{ record.downPayment }}
</view>
<view class="item">
<view class="name">余额分期期数</view>
{{ record.periods }}
<!-- <u-number-box :min="6" :max="36" :value="record.periods" :step="6" @change="onChangeStepper($event)" /> -->
</view>
<view class="item">
<view class="name">每期还款</view>
{{ record.repayment }}
</view>
<view class="item">
<view class="name">每期手续费</view>
{{ record.serviceCharges }}
</view>
<view class="item">
<view class="name">电动车保险</view>
<view>{{ record.batteryInsurance }}</view>
</view>
</view>
</block>
<block v-if="record.equipmentCategory === '30'">
<view class="title">
<text>合计总金额</text>
<text>{{ (record.batteryRent + record.batteryDeposit + record.batteryInsurance).toFixed(2) }}</text>
</view>
<u-cell-group>
<u-cell-item :title="`租金(按${record.periodsType == 0 ? '周' : '月'})`" :border-bottom="false" hover-class="none" :value="record.batteryRent"></u-cell-item>
<u-cell-item title="分期期数" :border-bottom="false" hover-class="none" :value="record.periods"></u-cell-item>
<u-cell-item title="押金" :border-bottom="false" hover-class="none" :value="record.batteryDeposit"></u-cell-item>
<u-cell-item title="保险" :border-bottom="false" hover-class="none" :value="record.batteryInsurance"></u-cell-item>
</u-cell-group>
</block>
<block v-if="record.equipmentCategory === '40'">
<view class="title">
<text>合计总金额</text>
<text>{{ (record.batteryRent + record.batteryDeposit + record.batteryInsurance).toFixed(2) }}</text>
</view>
<view class="fenqi">
<view class="item">
<view class="name">电动车租金</view>
{{ record.batteryRent }}
</view>
<view class="item">
<view class="name">电动车押金</view>
{{ record.batteryDeposit }}
</view>
<view class="item">
<view class="name">电动车保险</view>
{{ record.batteryInsurance }}
</view>
</view>
<!-- <u-cell-group>
<u-cell-item title="电动车租金" :border-bottom="false" hover-class="none" :value="`¥${record.batteryRent} 元`"></u-cell-item>
<u-cell-item title="电动车押金" :border-bottom="false" hover-class="none" :value="record.batteryDeposit"></u-cell-item>
<u-cell-item title="电动车保险" :border-bottom="false" hover-class="none" :value="record.batteryInsurance"></u-cell-item>
</u-cell-group> -->
</block>
<view class="xieyi">
<checkbox-group @change="onAgree" style="display: flex; align-items: center;">
<checkbox :checked="agree" />我已同意并阅读<div class="xieyi-text" @click="showXieyi">服务协议</div><div class="xieyi-text" @click="showXieyi">隐私协议</div>
</checkbox-group>
</view>
</view>
<view class="submit">
<u-input v-model="dealerId" :disabled="disabled" placeholder="请输入推荐人用户ID" />
</view>
<view class="submit">
<u-button type="primary" :disabled="!agree" @click="onBuy">立即下单</u-button>
</view>
<!-- <view class="submit">
<u-button type="success" :disabled="agree" @click="copyCode(record.equipmentCode)">复制设备编号</u-button>
</view> -->
</view>
</template>
<script>
import { userId } from '@/config.js';
import {
getEquipmentGoods
} from '@/websoft/api/equipment-goods.js'
import { getUser } from '@/websoft/api/user.js'
import { addOrder } from '@/websoft/api/order.js'
import { dateFormat } from '@/utils/util.js'
import { createOrderNo } from '@/utils/util.js'
import store from '../../store';
import {
payQuery
} from '@/websoft/api/payment.js'
export default {
components: {
// Search
},
data() {
return {
record: {},
// 正在加载中
isLoading: true,
// 当前选择的设备ID
goodsId: null,
show: true,
mode: 'range',
month: 6,
agree: false,
dealerId: '',
price: {
batteryRent: 300,
batteryDeposit: 300,
batteryInsurance: 0
},
}
},
onLoad(option) {
const app = this
// 记录当前选择的门店ID
app.goodsId = option.goodsId
// 获取设备列表
app.getEquipment()
},
onShow(){
var time = new Date().getTime() + 60*60*1000*24
const expirationTime = dateFormat('YYYY-mm-dd HH:MM:SS', new Date(time))
console.log("time: ",time);
console.log("expirationTime: ",expirationTime);
payQuery(752).then(res => {
console.log("res123: ",res);
})
},
methods: {
getEquipment() {
const app = this
const {
goodsId
} = this
getEquipmentGoods(goodsId).then(res => {
app.record = res.data
console.log("res: ", app.record);
})
},
onChangeStepper({ value }) {
this.month = value
},
change(e) {
console.log(e);
},
onInput(month) {
this.month = month
},
onAgree(){
this.agree = !this.agree
},
showXieyi(){
this.$navTo('pages/help/xieyi')
},
copyCode(text){
// #ifndef H5
console.log("text: ",text);
uni.setClipboardData({
text: text,
success: (result) => {
this.$success("复制成功")
}
})
// #endif
// #ifdef H5
let textarea = document.createElement("textarea")
textarea.value = text
textarea.readOnly = "readOnly"
document.body.appendChild(textarea)
textarea.select() // 选中文本内容
textarea.setSelectionRange(0, info.length)
uni.showToast({ //提示
title: '复制成功'
})
result = document.execCommand("copy")
textarea.remove()
// #endif
// copy() {
// let result
// // #ifndef H5
// //uni.setClipboardData方法就是讲内容复制到粘贴板
// uni.setClipboardData({
// data: this.downUrl.url, //要被复制的内容
// success: () => { //复制成功的回调函数
// uni.showToast({ //提示
// title: '复制成功'
// })
// }
// });
// // #endif
// }
},
onBuy(){
const app = this
const { record,dealerId } = this
let total = 0.0
// 未登录状态
if(!uni.getStorageSync('userId')) {
app.$error('请先登录',function(){
app.$navTo('pages/login/login');
});
return false;
}
if(record.equipmentCategory == '10'){
total = (record.batteryPrice).toFixed(2)
}
if(record.equipmentCategory == '20'){
total = (record.downPayment).toFixed(2)
}
if(record.equipmentCategory == '40' || record.equipmentCategory == '30'){
total = (record.batteryDeposit + record.batteryRent + record.batteryInsurance).toFixed(2)
}
const testTotal = 0.01
var time = new Date().getTime() + 60*60*1000*24
const expirationTime = dateFormat('YYYY-mm-dd HH:MM:SS', new Date(time))
app.expirationTime = expirationTime
addOrder({
orderNo: createOrderNo(),
merchantCode: record.merchantCode,
goodsId: app.goodsId,
totalPrice: total,
orderPrice: total,
payPrice: total,
month: app.month,
expirationTime: expirationTime,
batteryRent: record.batteryRent,
batteryDeposit: record.batteryDeposit,
batteryInsurance: record.batteryInsurance,
orderSource: record.equipmentCategory,
orderSourceId: record.goodsId,
dealerId
}).then(res => {
const { orderId } = res.data
app.$success("下单成功")
setTimeout(() => {
this.$navTo('pages/checkout/cashier/index', { orderId })
}, 100)
})
}
}
}
</script>
<style lang="scss" scoped>
.goods-info {
background-color: #ffffff;
border-radius: 12rpx;
width: 700rpx;
margin: 20rpx auto;
.goods {
padding: 20rpx;
display: flex;
image {
width: 220rpx;
height: 220rpx;
margin: 20rpx;
}
.info {
display: flex;
flex-direction: column;
padding: 20rpx;
.goods-name {
font-size: 34rpx;
margin-bottom: 10rpx;
}
.goods-desc {
font-size: 28rpx;
color: #999999;
}
.selling-point{
padding: 5px 0;
color: #e6760e;
}
}
}
.between-time {
width: 500rpx;
margin: auto;
padding-bottom: 20rpx;
text-align: center;
.select-slider {
line-height: 2em;
color: #e6760e;
}
}
}
.order {
background-color: #ffffff;
border-radius: 12rpx;
width: 700rpx;
margin: 20rpx auto;
padding: 10rpx 0;
.title {
font-weight: bold;
font-size: 30rpx;
padding: 20rpx;
display: flex;
justify-content: space-between;
}
.xieyi {
padding: 20rpx;
font-size: 26rpx;
}
}
.submit {
border-radius: 12rpx;
width: 700rpx;
margin: 20rpx auto;
padding: 10rpx 0;
}
.xieyi-text{
color: #0000ff;
}
.fenqi{
padding: 30rpx;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
.item{
display: flex;
justify-content: space-between;
padding: 20rpx 0;
}
}
</style>
+601
View File
@@ -0,0 +1,601 @@
<template>
<view class="container p-bottom" :style="appThemeStyle">
<view v-if="order.goodsList.length">
<!-- 实物订单选择配送方式 -->
<block v-if="order.orderType == OrderTypeEnum.PHYSICAL.value">
<!-- 配送方式选项卡 -->
<view v-if="isShowTab" class="swiper-tab dis-flex flex-y-center flex-x-around">
<view class="swiper-tab-item" :class="{ on: curDelivery == DeliveryTypeEnum.EXPRESS.value }"
@click="handleSwichDelivery(DeliveryTypeEnum.EXPRESS.value)">
<text>快递配送</text>
</view>
<view class="swiper-tab-item" :class="{ on: curDelivery == DeliveryTypeEnum.EXTRACT.value }"
@click="handleSwichDelivery(DeliveryTypeEnum.EXTRACT.value)">
<text>上门自提</text>
</view>
</view>
<!-- 快递配送配送地址 -->
<view v-if="curDelivery == DeliveryTypeEnum.EXPRESS.value" @click="onSelectAddress" class="flow-delivery">
<view class="flow-delivery__detail dis-flex flex-y-center">
<view class="detail-location dis-flex">
<text class="iconfont icon-dingwei"></text>
</view>
<view class="detail-content flex-box">
<block v-if="order.address">
<view class="detail-content__title dis-flex">
<text class="f-30">{{ order.address.name }}</text>
<text class="detail-content__title-phone f-28">{{ order.address.phone }}</text>
</view>
<view class="address detail-content__describe">
<text class="region" v-for="(region, idx) in order.address.region" :key="idx">{{ region }}</text>
<text class="detail">{{ order.address.detail }}</text>
</view>
</block>
<block v-else>
<view class="detail-content__describe dis-flex">
<text class="col-6">请选择配送地址</text>
</view>
</block>
</view>
<view class="detail-arrow dis-flex">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
</view>
<!-- 上门自提自提门店 -->
<block v-if="curDelivery == DeliveryTypeEnum.EXTRACT.value">
<view class="flow-delivery" @click="onSelectExtractPoint()">
<view class="flow-delivery__detail dis-flex flex-y-center">
<view class="detail-location dis-flex">
<text class="iconfont icon-dingwei"></text>
</view>
<view class="detail-content flex-box">
<block v-if="order.extractShop.shop_id">
<view class="detail-content__title dis-flex">
<text class="f-30">{{ order.extractShop.shop_name }}</text>
</view>
<view class="detail-content__describe">
<text class="col-7">{{ order.extractShop.region.province }} {{ order.extractShop.region.city }}</text>
<text class="col-7">{{ order.extractShop.region.region }} {{ order.extractShop.address }}</text>
</view>
</block>
<block v-else>
<view class="detail-content__describe dis-flex">
<text class="col-6">请选择自提点</text>
</view>
</block>
</view>
<view class="detail-arrow dis-flex">
<text class="iconfont icon-arrow-right user-orderJtou"></text>
</view>
</view>
</view>
<!-- 自提联系方式 -->
<view class="flow-extract-contact b-f">
<view class="contact-item dis-flex">
<view class="item_label dis-flex flex-x-end flex-y-center">
<text>联系人</text>
</view>
<view class="item_ipt flex-box dis-flex flex-y-center">
<input placeholder="请填写联系人姓名" v-model="linkman"></input>
</view>
</view>
<view class="contact-item dis-flex">
<view class="item_label dis-flex flex-x-end flex-y-center">
<text>联系电话</text>
</view>
<view class="item_ipt flex-box dis-flex flex-y-center">
<input placeholder="请填写联系电话" v-model="phone"></input>
</view>
</view>
</view>
</block>
</block>
<!-- 商品列表 -->
<view class="checkout_list" v-for="(item, index) in order.goodsList" :key="index">
<view class="flow-shopList dis-flex" data-index="index" @click="onTargetGoods(item.goods_id)">
<!-- 商品图片 -->
<view class="flow-list-left">
<image mode="scaleToFill" :src="item.goods_image"></image>
</view>
<view class="flow-list-right flex-box">
<!-- 商品名称 -->
<text class="goods-name twoline-hide">{{ item.goods_name }}</text>
<!-- 商品规格 -->
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in item.skuInfo.goods_props" :key="idx">
<text class="group-name">{{ props.group.name }}: </text>
<text>{{ props.value.name }}</text>
</view>
</view>
<!-- 商品数量和单价 -->
<view class="flow-list-cont dis-flex flex-x-between flex-y-center">
<text class="small">×{{ item.total_num }}</text>
<text class="flow-cont" :class="[item.is_user_grade ? 'price-delete' : '']">{{ item.goods_price }}</text>
</view>
<!-- 会员折扣价 -->
<view v-if="item.is_user_grade" class="grade-price">
<text>会员折扣价{{ item.grade_goods_price }}</text>
</view>
</view>
</view>
</view>
<view class="flow-num-box b-f">
<!-- <text>{{ order.orderTotalNum }}件商品合计</text>
<text class="flow-money col-m">{{ order.orderTotalPrice }}</text> -->
<text>{{ order.orderTotalNum }}件商品</text>
</view>
<!-- 商品金额 -->
<view class="flow-all-money b-f m-top20">
<view class="flow-all-list dis-flex">
<text class="flex-five">订单总金额</text>
<view class="flex-five t-r">
<text class="col-m">{{ order.orderTotalPrice }}</text>
</view>
</view>
<!-- 优惠券 -->
<view class="flow-all-list dis-flex">
<text class="flex-five">优惠券</text>
<view class="flex-five t-r">
<view v-if="order.couponList.length > 0" @click="handleShowPopup()">
<text class="col-m" v-if="order.couponId > 0">-{{ order.couponMoney }}</text>
<text class="col-m" v-else>{{ order.couponList.length }}张优惠券</text>
<text class="right-arrow iconfont icon-arrow-right"></text>
</view>
<text v-else class="">无优惠券可用</text>
</view>
</view>
<!-- 积分抵扣 -->
<view v-if="order.isAllowPoints" class="points flow-all-list dis-flex flex-y-center">
<view class="block-left flex-five" @click="handleShowPoints()">
<text class="title">可用{{ setting.points_name }}抵扣</text>
<text class="iconfont icon-help"></text>
</view>
<view class="flex-five dis-flex flex-x-end flex-y-center">
<text class="points-money col-m">-{{ order.pointsMoney }}</text>
<u-switch v-model="isUsePoints" size="48" active-color="#07c160" @change="getOrderData()"></u-switch>
</view>
</view>
<!-- 配送费用 -->
<view v-if="curDelivery == DeliveryTypeEnum.EXPRESS.value" class="dis-flex flow-all-list">
<text class="flex-five">配送费用</text>
<view class="flex-five t-r">
<view v-if="order.address">
<text class="col-m" v-if="order.isIntraRegion">+{{ order.expressPrice }}</text>
<text v-else>不在配送范围</text>
</view>
<view v-else>
<text class="col-7">请先选择配送地址</text>
</view>
</view>
</view>
</view>
<!-- 买家留言 -->
<view class="flow-all-money b-f m-top20">
<view class="ipt-wrapper dis-flex flow-all-list">
<input v-model="remark" placeholder="选填:买家留言(50字以内)"></input>
</view>
</view>
<!-- 提交订单 -->
<view class="flow-fixed-footer b-f m-top10">
<view class="dis-flex chackout-box">
<view class="chackout-left pl-12">实付款
<text class="col-m">{{ order.orderPayPrice }}</text>
</view>
<view class="chackout-right" @click="onSubmitOrder()">
<view class="flow-btn f-32" :class="{ disabled }">提交订单</view>
</view>
</view>
</view>
<!-- 积分说明弹窗 -->
<u-modal v-model="showPoints" :title="`${setting.points_name}说明`">
<scroll-view class="points-content" :scroll-y="true">
<text>{{ setting.points_describe }}</text>
</scroll-view>
</u-modal>
<!-- 优惠券弹出框 -->
<u-popup v-model="showPopup" mode="bottom">
<view class="popup__coupon">
<view class="coupon__title f-30">选择优惠券</view>
<!-- 优惠券列表 -->
<view class="coupon-list">
<scroll-view :scroll-y="true" style="height: 565rpx;">
<view class="coupon-item" v-for="(item, index) in order.couponList" :key="index">
<view class="item-wrapper" :class="[item.is_apply ? 'color-' + CouponColors[index % CouponColors.length] : 'color-gray']"
@click="handleSelectCoupon(index)">
<view class="coupon-type">{{ CouponTypeEnum[item.coupon_type].name }}</view>
<view class="tip dis-flex flex-dir-column flex-x-center">
<view v-if="item.coupon_type == CouponTypeEnum.FULL_DISCOUNT.value">
<text class="f-30"></text>
<text class="money">{{ item.reduce_price }}</text>
</view>
<text class="money" v-if="item.coupon_type == CouponTypeEnum.DISCOUNT.value">{{ item.discount }}</text>
<text class="pay-line">{{ item.min_price }}元可用</text>
</view>
<view class="split-line"></view>
<view class="content dis-flex flex-dir-column flex-x-between">
<view class="title">{{ item.name }}</view>
<view class="bottom dis-flex flex-y-center">
<view class="time flex-box">
<block v-if="item.start_time === item.end_time">{{ item.start_time }} 当天有效</block>
<block v-else>{{ item.start_time }}~{{ item.end_time }}</block>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 不使用优惠券 -->
<view class="coupon__do_not dis-flex flex-y-center flex-x-center">
<view class="control dis-flex flex-y-center flex-x-center" @click="handleNotUseCoupon()">
<text class="f-26">不使用优惠券</text>
</view>
</view>
</view>
</u-popup>
</view>
<u-toast ref="uToast" />
</view>
</template>
<script>
import * as Verify from '@/utils/verify'
import * as CheckoutApi from '@/api/checkout'
import * as SharpCheckoutApi from '@/api/sharp/checkout'
import * as BargainCheckoutApi from '@/api/bargain/checkout'
import * as GrouponCheckoutApi from '@/api/groupon/checkout'
import { CouponTypeEnum } from '@/common/enum/coupon'
import { OrderTypeEnum, DeliveryTypeEnum } from '@/common/enum/order'
const CouponColors = ['red', 'blue', 'violet', 'yellow']
// 根据指定mode获取对应的api类
const getCheckoutApi = (mode) => {
const apiEnum = {
buyNow: CheckoutApi,
cart: CheckoutApi,
bargain: BargainCheckoutApi,
sharp: SharpCheckoutApi,
groupon: GrouponCheckoutApi
}
return apiEnum[mode]
}
// 根据指定mode获取param
const getModeParam = (mode, options) => {
const param = {}
// 结算模式: 立即购买
if (mode === 'buyNow') {
param.goodsId = options.goodsId
param.goodsNum = options.goodsNum
param.goodsSkuId = options.goodsSkuId
}
// 结算模式: 购物车
if (mode === 'cart') {
param.cartIds = options.cartIds
}
// 结算模式: 砍价活动
if (mode === 'bargain') {
param.taskId = options.taskId
param.goodsSkuId = options.goodsSkuId
}
// 结算模式: 整点秒杀
if (mode === 'sharp') {
param.activeTimeId = options.activeTimeId
param.sharpGoodsId = options.sharpGoodsId
param.goodsSkuId = options.goodsSkuId
param.goodsNum = options.goodsNum
}
// 结算模式: 多人拼团
if (mode === 'groupon') {
param.grouponGoodsId = options.grouponGoodsId
param.taskId = options.taskId
param.goodsSkuId = options.goodsSkuId
param.goodsNum = options.goodsNum
param.stepPeople = options.stepPeople
}
return param
}
export default {
data() {
return {
// 枚举类
OrderTypeEnum,
DeliveryTypeEnum,
CouponTypeEnum,
// 当前页面参数
options: {},
// 配送方式
isShowTab: false,
DeliveryTypeEnum,
curDelivery: null,
// 自提信息
selectedShopId: 0, // 选择的门店ID
linkman: '', // 自提联系人
phone: '', // 自提联系电话
// 优惠券颜色组
CouponColors,
// 选择的优惠券
selectCouponId: 0,
// 是否使用积分抵扣
isUsePoints: false,
// 买家留言
remark: '',
// 禁用submit按钮
disabled: false,
// 是否显示积分说明
showPoints: false,
// 是否显示优惠券弹窗
showPopup: false,
// 订单信息 (从后端api中获取)
order: {
// 商品列表
goodsList: [],
// 优惠券列表
couponList: [],
// 是否存在收货地址
existAddress: false,
// 默认收货地址
address: null,
// 是否存在收货地址
existAddress: false,
// 当前用户收货城市是否存在配送规则中
isIntraRegion: true,
// 是否存在错误
hasError: false,
// 错误信息
errorMsg: '',
},
// 个人信息
personal: {},
// 商城设置
setting: {}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.options = options
// 注册全局事件订阅: 选择自提门店
uni.$on('syncSelectedId', selectedId => {
this.selectedShopId = selectedId
})
},
/**
* 生命周期函数--监听页面的卸载
*/
onUnload() {
// 卸载全局事件订阅: 选择自提门店
uni.$off('syncSelectedId')
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
// 获取当前订单信息
this.getOrderData()
},
methods: {
// 获取订单数据
getOrderData() {
const app = this
const { options: { mode } } = app
// 请求的参数
const params = app.getRequestParam()
// 请求api
getCheckoutApi(mode)
.order(mode, params)
.then(result => app.initData(result.data))
.catch(err => err)
},
// 初始化数据
initData({ order, setting, personal }) {
const app = this
app.order = order
app.personal = personal
app.setting = setting
// 显示错误信息
if (order.hasError) {
app.showToast(order.errorMsg, 3000)
}
// 当前选择的配送方式
app.curDelivery = order.delivery
// 如果只有一种配送方式则不显示选项卡
app.isShowTab = setting.deliveryType.length > 1
// 上门自提联系信息
if (app.linkman === '') {
app.linkman = order.lastExtract.linkman
}
if (app.phone === '') {
app.phone = order.lastExtract.phone
}
},
// 获取api请求的参数
getRequestParam() {
const app = this
const { options } = app
// 结算模式的固定参数
const modeParam = getModeParam(options.mode, options)
// 订单结算参数(用户选择)
const orderParam = {
delivery: app.curDelivery || 0,
shopId: app.selectedShopId || 0,
couponId: app.selectCouponId || 0,
isUsePoints: app.isUsePoints ? 1 : 0,
}
return { ...orderParam, ...modeParam }
},
// 切换配送方式
handleSwichDelivery(key) {
this.curDelivery = key
this.getOrderData()
},
// 显示积分说明
handleShowPoints() {
this.showPoints = true
},
// 显示优惠券弹窗
handleShowPopup() {
this.showPopup = true
},
// 选择优惠券
handleSelectCoupon(index) {
const app = this
const { couponList } = app.order
// 当前选择的优惠券
const couponItem = couponList[index]
// 判断是否在适用范围
if (!couponItem.is_apply) {
app.showToast(couponItem.not_apply_info)
return
}
// 记录选中的优惠券id
app.selectCouponId = couponItem.user_coupon_id
// 重新获取订单信息
app.getOrderData()
// 隐藏优惠券弹层
app.showPopup = false
},
// 不使用优惠券
handleNotUseCoupon() {
const app = this
app.selectCouponId = 0
// 重新获取订单信息
app.getOrderData()
// 隐藏优惠券弹层
app.showPopup = false
},
// 快递配送:选择收货地址
onSelectAddress() {
this.$navTo('pages/address/index', { from: 'checkout' })
},
// 上门自提:选择自提点
onSelectExtractPoint() {
this.$navTo('pages/shop/extract', { selectedId: this.selectedShopId })
},
// 跳转到商品详情页
onTargetGoods(goodsId) {
this.$navTo('pages/goods/detail', { goodsId })
},
// 订单提交
onSubmitOrder() {
const app = this
if (app.disabled) {
return false
}
// 表单验证
if (!app.onVerifyFrom()) {
return false
}
// 按钮禁用
app.disabled = true
// 请求api
getCheckoutApi(app.options.mode)
.submit(app.options.mode, app.getFormData())
.then(result => {
// 订单创建成功: 跳转到订单支付页
const orderId = result.data.orderId
setTimeout(() => {
this.$navTo('pages/checkout/cashier/index', { orderId }, 'redirectTo')
}, 100)
})
.catch(res => app.showToast(res.errMsg, 3000))
.finally(() => setTimeout(() => app.disabled = false, 800))
},
// 跳转到我的订单(等待1秒)
navToMyOrder() {
setTimeout(() => {
this.$navTo('pages/order/index', {}, 'redirectTo')
}, 1000)
},
// 表单提交的数据
getFormData() {
const app = this
const { options } = app
// 表单数据
const form = {
delivery: app.curDelivery,
couponId: app.selectCouponId || 0,
shopId: app.selectedShopId || 0,
linkman: app.linkman,
phone: app.phone,
isUsePoints: app.isUsePoints ? 1 : 0,
remark: app.remark || '',
}
// 获取不同模式的参数
const modeParam = getModeParam(options.mode, options)
return { ...form, ...modeParam }
},
// 表单验证
onVerifyFrom() {
const app = this
if (app.hasError) {
app.showToast(app.errorMsg, 3000)
return false
}
// 验证自提填写的联系方式
if (app.curDelivery == DeliveryTypeEnum.EXTRACT.value) {
app.linkman = app.linkman.trim()
app.phone = app.phone.trim()
if (app.selectedShopId <= 0) {
app.showToast('请选择自提的门店')
return false
}
if (Verify.isEmpty(app.linkman)) {
app.showToast('请填写自提联系人')
return false
}
if (Verify.isEmpty(app.phone)) {
app.showToast('请填写自提联系电话')
return false
}
if (!Verify.isPhone(app.phone)) {
app.showToast('请输入正确的联系电话')
return false
}
}
return true
},
// 显示toast信息
showToast(title, duration = 2000) {
this.$refs.uToast.show({ title, duration })
}
}
}
</script>
<style lang="scss" scoped>
@import "./style.scss";
</style>
+475
View File
@@ -0,0 +1,475 @@
// 配送信息
.flow-delivery {
padding: 34rpx 30rpx;
background: #fff url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANYAAAANCAYAAADVGpDCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3Yjk4M2ExYy1jMDhkLTQ1OTktYTI0Ny1kZjNjYzdiYTQ5ZTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDQwNkY3RkU5N0NGMTFFNUI3N0M4NTU4MzM2RjlFODIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDQwNkY3RkQ5N0NGMTFFNUI3N0M4NTU4MzM2RjlFODIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowNzgwZWI1NS03OGFhLTQzOTUtODQ4OC1lOWI5YmVlYTY1ZDciIHN0UmVmOmRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDo1OTRiYzUyMy1jMzc3LTExNzgtYTdkZS04NGY3YmM1ZGIxMDMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz556PLxAAACBElEQVR42tyaSyhEYRTHP48imlKibDQeSSlkSlEWLCRFsZNH5FE2FqQ8ErIRC9lIkTwXSpMkWWChhEJCSnlkoUZGSsr78f98n43CMFPu/Z/6NZuZ2zn33/+cb869XkmLx8IDEQaGQJbgiytQDSY3MyL+LYnL/HxPXSoHDIJQQq2WQQk4Dbbb/yUB29LJ+6e3B66VB3ZITbUIEqSpCGoJBP1ghtBUD6ARpEtTGSEhXzd+awE9oJzQUPegWdf3QlBPMhgDMYRa7YNisGWkpP5qrBQtVBShUHugUE9hs4fUtwG0utlEjRivoA/Ug1sj3vjffr8FNJEK1auPFHcE9UTq5pdK2PwcoAzMG7mjuRrRYEIfK9jiDJSCBZJ6ynSTsBBqNQ0qgdPISbq6vJCFbJOaagrEk5gqWNczRGiqG1Ah1LLMafRkf5pYIUKtZnMJDXUNasAIST2ZYFioRx9ssQaKwJFZEv5uYmWDXVJTrYBEElP562PfPKGpnkAbSDOTqb6aWAGgW6iHol5kQj2CdtAJngnqkc1hHMQRNr9DPaXWzZj8Z2PZtFCxhEIdaKE2CGqRJ4060AH8CLUaALX6f5VpBZLhI9SaeZXQVHKNLt84SCIxVbhQi5YuQlNd6OVElZlN9TGxrGBUn2PZ4lyoTdIsST0FQj0UDSLUak6ot3gcBLVY3wQYAJoVXxmNERajAAAAAElFTkSuQmCC') bottom left repeat-x;
background-size: 120rpx auto;
margin-bottom: 25rpx;
.detail-location {
font-size: 36rpx;
}
.detail-content {
padding: 0 20rpx;
.detail-content__title-phone {
margin-left: 10rpx;
}
.detail-content__describe {
font-size: 28rpx;
color: #777;
}
}
.detail-content__title {
margin-bottom: 6rpx;
}
}
// 买家留言
.flow-all-money {
.ipt-wrapper {
input {
font-size: 28rpx;
width: 100%;
height: 75rpx;
}
}
}
// 商品列表
.checkout_list {
padding: 20rpx 30rpx 4rpx 30rpx;
background: #fff;
border-bottom: 1rpx solid rgb(248, 248, 248);
.flow-shopList {
padding: 5rpx 0 10rpx;
border-bottom: 1rpx solid rgb(248, 248, 248);
&:last-child {
border-bottom: 0;
}
}
}
.flow-header-left {
padding-left: 90rpx;
}
/* 会员价 */
.flow-shopList {
.flow-list-right {
.flow-cont {
&.price-delete {
font-size: 26rpx;
color: #777;
text-decoration: line-through;
}
}
}
.grade-price {
padding-top: 8rpx;
font-size: 28rpx;
color: $main-bg;
text-align: right;
}
.goods-name{
font-size: 28rpx;
color: #333;
}
}
/* 优惠券选择 */
.popup__coupon {
background: #fff;
box-sizing: border-box;
padding: 30rpx;
.coupon__do_not {
.control {
width: 90%;
height: 72rpx;
color: #888;
border: 1rpx solid #e3e3e3;
border-radius: 10rpx;
}
}
.coupon__title {
text-align: center;
margin-bottom: 30rpx;
}
.coupon-list {
/* #ifdef H5 */
max-width: 1120rpx;
margin: 0 auto;
/* #endif */
margin-bottom: 24rpx;
}
.coupon-item {
overflow: hidden;
margin-bottom: 22rpx;
}
.item-wrapper {
width: 100%;
display: flex;
background: #fff;
border-radius: 8rpx;
color: #fff;
height: 180rpx;
.coupon-type {
position: absolute;
top: 0;
right: 0;
z-index: 10;
width: 128rpx;
padding: 6rpx 0;
background: #a771ff;
font-size: 20rpx;
text-align: center;
color: #ffffff;
transform: rotate(45deg);
transform-origin: 64rpx 64rpx;
}
&.color-blue {
background: linear-gradient(-125deg, #57bdbf, #2f9de2);
}
&.color-red {
background: linear-gradient(-128deg, #ff6d6d, #ff3636);
}
&.color-violet {
background: linear-gradient(-113deg, #ef86ff, #b66ff5);
.coupon-type {
background: #55b5ff;
}
}
&.color-yellow {
background: linear-gradient(-141deg, #f7d059, #fdb054);
}
&.color-gray {
background: linear-gradient(-113deg, #bdbdbd, #a2a1a2);
.coupon-type {
background: #9e9e9e;
}
}
.content {
flex: 1;
padding: 30rpx 20rpx;
border-radius: 16rpx 0 0 16rpx;
.title {
font-size: 32rpx;
}
.bottom {
.time {
font-size: 24rpx;
}
.receive {
height: 46rpx;
width: 122rpx;
border: 1rpx solid #fff;
border-radius: 30rpx;
color: #fff;
font-size: 24rpx;
display: flex;
justify-content: center;
align-items: center;
&.state {
border: none;
}
}
}
}
.tip {
position: relative;
flex: 0 0 32%;
text-align: center;
border-radius: 0 16rpx 16rpx 0;
.money {
font-weight: bold;
font-size: 52rpx;
}
.pay-line {
font-size: 22rpx;
}
}
.split-line {
position: relative;
flex: 0 0 0;
border-left: 4rpx solid #fff;
margin: 0 10rpx 0 6rpx;
background: #fff;
&:before,
{
border-radius: 0 0 16rpx 16rpx;
top: 0;
}
&:after {
border-radius: 16rpx 16rpx 0 0;
bottom: 0;
}
&:before,
&:after {
content: '';
position: absolute;
width: 24rpx;
height: 12rpx;
background: #f7f7f7;
left: -14rpx;
z-index: 1;
}
}
}
}
/* 积分抵扣 */
.points {
.title {
margin-right: 5rpx;
}
.icon-help {
font-size: 28rpx;
}
.points-money {
margin-right: 20rpx;
}
}
// 商品规格
.goods-props {
padding-top: 10rpx;
font-size: 24rpx;
color: #999;
.goods-props-item {
float: left;
.group-name {
margin-right: 6rpx;
}
}
}
// 右侧箭头
.right-arrow {
margin-left: 16rpx;
// color: #777;
font-size: 26rpx;
}
// 底部操作栏
.flow-fixed-footer {
position: fixed;
bottom: var(--window-bottom);
left: var(--window-left);
right: var(--window-right);
// width: 100%;
background: #fff;
border-top: 1px solid #eee;
z-index: 11;
// 设置ios刘海屏底部横线安全区域
padding-bottom: calc(constant(safe-area-inset-bottom) + var(--window-bottom));
padding-bottom: calc(env(safe-area-inset-bottom) + var(--window-bottom));
.chackout-left {
font-size: 28rpx;
line-height: 92rpx;
color: #777;
flex: 4;
padding-left: 12px;
}
.chackout-right {
font-size: 34rpx;
flex: 2;
}
// 提交按钮
.flow-btn {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
text-align: center;
line-height: 92rpx;
display: block;
font-size: 28rpx;
// 禁用按钮
&.disabled {
opacity: 0.6;
}
}
}
// 积分说明
.points-content {
padding: 30rpx 48rpx;
font-size: 28rpx;
line-height: 50rpx;
text-align: left;
color: #606266;
height: 620rpx;
box-sizing: border-box;
}
/* 共几件商品 */
.flow-num-box {
font-size: 28rpx;
color: #777;
padding: 16rpx 24rpx;
text-align: right;
}
/* app.scss */
.flow-shopList {
padding: 18rpx 0;
.flow-list-left {
margin-right: 20rpx;
image {
width: 180rpx;
height: 180rpx;
border: 1rpx solid #eee;
background: #fff;
}
}
.flow-list-right {
.flow-cont {
font-size: 28rpx;
color: $main-bg;
}
.small {
font-size: 26rpx;
color: #777;
}
.flow-list-cont {
padding-top: 10rpx;
}
}
}
.flow-all-money {
padding: 0 24rpx;
color: #444;
.flow-all-list {
font-size: 28rpx;
padding: 20rpx 0;
border-bottom: 1rpx solid rgb(248, 248, 248);
}
.flow-all-list:last-child {
border-bottom: none;
}
.flow-all-list-cont {
font-size: 28rpx;
padding: 10rpx 0;
}
.flow-arrow {
justify-content: flex-end;
align-items: center;
}
}
// 选项卡:配送方式
.swiper-tab {
width: 100%;
text-align: center;
height: 85rpx;
background-color: #fff;
border-bottom: 1px solid rgb(248, 248, 248);
display: flex;
align-items: center;
justify-content: space-around;
.swiper-tab-item {
width: 35%;
height: 100%;
font-size: 28rpx;
color: #777;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
border-bottom: 2px solid #ffffff00;
&.on {
color: $main-bg;
border-bottom: 2px solid $main-bg;
}
}
}
// 门店自提联系人
.flow-extract-contact {
padding: 8rpx 24rpx;
font-size: 28rpx;
color: #444;
margin-bottom: 25rpx;
.contact-item {
padding: 20rpx 0;
border-bottom: 1rpx solid rgb(248, 248, 248);
&:last-child {
border-bottom: none;
}
}
.item_label {
margin-right: 26rpx;
width: 150rpx;
}
.item_ipt input {
font-size: 28rpx;
width: 100%;
.input-placeholder {
font-size: 28rpx;
}
}
}
+276
View File
@@ -0,0 +1,276 @@
<template>
<view class="container" :style="appThemeStyle">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption" @up="upCallback">
<!-- tab栏 -->
<u-tabs :list="tabs" :is-scroll="false" :current="curTab" :active-color="appTheme.mainBg" :duration="0.2" @change="onChangeTab" />
<!-- 商品评价列表 -->
<view class="comment-list">
<view class="comment-item" v-for="(item, index) in list.data" :key="index">
<view class="item-head">
<!-- 用户信息 -->
<view class="user-info">
<avatar-image class="user-avatar" :url="item.user.avatar_url" :width="50" />
<text class="user-name f-26">{{ item.user.nick_name }}</text>
</view>
<!-- 评星 -->
<u-rate active-color="#f4a213" :current="rates[item.score]" :disabled="true" />
<!-- 评价日期-->
<view class="flex-box f-22 col-9 t-r">{{ item.create_time }}</view>
</view>
<!-- 评价内容 -->
<view class="item-content m-top20">
<text class="f-26">{{ item.content }}</text>
</view>
<!-- 评价图片 -->
<view class="images-list clearfix" v-if="item.images.length">
<view class="image-preview" v-for="(image, imgIdx) in item.images" :key="imgIdx">
<image class="image" mode="aspectFill" :src="image.image_url" @click="onPreviewImages(index, imgIdx)">
</image>
</view>
</view>
<!-- 商品规格 -->
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in item.orderGoods.goods_props" :key="idx">
<text class="group-name">{{ props.group.name }}: </text>
<text>{{ props.value.name }}</text>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import AvatarImage from '@/components/avatar-image'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import * as CommentApi from '@/api/comment'
const pageSize = 15
const tabs = [{
name: `全部`,
scoreType: -1
}, {
name: `好评`,
scoreType: 10
}, {
name: `中评`,
scoreType: 20
}, {
name: `差评`,
scoreType: 30
}]
export default {
components: {
MescrollBody,
AvatarImage
},
mixins: [MescrollMixin],
data() {
return {
// 当前商品ID
goodsId: null,
// 当前标签索引
curTab: 0,
// 评价列表数据
list: getEmptyPaginateObj(),
// 评价总数量
total: { all: 0, negative: 0, praise: 0, review: 0 },
// 评星数据转换
rates: { 10: 5, 20: 3, 30: 1 },
// 标签栏数据
tabs,
// 上拉加载配置
upOption: {
// 首次自动执行
auto: true,
// 每页数据的数量; 默认10
page: { size: pageSize },
// 数量要大于4条才显示无更多数据
noMoreSize: 4,
// 空布局
empty: {
tip: '亲,暂无相关商品评价'
}
},
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 记录属性值
this.goodsId = options.goodsId
// 获取指定评分总数
this.getTotal()
},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
// 设置列表数据
app.getCommentList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// 加载评价列表数据
getCommentList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
CommentApi.list(app.goodsId, { scoreType: app.getScoreType(), page: pageNo }, { load: false })
.then(result => {
// 合并新数据
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
})
},
// 评分类型
getScoreType() {
return this.tabs[this.curTab].scoreType
},
// 获取指定评分总数
getTotal() {
const app = this
CommentApi.total(app.goodsId)
.then(result => {
// tab标签内容
const total = result.data.total
app.getTabs(total)
})
},
// 获取tab标签内容
getTabs(total) {
const tabs = this.tabs
tabs[0].name = `全部(${total.all})`
tabs[1].name = `好评(${total.praise})`
tabs[2].name = `中评(${total.review})`
tabs[3].name = `差评(${total.negative})`
},
// 切换标签项
onChangeTab(index) {
const app = this
// 设置当前选中的标签
app.curTab = index
// 刷新评价列表
app.onRefreshList()
},
// 刷新评价列表
onRefreshList() {
this.list = getEmptyPaginateObj()
setTimeout(() => {
this.mescroll.resetUpScroll()
}, 120)
},
// 预览评价图片
onPreviewImages(dataIdx, imgIndex) {
const app = this
const images = app.list.data[dataIdx].images
const imageUrls = images.map(item => item.image_url)
uni.previewImage({
current: imageUrls[imgIndex],
urls: imageUrls
})
}
}
}
</script>
<style lang="scss" scoped>
.comment-item {
padding: 30rpx;
box-sizing: border-box;
border-bottom: 1rpx solid #f7f7f7;
background: #fff;
}
.item-head {
display: flex;
align-items: center;
// 用户信息
.user-info {
margin-right: 15rpx;
display: flex;
align-items: center;
.user-avatar {
margin-right: 15rpx;
}
.user-name {
color: #999;
}
}
}
// 评价内容
.item-content {
font-size: 30rpx;
color: #333;
margin: 16rpx 0;
}
// 评价图片
.images-list {
&::after {
clear: both;
content: " ";
display: table;
}
.image-preview {
float: left;
margin-bottom: 15rpx;
margin-right: 15rpx;
&:nth-child(3n+0) {
margin-right: 0;
}
.image {
display: block;
width: 220rpx;
height: 220rpx;
}
}
}
// 商品规格
.goods-props {
font-size: 24rpx;
color: #999;
.goods-props-item {
float: left;
.group-name {
margin-right: 6rpx;
}
}
}
</style>
+251
View File
@@ -0,0 +1,251 @@
<template>
<view class="container">
<view v-if="list.length" class="coupon-list">
<view class="coupon-item" v-for="(item, index) in list" :key="index">
<view class="item-wrapper"
:class="[ item.state.value ? 'color-' + color[index % color.length] : 'color-gray' ]">
<view class="coupon-type">{{ CouponTypeEnum[item.coupon_type].name }}</view>
<view class="tip dis-flex flex-dir-column flex-x-center">
<view v-if="item.coupon_type == CouponTypeEnum.FULL_DISCOUNT.value">
<text class="f-30"></text>
<text class="money">{{ item.reduce_price }}</text>
</view>
<text class="money" v-if="item.coupon_type == CouponTypeEnum.DISCOUNT.value">{{ item.discount }}</text>
<text class="pay-line">{{ item.min_price }}元可用</text>
</view>
<view class="split-line"></view>
<view class="content dis-flex flex-dir-column flex-x-between">
<view class="title oneline-hide">{{ item.name }}</view>
<view class="bottom dis-flex flex-y-center">
<view class="time flex-box">
<text v-if="item.expire_type == 10">领取{{ item.expire_day }}天内有效</text>
<text v-if="item.expire_type == 20">
<block v-if="item.start_time === item.end_time">{{ item.start_time }} 当天有效</block>
<block v-else>{{ item.start_time }}~{{ item.end_time }}</block>
</text>
</view>
<view class="receive" v-if="item.state.value" @click="receive(item.coupon_id)">
<text>立即领取</text>
</view>
<view v-else class="receive state">
<text>{{ item.state.text }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<empty v-if="!list.length" :isLoading="isLoading" />
</view>
</template>
<script>
import * as CouponApi from '@/api/coupon'
import * as MyCouponApi from '@/api/myCoupon'
import { CouponTypeEnum } from '@/common/enum/coupon'
import Empty from '@/components/empty'
const color = ['red', 'blue', 'violet', 'yellow']
export default {
components: {
Empty
},
data() {
return {
// 枚举类
CouponTypeEnum,
// 颜色组
color,
// 优惠券列表
list: [],
// 正在加载中
isLoading: true
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 获取优惠券列表
this.getCouponList()
},
methods: {
/**
* 获取优惠券列表
* @param {bool} load 是否显示loading弹窗
*/
getCouponList(load = true) {
const app = this
app.isLoading = true
CouponApi.list({}, { load })
.then(result => {
app.list = result.data.list
})
.finally(() => app.isLoading = false)
},
// 立即领取
receive(couponId) {
const app = this
MyCouponApi.receive(couponId)
.then(result => {
// 显示领取成功提示
app.$success(result.message)
// 刷新优惠券列表
app.getCouponList(false)
})
}
}
}
</script>
<style lang="scss" scoped>
.coupon-list {
padding: 20rpx;
}
.coupon-item {
position: relative;
overflow: hidden;
margin-bottom: 22rpx;
}
.item-wrapper {
width: 100%;
display: flex;
background: #fff;
border-radius: 8rpx;
color: #fff;
height: 180rpx;
.coupon-type {
position: absolute;
top: 0;
right: 0;
z-index: 10;
width: 128rpx;
padding: 3px 0;
background: #a771ff;
font-size: 20rpx;
text-align: center;
color: #ffffff;
transform: rotate(45deg);
transform-origin: 64rpx 64rpx;
}
&.color-blue {
background: linear-gradient(-125deg, #57bdbf, #2f9de2);
}
&.color-red {
background: linear-gradient(-128deg, #ff6d6d, #ff3636);
}
&.color-violet {
background: linear-gradient(-113deg, #ef86ff, #b66ff5);
.coupon-type {
background: #55b5ff;
}
}
&.color-yellow {
background: linear-gradient(-141deg, #f7d059, #fdb054);
}
&.color-gray {
background: linear-gradient(-113deg, #bdbdbd, #a2a1a2);
.coupon-type {
background: #9e9e9e;
}
}
.content {
flex: 1;
padding: 30rpx 20rpx;
border-radius: 8px 0 0 8px;
.title {
width: 400rpx;
font-size: 32rpx;
}
.bottom {
.time {
font-size: 24rpx;
}
.receive {
height: 46rpx;
width: 122rpx;
border: 1px solid #fff;
border-radius: 30rpx;
color: #fff;
font-size: 24rpx;
display: flex;
justify-content: center;
align-items: center;
&.state {
border: none;
}
}
}
}
.tip {
position: relative;
flex: 0 0 32%;
text-align: center;
border-radius: 0 8px 8px 0;
.money {
font-weight: bold;
font-size: 52rpx;
}
.pay-line {
font-size: 22rpx;
}
}
.split-line {
position: relative;
flex: 0 0 0;
border-left: 4rpx solid #fff;
margin: 0 5px 0 3px;
background: #fff;
&:before,
{
border-radius: 0 0 16rpx 16rpx;
top: 0;
}
&:after {
border-radius: 16rpx 16rpx 0 0;
bottom: 0;
}
&:before,
&:after {
content: '';
position: absolute;
width: 24rpx;
height: 12rpx;
background: #f7f7f7;
left: -14rpx;
z-index: 1;
}
}
}
</style>
+120
View File
@@ -0,0 +1,120 @@
<template>
<view class="container" :style="appThemeStyle">
<!-- 店铺页面组件 -->
<Page :items="items" />
</view>
</template>
<script>
import * as Api from '@/api/page'
import Page from '@/components/page'
const App = getApp()
export default {
components: {
Page
},
data() {
return {
// 页面参数
options: {},
// 页面属性
page: {},
// 页面元素
items: []
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 当前页面参数
this.options = options
// 加载页面数据
this.getPageData()
},
methods: {
/**
* 加载页面数据
* @param {Object} callback
*/
getPageData(callback) {
const app = this
const pageId = app.options.pageId || 0
Api.detail(pageId)
.then(result => {
// 设置页面数据
const { data: { pageData } } = result
app.page = pageData.page
app.items = pageData.items
// 设置顶部导航栏栏
app.setPageBar();
})
.finally(() => callback && callback())
},
/**
* 设置顶部导航栏
*/
setPageBar() {
const { page } = this
// 设置页面标题
uni.setNavigationBarTitle({
title: page.params.title
})
// 设置navbar标题、颜色
uni.setNavigationBarColor({
frontColor: page.style.titleTextColor === 'white' ? '#ffffff' : '#000000',
backgroundColor: page.style.titleBackgroundColor
})
}
},
/**
* 下拉刷新
*/
onPullDownRefresh() {
// 获取首页数据
this.getPageData(() => {
uni.stopPullDownRefresh()
})
},
/**
* 分享当前页面
*/
onShareAppMessage() {
const app = this
const { page } = app
return {
title: page.params.share_title,
path: "/pages/index/index?" + app.$getShareUrlParams()
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
const app = this
const { page } = app
return {
title: page.params.share_title,
path: "/pages/index/index?" + app.$getShareUrlParams()
}
}
}
</script>
<style lang="scss" scoped>
.container {
background: #fff;
}
</style>
+316
View File
@@ -0,0 +1,316 @@
<template>
<view v-if="!isLoading" class="container b-f">
<!-- 头部背景图 -->
<view class="dealer-bg">
<image class="image" mode="widthFix" :src="background"></image>
</view>
<!-- 等待审核 -->
<view v-if="isApplying" class="dealer-boot dis-flex flex-dir-column flex-y-center">
<view class="boot__msg f-30 dis-flex flex-dir-column flex-y-center">
<text class="msg__icon iconfont icon-shenhezhong"></text>
<text class="msg__content m-top20 f-29 col-80">{{ words.wait_audit.value }}</text>
</view>
<!-- 去商城逛逛 -->
<view class="boot__submit form-submit dis-flex flex-x-center">
<button class="button" @click="$navTo('pages/index/index')">{{ words.goto_mall.value }}</button>
</view>
</view>
<!-- 填写申请信息 -->
<view v-else class="dis-flex flex-dir-column flex-y-center">
<view class="widget-form b-f m-top20 dis-flex flex-dir-column">
<view class="form-title f-30">{{ words.title.value }}</view>
<view class="form-box dis-flex flex-dir-column">
<form @submit="handleSubmit">
<view class="form-field dis-flex flex-y-center">
<view class="field-label">邀请人</view>
<view class="field-input">
<text>{{ refereeName }}请核对</text>
</view>
</view>
<view class="form-field dis-flex flex-y-center">
<view class="field-label">姓名</view>
<view class="field-input flex-box">
<input class="input" name="name" placeholder="请输入真实姓名"></input>
</view>
</view>
<view class="form-field dis-flex flex-y-center">
<view class="field-label">手机号</view>
<view class="field-input flex-box">
<input class="input" name="mobile" placeholder="请输入手机号"></input>
</view>
</view>
<!-- 申请协议 -->
<view class="form-license dis-flex flex-x-center flex-y-center">
<view class="license-radio dis-flex flex-y-center" @click="isRead = !isRead">
<text class="license-icon f-38 iconfont icon-radio" :class="[isRead ? 'c-violet' : 'col-bb']"></text>
<text class="f-28 col-80">我已阅读并了解</text>
</view>
<text @click="handleShowLicense()" class="f-28 c-violet">{{ words.license.value }}</text>
</view>
<!-- 立即申请 -->
<view class="form-submit dis-flex flex-x-center">
<button formType="submit" :disabled="disabled">{{ words.submit.value }}</button>
</view>
</form>
</view>
</view>
</view>
<!-- 入驻协议弹窗 -->
<u-modal v-model="showLicense" title="申请协议">
<scroll-view class="pops-content" :scroll-y="true">
<text>{{ license }}</text>
</scroll-view>
</u-modal>
</view>
</template>
<script>
import * as ApplyApi from '@/api/dealer/apply'
import SettingModel from '@/common/model/dealer/Setting'
export default {
data() {
return {
// 正在加载
isLoading: true,
// 当前是否为分销商
isDealer: undefined,
// 当前是否在申请中
isApplying: undefined,
// 推荐人昵称
refereeName: undefined,
// 文字设置
words: undefined,
// 背景图
background: undefined,
// 入驻协议
license: undefined,
// 入驻协议阅读状态
isRead: false,
// 显示入驻协议弹窗
showLicense: false,
// 按钮禁用
disabled: false
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getSetting()
this.getApplyStatus()
},
methods: {
// 获取分销设置
getSetting() {
const app = this
SettingModel.data()
.then(setting => {
// 赋值属性
app.words = setting.words.apply.words
app.background = setting.background.apply
app.license = setting.license.license
// 设置当前页面标题
app.setPageTitle(setting.words.apply.title)
})
},
// 分销商申请状态
getApplyStatus() {
const app = this
app.isLoading = true
ApplyApi.status()
.then(result => {
const data = result.data
app.isDealer = data.isDealer
app.isApplying = data.isApplying
app.refereeName = data.refereeName
})
.finally(() => app.isLoading = false)
},
// 设置当前页面标题
setPageTitle(title) {
uni.setNavigationBarTitle({ title: title.value })
},
// 切换支付选项
handleChecked(value) {
this.payment = value
},
// 显示入驻协议弹窗
handleShowLicense() {
this.showLicense = true
},
// 表单提交
handleSubmit({ detail }) {
const app = this
// 表单验证
if (!app.onValidation(detail.value)) {
return false
}
// 确认提交
app.disabled = true
ApplyApi.submit({ form: detail.value })
.then(result => {
app.$toast(result.message)
setTimeout(() => uni.navigateBack(), 1200)
})
.finally(() => app.disabled = false)
},
// 表单验证
onValidation(data) {
const app = this
// 验证可提现佣金
if (!data.name) {
app.$error('请填写姓名')
return false
}
// 验证手机号
if (!/^\+?\d[\d -]{8,12}\d/.test(data.mobile)) {
app.$error('手机号格式不正确')
return false
}
// 验证是否阅读协议
if (!app.isRead) {
app.$error('请先阅读分销商申请协议')
return false
}
return true
},
}
}
</script>
<style>
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
.c-violet {
color: #786cff;
}
.col-80 {
color: #808080;
}
.col-bb {
color: #bbb;
}
.f-38 {
font-size: 38rpx;
}
.dealer-bg {
.image {
width: 100%;
}
}
.widget-form {
position: relative;
width: 700rpx;
box-sizing: border-box;
box-shadow: 0 1rpx 20rpx rgba(0, 0, 0, 0.21);
border-radius: 12rpx;
margin-top: -80rpx;
.form-title {
padding: 0 40rpx;
height: 90rpx;
border-bottom: 1rpx solid #e7e7e7;
display: flex;
align-items: center;
}
.form-box {
padding: 40rpx 35rpx;
.form-field {
height: 80rpx;
margin-bottom: 24rpx;
padding: 10rpx 28rpx;
background-color: #f9f9f9;
box-sizing: border-box;
font-size: 28rpx;
.field-label {
width: 130rpx;
color: #808080;
}
.input {
font-size: 28rpx;
}
}
}
}
.form-license {
.license-icon {
margin-right: 12rpx;
}
}
.form-submit {
margin-top: 40rpx;
button {
font-size: 30rpx;
background: #786cff;
border: 1rpx solid #786cff;
color: white;
border-radius: 50rpx;
padding: 0 120rpx;
&[disabled] {
background: #8e84fc;
border-color: #8e84fc;
color: white;
}
}
}
// 申请协议
.pops-content {
padding: 30rpx 48rpx;
font-size: 28rpx;
line-height: 50rpx;
text-align: left;
color: #606266;
min-height: 320rpx;
max-height: 640rpx;
box-sizing: border-box;
}
// 等待审核
.dealer-boot {
padding: 10rpx 30rpx;
margin-top: 80rpx;
.msg__icon {
font-size: 120rpx;
color: #8e84fc;
}
.boot__submit {
margin-top: 60rpx;
}
}
</style>
+309
View File
@@ -0,0 +1,309 @@
<template>
<view v-if="!isLoading" class="container b-f">
<!-- 分销商中心 -->
<view class="center" v-if="isDealer">
<!-- 头部背景图 -->
<view class="dealer-bg">
<image class="image" mode="widthFix" :src="setting.background"></image>
</view>
<!-- 内容区 -->
<view class="widget-body b-f dis-flex flex-dir-column flex-y-center">
<!-- 用户信息 -->
<view class="widget widget__base m-top20 b-f dis-flex flex-dir-column">
<view class="base__user f-30">
<!-- 用户头像 -->
<avatar-image class="user-avatar" :url="user.avatar_url" :width="150" :borderWidth="4"
:borderColor="`#fff`" />
<view class="user-nickName f-32">{{ user.nick_name }}</view>
<view class="user-referee f-24 col-9">
{{ setting.words.index.words.referee.value }}{{ refereeName }}
</view>
</view>
<view class="base__capital dis-flex flex-dir-column">
<!-- 佣金卡片 -->
<view class="capital-card dis-flex">
<view class="card-left">
<view class="f-28 col-f">
<text space="ensp">{{ setting.words.index.words.money.value }} {{ dealer.money }}</text>
<text class="m-l-10"></text>
</view>
<view class="f-28 col-f">
<text space="ensp">{{ setting.words.index.words.freeze_money.value }} {{ dealer.freeze_money }}</text>
<text class="m-l-10"></text>
</view>
</view>
<view class="card-right flex-box dis-flex flex-x-end flex-y-center">
<view class="withdraw-btn f-26" @click="$navTo('pages/dealer/withdraw/apply')">
{{ setting.words.index.words.withdraw.value }}
</view>
</view>
</view>
<!-- 已提现金额 -->
<view class="capital-already clear">
<view class="already-left f-26 fl">{{ setting.words.index.words.total_money.value }}</view>
<view class="already-right f-26 fr">{{ dealer.total_money }}</view>
</view>
</view>
</view>
<!-- 操作列表 -->
<view class="widget widget__operat clear b-f">
<view class="operat__item" @click="$navTo('pages/dealer/withdraw/list')">
<view class="item__icon">
<text class="iconfont icon-zhangben" style="color:#F9BA21;"></text>
</view>
<view class="item__text f-26">{{ setting.words.withdraw_list.title.value }}</view>
</view>
<view class="operat__item" @click="$navTo('pages/dealer/order')">
<view class="item__icon">
<text class="iconfont icon-dingdan" style="color:#FF7575;"></text>
</view>
<view class="item__text f-26">{{ setting.words.order.title.value }}</view>
</view>
<view class="operat__item" @click="$navTo('pages/dealer/team')">
<view class="item__icon">
<text class="iconfont icon-tuandui" style="color:#59C78E;"></text>
</view>
<view class="item__text f-26">{{ setting.words.team.title.value }}</view>
</view>
<view class="operat__item" @click="$navTo('pages/dealer/poster')">
<view class="item__icon">
<text class="iconfont icon-erweima" style="color:#5fa5ff;"></text>
</view>
<view class="item__text f-26">{{ setting.words.poster.title.value }}</view>
</view>
</view>
</view>
</view>
<!-- 当前不是分销商 -->
<view class="container b-f" v-if="!isDealer">
<view class="no-dealer">
<view class="no-icon dis-flex flex-x-center">
<image src="/static/not-dealer.png"></image>
</view>
<view class="no-msg dis-flex flex-x-center f-30">{{ setting.words.index.words.not_dealer.value }}
</view>
<!-- 立即申请 -->
<view class="no-submit form-submit">
<view class="button" @click="$navTo('pages/dealer/apply')">{{ setting.words.index.words.apply_now.value }}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import AvatarImage from '@/components/avatar-image'
import * as Api from '@/api/dealer'
export default {
components: {
AvatarImage
},
data() {
return {
// 正在加载
isLoading: true,
// 当前用户信息
user: undefined,
// 当前是否为分销商
isDealer: false,
// 当前分销商信息
dealer: undefined,
// 推荐人昵称
refereeName: undefined,
// 分销设置
setting: {
background: undefined,
words: undefined
}
}
},
/**
* 生命周期函数--监听页面加载
*/
onShow(options) {
this.getCenter()
},
methods: {
// 获取分销商中心数据
getCenter() {
const app = this
app.isLoading = true
Api.center()
.then(result => {
// api数据赋值
const data = result.data
app.isDealer = data.isDealer
app.user = data.user
app.dealer = data.dealer
app.refereeName = data.refereeName
app.setting = data.setting
// 设置当前页面标题
app.setPageTitle()
app.isLoading = false
})
},
// 设置当前页面标题
setPageTitle() {
uni.setNavigationBarTitle({
title: this.setting.words.index.title.value
})
}
}
}
</script>
<style>
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
.dealer-bg {
.image {
width: 100%;
}
}
.widget-body {
position: relative;
.widget {
width: 88%;
box-sizing: border-box;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.11);
border-radius: 12rpx;
}
}
.widget__base {
margin-top: -60rpx;
.base__user {
position: relative;
padding: 15rpx 40rpx;
border-bottom: 1rpx solid #e7e7e7;
.user-avatar {
position: absolute;
top: -75rpx;
right: 60rpx;
}
.user-nickName {
margin-top: 30rpx;
margin-bottom: 10rpx;
}
}
.base__capital {
padding: 35rpx;
.capital-card {
height: 200rpx;
padding: 36rpx 0;
background-color: #8e84fc;
border-radius: 10rpx;
box-sizing: border-box;
.card-left {
flex: 1;
display: flex;
justify-content: space-around;
align-items: flex-start;
flex-direction: column;
padding-left: 32rpx;
}
.card-right {
.withdraw-btn {
width: 130rpx;
height: 50rpx;
background: #fff;
color: #8e84fc;
border-radius: 25rpx;
margin-right: 32rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
}
.capital-already {
padding: 20rpx;
padding-bottom: 0;
}
}
}
/* 操作列表 */
.widget__operat {
padding: 50rpx;
margin-top: 40rpx;
.operat__item {
width: 33.33333%;
float: left;
margin-bottom: 50rpx;
text-align: center;
&:last-child {
margin-bottom: 0;
}
.item__icon {
margin-bottom: 8rpx;
font-size: 58rpx;
}
}
}
/* 当前不是分销商 */
.no-dealer {
padding-top: 150rpx;
}
.no-icon {
image {
width: 420rpx;
height: 240rpx;
}
}
.no-msg {
padding: 86rpx 0;
}
.form-submit {
.button {
font-size: 30rpx;
background: #786cff;
border: 1rpx solid #786cff;
color: white;
border-radius: 50rpx;
padding: 22rpx 0;
width: 470rpx;
box-sizing: border-box;
margin: 0 auto;
text-align: center;
&.disabled {
opacity: 0.6;
}
}
}
</style>
+223
View File
@@ -0,0 +1,223 @@
<template>
<view class="container">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption"
@up="upCallback">
<!-- tab栏 -->
<u-tabs :list="tabList" :is-scroll="false" :current="curTab" active-color="#786cff" :duration="0.2"
@change="onChangeTab" />
<!-- 列表数据 -->
<view class="widget-list b-f">
<view class="widget__detail" v-for="(item, index) in list.data" :key="index">
<view class="detail__row dis-flex flex-x-between">
<view class="detail__left f-24">订单号{{ item.order.order_no }}</view>
<view class="detail__right f-24 c-violet">
{{ item.order.state_text }}
</view>
</view>
<view class="detail__row m-top10 dis-flex flex-x-between">
<view class="detail__left dis-flex flex-y-center">
<avatar-image class="user-avatar" :url="item.user.avatar_url" :width="100" :borderWidth="4"
:borderColor="`#fff`" />
<view class="user-info dis-flex flex-dir-column flex-x-center">
<view class="user-nickName f-28">{{ item.user.nick_name }}</view>
<view class="user-time f-24 c-80">消费金额{{ item.order_price }}</view>
</view>
</view>
<view class="detail__right dis-flex flex-dir-column flex-x-center flex-y-center">
<view class="detail__money t-r col-m">
<text class="f-26">+ </text>
<text class="f-28">{{ item.my_money }}</text>
</view>
<view class="detail__time f-22 c-80">{{ item.create_time }}</view>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import AvatarImage from '@/components/avatar-image'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import * as Api from '@/api/dealer/order'
import SettingModel from '@/common/model/dealer/Setting'
const pageSize = 15
export default {
components: {
MescrollBody,
AvatarImage
},
mixins: [MescrollMixin],
data() {
return {
// 选项卡列表
tabList: [],
// 当前选项
curTab: 0,
// 列表数据
list: getEmptyPaginateObj(),
// 上拉加载配置
upOption: {
// 首次自动执行
auto: true,
// 每页数据的数量; 默认10
page: { size: pageSize },
// 数量要大于12条才显示无更多数据
noMoreSize: 12,
// 空布局
empty: {
tip: '亲,暂无相关数据'
}
}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getSetting()
},
methods: {
// 获取分销设置
getSetting() {
const app = this
SettingModel.data()
.then(setting => {
const words = setting.words.order
app.setPageTitle(words.title)
app.setTabList(words.words)
})
},
// 设置页面标题
setPageTitle(title) {
uni.setNavigationBarTitle({
title: title.value
})
},
// 设置选项卡数据
setTabList(words) {
const app = this
app.tabList = [
{ value: -1, name: words.all.value },
{ value: 0, name: words.unsettled.value },
{ value: 1, name: words.settled.value }
]
},
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
// 设置列表数据
app.getList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// 获取提现列表
getList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
Api.list({ settled: app.getTabValue(), page: pageNo })
.then(result => {
// 合并新数据
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
})
},
// 获取当前标签项的值
getTabValue() {
const app = this
if (app.tabList.length) {
return app.tabList[app.curTab].value
}
return -1
},
// 切换标签项
onChangeTab(index) {
const app = this
// 设置当前选中的标签
app.curTab = index
// 刷新订单列表
app.onRefreshList()
},
// 刷新列表数据
onRefreshList() {
this.list = getEmptyPaginateObj()
setTimeout(() => {
this.mescroll.resetUpScroll()
}, 120)
}
}
}
</script>
<style>
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
.c-violet {
color: #786cff;
}
.c-80 {
color: #808080;
}
// 订单列表
.widget-list {
padding: 10rpx 20rpx 40rpx 20rpx;
box-sizing: border-box;
}
.widget__detail {
padding: 20rpx 15rpx;
box-sizing: border-box;
font-size: 28rpx;
border-bottom: 1rpx solid #e7e7e7;
.user-avatar {
margin-right: 16rpx;
}
.user-info {
height: 100%;
.user-nickName {
margin-bottom: 8rpx;
}
}
.detail__money {
width: 100%;
margin-bottom: 8rpx;
}
}
</style>
+80
View File
@@ -0,0 +1,80 @@
<template>
<view class="container">
<view v-if="imageUrl" class="poster">
<image class="image" mode="widthFix" :src="imageUrl" @click="onPreviewImage"></image>
</view>
</view>
</template>
<script>
import * as Api from '@/api/dealer/poster'
import SettingModel from '@/common/model/dealer/Setting'
export default {
data() {
return {
// 海报图url
imageUrl: undefined
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getSetting()
this.getPoster()
},
methods: {
// 获取分销设置
getSetting() {
const app = this
SettingModel.data()
.then(setting => {
const words = setting.words.poster
app.setPageTitle(words.title)
})
},
// 获取推广二维码
getPoster() {
const app = this
app.isLoading = true
Api.qrcode({ channel: app.platform })
.then(result => {
// api数据赋值
app.imageUrl = result.data.imageUrl
})
.finally(() => app.isLoading = false)
},
// 设置当前页面标题
setPageTitle(title) {
uni.setNavigationBarTitle({ title: title.value })
},
// 预览海报图
onPreviewImage() {
uni.previewImage({
current: this.imageUrl,
urls: [this.imageUrl]
})
}
}
}
</script>
<style>
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
.poster .image {
display: block;
width: 100%;
}
</style>
+267
View File
@@ -0,0 +1,267 @@
<template>
<view class="container" v-if="!isLoading">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption"
@up="upCallback">
<!-- tab栏 -->
<u-tabs :list="tabList" :is-scroll="false" :current="curTab" active-color="#786cff" :duration="0.2"
@change="onChangeTab" />
<!-- 团队总人数 -->
<view class="widget-people f-28 col-9">{{ words.total_team.value }}{{ teamTotal }}</view>
<!-- 列表数据 -->
<view class="widget-list b-f">
<view class="widget__detail dis-flex flex-x-between" v-for="(item, index) in list.data" :key="index">
<view class="detail__left dis-flex flex-y-center">
<avatar-image class="user-avatar" :url="item.user.avatar_url" :width="100" :borderWidth="4"
:borderColor="`#fff`" />
<view class="user-info dis-flex flex-dir-column flex-x-center">
<view class="user-nickName f-28">{{ item.user.nick_name }}</view>
<view class="user-time col-9 f-24">{{ item.create_time }}</view>
</view>
</view>
<view class="detail__right dis-flex flex-dir-column flex-x-center flex-y-center">
<view class="detail__money">
<text class="f-24"></text>
<text class="f-34">{{ item.user.expend_money }}</text>
</view>
<view class="detail__member f-22" v-if="item.subDealer">
{{ item.subDealer.first_num + item.subDealer.second_num + item.subDealer.third_num }}个成员
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import AvatarImage from '@/components/avatar-image'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import * as Api from '@/api/dealer/team'
import * as DealerApi from '@/api/dealer'
import SettingModel from '@/common/model/dealer/Setting'
const pageSize = 15
export default {
components: {
MescrollBody,
AvatarImage
},
mixins: [MescrollMixin],
data() {
return {
// 选项卡列表
tabList: [],
// 当前选项
curTab: 0,
// 列表数据
list: getEmptyPaginateObj(),
// 上拉加载配置
upOption: {
// 首次自动执行
auto: true,
// 每页数据的数量; 默认10
page: { size: pageSize },
// 数量要大于12条才显示无更多数据
noMoreSize: 12,
// 空布局
empty: {
tip: '亲,暂无相关数据'
}
},
// 正在加载
isLoading: true,
// 文字设置
words: undefined,
// 团队总人数
teamTotal: undefined
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const app = this
app.isLoading = true
Promise.all([app.getSetting(), app.getDealerUser()])
.then(result => {
const setting = result[0]
const dealer = result[1]
app.setTabList(setting, dealer)
})
.finally(() => app.isLoading = false)
},
methods: {
// 获取分销设置
getSetting() {
const app = this
return new Promise((resolve, reject) => {
SettingModel.data()
.then(setting => {
app.words = setting.words.team.words
app.setPageTitle(setting)
resolve(setting)
})
.catch(reject)
})
},
// 获取分销用户信息
getDealerUser() {
const app = this
return new Promise((resolve, reject) => {
DealerApi.user()
.then(result => resolve(result.data.dealer))
.catch(reject)
})
},
// 设置页面标题
setPageTitle(setting) {
uni.setNavigationBarTitle({
title: setting.words.team.title.value
})
},
// 设置选项卡数据
setTabList(setting, dealer) {
const app = this
const words = setting.words.team.words
app.tabList = [
{ value: 1, name: words.first.value, count: dealer.first_num }
]
app.teamTotal = dealer.first_num
if (setting.basic.level >= 2) {
app.tabList.push({
value: 2,
name: words.second.value,
count: dealer.second_num
})
app.teamTotal += dealer.second_num
}
if (setting.basic.level >= 3) {
app.tabList.push({
value: 3,
name: words.third.value,
count: dealer.third_num
})
app.teamTotal += dealer.third_num
}
},
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
// 设置列表数据
app.getList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// 获取提现列表
getList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
Api.list({ level: app.getTabValue(), page: pageNo })
.then(result => {
// 合并新数据
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
})
},
// 获取当前标签项的值
getTabValue() {
const app = this
if (app.tabList.length) {
return app.tabList[app.curTab].value
}
return 1
},
// 切换标签项
onChangeTab(index) {
const app = this
// 设置当前选中的标签
app.curTab = index
// 刷新订单列表
app.onRefreshList()
},
// 刷新列表数据
onRefreshList() {
this.list = getEmptyPaginateObj()
setTimeout(() => {
this.mescroll.resetUpScroll()
}, 120)
}
}
}
</script>
<style>
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
// 团队人数
.widget-people {
padding: 15rpx 25rpx;
height: 65rpx;
box-sizing: border-box;
background: #f7f7f7;
}
// 列表内容
.widget-list {
padding: 10rpx 20rpx 40rpx 20rpx;
box-sizing: border-box;
}
.widget__detail {
padding: 20rpx 15rpx;
box-sizing: border-box;
font-size: 28rpx;
border-bottom: 1rpx solid #e7e7e7;
.user-avatar {
margin-right: 16rpx;
}
.user-info {
height: 100%;
}
.user-nickName {
margin-bottom: 8rpx;
}
.detail__member {
background-color: #786cff;
color: #fff;
padding: 2rpx 15rpx;
border-radius: 6rpx;
margin-top: 10rpx;
}
}
</style>
+358
View File
@@ -0,0 +1,358 @@
<template>
<view v-if="!isLoading" class="container b-f">
<!-- 头部背景图 -->
<view class="dealer-bg">
<image class="image" mode="widthFix" :src="background"></image>
</view>
<view class="widget-body">
<form @submit="handleSubmit">
<!-- 提现佣金 -->
<view class="widget widget__capital m-top20 b-f dis-flex flex-dir-column">
<view class="capital__item dis-flex flex-x-between flex-y-center">
<view class="item__left">{{ words.capital.value }}</view>
<view class="item__right c-violet">
<text class="f-24"></text>
<text class="f-34">{{ dealer.money }}</text>
</view>
</view>
<view class="capital__item dis-flex flex-y-center">
<view class="item__left">{{ words.money.value }}</view>
<view class="item__right flex-box">
<input class="input" name="money" :placeholder="words.money_placeholder.value"></input>
</view>
</view>
</view>
<!-- 最低提现金额 -->
<view class="capital__lowest m-top20 col-7 t-r">
{{ words.min_money.value }}{{ settlement.min_money }}
</view>
<!-- 提现方式 -->
<view class="widget widget__form m-top20 b-f dis-flex flex-dir-column">
<view class="form__title f-28">提现方式</view>
<view class="form__box">
<block v-for="(item, index) in settlement.pay_type" :key="index">
<block v-if="item == PayTypeEnum.WECHAT.value">
<!-- 微信支付 -->
<view class="form__field dis-flex flex-y-center">
<view class="form__radio dis-flex flex-y-center" @click="handleChecked(PayTypeEnum.WECHAT.value)">
<text class="radio__icon iconfont icon-radio"
:class="[payment == PayTypeEnum.WECHAT.value ? 'c-violet' : 'col-bb']"></text>
<text class="f-28">{{ PayTypeEnum.WECHAT.name }}</text>
</view>
</view>
</block>
<block v-if="item == PayTypeEnum.ALIPAY.value">
<!-- 支付宝 -->
<view class="form__field dis-flex flex-y-center">
<view class="form__radio dis-flex flex-y-center" @click="handleChecked(PayTypeEnum.ALIPAY.value)">
<text class="radio__icon iconfont icon-radio"
:class="[payment == PayTypeEnum.ALIPAY.value ? 'c-violet' : 'col-bb']"></text>
<text class="f-28">{{ PayTypeEnum.ALIPAY.name }}</text>
</view>
</view>
<block v-if="payment == PayTypeEnum.ALIPAY.value">
<view class="form__field dis-flex flex-y-center">
<view class="field-input flex-box">
<input class="input" name="alipay_name" placeholder="请输入真实姓名"></input>
</view>
</view>
<view class="form__field dis-flex flex-y-center">
<view class="field-input flex-box">
<input class="input" name="alipay_account" placeholder="请输入支付宝账号"></input>
</view>
</view>
</block>
</block>
<block v-if="item == PayTypeEnum.BANK_CARD.value">
<!-- 银行卡 -->
<view class="form__field dis-flex flex-y-center">
<view class="form__radio dis-flex flex-y-center" @click="handleChecked(PayTypeEnum.BANK_CARD.value)">
<text class="radio__icon iconfont icon-radio"
:class="[payment == PayTypeEnum.BANK_CARD.value ? 'c-violet' : 'col-bb']"></text>
<text class="f-28">{{ PayTypeEnum.BANK_CARD.name }}</text>
</view>
</view>
<block v-if="payment == PayTypeEnum.BANK_CARD.value">
<view class="form__field dis-flex flex-y-center">
<view class="field-input flex-box">
<input class="input" name="bank_name" placeholder="请输入真实姓名"></input>
</view>
</view>
<view class="form__field dis-flex flex-y-center">
<view class="field-input flex-box">
<input class="input" name="bank_account" placeholder="请输入开户行名称/地址"></input>
</view>
</view>
<view class="form__field dis-flex flex-y-center">
<view class="field-input flex-box">
<input class="input" name="bank_card" placeholder="请输入银行卡号"></input>
</view>
</view>
</block>
</block>
</block>
</view>
</view>
<!-- 提交申请 -->
<view class="form-submit dis-flex flex-x-center">
<button formType="submit" :disabled="disabled">{{ words.submit.value }}</button>
</view>
</form>
</view>
</view>
</template>
<script>
import * as DealerApi from '@/api/dealer'
import * as WithdrawApi from '@/api/dealer/withdraw'
import SettingModel from '@/common/model/dealer/Setting'
import { PayTypeEnum } from '@/common/enum/dealer/withdraw'
export default {
data() {
return {
// 枚举类
PayTypeEnum,
// 正在加载
isLoading: true,
// 分销商用户信息
dealer: undefined,
// 当前提现方式(选中的)
payment: undefined,
// 分销结算设置
settlement: undefined,
// 文字设置
words: undefined,
// 背景图
background: undefined,
// 按钮禁用
disabled: false
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getSetting()
this.getDealer()
},
methods: {
// 获取分销设置
getSetting() {
const app = this
SettingModel.data()
.then(setting => {
// 赋值属性
app.payment = setting.settlement.pay_type[0]
app.settlement = setting.settlement
app.words = setting.words.withdraw_apply.words
app.background = setting.background.withdraw_apply
// 设置当前页面标题
app.setPageTitle(setting.words.withdraw_apply.title)
})
},
// 获取分销商
getDealer() {
const app = this
app.isLoading = true
DealerApi.user()
.then(result => app.dealer = result.data.dealer)
.finally(() => app.isLoading = false)
},
// 设置当前页面标题
setPageTitle(title) {
uni.setNavigationBarTitle({ title: title.value })
},
// 切换支付选项
handleChecked(value) {
this.payment = value
},
// 表单提交
handleSubmit({ detail }) {
const app = this
// 表单验证
if (!app.onValidation(detail.value)) {
return false
}
// 确认是否提交
uni.showModal({
title: '友情提示',
content: '确定提交提现申请吗?请确认填写无误',
showCancel: true,
success(res) {
if (res.confirm) {
app.onSubmit(detail.value)
} else if (res.cancel) {
app.disabled = false
}
}
})
},
// 提交提现申请
onSubmit(data) {
const app = this
app.disabled = true
data.pay_type = app.payment
WithdrawApi.submit({ form: data })
.then(result => {
app.$toast(result.message)
setTimeout(() => uni.navigateBack(), 1200)
})
.finally(() => app.disabled = false)
},
// 表单验证
onValidation(data) {
const app = this
const words = app.words
// 验证可提现佣金
if (app.dealer.money <= 0) {
app.$error('当前没有' + words.capital.value)
return false
}
// 验证提现金额
if (!data.money || data.money.length < 1) {
app.$error('请填写' + words.money.value)
return false
}
return true
}
}
}
</script>
<style>
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
.c-violet {
color: #786cff;
}
.col-bb {
color: #bbb;
}
.dealer-bg {
.image {
width: 100%;
}
}
.widget-body {
position: relative;
width: 88%;
margin: 0 auto;
.widget {
box-sizing: border-box;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.11);
border-radius: 12rpx;
}
}
// 提现金额
.widget__capital {
padding: 10rpx 0;
margin-top: -60rpx;
.capital__item {
height: 80rpx;
padding: 10rpx 35rpx;
font-size: 28rpx;
border-bottom: 1rpx solid #e7e7e7;
box-sizing: border-box;
&:last-child {
border-bottom: none;
}
.item__left {
margin-right: 20rpx;
}
.item__right {
.input {
font-size: 28rpx;
text-align: right;
}
}
}
}
.capital__lowest {
padding-right: 20rpx;
box-sizing: border-box;
font-size: 26rpx;
}
// 提现方式
.widget__form {
padding: 10rpx 0 20rpx 0;
.form__title {
padding: 16rpx 35rpx;
border-bottom: 1rpx solid #f3f3f3;
}
.form__box {
padding: 20rpx 35rpx;
}
.form__field {
height: 80rpx;
padding: 0;
box-sizing: border-box;
font-size: 28rpx;
margin-bottom: 10rpx;
.radio__icon {
font-size: 38rpx;
margin-right: 12rpx;
}
.field-input {
.input {
background-color: #f9f9f9;
// height: 70rpx;
padding: 16rpx 20rpx;
font-size: 28rpx;
}
}
}
}
// 提交申请
.form-submit {
margin-top: 40rpx;
button {
font-size: 30rpx;
background: #786cff;
border: 1rpx solid #786cff;
color: white;
border-radius: 50rpx;
padding: 0 120rpx;
}
button[disabled] {
background: #8e84fc;
border-color: #8e84fc;
color: white;
}
}
</style>
+237
View File
@@ -0,0 +1,237 @@
<template>
<view class="container">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption" @up="upCallback">
<!-- tab栏 -->
<u-tabs :list="tabList" :is-scroll="false" :current="curTab" active-color="#786cff" :duration="0.2" @change="onChangeTab" />
<!-- 列表数据 -->
<view class="widget-list">
<view class="widget__detail dis-flex flex-x-between" v-for="(item, index) in list.data" :key="index">
<view class="detail__left dis-flex flex-dir-column flex-x-around">
<view class="detail__money f-30">提现 {{ item.money }}</view>
<view class="detail__time col-9 f-24">{{ item.create_time }}</view>
</view>
<view class="detail__right dis-flex flex-dir-column flex-x-center flex-y-center">
<view class="detail__status" :class="[ApplyStatusColor[item.apply_status]]">
<text>{{ ApplyStatusText[item.apply_status] }}</text>
</view>
<block v-if="item.apply_status == 30">
<view class="detail__reason" @click="handleShowRejectReason(item)">驳回原因
</view>
</block>
</view>
</view>
</view>
</mescroll-body>
<!-- 砍价规则弹窗 -->
<u-modal v-model="showRejectReason" title="驳回原因">
<view class="pops-content">
<text>{{ rejectReason }}</text>
</view>
</u-modal>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import * as Api from '@/api/dealer/withdraw'
import SettingModel from '@/common/model/dealer/Setting'
import { ApplyStatusEnum } from '@/common/enum/dealer/withdraw'
const pageSize = 15
// 提现状态文字
const ApplyStatusText = {}
// 提现状态颜色
const ApplyStatusColor = {
[ApplyStatusEnum.WAIT.value]: 'col-m',
[ApplyStatusEnum.PASSED.value]: 'col-green',
[ApplyStatusEnum.REJECT.value]: 'col-m',
[ApplyStatusEnum.PAYMENT.value]: 'col-green'
}
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
// 枚举类
ApplyStatusEnum,
ApplyStatusColor,
ApplyStatusText,
// 选项卡列表
tabList: [],
// 当前选项
curTab: 0,
// 列表数据
list: getEmptyPaginateObj(),
// 上拉加载配置
upOption: {
// 首次自动执行
auto: true,
// 每页数据的数量; 默认10
page: { size: pageSize },
// 数量要大于12条才显示无更多数据
noMoreSize: 12,
// 空布局
empty: {
tip: '亲,暂无相关数据'
}
},
// 驳回原因弹窗
showRejectReason: false,
rejectReason: ''
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getSetting()
},
methods: {
// 获取分销设置
getSetting() {
const app = this
SettingModel.data()
.then(setting => {
const words = setting.words.withdraw_list
app.setPageTitle(words.title)
app.setTabList(words.words)
})
},
// 设置页面标题
setPageTitle(title) {
uni.setNavigationBarTitle({
title: title.value
})
},
// 设置选项卡数据
setTabList(words) {
const app = this
app.tabList = [
{ value: -1, name: words.all.value },
{ value: ApplyStatusEnum.WAIT.value, name: words.apply_10.value },
{ value: ApplyStatusEnum.PASSED.value, name: words.apply_20.value },
{ value: ApplyStatusEnum.PAYMENT.value, name: words.apply_40.value },
{ value: ApplyStatusEnum.REJECT.value, name: words.apply_30.value }
]
},
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
// 设置列表数据
app.getList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// 获取提现列表
getList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
Api.list({ applyStatus: app.getTabValue(), page: pageNo })
.then(result => {
// 合并新数据
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
})
},
// 获取当前标签项的值
getTabValue() {
const app = this
if (app.tabList.length) {
return app.tabList[app.curTab].value
}
return -1
},
// 切换标签项
onChangeTab(index) {
const app = this
// 设置当前选中的标签
app.curTab = index
// 刷新订单列表
app.onRefreshList()
},
// 刷新列表数据
onRefreshList() {
this.list = getEmptyPaginateObj()
setTimeout(() => {
this.mescroll.resetUpScroll()
}, 120)
},
// 显示驳回原因
handleShowRejectReason(item) {
this.showRejectReason = true
this.rejectReason = item.reject_reason
},
}
}
</script>
<style>
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
// 提现明细列表
.widget-list {
padding: 16rpx 20rpx 40rpx 20rpx;
box-sizing: border-box;
}
.widget__detail {
padding: 26rpx 15rpx;
box-sizing: border-box;
font-size: 26rpx;
border-bottom: 1rpx solid #f2f2f2;
}
.widget__detail .detail__money {
font-size: 30rpx;
}
.widget__detail .detail__reason {
color: #8e84fc;
}
// 驳回原因 (弹窗)
.pops-content {
padding: 30rpx 48rpx;
font-size: 28rpx;
line-height: 44rpx;
text-align: left;
color: #606266;
height: 220rpx;
box-sizing: border-box;
}
</style>
+29
View File
@@ -0,0 +1,29 @@
<template>
</template>
<script>
export default {
data() {
return {
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
methods: {
}
}
</script>
<style lang="scss" scoped>
</style>
+441
View File
@@ -0,0 +1,441 @@
<template>
<view class="container" :style="appThemeStyle">
<view class="addres-list">
<view class="address-item">
<view class="head-info">
<view class="title">{{ title }}</view>
<view class="description">{{ description }}</view>
</view>
<u-form :label-width="200" :model="form" ref="uForm">
<u-form-item left-icon="info-circle" label="剩余电量">
<view class="item-right">{{ form.surplusPower }}</view>
</u-form-item>
<u-form-item left-icon="info-circle" label="设备电压">
<view class="item-right">{{ form.equipmentVoltage }}</view>
</u-form-item>
<u-form-item left-icon="info-circle" label="GPS信号">
<view class="item-right">{{ form.gps }}</view>
</u-form-item>
<u-form-item left-icon="info-circle" label="GSM信号">
<view class="item-right">{{ form.gsm }}</view>
</u-form-item>
<u-form-item left-icon="info-circle" label="设备电流">
<view class="item-right">{{ form.equipmentCurrent }}</view>
</u-form-item>
<u-form-item left-icon="info-circle" label="标称容量">
<view class="item-right">{{ form.nominalCapacity }}</view>
</u-form-item>
<u-form-item left-icon="info-circle" label="设备温度">
<view class="item-right">{{ form.equipmentTemperature }}</view>
</u-form-item>
<u-form-item left-icon="info-circle" label="电池温度">
<view class="item-right">{{ form.batteryTemperature }}</view>
</u-form-item>
</u-form>
</view>
<view class="address-item">
<u-row gutter="16" justify="center">
<u-col span="4">
<view class="demo-layout bg-purple">
<text class="desc">最高电压</text>
<text class="value1">3.168V</text>
</view>
</u-col>
<u-col span="4">
<view class="demo-layout bg-purple-light">
<text class="desc">最低电压</text>
<text class="value2">3.166V</text>
</view>
</u-col>
<u-col span="4">
<view class="demo-layout bg-purple-dark">
<text class="desc">相差压值</text>
<text class="value3">0.002V</text>
</view>
</u-col>
</u-row>
</view>
<view class="address-item">
<view class="head-info">
<view class="title">电芯电压</view>
</view>
<view class="content">
<image class="image" src="../../../static/battery.png"></image>
</view>
</view>
</view>
</view>
</template>
<script>
import * as AddressApi from '@/api/address'
import Empty from '@/components/empty'
export default {
components: {
Empty
},
data() {
return {
//当前页面参数
options: {},
form: {
surplusPower: '98%',
equipmentVoltage: '82.6V',
gps: '',
gsm: '',
equipmentCurrent: '12AH',
nominalCapacity: '50AH',
equipmentTemperature: '2℃',
batteryTemperature: '35℃'
},
// 正在加载
isLoading: true,
// 收货地址列表
list: [],
// 默认收货地址
defaultId: null,
title: '设备信息',
description: '要查看设备的相关参数信息'
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 当前页面参数
this.options = options
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
// 获取页面数据
this.getPageData()
},
methods: {
// 获取页面数据
getPageData() {
const app = this
app.isLoading = true
Promise.all([app.getDefaultId(), app.getAddressList()])
.then(() => {
// 列表排序把默认收货地址放到最前
app.onReorder()
})
.finally(() => app.isLoading = false)
},
// 获取收货地址列表
getAddressList() {
const app = this
return new Promise((resolve, reject) => {
AddressApi.list()
.then(result => {
app.list = result.data.list
resolve(result)
})
.catch(reject)
})
},
// 获取默认的收货地址
getDefaultId() {
return new Promise((resolve, reject) => {
const app = this
AddressApi.defaultId()
.then(result => {
app.defaultId = result.data.defaultId
resolve(result)
})
.catch(reject)
})
},
// 列表排序把默认收货地址放到最前
onReorder() {
const app = this
app.list.sort(item => {
return item.address_id == app.defaultId ? -1 : 1
})
},
navTo(url) {
this.$navTo(url)
},
/**
* 添加新地址
*/
handleCreate() {
this.$navTo('pages/address/create')
},
/**
* 编辑地址
* @param {int} addressId 收货地址ID
*/
handleUpdate(addressId) {
this.$navTo('pages/address/update', {
addressId
})
},
/**
* 删除收货地址
* @param {int} addressId 收货地址ID
*/
handleRemove(addressId) {
const app = this
uni.showModal({
title: "提示",
content: "您确定要删除当前收货地址吗?",
success({
confirm
}) {
confirm && app.onRemove(addressId)
}
});
},
/**
* 确认删除收货地址
* @param {int} addressId 收货地址ID
*/
onRemove(addressId) {
const app = this
AddressApi.remove(addressId)
.then(result => {
app.getPageData()
})
},
/**
* 设置为默认地址
* @param {Object} addressId
*/
handleSetDefault(addressId) {
const app = this
AddressApi.setDefault(addressId)
.then(result => {
app.defaultId = addressId
app.options.from === 'checkout' && uni.navigateBack()
})
}
}
}
</script>
<style lang="scss" scoped>
.addres-list {
padding-bottom: calc(constant(safe-area-inset-bottom) + 140rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
padding-top: 20rpx;
.head-info {
width: 750rpx;
margin: auto;
display: felx;
flex-direction: column;
.title {
font-weight: bold;
font-size: 34rpx;
}
.description {
padding: 10rpx 0;
font-size: 30rpx;
color: #999999;
}
}
}
// 项目内容
.address-item {
margin: 0 auto 20rpx auto;
padding: 30rpx 40rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0 rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
background: #fff;
.title {
display: flex;
justify-content: space-between;
}
.item-right {
display: flex;
justify-content: flex-end;
}
.demo-layout {
display: flex;
flex-direction: column;
.value1 {
color: #de4e02;
}
.value2 {
color: #ff0089;
}
.value3 {
color: #0c10f5;
}
}
.image{
width: 200rpx;
height: 200rpx;
}
}
.contacts {
margin-bottom: 16rpx;
display: flex;
justify-content: space-between;
.bms-info {
display: flex;
flex-direction: column;
.item {
margin-right: 16rpx;
}
.name {
margin-right: 16rpx;
}
}
.bms-image {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
width: 120rpx;
.bms-image-box {
display: flex;
font-size: 22rpx;
margin-bottom: 18rpx;
color: #00af00;
.bms-image-fbf {
margin-top: 22rpx;
}
}
.image {
width: 50rpx;
height: 60rpx;
}
}
}
.address {
font-size: 28rpx;
.region {
margin-right: 10rpx;
}
}
.line {
margin: 20rpx 0;
border-bottom: 1rpx solid #f3f3f3;
}
.line-align {
width: 6rpx;
height: 48rpx;
border-right: 1rpx solid #f3f3f3;
}
.item-option {
display: flex;
justify-content: space-around;
align-items: center;
height: 48rpx;
// 单选框
.item-radio {
font-size: 28rpx;
.radio {
vertical-align: middle;
transform: scale(0.76)
}
.text {
vertical-align: middle;
}
}
// 操作
.events {
display: flex;
align-items: center;
line-height: 48rpx;
.event-item {
font-size: 28rpx;
margin-right: 26rpx;
color: #4c4c4c;
&:last-child {
margin-right: 0;
}
.title {
margin-left: 8rpx;
}
}
}
}
// 底部操作栏
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
// 设置ios刘海屏底部横线安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.btn-wrapper {
height: 120rpx;
padding: 0 40rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 86rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 1rpx 5rpx 0 rgba(0, 0, 0, 0.05);
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
}
}
</style>
+255
View File
@@ -0,0 +1,255 @@
<template>
<view>
<view class="goods-info">
<view class="goods">
<image src="../../static/goods/battery.png" mode="aspectFit"></image>
<view class="info">
<text class="goods-name">{{ record.equipmentName }}</text>
<!-- <text class="goods-desc">归属门店{{ record.merchantName }}</text> -->
<text class="goods-desc">电池型号{{ record.batteryModel }}</text>
<text class="goods-desc">电池编号{{ record.equipmentCode }}</text>
<text class="goods-desc">是否激活{{ record.isCtive == 0 ? '已激活' : '未激活' }}</text>
<text class="goods-desc">BMS{{ record.bms }}</text>
<text class="goods-desc">工作状态{{ record.workingStatus }}</text>
<text class="goods-desc">租赁状态{{ record.leaseStatus }}</text>
<text class="goods-desc">电池状态{{ record.batteryStatus }}</text>
<text class="goods-desc">电池电量{{ record.batteryPower }}</text>
<text class="goods-desc">是否在线{{ record.isOnline }}</text>
<text class="goods-desc">总电压{{ record.totalVoltage }}</text>
<text class="goods-desc">剩余容量{{ record.surplusCapacity }}</text>
<text class="selling-point" v-if="record.equipmentCategory === '锂电池租赁'">全新电池连续组满15个月电池归客户</text>
</view>
</view>
</view>
<view class="submit">
<u-button type="primary" @click="onBuy">立即购买</u-button>
</view>
<!-- <view class="submit">
<u-button type="success" :disabled="agree" @click="copyCode(record.equipmentCode)">复制设备编号</u-button>
</view> -->
<view class="submit">
<u-button type="success" :disabled="agree" @click="gohome">返回首页</u-button>
</view>
</view>
</template>
<script>
import { userId } from '@/config.js';
import {
getEquipment
} from '@/websoft/api/equipment.js'
import { getUser } from '@/websoft/api/user.js'
import { addOrder } from '@/websoft/api/order.js'
import { createOrderNo } from '@/utils/util.js'
import store from '../../store';
export default {
components: {
// Search
},
data() {
return {
record: {},
// 正在加载中
isLoading: true,
// 当前选择的设备ID
equipmentId: null,
merchantId: null,
merchantCode: null,
show: true,
mode: 'range',
month: 6,
agree: false,
price: {
batteryRent: 300,
batteryDeposit: 300,
batteryInsurance: 0
},
}
},
onLoad(option) {
const app = this
// 记录当前选择的门店ID
console.log("option: ",option);
app.equipmentId = Number(option.equipmentId)
app.merchantId = Number(option.merchantId)
app.merchantCode = option.merchantCode
// 获取设备列表
app.getEquipment()
},
methods: {
getEquipment() {
const app = this
const {
equipmentId
} = this
getEquipment(equipmentId).then(res => {
app.record = res.data
console.log("res2222: ", app.record);
})
},
gohome(){
uni.reLaunch({
url: '/pages/index/index'
})
},
onChangeStepper({ value }) {
this.month = value
},
change(e) {
console.log(e);
},
onInput(month) {
this.month = month
},
onAgree(){
this.agree = !this.agree
},
showXieyi(){
this.$navTo('pages/help/xieyi')
},
copyCode(text){
// #ifndef H5
console.log("text: ",text);
uni.setClipboardData({
text: text,
success: (result) => {
this.$success("复制成功")
}
})
// #endif
// #ifdef H5
let textarea = document.createElement("textarea")
textarea.value = text
textarea.readOnly = "readOnly"
document.body.appendChild(textarea)
textarea.select() // 选中文本内容
textarea.setSelectionRange(0, info.length)
uni.showToast({ //提示
title: '复制成功'
})
result = document.execCommand("copy")
textarea.remove()
// #endif
// copy() {
// let result
// // #ifndef H5
// //uni.setClipboardData方法就是讲内容复制到粘贴板
// uni.setClipboardData({
// data: this.downUrl.url, //要被复制的内容
// success: () => { //复制成功的回调函数
// uni.showToast({ //提示
// title: '复制成功'
// })
// }
// });
// // #endif
// }
},
onBuy(){
const app = this
const { equipmentId } = this
this.$navTo('pages/merchant/merchant', { equipmentId })
}
}
}
</script>
<style lang="scss" scoped>
.goods-info {
background-color: #ffffff;
border-radius: 12rpx;
width: 700rpx;
margin: 20rpx auto;
.goods {
padding: 20rpx;
display: flex;
image {
width: 170rpx;
height: 170rpx;
margin: 20rpx;
}
.info {
display: flex;
flex-direction: column;
padding: 20rpx;
.goods-name {
font-size: 34rpx;
margin-bottom: 10rpx;
}
.goods-desc {
font-size: 28rpx;
color: #999999;
}
.selling-point{
padding: 5px 0;
color: #e6760e;
}
}
}
.between-time {
width: 500rpx;
margin: auto;
padding-bottom: 20rpx;
text-align: center;
.select-slider {
line-height: 2em;
color: #e6760e;
}
}
}
.order {
background-color: #ffffff;
border-radius: 12rpx;
width: 700rpx;
margin: 20rpx auto;
padding: 10rpx 0;
.title {
font-weight: bold;
font-size: 30rpx;
padding: 20rpx;
display: flex;
justify-content: space-between;
}
.xieyi {
padding: 20rpx;
font-size: 26rpx;
}
}
.submit {
border-radius: 12rpx;
width: 700rpx;
margin: 20rpx auto;
padding: 10rpx 0;
}
.xieyi-text{
color: #0000ff;
}
.fenqi{
padding: 30rpx;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
.item{
display: flex;
justify-content: space-between;
padding: 20rpx 0;
}
}
</style>
+159
View File
@@ -0,0 +1,159 @@
<template>
<!-- 商品评价 -->
<view v-if="!isLoading && list.length" class="goods-comment m-top20">
<view class="item-title dis-flex">
<view class="block-left flex-box">
商品评价 (<text class="total">{{ total }}</text>)
</view>
<view class="block-right">
<text @click="onTargetToComment" class="show-more col-9">查看更多</text>
<text class="iconfont icon-arrow-right col-9"></text>
</view>
</view>
<!-- 评论列表 -->
<view class="comment-list">
<view class="comment-item" v-for="(item, index) in list" :key="index">
<view class="comment-item_row dis-flex flex-y-center">
<view class="user-info dis-flex flex-y-center">
<avatar-image class="user-avatar" :url="item.user.avatar_url" :width="50" />
<text class="user-name">{{ item.user.nick_name }}</text>
</view>
<!-- 评星 -->
<view class="star-rating">
<u-rate active-color="#f4a213" :current="rates[item.score]" :disabled="true" />
</view>
</view>
<view class="item-content m-top20">
<text class="f-26 twoline-hide">{{ item.content }}</text>
</view>
<view class="comment-time">{{ item.create_time }}</view>
</view>
</view>
</view>
</template>
<script>
import AvatarImage from '@/components/avatar-image'
import * as CommentApi from '@/api/comment'
export default {
components: {
AvatarImage
},
props: {
// 商品ID
goodsId: {
type: Number,
default: null
},
// 加载多少条记录 默认2条
limit: {
type: Number,
default: 2
}
},
data() {
return {
// 正在加载
isLoading: true,
// 评星数据转换
rates: { 10: 5, 20: 3, 30: 1 },
// 评价列表数据
list: [],
// 评价总数量
total: 0
}
},
created() {
// 加载评价列表数据
this.getCommentList()
},
methods: {
// 加载评价列表数据
getCommentList() {
const app = this
app.isLoading = true
CommentApi.listRows(app.goodsId, app.limit)
.then(result => {
app.list = result.data.list
app.total = result.data.total
})
.catch(err => err)
.finally(() => app.isLoading = false)
},
// 跳转到评论列表页
onTargetToComment() {
const app = this
app.$navTo('pages/comment/index', { goodsId: app.goodsId })
}
}
}
</script>
<style lang="scss" scoped>
.goods-comment {
padding: 20rpx 30rpx;
background-color: #fff;
}
.item-title {
font-size: 28rpx;
margin-bottom: 25rpx;
.total {
margin: 0 4rpx;
}
.show-more {
margin-right: 8rpx;
font-size: 24rpx;
}
}
.comment-item {
padding: 15rpx 5rpx;
margin-bottom: 10rpx;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
margin-bottom: 0;
border-bottom: none;
}
.comment-item_row {
margin-bottom: 10rpx;
}
}
.user-info {
margin-right: 15rpx;
.user-avatar {
margin-right: 10rpx;
}
.user-name {
font-size: 24rpx;
}
}
.item-content {
color: #333;
margin: 16rpx 0;
max-height: 76rpx;
line-height: 38rpx;
}
.comment-time {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
</style>
+158
View File
@@ -0,0 +1,158 @@
<template>
<view v-if="list.length" class="service-wrapper" :style="appThemeStyle">
<!-- 服务简述 -->
<view class="service-simple" @click="handlePopup">
<view class="s-list">
<view class="s-item" v-for="(item, index) in list" :key="index">
<text class="item-icon iconfont icon-fuwu"></text>
<text class="item-val">{{ item.name }}</text>
</view>
</view>
<!-- 扩展箭头 -->
<view class="s-arrow f-26 col-9 t-r">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
<!-- 详情内容弹窗 -->
<u-popup v-model="showPopup" mode="bottom" :closeable="true" :border-radius="26">
<view class="service-content">
<view class="title">服务</view>
<scroll-view class="content-scroll" :scroll-y="true">
<view class="s-list clearfix">
<view class="s-item" v-for="(item, index) in list" :key="index">
<text class="item-icon iconfont icon-fuwu"></text>
<view class="item-val">{{ item.name }}</view>
<view class="item-summary">{{ item.summary }}</view>
</view>
</view>
</scroll-view>
</view>
</u-popup>
</view>
</template>
<script>
import * as ServiceApi from '@/api/goods/service'
export default {
props: {
// 商品ID
goodsId: {
type: Number,
default: null
}
},
data() {
return {
// 正在加载
isLoading: true,
// 显示详情内容弹窗
showPopup: false,
// 服务列表数据
list: []
}
},
created() {
// 获取商品服务列表
this.getServiceList()
},
methods: {
// 获取商品服务列表
getServiceList() {
const app = this
app.isLoading = true
ServiceApi.list(app.goodsId)
.then(result => app.list = result.data.list)
.finally(() => app.isLoading = false)
},
// 显示弹窗
handlePopup() {
this.showPopup = !this.showPopup
}
}
}
</script>
<style lang="scss" scoped>
.service-wrapper {
min-height: 24rpx;
margin-bottom: -24rpx;
}
// 服务简述
.service-simple {
padding: 24rpx 30rpx;
display: flex;
align-items: center;
.s-list {
flex: 1;
margin-left: -15rpx;
}
.s-item {
float: left;
font-size: 26rpx;
margin: 8rpx 15rpx;
.item-icon {
color: $main-bg;
}
.item-val {
margin-left: 12rpx;
}
}
}
// 服务详细内容
.service-content {
padding: 24rpx;
.title {
font-size: 30rpx;
margin-bottom: 50rpx;
font-weight: bold;
text-align: center;
}
.content-scroll {
min-height: 400rpx;
max-height: 760rpx;
}
.s-list {
padding: 0 30rpx 0 80rpx;
}
.s-item {
position: relative;
margin-bottom: 60rpx;
.item-icon {
position: absolute;
top: 6rpx;
left: -50rpx;
color: $main-bg;
}
.item-val {
font-size: 28rpx;
}
.item-summary {
font-size: 26rpx;
margin-top: 20rpx;
color: #6d6d6d;
}
}
}
</style>
+189
View File
@@ -0,0 +1,189 @@
<template>
<goods-sku-popup :value="value" @input="onChangeValue" border-radius="20" :localdata="goodsInfo" :mode="skuMode" :maskCloseAble="true"
:priceColor="appTheme.mainBg" :buyNowBackgroundColor="appTheme.mainBg" :addCartColor="appTheme.viceText" :addCartBackgroundColor="appTheme.viceBg"
:activedStyle="{ color: appTheme.mainBg, borderColor: appTheme.mainBg, backgroundColor: activedBtnBackgroundColor }" @open="openSkuPopup"
@close="closeSkuPopup" @add-cart="addCart" @buy-now="buyNow" buyNowText="立即购买" :maxBuyNum="maxBuyNum" />
</template>
<script>
import { setCartTotalNum } from '@/core/app'
import { hex2rgba } from '@/utils/color'
import * as CartApi from '@/api/cart'
import GoodsSkuPopup from '@/components/goods-sku-popup'
export default {
components: {
GoodsSkuPopup
},
model: {
prop: 'value',
event: 'input'
},
props: {
// true 组件显示 false 组件隐藏
value: {
Type: Boolean,
default: false
},
// 模式 1:都显示 2:只显示购物车 3:只显示立即购买
skuMode: {
type: Number,
default: 1
},
// 商品详情信息
goods: {
type: Object,
default: {}
}
},
data() {
return {
// 商品信息
goodsInfo: {},
// 限购数量
maxBuyNum: null
}
},
computed: {
// 规格按钮选中时的背景色
activedBtnBackgroundColor() {
return hex2rgba(this.appTheme.mainBg, 0.1)
}
},
created() {
const app = this
const { goods } = app
app.goodsInfo = {
_id: goods.goods_id,
name: goods.goods_name,
goods_thumb: goods.goods_image,
sku_list: app.getSkuList(),
spec_list: app.getSpecList()
}
app.maxBuyNum = app.getMaxBuyNum()
},
methods: {
// 监听组件显示隐藏
onChangeValue(val) {
this.$emit('input', val)
},
// 整理商品SKU列表
getSkuList() {
const app = this
const { goods: { goods_name, goods_image, skuList } } = app
const skuData = []
skuList.forEach(item => {
skuData.push({
_id: item.id,
goods_sku_id: item.goods_sku_id,
goods_id: item.goods_id,
goods_name: goods_name,
image: item.image_url ? item.image_url : goods_image,
price: item.goods_price * 100,
stock: item.stock_num,
spec_value_ids: item.spec_value_ids,
sku_name_arr: app.getSkuNameArr(item.spec_value_ids)
})
})
return skuData
},
// 获取sku记录的规格值列表
getSkuNameArr(specValueIds) {
const app = this
const defaultData = ['默认']
const skuNameArr = []
if (specValueIds) {
specValueIds.forEach((valueId, groupIndex) => {
const specValueName = app.getSpecValueName(valueId, groupIndex)
skuNameArr.push(specValueName)
})
}
return skuNameArr.length ? skuNameArr : defaultData
},
// 获取指定的规格值名称
getSpecValueName(valueId, groupIndex) {
const app = this
const { goods: { specList } } = app
const res = specList[groupIndex].valueList.find(specValue => {
return specValue.spec_value_id == valueId
})
return res.spec_value
},
// 整理规格数据
getSpecList() {
const { goods: { specList } } = this
const defaultData = [{ name: '默认', list: [{ name: '默认' }] }]
const specData = []
specList.forEach(group => {
const children = []
group.valueList.forEach(specValue => {
children.push({ name: specValue.spec_value })
})
specData.push({
name: group.spec_name,
list: children
})
})
return specData.length ? specData : defaultData
},
// 限购数量
getMaxBuyNum() {
const { goods } = this
return goods.is_restrict ? goods.restrict_single : null
},
// sku组件 开始-----------------------------------------------------------
openSkuPopup() {
// console.log("监听 - 打开sku组件")
},
closeSkuPopup() {
// console.log("监听 - 关闭sku组件")
},
// 加入购物车按钮
addCart(selectShop) {
const app = this
const { goods_id, goods_sku_id, buy_num } = selectShop
CartApi.add(goods_id, goods_sku_id, buy_num)
.then(result => {
// 显示成功
app.$toast(result.message)
// 隐藏当前弹窗
app.onChangeValue(false)
// 购物车商品总数量
const cartTotal = result.data.cartTotal
// 缓存购物车数量
setCartTotalNum(cartTotal)
// 传递给父级
app.$emit('addCart', cartTotal)
})
},
// 立即购买
buyNow(selectShop) {
// 跳转到订单结算页
this.$navTo('pages/checkout/index', {
mode: 'buyNow',
goodsId: selectShop.goods_id,
goodsSkuId: selectShop.goods_sku_id,
goodsNum: selectShop.buy_num
})
// 隐藏当前弹窗
this.onChangeValue(false)
}
}
}
</script>
<style lang="scss" scoped>
</style>
+146
View File
@@ -0,0 +1,146 @@
<template>
<!-- 商品图片 -->
<view class="images-swiper">
<swiper class="swiper-box" :autoplay="autoplay" :duration="duration" :indicator-dots="indicatorDots"
:interval="interval" :circular="true" @change="setCurrent">
<!-- 主图视频 -->
<swiper-item v-if="video">
<view class="slide-video">
<video id="myVideo" class="video" :poster="videoCover.preview_url" :src="video.external_url" controls
x5-playsinline playsinline webkit-playsinline webkit-playsinline x5-video-player-type="h5"
x5-video-player-fullscreen x5-video-orientation="portrait" :enable-progress-gesture="false"
@play="onVideoPlay"></video>
</view>
</swiper-item>
<!-- 轮播图片 -->
<swiper-item v-for="(item, index) in images" :key="index" @click="onPreviewImages(index)">
<view class="slide-image">
<image class="image" :draggable="false" :src="item.preview_url"></image>
</view>
</swiper-item>
</swiper>
<view class="swiper-count">
<text>{{ currentIndex }}</text>
<text>/</text>
<text>{{ images.length + (video ? 1 : 0) }}</text>
</view>
</view>
</template>
<script>
export default {
props: {
// 主图视频
video: {
type: Object,
default () {
return null
}
},
// 主图视频封面
videoCover: {
type: Object,
default () {
return null
}
},
// 图片轮播
images: {
type: Array,
default: []
}
},
data() {
return {
indicatorDots: true, // 是否显示面板指示点
autoplay: true, // 是否自动切换
interval: 4000, // 自动切换时间间隔
duration: 800, // 滑动动画时长
currentIndex: 1, // 轮播图指针
}
},
methods: {
// 事件:视频开始播放
onVideoPlay(e) {
this.autoplay = false
},
// 设置轮播图当前指针 数字
setCurrent({ detail }) {
const app = this
app.currentIndex = detail.current + 1
},
// 浏览商品图片
onPreviewImages(index) {
const app = this
const imageUrls = []
app.images.forEach(item => {
imageUrls.push(item.preview_url);
});
uni.previewImage({
current: imageUrls[index],
urls: imageUrls
})
}
}
}
</script>
<style lang="scss" scoped>
// swiper组件
.images-swiper {
position: relative;
}
.swiper-box {
width: 100%;
height: 100vw;
/* #ifdef H5 */
max-width: 480px;
max-height: 480px;
margin: 0 auto;
/* #endif */
// 主图视频
.slide-video {
width: 100%;
height: 100%;
.video {
display: block;
width: 100%;
height: 100%;
}
}
// 图片轮播
.slide-image {
position: relative;
width: 100%;
height: 100%;
.image {
display: block;
width: 100%;
height: 100%;
}
}
}
// swiper计数
.swiper-count {
position: absolute;
right: 36rpx;
bottom: 72rpx;
padding: 2rpx 18rpx;
background: rgba(0, 0, 0, 0.363);
border-radius: 50rpx;
color: #fff;
font-size: 26rpx;
}
</style>
+229
View File
@@ -0,0 +1,229 @@
.container {
// 设置ios刘海屏底部横线安全区域
// 110 - 18 + 4
padding-bottom: calc(constant(safe-area-inset-bottom) + 106rpx + 6rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 106rpx + 6rpx);
}
// 商品信息
.goods-info {
background: #fff;
padding: 25rpx 30rpx;
}
.info-item__top {
min-height: 40rpx;
margin-bottom: 20rpx;
line-height: 1;
}
.floor-price__samll {
font-size: 26rpx;
line-height: 1;
color: $main-bg;
margin-bottom: -10rpx;
}
// 商品价
.floor-price {
color: $main-bg;
margin-right: 15rpx;
font-size: 42rpx;
}
.original-price {
font-size: 26rpx;
text-decoration: line-through;
color: #959595;
margin-right: 15rpx;
margin-bottom: -6rpx;
}
// 会员价标签
.user-grade {
background: #3c3c3c;
border-radius: 6rpx;
padding: 8rpx 14rpx;
margin-right: 15rpx;
font-size: 24rpx;
color: #EEE0C3;
}
.goods-sales {
font-size: 24rpx;
color: #959595;
}
.info-item__name .goods-name {
font-size: 30rpx;
}
/* 商品分享 */
.goods-share__line {
border-left: 1rpx solid #f4f4f4;
height: 60rpx;
margin: 0 30rpx;
}
.goods-share .share-btn {
line-height: normal;
padding: 0;
background: none;
border-radius: 0;
box-shadow: none;
font-size: 8pt;
border: none;
color: #191919;
}
.goods-share .share-btn::after {
border: none;
}
.goods-share .share__icon {
font-size: 40rpx;
margin-bottom: 5rpx;
}
// 商品卖点
.info-item_selling-point {
margin-top: 8rpx;
font-size: 24rpx;
color: #808080;
}
// 选择商品规格
.goods-choice {
padding: 26rpx 30rpx;
font-size: 28rpx;
.spec-list {
display: flex;
align-items: center;
.spec-name {
margin-right: 10rpx;
}
}
}
// 商品详情
.goods-content .item-title {
padding: 26rpx 30rpx;
font-size: 28rpx;
}
// 底部操作栏
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
display: flex;
z-index: 11;
box-shadow: 0 -4rpx 40rpx 0 rgba(151, 151, 151, 0.24);
background: #fff;
// 设置ios刘海屏底部横线安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.footer-container {
width: 100%;
display: flex;
height: 106rpx;
}
// 快捷菜单
.foo-item-fast {
box-sizing: border-box;
min-width: 214rpx;
line-height: 1;
display: flex;
align-items: center;
justify-content: space-evenly;
margin-right: 12rpx;
.fast-item {
position: relative;
padding: 4rpx 0;
line-height: 1;
text-align: center;
width: 84rpx;
&--cart {
margin-left: 6rpx;
.fast-icon { margin-left: -12rpx; }
}
// 角标
.fast-badge {
display: inline-block;
box-sizing: border-box;
min-width: 16px;
padding: 0 3px;
color: #fff;
font-weight: 500;
font-size: 12px;
font-family: -apple-system-font, Helvetica Neue, Arial, sans-serif;
line-height: 1.2;
text-align: center;
background-color: #ee0a24;
border: 1px solid #fff;
border-radius: 999px;
}
.fast-badge--fixed {
position: absolute;
top: 0;
right: 0;
transform-origin: 100%
}
.fast-icon {
font-size: 44rpx;
margin-bottom: 8rpx;
}
.fast-text {
font-size: 22rpx;
}
}
}
// 操作按钮
.foo-item-btn {
flex: 1;
.btn-wrapper {
height: 100%;
display: flex;
align-items: center;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 72rpx;
margin-right: 16rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
// 立即购买按钮
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
}
// 购物车按钮
.btn-item-deputy {
background: linear-gradient(to right, $vice-bg, $vice-bg2);
color: $vice-text;
}
}
+328
View File
@@ -0,0 +1,328 @@
<template>
<view v-show="!isLoading" class="container" :style="appThemeStyle">
<!-- 商品图片轮播 -->
<SlideImage v-if="!isLoading" :video="goods.video" :videoCover="goods.videoCover" :images="goods.goods_images" />
<!-- 商品信息 -->
<view v-if="!isLoading" class="goods-info m-top20">
<!-- 价格销量 -->
<view class="info-item info-item__top dis-flex flex-x-between flex-y-end">
<view class="block-left dis-flex flex-y-center">
<!-- 商品售价 -->
<text class="floor-price__samll"></text>
<text class="floor-price">{{ goods.goods_price_min }}</text>
<!-- 会员价标签 -->
<view v-if="goods.is_user_grade" class="user-grade">
<text>会员价</text>
</view>
<!-- 划线价 -->
<text v-if="goods.line_price_min > 0" class="original-price">{{ goods.line_price_min }}</text>
</view>
<view class="block-right dis-flex">
<!-- 销量 -->
<view class="goods-sales">
<text>已售{{ goods.goods_sales }}</text>
</view>
</view>
</view>
<!-- 标题分享 -->
<view class="info-item info-item__name dis-flex flex-y-center">
<view class="goods-name flex-box">
<text class="twoline-hide">{{ goods.goods_name }}</text>
</view>
<view class="goods-share__line"></view>
<view class="goods-share">
<button class="share-btn dis-flex flex-dir-column" @click="onShowShareSheet()">
<text class="share__icon iconfont icon-fenxiang"></text>
<text class="f-24">分享</text>
</button>
</view>
</view>
<!-- 商品卖点 -->
<view v-if="goods.selling_point" class="info-item info-item_selling-point">
<text>{{ goods.selling_point }}</text>
</view>
</view>
<!-- 选择商品规格 -->
<view v-if="goods.spec_type == 20" class="goods-choice m-top20 b-f" @click="onShowSkuPopup(1)">
<view class="spec-list">
<view class="flex-box">
<text class="col-8">选择</text>
<text class="spec-name" v-for="(item, index) in goods.specList" :key="index">{{ item.spec_name }}</text>
</view>
<view class="f-26 col-9 t-r">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
</view>
<!-- 商品服务 -->
<Service v-if="!isLoading" :goods-id="goodsId" />
<!-- 商品SKU弹窗 -->
<SkuPopup v-if="!isLoading" v-model="showSkuPopup" :skuMode="skuMode" :goods="goods" @addCart="onAddCart" />
<!-- 商品评价 -->
<Comment v-if="!isLoading" :goods-id="goodsId" :limit="2" />
<!-- 商品描述 -->
<view v-if="!isLoading" class="goods-content m-top20">
<view class="item-title b-f">
<text>商品描述</text>
</view>
<block v-if="goods.content != ''">
<view class="goods-content__detail b-f">
<mp-html :content="goods.content" />
</view>
</block>
<empty v-else tips="亲,暂无商品描述" />
</view>
<!-- 商品推荐 -->
<recommended />
<!-- 底部选项卡 -->
<view class="footer-fixed">
<view class="footer-container">
<!-- 导航图标 -->
<view class="foo-item-fast">
<!-- 首页 -->
<view class="fast-item fast-item--home" @click="onTargetHome">
<view class="fast-icon">
<text class="iconfont icon-shouye"></text>
</view>
<view class="fast-text">
<text>首页</text>
</view>
</view>
<!-- 客服 -->
<customer-btn v-if="isShowCustomerBtn">
<view class="fast-item">
<view class="fast-icon">
<text class="iconfont icon-kefu1"></text>
</view>
<view class="fast-text">
<text>客服</text>
</view>
</view>
</customer-btn>
<!-- 购物车 -->
<view class="fast-item fast-item--cart" @click="onTargetCart">
<view v-if="cartTotal > 0" class="fast-badge fast-badge--fixed">{{ cartTotal > 99 ? '99+' : cartTotal }}
</view>
<view class="fast-icon">
<text class="iconfont icon-gouwuche"></text>
</view>
<view class="fast-text">
<text>购物车</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="foo-item-btn">
<view class="btn-wrapper">
<view v-if="isEnableCart" class="btn-item btn-item-deputy" @click="onShowSkuPopup(2)">
<text>加入购物车</text>
</view>
<view class="btn-item btn-item-main" @click="onShowSkuPopup(3)">
<text>立即购买</text>
</view>
</view>
</view>
</view>
</view>
<!-- 快捷导航 -->
<!-- <shortcut bottom="120rpx" /> -->
<!-- 分享菜单 -->
<share-sheet v-model="showShareSheet" :shareTitle="goods.goods_name" :shareImageUrl="goods.goods_image" :posterApiCall="posterApiCall" :posterApiParam="{ goodsId }" />
</view>
</template>
<script>
import { getSceneData } from '@/core/app'
import * as GoodsApi from '@/api/goods'
import * as CartApi from '@/api/cart'
import SettingModel from '@/common/model/Setting'
import { GoodsTypeEnum } from '@/common/enum/goods'
import Recommended from '@/components/recommended'
import ShareSheet from '@/components/share-sheet'
import CustomerBtn from '@/components/customer-btn'
import SlideImage from './components/SlideImage'
import SkuPopup from './components/SkuPopup'
import Comment from './components/Comment'
import Service from './components/Service'
export default {
components: {
Recommended,
ShareSheet,
CustomerBtn,
SlideImage,
SkuPopup,
Comment,
Service
},
data() {
return {
// 正在加载
isLoading: true,
// 当前商品ID
goodsId: null,
// 商品详情
goods: {},
// 购物车总数量
cartTotal: 0,
// 显示/隐藏SKU弹窗
showSkuPopup: false,
// 模式 1:都显示 2:只显示购物车 3:只显示立即购买
skuMode: 1,
// 显示/隐藏分享菜单
showShareSheet: false,
// 获取商品海报图api方法
posterApiCall: GoodsApi.poster,
// 是否支持加入购物车
isEnableCart: false,
// 是否显示在线客服按钮
isShowCustomerBtn: false
}
},
/**
* 生命周期函数--监听页面加载
*/
async onLoad(options) {
// 记录query参数
this.onRecordQuery(options)
// 加载页面数据
this.onRefreshPage()
// 是否显示在线客服按钮
this.isShowCustomerBtn = await SettingModel.isShowCustomerBtn()
},
methods: {
// 记录query参数
onRecordQuery(query) {
const scene = getSceneData(query)
this.goodsId = query.goodsId ? parseInt(query.goodsId) : parseInt(scene.gid)
},
// 刷新页面数据
onRefreshPage() {
const app = this
app.isLoading = true
Promise.all([app.getGoodsDetail(), app.getCartTotal()])
.finally(() => app.isLoading = false)
},
// 获取商品信息
getGoodsDetail() {
const app = this
return new Promise((resolve, reject) => {
GoodsApi.detail(app.goodsId)
.then(result => {
app.goods = result.data.detail
if (app.goods.goods_type == GoodsTypeEnum.PHYSICAL.value) {
app.isEnableCart = true
}
resolve(result)
})
.catch(reject)
})
},
// 获取购物车总数量
getCartTotal() {
const app = this
return new Promise((resolve, reject) => {
CartApi.total()
.then(result => {
app.cartTotal = result.data.cartTotal
resolve(result)
})
.catch(reject)
})
},
// 更新购物车数量
onAddCart(total) {
this.cartTotal = total
},
/**
* 显示/隐藏SKU弹窗
* @param {skuMode} 模式 1:都显示 2:只显示购物车 3:只显示立即购买
*/
onShowSkuPopup(skuMode = 1) {
const app = this
if (app.isEnableCart) {
app.skuMode = skuMode
} else {
app.skuMode = 3
}
app.showSkuPopup = !app.showSkuPopup
},
// 显示隐藏分享菜单
onShowShareSheet() {
this.showShareSheet = !this.showShareSheet
},
// 跳转到首页
onTargetHome(e) {
this.$navTo('pages/index/index')
},
// 跳转到购物车页
onTargetCart() {
this.$navTo('pages/cart/index')
},
},
/**
* 分享当前页面
*/
onShareAppMessage() {
const app = this
// 构建页面参数
const params = app.$getShareUrlParams({
goodsId: app.goodsId,
})
return {
title: app.goods.goods_name,
path: `/pages/goods/detail?${params}`
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
const app = this
// 构建页面参数
const params = app.$getShareUrlParams({
goodsId: app.goodsId,
})
return {
title: app.goods.goods_name,
path: `/pages/goods/detail?${params}`
}
}
}
</script>
<style>
page {
background: #fafafa;
}
</style>
<style lang="scss" scoped>
@import "./detail.scss";
</style>
+463
View File
@@ -0,0 +1,463 @@
<template>
<view class="container" :style="appThemeStyle">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ native: true }" @down="downCallback" :up="upOption"
@up="upCallback">
<!-- 页面头部 -->
<view class="header">
<search class="search" :tips="options.search ? options.search : '搜索商品'" @event="handleSearch" />
<!-- 切换列表显示方式 -->
<view class="show-view" @click="handleShowView">
<text class="iconfont icon-view-tile" v-if="showView"></text>
<text class="iconfont icon-view-list" v-else></text>
</view>
</view>
<!-- 排序标签 -->
<view class="store-sort">
<view class="sort-item" :class="{ active: sortType === 'all' }" @click="handleSortType('all')">
<text>综合</text>
</view>
<view class="sort-item" :class="{ active: sortType === 'sales' }" @click="handleSortType('sales')">
<text>销量</text>
</view>
<view class="sort-item sort-item-price" :class="{ active: sortType === 'price' }" @click="handleSortType('price')">
<text>价格</text>
<view class="price-arrow">
<view class="icon up" :class="{ active: sortType === 'price' && !sortPrice }">
<text class="iconfont icon-arrow-up"></text>
</view>
<view class="icon down" :class="{ active: sortType === 'price' && sortPrice }">
<text class="iconfont icon-arrow-down"></text>
</view>
</view>
</view>
</view>
<!-- 商品列表 -->
<view class="goods-list clearfix" :class="['column-' + (showView ? '1' : '2')]">
<view class="goods-item" v-for="(item, index) in list.data" :key="index" @click="onTargetDetail(item.goods_id)">
<!-- 单列显示 -->
<view v-if="showView" class="dis-flex">
<!-- 商品图片 -->
<view class="goods-item_left">
<image class="image" :src="item.goods_image"></image>
</view>
<view class="goods-item_right">
<!-- 商品名称 -->
<view class="goods-name">
<text class="twoline-hide">{{ item.goods_name }}</text>
</view>
<view class="goods-item_desc">
<!-- 商品卖点 -->
<view class="desc-selling_point dis-flex">
<text class="oneline-hide">{{ item.selling_point }}</text>
</view>
<!-- 商品销量 -->
<view class="desc-goods_sales dis-flex">
<text>已售{{ item.goods_sales }}</text>
</view>
<!-- 商品价格 -->
<view class="desc_footer">
<text class="price_x">¥{{ item.goods_price_min }}</text>
<text class="price_y col-9" v-if="item.line_price_min > 0">¥{{ item.line_price_min }}</text>
</view>
</view>
</view>
</view>
<!-- 多列显示 -->
<view v-else class="">
<!-- 商品图片 -->
<view class="goods-image">
<image class="image" mode="aspectFill" :src="item.goods_image"></image>
</view>
<view class="detail">
<!-- 商品名称 -->
<view class="goods-name">
<text class="twoline-hide">{{ item.goods_name }}</text>
</view>
<!-- 商品价格 -->
<view class="detail-price oneline-hide">
<text class="goods-price f-30 col-m">{{ item.goods_price_min }}</text>
<text v-if="item.line_price_min > 0" class="line-price col-9 f-24">{{ item.line_price_min }}</text>
</view>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import * as GoodsApi from '@/api/goods'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import Search from '@/components/search'
const pageSize = 15
const showViewKey = 'GoodsList-ShowView';
export default {
components: {
MescrollBody,
Search
},
mixins: [MescrollMixin],
data() {
return {
showView: false, // 列表显示方式 (true列表、false平铺)
sortType: 'all', // 排序类型
sortPrice: false, // 价格排序 (true高到低 false低到高)
options: {}, // 当前页面参数
list: getEmptyPaginateObj(), // 商品列表数据
// 上拉加载配置
upOption: {
// 首次自动执行
auto: true,
// 每页数据的数量; 默认10
page: { size: pageSize },
// 数量要大于4条才显示无更多数据
noMoreSize: 4,
}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 记录options
this.options = options
// 设置默认列表显示方式
this.setShowView()
},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
// 设置列表数据
app.getGoodsList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// 设置默认列表显示方式
setShowView() {
this.showView = uni.getStorageSync(showViewKey) || false
},
/**
* 获取商品列表
* @param {number} pageNo 页码
*/
getGoodsList(pageNo = 1) {
const app = this
console.log(app.options)
const param = {
sortType: app.sortType,
sortPrice: Number(app.sortPrice),
categoryId: app.options.categoryId || 0,
goodsName: app.options.search || '',
page: pageNo
}
return new Promise((resolve, reject) => {
GoodsApi.list(param)
.then(result => {
// 合并新数据
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
.catch(reject)
})
},
// 切换排序方式
handleSortType(newSortType) {
const app = this
const newSortPrice = newSortType === 'price' ? !app.sortPrice : true
app.sortType = newSortType
app.sortPrice = newSortPrice
// 刷新列表数据
app.list = getEmptyPaginateObj()
app.mescroll.resetUpScroll()
},
// 切换列表显示方式
handleShowView() {
const app = this
app.showView = !app.showView
uni.setStorageSync(showViewKey, app.showView)
},
// 跳转商品详情页
onTargetDetail(goodsId) {
this.$navTo('pages/goods/detail', { goodsId })
},
/**
* 商品搜索
*/
handleSearch() {
const searchPageUrl = 'pages/search/index'
// 判断来源页面
let pages = getCurrentPages()
if (pages.length > 1 &&
pages[pages.length - 2].route === searchPageUrl) {
uni.navigateBack()
return
}
// 跳转到商品搜索页
this.$navTo(searchPageUrl)
}
},
/**
* 设置分享内容
*/
onShareAppMessage() {
// 构建分享参数
return {
title: "全部分类",
path: "/pages/category/index?" + this.$getShareUrlParams()
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
// 构建分享参数
return {
title: "全部分类",
path: "/pages/category/index?" + this.$getShareUrlParams()
}
}
}
</script>
<style lang="scss" scoped>
// 页面头部
.header {
display: flex;
align-items: center;
background-color: #fff;
// 搜索框
.search {
flex: 1;
}
// 切换显示方式
.show-view {
width: 60rpx;
height: 60rpx;
font-size: 36rpx;
color: #505050;
display: flex;
align-items: center;
}
}
// 排序组件
.store-sort {
position: sticky;
top: var(--window-top);
display: flex;
padding: 20rpx 0;
font-size: 28rpx;
background: #fff;
color: #000;
z-index: 99;
.sort-item {
flex-basis: 33.3333%;
display: flex;
justify-content: center;
align-items: center;
height: 50rpx;
&.active {
color: $main-bg;
}
}
.sort-item-price .price-arrow {
margin-left: 20rpx;
font-size: 24rpx;
color: #000;
.icon {
&.active {
color: $main-bg;
}
&.up {
margin-bottom: -16rpx;
}
&.down {
margin-top: -16rpx;
}
}
}
}
// 商品列表
.goods-list {
padding: 4rpx;
box-sizing: border-box;
}
// 单列显示
.goods-list.column-1 {
.goods-item {
width: 100%;
height: 280rpx;
margin-bottom: 12rpx;
padding: 20rpx;
box-sizing: border-box;
background: #fff;
line-height: 1.6;
&:last-child {
margin-bottom: 0;
}
}
.goods-item_left {
display: flex;
width: 300rpx;
background: #fff;
align-items: center;
.image {
display: block;
width: 240rpx;
height: 240rpx;
}
}
.goods-item_right {
position: relative;
flex: 1;
.goods-name {
margin-top: 10rpx;
min-height: 68rpx;
line-height: 1.3;
white-space: normal;
color: #484848;
font-size: 26rpx;
}
}
.goods-item_desc {
margin-top: 8rpx;
}
.desc-selling_point {
width: 400rpx;
font-size: 24rpx;
color: #e49a3d;
}
.desc-goods_sales {
color: #999;
font-size: 24rpx;
}
.desc_footer {
font-size: 24rpx;
.price_x {
margin-right: 16rpx;
color: $main-bg;
font-size: 30rpx;
}
.price_y {
text-decoration: line-through;
}
}
}
// 平铺显示
.goods-list.column-2 {
.goods-item {
width: 50%;
}
}
.goods-item {
float: left;
box-sizing: border-box;
padding: 6rpx;
.goods-image {
position: relative;
width: 100%;
height: 0;
padding-bottom: 100%;
overflow: hidden;
background: #fff;
&:after {
content: '';
display: block;
margin-top: 100%;
}
.image {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
-o-object-fit: cover;
object-fit: cover;
}
}
.detail {
padding: 8rpx;
background: #fff;
.goods-name {
min-height: 68rpx;
line-height: 1.3;
white-space: normal;
color: #484848;
font-size: 26rpx;
margin-bottom: 4rpx;
}
.detail-price {
.goods-price {
margin-right: 8rpx;
}
.line-price {
text-decoration: line-through;
}
}
}
}
</style>
+214
View File
@@ -0,0 +1,214 @@
<template>
<goods-sku-popup :value="value" @input="onChangeValue" border-radius="20" :localdata="goodsInfo" :mode="skuMode" :maskCloseAble="true"
:priceColor="appTheme.mainBg" :buyNowBackgroundColor="appTheme.mainBg" :addCartColor="appTheme.viceText" :addCartBackgroundColor="appTheme.viceBg"
:activedStyle="{ color: appTheme.mainBg, borderColor: appTheme.mainBg, backgroundColor: activedBtnBackgroundColor }"
@open="openSkuPopup" @close="closeSkuPopup" @buy-now="buyNow" buyNowText="立即购买" :maxBuyNum="maxBuyNum" :buyMode="buyMode" :isJoin="!!taskId"
:stepPeople="stepPeople" />
</template>
<script>
import { hex2rgba } from '@/utils/color'
import * as TaskApi from '@/api/bargain/task'
import GoodsSkuPopup from './goods-sku-popup'
export default {
components: {
GoodsSkuPopup
},
model: {
prop: 'value',
event: 'input'
},
props: {
// 显示隐藏
value: {
Type: Boolean,
default: false
},
// 模式 1:都显示 2:只显示购物车 3:只显示立即购买
skuMode: {
type: Number,
default: 1
},
// 商品详情信息
goods: {
type: Object,
default: {}
},
// 购买模式 1:拼团购买 2:单独购买
buyMode: {
type: Number,
default: 1
},
// 拼单ID (仅参与拼单时传入)
taskId: {
type: Number,
default: undefined
},
// 阶梯团人数 (仅参与拼单时传入)
stepPeople: {
type: Number,
default: undefined
},
},
computed: {
// 规格按钮选中时的背景色
activedBtnBackgroundColor() {
return hex2rgba(this.appTheme.mainBg, 0.1)
}
},
watch: {
buyMode(val) {
this.init()
}
},
data() {
return {
// 商品信息
goodsInfo: {},
// 限购数量
maxBuyNum: null
}
},
created() {
this.init()
},
methods: {
// 初始化SKU数据
init() {
const app = this
const { goods } = app
app.goodsInfo = {
_id: goods.goods_id,
name: goods.goods_name,
goods_thumb: goods.goods_image,
sku_list: app.getSkuList(),
spec_list: app.getSpecList(),
active_type: goods.active_type,
steps_config: goods.steps_config,
}
app.maxBuyNum = app.getMaxBuyNum()
},
// 监听组件显示隐藏
onChangeValue(val) {
this.$emit('input', val)
},
// 整理商品SKU列表
getSkuList() {
const app = this
const { goods: { goods_name, goods_image, skuList } } = app
const skuData = []
skuList.forEach(item => {
skuData.push({
_id: item.id,
goods_sku_id: item.goods_sku_id,
goods_id: item.goods_id,
goods_name: goods_name,
image: item.image_url ? item.image_url : goods_image,
price: item.groupon_price,
stock: item.stock_num,
spec_value_ids: item.spec_value_ids,
sku_name_arr: app.getSkuNameArr(item.spec_value_ids),
groupon_price: item.groupon_price,
original_price: item.original_price,
steps_price_config: item.steps_price_config,
})
})
return skuData
},
// 获取sku记录的规格值列表
getSkuNameArr(specValueIds) {
const app = this
const defaultData = ['默认']
const skuNameArr = []
if (specValueIds) {
specValueIds.forEach((valueId, groupIndex) => {
const specValueName = app.getSpecValueName(valueId, groupIndex)
skuNameArr.push(specValueName)
})
}
return skuNameArr.length ? skuNameArr : defaultData
},
// 获取指定的规格值名称
getSpecValueName(valueId, groupIndex) {
const app = this
const { goods: { specList } } = app
const res = specList[groupIndex].valueList.find(specValue => {
return specValue.spec_value_id == valueId
})
return res.spec_value
},
// 整理规格数据
getSpecList() {
const { goods: { specList, steps_config } } = this
const defaultData = [{ name: '默认', list: [{ name: '默认' }] }]
const specData = []
specList.forEach(group => {
const children = []
group.valueList.forEach(specValue => {
children.push({ name: specValue.spec_value })
})
specData.push({
name: group.spec_name,
list: children
})
})
return specData.length ? specData : defaultData
},
// 限购数量
getMaxBuyNum() {
const { goods, buyMode } = this
return (buyMode == 1 && goods.is_restrict) ? goods.restrict_single : null
},
// sku组件 开始-----------------------------------------------------------
openSkuPopup() {
// console.log("监听 - 打开sku组件")
},
closeSkuPopup() {
// console.log("监听 - 关闭sku组件")
},
// 立即购买
buyNow(selectShop) {
const app = this
// 跳转到订单结算页
app.$navTo('pages/checkout/index', app.getOrderParam(selectShop))
// 隐藏当前弹窗
app.onChangeValue(false)
},
// 生成下单参数
getOrderParam(selectShop) {
const { goods, buyMode, taskId } = this
const param = {
goodsSkuId: selectShop.goods_sku_id,
goodsNum: selectShop.buy_num
}
if (buyMode == 1) {
param.mode = 'groupon'
param.grouponGoodsId = goods.groupon_goods_id
param.taskId = taskId
param.stepPeople = selectShop.stepPeople
} else {
param.mode = 'buyNow'
param.goodsId = goods.goods_id
}
return param
}
}
}
</script>
<style lang="scss" scoped>
</style>
+193
View File
@@ -0,0 +1,193 @@
<template>
<!-- 进行中的团购 -->
<view v-if="list.length" class="goods-task m-top20" :style="appThemeStyle">
<view class="item-title dis-flex">
<view class="block-left flex-box">
<text>进行中的团购</text>
</view>
<view class="block-right">
<text class="iconfont icon-arrow-right col-9" @click="onShowMore()"></text>
</view>
</view>
<!-- 拼单列表 -->
<view class="task-list">
<view class="task-item" v-for="(item, index) in list" :key="index">
<view class="user-info">
<avatar-image class="user-avatar" :url="item.user.avatar_url" :width="60" />
<text class="user-name oneline-hide">{{ item.user.nick_name }}</text>
</view>
<view class="task-status">
<view class="people">
<text>还差</text>
<text class="col-m">{{ item.surplus_people }}</text>
<text>成团</text>
</view>
<view class="count-down">
<text>剩余</text>
<count-down :date="item.end_time" separator="colon" theme="text" />
</view>
</view>
<view class="item-action">
<view class="button" @click="onTargetTask(item.task_id)">去参团</view>
</view>
</view>
</view>
<!-- 更多拼单弹窗 -->
<u-modal v-model="showMore" title="可参与的拼单">
<scroll-view style="max-height: 610rpx; touch-action: none;" :scroll-y="true">
<view class="pops-content">
<view class="task-item" v-for="(item, index) in moreList" :key="index">
<view class="user-info">
<avatar-image class="user-avatar" :url="item.user.avatar_url" :width="60" />
<text class="user-name oneline-hide">{{ item.user.nick_name }}</text>
</view>
<view class="item-action">
<view class="button" @click="onTargetTask(item.task_id)">去参团</view>
</view>
</view>
</view>
</scroll-view>
</u-modal>
</view>
</template>
<script>
import AvatarImage from '@/components/avatar-image'
import CountDown from '@/components/countdown'
import * as TaskApi from '@/api/groupon/task'
export default {
components: {
AvatarImage,
CountDown
},
props: {
// 拼团商品ID
grouponGoodsId: {
type: Number,
default: null
},
// 商品ID
list: {
type: Array,
default: []
}
},
data() {
return {
// 显示更多拼单
showMore: false,
// 更多拼单列表
moreList: []
}
},
methods: {
// 显示更多拼单
onShowMore() {
const app = this
if (!app.moreList.length) {
TaskApi.listByGoods(app.grouponGoodsId)
.then(result => {
app.moreList = result.data.list
app.showMore = true
})
} else {
app.showMore = true
}
},
// 跳转到评论列表页
onTargetTask(taskId) {
this.$navTo('pages/groupon/task/index', { taskId })
}
}
}
</script>
<style lang="scss" scoped>
.goods-task {
padding: 20rpx 30rpx;
background-color: #fff;
}
.item-title {
font-size: 28rpx;
margin-bottom: 24rpx;
}
.task-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 6rpx;
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0 !important;
border-bottom: none;
}
}
.user-info {
width: 260rpx;
display: flex;
align-items: center;
.user-avatar {
margin-right: 10rpx;
}
.user-name {
font-size: 28rpx;
}
}
.task-status {
width: 250rpx;
padding-left: 36rpx;
font-size: 26rpx;
.people {
margin-bottom: 10rpx;
}
.count-down {
display: flex;
color: #999;
font-size: 24rpx;
}
}
.item-action {
.button {
padding: 0 24rpx;
line-height: 52rpx;
text-align: center;
font-size: 28rpx;
border-radius: 40rpx;
color: #fff;
background: $main-bg;
}
}
// 更多拼单
.pops-content {
padding: 40rpx 30rpx;
.task-item {
margin-bottom: 44rpx;
}
.user-avatar {
margin-right: 30rpx;
}
}
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,450 @@
<!-- 步进器 -->
<template>
<view class="number-box">
<view class="u-icon-minus" @touchstart.prevent="btnTouchStart('minus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal <= min }"
:style="{
background: bgColor,
height: inputHeight + 'rpx',
color: color,
fontSize: size + 'rpx',
minHeight: '1.4em'
}">
<view :style="'font-size:'+(Number(size)+10)+'rpx'" class="num-btn"></view>
</view>
<input :disabled="disabledInput || disabled" :cursor-spacing="getCursorSpacing" :class="{ 'u-input-disabled': disabled }"
v-model="inputVal" class="u-number-input" @blur="onBlur"
type="number" :style="{
color: color,
fontSize: size + 'rpx',
background: bgColor,
height: inputHeight + 'rpx',
width: inputWidth + 'rpx',
}" />
<view class="u-icon-plus" @touchstart.prevent="btnTouchStart('plus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal >= max }"
:style="{
background: bgColor,
height: inputHeight + 'rpx',
color: color,
fontSize: size + 'rpx',
minHeight: '1.4em',
}">
<view :style="'font-size:'+(Number(size)+10)+'rpx'" class="num-btn"></view>
</view>
</view>
</template>
<script>
/**
* numberBox 步进器
* @description 该组件一般用于商城购物选择物品数量的场景。注意:该输入框只能输入大于或等于0的整数,不支持小数输入
* @tutorial https://www.uviewui.com/components/numberBox.html
* @property {Number} value 输入框初始值(默认1)
* @property {String} bg-color 输入框和按钮的背景颜色(默认#F2F3F5)
* @property {Number} min 用户可输入的最小值(默认0)
* @property {Number} max 用户可输入的最大值(默认99999)
* @property {Number} step 步长,每次加或减的值(默认1)
* @property {Number} stepFirst 步进值,首次增加或最后减的值(默认step值和一致)
* @property {Boolean} disabled 是否禁用操作,禁用后无法加减或手动修改输入框的值(默认false)
* @property {Boolean} disabled-input 是否禁止输入框手动输入值(默认false)
* @property {Boolean} positive-integer 是否只能输入正整数(默认true)
* @property {String | Number} size 输入框文字和按钮字体大小,单位rpx(默认26)
* @property {String} color 输入框文字和加减按钮图标的颜色(默认#323233)
* @property {String | Number} input-width 输入框宽度,单位rpx(默认80)
* @property {String | Number} input-height 输入框和按钮的高度,单位rpx(默认50)
* @property {String | Number} index 事件回调时用以区分当前发生变化的是哪个输入框
* @property {Boolean} long-press 是否开启长按连续递增或递减(默认true)
* @property {String | Number} press-time 开启长按触发后,每触发一次需要多久,单位ms(默认250)
* @property {String | Number} cursor-spacing 指定光标于键盘的距离,避免键盘遮挡输入框,单位rpx(默认200)
* @event {Function} change 输入框内容发生变化时触发,对象形式
* @event {Function} blur 输入框失去焦点时触发,对象形式
* @event {Function} minus 点击减少按钮时触发(按钮可点击情况下),对象形式
* @event {Function} plus 点击增加按钮时触发(按钮可点击情况下),对象形式
* @example <number-box :min="1" :max="100"></number-box>
*/
export default {
name: "NumberBox",
emits: ["update:modelValue", "input", "change", "blur", "plus", "minus"],
props: {
// 预显示的数字
value: {
type: Number,
default: 1
},
modelValue: {
type: Number,
default: 1
},
// 背景颜色
bgColor: {
type: String,
default: '#F2F3F5'
},
// 最小值
min: {
type: Number,
default: 0
},
// 最大值
max: {
type: Number,
default: 99999
},
// 步进值,每次加或减的值
step: {
type: Number,
default: 1
},
// 步进值,首次增加或最后减的值
stepFirst: {
type: Number,
default: 0
},
// 是否只能输入 step 的倍数
stepStrictly: {
type: Boolean,
default: false
},
// 是否禁用加减操作
disabled: {
type: Boolean,
default: false
},
// input的字体大小,单位rpx
size: {
type: [Number, String],
default: 26
},
// 加减图标的颜色
color: {
type: String,
default: '#323233'
},
// input宽度,单位rpx
inputWidth: {
type: [Number, String],
default: 80
},
// input高度,单位rpx
inputHeight: {
type: [Number, String],
default: 50
},
// index索引,用于列表中使用,让用户知道是哪个numberbox发生了变化,一般使用for循环出来的index值即可
index: {
type: [Number, String],
default: ''
},
// 是否禁用输入框,与disabled作用于输入框时,为OR的关系,即想要禁用输入框,又可以加减的话
// 设置disabled为falsedisabledInput为true即可
disabledInput: {
type: Boolean,
default: false
},
// 输入框于键盘之间的距离
cursorSpacing: {
type: [Number, String],
default: 100
},
// 是否开启长按连续递增或递减
longPress: {
type: Boolean,
default: true
},
// 开启长按触发后,每触发一次需要多久
pressTime: {
type: [Number, String],
default: 250
},
// 是否只能输入大于或等于0的整数(正整数)
positiveInteger: {
type: Boolean,
default: true
}
},
watch: {
value(v1, v2) {
// 只有value的改变是来自外部的时候,才去同步inputVal的值,否则会造成循环错误
if(!this.changeFromInner) {
this.inputVal = v1;
// 因为inputVal变化后,会触发this.handleChange(),在其中changeFromInner会再次被设置为true
// 造成外面修改值,也导致被认为是内部修改的混乱,这里进行this.$nextTick延时,保证在运行周期的最后处
// 将changeFromInner设置为false
this.$nextTick(function(){
this.changeFromInner = false;
})
}
},
modelValue(v1, v2) {
// 只有value的改变是来自外部的时候,才去同步inputVal的值,否则会造成循环错误
if(!this.changeFromInner) {
this.inputVal = v1;
// 因为inputVal变化后,会触发this.handleChange(),在其中changeFromInner会再次被设置为true
// 造成外面修改值,也导致被认为是内部修改的混乱,这里进行this.$nextTick延时,保证在运行周期的最后处
// 将changeFromInner设置为false
this.$nextTick(function(){
this.changeFromInner = false;
})
}
},
inputVal(v1, v2) {
// 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串
if (v1 == '') return;
let value = 0;
// 首先判断是否数值,并且在min和max之间,如果不是,使用原来值
let tmp = this.isNumber(v1);
if (tmp && v1 >= this.min && v1 <= this.max) value = v1;
else value = v2;
// 判断是否只能输入大于等于0的整数
if(this.positiveInteger) {
// 小于0,或者带有小数点,
if(v1 < 0 || String(v1).indexOf('.') !== -1) {
value = v2;
// 双向绑定input的值,必须要使用$nextTick修改显示的值
this.$nextTick(() => {
this.inputVal = v2;
})
}
}
// 发出change事件
this.handleChange(value, 'change');
},
min(v1){
if(v1 !== undefined && v1!="" && this.getValue() < v1){
this.$emit("input",v1);
}
},
max(v1){
if(v1 !== undefined && v1!="" && this.getValue() > v1){
this.$emit("input",v1);
}
}
},
data() {
return {
inputVal: 1, // 输入框中的值,不能直接使用props中的value,因为应该改变props的状态
timer: null, // 用作长按的定时器
changeFromInner: false, // 值发生变化,是来自内部还是外部
innerChangeTimer: null, // 内部定时器
};
},
created() {
this.inputVal = Number(this.getValue());
},
computed: {
getCursorSpacing() {
// 先将值转为px单位,再转为数值
return Number(uni.upx2px(this.cursorSpacing));
}
},
methods: {
getValue(){
// #ifndef VUE3
return this.value;
// #endif
// #ifdef VUE3
return this.modelValue;
// #endif
},
// 点击退格键
btnTouchStart(callback) {
// 先执行一遍方法,否则会造成松开手时,就执行了clearTimer,导致无法实现功能
this[callback]();
// 如果没开启长按功能,直接返回
if (!this.longPress) return;
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
this.timer = null;
this.timer = setInterval(() => {
// 执行加或减函数
this[callback]();
}, this.pressTime);
},
clearTimer() {
this.$nextTick(() => {
clearInterval(this.timer);
this.timer = null;
})
},
minus() {
this.computeVal('minus');
},
plus() {
this.computeVal('plus');
},
// 为了保证小数相加减出现精度溢出的问题
calcPlus(num1, num2) {
let baseNum, baseNum1, baseNum2;
try {
baseNum1 = num1.toString().split('.')[1].length;
} catch (e) {
baseNum1 = 0;
}
try {
baseNum2 = num2.toString().split('.')[1].length;
} catch (e) {
baseNum2 = 0;
}
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2; //精度
return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision);
},
// 为了保证小数相加减出现精度溢出的问题
calcMinus(num1, num2) {
let baseNum, baseNum1, baseNum2;
try {
baseNum1 = num1.toString().split('.')[1].length;
} catch (e) {
baseNum1 = 0;
}
try {
baseNum2 = num2.toString().split('.')[1].length;
} catch (e) {
baseNum2 = 0;
}
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2;
return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision);
},
computeVal(type) {
uni.hideKeyboard();
if (this.disabled) return;
let value = 0;
// 新增stepFirst开始
// 减
if (type === 'minus') {
if(this.stepFirst > 0 && this.inputVal == this.stepFirst){
value = this.min;
}else{
value = this.calcMinus(this.inputVal, this.step);
}
} else if (type === 'plus') {
if(this.stepFirst > 0 && this.inputVal < this.stepFirst){
value = this.stepFirst;
}else{
value = this.calcPlus(this.inputVal, this.step);
}
}
if(this.stepStrictly){
let strictly = value % this.step;
if(strictly > 0){
value -= strictly;
}
}
if (value > this.max ) {
value = this.max;
}else if (value < this.min) {
value = this.min;
}
// 新增stepFirst结束
this.inputVal = value;
this.handleChange(value, type);
},
// 处理用户手动输入的情况
onBlur(event) {
let val = 0;
let value = event.detail.value;
// 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值
// 这里不直接判断是否正整数,是因为用户传递的props min值可能为0
if (!/(^\d+$)/.test(value) || value[0] == 0) val = this.min;
val = +value;
// 新增stepFirst开始
if(this.stepFirst > 0 && this.inputVal < this.stepFirst && this.inputVal>0){
val = this.stepFirst;
}
// 新增stepFirst结束
if(this.stepStrictly){
let strictly = val % this.step;
if(strictly > 0){
val -= strictly;
}
}
if (val > this.max) {
val = this.max;
} else if (val < this.min) {
val = this.min;
}
this.$nextTick(() => {
this.inputVal = val;
})
this.handleChange(val, 'blur');
},
handleChange(value, type) {
if (this.disabled) return;
// 清除定时器,避免造成混乱
if(this.innerChangeTimer) {
clearTimeout(this.innerChangeTimer);
this.innerChangeTimer = null;
}
// 发出input事件,修改通过v-model绑定的值,达到双向绑定的效果
this.changeFromInner = true;
// 一定时间内,清除changeFromInner标记,否则内部值改变后
// 外部通过程序修改value值,将会无效
this.innerChangeTimer = setTimeout(() => {
this.changeFromInner = false;
}, 150);
this.$emit('input', Number(value));
this.$emit("update:modelValue", Number(value));
this.$emit(type, {
// 转为Number类型
value: Number(value),
index: this.index
})
},
/**
* 验证十进制数字
*/
isNumber(value) {
return /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value)
}
}
};
</script>
<style lang="scss" scoped>
.number-box {
display: inline-flex;
align-items: center;
}
.u-number-input {
position: relative;
text-align: center;
padding: 0;
margin: 0 6rpx;
display: flex;
align-items: center;
justify-content: center;
}
.u-icon-plus,
.u-icon-minus {
width: 60rpx;
display: flex;
justify-content: center;
align-items: center;
}
.u-icon-plus {
border-radius: 0 8rpx 8rpx 0;
}
.u-icon-minus {
border-radius: 8rpx 0 0 8rpx;
}
.u-icon-disabled {
color: #c8c9cc !important;
background: #f7f8fa !important;
}
.u-input-disabled {
color: #c8c9cc !important;
background-color: #f2f3f5 !important;
}
.num-btn{
font-weight:550;
position: relative;
top:-4rpx;
}
</style>
+382
View File
@@ -0,0 +1,382 @@
<template>
<view v-show="!isLoading" class="container" :style="appThemeStyle">
<!-- 商品图片轮播 -->
<SlideImage v-if="!isLoading" :video="goods.video" :videoCover="goods.videoCover" :images="goods.goods_images" />
<!-- 商品信息 -->
<view v-if="!isLoading" class="goods-info m-top20">
<!-- 价格销量 -->
<view class="info-item info-item__top dis-flex flex-x-between flex-y-end">
<view class="block-left dis-flex flex-y-center">
<view class="active-tag">
<text>{{ goods.active_type != ActiveTypeEnum.NORMAL.value ? ActiveTypeEnum[goods.active_type].name2 : '多人拼团' }}</text>
</view>
<!-- 拼团价 -->
<text class="floor-price__samll"></text>
<text class="floor-price">{{ goods.groupon_price }}</text>
<!-- 商品原价 -->
<text class="original-price">{{ goods.original_price }}</text>
</view>
<view class="block-right dis-flex">
<!-- 销量 -->
<view class="goods-sales">
<text>已抢{{ goods.active_sales }}</text>
</view>
</view>
</view>
<!-- 标题分享 -->
<view class="info-item info-item__name dis-flex flex-y-center">
<view class="goods-name flex-box">
<text class="twoline-hide">{{ goods.goods_name }}</text>
</view>
<view class="goods-share__line"></view>
<view class="goods-share">
<button class="share-btn dis-flex flex-dir-column" @click="onShowShareSheet()">
<text class="share__icon iconfont icon-fenxiang"></text>
<text class="f-24">分享</text>
</button>
</view>
</view>
<!-- 商品卖点 -->
<view v-if="goods.selling_point" class="info-item info-item_selling-point">
<text>{{ goods.selling_point }}</text>
</view>
<!-- 活动倒计时 -->
<view v-if="goods.active_status != ActiveStatusEnum.STATE_END.value" class="info-item info-item_status info-item_countdown dis-flex flex-y-center">
<text class="countdown-icon iconfont icon-naozhong"></text>
<text>距离拼团活动{{ goods.active_status == ActiveStatusEnum.STATE_SOON.value ? '开始' : '结束' }}</text>
<text class="m-r-10">还剩</text>
<count-down :date="goods.end_time" separator="zh" theme="text" />
</view>
<!-- 活动已结束 -->
<view v-else class="info-item info-item_status info-item_end">
<text class="countdown-icon iconfont icon-naozhong"></text>
<text>拼团活动已结束下次记得早点来哦~</text>
</view>
</view>
<!-- 凑团信息 -->
<TaskList v-if="!isLoading" :grouponGoodsId="goods.groupon_goods_id" :list="goods.taskQuickJoinList" />
<!-- 选择商品规格 -->
<view v-if="goods.spec_type == 20" class="goods-choice m-top20 b-f" @click="onShowSkuPopup(1)">
<view class="spec-list">
<view class="flex-box">
<text class="col-8">选择</text>
<text class="spec-name" v-for="(item, index) in goods.specList" :key="index">{{ item.spec_name }}</text>
</view>
<view class="f-26 col-9 t-r">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
</view>
<!-- 拼团玩法 -->
<view class="rule-nav m-top20 b-f" @click="handleShowRules()">
<view class="top-nav dis-flex flex-x-between">
<text>拼团玩法</text>
<text class="f-25 col-9">查看规则</text>
</view>
<!-- 拼团步骤 -->
<view class="rule-simple dis-flex flex-x-around">
<view class="simple-item dis-flex flex-dir-column flex-y-center">
<view class="i-number dis-flex flex-x-center flex-y-center">
<text class="f-30">1</text>
</view>
<view class="i-text f-28">选择商品</view>
</view>
<view class="simple-item dis-flex flex-dir-column flex-y-center">
<view class="i-number dis-flex flex-x-center flex-y-center">
<text class="f-30">2</text>
</view>
<view class="i-text f-28">开团/参团</view>
</view>
<view class="simple-item dis-flex flex-dir-column flex-y-center">
<view class="i-number dis-flex flex-x-center flex-y-center">
<text class="f-30">3</text>
</view>
<view class="i-text f-28">邀请好友</view>
</view>
<view class="simple-item dis-flex flex-dir-column flex-y-center">
<view class="i-number dis-flex flex-x-center flex-y-center">
<text class="f-30">4</text>
</view>
<view class="i-text f-28">人满成团</view>
</view>
</view>
</view>
<!-- 商品SKU弹窗 -->
<SkuPopup v-if="!isLoading" v-model="showSkuPopup" :skuMode="skuMode" :goods="goods" :buyMode="buyMode" />
<!-- 商品评价 -->
<Comment v-if="!isLoading" :goods-id="goods.goods_id" :limit="2" />
<!-- 商品描述 -->
<view v-if="!isLoading" class="goods-content m-top20">
<view class="item-title b-f">
<text>商品描述</text>
</view>
<block v-if="goods.content != ''">
<view class="goods-content__detail b-f">
<mp-html :content="goods.content" />
</view>
</block>
<empty v-else tips="亲,暂无商品描述" />
</view>
<!-- 底部选项卡 -->
<view class="footer-fixed">
<view class="footer-container">
<!-- 导航图标 -->
<view class="foo-item-fast">
<!-- 首页 -->
<view class="fast-item fast-item--home" @click="onTargetHome">
<view class="fast-icon">
<text class="iconfont icon-shouye"></text>
</view>
<view class="fast-text">
<text>首页</text>
</view>
</view>
<!-- 客服 -->
<customer-btn v-if="isShowCustomerBtn">
<view class="fast-item">
<view class="fast-icon">
<text class="iconfont icon-kefu1"></text>
</view>
<view class="fast-text">
<text>客服</text>
</view>
</view>
</customer-btn>
<!-- 购物车 (客服按钮不显示时) -->
<view v-if="!isShowCustomerBtn" class="fast-item fast-item--cart" @click="onTargetCart">
<view v-if="cartTotal > 0" class="fast-badge fast-badge--fixed">{{ cartTotal > 99 ? '99+' : cartTotal }}
</view>
<view class="fast-icon">
<text class="iconfont icon-gouwuche"></text>
</view>
<view class="fast-text">
<text>购物车</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="foo-item-btn">
<view class="btn-wrapper">
<block v-if="goods.active_status == ActiveStatusEnum.STATE_BEGIN.value">
<view v-if="goods.is_alone_buy" class="btn-item btn-item-deputy" @click="onShowSkuPopup(2)">
<view class="price">{{ goods.original_price }}</view>
<view>单独购买</view>
</view>
<view class="btn-item btn-item-main" @click="onShowSkuPopup(1)">
<view class="price">{{ goods.groupon_price }}</view>
<view>发起拼团</view>
</view>
</block>
<view v-else class="btn-item btn-item-gray">
<text>{{ goods.active_status == ActiveStatusEnum.STATE_SOON.value ? '活动未开始' : '活动已结束' }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 分享菜单 -->
<share-sheet v-model="showShareSheet" :shareTitle="goods.goods_name" :shareImageUrl="goods.goods_image" :posterApiCall="posterApiCall" :posterApiParam="{ grouponGoodsId }" />
<!-- 拼团规则弹窗 -->
<u-modal v-if="!isLoading" v-model="showRules" title="拼团规则">
<scroll-view style="height: 610rpx; touch-action: none;" :scroll-y="true">
<view class="pops-content">
<text>{{ setting.ruleDetail }}</text>
</view>
</scroll-view>
</u-modal>
</view>
</template>
<script>
import { getSceneData } from '@/core/app'
import ShareSheet from '@/components/share-sheet'
import CustomerBtn from '@/components/customer-btn'
import SkuPopup from './components/SkuPopup'
import TaskList from './components/TaskList'
import SlideImage from '../../goods/components/SlideImage'
import Comment from '../../goods/components/Comment'
import CountDown from '@/components/countdown'
import * as GrouponGoodsApi from '@/api/groupon/goods'
import * as CartApi from '@/api/cart'
import SettingModel from '@/common/model/Setting'
import { ActiveTypeEnum, ActiveStatusEnum } from '@/common/enum/groupon'
export default {
components: {
ShareSheet,
CustomerBtn,
SlideImage,
TaskList,
SkuPopup,
Comment,
CountDown
},
data() {
return {
// 正在加载
isLoading: true,
// 枚举类
ActiveTypeEnum,
ActiveStatusEnum,
// 显示/隐藏SKU弹窗
showSkuPopup: false,
// 按钮模式 1:都显示 2:只显示购物车 3:只显示立即购买
skuMode: 3,
// 购买模式 1:拼团购买 2:单独购买
buyMode: 1,
// 显示/隐藏分享菜单
showShareSheet: false,
// 获取商品海报图api方法
posterApiCall: GrouponGoodsApi.poster,
// 显示拼团规则
showRules: false,
// 拼团规则内容
setting: {},
// 当前拼团商品ID
grouponGoodsId: null,
// 拼团商品详情
goods: {},
// 购物车总数量
cartTotal: 0,
// 是否显示在线客服按钮
isShowCustomerBtn: false
}
},
/**
* 生命周期函数--监听页面加载
*/
async onLoad(options) {
// 记录query参数
this.onRecordQuery(options)
// 加载页面数据
this.onRefreshPage()
// 是否显示在线客服按钮
this.isShowCustomerBtn = await SettingModel.isShowCustomerBtn()
},
methods: {
// 记录query参数
onRecordQuery(query) {
const scene = getSceneData(query)
this.grouponGoodsId = query.grouponGoodsId ? parseInt(query.grouponGoodsId) : parseInt(scene.gid)
},
// 刷新页面数据
onRefreshPage() {
const app = this
app.isLoading = true
Promise.all([app.getActiveDetail(), app.getCartTotal()])
.finally(() => app.isLoading = false)
},
// 获取拼团活动详情
getActiveDetail() {
const app = this
return new Promise((resolve, reject) => {
GrouponGoodsApi.detail(app.grouponGoodsId)
.then(result => {
app.goods = result.data.detail
app.setting = result.data.setting
resolve(result)
})
.catch(reject)
})
},
// 获取购物车总数量
getCartTotal() {
const app = this
return new Promise((resolve, reject) => {
CartApi.total()
.then(result => {
app.cartTotal = result.data.cartTotal
resolve(result)
})
.catch(reject)
})
},
// 显示拼团规则
handleShowRules() {
this.showRules = true
},
// 显示/隐藏SKU弹窗
onShowSkuPopup(buyMode = 1) {
this.buyMode = buyMode
this.showSkuPopup = !this.showSkuPopup
},
// 显示隐藏分享菜单
onShowShareSheet() {
this.showShareSheet = !this.showShareSheet
},
// 跳转到首页
onTargetHome(e) {
this.$navTo('pages/index/index')
},
// 跳转到购物车页
onTargetCart() {
this.$navTo('pages/cart/index')
},
},
/**
* 分享当前页面
*/
onShareAppMessage() {
// 构建页面参数
const app = this
const params = app.$getShareUrlParams({
grouponGoodsId: app.grouponGoodsId
})
return {
title: app.goods.goods_name,
path: `/pages/groupon/goods/index?${params}`
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
// 构建页面参数
const app = this
const params = app.$getShareUrlParams({
grouponGoodsId: app.grouponGoodsId
})
return {
title: app.goods.goods_name,
path: `/pages/groupon/goods/index?${params}`
}
},
}
</script>
<style>
page {
background: #fafafa;
}
</style>
<style lang="scss" scoped>
@import "./style.scss";
</style>
+325
View File
@@ -0,0 +1,325 @@
.container {
// 设置ios刘海屏底部横线安全区域
// 110 - 18 + 4
padding-bottom: calc(constant(safe-area-inset-bottom) + 106rpx + 6rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 106rpx + 6rpx);
}
// 商品信息
.goods-info {
background: #fff;
padding: 25rpx 30rpx;
}
.info-item__top {
min-height: 40rpx;
margin-bottom: 20rpx;
}
.info-item__top .active-tag {
color: #fff;
background: linear-gradient(to right, #ffa600, #f5b914);
padding: 4rpx 16rpx;
border-radius: 15rpx;
font-size: 26rpx;
text-align: center;
margin-right: 15rpx;
}
.floor-price__samll {
font-size: 26rpx;
line-height: 1;
color: $main-bg;
margin-bottom: -10rpx;
}
/* 商品价 */
.floor-price {
color: $main-bg;
margin-right: 15rpx;
font-size: 42rpx;
line-height: 1;
}
.original-price {
font-size: 26rpx;
line-height: 1;
text-decoration: line-through;
color: #959595;
margin-bottom: -6rpx;
}
.goods-sales {
font-size: 24rpx;
color: #959595;
}
.info-item__name .goods-name {
font-size: 30rpx;
}
/* 商品分享 */
.goods-share__line {
border-left: 1rpx solid #f4f4f4;
height: 60rpx;
margin: 0 30rpx;
}
.goods-share .share-btn {
line-height: normal;
padding: 0;
background: none;
border-radius: 0;
box-shadow: none;
font-size: 8pt;
border: none;
color: #191919;
}
.goods-share .share-btn::after {
border: none;
}
.goods-share .share__icon {
font-size: 40rpx;
margin-bottom: 5rpx;
}
/* 商品卖点 */
.info-item_selling-point {
margin-top: 8rpx;
font-size: 24rpx;
color: #808080;
}
// 选择商品规格
.goods-choice {
padding: 26rpx 30rpx;
font-size: 28rpx;
.spec-list {
display: flex;
align-items: center;
.spec-name {
margin-right: 10rpx;
}
}
}
/* 商品详情 */
.goods-content .item-title {
padding: 26rpx 30rpx;
font-size: 28rpx;
}
/* 底部操作栏 */
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
display: flex;
z-index: 11;
box-shadow: 0 -4rpx 40rpx 0 rgba(151, 151, 151, 0.24);
background: #fff;
// 设置ios刘海屏底部横线安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.footer-container {
width: 100%;
display: flex;
height: 106rpx;
}
// 快捷菜单
.foo-item-fast {
box-sizing: border-box;
min-width: 214rpx;
line-height: 1;
display: flex;
align-items: center;
justify-content: space-evenly;
margin-right: 12rpx;
.fast-item {
position: relative;
padding: 4rpx 0;
line-height: 1;
text-align: center;
width: 84rpx;
&--cart {
margin-left: 6rpx;
.fast-icon { margin-left: -12rpx; }
}
// 角标
.fast-badge {
display: inline-block;
box-sizing: border-box;
min-width: 16px;
padding: 0 3px;
color: #fff;
font-weight: 500;
font-size: 12px;
font-family: -apple-system-font, Helvetica Neue, Arial, sans-serif;
line-height: 1.2;
text-align: center;
background-color: #ee0a24;
border: 1px solid #fff;
border-radius: 999px;
}
.fast-badge--fixed {
position: absolute;
top: 0;
right: 0;
transform-origin: 100%
}
.fast-icon {
font-size: 44rpx;
margin-bottom: 8rpx;
}
.fast-text {
font-size: 22rpx;
}
}
}
// 操作按钮
.foo-item-btn {
flex: 1;
.btn-wrapper {
height: 100%;
display: flex;
align-items: center;
}
.btn-item {
flex: 1;
height: 100%;
font-size: 28rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 0;
color: #fff;
// 发起拼团
&.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
}
// 单独购买
&.btn-item-deputy {
background: linear-gradient(to right, $vice-bg, $vice-bg2);
color: $vice-text;
}
// 活动结束
&.btn-item-gray {
background-color: #ccc;
}
}
.price {
font-size: 28rpx;
margin-bottom: 4rpx;
}
}
// 活动状态
.info-item_status {
margin-top: 20rpx;
padding: 15rpx 20rpx;
font-size: 24rpx;
border-radius: 6rpx;
}
.info-item_status .countdown-icon {
font-size: 28rpx;
margin-right: 15rpx;
}
// 活动倒计时
.info-item_countdown {
background: #f0f9ff;
color: #8f8f8f;
}
.info-item_countdown .countdown-icon {
color: #1397d8;
}
// 活动已结束
.info-item_end {
background: #ccc;
color: #fff;
}
// 拼团玩法
.rule-nav {
padding: 24rpx;
font-size: 28rpx;
.rule-simple {
margin-top: 35rpx;
color: #737373;
}
.i-number {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-bottom: 15rpx;
border: 1rpx dashed #c0c0c0;
}
}
// 拼团玩法
.groupon-rules {
padding: 20rpx 0;
font-size: 29rpx;
.item-title {
padding: 0 30rpx;
}
.rule-simple {
margin-top: 35rpx;
color: #737373;
}
.i-number {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-bottom: 15rpx;
border: 1rpx dashed #c0c0c0;
}
}
// 拼团规则 (弹窗)
.pops-content {
padding: 30rpx 48rpx;
font-size: 28rpx;
line-height: 44rpx;
text-align: left;
color: #606266;
min-height: 320rpx;
max-height: 640rpx;
box-sizing: border-box;
}
+501
View File
@@ -0,0 +1,501 @@
<template>
<view class="container" :style="appThemeStyle">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption" @up="upCallback">
<!-- 拼团海报图 -->
<view v-if="curTab == 0 && setting.backdrop" class="banner">
<image class="image" :src="setting.backdrop.src" mode="widthFix"></image>
</view>
<!-- 拼团活动 -->
<view v-if="curTab == 0" class="groupon-hall active-list">
<view class="goods-item--container" v-for="(item, index) in goodsList.data" :key="index">
<view class="goods-item" @click="onTargetGoods(item)">
<!-- 商品图片 -->
<view class="goods-item_left">
<view v-if="item.active_type != ActiveTypeEnum.NORMAL.value" class="label">
<text>{{ ActiveTypeEnum[item.active_type].name2 }}</text>
</view>
<image class="image" :src="item.goods_image"></image>
</view>
<view class="goods-item_right">
<!-- 商品标题 -->
<view class="goods-name">
<text class="twoline-hide">{{ item.goods_name }}</text>
</view>
<!-- 商品信息 -->
<view class="goods-item_desc">
<view class="desc_situation">
<u-tag class="people" :color="appTheme.mainBg" :border-color="appTheme.mainBg" :text="`${item.show_people}人团`"
type="error" size="mini" mode="plain" />
<u-tag v-if="item.active_sales" :color="appTheme.mainBg" :border-color="tagBorderColor" :bg-color="tagBackgroundColor"
:text="`已团${item.active_sales}件`" type="error" size="mini" />
</view>
<view class="desc_footer">
<view class="item-prices oneline-hide">
<text class="price_x">¥{{ item.groupon_price }}</text>
<text class="price_y cl-9">¥{{ item.original_price }}</text>
</view>
<view class="settlement">去拼团</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 我的拼团 -->
<view v-if="curTab == 1" class="groupon-hall my-list">
<view class="goods-item--container" v-for="(item, index) in myList.data" :key="index">
<view class="goods-item" @click="onTargetTask(item)">
<!-- 商品图片 -->
<view class="goods-item_left">
<view v-if="item.active_type != ActiveTypeEnum.NORMAL.value" class="label">
<text>{{ ActiveTypeEnum[item.active_type].name2 }}</text>
</view>
<image class="image" :src="item.goods.goods_image"></image>
</view>
<view class="goods-item_right">
<!-- 商品标题 -->
<view class="goods-name">
<text class="twoline-hide">{{ item.goods.goods_name }}</text>
</view>
<!-- 商品信息 -->
<view class="goods-item_desc">
<view class="desc_situation">
<u-tag v-if="item.status == TaskStatusEnum.NORMAL.value" :text="`已拼${item.joined_people}人,还差${item.people - item.joined_people}人`"
type="warning" size="mini" />
<u-tag v-else class="people" :text="`${item.people}人团`" type="error" size="mini" mode="plain" />
</view>
<view class="desc_footer">
<view class="item-status">
<text>{{ TaskStatusEnum[item.status].name }}</text>
</view>
<view v-if="item.status == TaskStatusEnum.NORMAL.value" class="settlement">查看拼单</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 底部选项卡 -->
<view class="footer-fixed">
<view class="footer-container">
<!-- 拼团活动 -->
<view class="tabbar-item flex-box" :class="{ active: curTab == 0 }">
<view class="tabbar-item-content dis-flex flex-x-center flex-y-center" @click="onChangeTab(0)">
<view class="tabbar-item-icon">
<text class="iconfont icon-shangcheng"></text>
</view>
<view class="tabbar-item-name">
<text>拼团活动</text>
</view>
</view>
</view>
<!-- 分割线 -->
<view class="tabbar-item__divider">
<view class="divider-line"></view>
</view>
<!-- 我的砍价 -->
<view class="tabbar-item flex-box" :class="{ active: curTab == 1 }">
<view class="tabbar-item-content dis-flex flex-x-center flex-y-center" @click="onChangeTab(1)">
<view class="tabbar-item-icon">
<text class="iconfont icon-sy-yh"></text>
</view>
<view class="tabbar-item-name">
<text>我的拼团</text>
</view>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import { hex2rgba } from '@/utils/color'
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import { ActiveStatusEnum, ActiveTypeEnum, TaskStatusEnum } from '@/common/enum/groupon'
import * as TaskApi from '@/api/groupon/task'
import * as GoodsApi from '@/api/groupon/goods'
import SettingModel from '@/common/model/groupon/Setting'
const pageSize = 15
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
// 枚举类
ActiveStatusEnum,
ActiveTypeEnum,
TaskStatusEnum,
// 当前tab索引
curTab: 0,
// 上拉加载配置
upOption: {
// 首次自动执行
auto: true,
// 每页数据的数量; 默认10
page: { size: pageSize },
// 数量要大于3条才显示无更多数据
noMoreSize: 3,
},
// 拼团设置
setting: {},
// 拼团商品列表
goodsList: getEmptyPaginateObj(),
// 我的拼单列表
myList: getEmptyPaginateObj(),
}
},
watch: {
curTab(val) {
// 设置页面标题
uni.setNavigationBarTitle({ title: val == 0 ? '拼团活动' : '我的拼团' })
}
},
computed: {
// 标签背景色
tagBackgroundColor() {
return hex2rgba(this.appTheme.mainBg, 0.1)
},
// 标签边框颜色
tagBorderColor() {
return hex2rgba(this.appTheme.mainBg, 0.6)
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// 设置当前tab索引
if (options.tab) {
this.curTab = options.tab
}
// 获取拼团设置
this.getSetting()
},
methods: {
// 获取拼团设置
getSetting() {
SettingModel.data(true).then(setting => this.setting = setting)
},
/**
* 上拉加载的回调
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
// 设置列表数据
app.getListData(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// 获取列表数据(根据当前选项卡判断调用的方法)
getListData(pageNo) {
const apiFuc = {
0: this.getGoodsList,
1: this.getMyList
}
return apiFuc[this.curTab](pageNo)
},
/**
* 获取拼团商品列表
* @param {Number} pageNo 页码
*/
getGoodsList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
GoodsApi.list({ page: pageNo }, { load: false })
.then(result => {
// 合并新数据
const newList = result.data.list
app.goodsList.data = getMoreListData(newList, app.goodsList, pageNo)
resolve(newList)
})
.catch(reject)
})
},
// 获取我的拼团列表
getMyList(pageNo) {
const app = this
return new Promise((resolve, reject) => {
TaskApi.myList({ page: pageNo }, { load: false })
.then(result => {
// 合并新数据
const newList = result.data.list
app.myList.data = getMoreListData(newList, app.myList, pageNo)
resolve(newList)
})
.catch(reject)
})
},
// 切换当前选项卡
onChangeTab(key = 0) {
const app = this
// 记录选项卡索引
app.curTab = key
// 刷新列表数据
app.goodsList = getEmptyPaginateObj()
app.myList = getEmptyPaginateObj()
app.mescroll.resetUpScroll()
},
// 跳转到拼团商品详情
onTargetGoods(item) {
this.$navTo('pages/groupon/goods/index', { grouponGoodsId: item.groupon_goods_id })
},
// 跳转拼单详情页
onTargetTask(item) {
this.$navTo('pages/groupon/task/index', { taskId: item.task_id })
},
},
/**
* 分享当前页面
*/
onShareAppMessage() {
// 构建页面参数
const params = this.$getShareUrlParams()
return {
title: '拼团活动',
path: `/pages/groupon/index?${params}`
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
// 构建页面参数
const params = this.$getShareUrlParams()
return {
title: '拼团活动',
path: `/pages/groupon/index?${params}`
}
},
}
</script>
<style lang="scss">
page {
background: #F5F5F8;
}
</style>
<style lang="scss" scoped>
.container {
// 设置ios刘海屏底部横线安全区域
// 110 - 18 + 4
padding-bottom: calc(constant(safe-area-inset-bottom) + 96rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 96rpx);
}
.banner {
z-index: 0;
.image {
display: block;
width: 100%;
}
}
.groupon-hall {
padding: 0 24rpx;
position: relative;
z-index: 1;
&.active-list {
margin-top: -80rpx;
}
&.my-list {
padding-top: 30rpx;
}
}
.goods-item--container {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0 !important;
}
}
.goods-item {
padding: 28rpx 24rpx;
display: flex;
background: #fff;
box-sizing: border-box;
border-radius: 14rpx;
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.07);
}
.goods-item_left {
position: relative;
background: #fff;
margin-right: 20rpx;
.label {
position: absolute;
top: 0;
left: 0;
z-index: 1;
background: linear-gradient(to right, #ffa600, #f5b914);
color: #fff;
font-size: 24rpx;
padding: 6rpx 8rpx;
border-radius: 8rpx;
}
.image {
display: block;
width: 220rpx;
height: 220rpx;
border-radius: 10rpx;
}
}
.goods-item_right {
position: relative;
flex: 1;
.goods-name {
display: block;
width: 100%;
min-height: 68rpx;
font-size: 28rpx;
line-height: 1.3;
color: #333;
}
}
.goods-item_desc {
margin-top: 20rpx;
.desc_situation {
font-size: 26rpx;
line-height: 1.3;
color: $main-bg;
margin-top: 20rpx;
}
.people {
margin-right: 14rpx;
}
.desc_footer {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
right: 0rpx;
bottom: 0rpx;
min-height: 44rpx;
.item-status {
color: $main-bg;
}
.item-prices {
padding-right: 6rpx;
.price_x {
margin-right: 14rpx;
color: $main-bg;
font-size: 28rpx;
}
.price_y {
color: #999;
text-decoration: line-through;
font-size: 24rpx;
}
}
.settlement {
padding: 0 30rpx;
line-height: 56rpx;
text-align: center;
font-size: 28rpx;
border-radius: 40rpx;
color: #fff;
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
}
}
}
// 底部选项卡
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
box-shadow: 0 -4rpx 40rpx 0 rgba(151, 151, 151, 0.24);
background: #fff;
// 设置ios刘海屏底部横线安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.footer-container {
display: flex;
align-items: center;
height: 96rpx;
}
.tabbar-item {
font-size: 30rpx;
&.active {
.tabbar-item-content {
color: $main-bg;
}
}
.tabbar-item-icon {
margin-right: 15rpx;
}
}
// 分割线
.tabbar-item__divider {
padding: 22rpx 0;
}
.divider-line {
width: 1rpx;
height: 62rpx;
background: #ddd;
}
</style>
+533
View File
@@ -0,0 +1,533 @@
<template>
<view v-if="!isLoading && detail && goods" class="container">
<!-- 背景区块 -->
<view class="bg-layer"></view>
<!-- 商品信息 -->
<view class="goods-info" @click="onTargetGoods()">
<view class="goods-image">
<view v-if="detail.active_type != ActiveTypeEnum.NORMAL.value" class="label">
<text>{{ ActiveTypeEnum[detail.active_type].name2 }}</text>
</view>
<image class="image" :src="goods.goods_image"></image>
</view>
<view class="goods-detail">
<view class="goods-name">
<text class="twoline-hide">{{ goods.goods_name }}</text>
</view>
<view class="goods-price">
<text class="f-26 col-m"></text>
<text class="m-price">{{ goods.groupon_price }}</text>
<text class="line-price">{{ goods.original_price }}</text>
</view>
<view class="goods-tag">
<text class="tag-item">{{ detail.people }}人团</text>
<text v-if="goods.diff_price != '0.00'" class="tag-item">拼团省{{ goods.diff_price }}</text>
</view>
</view>
</view>
<!-- 拼团成员 -->
<view class="main">
<!-- 拼团状态 (成功失败) -->
<view v-if="detail.status == TaskStatusEnum.FAIL.value" class="main_status main_status__fail">
<text class="status-icon iconfont icon-shibai"></text>
<text>超过有效时间拼团失败</text>
</view>
<view v-if="detail.status == TaskStatusEnum.SUCCESS.value" class="main_status main_status__success">
<text class="status-icon iconfont icon-success"></text>
<text>拼团已成功</text>
</view>
<!-- 参团用户 -->
<view class="main-user">
<view v-for="(item, index) in detail.users" :key="item.id" class="user-item">
<avatar-image :url="item.userInfo.avatar_url" :width="100" />
<view v-if="item.is_leader" class="user-role"><text class="role-name">团长</text></view>
</view>
<!-- 虚位以待 -->
<view v-for="(val, idx) in detail.people - detail.joined_people" :key="idx" class="user-item user-item__wait">
<text class="iconfont icon-wenhao"></text>
</view>
</view>
<!-- 拼单状态拼团中 -->
<view v-if="detail.status == TaskStatusEnum.NORMAL.value" class="main_tiem">
<text>还差</text>
<text class="main_timer_color">{{ detail.people - detail.joined_people }}</text>
<text>个名额</text>
<!-- 倒计时 -->
<count-down :date="detail.end_time" separator="colon" theme="custom" customBgColor="#FE5246" />
<text>后结束</text>
</view>
<!-- 操作按钮 -->
<block v-if="detail.status == TaskStatusEnum.NORMAL.value">
<view v-if="!detail.is_join" class="button" @click="onShowSkuPopup()">立即参团</view>
<!-- 分享给朋友 -->
<button v-else open-type="share" class="button btn-normal" @click="handleShareBtn()">
<view class="btn-item btn-item__main"><text>立即分享</text></view>
</button>
</block>
<view v-else class="button" @click="onTargetGoods()">去开团</view>
</view>
<!-- 拼团须知 -->
<view class="notice" @click="handleShowRules()">
<text class="f-30">拼团须知</text>
<text class="t-brief">{{ setting.ruleBrief }}</text>
<text class="icon-arrow"></text>
</view>
<!-- 商品SKU弹窗 -->
<SkuPopup v-model="showSkuPopup" :skuMode="skuMode" :goods="goods" :buyMode="1" :taskId="detail.task_id" :stepPeople="detail.people" />
<!-- 拼团规则弹窗 -->
<u-modal v-model="showRules" title="拼团规则">
<scroll-view style="height: 610rpx; touch-action: none;" :scroll-y="true">
<view class="pops-content">
<text>{{ setting.ruleDetail }}</text>
</view>
</scroll-view>
</u-modal>
<!-- 商品推荐 -->
<recommended />
</view>
</template>
<script>
import { getCurrentPage, buildUrL } from '@/core/app'
import AvatarImage from '@/components/avatar-image'
import CountDown from '@/components/countdown'
import Recommended from '@/components/recommended'
import SkuPopup from '../goods/components/SkuPopup'
import { ActiveTypeEnum, ActiveStatusEnum, TaskStatusEnum } from '@/common/enum/groupon'
import * as TaskApi from '@/api/groupon/task'
import * as GoodsApi from '@/api/groupon/goods'
import SettingModel from '@/common/model/Setting'
import GrouponSettingModel from '@/common/model/groupon/Setting'
export default {
components: {
AvatarImage,
SkuPopup,
CountDown,
Recommended
},
data() {
return {
//
isLoading: true,
//
ActiveTypeEnum,
ActiveStatusEnum,
TaskStatusEnum,
//
showRules: false,
// /SKU
showSkuPopup: false,
// 1: 2: 3:
skuMode: 3,
// ID
taskId: null,
//
detail: null,
//
goods: null,
//
setting: {}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({ taskId }) {
// ID
this.taskId = taskId
//
this.getSetting()
//
this.getTaskDetail()
},
methods: {
//
getSetting() {
GrouponSettingModel.data(true).then(setting => (this.setting = setting))
},
//
getTaskDetail() {
const { taskId } = this
this.isLoading = true
TaskApi.detail(taskId)
.then(result => {
this.detail = result.data.detail
this.goods = result.data.goods
})
.finally(() => (this.isLoading = false))
},
//
onTargetGoods() {
const { goods } = this
this.$navTo('pages/groupon/goods/index', { grouponGoodsId: goods.groupon_goods_id })
},
//
handleShareBtn() {
// #ifndef MP
this.handleCopyLink()
// #endif
},
//
handleCopyLink() {
const app = this
app.getShareUrl().then(shareUrl => {
//
uni.setClipboardData({
data: shareUrl,
success: () => app.$toast('复制链接成功,快去发送给朋友吧'),
fail: err => app.$toast('复制失败')
})
})
},
// (H5)
getShareUrl() {
const { path, query } = getCurrentPage()
return new Promise((resolve, reject) => {
// h5
SettingModel.h5Url(true).then(baseUrl => {
//
const shareUrl = buildUrL(baseUrl, path, query)
resolve(shareUrl)
})
})
},
//
handleShowRules() {
this.showRules = true
},
// /SKU
onShowSkuPopup() {
this.showSkuPopup = !this.showSkuPopup
}
},
/**
* 分享当前页面
*/
onShareAppMessage() {
//
const app = this
const params = app.$getShareUrlParams({ taskId: app.taskId })
return {
title: app.goods.goods_name,
path: `/pages/groupon/task/index?${params}`
}
},
/**
* 分享到朋友圈
*/
onShareTimeline() {
//
const app = this
const params = app.$getShareUrlParams({ taskId: app.taskId })
return {
title: app.goods.goods_name,
path: `/pages/groupon/task/index?${params}`
}
},
}
</script>
<style lang="scss" scoped>
.container {
position: relative;
overflow: hidden;
}
//
.icon-arrow {
display: inline-block;
width: 0;
height: 0;
border-width: 10rpx;
border-style: solid;
border-color: transparent transparent transparent #ccc;
}
//
.bg-layer {
position: absolute;
top: 0;
width: 100%;
z-index: 0;
height: 250rpx;
text-align: center;
line-height: 100rpx;
&::after {
width: 140%;
height: 250rpx;
position: absolute;
left: -20%;
top: 0;
z-index: -1;
content: '';
border-radius: 0 0 50% 50%;
background-image: linear-gradient(180deg, #ff5644, #fd7524);
}
}
//
.goods-info {
position: relative;
margin: auto;
margin-top: 30rpx;
padding: 28rpx;
padding-bottom: 22rpx;
width: 92%;
background: #fff;
border-radius: 10rpx;
box-sizing: border-box;
box-shadow: 0px 5px 12px rgba(226, 226, 226, 0.62);
display: flex;
.goods-image {
position: relative;
margin-right: 30rpx;
.label {
position: absolute;
top: 0;
left: 0;
z-index: 1;
background: linear-gradient(to right, #ffa600, #f5b914);
color: #fff;
font-size: 24rpx;
padding: 6rpx 8rpx;
border-radius: 8rpx;
}
.image {
display: block;
width: 240rpx;
height: 240rpx;
border-radius: 10rpx;
}
}
.goods-detail {
background: #fff;
.goods-name {
display: block;
font-size: 28rpx;
line-height: 1.5;
}
.goods-price {
margin: 20rpx 0;
color: #eb5841;
.m-price {
display: inline-block;
font-size: 40rpx;
}
.line-price {
display: inline-block;
margin: 10rpx;
font-size: 24rpx;
color: #8e8e8e;
text-decoration: line-through;
}
}
.goods-tag {
margin-bottom: -10rpx;
.tag-item {
display: inline-block;
padding: 4rpx 12rpx;
font-size: 24rpx;
line-height: 36rpx;
border-radius: 5rpx;
color: #fa3534;
background: #fef0f0;
margin-right: 10rpx;
margin-bottom: 10rpx;
box-sizing: border-box;
}
}
}
&:after {
display: block;
clear: both;
content: '';
visibility: hidden;
height: 0;
}
}
//
.main {
position: relative;
margin: auto;
margin-top: 30rpx;
padding: 50rpx 20rpx;
background: #fff;
box-sizing: border-box;
.main-user {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 0 auto;
width: 600rpx;
margin-bottom: 30rpx;
.user-item {
width: 100rpx;
height: 100rpx;
margin: 0 25rpx 25rpx 0;
position: relative;
box-sizing: border-box;
&:nth-child(5n + 0) {
margin-right: 0;
}
.user-role {
width: 100%;
text-align: center;
height: 40rpx;
position: absolute;
left: 0;
bottom: -1rpx;
.role-name {
background: #eb5841;
border-radius: 15rpx;
display: inline-block;
line-height: 1.4;
font-size: 24rpx;
color: #fff;
width: 78rpx;
}
}
&.user-item__wait {
border-radius: 50%;
background: #f8f8f8;
border: 1rpx dashed #dbdbdb;
display: flex;
justify-content: center;
align-items: center;
text {
color: #dbdbdb;
font-size: 38rpx;
}
}
}
}
.button {
display: block;
margin-top: 40rpx;
width: 550rpx;
line-height: 84rpx;
font-size: 28rpx;
background-image: linear-gradient(90deg, #fe5246, #fb265a);
border: none;
box-shadow: 0px 3px 8px rgba(255, 8, 15, 0.25);
text-align: center;
margin: 0 auto;
color: #fff;
border-radius: 40rpx;
animation: btn_anim 0.9s linear infinite;
transform-origin: center;
}
}
//
@keyframes btn_anim {
0% {
-webkit-transform: scale(1);
transform: scale(1);
}
40% {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
}
/* 拼团状态 */
.main_status {
margin-bottom: 40rpx;
font-size: 35rpx;
text-align: center;
.status-icon {
margin-right: 15rpx;
}
}
.main_status__fail {
color: $main-bg;
}
.main_status__success {
color: #08b625;
}
/* 倒计时 */
.main_tiem {
margin-bottom: 50rpx;
font-size: 30rpx;
text-align: center;
display: flex;
justify-content: center;
.main_timer_color {
color: #fc8434;
margin: 0 6rpx;
}
}
//
.notice {
display: flex;
margin: auto;
margin-top: 30rpx;
padding: 35rpx 20rpx;
background: #fff;
border-radius: 10rpx;
box-sizing: border-box;
justify-content: space-between;
align-items: center;
.t-brief {
font-size: 26rpx;
color: #a6a6a6;
}
}
// ()
.pops-content {
padding: 30rpx 48rpx;
font-size: 28rpx;
line-height: 44rpx;
text-align: left;
color: #606266;
min-height: 320rpx;
max-height: 640rpx;
box-sizing: border-box;
}
</style>
+54
View File
@@ -0,0 +1,54 @@
<template>
<view class="help">
<block v-for="(item,index) in itemList" :key="index">
<u-card :title="item.title">
<view class="" slot="body">
<u-parse :html="item.content"></u-parse>
</view>
</u-card>
</block>
</view>
</template>
<script>
import { pageArticle } from '@/websoft/api/article.js'
export default {
data() {
return {
//
itemList: [{
head: "关于我们",
body: "",
open: true,
disabled: true
}],
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const app = this
pageArticle({
categoryId: 43
}).then(res => {
app.itemList = res.data.list
})
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.help {
border-bottom: 1rpx solid #f6f6f9;
width: 750rpx;
margin: 0rpx auto;
}
</style>
+55
View File
@@ -0,0 +1,55 @@
<template>
<view class="help">
<block v-for="(item,index) in itemList" :key="index">
<u-card :title="item.title">
<view class="" slot="body">
<u-parse :html="item.content"></u-parse>
</view>
</u-card>
</block>
</view>
</template>
<script>
import { pageArticle } from '@/websoft/api/article.js'
export default {
data() {
return {
//
itemList: [{
head: "关于我们",
body: "",
open: true,
disabled: true
}],
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const app = this
pageArticle({
categoryId: 42
}).then(res => {
console.log("res: ",res);
app.itemList = res.data.list
})
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.help {
border-bottom: 1rpx solid #f6f6f9;
width: 750rpx;
margin: 0rpx auto;
}
</style>
+55
View File
@@ -0,0 +1,55 @@
<template>
<view class="help">
<block v-for="(item,index) in itemList" :key="index">
<view class="content" slot="body">
<u-parse :html="item.content"></u-parse>
</view>
</block>
</view>
</template>
<script>
import { pageArticle } from '@/websoft/api/article.js'
export default {
data() {
return {
//
itemList: [{
head: "关于我们",
body: "",
open: true,
disabled: true
}],
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const app = this
pageArticle({
categoryId: 51
}).then(res => {
app.itemList = res.data.list
})
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.help {
border-bottom: 1rpx solid #f6f6f9;
width: 750rpx;
margin: 0rpx auto;
.content{
padding: 20rpx 40rpx;
}
}
</style>
+179
View File
@@ -0,0 +1,179 @@
<template>
<view class="content">
<!-- 店铺页面组件 -->
<view class="top-bar" v-if="isLogin" @click="navTo('pages/user/equipment/index')">点击查看设备情况</view>
<view class="login-bar" v-else>
<text class="info">授权登录获取头像昵称加入安博驰</text>
<text class="login" @click="navTo('pages/user/user')">立即登录</text>
</view>
<map id="map" class="map" :scale="scale" :show-location="true" :latitude="latitude" :longitude="longitude"></map>
<view class="bottom-bar" @click="scan()">扫码设备二维码下单租赁</view>
</view>
</template>
<script>
import { userId } from '@/config.js';
import { getUser } from '@/websoft/api/user.js'
import { login } from '@/websoft/api/login.js'
import store from '@/store/index.js'
import storage from '@/utils/storage'
import {
ACCESS_TOKEN,
USER_ID
} from '@/store/mutation-types'
export default {
data() {
return {
user: {},
avatar: '/static/logo.png',
nickName: 'Hello',
latitude: 22.766777,
longitude: 108.375152,
scale: 10,
isLogin: false,
// #ifdef MP-ALIPAY
canIUseAuthButton: my.canIUse('button.open-type.getAuthorize'),
// #endif
}
},
onLoad() {
const app = this
app.getLocation(res => {
if(res.latitude &&res.longitude){
app.latitude = res.latitude
app.longitude = res.longitude
app.scale = 16
}
})
},
onShow(){
this.getUserInfo()
},
methods: {
getUserInfo() {
const { form } = this
const app = this
getUser().then(res => {
if( res.code == 0 && res.data.username != 'www') {
app.isLogin = true
}
})
},
//
checkLogin(){
if(!!store.getters.userId && store.getters.userId != userId){
this.isLogin = true
}
},
//
// https://uniapp.dcloud.io/api/location/location?id=getlocation
getLocation(callback) {
const app = this
uni.getLocation({
success: callback,
fail() {
app.$toast('获取定位失败,请点击右下角按钮重新尝试定位')
app.isAuthor = false
}
})
},
//
navTo(url) {
uni.$u.route(url);
},
scan(){
const app = this
//
// uni.scanCode({
// success (res) {
// console.log("res: ",res);
// // app.$navTo('package/equipment/checkout/index')
// // // app.$navTo(res.path)
// my.alert({ title: res.code });
// }
// })
my.scan({
scanType: ['barCode'],
success: res => {
console.log("res: ",res);
my.alert({ title: res.code });
},
});
}
}
}
</script>
<style lang="scss" scoped>
.container {
background: #fff;
}
.top-bar{
position: fixed;
top: 10rpx;
left: 25rpx;
line-height: 50rpx;
height: 50rpx;
z-index: 1000;
background-color: #fff;
width: 700rpx;
border-radius: 12rpx;
text-align: center;
}
.get-phone-number{
position: fixed;
top: 300rpx;
left: 25rpx;
z-index: 1000;
line-height: 70rpx;
height: 70rpx;
background-color: #0f80ff;
color: #fff;
width: 700rpx;
margin: 25rpx auto;
border-radius: 12rpx;
text-align: center;
}
.bottom-bar{
position: fixed;
bottom: 10rpx;
left: 25rpx;
z-index: 1000;
line-height: 70rpx;
height: 70rpx;
background-color: #0f80ff;
color: #fff;
width: 700rpx;
margin: 25rpx auto;
border-radius: 12rpx;
text-align: center;
}
.login-bar{
position: fixed;
top: 10rpx;
left: 25rpx;
z-index: 1000;
line-height: 70rpx;
height: 70rpx;
color: #fff;
width: 700rpx;
margin: 17rpx auto;
display: flex;
justify-content: space-between;
.info{
padding-left: 20rpx;
background-color: #000000;
opacity: 0.5;
width: 100%;
}
.login{
width: 200rpx;
padding: 0 20rpx;
background-color: #0f80ff !important;
opacity: 1;
}
}
</style>
+257
View File
@@ -0,0 +1,257 @@
<template>
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ native: true }" @down="downCallback"
:up="upOption" @up="upCallback">
<view class="live-room-list">
<view v-for="(item, index) in list.data" :key="index" @click="onTargetLiveRoom(item.room_id)"
:class="[`live-room-item live-status__${item.live_status}`]">
<!-- 直播状态 -->
<view class="room-head dis-flex flex-y-center">
<!-- 直播中 -->
<text v-if="item.live_status == 101" class="live-status_icon iconfont icon-zhibozhong"></text>
<!-- 未开播 -->
<text v-if="item.live_status == 102" class="live-status_icon iconfont icon-shijian-s"></text>
<!-- 已结束 -->
<text v-if="item.live_status >= 103" class="live-status_icon iconfont icon-shipin"></text>
<!-- 状态说明 -->
<text class="live-status_text">{{ item.live_status_text_1 }}</text>
</view>
<!-- 房间名称 -->
<view class="room-name oneline-hide">
<text>{{ item.room_name }}</text>
</view>
<!-- 房间封面 -->
<view class="room-cover">
<image class="image" :src="item.share_img" mode="aspectFill"></image>
</view>
<!-- 主播信息 -->
<view class="room-anchor dis-flex">
<view class="lay-left flex-box dis-flex flex-y-center">
<!-- 主播头像 -->
<!-- mix: 微信api未提供主播头像, 此处显示封面图 -->
<view class="anchor-avatar">
<image class="image" :src="item.share_img" mode="aspectFill"></image>
</view>
<!-- 主播昵称 -->
<view class="anchor-name">
<text>{{ item.anchor_name }}</text>
</view>
</view>
<view class="lay-right">
<text class="live-status_text2">{{ item.live_status_text_2 }}</text>
</view>
</view>
</view>
</view>
</mescroll-body>
</template>
<script>
import { getEmptyPaginateObj, getMoreListData, getShareParams } from '@/core/app'
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import { formatDate, dateFormat } from '@/utils/util'
import * as Api from '@/api/live/room'
const pageSize = 15
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
//
upOption: {
//
auto: true,
// ; 10
page: { size: pageSize },
// 3
noMoreSize: 3,
},
//
list: getEmptyPaginateObj()
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
//
this.getLiveRoomList()
},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
//
app.getLiveRoomList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
/**
* 获取直播间列表
* @param {Number} pageNo 页码
*/
getLiveRoomList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
Api.list({ page: pageNo }, { load: false })
.then(result => {
//
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
.catch(reject)
})
},
//
onTargetLiveRoom(roomId) {
const { platform, $toast } = this
if (platform !== 'MP-WEIXIN') {
$toast('很抱歉,直播间仅支持微信小程序,请前往微信小程序端')
return
}
const customParams = getShareParams({
path: 'pages/index/index'
})
wx.navigateTo({
url: `plugin-private://wx2b03c6e691cd7370/pages/live-player-plugin?room_id=${roomId}&custom_params=${encodeURIComponent(JSON.stringify(customParams))}`
})
}
},
/**
* 分享当前页面
*/
onShareAppMessage() {
return {
title: '直播列表',
path: "/pages/live/index?" + this.$getShareUrlParams()
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本暂只在 Android 平台支持详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
return {
title: '直播列表',
path: "/pages/live/index?" + this.$getShareUrlParams()
}
}
}
</script>
<style lang="scss" scoped>
.live-room-item {
width: 710rpx;
margin: 0 auto 20rpx auto;
padding: 25rpx 24rpx;
background: #fff;
border-radius: 5rpx;
box-sizing: border-box;
box-shadow: 0 2rpx 4rpx 0 rgba(0, 0, 0, 0.05);
&:first-child {
margin-top: 20rpx;
}
}
.room-head {
color: #b6b6b6;
line-height: 40rpx;
.live-status_icon {
margin-right: 15rpx;
font-size: 34rpx;
}
.live-status_text {
font-size: 26rpx;
}
}
//
.live-status__101 .room-head {
color: #db384b;
}
.live-status__102 .room-head {
color: #db384b;
}
//
.room-name {
margin-top: 10rpx;
font-size: 28rpx;
}
//
.room-cover {
margin-top: 15rpx;
.image {
display: block;
width: 100%;
height: 371rpx;
}
}
//
.room-anchor {
margin-top: 20rpx;
.anchor-avatar {
margin-right: 12rpx;
.image {
display: block;
width: 45rpx;
height: 45rpx;
border-radius: 50%;
}
}
.anchor-name {
font-size: 26rpx;
}
}
.live-status_text2 {
color: #b6b6b6;
font-size: 26rpx;
}
//
.live-status__101 .live-status_text2 {
color: #db384b;
}
.live-status__102 .live-status_text2 {
color: #db384b;
}
</style>
+377
View File
@@ -0,0 +1,377 @@
<template>
<view class="container">
<!-- 页面头部 -->
<view class="header">
<view class="title">
<text>账号登录</text>
</view>
<!-- <view class="sub-title">
<text>未注册的手机号登录后将自动注册</text>
</view> -->
</view>
<!-- 表单 -->
<view class="login-form">
<!-- 手机号 -->
<view class="form-item">
<input class="form-item--input" type="number" v-model="mobile" maxlength="20" placeholder="请输入登录账号|手机号码|邮箱" />
</view>
<view class="form-item">
<input class="form-item--input" type="text" v-model="password" maxlength="30" placeholder="请输入登录密码" />
</view>
<!-- 图形验证码 -->
<view class="form-item">
<input class="form-item--input" type="text" v-model="captchaCode" maxlength="5" placeholder="请输入图形验证码" />
<view class="form-item--parts">
<view class="captcha" @click="getCaptcha()">
<image class="image" :src="captcha.base64"></image>
</view>
</view>
</view>
<!-- 短信验证码 -->
<view class="form-item" style="display: none;">
<input class="form-item--input" type="number" v-model="smsCode" maxlength="6" placeholder="请输入短信验证码" />
<view class="form-item--parts">
<view class="captcha-sms" @click="handelSmsCaptcha()">
<text v-if="!smsState" class="activate">获取验证码</text>
<text v-else class="un-activate">重新发送({{ times }})</text>
</view>
</view>
</view>
<!-- 登录按钮 -->
<view class="login-button" @click="handleLogin">
<text>登录</text>
</view>
</view>
<!-- 微信授权手机号一键登录 -->
<!-- #ifdef MP-WEIXIN -->
<MpWeixinMobile :isParty="isParty" :partyData="partyData" />
<!-- #endif -->
</view>
</template>
<script>
import store from '@/store'
import storage from '@/utils/storage'
import {
ACCESS_TOKEN,
USER_ID
} from '@/store/mutation-types'
import { getCaptcha, login, sendSmsCaptcha } from '@/websoft/api/login.js'
import * as Verify from '@/utils/verify'
import http from '@/websoft/api'
import MpWeixinMobile from './mp-weixin-mobile'
// ()
const times = 60
//
const GET_CAPTCHA = 10
const SUBMIT_LOGIN = 20
export default {
components: {
MpWeixinMobile
},
props: {
//
isParty: {
type: Boolean,
default: () => false
},
//
partyData: {
type: Object
}
},
data() {
return {
//
isLoading: false,
//
captcha: {},
//
smsState: false,
//
times,
//
mobile: '',
password: '',
//
captchaCode: '',
//
smsCode: ''
}
},
/**
* 生命周期函数--监听页面加载
*/
created() {
//
this.getCaptcha()
},
methods: {
//
getCaptcha() {
const app = this
getCaptcha()
.then(result => app.captcha = result.data)
},
//
handelSmsCaptcha() {
const app = this
if (!app.isLoading && !app.smsState && app.formValidation(GET_CAPTCHA)) {
app.sendSmsCaptcha()
}
},
//
formValidation(scene = GET_CAPTCHA) {
const app = this
//
if (scene === GET_CAPTCHA) {
if (!app.validteMobile(app.mobile) || !app.validteCaptchaCode(app.captchaCode)) {
return false
}
}
//
if (scene === SUBMIT_LOGIN) {
if (!app.validteMobile(app.mobile) || !app.validteSmsCode(app.smsCode)) {
return false
}
}
return true
},
//
validteMobile(str) {
if (Verify.isEmpty(str)) {
this.$toast('请先输入手机号')
return false
}
if (!Verify.isMobile(str)) {
// this.$toast('')
// return false
}
return true
},
//
validteCaptchaCode(str) {
if (Verify.isEmpty(str)) {
this.$toast('请先输入图形验证码')
return false
}
return true
},
//
validteSmsCode(str) {
if (Verify.isEmpty(str)) {
// this.$toast('')
// return false
}
return true
},
//
sendSmsCaptcha() {
const app = this
app.isLoading = true
sendSmsCaptcha({
phone: app.mobile
})
.then(result => {
//
app.$toast(result.message)
//
app.timer()
})
.catch(() => app.getCaptcha())
.finally(() => app.isLoading = false)
},
//
timer() {
const app = this
app.smsState = true
const inter = setInterval(() => {
app.times = app.times - 1
if (app.times <= 0) {
app.smsState = false
app.times = times
clearInterval(inter)
}
}, 1000)
},
//
handleLogin() {
const app = this
if (!app.isLoading && app.formValidation(SUBMIT_LOGIN)) {
app.submitLogin()
}
},
//
submitLogin() {
const app = this
app.isLoading = true
console.log("登录: ",'登录');
login({username: app.mobile,password:app.password}).then(res => {
//
const expiryTime = 30 * 86400 // 30
storage.set(ACCESS_TOKEN, res.data.access_token, expiryTime)
storage.set(USER_ID, res.data.user.userId, expiryTime)
//
http.setConfig((config) => {
config.header = {
Authorization: res.data.access_token
}
return config
})
app.$toast(res.message)
//
setTimeout(() => {
// app.onNavigateBack(1)
app.$navTo('pages/user/user')
}, 2000)
})
// store.dispatch('Login', {
// smsCode: app.smsCode,
// mobile: app.mobile,
// isParty: app.isParty,
// partyData: app.partyData,
// refereeId: store.getters.refereeId
// })
// .then(result => {
// //
// app.$toast(result.message)
// //
// setTimeout(() => {
// app.onNavigateBack(1)
// }, 2000)
// })
// .catch(err => {
// //
// if (err.result.data.isBack) {
// setTimeout(() => app.onNavigateBack(1), 2000)
// }
// })
// .finally(() => app.isLoading = false)
},
/**
* 登录成功-跳转回原页面
*/
onNavigateBack(delta = 1) {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: Number(delta || 1)
})
} else {
this.$navTo('pages/index/index')
}
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 100rpx 60rpx;
min-height: 100vh;
background-color: #fff;
}
//
.header {
margin-bottom: 60rpx;
.title {
color: #191919;
font-size: 54rpx;
}
.sub-title {
margin-top: 20rpx;
color: #b3b3b3;
font-size: 28rpx;
}
}
//
.form-item {
display: flex;
padding: 18rpx;
border-bottom: 1rpx solid #f3f1f2;
margin-bottom: 30rpx;
height: 96rpx;
&--input {
font-size: 28rpx;
letter-spacing: 1rpx;
flex: 1;
height: 100%;
}
&--parts {
min-width: 100rpx;
height: 100%;
}
//
.captcha {
height: 100%;
.image {
display: block;
width: 192rpx;
height: 100%;
}
}
//
.captcha-sms {
font-size: 28rpx;
line-height: 50rpx;
padding-right: 20rpx;
.activate {
color: $main-bg;
}
.un-activate {
color: #9e9e9e;
}
}
}
//
.login-button {
width: 100%;
height: 86rpx;
margin-top: 80rpx;
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
border-radius: 80rpx;
box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.1);
letter-spacing: 5rpx;
display: flex;
justify-content: center;
align-items: center;
}
</style>
+144
View File
@@ -0,0 +1,144 @@
<template>
<!-- 微信授权手机号一键登录 -->
<view class="wechat-auth">
<button class="btn-normal" open-type="getPhoneNumber" @getphonenumber="handelMpWeixinMobileLogin($event)" @click="clickPhoneNumber">
<view class="wechat-auth-container">
<image class="icon" src="../../../static/channel/wechat.png"></image>
<text class="title">微信手机号一键登录</text>
</view>
</button>
</view>
</template>
<script>
import store from '@/store'
import { isEmpty, inArray } from '@/utils/util'
export default {
props: {
//
isParty: {
type: Boolean,
default: () => false
},
//
partyData: {
type: Object
}
},
data() {
return {
// (code)
// openid
code: ''
}
},
methods: {
// :
// : getphonenumbercode
// getphonenumbercode,encryptedData
async clickPhoneNumber() {
this.code = await this.getCode()
},
//
// getphonenumber
async handelMpWeixinMobileLogin({ detail }) {
const app = this
if (detail.errMsg != 'getPhoneNumber:ok') {
console.log('微信授权获取手机号失败', detail.errMsg)
// app.$error(detail.errMsg)
return
}
if (detail.errMsg == 'getPhoneNumber:ok') {
app.isLoading = true
store.dispatch('LoginMpWxMobile', {
code: app.code,
encryptedData: detail.encryptedData,
iv: detail.iv,
isParty: app.isParty,
partyData: app.partyData,
refereeId: store.getters.refereeId
})
.then(result => {
//
app.$toast(result.message)
//
setTimeout(() => {
app.onNavigateBack(1)
}, 2000)
})
.catch(err => {
const resultData = err.result.data
//
if (isEmpty(resultData)) {
app.$toast(err.result.message)
}
//
if (resultData.isBack) {
setTimeout(() => app.onNavigateBack(1), 2000)
}
})
.finally(() => app.isLoading = false)
}
},
// code
// https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html
getCode() {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: res => {
console.log('code', res.code)
resolve(res.code)
},
fail: reject
})
})
},
/**
* 登录成功-跳转回原页面
*/
onNavigateBack(delta = 1) {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: Number(delta || 1)
})
} else {
this.$navTo('pages/index/index')
}
}
}
}
</script>
<style lang="scss" scoped>
//
.wechat-auth {
width: 320rpx;
margin: 50rpx auto 0 auto;
.wechat-auth-container {
display: flex;
justify-content: center;
}
.icon {
width: 38rpx;
height: 38rpx;
margin-right: 15rpx;
}
.title {
font-size: 28rpx;
color: #666666;
}
}
</style>
+238
View File
@@ -0,0 +1,238 @@
<template>
<view class="container">
<view class="wechatapp">
<view class="header">
<!-- <open-data class="avatar" type="userAvatarUrl"></open-data> -->
<image class="image"
:src="storeInfo && storeInfo.image_url ? storeInfo.image_url : '/static/default-avatar.png'"></image>
</view>
</view>
<view class="auth-title">申请获取以下权限</view>
<view class="auth-subtitle">获得你的公开信息昵称头像等</view>
<view class="login-btn">
<!-- 获取微信用户信息 -->
<button class="button btn-normal" @click.stop="getUserProfile">授权登录</button>
</view>
<view class="no-login-btn">
<button class="button btn-normal" @click="handleCancel">暂不登录</button>
</view>
</view>
</template>
<script>
import store from '@/store'
import { isEmpty } from '@/utils/util'
import SettingModel from '@/common/model/Setting'
export default {
data() {
return {
//
storeInfo: undefined,
// (code)
// openid
code: ''
}
},
created() {
//
this.getStoreInfo()
},
methods: {
//
getStoreInfo() {
SettingModel.item('store').then(store => this.storeInfo = store)
// SettingModel.h5Url(true)
},
// code
// https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html
getCode() {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: res => {
console.log('code', res.code)
resolve(res.code)
},
fail: reject
})
})
},
// ()
getUserProfile() {
const app = this
wx.canIUse('getUserProfile') && wx.getUserProfile({
lang: 'zh_CN',
desc: '获取用户相关信息',
success({ userInfo }) {
console.log('用户同意了授权')
console.log('userInfo', userInfo)
//
app.onAuthSuccess(userInfo)
},
fail() {
console.log('用户拒绝了授权')
}
})
},
//
// :
// 1.codeuserInfo
// 2., ,
// 3.,
async onAuthSuccess(userInfo) {
const app = this
//
store.dispatch('LoginMpWx', {
partyData: {
code: await app.getCode(),
oauth: 'MP-WEIXIN',
userInfo
},
refereeId: store.getters.refereeId
})
.then(result => {
//
app.$toast(result.message)
//
setTimeout(() => {
app.onNavigateBack()
}, 2000)
})
.catch(err => {
const resultData = err.result.data
//
if (isEmpty(resultData)) {
app.$toast(err.result.message)
}
//
if (resultData.isBack) {
setTimeout(() => app.onNavigateBack(1), 2000)
}
//
if (resultData.isBindMobile) {
app.onEmitSuccess(userInfo)
}
})
},
// oauth
// code, code()
async onEmitSuccess(userInfo) {
const app = this
app.$emit('success', {
oauth: 'MP-WEIXIN', // : MP-WEIXIN
code: await app.getCode(), // code, openid
userInfo //
})
},
/**
* 暂不登录
*/
handleCancel() {
//
this.onNavigateBack()
},
/**
* 登录成功-跳转回原页面
*/
onNavigateBack(delta = 1) {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: Number(delta || 1)
})
} else {
this.$navTo('pages/index/index')
}
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 0 60rpx;
font-size: 32rpx;
background: #fff;
min-height: 100vh;
}
.wechatapp {
padding: 80rpx 0 48rpx;
border-bottom: 1rpx solid #e3e3e3;
margin-bottom: 72rpx;
text-align: center;
.header {
width: 190rpx;
height: 190rpx;
border: 4rpx solid #fff;
margin: 0 auto 0;
border-radius: 50%;
overflow: hidden;
box-shadow: 2rpx 0 10rpx rgba(50, 50, 50, 0.3);
.image {
display: block;
width: 100%;
height: 100%;
}
}
}
.auth-title {
color: #585858;
font-size: 34rpx;
margin-bottom: 40rpx;
}
.auth-subtitle {
color: #888;
margin-bottom: 88rpx;
font-size: 28rpx;
}
.login-btn {
padding: 0 20rpx;
.button {
height: 88rpx;
background: #04be01;
color: #fff;
font-size: 30rpx;
border-radius: 999rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
.no-login-btn {
margin-top: 20rpx;
padding: 0 20rpx;
.button {
height: 88rpx;
background: #dfdfdf;
color: #fff;
font-size: 30rpx;
border-radius: 999rpx;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>
+124
View File
@@ -0,0 +1,124 @@
<template>
<!-- 跳转到微信授权地址 -->
</template>
<script>
import queryStringify from '@/js_sdk/queryStringify'
import store from '@/store'
import { isEmpty, urlEncode } from '@/utils/util'
import * as Api from '@/api/wxofficial'
export default {
data() {
return {
//
isCallback: false
}
},
created() {
//
this.onCallback()
//
this.redirectUrl()
},
methods: {
//
onCallback() {
//
const wxParam = queryStringify.parse(window.location.search)
if (!isEmpty(wxParam)) {
const url = window.location.href.replace(window.location.search, '')
window.location.href = url + '?' + urlEncode(wxParam)
return
}
// code
const query = this.$route.query
if (isEmpty(query) || !query.code) {
return
}
//
this.isCallback = true
Api.oauthUserInfo(query.code)
.then(({ data }) => {
console.log('用户同意了授权')
console.log('userInfo', data)
//
this.onAuthSuccess(data)
})
},
//
// :
// 1.openiduserInfo
// 2., ,
// 3.,
async onAuthSuccess({ userInfo, encryptedData, iv }) {
const app = this
//
store.dispatch('LoginWxOfficial', {
partyData: { oauth: 'H5-WEIXIN', userInfo, encryptedData, iv },
refereeId: store.getters.refereeId
})
.then(result => {
//
app.$toast(result.message)
//
setTimeout(() => app.onNavigateBack(), 2000)
})
.catch(err => {
const resultData = err.result.data
//
if (isEmpty(resultData)) {
app.$toast(err.result.message)
}
//
if (resultData.isBindMobile) {
app.onEmitSuccess({ userInfo, encryptedData, iv })
}
})
},
//
redirectUrl() {
if (this.isCallback) {
return
}
const callbackUrl = window.location.href
Api.oauthUrl(callbackUrl)
.then(result => {
const url = result.data.redirectUrl
window.location.href = url
})
},
// oauth
async onEmitSuccess({ userInfo, encryptedData, iv }) {
this.$emit('success', {
oauth: 'H5-WEIXIN', // : H5-WEIXIN
userInfo,
encryptedData,
iv
})
},
/**
* 登录成功-跳转回原页面
*/
onNavigateBack(delta = 1) {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: Number(delta || 1)
})
} else {
this.$navTo('pages/index/index')
}
}
}
}
</script>
+102
View File
@@ -0,0 +1,102 @@
<template>
<view v-if="isLoad" class="login" :style="appThemeStyle">
<MpWeixin v-if="isMpWeixinAuth" @success="onGetUserInfoSuccess" />
<WxOfficial v-else-if="isWxOfficialAuth" @success="onGetUserInfoSuccess" />
<Main v-else :isParty="isParty" :partyData="partyData" />
</view>
</template>
<script>
import Main from './components/main'
import MpWeixin from './components/mp-weixin'
import WxOfficial from './components/wx-official'
import SettingKeyEnum from '@/common/enum/setting/Key'
import SettingModel from '@/common/model/Setting'
export default {
components: {
Main,
MpWeixin,
WxOfficial
},
data() {
return {
// [onLoadview]
isLoad: false,
// ()
setting: {},
//
isMpWeixinAuth: false,
//
isWxOfficialAuth: false,
//
isParty: false,
//
partyData: {}
}
},
/**
* 生命周期函数--监听页面加载
*/
async onLoad(options) {
//
// await this.getRegisterSetting()
//
await this.setShowUserInfo()
//
this.isLoad = true
},
methods: {
// [--]
async getRegisterSetting() {
await SettingModel.item(SettingKeyEnum.REGISTER.value, false)
.then(setting => this.setting = setting)
},
/**
* 设置当前是否显示第三方授权登录
* - 条件1: 只有对应的客户端显示获取用户信息按钮, 例如微信小程序微信公众号
* - 条件2: 注册设置是否已开启该选项
*/
async setShowUserInfo() {
const app = this
// , getUserProfile
const isMpWeixin = app.platform === 'MP-WEIXIN' && wx.canIUse('getUserProfile')
const isWxOfficial = app.platform === 'H5-WEIXIN'
//
app.isMpWeixinAuth = isMpWeixin && app.setting.isOauthMpweixin
app.isWxOfficialAuth = isWxOfficial && app.setting.isOauthWxofficial
},
//
onGetUserInfoSuccess(result) {
//
this.partyData = result
//
this.onShowRegister()
},
//
onShowRegister() {
//
if (this.partyData.oauth === 'MP-WEIXIN') {
this.isMpWeixinAuth = false
}
//
if (this.partyData.oauth === 'H5-WEIXIN') {
this.isWxOfficialAuth = false
}
//
this.isParty = true
}
}
}
</script>
<style lang="scss" scoped>
</style>
+207
View File
@@ -0,0 +1,207 @@
<template>
<view class="ws-page">
<view class="logo">
<image src="../../static/logo.png" mode="widthFix"></image>
</view>
<!-- 支付宝授权手机号一键登录 -->
<view class="alipay-auth">
<button @click="login" :disabled="loding">支付宝授权一键登录</button>
</view>
<!-- 手机号登录 -->
<view class="mobile-auth">
<button @click="loginByUserName">账号登录</button>
</view>
<!-- <view class="xieyi">
<checkbox-group @change="onAgree" style="display: flex; align-items: center;">
<checkbox :checked="agree" class="agree" />登录即代表您已同意<div class="xieyi-text" @click="showXieyi">服务协议与隐私协议</div>
</checkbox-group>
</view> -->
</view>
</template>
<script>
import store from '@/store'
import { getAuthCode, getPhoneNumber, login, alipayLogin, register } from '@/websoft/api/login.js'
import { ACCESS_TOKEN, USER_ID } from '@/store/mutation-types'
import storage from '@/utils/storage'
import http from '@/websoft/api'
import { roleId } from '@/config.js'
import {
isEmpty,
inArray
} from '@/utils/util'
export default {
data() {
return {
agree: true,
userId: '',
loding: false
}
},
methods: {
// :
login() {
const app = this
my.getAuthCode({
scopes: 'auth_user',
success: ({ authCode }) => {
console.log("获取支付宝授权码: ",authCode);
app.loding = true
// ID
getAuthCode({authCode})
.then(res => {
store.dispatch('setUserInfo',res.data.user)
store.dispatch('setUserId',res.data.user.userId)
store.dispatch('setToken',res.data.access_token)
const expiryTime = 30 * 86400 // 30
storage.set(ACCESS_TOKEN, res.data.access_token, expiryTime)
storage.set(USER_ID, res.data.user.userId, expiryTime)
//
http.setConfig((config) => {
config.header = {
Authorization: res.data.access_token
}
return config
})
app.$toast(res.message)
//
setTimeout(() => {
app.$navTo('pages/user/user')
}, 2000)
})
.catch(e => {
app.$error(e.message)
})
}
});
},
onAgree() {
this.agree = !this.agree
},
loginByUserName(){
this.$navTo('pages/login/index')
},
// // #ifdef MP-ALIPAY
// my.getPhoneNumber({
// success: (res) => {
// let encryptedData = res.response;
// getPhoneNumber({encryptedData}).then(response => {
// console.log(": ",response);
// const json = JSON.parse(response.data)
// console.log("json: ",json);
// if(json.mobile){
// //
// app.onAuthSuccess(json.mobile)
// }
// })
// },
// fail: (res) => {
// console.log(res);
// },
// });
// // #endif
/**
* 登录成功-跳转回原页面
*/
onNavigateBack(delta = 1) {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: Number(delta || 1)
})
} else {
this.$navTo('pages/user/user')
}
},
showXieyi(){
this.$navTo('pages/help/xieyi')
}
}
}
</script>
<style lang="scss" scoped>
.ws-page {
padding: 40rpx;
height: 70vh;
display: flex;
flex-direction: column;
.logo {
margin: 100rpx auto;
text-align: center;
image {
width: 140rpx;
height: 140rpx;
border-radius: 20rpx;
}
}
.alipay-auth {
margin: 12rpx 0;
button {
background-color: #3869ea;
color: #ffffff;
border-radius: 12rpx;
}
}
.mobile-auth {
margin: 12rpx 0;
button {
border-radius: 12rpx;
}
}
.xieyi {
margin-top: 100rpx;
font-size: 28rpx;
text-align: center;
text {
color: #3869ea;
}
.agree {
border-radius: 100%;
}
}
}
//
.wechat-auth {
width: 320rpx;
margin: 50rpx auto 0 auto;
.wechat-auth-container {
display: flex;
justify-content: center;
}
.icon {
width: 38rpx;
height: 38rpx;
margin-right: 15rpx;
}
.title {
font-size: 28rpx;
color: #666666;
}
}
.xieyi-text{
color: #0000ff;
}
</style>
+330
View File
@@ -0,0 +1,330 @@
<template>
<view>
<!-- 门脸图 -->
<view class="shop-banner">
<image :src="record.logo" class="logo" mode="aspectFit"></image>
<view class="shop-info">
<view class="shop-name">{{ record.merchantName }}</view>
<view class="shop-desc">
<view class="shop-desc-text"><u-rate :count="count" :gutter="-10" :value="value" readonly size="20" @change="onChangeStar"></u-rate></view>
<!-- <view class="shop-desc-text">店铺粉丝 321</view> -->
<view class="shop-desc-text">全部商品 {{ count }}</view>
</view>
</view>
<view class="follow"><u-button size="mini">关注</u-button></view>
</view>
<view class="category" v-for="(cate,index) in category" :key="index">
<view class="category-name" v-if="cate.count > 0">{{ cate.dictDataName }}</view>
<block v-for="(eq,i) in equipmentList" :key="i">
<view class="goods" v-if="cate.dictDataCode == eq.equipmentCategory">
<block v-if="eq.equipmentCategory == '10'">
<image v-if="eq.image" :src="eq.image" class="equipment-image" mode="aspectFit"></image>
<image v-else src="../../static/goods/battery.png" class="equipment-image" mode="aspectFit"></image>
<view class="info">
<text class="equipment-model">{{ eq.batteryModel }}</text>
<text class="equipment-count">库存{{ eq.stockTotal }}</text>
<text class="equipment-price">{{ eq.batteryPrice }}</text>
</view>
<view class="tobuy">
<u-button type="success" :disabled="disabled" size="mini" @click="buy(eq.goodsId)">立即购买</u-button>
</view>
</block>
<block v-if="eq.equipmentCategory == '20'">
<image v-if="eq.image" :src="eq.image" class="equipment-image" mode="aspectFit"></image>
<image v-else src="../../static/goods/battery.png" class="equipment-image" mode="aspectFit"></image>
<view class="info">
<text class="equipment-model">{{ eq.batteryModel }}</text>
<text class="equipment-count">库存{{ eq.stockTotal }}</text>
<text class="equipment-price">{{ eq.repayment }}/</text>
</view>
<view class="tobuy">
<u-button type="error" :disabled="disabled" size="mini" @click="buy(eq.goodsId)">立即购买</u-button>
</view>
</block>
<block v-if="eq.equipmentCategory == '30'">
<image v-if="eq.image" :src="eq.image" class="equipment-image" mode="aspectFit"></image>
<image v-else src="../../static/goods/battery.png" class="equipment-image" mode="aspectFit"></image>
<view class="info">
<text class="equipment-model">{{ eq.batteryModel }}</text>
<text class="equipment-count">库存{{ eq.stockTotal }}</text>
<text class="equipment-price">{{ eq.batteryRent }}/</text>
</view>
<view class="tobuy">
<u-button type="warning" :disabled="disabled" size="mini" @click="buy(eq.goodsId)">立即租用</u-button>
</view>
</block>
<block v-if="eq.equipmentCategory == '40'">
<image v-if="eq.image" :src="eq.image" class="equipment-image" mode="aspectFit"></image>
<image v-else src="../../static/goods/battery.png" class="equipment-image" mode="aspectFit"></image>
<view class="info">
<text class="equipment-model">{{ eq.batteryModel }}</text>
<text class="equipment-count">库存{{ eq.stockTotal }}</text>
<text class="equipment-price">{{ eq.batteryRent }}/</text>
</view>
<view class="tobuy">
<u-button type="primary" :disabled="disabled" size="mini" @click="buy(eq.goodsId)">立即租用</u-button>
</view>
</block>
</view>
</block>
</view>
<empty v-if="equipmentList.length == 0" :isLoading="isLoading" tips="商品已售罄" />
</view>
</template>
<script>
// import Search from '@/components/page/diyComponents/search/index.vue'
// import {
// getMoreListData
// } from '@/core/app'
// import * as ShopApi from '@/api/shop.js'
import Empty from '@/components/empty'
import {
listMerchant
} from '@/websoft/api/merchant.js'
import { pageEquipmentGoods } from '@/websoft/api/equipment-goods.js'
import { getDictionaryOptions } from '@/websoft/api/dict.js'
// import Empty from '@/components/empty'
export default {
components: {
// Search,
Empty
},
data() {
return {
record: {},
//
isLoading: true,
//
isAuthor: true,
// ID
merchantId: null,
//
merchantCode: null,
//
equipmentList: [],
modelList: [],
//
count: 0,
value: 3,
disabled: false,
category: [
{
id: 0,
name: '销售',
count: 0
},
{
id: 1,
name: '分期',
count: 0
},
{
id: 2,
name: '以租代购',
count: 0
},
{
id: 3,
name: '租赁',
count: 0
}
]
}
},
onLoad(option) {
console.log("option: ",option);
const app = this
// ID
app.equipmentId = option.equipmentId
app.merchantId = option.merchantId
app.merchantCode = option.merchantCode
// //
app.getMerchant()
// //
app.getDict()
},
methods: {
getEquipment(){
const app = this
const { merchantCode } = this
pageEquipmentGoods({merchantCode, status: 0}).then(res => {
app.count = res.data.count
app.equipmentList = res.data.list
app.category[0].count = app.equipmentList.filter(d => d.equipmentCategory == '10').length
app.category[1].count = app.equipmentList.filter(d => d.equipmentCategory == '20').length
app.category[2].count = app.equipmentList.filter(d => d.equipmentCategory == '30').length
app.category[3].count = app.equipmentList.filter(d => d.equipmentCategory == '40').length
})
},
getDict(){
const app = this
getDictionaryOptions({dictCode: 'equipmentCategory'}).then(res => {
app.category = res.data
})
getDictionaryOptions({dictCode: 'equipmentModel'}).then(res => {
app.modelList = res.data
})
//
app.getEquipment()
},
//
getMerchant() {
const app = this
const { merchantId } = this
app.isLoading = true
console.log("merchantIdmerchantId: ",merchantId);
listMerchant({merchantId})
.then(result => {
app.record = result.data[0]
})
.finally(() => app.isLoading = false)
},
//
// https://uniapp.dcloud.io/api/location/location?id=getlocation
getLocation(callback) {
const app = this
uni.getLocation({
type: 'wgs84',
success: callback,
fail() {
app.$toast('获取定位失败,请点击右下角按钮重新尝试定位')
app.isAuthor = false
}
})
},
//
onAuthorize() {
const app = this
// #ifdef MP
uni.openSetting({
success(res) {
if (res.authSetting['scope.userLocation']) {
console.log('定位权限授权成功')
app.isAuthor = true
setTimeout(() => {
//
app.getLocation((res) => {
app.getShopList(res.longitude, res.latitude)
})
}, 1000)
}
}
})
// #endif
// #ifdef H5
//
app.getLocation((res) => {
app.getShopList(res.longitude, res.latitude)
})
// #endif
},
/**
* 选择门店
*/
onSelectedShop(merchantId) {
this.$navTo('package/shops/detail/index', {
merchantId
})
},
buy(goodsId){
console.log("goodsId: ",goodsId);
this.$navTo('pages/checkout/checkout', { goodsId })
},
changeCity() {
this.$toast('切换区域')
},
onChangeStar(e){
console.log("e: ",e);
}
}
}
</script>
<style lang="scss" scoped>
.shop-banner{
background-color: #f3f3f3;
width: 100%;
height: 160rpx;
display: flex;
align-items: center;
.logo{
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
margin: 0 20rpx;
}
.shop-info{
height: 100rpx;
width: 560rpx;
display: flex;
justify-content: space-around;
flex-direction: column;
.shop-name{
font-size: 36rpx;
font-weight: bold;
}
.shop-desc{
display: flex;
.shop-desc-text{
font-size: 22rpx;
margin-right: 20rpx;
}
}
}
.follow{
margin-right: 20rpx;
}
}
.category{
width: 700rpx;
margin: 20rpx auto;
.category-name{
margin-bottom: 20rpx;
color: #999999;
}
.goods{
background-color: #ffffff;
border-radius: 12rpx;
padding: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
.equipment-image{
width: 240rpx;
height: 160rpx;
margin-right: 20rpx;
background-color: #f7f7f7;
}
.info{
display: flex;
flex-direction: column;
justify-content: space-around;
height: 120rpx;
min-width: 280rpx;
.equipment-model{
font-size: 34rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.equipment-count{
color: #999999;
}
.equipment-price{
color: #ff0000;
}
}
}
}
</style>
+323
View File
@@ -0,0 +1,323 @@
<template>
<view>
<!-- 展馆图标 -->
<view class="header-box">
<view class="location" @click="changeCity">
附近站点推荐
</view>
</view>
<!-- 门店列表 -->
<view class="shop-list">
<view class="item" v-for="(item, index) in shopList" :key="index"
@click="onSelectedShop(item.merchantId,item.merchantCode)">
<image :src="item.logo" mode="aspectFit"></image>
<view class="info">
<text class="title">{{item.merchantName}}</text>
<text class="desc">{{item.address}}</text>
<view class="tag">
<view class="mr12">
<u-tag size="mini" plain text="租车站"></u-tag>
</view>
<view class="mr12">
<u-tag size="mini" type="warning" plain text="换点站"></u-tag>
</view>
<view class="mr12">
<u-tag size="mini" type="success" plain text="买车站"></u-tag>
</view>
</view>
<text class="distance">距离4.2km</text>
</view>
</view>
</view>
<!-- 定位按钮 -->
<view v-if="!isAuthor" class="widget-location dis-flex flex-x-center flex-y-center" @click="onAuthorize()">
<text class="iconfont icon-locate"></text>
</view>
<empty v-if="shopList.length == 0" :isLoading="isLoading" tips="请先登录" />
</view>
</template>
<script>
import store from '@/store/index.js'
import storage from '../../utils/storage'
import { ACCESS_TOKEN, USER_ID } from '@/store/mutation-types'
import { pageMerchant } from '@/websoft/api/merchant.js'
import Empty from '@/components/empty'
import { tenantId } from '@/config.js';
export default {
components: {
// Search,
Empty
},
data() {
return {
title: '国内馆',
//
isLoading: false,
//
isAuthor: true,
// ID
selectedId: null,
//
shopList: []
}
},
onLoad() {
const app = this
if(!uni.getStorageSync('userId')){
console.log("未登录1: ");
return false;
}
//
app.getLocation(res => {
app.getShopList(res.longitude, res.latitude)
})
},
onShow() {
const app = this
console.log("app.shopList.length: ",app.$store.getters.token);
if(app.shopList.length == 0){
console.log("sssss: ");
app.getShopList(app.longitude,app.latitude)
}
},
methods: {
//
getShopList(longitude, latitude) {
const app = this
app.isLoading = true
pageMerchant({tenantId})
.then(result => {
if(result.code == 0){
app.shopList = result.data.list
}
})
.finally(() => app.isLoading = false)
},
//
// https://uniapp.dcloud.io/api/location/location?id=getlocation
getLocation(callback) {
const app = this
uni.getLocation({
success: callback,
fail() {
app.$toast('获取定位失败,请点击右下角按钮重新尝试定位')
app.isAuthor = false
}
})
},
//
onAuthorize() {
const app = this
// #ifdef MP
uni.openSetting({
success(res) {
if (res.authSetting['scope.userLocation']) {
console.log('定位权限授权成功')
app.isAuthor = true
setTimeout(() => {
//
app.getLocation((res) => {
app.getShopList(res.longitude, res.latitude)
})
}, 1000)
}
}
})
// #endif
// #ifdef H5
//
app.getLocation((res) => {
app.getShopList(res.longitude, res.latitude)
})
// #endif
},
navTo(merchantId) {
const navTo = uni.$u.route()
navTo('pages/merchant/detail', {
merchantId
})
},
/**
* 选择门店
*/
onSelectedShop(merchantId,merchantCode) {
uni.$u.route('pages/merchant/detail', {
merchantId,merchantCode
})
},
changeCity() {
this.$toast('切换区域')
}
}
}
</script>
<style lang="scss" scoped>
.bg {
background-color: #F5F5F8
}
.shop-list {
.item {
padding: 20rpx;
margin: 20rpx auto;
background-color: #ffffff;
width: 660rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
image {
width: 180rpx;
height: 180rpx;
margin-right: 40rpx;
background-color: #f9f9f9;
}
.info {
display: flex;
flex-direction: column;
.title {
font-size: 38rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.desc {
font-size: 28rpx;
}
.tag {
font-size: 28rpx;
display: flex;
.mr12 {
margin-right: 12rpx;
}
}
.distance {
font-size: 28rpx;
}
}
}
}
.header-box {
padding: 20rpx 36rpx 0 36rpx;
display: flex;
justify-content: space-between;
.location {
display: flex;
justify-content: center;
align-items: center;
color: #999999;
image {
width: 60rpx;
height: 60rpx
}
}
.u-subsection {
width: 260rpx;
margin-top: 7rpx
}
}
.search-wrapper {
display: flex;
width: 80%;
height: 64rpx
}
//
.search-input {
width: 90%;
background: #fff;
border-radius: 10rpx 0 0 10rpx;
box-sizing: border-box;
overflow: hidden;
.search-input-wrapper {
display: flex;
.left {
display: flex;
width: 60rpx;
justify-content: center;
align-items: center;
.search-icon {
display: block;
color: #b4b4b4;
font-size: 28rpx
}
}
.right {
flex: 1;
input {
font-size: 28rpx;
height: 64rpx;
display: flex;
.input-placeholder {
color: #aba9a9
}
}
}
}
}
.shop-avatar {
width: 200rpx;
margin-right: 24rpx
}
//
.search-button {
width: 25%;
box-sizing: border-box;
.button {
height: 64rpx;
font-size: 28rpx;
border-radius: 0 5px 5px 0;
background: #2C71C7
}
}
.shop-info {
width: 100%;
margin-left: 20rpx;
.title {
font-size: 38rpx;
font-weight: bold;
padding-bottom: 10rpx;
}
.about {
display: flex;
.mr12 {
margin-right: 12rpx;
}
}
}
.u-body-item {
align-items: stretch !important
}
</style>
+307
View File
@@ -0,0 +1,307 @@
<template>
<view class="container" :style="appThemeStyle">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption"
@up="upCallback">
<!-- tab栏 -->
<u-tabs :list="tabs" :is-scroll="false" :current="curTab" :active-color="appTheme.mainBg" :duration="0.2"
@change="onChangeTab" />
<!-- 优惠券列表 -->
<view class="coupon-list">
<view class="coupon-item" v-for="(item, index) in list.data" :key="index">
<view class="item-wrapper" :class="['color-' + (item.state.value ? color[index % color.length] : 'gray')]">
<view class="coupon-type">{{ CouponTypeEnum[item.coupon_type].name }}</view>
<view class="tip dis-flex flex-dir-column flex-x-center">
<view v-if="item.coupon_type == CouponTypeEnum.FULL_DISCOUNT.value">
<text class="f-30"></text>
<text class="money">{{ item.reduce_price }}</text>
</view>
<text class="money" v-if="item.coupon_type == CouponTypeEnum.DISCOUNT.value">{{ item.discount }}</text>
<text class="pay-line">{{ item.min_price }}元可用</text>
</view>
<view class="split-line"></view>
<view class="content dis-flex flex-dir-column flex-x-between">
<view class="title">{{ item.name }}</view>
<view class="bottom dis-flex flex-y-center">
<view class="time flex-box">
<block v-if="item.start_time === item.end_time">{{ item.start_time }} 当天有效</block>
<block v-else>{{ item.start_time }}~{{ item.end_time }}</block>
</view>
<view class="receive state">
<text>{{ item.state.text }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import * as MyCouponApi from '@/api/myCoupon'
import { CouponTypeEnum } from '@/common/enum/coupon'
const color = ['red', 'blue', 'violet', 'yellow']
const pageSize = 15
const tabs = [{
name: `未使用`,
value: 'isUnused'
}, {
name: `已使用`,
value: 'isUse'
}, {
name: `已过期`,
value: 'isExpire'
}]
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
//
CouponTypeEnum,
//
color,
//
tabs,
//
curTab: 0,
//
list: getEmptyPaginateObj(),
//
upOption: {
//
auto: true,
// ; 10
page: { size: pageSize },
// 4
noMoreSize: 4,
//
empty: {
tip: '亲,暂无相关优惠券'
}
}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
//
app.getCouponList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
/**
* 获取优惠券列表
*/
getCouponList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
MyCouponApi.list({ dataType: app.getTabValue(), page: pageNo }, { load: false })
.then(result => {
//
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
})
},
//
getTabValue() {
return this.tabs[this.curTab].value
},
//
onChangeTab(index) {
const app = this
//
app.curTab = index
//
app.onRefreshList()
},
//
onRefreshList() {
this.list = getEmptyPaginateObj()
setTimeout(() => {
this.mescroll.resetUpScroll()
}, 120)
},
}
}
</script>
<style lang="scss" scoped>
.coupon-list {
padding: 20rpx;
}
.coupon-item {
position: relative;
overflow: hidden;
margin-bottom: 22rpx;
}
.item-wrapper {
width: 100%;
display: flex;
background: #fff;
border-radius: 8rpx;
color: #fff;
height: 180rpx;
.coupon-type {
position: absolute;
top: 0;
right: 0;
z-index: 10;
width: 128rpx;
padding: 6rpx 0;
background: #a771ff;
font-size: 20rpx;
text-align: center;
color: #ffffff;
transform: rotate(45deg);
transform-origin: 64rpx 64rpx;
}
&.color-blue {
background: linear-gradient(-125deg, #57bdbf, #2f9de2);
}
&.color-red {
background: linear-gradient(-128deg, #ff6d6d, #ff3636);
}
&.color-violet {
background: linear-gradient(-113deg, #ef86ff, #b66ff5);
.coupon-type {
background: #55b5ff;
}
}
&.color-yellow {
background: linear-gradient(-141deg, #f7d059, #fdb054);
}
&.color-gray {
background: linear-gradient(-113deg, #bdbdbd, #a2a1a2);
.coupon-type {
background: #9e9e9e;
}
}
.content {
flex: 1;
padding: 30rpx 20rpx;
border-radius: 16rpx 0 0 16rpx;
.title {
font-size: 32rpx;
}
.bottom {
.time {
font-size: 24rpx;
}
.receive {
height: 46rpx;
width: 122rpx;
border: 1rpx solid #fff;
border-radius: 30rpx;
color: #fff;
font-size: 24rpx;
display: flex;
justify-content: center;
align-items: center;
&.state {
border: none;
}
}
}
}
.tip {
position: relative;
flex: 0 0 32%;
text-align: center;
border-radius: 0 16rpx 16rpx 0;
.money {
font-weight: bold;
font-size: 52rpx;
}
.pay-line {
font-size: 22rpx;
}
}
.split-line {
position: relative;
flex: 0 0 0;
border-left: 4rpx solid #fff;
margin: 0 10rpx 0 6rpx;
background: #fff;
&:before,
{
border-radius: 0 0 16rpx 16rpx;
top: 0;
}
&:after {
border-radius: 16rpx 16rpx 0 0;
bottom: 0;
}
&:before,
&:after {
content: '';
position: absolute;
width: 24rpx;
height: 12rpx;
background: #f7f7f7;
left: -14rpx;
z-index: 1;
}
}
}
</style>
+311
View File
@@ -0,0 +1,311 @@
<template>
<view>
<!-- 消息列表 -->
<view class="shop-list">
<view class="item" v-for="(item, index) in list" :key="index"
@click="onSelectedShop(item.merchantId,item.merchantCode)">
<image :src="item.logo" mode="aspectFit"></image>
<view class="info">
<text class="title">{{item.merchantName}}</text>
<text class="desc">{{item.address}}</text>
<view class="tag">
<view class="mr12">
<u-tag size="mini" plain text="租车站"></u-tag>
</view>
<view class="mr12">
<u-tag size="mini" type="warning" plain text="换点站"></u-tag>
</view>
<view class="mr12">
<u-tag size="mini" type="success" plain text="买车站"></u-tag>
</view>
</view>
<text class="distance">距离4.2km</text>
</view>
</view>
</view>
<!-- 定位按钮 -->
<view v-if="!isAuthor" class="widget-location dis-flex flex-x-center flex-y-center" @click="onAuthorize()">
<text class="iconfont icon-locate"></text>
</view>
<empty v-if="list.length == 0" :isLoading="isLoading" tips="亲,暂无消息" />
</view>
</template>
<script>
import store from '@/store/index.js'
import storage from '../../utils/storage'
import { ACCESS_TOKEN, USER_ID } from '@/store/mutation-types'
import { pageMerchant } from '@/websoft/api/merchant.js'
import { getSetting } from '@/websoft/api/setting.js'
import Empty from '@/components/empty'
import { userId } from '../../config'
export default {
components: {
// Search,
Empty
},
data() {
return {
title: '国内馆',
//
isLoading: false,
//
isAuthor: true,
// ID
selectedId: null,
//
list: []
}
},
onLoad() {
const app = this
if(!store.getters.userId){
console.log("未登录1: ");
return false;
}
//
app.getLocation(res => {
app.getShopList(res.longitude, res.latitude)
})
},
onShow() {
const app = this
// getSetting('wx-official').then(res => {
// console.log("res: ",res);
// })
if(app.list.length == 0){
app.getShopList(app.longitude,app.latitude)
}
},
methods: {
//
getShopList(longitude, latitude) {
const app = this
},
//
// https://uniapp.dcloud.io/api/location/location?id=getlocation
getLocation(callback) {
const app = this
uni.getLocation({
success: callback,
fail() {
app.$toast('获取定位失败,请点击右下角按钮重新尝试定位')
app.isAuthor = false
}
})
},
//
onAuthorize() {
const app = this
// #ifdef MP
uni.openSetting({
success(res) {
if (res.authSetting['scope.userLocation']) {
console.log('定位权限授权成功')
app.isAuthor = true
setTimeout(() => {
//
app.getLocation((res) => {
app.getShopList(res.longitude, res.latitude)
})
}, 1000)
}
}
})
// #endif
// #ifdef H5
//
app.getLocation((res) => {
app.getShopList(res.longitude, res.latitude)
})
// #endif
},
navTo(merchantId) {
const navTo = uni.$u.route()
navTo('pages/merchant/detail', {
merchantId
})
},
/**
* 选择门店
*/
onSelectedShop(merchantId,merchantCode) {
uni.$u.route('pages/merchant/detail', {
merchantId,merchantCode
})
},
changeCity() {
this.$toast('切换区域')
}
}
}
</script>
<style lang="scss" scoped>
.bg {
background-color: #F5F5F8
}
.shop-list {
.item {
padding: 20rpx;
margin: 20rpx auto;
background-color: #ffffff;
width: 660rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
image {
width: 180rpx;
height: 180rpx;
margin-right: 40rpx;
background-color: #f9f9f9;
}
.info {
display: flex;
flex-direction: column;
.title {
font-size: 38rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.desc {
font-size: 28rpx;
}
.tag {
font-size: 28rpx;
display: flex;
.mr12 {
margin-right: 12rpx;
}
}
.distance {
font-size: 28rpx;
}
}
}
}
.header-box {
padding: 20rpx 36rpx 0 36rpx;
display: flex;
justify-content: space-between;
.location {
display: flex;
justify-content: center;
align-items: center;
color: #999999;
image {
width: 60rpx;
height: 60rpx
}
}
.u-subsection {
width: 260rpx;
margin-top: 7rpx
}
}
.search-wrapper {
display: flex;
width: 80%;
height: 64rpx
}
//
.search-input {
width: 90%;
background: #fff;
border-radius: 10rpx 0 0 10rpx;
box-sizing: border-box;
overflow: hidden;
.search-input-wrapper {
display: flex;
.left {
display: flex;
width: 60rpx;
justify-content: center;
align-items: center;
.search-icon {
display: block;
color: #b4b4b4;
font-size: 28rpx
}
}
.right {
flex: 1;
input {
font-size: 28rpx;
height: 64rpx;
display: flex;
.input-placeholder {
color: #aba9a9
}
}
}
}
}
.shop-avatar {
width: 200rpx;
margin-right: 24rpx
}
//
.search-button {
width: 25%;
box-sizing: border-box;
.button {
height: 64rpx;
font-size: 28rpx;
border-radius: 0 5px 5px 0;
background: #2C71C7
}
}
.shop-info {
width: 100%;
margin-left: 20rpx;
.title {
font-size: 38rpx;
font-weight: bold;
padding-bottom: 10rpx;
}
.about {
display: flex;
.mr12 {
margin-right: 12rpx;
}
}
}
.u-body-item {
align-items: stretch !important
}
</style>
+566
View File
@@ -0,0 +1,566 @@
<template>
<view v-if="!isLoading" class="container" :style="appThemeStyle">
<view class="goods-list">
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
<!-- 商品详情 -->
<view class="goods-main">
<!-- 商品图片 -->
<view class="goods-image">
<image class="image" :src="item.goodsImage" mode="scaleToFill"></image>
</view>
<!-- 商品信息 -->
<view class="goods-content">
<view class="goods-title"><text class="twoline-hide">{{ item.goodsName }}</text></view>
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in item.goodsProps" :key="idx">
<text>{{ props.value.name }}</text>
</view>
</view>
</view>
<!-- 交易信息 -->
<view class="goods-trade">
<view class="goods-price">
<text class="unit"></text>
<text class="value">{{ item.goodsPrice }}</text>
</view>
<view class="goods-num">
<text>×{{ item.totalNum }}</text>
</view>
</view>
</view>
<!-- 选择评价 -->
<view class="score-row">
<view class="score-item score-praise" :class="{ active: formData[index].score == 10 }" @click="setScore(index, 10)">
<view class="score">
<text class="score-icon iconfont icon-haoping"></text>
<text class="score-text">好评</text>
</view>
</view>
<view class="score-item score-review" :class="{ active: formData[index].score == 20 }" @click="setScore(index, 20)">
<view class="score">
<text class="score-icon iconfont icon-zhongping"></text>
<text class="score-text">中评</text>
</view>
</view>
<view class="score-item score-negative" :class="{ active: formData[index].score == 30 }" @click="setScore(index, 30)">
<view class="score">
<text class="score-icon iconfont icon-chaping"></text>
<text class="score-text">差评</text>
</view>
</view>
</view>
<!-- 评价内容 -->
<view class="form-content">
<textarea class="textarea" v-model="formData[index].content" maxlength="500" placeholder="请输入评价内容 (留空则不评价)"></textarea>
</view>
<!-- 图片列表 -->
<view class="image-list">
<view class="image-preview" v-for="(image, imageIndex) in formData[index].imageList" :key="imageIndex">
<text class="image-delete iconfont icon-shanchu" @click="deleteImage(index, imageIndex)"></text>
<image class="image" mode="aspectFill" :src="image.path"></image>
</view>
<view v-if="!formData[index].imageList || formData[index].imageList.length < maxImageLength" class="image-picker"
@click="chooseImage(index)">
<text class="choose-icon iconfont icon-camera"></text>
<text class="choose-text">上传图片</text>
</view>
</view>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="footer-fixed">
<view class="btn-wrapper">
<view class="btn-item btn-item-main" :class="{ disabled }" @click="handleSubmit()">确认提交</view>
</view>
</view>
</view>
</template>
<script>
import * as UploadApi from '@/api/upload'
import * as OrderCommentApi from '@/api/order/comment'
const maxImageLength = 6
export default {
data() {
return {
//
isLoading: true,
// ID
orderId: null,
//
goodsList: [],
//
formData: [],
//
maxImageLength,
//
disabled: false
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({ orderId }) {
this.orderId = orderId
//
// this.getGoodsList()
},
methods: {
//
getGoodsList() {
const app = this
app.isLoading = true
OrderCommentApi.list(app.orderId)
.then(result => {
app.goodsList = result.data.goodsList
app.initFormData()
app.isLoading = false
})
},
// form
initFormData() {
const { goodsList } = this
const formData = goodsList.map(item => {
return {
goods_id: item.goods_id,
order_goods_id: item.order_goods_id,
score: 10,
content: '',
imageList: [],
uploaded: []
}
})
this.formData = formData
},
//
setScore(index, score) {
this.formData[index].score = score
},
//
chooseImage(index) {
const app = this
const oldImageList = app.formData[index].imageList
//
uni.chooseImage({
count: maxImageLength - oldImageList.length,
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success({ tempFiles }) {
// tempFiles = [{path:'xxx', size:100}]
app.formData[index].imageList = oldImageList.concat(tempFiles)
}
});
},
//
deleteImage(index, imageIndex) {
this.formData[index].imageList.splice(imageIndex, 1)
},
//
handleSubmit() {
const app = this
//
if (app.disabled === true) return false
//
app.disabled = true
//
const imagesLength = app.getImagesLength()
if (imagesLength > 0) {
app.uploadFile()
.then(() => {
console.log('then')
app.onSubmit()
})
.catch(err => {
console.log('catch')
app.disabled = false
if (err.statusCode !== 0) {
app.$toast(err.errMsg)
}
console.log('err', err)
})
} else {
app.onSubmit()
}
},
//
getImagesLength() {
const { formData } = this
let imagesLength = 0
formData.forEach(item => {
if (item.content.trim()) {
imagesLength += item.imageList.length
}
})
return imagesLength
},
//
onSubmit() {
const app = this
OrderCommentApi.submit(app.orderId, app.formData)
.then(result => {
app.$toast(result.message)
setTimeout(() => {
app.disabled = false
uni.navigateBack()
}, 1500)
})
.catch(err => app.disabled = false)
},
//
uploadFile() {
const app = this
const { formData } = app
//
const files = []
formData.forEach((item, index) => {
if (item.content.trim() && item.imageList.length) {
const images = item.imageList.map(image => image)
files.push({ formDataIndex: index, images })
}
})
//
return new Promise((resolve, reject) => {
Promise.all(files.map((file, index) => {
return new Promise((resolve, reject) => {
UploadApi.image(file.images)
.then(fileIds => {
app.formData[index].uploaded = fileIds
resolve(fileIds)
})
.catch(reject)
})
}))
.then(resolve, reject)
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
// ios线
padding-bottom: calc(constant(safe-area-inset-bottom) + 140rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
}
.goods-list {
font-size: 28rpx;
padding-top: 30rpx;
}
.goods-item {
width: 94%;
background: #fff;
padding: 24rpx 24rpx;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
margin: 0 auto 30rpx auto;
border-radius: 20rpx;
.goods-detail {
padding: 24rpx 20rpx;
.left {
.goods-image {
display: block;
width: 150rpx;
height: 150rpx;
}
}
.right {
padding-left: 20rpx;
}
}
.score-row {
display: flex;
justify-content: space-around;
padding: 24rpx 20rpx;
.score-item {
display: flex;
justify-content: center;
align-items: center;
&.score-praise {
color: $main-bg;
.score-icon {
background: $main-bg;
}
}
&.score-review {
color: $vice-bg;
.score-icon {
background: $vice-bg;
}
}
&.score-negative {
color: #9b9b9b;
.score-icon {
background: #9b9b9b;
}
}
.score {
padding: 10rpx 20rpx 10rpx 10rpx;
border-radius: 30rpx;
.score-icon {
margin-right: 10rpx;
padding: 10rpx;
border-radius: 50%;
font-size: 30rpx;
color: #fff;
}
}
&.active {
.score {
color: #fff;
}
&.score-praise {
.score {
background: $main-bg;
}
}
&.score-review {
.score {
background: $vice-bg;
}
}
&.score-negative {
.score {
background: #9b9b9b;
}
}
}
}
}
//
.form-content {
padding: 14rpx 10rpx;
.textarea {
width: 100%;
height: 220rpx;
padding: 12rpx;
border: 1rpx solid #e8e8e8;
border-radius: 5rpx;
box-sizing: border-box;
font-size: 26rpx;
}
}
.image-list {
padding: 0 20rpx;
margin-top: 20rpx;
margin-bottom: -20rpx;
&:after {
clear: both;
content: " ";
display: table;
}
.image {
display: block;
width: 100%;
height: 100%;
}
.image-picker,
.image-preview {
width: 184rpx;
height: 184rpx;
margin-right: 30rpx;
margin-bottom: 30rpx;
float: left;
&:nth-child(3n+0) {
margin-right: 0;
}
}
.image-picker {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1rpx dashed #ccc;
color: #ccc;
.choose-icon {
font-size: 48rpx;
margin-bottom: 6rpx;
}
.choose-text {
font-size: 24rpx;
}
}
.image-preview {
position: relative;
.image-delete {
position: absolute;
top: -15rpx;
right: -15rpx;
height: 42rpx;
width: 42rpx;
background: rgba(0, 0, 0, 0.64);
border-radius: 50%;
color: #fff;
font-weight: bolder;
font-size: 22rpx;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
}
}
}
//
.goods-main {
display: flex;
margin-bottom: 20rpx;
//
.goods-image {
width: 180rpx;
height: 180rpx;
.image {
display: block;
width: 100%;
height: 100%;
border-radius: 8rpx;
}
}
//
.goods-content {
flex: 1;
padding-left: 16rpx;
padding-top: 16rpx;
.goods-title {
font-size: 26rpx;
max-height: 76rpx;
}
.goods-props {
margin-top: 14rpx;
height: 40rpx;
color: #ababab;
font-size: 24rpx;
overflow: hidden;
.goods-props-item {
display: inline-block;
margin-right: 14rpx;
padding: 4rpx 16rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
width: auto;
}
}
}
//
.goods-trade {
padding-top: 16rpx;
width: 150rpx;
text-align: right;
color: $uni-text-color-grey;
font-size: 26rpx;
.goods-price {
vertical-align: bottom;
margin-bottom: 16rpx;
.unit {
margin-right: -2rpx;
font-size: 24rpx;
}
}
}
}
//
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
// ios线
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.btn-wrapper {
height: 140rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 80rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
//
&.disabled {
opacity: 0.6;
}
}
}
</style>
+323
View File
@@ -0,0 +1,323 @@
<template>
<view class="container" :style="appThemeStyle">
<view class="reject" v-if="form.apply_status == 10">
<u-alert-tips type="error" title="申请已提交:" description="请耐心等待工作人员的审核"></u-alert-tips>
</view>
<view class="reject" v-else-if="form.apply_status == 30">
<u-alert-tips type="error" title="您的申请已被驳回:" :description="form.reject_reason"></u-alert-tips>
</view>
<u-form :model="form" ref="uForm" label-width="140rpx">
<!-- 标题 -->
<view class="page-title">身份证信息</view>
<view class="form-wrapper">
<u-form-item label="正面" prop="img1">
<image :src="orderSourceData[0]" v-if="orderSourceData[0]" mode="aspectFill" style="width: 300rpx;height: 200rpx"></image>
<image src="../../static/not-dealer.png" v-else mode="aspectFill" style="width: 300rpx;height: 200rpx" @click="chooseImage(1)"></image>
</u-form-item>
<u-form-item label="反面" prop="img2">
<image :src="orderSourceData[1]" v-if="orderSourceData[1]" mode="aspectFill" style="width: 300rpx;height: 200rpx"></image>
<image src="../../static/not-dealer.png" v-else mode="aspectFill" style="width: 300rpx;height: 200rpx" @click="chooseImage(2)"></image>
</u-form-item>
</view>
<!-- 标题 -->
<view class="page-title">其他</view>
<!-- 表单组件 -->
<view class="form-wrapper">
<u-form-item label="人车合照" prop="img3">
<image :src="orderSourceData[2]" v-if="orderSourceData[2]" mode="aspectFill" style="width: 300rpx;height: 200rpx"></image>
<image src="../../static/not-dealer.png" v-else mode="aspectFill" style="width: 300rpx;height: 200rpx" @click="chooseImage(3)"></image>
</u-form-item>
<u-form-item label="车子照片" prop="img4">
<image :src="orderSourceData[3]" v-if="orderSourceData[3]" mode="aspectFill" style="width: 300rpx;height: 200rpx"></image>
<image src="../../static/not-dealer.png" v-else mode="aspectFill" style="width: 300rpx;height: 200rpx" @click="chooseImage(4)"></image>
</u-form-item>
<u-form-item label="安装照片" prop="img5">
<image :src="orderSourceData[4]" v-if="orderSourceData[4]" mode="aspectFill" style="width: 300rpx;height: 200rpx"></image>
<image src="../../static/not-dealer.png" v-else mode="aspectFill" style="width: 300rpx;height: 200rpx" @click="chooseImage(5)"></image>
</u-form-item>
</view>
</u-form>
<view class="btn">
<u-button type="primary" shape="circle" @click="handleSubmit()">
{{ submitText }}
</u-button>
</view>
<view class="btn">
<u-button shape="circle" @click="resetting">
重置
</u-button>
</view>
<view class="btn"></view>
</view>
</template>
<script>
import {
uploadFile
} from '@/websoft/api/upload.js'
import {
pageOrder,
removeOrder,
receiptOrder,
getOrder
} from '@/websoft/api/order.js'
import { fileUrl } from '@/config.js';
export default {
data() {
return {
order: {},
orderSourceData: [],
//
disabled: false,
submitText: '证件已上传并确认收货',
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const app = this
app.orderId = options.orderId
},
onShow() {
this.getData()
},
methods: {
//
getData(){
const app = this
const { orderId } = this
getOrder(orderId).then(res => {
console.log("res: ",res);
app.order = res.data
if(res.data.orderSourceData.length > 0){
app.orderSourceData = JSON.parse(res.data.orderSourceData)
}
})
},
//
resetting(){
const app = this
const { orderId } = app
receiptOrder({
orderId,
orderSourceData: ''
}).then(result => {
app.$success("重置成功")
app.orderSourceData = []
//
app.getData()
})
},
//
handleSubmit() {
const app = this
this.$navTo('pages/order/index')
},
//
chooseImage(id) {
const app = this
const { orderId } = this
const type = 'photo' + id
//
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success(chooseImageRes) {
const tempFilePaths = chooseImageRes.tempFilePaths;
uploadFile({
filePath: tempFilePaths[0],
fileType: 'image',
name: 'file'
}).then(res => {
console.log("res: ", res);
app.orderSourceData.push(fileUrl + res.data.path)
receiptOrder({
orderId,
orderSourceData: JSON.stringify(app.orderSourceData)
}).then(result => {
app.$success("上传成功")
//
app.getData()
})
})
}
});
},
}
}
</script>
<style>
page {
background: #f7f8fa;
}
</style>
<style lang="scss" scoped>
.page-title {
width: 94%;
margin: 0 auto;
padding-top: 40rpx;
font-size: 28rpx;
color: rgba(69, 90, 100, 0.6);
}
.form-wrapper {
margin: 20rpx auto 20rpx auto;
padding: 0 40rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
background: #fff;
}
/* 底部操作栏 */
.footer {
margin-top: 80rpx;
.btn-wrapper {
height: 100%;
// display: flex;
// align-items: center;
padding: 0 20rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 86rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
.btn-item-wechat {
background: #0ba90b;
margin-bottom: 26rpx;
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
//
&.disabled {
opacity: 0.6;
}
}
}
//
.row-voucher {
padding: 24rpx 20rpx;
.image-list {
padding: 0 20rpx;
margin-top: 20rpx;
margin-bottom: -20rpx;
&:after {
clear: both;
content: " ";
display: table;
}
.image {
display: block;
width: 100%;
height: 100%;
}
.image-picker,
.image-preview {
width: 184rpx;
height: 184rpx;
margin-right: 30rpx;
margin-bottom: 30rpx;
float: left;
&:nth-child(3n+0) {
margin-right: 0;
}
}
.image-picker {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1rpx dashed #ccc;
color: #ccc;
.choose-icon {
font-size: 48rpx;
margin-bottom: 6rpx;
}
.choose-text {
font-size: 24rpx;
}
}
.image-preview {
position: relative;
.image-delete {
position: absolute;
top: -15rpx;
right: -15rpx;
height: 42rpx;
width: 42rpx;
background: rgba(0, 0, 0, 0.64);
border-radius: 50%;
color: #fff;
font-weight: bolder;
font-size: 22rpx;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
}
}
}
.form-license {
padding: 20rpx;
}
.pops-content {
padding: 20rpx;
}
.sfz,
.sfz1,
.sfz2,
.yyzz {
width: 140rpx;
padding-top: 20rpx;
padding-right: 10rpx;
}
.btn {
width: 700rpx;
margin: 20rpx auto;
height: 100rpx;
}
.reject {
width: 700rpx;
margin: 20rpx auto;
}
</style>
+1577
View File
File diff suppressed because it is too large Load Diff
+266
View File
@@ -0,0 +1,266 @@
<template>
<view v-if="!isLoading && express.length" class="container">
<!-- 标签栏 -->
<u-tabs v-show="tabs.length > 1" class="my-tabs" :list="tabs" :isScroll="true" :current="curTab"
:active-color="appTheme.mainBg" :duration="0.2" bar-width="60" @change="onChangeTab"></u-tabs>
<!-- 商品列表 -->
<view v-show="tabs.length > 1" class="deliver-goods-list i-card clearfix">
<view class="goods-item" v-for="(goods, idx) in express[curTab].goods" :key="idx">
<image class="goods-img" :src="goods.goods.goods_image" alt="商品图片" />
<view class="title">{{ goods.delivery_num }}</view>
</view>
</view>
<!-- 物流信息 -->
<view class="express i-card">
<view class="info-item">
<view class="item-lable">物流公司</view>
<view class="item-content">
<text v-if="express[curTab].delivery_method == 20">无需物流</text>
<text v-else>{{ express[curTab].express ? express[curTab].express.express_name : '--' }}</text>
</view>
</view>
<view class="info-item">
<view class="item-lable">物流单号</view>
<view class="item-content">
<text>{{ express[curTab].express_no ? express[curTab].express_no : '--' }}</text>
<view v-show="express[curTab].express_no" class="act-copy"
@click.stop="handleCopy(express[curTab].express_no)">
<text>复制</text>
</view>
</view>
</view>
</view>
<!-- 物流轨迹 -->
<view class="logis-detail" v-if="express[curTab].traces && express[curTab].traces.length">
<view class="logis-item" :class="{ first: index === 0 }" v-for="(item, index) in express[curTab].traces"
:key="index">
<view class="logis-item-content">
<view class="logis-item-content__describe">
<text class="f-26">{{ item.context }}</text>
</view>
<view class="logis-item-content__time">
<text class="f-22">{{ item.time }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import * as OrderApi from '@/api/order'
export default {
data() {
return {
//
isLoading: true,
//
curTab: 0,
// ID
orderId: null,
//
express: {}
}
},
computed: {
tabs() {
if (this.express && this.express.length) {
return this.express.map((item, index) => {
return { name: `包裹${index + 1}` }
})
}
return []
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({ orderId }) {
this.orderId = orderId
//
this.getExpress()
},
methods: {
//
getExpress() {
const app = this
app.isLoading = true
OrderApi.express(app.orderId)
.then(result => {
app.express = result.data.express
app.isLoading = false
})
},
//
handleCopy(value) {
const app = this
uni.setClipboardData({
data: value,
success() {
app.$toast('复制成功')
}
})
},
//
onChangeTab(index) {
this.curTab = index
},
}
}
</script>
<style lang="scss" scoped>
.my-tabs {
margin-bottom: 20rpx;
}
//
.i-card {
background: #fff;
padding: 24rpx 24rpx;
}
//
.express {
margin-bottom: 20rpx;
.info-item {
display: flex;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.item-lable {
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
margin-right: 6rpx;
}
.item-content {
flex: 1;
display: flex;
align-items: center;
font-size: 26rpx;
color: #333;
.act-copy {
margin-left: 20rpx;
padding: 2rpx 20rpx;
font-size: 22rpx;
color: #666;
border: 1rpx solid #c1c1c1;
border-radius: 16rpx;
}
}
}
}
//
.deliver-goods-list {
margin-bottom: -30rpx;
.goods-item {
position: relative;
border-radius: 8rpx;
overflow: hidden;
width: 130rpx;
height: 130rpx;
float: left;
margin-right: 30rpx;
margin-bottom: 30rpx;
}
.goods-img {
display: block;
width: 100%;
height: 100%;
}
.title {
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
background: rgba(0, 0, 0, 0.6);
color: #fff;
padding: 4rpx 0;
font-size: 24rpx;
}
}
//
.logis-detail {
padding: 30rpx;
background-color: #fff;
.logis-item {
position: relative;
padding: 10px 0 10px 25px;
box-sizing: border-box;
border-left: 2px solid #ccc;
&.first {
border-left: 2px solid #f40;
&:after {
background: #f40;
}
.logis-item-content {
background: #ff6e39;
color: #fff;
&:after {
border-bottom-color: #ff6e39;
}
}
}
&:after {
content: ' ';
display: inline-block;
position: absolute;
left: -6px;
top: 30px;
width: 6px;
height: 6px;
border-radius: 10px;
background: #bdbdbd;
border: 2px solid #fff;
}
.logis-item-content {
position: relative;
background: #f9f9f9;
padding: 10rpx 20rpx;
box-sizing: border-box;
color: #666;
&:after {
content: '';
display: inline-block;
position: absolute;
left: -10px;
top: 18px;
border-left: 10px solid #fff;
border-bottom: 10px solid #f3f3f3;
}
}
}
}
</style>
+835
View File
@@ -0,0 +1,835 @@
<template>
<view v-if="!isLoading" class="container" :style="appThemeStyle">
<view class="header">
<!-- 订单状态 -->
<view class="order-status">
<view class="status-icon">
<!-- 进行中的订单 -->
<block v-if="order.order_status == OrderStatusEnum.NORMAL.value">
<!-- 待支付 -->
<block v-if="order.pay_status == PayStatusEnum.PENDING.value">
<image class="image" src="/static/order/status/wait_pay.png" mode="aspectFit"></image>
</block>
<!-- 待发货 -->
<block v-else-if="order.delivery_status == DeliveryStatusEnum.NOT_DELIVERED.value">
<image class="image" src="/static/order/status/wait_deliver.png" mode="aspectFit"></image>
</block>
<!-- 待收货 -->
<block v-else-if="order.receipt_status == ReceiptStatusEnum.NOT_RECEIVED.value">
<image class="image" src="/static/order/status/wait_receipt.png" mode="aspectFit"></image>
</block>
</block>
<!-- 已完成 -->
<block v-if="order.order_status == OrderStatusEnum.COMPLETED.value">
<image class="image" src="/static/order/status/received.png" mode="aspectFit"></image>
</block>
<!-- 已取消/待取消 -->
<block v-if="order.order_status == OrderStatusEnum.CANCELLED.value || order.order_status == OrderStatusEnum.APPLY_CANCEL.value">
<image class="image" src="/static/order/status/close.png" mode="aspectFit"></image>
</block>
</view>
<view class="status-text">
<text>{{ order.state_text }}</text>
</view>
</view>
</view>
<!-- 快递配送配送地址 -->
<view v-if="order.delivery_type == DeliveryTypeEnum.EXPRESS.value" class="delivery-address i-card">
<view class="link-man">
<text class="name">{{ order.address.name }}</text>
<text class="phone">{{ order.address.phone }}</text>
</view>
<view class="address">
<text class="region" v-for="(region, idx) in order.address.region" :key="idx">{{ region }}</text>
<text class="detail">{{ order.address.detail }}</text>
</view>
</view>
<!-- 快递配送门店自提 -->
<view v-if="order.delivery_type == DeliveryTypeEnum.EXTRACT.value" class="delivery-extract i-card"
@click="handleTargetExtract(order.extract_shop.shop_id)">
<view class="extract-top">
<text class="title">自提门店</text>
<text class="subtitle">您须到该自提点取货</text>
</view>
<view class="shop-info">
<view class="icon-location">
<text class="iconfont icon-dingwei"></text>
</view>
<view class="shop-content">
<view class="shop-name">
<text>{{ order.extract_shop.shop_name }}</text>
</view>
<view class="shop-describe">
<text class="item-text">{{ order.extract_shop.region.province }}</text>
<text class="item-text">{{ order.extract_shop.region.city }}</text>
<text class="item-text">{{ order.extract_shop.region.region }}</text>
<text class="item-text">{{ order.extract_shop.address }}</text>
</view>
</view>
<view class="icon-arrow">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
</view>
<!-- 物流信息 -->
<view v-if="order.delivery_type == DeliveryTypeEnum.EXPRESS.value && order.delivery_status == DeliveryStatusEnum.DELIVERED.value && order.express"
class="express i-card" @click="handleTargetExpress()">
<view class="main">
<view class="info-item">
<view class="item-lable">物流公司</view>
<view class="item-content">
<text>{{ order.express.express_name }}</text>
</view>
</view>
<view class="info-item">
<view class="item-lable">物流单号</view>
<view class="item-content">
<text>{{ order.express_no }}</text>
<view class="act-copy" @click.stop="handleCopy(order.express_no)">
<text>复制</text>
</view>
</view>
</view>
</view>
<view class="right-arrow">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
<!-- 商品列表 -->
<view class="goods-list i-card">
<view class="goods-item" v-for="(goods, idx) in order.goods" :key="idx">
<view class="goods-main" @click="handleTargetGoods(goods.goods_id)">
<!-- 商品图片 -->
<view class="goods-image">
<image class="image" :src="goods.goods_image" mode="scaleToFill"></image>
</view>
<!-- 商品信息 -->
<view class="goods-content">
<view class="goods-title"><text class="twoline-hide">{{ goods.goods_name }}</text></view>
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in goods.goods_props" :key="idx">
<text>{{ props.value.name }}</text>
</view>
</view>
</view>
<!-- 交易信息 -->
<view class="goods-trade">
<view class="goods-price">
<text class="unit"></text>
<text class="value">{{ goods.goods_price }}</text>
</view>
<view class="goods-num">
<text>×{{ goods.total_num }}</text>
</view>
</view>
</view>
<!-- 商品售后 -->
<view class="goods-refund">
<text v-if="goods.refund" class="stata-text">已申请售后</text>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="order-info i-card">
<view class="info-item">
<view class="item-lable">订单编号</view>
<view class="item-content">
<text>{{ order.order_no }}</text>
<view class="act-copy" @click="handleCopy(order.order_no)">
<text>复制</text>
</view>
</view>
</view>
<view class="info-item">
<view class="item-lable">下单时间</view>
<view class="item-content">
<text>{{ order.create_time }}</text>
</view>
</view>
<view class="info-item">
<view class="item-lable">买家留言</view>
<view class="item-content">
<text>{{ order.buyer_remark ? order.buyer_remark : '--' }}</text>
</view>
</view>
</view>
<!-- 结算信息 -->
<view class="trade-info i-card">
<view class="info-item">
<view class="item-lable">订单金额</view>
<view class="item-content">
<text>{{ order.total_price }}</text>
</view>
</view>
<view v-if="order.coupon_money > 0" class="info-item">
<view class="item-lable">优惠券抵扣</view>
<view class="item-content">
<text>-{{ order.coupon_money }}</text>
</view>
</view>
<view v-if="order.points_money > 0" class="info-item">
<view class="item-lable">{{ setting.points_name }}抵扣</view>
<view class="item-content">
<text>-{{ order.points_money }}</text>
</view>
</view>
<view class="info-item">
<view class="item-lable">运费</view>
<view class="item-content">
<text>+{{ order.express_price }}</text>
</view>
</view>
<view v-if="order.update_price.value != '0.00'" class="info-item">
<view class="item-lable">后台改价</view>
<view class="item-content">
<text>{{ order.update_price.symbol }}</text>
<text>{{ order.update_price.value }}</text>
</view>
</view>
<view class="divider"></view>
<view class="trade-total">
<text class="lable">实付款</text>
<view class="goods-price">
<text class="unit"></text>
<text class="value">{{ order.pay_price }}</text>
</view>
</view>
</view>
<!-- 底部操作按钮 -->
<view v-if="order.order_status != OrderStatusEnum.CANCELLED.value" class="footer-fixed">
<view class="btn-wrapper">
<!-- 已申请取消 -->
<view v-if="order.order_status == OrderStatusEnum.APPLY_CANCEL.value" class="f-28 col-8">取消申请中</view>
<!-- 确认核销 -->
<block v-else-if="order.pay_status == PayStatusEnum.SUCCESS.value && order.delivery_type == DeliveryTypeEnum.EXTRACT.value
&& order.delivery_status == DeliveryStatusEnum.NOT_DELIVERED.value">
<view class="btn-item active" @click="onConfirmExtract()">确认核销</view>
</block>
</view>
</view>
</view>
</template>
<script>
import { getSceneData } from '@/core/app'
import {
DeliveryStatusEnum,
DeliveryTypeEnum,
OrderStatusEnum,
PayStatusEnum,
ReceiptStatusEnum
} from '@/common/enum/order'
import * as OrderApi from '@/api/shop/order'
import { wxPayment } from '@/core/app'
export default {
data() {
return {
//
DeliveryStatusEnum,
DeliveryTypeEnum,
OrderStatusEnum,
PayStatusEnum,
ReceiptStatusEnum,
// ID
orderId: null,
//
isLoading: true,
//
order: {},
//
setting: {}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// query
this.onRecordQuery(options)
//
this.getOrderDetail()
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
methods: {
// query
onRecordQuery(query) {
const scene = getSceneData(query)
this.orderId = query.orderId ? parseInt(query.orderId) : parseInt(scene.oid)
},
//
getOrderDetail() {
const app = this
app.isLoading = true
OrderApi.detail(app.orderId)
.then(result => {
app.order = result.data.order
app.setting = result.data.setting
app.isLoading = false
})
},
//
handleCopy(value) {
const app = this
uni.setClipboardData({
data: value,
success() {
app.$toast('复制成功')
}
})
},
//
handleTargetExtract(shopId) {
this.$navTo('pages/shop/detail', { shopId })
},
//
handleTargetExpress() {
this.$navTo('pages/order/express/index', { orderId: this.orderId })
},
//
handleTargetGoods(goodsId) {
this.$navTo('pages/goods/detail', { goodsId })
},
//
onConfirmExtract() {
const app = this
uni.showModal({
title: '友情提示',
content: '确认要核销该订单吗?请确认买家已取到货~',
success(o) {
if (o.confirm) {
OrderApi.extract(app.orderId)
.then(result => {
//
app.$success(result.message)
setTimeout(() => {
//
app.getOrderDetail()
}, 1500)
})
}
}
});
}
},
}
</script>
<style>
page {
background: #f4f4f4;
}
</style>
<style lang="scss" scoped>
.container {
// ios线
padding-bottom: calc(constant(safe-area-inset-bottom) + 106rpx + 6rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 106rpx + 6rpx);
}
//
.header {
display: flex;
justify-content: space-between;
background-color: #e8c269;
height: 280rpx;
padding: 56rpx 30rpx 0 30rpx;
.order-status {
display: flex;
align-items: center;
height: 128rpx;
.status-icon {
width: 128rpx;
height: 128rpx;
.image {
display: block;
width: 100%;
height: 100%;
}
}
.status-text {
padding-left: 20rpx;
color: #fff;
font-size: 38rpx;
font-weight: bold;
}
}
.next-action {
display: flex;
align-items: center;
height: 128rpx;
.action-btn {
min-width: 152rpx;
height: 56rpx;
padding: 0 30rpx;
background-color: #fff;
border-radius: 28rpx;
border-color: rgb(102, 102, 102);
cursor: pointer;
user-select: none;
color: #c7a157;
display: flex;
justify-content: center;
align-items: center;
}
}
}
//
.i-card {
background: #fff;
padding: 24rpx 24rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
margin: 0 auto 20rpx auto;
border-radius: 20rpx;
}
//
.delivery-extract {
margin-top: -50rpx;
.extract-top {
margin-bottom: 20rpx;
.title {
font-size: 28rpx;
margin-right: 30rpx;
}
.subtitle {
font-size: 24rpx;
color: #888;
}
}
.shop-info {
display: flex;
align-items: center;
}
.icon-location {
font-size: 34rpx;
}
.shop-content {
flex: 1;
margin-left: 26rpx;
font-size: 24rpx;
.shop-name {
font-size: 28rpx;
margin-bottom: 8rpx;
}
.shop-describe {
color: #777;
.item-text {
margin-right: 8rpx;
}
}
}
}
//
.delivery-address {
margin-top: -50rpx;
.link-man {
line-height: 46rpx;
color: #333;
.name {
margin-right: 10rpx;
}
}
.address {
margin-top: 12rpx;
color: #999;
font-size: 24rpx;
.detail {
margin-left: 6rpx;
}
}
}
//
.express {
display: flex;
align-items: center;
.main {
flex: 1;
}
.info-item {
display: flex;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.item-lable {
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
margin-right: 30rpx;
}
.item-content {
flex: 1;
display: flex;
align-items: center;
font-size: 26rpx;
color: #333;
.act-copy {
margin-left: 20rpx;
padding: 2rpx 20rpx;
font-size: 22rpx;
color: #666;
border: 1rpx solid #c1c1c1;
border-radius: 16rpx;
}
}
}
//
.right-arrow {
margin-left: 16rpx;
// color: #777;
font-size: 26rpx;
}
}
//
.goods-list {
//
.goods-item {
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
//
.goods-main {
display: flex;
}
//
.goods-image {
width: 180rpx;
height: 180rpx;
.image {
display: block;
width: 100%;
height: 100%;
border-radius: 8rpx;
}
}
//
.goods-content {
flex: 1;
padding-left: 16rpx;
padding-top: 16rpx;
.goods-title {
font-size: 26rpx;
max-height: 76rpx;
}
.goods-props {
margin-top: 14rpx;
height: 40rpx;
color: #ababab;
font-size: 24rpx;
overflow: hidden;
.goods-props-item {
display: inline-block;
margin-right: 14rpx;
padding: 4rpx 16rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
width: auto;
}
}
}
//
.goods-trade {
padding-top: 16rpx;
width: 150rpx;
text-align: right;
color: $uni-text-color-grey;
font-size: 26rpx;
.goods-price {
vertical-align: bottom;
margin-bottom: 16rpx;
.unit {
margin-right: -2rpx;
font-size: 24rpx;
}
}
}
//
.goods-refund {
display: flex;
justify-content: flex-end;
.stata-text {
font-size: 24rpx;
color: #999;
}
.action-btn {
border-radius: 28rpx;
padding: 8rpx 26rpx;
font-size: 24rpx;
color: #383838;
border: 1rpx solid #a8a8a8;
}
}
}
}
//
.order-info {
.info-item {
display: flex;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.item-lable {
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
margin-right: 30rpx;
}
.item-content {
flex: 1;
display: flex;
align-items: center;
font-size: 26rpx;
color: #333;
.act-copy {
margin-left: 20rpx;
padding: 2rpx 20rpx;
font-size: 22rpx;
color: #666;
border: 1rpx solid #c1c1c1;
border-radius: 16rpx;
}
}
}
}
//
.trade-info {
.info-item {
display: flex;
margin-bottom: 24rpx;
.item-lable {
font-size: 24rpx;
color: #999;
margin-right: 24rpx;
}
.item-content {
flex: 1;
font-size: 26rpx;
color: #333;
text-align: right;
}
}
.divider {
height: 1rpx;
background: #f1f1f1;
margin-bottom: 24rpx;
}
.trade-total {
display: flex;
justify-content: flex-end;
.goods-price {
margin-left: 12rpx;
vertical-align: bottom;
color: $main-bg;
.unit {
margin-right: -2rpx;
font-size: 24rpx;
}
}
}
}
//
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
box-shadow: 0 -4rpx 40rpx 0 rgba(151, 151, 151, 0.24);
background: #fff;
// ios线
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.btn-wrapper {
height: 106rpx;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 30rpx;
}
.btn-item {
min-width: 180rpx;
border-radius: 30rpx;
padding: 12rpx 26rpx;
font-size: 28rpx;
color: #383838;
text-align: center;
border: 1rpx solid #a8a8a8;
margin-left: 24rpx;
&.active {
border: none;
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
}
}
}
// -
.pay-popup {
padding: 24rpx;
.title {
font-size: 30rpx;
margin-bottom: 50rpx;
font-weight: bold;
text-align: center;
}
.pop-content {
min-height: 260rpx;
padding: 0 10rpx;
.pay-item {
padding: 20rpx 35rpx;
font-size: 28rpx;
border-bottom: 1rpx solid #f1f1f1;
&:last-child {
border-bottom: none;
}
.item-left_icon {
margin-right: 20rpx;
font-size: 32rpx;
&.wechat {
color: #00c800;
}
&.balance {
color: #ff9700;
}
}
}
}
}
// -
.qrcode-popup {
padding: 36rpx 30rpx;
.title {
font-size: 30rpx;
margin-bottom: 26rpx;
font-weight: bold;
text-align: center;
}
.pop-content {
min-height: 260rpx;
padding: 0 10rpx;
.image {
display: block;
width: 510rpx;
height: 510rpx;
}
}
}
</style>
+811
View File
@@ -0,0 +1,811 @@
<template>
<view class="container" :style="appThemeStyle">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ native: true }"
@down="downCallback" :up="upOption" @up="upCallback">
<!-- tab栏 -->
<u-tabs :list="tabs" :is-scroll="false" :current="curTab" :active-color="appTheme.mainBg" :duration="0.2"
@change="onChangeTab" />
<!-- 订单列表 -->
<view class="order-list">
<view class="order-item" v-for="(item, index) in list" :key="index" v-if="item.equipmentGoods">
<view class="item-top">
<view class="item-top-left">
<text class="order-time">{{ item.merchantName }}</text>
</view>
<view class="item-top-right">
<text class="state-text" v-if="item.payStatus == 10">待支付</text>
<text class="state-text" v-if="item.payStatus == 20 && item.receiptStatus == 10">待绑定</text>
<text class="state-text" v-if="item.orderStatus == 30 && item.receiptStatus == 20">已完成</text>
<text class="state-text" v-if="item.receiptStatus == 30">已退租</text>
</view>
</view>
<!-- 商品列表 -->
<view class="goods-list" @click="handleTargetDetail(item.orderId)">
<view class="goods-item">
<!-- 商品图片 -->
<view class="goods-image">
<image v-if="item.image" :src="item.image" class="equipment-image" mode="aspectFit">
</image>
<image v-else class="image" src="/static/goods/battery.png" mode="scaleToFill"></image>
</view>
<!-- 商品信息 -->
<view class="goods-content">
<view class="goods-title">
<text class="twoline-hide">
下单类型
<span v-if="item.equipmentGoods.equipmentCategory == 10">销售</span>
<span v-if="item.equipmentGoods.equipmentCategory == 20">分期</span>
<span v-if="item.equipmentGoods.equipmentCategory == 30">以租代购</span>
<span v-if="item.equipmentGoods.equipmentCategory == 40">租赁</span>
</text>
<text class="twoline-hide">订单编号{{ item.orderNo }}</text>
<text class="twoline-hide"
v-if="item.orderStatus == 30">到期时间{{ item.expirationTime }}</text>
<text class="twoline-hide" v-else>下单时间{{ item.payTime }}</text>
<text class="twoline-hide"
v-if="item.orderStatus == 30">设备编号{{ item.equipment.equipmentCode }}</text>
<text class="twoline-hide">设备名称{{ item.equipmentGoods.goodsName }}</text>
<text class="twoline-hide">设备型号{{ item.equipmentGoods.batteryModel }}</text>
<text class="twoline-hide yuqi-text" v-if="item.expirationDay < 0">逾期状态: (已逾期{{ Math.abs(item.expirationDay) }})</text>
<text class="twoline-hide yuqi-text" v-if="item.expirationDay < 7 && item.expirationDay > 0">逾期状态: 即将过期</text>
</view>
<view class="goods-props clearfix">
<!-- <view class="goods-props-item">
<text>props.value.name</text>
</view> -->
</view>
</view>
<!-- 交易信息 -->
<!-- <view class="goods-trade">
<view class="goods-price">
<text class="unit"></text>
<text class="value">{{ item.batteryRent }}</text>
</view>
<view class="goods-num">
<text>x1</text>
</view>
</view> -->
</view>
</view>
<!-- 订单合计 -->
<view class="order-total">
<!-- <text>押金{{ item.batteryDeposit }}, 保险{{ item.batteryInsurance }}, 订单金额</text> -->
<text>合计</text>
<text class="unit" style="color: #ff0000;"></text>
<text class="money">{{ item.totalPrice }}</text>
</view>
<!-- 订单操作 -->
<view v-if="item.orderStatus != OrderStatusEnum.CANCELLED.value" class="order-handle">
<view class="btn-group clearfix">
<!-- 未支付取消订单 -->
<block v-if="item.payStatus == PayStatusEnum.PENDING.value">
<view class="btn-item" @click="onCancel(item.orderId)">取消</view>
</block>
<!-- 已支付进行中的订单 -->
<block v-if="item.orderStatus != OrderStatusEnum.APPLY_CANCEL.value">
<!-- <block v-if="item.payStatus == PayStatusEnum.SUCCESS.value && item.deliveryStatus == DeliveryStatusEnum.NOT_DELIVERED.value">
<view class="btn-item" @click="onCancel(item.orderId)">申请取消</view>
</block> -->
<!-- 订单核销码 -->
<block v-if="item.payStatus == PayStatusEnum.SUCCESS.value && item.deliveryType == DeliveryTypeEnum.EXTRACT.value
&& item.deliveryStatus == DeliveryStatusEnum.NOT_DELIVERED.value">
<view class="btn-item active" @click="onExtractQRCode(item.orderId)">
<text class="iconfont icon-qr-extract"></text>
<text class="m-l-10">核销码</text>
</view>
</block>
</block>
<!-- 已申请取消 -->
<view v-else class="f-28 col-8">取消申请中</view>
<!-- 未支付的订单 -->
<block v-if="item.payStatus == PayStatusEnum.PENDING.value">
<view class="btn-item active" @click="onPay(item.orderId)">去支付</view>
</block>
<!-- 确认收货 -->
<block
v-if="item.payStatus == PayStatusEnum.SUCCESS.value && item.deliveryStatus == DeliveryStatusEnum.NOT_DELIVERED.value && item.receiptStatus == ReceiptStatusEnum.NOT_RECEIVED.value">
<view class="btn-item active" v-if="(item.orderSourceData && JSON.parse(item.orderSourceData).length == 5) || item.equipmentGoods.equipmentCategory == '10'" @click="onBind(item)">绑定设备</view>
<view class="btn-item active" v-else @click="chooseImage(item.orderId)">确认收货</view>
</block>
<!-- 订单评价 -->
<block v-if="item.orderStatus == OrderStatusEnum.COMPLETED.value && item.isComment == 0">
<view class="btn-item" v-if="item.equipmentGoods.equipmentCategory == '40'"
@click="handleChangeEquipment(item.orderId,item.equipment.equipmentId)">换电</view>
<!-- <view class="btn-item" v-if="item.equipmentGoods.equipmentCategory == '锂电池租赁' || item.equipmentGoods.equipmentCategory == '电动车租赁'" @click="handleTargetDetail(item.orderId)">续费</view> -->
<view class="btn-item" @click="handleTargetDetail(item.orderId)">详情</view>
</block>
</view>
</view>
</view>
</view>
</mescroll-body>
<!-- 核销二维码弹窗 -->
<u-popup v-model="showQRCodePopup" mode="center" border-radius="26" :closeable="true">
<view class="qrcode-popup">
<view class="title">自提核销二维码</view>
<view class="pop-content">
<image v-if="qrcodeImage" class="image" :src="qrcodeImage"></image>
</view>
</view>
</u-popup>
<!-- 绑定设备码 -->
<u-popup v-model="showQRCodeBind" mode="center" border-radius="26" :closeable="true">
<view class="qrcode-bind">
<view class="title">请核对要绑定的设备编号是否正确</view>
<view class="bind-content">
<input v-model="bindValue" class="input" @confirm="onSearch" focus="true" placeholder="请输入设备编号"
type="text"></input>
</view>
<view class="submit">
<u-button type="error" @click="doBind">立即绑定</u-button>
</view>
</view>
</u-popup>
<!-- 换电 -->
<u-popup v-model="showQRCodeBindChange" mode="center" border-radius="26" :closeable="true">
<view class="qrcode-bind">
<view class="title">请输入更换的新电池编号</view>
<view class="bind-content">
<input v-model="bindValue" class="input" @confirm="onSearch" focus="true" placeholder="请输入设备编号"
type="text"></input>
</view>
<view class="submit">
<u-button type="error" @click="doEquipment(true)">确定更换电池</u-button>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import {
DeliveryStatusEnum,
DeliveryTypeEnum,
OrderStatusEnum,
PayStatusEnum,
ReceiptStatusEnum
} from '@/common/enum/order'
import store from '@/store/index.js'
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import {
getEmptyPaginateObj,
getMoreListData
} from '@/core/app'
import * as OrderApi from '@/api/order'
import {
wxPayment
} from '@/core/app'
import * as EquipmentApi from '@/websoft/api/equipment.js'
import {
pageOrder,
removeOrder,
receiptOrder
} from '@/websoft/api/order.js'
import {
bindEquipment
} from '@/websoft/api/equipment.js'
import {
uploadFile
} from '@/websoft/api/upload.js'
import { fileUrl } from '@/config.js';
//
const pageSize = 15
// tab
const tabs = [{
name: `全部`,
value: 'all'
}, {
name: `待支付`,
value: 'payment'
}, {
name: `待绑定`,
value: 'delivery'
}, {
name: `已完成`,
value: 'comment'
}]
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
//
DeliveryStatusEnum,
DeliveryTypeEnum,
OrderStatusEnum,
PayStatusEnum,
ReceiptStatusEnum,
//
options: {
dataType: 'all'
},
// tab
tabs,
//
curTab: 0,
//
list: [],
//
upOption: {
//
auto: true,
// ; 10
page: {
size: pageSize
},
// 4
noMoreSize: 4,
//
empty: {
tip: '亲,暂无订单记录'
}
},
// onShow
canReset: false,
//
showQRCodePopup: false,
// url ()
qrcodeImage: '',
where: {},
showQRCodeBind: false,
bindValue: '',
//
equipment: {},
equipmentId: null,
showQRCodeBindChange: false
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
//
this.initCurTab(options)
// :
uni.$on('syncRefreshOrder', canReset => {
this.canReset = canReset
})
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
this.canReset && this.onRefreshList()
this.canReset = false
},
/**
* 生命周期函数--监听页面的卸载
*/
onUnload() {
//
uni.$off('syncRefreshOrder')
},
methods: {
//
initCurTab(options) {
const app = this
if (options.dataType) {
const index = app.tabs.findIndex(item => item.value == options.dataType)
app.curTab = index > -1 ? index : 0
}
},
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
//
app.getOrderList(page.num)
// .then(list => {
// console.log("list: ",list);
// const curPageLen = list.data.length
// const totalSize = list.data.total
// app.mescroll.endBySize(curPageLen, totalSize)
// })
// .catch(() => app.mescroll.endErr())
},
//
getOrderList(pageNo = 1) {
const app = this
const {
curTab
} = this
// app.where.userId = uni.getStorageSync('userId')
console.log("curTab: ", curTab);
app.where = {}
if (curTab == 0) {
app.where = {}
}
if (curTab == 1) {
app.where.payStatus = 10;
}
if (curTab == 2) {
app.where.payStatus = 20;
app.where.receiptStatus = 10;
}
if (curTab == 3) {
app.where.payStatus = 20;
app.where.receiptStatus = 20;
}
if (curTab == 4) {
app.where.receiptStatus = 20;
app.where.isComment = 0;
}
app.where.userId = uni.getStorageSync('userId');
app.where.isRenew = 0;
pageOrder(app.where).then(res => {
app.list = res.data.list
const curPageLen = res.data.list.length
const totalSize = res.data.count
console.log("totalSize: ", totalSize);
app.mescroll.endBySize(curPageLen, totalSize)
app.list.map((d,index) => {
const setTime = new Date(d.expirationTime);
const nowTime = new Date();
const restSec = setTime.getTime() - nowTime.getTime();
//
const day = parseInt(restSec / (60*60*24*1000));
console.log("逾期天数: ",Math.abs(day));
app.list[index].expirationDay = day
})
})
// return new Promise((resolve, reject) => {
// OrderApi.list({ dataType: app.getTabValue(), page: pageNo }, { load: false })
// .then(result => {
// //
// const newList = app.initList(result.data.list)
// app.list.data = getMoreListData(newList, app.list, pageNo)
// resolve(newList)
// })
// })
},
//
initList(newList) {
newList.data.forEach(item => {
item.totalNum = 0
item.goods.forEach(goods => {
item.totalNum += goods.totalNum
})
})
return newList
},
//
getTabValue() {
return this.tabs[this.curTab].value
},
//
onChangeTab(index) {
const app = this
//
app.curTab = index
//
app.onRefreshList()
},
//
onRefreshList() {
this.list = getEmptyPaginateObj()
setTimeout(() => {
this.mescroll.resetUpScroll()
}, 120)
},
//
onCancel(orderId) {
const app = this
uni.showModal({
title: '友情提示',
content: '确认要取消该订单吗?',
success(o) {
if (o.confirm) {
removeOrder(orderId)
.then(result => {
//
app.$toast(result.message)
//
app.onRefreshList()
})
}
}
});
},
//
onReceipt(orderId) {
const app = this
uni.showModal({
title: '友情提示',
content: '确认收到商品了吗?',
success(o) {
if (o.confirm) {
OrderApi.receipt(orderId)
.then(result => {
//
app.$success(result.message)
//
app.onRefreshList()
})
}
}
});
},
//
chooseImage(orderId) {
const app = this
this.$navTo('pages/order/delivery', {
orderId
})
return;
//
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success(chooseImageRes) {
const tempFilePaths = chooseImageRes.tempFilePaths;
uploadFile({
filePath: tempFilePaths[0],
fileType: 'image',
name: 'file'
}).then(res => {
console.log("res: ", res);
const orderSourceData = JSON.stringify({
orderId,
photo: fileUrl + res.data.path
})
receiptOrder({
orderId,
orderSourceData
}).then(result => {
app.$success(result.message)
//
app.onRefreshList()
})
})
// uni.uploadFile({
// url: 'http://127.0.0.1:9090/api/open/file/upload', //
// filePath: tempFiles,
// name: 'file',
// formData: {
// 'authorization': 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VybmFtZVwiOlwiMjA4ODIwMjk1OTA0NDIwNVwiLFwidGVuYW50SWRcIjo2fSIsImV4cCI6MTY3ODUwNDc1NSwiaWF0IjoxNjc4NDE4MzU1fQ.M-8RB724jmHmhMKut-AFW9i79vJVNdBDqoq-EInDses'
// },
// success: (uploadFileRes) => {
// console.log(uploadFileRes.data);
// }
// });
// tempFiles = [{path:'xxx', size:100}]
// app.imageList = oldImageList.concat(tempFiles)
}
});
},
//
onBind(item) {
const app = this
const {
equipment
} = item
app.showQRCodeBind = true
app.equipment = item
// app.bindValue = equipment.equipmentCode
},
//
doBind() {
const app = this
const {
equipment,
bindValue
} = this
bindEquipment({
equipmentCode: bindValue,
orderId: equipment.orderId
}).then(result => {
app.$success(result.message)
app.showQRCodeBind = false
//
app.onRefreshList()
}).catch(err => {
app.$error(err.message)
})
console.log("this.bindValue: ", this.bindValue);
},
//
onExtractQRCode(orderId) {
const app = this
OrderApi.extractQrcode(orderId, {
channel: app.platform
})
.then(result => {
app.qrcodeImage = result.data.qrcode
app.showQRCodePopup = true
})
},
//
onPay(orderId) {
this.$navTo('pages/checkout/cashier/index', {
orderId
})
},
//
handleTargetDetail(orderId) {
this.$navTo('pages/order/detail', {
orderId
})
},
//
handleTargetComment(orderId) {
this.$navTo('pages/order/comment/index', {
orderId
})
},
handleChangeEquipment(orderId, equipmentId) {
const app = this
app.orderId = orderId
app.equipmentId = equipmentId
app.showQRCodeBindChange = true
},
//
doEquipment(canReset = false) {
const app = this
const {
orderId,
bindValue,
equipmentId
} = this
EquipmentApi.changeEquipment({
equipmentCode: bindValue,
orderId,
equipmentId
}).then(result => {
app.$success(result.message)
app.showQRCodeBindChange = false
app.bindValue = ''
//
app.onRefreshList()
}).catch(err => {
app.$error(err.message)
})
console.log("this.bindValue: ", this.bindValue);
}
},
}
</script>
<style lang="scss" scoped>
//
.order-item {
margin: 20rpx auto 20rpx auto;
padding: 30rpx 30rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
background: #fff;
}
//
.item-top {
display: flex;
justify-content: space-between;
font-size: 26rpx;
margin-bottom: 40rpx;
.order-time {
color: #333333;
font-weight: bold;
}
.state-text {
color: $main-bg;
}
}
//
.goods-list {
//
.goods-item {
display: flex;
margin-bottom: 40rpx;
//
.goods-image {
width: 100rpx;
height: 100rpx;
.image {
display: block;
width: 100rpx;
height: 100rpx;
border-radius: 8rpx;
}
}
//
.goods-content {
flex: 1;
padding-left: 16rpx;
padding-top: 16rpx;
.goods-title {
width: 420rpx;
font-size: 26rpx;
max-height: 76rpx;
line-height: 36rpx;
}
.goods-props {
margin-top: 14rpx;
height: 40rpx;
color: #ababab;
font-size: 24rpx;
overflow: hidden;
.goods-props-item {
display: inline-block;
margin-right: 14rpx;
padding: 4rpx 16rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
width: auto;
}
}
}
//
.goods-trade {
padding-top: 16rpx;
width: 200rpx !important;
text-align: right;
color: $uni-text-color-grey;
font-size: 26rpx;
.goods-price {
vertical-align: bottom;
margin-bottom: 16rpx;
.unit {
margin-right: -2rpx;
font-size: 24rpx;
}
}
}
}
}
//
.order-total {
font-size: 26rpx;
vertical-align: bottom;
text-align: right;
height: 50rpx;
padding-top: 20rpx;
margin-bottom: 30rpx;
.unit {
margin-left: 8rpx;
margin-right: -2rpx;
font-size: 26rpx;
}
.money {
font-size: 28rpx;
color: #ff0000;
}
}
//
.order-handle {
.btn-group {
.btn-item {
border-radius: 10rpx;
padding: 8rpx 20rpx;
margin-left: 15rpx;
font-size: 26rpx;
float: right;
color: #383838;
border: 1rpx solid #a8a8a8;
&:last-child {
margin-left: 0;
}
&.active {
color: $main-bg;
border: 1rpx solid $main-bg;
}
}
}
}
// -
.qrcode-popup {
padding: 36rpx 30rpx;
.title {
font-size: 30rpx;
margin-bottom: 26rpx;
font-weight: bold;
text-align: center;
}
.pop-content {
min-height: 260rpx;
padding: 0 10rpx;
.image {
display: block;
width: 510rpx;
height: 510rpx;
}
}
}
// -
.qrcode-bind {
width: 550rpx;
height: 300rpx;
padding: 36rpx 30rpx;
.title {
font-size: 30rpx;
margin-bottom: 26rpx;
font-weight: bold;
text-align: center;
}
.bind-content {
padding: 0 10rpx;
border: 1rpx solid #eee;
font-size: 24rpx;
.image {
display: block;
width: 510rpx;
height: 510rpx;
}
}
.submit {
margin-top: 30rpx;
}
}
.yuqi-text{
color: #ff0000;
}
</style>
+127
View File
@@ -0,0 +1,127 @@
<template>
<view class="container">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption"
@up="upCallback">
<view class="log-list">
<view v-for="(item, index) in list.data" :key="index" class="log-item">
<view class="item-left flex-box">
<view class="rec-status">
<text>{{ item.describe }}</text>
</view>
<view class="rec-time">
<text>{{ item.create_time }}</text>
</view>
</view>
<view class="item-right" :class="[item.value > 0 ? 'col-green' : 'col-6']">
<text>{{ item.value > 0 ? '+' : '' }}{{ item.value }}</text>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import * as LogApi from '@/api/points/log'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
const pageSize = 15
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
//
list: getEmptyPaginateObj(),
//
upOption: {
//
auto: true,
// ; 10
page: { size: pageSize },
// 12
noMoreSize: 12,
//
empty: {
tip: '亲,暂无相关数据'
}
}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
//
app.getLogList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
//
getLogList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
LogApi.list({ page: pageNo })
.then(result => {
//
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
})
}
}
}
</script>
<style lang="scss" scoped>
page,
.container {
background: #fff;
}
.log-list {
padding: 0 30rpx;
}
.log-item {
font-size: 28rpx;
padding: 20rpx 20rpx;
line-height: 1.8;
border-bottom: 1rpx solid rgb(238, 238, 238);
display: flex;
justify-content: center;
align-items: center;
}
.rec-status {
color: #333;
.rec-time {
color: rgb(160, 160, 160);
font-size: 26rpx;
}
}
</style>
+434
View File
@@ -0,0 +1,434 @@
<template>
<view v-if="!isLoading" class="container" :style="appThemeStyle">
<!-- 商品详情 -->
<view class="goods-detail b-f dis-flex flex-dir-row">
<view class="left">
<image class="goods-image" :src="goods.goods_image"></image>
</view>
<view class="right dis-flex flex-box flex-dir-column flex-x-around">
<view class="goods-name">
<text class="twoline-hide">{{ goods.goods_name }}</text>
</view>
<view class="dis-flex col-9 f-24">
<view class="flex-box">
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in goods.goods_props" :key="idx">
<text>{{ props.value.name }}</text>
</view>
</view>
</view>
<text class="t-r">×{{ goods.total_num }}</text>
</view>
</view>
</view>
<!-- 服务类型 -->
<view class="row-service b-f m-top20">
<view class="row-title">服务类型</view>
<view class="service-switch dis-flex">
<view class="switch-item" v-for="(item, index) in RefundTypeEnum.data" :key="index" :class="{ active: formData.type == item.value }"
@click="onSwitchService(item.value)">{{ item.name }}</view>
</view>
</view>
<!-- 申请原因 -->
<view class="row-textarea b-f m-top20">
<view class="row-title">申请原因</view>
<view class="content">
<textarea class="textarea" v-model="formData.content" maxlength="2000" placeholder="请详细填写申请原因,注意保持商品的完好,建议您先与卖家沟通"
placeholderStyle="color:#ccc"></textarea>
</view>
</view>
<!-- 退款金额 -->
<view v-if="formData.type == RefundTypeEnum.RETURN.value" class="row-money b-f m-top20 dis-flex">
<view class="row-title">退款金额</view>
<view class="money col-m">{{ goods.total_pay_price }}</view>
</view>
<!-- 上传凭证 -->
<view class="row-voucher b-f m-top20">
<view class="row-title">上传凭证 (最多6张)</view>
<view class="image-list">
<!-- 图片列表 -->
<view class="image-preview" v-for="(image, imageIndex) in imageList" :key="imageIndex">
<text class="image-delete iconfont icon-shanchu" @click="deleteImage(imageIndex)"></text>
<image class="image" mode="aspectFill" :src="image.path"></image>
</view>
<!-- 上传图片 -->
<view v-if="imageList.length < maxImageLength" class="image-picker" @click="chooseImage()">
<text class="choose-icon iconfont icon-camera"></text>
<text class="choose-text">上传图片</text>
</view>
</view>
</view>
<!-- 底部操作按钮 -->
<view class="footer-fixed">
<view class="btn-wrapper">
<view class="btn-item btn-item-main" :class="{ disabled }" @click="handleSubmit()">确认提交</view>
</view>
</view>
</view>
</template>
<script>
import { RefundTypeEnum } from '@/common/enum/order/refund'
import * as UploadApi from '@/api/upload'
import * as RefundApi from '@/api/refund'
const maxImageLength = 6
export default {
data() {
return {
//
RefundTypeEnum,
//
isLoading: true,
// id
orderGoodsId: null,
//
goods: {},
//
formData: {
// ID
images: [],
//
type: 10,
//
content: ''
},
//
imageList: [],
//
maxImageLength,
//
disabled: false
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({ orderGoodsId }) {
this.orderGoodsId = orderGoodsId
//
this.getGoodsDetail()
},
methods: {
//
getGoodsDetail() {
const app = this
app.isLoading = true
RefundApi.goods(app.orderGoodsId)
.then(result => {
app.goods = result.data.goods
app.isLoading = false
})
},
//
onSwitchService(value) {
this.formData.type = value
},
//
chooseImage() {
const app = this
const oldImageList = app.imageList
//
uni.chooseImage({
count: maxImageLength - oldImageList.length,
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success({ tempFiles }) {
// tempFiles = [{path:'xxx', size:100}]
app.imageList = oldImageList.concat(tempFiles)
}
});
},
//
deleteImage(imageIndex) {
this.imageList.splice(imageIndex, 1)
},
//
handleSubmit() {
const app = this
const { imageList } = app
//
if (app.disabled === true) return false
//
if (!app.formData.content.trim().length) {
app.$toast('请填写申请原因')
return false
}
//
app.disabled = true
//
if (imageList.length > 0) {
app.uploadFile()
.then(() => app.onSubmit())
.catch(err => {
app.disabled = false
if (err.statusCode !== 0) {
app.$toast(err.errMsg)
}
console.log('err', err)
})
} else {
app.onSubmit()
}
},
//
onSubmit() {
const app = this
RefundApi.apply(app.orderGoodsId, app.formData)
.then(result => {
app.$toast(result.message)
setTimeout(() => {
app.disabled = false
uni.navigateBack()
}, 1500)
})
.catch(err => app.disabled = false)
},
//
uploadFile() {
const app = this
const { imageList } = app
//
return new Promise((resolve, reject) => {
if (imageList.length > 0) {
UploadApi.image(imageList)
.then(fileIds => {
app.formData.images = fileIds
resolve(fileIds)
})
.catch(reject)
} else {
resolve()
}
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
// ios线
padding-bottom: calc(constant(safe-area-inset-bottom) + 140rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
}
.row-title {
color: #888;
margin-bottom: 20rpx;
}
//
.goods-detail {
padding: 24rpx 20rpx;
.left {
.goods-image {
display: block;
width: 150rpx;
height: 150rpx;
}
}
.right {
padding-left: 20rpx;
}
.goods-props {
margin-top: 14rpx;
height: 40rpx;
color: #ababab;
font-size: 24rpx;
overflow: hidden;
.goods-props-item {
display: inline-block;
margin-right: 14rpx;
padding: 4rpx 16rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
width: auto;
}
}
}
/* 服务类型 */
.row-service {
padding: 24rpx 20rpx;
}
.service-switch {
.switch-item {
padding: 6rpx 30rpx;
margin-right: 25rpx;
border-radius: 10rpx;
border: 1px solid rgb(177, 177, 177);
color: #888888;
&.active {
color: $main-bg;
border: 1px solid $main-bg;
}
}
}
/* 申请原因 */
.row-textarea {
padding: 24rpx 20rpx;
.textarea {
width: 100%;
height: 220rpx;
padding: 12rpx;
border: 1rpx solid #e8e8e8;
border-radius: 5rpx;
box-sizing: border-box;
font-size: 26rpx;
}
}
/* 退款金额 */
.row-money {
padding: 24rpx 20rpx;
.row-title {
margin-bottom: 0;
margin-right: 30rpx;
}
}
//
.row-voucher {
padding: 24rpx 20rpx;
.image-list {
padding: 0 20rpx;
margin-top: 20rpx;
margin-bottom: -20rpx;
&:after {
clear: both;
content: " ";
display: table;
}
.image {
display: block;
width: 100%;
height: 100%;
}
.image-picker,
.image-preview {
width: 184rpx;
height: 184rpx;
margin-right: 30rpx;
margin-bottom: 30rpx;
float: left;
&:nth-child(3n+0) {
margin-right: 0;
}
}
.image-picker {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1rpx dashed #ccc;
color: #ccc;
.choose-icon {
font-size: 48rpx;
margin-bottom: 6rpx;
}
.choose-text {
font-size: 24rpx;
}
}
.image-preview {
position: relative;
.image-delete {
position: absolute;
top: -15rpx;
right: -15rpx;
height: 42rpx;
width: 42rpx;
background: rgba(0, 0, 0, 0.64);
border-radius: 50%;
color: #fff;
font-weight: bolder;
font-size: 22rpx;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
}
}
}
//
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
// ios线
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.btn-wrapper {
height: 140rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 80rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
//
&.disabled {
opacity: 0.6;
}
}
}
</style>
+481
View File
@@ -0,0 +1,481 @@
<template>
<view v-if="!isLoading" class="container p-bottom" :style="appThemeStyle">
<!-- 顶部状态栏 -->
<view class="detail-header dis-flex flex-y-center">
<view class="header-backdrop">
<image class="image" src="/static/order/refund-bg.png"></image>
</view>
<view class="header-state">
<text class="f-32 col-f">{{ detail.state_text }}</text>
</view>
</view>
<!-- 商品详情 -->
<view class="detail-goods b-f m-top20 dis-flex flex-dir-row" @click="onGoodsDetail(detail.orderGoods.goods_id)">
<view class="left">
<image class="goods-image" :src="detail.orderGoods.goods_image"></image>
</view>
<view class="right dis-flex flex-box flex-dir-column flex-x-around">
<view class="goods-name">
<text class="twoline-hide">{{ detail.orderGoods.goods_name }}</text>
</view>
<view class="dis-flex col-9 f-24">
<view class="flex-box">
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in detail.orderGoods.goods_props" :key="idx">
<text>{{ props.value.name }}</text>
</view>
</view>
</view>
<text class="t-r">×{{ detail.orderGoods.total_num }}</text>
</view>
</view>
</view>
<!-- 商品金额 -->
<view class="detail-order b-f row-block">
<view class="item dis-flex flex-x-end flex-y-center">
<text class="">商品金额</text>
<text class="col-m">{{ detail.orderGoods.total_pay_price }}</text>
</view>
</view>
<!-- 已退款金额 -->
<view v-if="detail.status == RefundStatusEnum.COMPLETED.value && detail.type == 10"
class="detail-order b-f row-block dis-flex flex-x-end flex-y-center">
<text class="">已退款金额</text>
<text class="col-m">{{ detail.refund_money }}</text>
</view>
<!-- 售后信息 -->
<view v-if="detail.status == RefundStatusEnum.REJECTED.value" class="detail-refund b-f m-top20">
<view class="detail-refund__row dis-flex">
<view class="text">
<text>售后类型</text>
</view>
<view class="flex-box">
<text>{{ RefundTypeEnum[detail.type].name }}</text>
</view>
</view>
<view class="detail-refund__row dis-flex">
<view class="text">
<text>申请原因</text>
</view>
<view class="flex-box">
<text>{{ detail.apply_desc }}</text>
</view>
</view>
<view v-if="detail.images.length > 0" class="detail-refund__row dis-flex">
<view class="text">
<text>申请凭证</text>
</view>
<view class="image-list flex-box">
<view class="image-preview" v-for="(item, index) in detail.images" :key="index">
<image class="image" mode="aspectFill" :src="item.image_url" @click="handlePreviewImages(index)"></image>
</view>
</view>
</view>
</view>
<!-- 售后信息 -->
<view v-if="detail.status.value == 10" class="detail-refund b-f m-top20">
<view class="detail-refund__row dis-flex">
<view class="text">
<text class="col-m">拒绝原因</text>
</view>
<view class="flex-box">
<text>{{ detail.refuse_desc }}</text>
</view>
</view>
</view>
<!-- 退货物流信息 -->
<view v-if="detail.audit_status == AuditStatusEnum.REVIEWED.value && detail.is_user_send" class="detail-address b-f m-top20">
<view class="detail-address__row address-title">
<text class="col-m">退货物流信息</text>
</view>
<view class="detail-address__row address-details">
<view class="address-details__row">
<text>物流公司{{ detail.express.express_name }}</text>
</view>
<view class="address-details__row">
<text>物流单号{{ detail.express_no }}</text>
</view>
<!-- <view class="address-details__row">
<text>发货状态{{ detail.is_user_send ? '已发货' : '未发货' }}</text>
</view> -->
<view class="address-details__row">
<text>发货时间{{ detail.send_time }}</text>
</view>
</view>
</view>
<!-- 商家收货地址 -->
<view v-if="detail.audit_status == AuditStatusEnum.REVIEWED.value" class="detail-address b-f m-top20">
<view class="detail-address__row address-title">
<text class="col-m">商家退货地址</text>
</view>
<view class="detail-address__row address-details">
<view class="address-details__row">
<text>收货人{{ detail.address.name }}</text>
</view>
<view class="address-details__row">
<text>联系电话{{ detail.address.phone }}</text>
</view>
<view class="address-details__row dis-flex">
<view class="text">
<text>详细地址</text>
</view>
<view class="address flex-box">
<text class="region" v-for="(region, idx) in detail.address.region" :key="idx">{{ region }}</text>
<text class="detail">{{ detail.address.detail }}</text>
</view>
</view>
</view>
<view class="detail-address__row address-tips">
<view class="f-26 col-9">
<text>· 未与卖家协商一致情况下请勿寄到付或平邮</text>
</view>
<view class="f-26 col-9">
<text>· 请填写真实有效物流信息</text>
</view>
</view>
</view>
<!-- 填写物流信息 -->
<form v-if="detail.type == RefundTypeEnum.RETURN.value && detail.audit_status == AuditStatusEnum.REVIEWED.value && !detail.is_user_send"
@submit="onSubmit()">
<view class="detail-express b-f m-top20">
<view class="form-group dis-flex flex-y-center">
<view class="field">物流公司</view>
<view class="flex-box">
<picker mode="selector" :range="expressList" range-key="express_name" :value="expressIndex" @change="onChangeExpress">
<text v-if="expressIndex > -1">{{ expressList[expressIndex].express_name }}</text>
<text v-else class="col-80">请选择物流公司</text>
</picker>
</view>
</view>
<view class="form-group dis-flex flex-y-center">
<view class="field">物流单号</view>
<view class="flex-box">
<input class="input" v-model="formData.expressNo" placeholder="请填写物流单号"></input>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="footer">
<view class="btn-wrapper">
<button class="btn-item btn-item-main btn-normal" :class="{ disabled }" formType="submit">确认发货</button>
</view>
</view>
</form>
</view>
</template>
<script>
import { AuditStatusEnum, RefundStatusEnum, RefundTypeEnum } from '@/common/enum/order/refund'
import * as RefundApi from '@/api/refund'
import * as ExpressApi from '@/api/express'
export default {
data() {
return {
//
AuditStatusEnum,
RefundStatusEnum,
RefundTypeEnum,
//
isLoading: true,
// ID
orderRefundId: null,
//
detail: {},
//
expressList: [],
//
formData: {
// ID
expressId: null,
//
expressNo: ''
},
//
expressIndex: -1,
//
disabled: false
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({ orderRefundId }) {
// ID
this.orderRefundId = orderRefundId
//
this.getPageData()
},
methods: {
//
getPageData() {
const app = this
app.isLoading = true
Promise.all([app.getRefundDetail(), app.getExpressList()])
.then(result => {
app.isLoading = false
})
},
//
getRefundDetail() {
const app = this
return new Promise((resolve, reject) => {
RefundApi.detail(app.orderRefundId)
.then(result => {
app.detail = result.data.detail
resolve()
})
.catch(reject)
})
},
//
getExpressList() {
const app = this
return new Promise((resolve, reject) => {
ExpressApi.list()
.then(result => {
app.expressList = result.data.list
resolve()
})
.catch(reject)
})
},
//
onGoodsDetail(goodsId) {
this.$navTo('pages/goods/detail', { goodsId })
},
//
handlePreviewImages(index) {
const { detail: { images } } = this
const imageUrls = images.map(item => item.image_url)
uni.previewImage({
current: imageUrls[index],
urls: imageUrls
})
},
//
onChangeExpress(e) {
const expressIndex = e.detail.value
const { expressList } = this
this.expressIndex = expressIndex
this.formData.expressId = expressList[expressIndex].express_id
},
//
onSubmit() {
const app = this
//
if (app.disabled === true) return false
//
app.disabled = true
//
RefundApi.delivery(app.orderRefundId, app.formData)
.then(result => {
app.$toast(result.message)
setTimeout(() => {
app.disabled = false
uni.navigateBack()
}, 1500)
})
.catch(err => app.disabled = false)
}
}
}
</script>
<style lang="scss" scoped>
//
.detail-header {
position: relative;
width: 100%;
height: 140rpx;
.header-backdrop {
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 0;
.image {
display: block;
width: 100%;
height: 140rpx;
}
}
}
.header-state {
z-index: 1;
padding: 0 50rpx;
}
/* 商品详情 */
.detail-goods {
padding: 24rpx 20rpx;
.left {
.goods-image {
display: block;
width: 150rpx;
height: 150rpx;
}
}
.right {
padding-left: 20rpx;
}
.goods-props {
margin-top: 14rpx;
height: 40rpx;
color: #ababab;
font-size: 24rpx;
overflow: hidden;
.goods-props-item {
display: inline-block;
margin-right: 14rpx;
padding: 4rpx 16rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
width: auto;
}
}
}
.detail-order {
padding: 15rpx 20rpx;
font-size: 26rpx;
.item {
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
}
}
/* 售后详情 */
.detail-refund {
padding: 15rpx 20rpx;
}
.detail-refund__row {
margin: 20rpx 0;
}
/* 申请凭证 */
.image-list {
margin-bottom: -15rpx;
.image-preview {
margin: 0 15rpx 15rpx 0;
float: left;
.image {
display: block;
width: 180rpx;
height: 180rpx;
}
&:nth-child(3n+0) {
margin-right: 0;
}
}
}
/* 商家收货地址 */
.detail-address {
padding: 20rpx 34rpx;
}
.address-details {
padding: 8rpx 0;
border-bottom: 1px solid #eee;
.address-details__row {
margin: 14rpx 0;
}
}
.address-tips {
margin-top: 16rpx;
line-height: 46rpx;
}
.detail-address__row {
// margin: 18rpx 0;
}
/* 填写物流信息 */
.detail-express {
padding: 10rpx 30rpx;
}
.form-group {
height: 60rpx;
margin: 14rpx 0;
.input {
height: 100%;
font-size: 28rpx;
}
}
/* 底部操作栏 */
.footer {
margin-top: 60rpx;
.btn-wrapper {
height: 100%;
display: flex;
align-items: center;
padding: 0 20rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 80rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
//
&.disabled {
opacity: 0.6;
}
}
}
</style>
+263
View File
@@ -0,0 +1,263 @@
<template>
<view class="container" :style="appThemeStyle">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ native: true }" @down="downCallback" :up="upOption"
@up="upCallback">
<!-- tab栏 -->
<u-tabs :list="tabs" :is-scroll="false" :current="curTab" :active-color="appTheme.mainBg" :duration="0.2" @change="onChangeTab" />
<!-- 退款/售后单 -->
<view class="widget-list">
<view class="widget-detail" v-for="(item, index) in list.data" :key="index">
<view class="row-block dis-flex flex-y-center">
<view class="flex-box">{{ item.create_time }}</view>
<view class="flex-box t-r">
<text class="col-m">{{ item.state_text }}</text>
</view>
</view>
<view class="detail-goods row-block dis-flex" @click.stop="handleTargetDetail(item.order_refund_id)">
<view class="goods-image">
<image class="image" :src="item.orderGoods.goods_image" mode="aspectFit"></image>
</view>
<view class="goods-right flex-box">
<view class="goods-name">
<text class="twoline-hide">{{ item.orderGoods.goods_name }}</text>
</view>
<view class="goods-props clearfix">
<view class="goods-props-item" v-for="(props, idx) in item.orderGoods.goods_props" :key="idx">
<text>{{ props.value.name }}</text>
</view>
</view>
<view class="goods-num t-r">
<text class="f-26 col-8">×{{ item.orderGoods.total_num }}</text>
</view>
</view>
</view>
<view class="detail-order row-block">
<view class="item dis-flex flex-x-end flex-y-center">
<text class="">付款金额</text>
<text class="col-m">{{ item.orderGoods.total_pay_price }}</text>
</view>
</view>
<view class="detail-operate row-block dis-flex flex-x-end flex-y-center">
<view class="detail-btn btn-detail" @click.stop="handleTargetDetail(item.order_refund_id)">查看详情</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import * as RefundApi from '@/api/refund'
//
const pageSize = 15
// tab
const tabs = [{
name: '全部',
value: -1
}, {
name: '待处理',
value: 0
}]
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
//
list: getEmptyPaginateObj(),
// tabs
tabs,
//
curTab: 0,
//
upOption: {
//
auto: true,
// ; 10
page: { size: pageSize },
// 2
noMoreSize: 2,
//
empty: {
tip: '亲,暂无售后单记录'
}
},
// onShow
canReset: false,
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
this.canReset && this.onRefreshList()
this.canReset = true
},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
//
app.getRefundList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// 退/
getRefundList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
RefundApi.list({ state: app.getTabValue(), page: pageNo }, { load: false })
.then(result => {
//
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
})
},
//
onChangeTab(index) {
const app = this
//
app.curTab = index
//
app.onRefreshList()
},
//
onRefreshList() {
this.list = getEmptyPaginateObj()
setTimeout(() => {
this.mescroll.resetUpScroll()
}, 120)
},
//
getTabValue() {
return this.tabs[this.curTab].value
},
//
handleTargetDetail(orderRefundId) {
this.$navTo('pages/refund/detail', { orderRefundId })
},
}
}
</script>
<style lang="scss" scoped>
.widget-detail {
box-sizing: border-box;
background: #fff;
margin-bottom: 20rpx;
.row-block {
padding: 0 20rpx;
min-height: 70rpx;
}
.detail-goods {
padding: 20rpx;
background: #f9f9f9;
.goods-image {
margin-right: 20rpx;
.image {
display: block;
width: 200rpx;
height: 200rpx;
}
}
.goods-right {
padding: 15rpx 0;
}
.goods-name {
margin-bottom: 10rpx;
}
.goods-props {
margin-top: 14rpx;
height: 40rpx;
color: #ababab;
font-size: 24rpx;
overflow: hidden;
.goods-props-item {
display: inline-block;
margin-right: 14rpx;
padding: 4rpx 16rpx;
border-radius: 12rpx;
background-color: #F5F5F5;
width: auto;
}
}
}
.detail-operate {
padding-bottom: 20rpx;
.detail-btn {
border-radius: 4px;
border: 1rpx solid #ccc;
padding: 8rpx 20rpx;
font-size: 28rpx;
color: #555;
margin-left: 10rpx;
}
}
.detail-order {
padding: 10rpx 20rpx;
font-size: 28rpx;
height: 50rpx;
display: flex;
align-items: center;
.item {
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
}
}
}
</style>
+227
View File
@@ -0,0 +1,227 @@
<template>
<view class="container" :style="appThemeStyle">
<view class="search-wrapper">
<view class="search-input">
<view class="search-input-wrapper">
<view class="left">
<text class="search-icon iconfont icon-search"></text>
</view>
<view class="right">
<input v-model="searchValue" class="input" focus="true" placeholder="请输入您搜索的商品" type="text"></input>
</view>
</view>
</view>
<view class="search-button">
<view class="button" @click="onSearch">搜索</view>
</view>
</view>
<view class="history" v-if="historySearch.length">
<view class="his-head">
<text class="title">最近搜索</text>
<text class="icon iconfont icon-delete" @click="clearSearch"></text>
</view>
<view class="his-list">
<view class="his-item" v-for="(val, index) in historySearch" :key="index">
<view class="history-button" @click="handleQuick(val)">{{ val }}</view>
</view>
</view>
</view>
<!-- </view> -->
</view>
</template>
<script>
const HISTORY_SEARCH = 'historySearch'
export default {
data() {
return {
historySearch: [],
searchValue: ''
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
//
this.historySearch = this.getHistorySearch()
},
methods: {
/**
* 获取历史搜索
*/
getHistorySearch() {
return uni.getStorageSync(HISTORY_SEARCH) || []
},
/**
* 搜索提交
*/
onSearch() {
const { searchValue } = this
if (searchValue) {
//
this.setHistory(searchValue)
//
this.$navTo('pages/goods/list', { search: searchValue })
}
},
/**
* 记录历史搜索
*/
setHistory(searchValue) {
const data = this.getHistorySearch()
const index = data.indexOf(searchValue)
index > -1 && data.splice(index, 1)
data.unshift(searchValue)
this.historySearch = data
this.onUpdateStorage()
},
/**
* 清空最近搜索记录
*/
clearSearch() {
this.historySearch = []
this.onUpdateStorage()
},
/**
* 更新历史搜索缓存
* @param {Object} data
*/
onUpdateStorage(data) {
uni.setStorageSync(HISTORY_SEARCH, this.historySearch)
},
/**
* 跳转到最近搜索
*/
handleQuick(search) {
this.$navTo('pages/goods/list', { search })
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 20rpx;
min-height: 100vh;
background: #f7f7f7;
}
.search-wrapper {
display: flex;
height: 64rpx;
}
//
.search-input {
width: 80%;
background: #fff;
border-radius: 10rpx 0 0 10rpx;
box-sizing: border-box;
overflow: hidden;
.search-input-wrapper {
display: flex;
.left {
display: flex;
width: 60rpx;
justify-content: center;
align-items: center;
.search-icon {
display: block;
color: #b4b4b4;
font-size: 28rpx;
}
}
.right {
flex: 1;
input {
font-size: 28rpx;
height: 64rpx;
display: flex;
align-items: center;
.input-placeholder {
color: #aba9a9;
}
}
}
}
}
//
.search-button {
width: 20%;
box-sizing: border-box;
.button {
height: 64rpx;
font-size: 28rpx;
border-radius: 0 10rpx 10rpx 0;
background: $main-bg;
color: $main-text;
display: flex;
justify-content: center;
align-items: center;
}
}
//
.history {
.his-head {
font-size: 28rpx;
padding: 50rpx 0 0 0;
color: #777;
.icon {
float: right;
}
}
.his-list {
padding: 20rpx 0;
overflow: hidden;
.his-item {
width: 33.3%;
float: left;
padding: 10rpx;
box-sizing: border-box;
.history-button {
text-align: center;
padding: 14rpx;
line-height: 30rpx;
border-radius: 100rpx;
background: #fff;
font-size: 26rpx;
border: 1rpx solid #efefef;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
}
</style>
+181
View File
@@ -0,0 +1,181 @@
<template>
<goods-sku-popup :value="value" @input="onChangeValue" border-radius="20" :localdata="goodsInfo" :mode="skuMode" :maskCloseAble="true"
:priceColor="appTheme.mainBg" :buyNowBackgroundColor="appTheme.mainBg" :addCartColor="appTheme.viceText" :addCartBackgroundColor="appTheme.viceBg"
:activedStyle="{ color: appTheme.mainBg, borderColor: appTheme.mainBg, backgroundColor: activedBtnBackgroundColor }" @open="openSkuPopup"
@close="closeSkuPopup" @buy-now="buyNow" buyNowText="立即购买" :maxBuyNum="goods.limit_num" :noStockText="noStockText" />
</template>
<script>
import { hex2rgba } from '@/utils/color'
import * as TaskApi from '@/api/bargain/task'
import GoodsSkuPopup from '@/components/goods-sku-popup'
export default {
components: {
GoodsSkuPopup
},
model: {
prop: 'value',
event: 'input'
},
props: {
// true false
value: {
Type: Boolean,
default: false
},
// 1: 2: 3:
skuMode: {
type: Number,
default: 1
},
//
active: {
type: Object,
default: {}
},
//
noStockText: {
Type: String,
default: "该商品已抢完"
},
//
goods: {
type: Object,
default: {}
}
},
data() {
return {
goodsInfo: {}
}
},
computed: {
//
activedBtnBackgroundColor() {
return hex2rgba(this.appTheme.mainBg, 0.1)
}
},
created() {
const app = this
const { goods } = app
app.goodsInfo = {
_id: goods.goods_id,
name: goods.goods_name,
goods_thumb: goods.goods_image,
sku_list: app.getSkuList(),
spec_list: app.getSpecList()
}
},
methods: {
//
onChangeValue(val) {
this.$emit('input', val)
},
/**
* 获取商品信息
* 这里可以看到每次打开SKU都会去重新请求商品信息,为的是每次打开SKU组件可以实时看到剩余库存
*/
findGoodsInfo() {
return new Promise((resolve, reject) => {
resolve(goodsInfo)
})
},
// SKU
getSkuList() {
const app = this
const { goods: { goods_name, goods_image, skuList } } = app
const skuData = []
skuList.forEach(item => {
skuData.push({
_id: item.id,
goods_sku_id: item.goods_sku_id,
goods_id: item.goods_id,
goods_name: goods_name,
image: item.image_url ? item.image_url : goods_image,
price: item.seckill_price * 100,
stock: item.seckill_stock,
spec_value_ids: item.spec_value_ids,
sku_name_arr: app.getSkuNameArr(item.spec_value_ids)
})
})
return skuData
},
// sku
getSkuNameArr(specValueIds) {
const app = this
const defaultData = ['默认']
const skuNameArr = []
if (specValueIds) {
specValueIds.forEach((valueId, groupIndex) => {
const specValueName = app.getSpecValueName(valueId, groupIndex)
skuNameArr.push(specValueName)
})
}
return skuNameArr.length ? skuNameArr : defaultData
},
//
getSpecValueName(valueId, groupIndex) {
const app = this
const { goods: { specList } } = app
const res = specList[groupIndex].valueList.find(specValue => {
return specValue.spec_value_id == valueId
})
return res.spec_value
},
//
getSpecList() {
const { goods: { specList } } = this
const defaultData = [{ name: '默认', list: [{ name: '默认' }] }]
const specData = []
specList.forEach(group => {
const children = []
group.valueList.forEach(specValue => {
children.push({ name: specValue.spec_value })
})
specData.push({
name: group.spec_name,
list: children
})
})
return specData.length ? specData : defaultData
},
// sku -----------------------------------------------------------
openSkuPopup() {
// console.log(" - sku")
},
closeSkuPopup() {
// console.log(" - sku")
},
//
buyNow(selectShop) {
const app = this
//
app.$navTo('pages/checkout/index', {
mode: 'sharp',
activeTimeId: app.active.active_time_id,
sharpGoodsId: app.goods.sharp_goods_id,
goodsSkuId: selectShop.goods_sku_id,
goodsNum: selectShop.buy_num
})
//
app.onChangeValue(false)
}
}
}
</script>
<style lang="scss" scoped>
</style>
+339
View File
@@ -0,0 +1,339 @@
<template>
<view v-show="!isLoading" class="container" :style="appThemeStyle">
<!-- 商品图片轮播 -->
<SlideImage v-if="!isLoading" :video="goods.video" :videoCover="goods.videoCover" :images="goods.goods_images" />
<!-- 商品信息 -->
<view v-if="!isLoading" class="goods-info m-top20">
<!-- 价格销量 -->
<view class="info-item info-item__top dis-flex flex-x-between flex-y-end">
<view class="block-left dis-flex flex-y-center">
<view class="active-tag">
<text>限时秒杀</text>
</view>
<!-- 秒杀价 -->
<text class="floor-price__samll"></text>
<text class="floor-price">{{ goods.seckill_price }}</text>
<!-- 商品原价 -->
<text class="original-price">{{ goods.original_price }}</text>
</view>
<view class="block-right dis-flex">
<!-- 销量 -->
<view class="goods-sales">
<text>已抢{{ active.sales_actual }}</text>
</view>
</view>
</view>
<!-- 标题分享 -->
<view class="info-item info-item__name dis-flex flex-y-center">
<view class="goods-name flex-box">
<text class="twoline-hide">{{ goods.goods_name }}</text>
</view>
<view class="goods-share__line"></view>
<view class="goods-share">
<button class="share-btn dis-flex flex-dir-column" @click="onShowShareSheet()">
<text class="share__icon iconfont icon-fenxiang"></text>
<text class="f-24">分享</text>
</button>
</view>
</view>
<!-- 商品卖点 -->
<view v-if="goods.selling_point" class="info-item info-item_selling-point">
<text>{{ goods.selling_point }}</text>
</view>
<!-- 活动倒计时 -->
<view v-if="active.active_status != GoodsStatusEnum.STATE_END.value" class="info-item info-item_status info-item_countdown dis-flex flex-y-center">
<text class="countdown-icon iconfont icon-naozhong"></text>
<text>距离秒杀{{ active.active_status == GoodsStatusEnum.STATE_SOON.value ? '开始' : '结束' }}</text>
<text class="m-r-10">还剩</text>
<count-down :date="active.count_down_time" separator="zh" theme="text" />
</view>
<!-- 活动已结束 -->
<view v-else class="info-item info-item_status info-item_end">
<text class="countdown-icon iconfont icon-naozhong"></text>
<text>秒杀活动已结束下次记得早点来哦~</text>
</view>
</view>
<!-- 选择商品规格 -->
<view v-if="goods.spec_type == 20" class="goods-choice m-top20 b-f" @click="onShowSkuPopup()">
<view class="spec-list">
<view class="flex-box">
<text class="col-8">选择</text>
<text class="spec-name" v-for="(item, index) in goods.specList" :key="index">{{ item.spec_name }}</text>
</view>
<view class="f-26 col-9 t-r">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
</view>
<!-- 商品服务 -->
<!-- <Service v-if="!isLoading" :goods-id="goods.goods_id" /> -->
<!-- 商品SKU弹窗 -->
<SkuPopup v-if="!isLoading" v-model="showSkuPopup" :skuMode="skuMode" :active="active" :goods="goods" :noStockText="noStockText" />
<!-- 商品评价 -->
<Comment v-if="!isLoading" :goods-id="goods.goods_id" :limit="2" />
<!-- 商品描述 -->
<view v-if="!isLoading" class="goods-content m-top20">
<view class="item-title b-f">
<text>商品描述</text>
</view>
<block v-if="goods.content != ''">
<view class="goods-content__detail b-f">
<mp-html :content="goods.content" />
</view>
</block>
<empty v-else tips="亲,暂无商品描述" />
</view>
<!-- 底部选项卡 -->
<view class="footer-fixed">
<view class="footer-container">
<!-- 导航图标 -->
<view class="foo-item-fast">
<!-- 首页 -->
<view class="fast-item fast-item--home" @click="onTargetHome">
<view class="fast-icon">
<text class="iconfont icon-shouye"></text>
</view>
<view class="fast-text">
<text>首页</text>
</view>
</view>
<!-- 客服 -->
<customer-btn v-if="isShowCustomerBtn">
<view class="fast-item">
<view class="fast-icon">
<text class="iconfont icon-kefu1"></text>
</view>
<view class="fast-text">
<text>客服</text>
</view>
</view>
</customer-btn>
<!-- 购物车 (客服按钮不显示时) -->
<view v-if="!isShowCustomerBtn" class="fast-item fast-item--cart" @click="onTargetCart">
<view v-if="cartTotal > 0" class="fast-badge fast-badge--fixed">{{ cartTotal > 99 ? '99+' : cartTotal }}
</view>
<view class="fast-icon">
<text class="iconfont icon-gouwuche"></text>
</view>
<view class="fast-text">
<text>购物车</text>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="foo-item-btn">
<view class="btn-wrapper">
<view v-if="active.active_status == GoodsStatusEnum.STATE_BEGIN.value" class="btn-item btn--main" @click="onShowSkuPopup()">
<text>立即购买</text>
</view>
<button v-else class="btn-item btn--gray">
<text>{{ active.active_status == GoodsStatusEnum.STATE_SOON.value ? '活动未开始' : '活动已结束' }}</text>
</button>
</view>
</view>
</view>
</view>
<!-- 分享菜单 -->
<share-sheet v-model="showShareSheet" :shareTitle="goods.goods_name" :shareImageUrl="goods.goods_image" :posterApiCall="posterApiCall" :posterApiParam="{ activeTimeId, sharpGoodsId }" />
</view>
</template>
<script>
import { getSceneData } from '@/core/app'
import ShareSheet from '@/components/share-sheet'
import CustomerBtn from '@/components/customer-btn'
import SkuPopup from './components/SkuPopup'
import SlideImage from '../../goods/components/SlideImage'
import Comment from '../../goods/components/Comment'
// import Service from '../../goods/components/Service'
import CountDown from '@/components/countdown'
import * as SharpGoodsApi from '@/api/sharp/goods'
import * as CartApi from '@/api/cart'
import SettingModel from '@/common/model/Setting'
import { ActiveStatusEnum, GoodsStatusEnum } from '@/common/enum/sharp'
export default {
components: {
ShareSheet,
CustomerBtn,
// Shortcut,
SlideImage,
SkuPopup,
Comment,
// Service,
CountDown
},
data() {
return {
//
isLoading: true,
//
ActiveStatusEnum,
GoodsStatusEnum,
// /SKU
showSkuPopup: false,
// 1: 2: 3:
skuMode: 3,
//
noStockText: '该商品已抢完',
// /
showShareSheet: false,
// api
posterApiCall: SharpGoodsApi.poster,
// ID
activeTimeId: null,
// ID
sharpGoodsId: null,
//
active: {},
//
goods: {},
//
cartTotal: 0,
// 线
isShowCustomerBtn: false
}
},
/**
* 生命周期函数--监听页面加载
*/
async onLoad(options) {
// query
this.onRecordQuery(options)
//
this.onRefreshPage()
// 线
this.isShowCustomerBtn = await SettingModel.isShowCustomerBtn()
},
methods: {
// query
onRecordQuery(query) {
const scene = getSceneData(query)
this.activeTimeId = query.activeTimeId ? parseInt(query.activeTimeId) : parseInt(scene.aid)
this.sharpGoodsId = query.sharpGoodsId ? parseInt(query.sharpGoodsId) : parseInt(scene.gid)
},
//
onRefreshPage() {
const app = this
app.isLoading = true
Promise.all([app.getActiveDetail(), app.getCartTotal()])
.then(() => {
const activeStatus = app.active.active_status
if (activeStatus != GoodsStatusEnum.STATE_BEGIN.value) {
app.skuMode = 4
app.noStockText = activeStatus == GoodsStatusEnum.STATE_SOON.value ? '活动未开始' : '活动已结束'
}
})
.finally(() => app.isLoading = false)
},
//
getActiveDetail() {
const app = this
return new Promise((resolve, reject) => {
SharpGoodsApi.detail(app.activeTimeId, app.sharpGoodsId)
.then(result => {
app.active = result.data.active
app.goods = result.data.goods
resolve(result)
})
.catch(reject)
})
},
//
getCartTotal() {
const app = this
return new Promise((resolve, reject) => {
CartApi.total()
.then(result => {
app.cartTotal = result.data.cartTotal
resolve(result)
})
.catch(reject)
})
},
/**
* 显示/隐藏SKU弹窗
*/
onShowSkuPopup() {
this.showSkuPopup = !this.showSkuPopup
},
//
onShowShareSheet() {
this.showShareSheet = !this.showShareSheet
},
//
onTargetHome(e) {
this.$navTo('pages/index/index')
},
//
onTargetCart() {
this.$navTo('pages/cart/index')
},
},
/**
* 分享当前页面
*/
onShareAppMessage() {
//
const app = this
const params = app.$getShareUrlParams({
activeTimeId: app.activeTimeId,
sharpGoodsId: app.sharpGoodsId
})
return {
title: app.goods.goods_name,
path: `/pages/sharp/goods/index?${params}`
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本暂只在 Android 平台支持详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
//
const app = this
const params = app.$getShareUrlParams({
activeTimeId: app.activeTimeId,
sharpGoodsId: app.sharpGoodsId
})
return {
title: app.goods.goods_name,
path: `/pages/sharp/goods/index?${params}`
}
},
}
</script>
<style>
page {
background: #fafafa;
}
</style>
<style lang="scss" scoped>
@import "./style.scss";
</style>
+306
View File
@@ -0,0 +1,306 @@
.container {
// 设置ios刘海屏底部横线安全区域
// 110 - 18 + 4
padding-bottom: calc(constant(safe-area-inset-bottom) + 106rpx + 6rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 106rpx + 6rpx);
}
/* 商品信息 */
.goods-info {
background: #fff;
padding: 25rpx 30rpx;
}
.info-item__top {
min-height: 40rpx;
margin-bottom: 20rpx;
}
.info-item__top .active-tag {
color: #fff;
background: $main-bg;
padding: 4rpx 10rpx;
border-radius: 15rpx;
font-size: 26rpx;
text-align: center;
margin-right: 15rpx;
}
.floor-price__samll {
font-size: 26rpx;
line-height: 1;
color: $main-bg;
margin-bottom: -10rpx;
}
/* 商品价 */
.floor-price {
color: $main-bg;
margin-right: 15rpx;
font-size: 42rpx;
line-height: 1;
}
.original-price {
font-size: 26rpx;
line-height: 1;
text-decoration: line-through;
color: #959595;
margin-bottom: -6rpx;
}
.goods-sales {
font-size: 24rpx;
color: #959595;
}
.info-item__name .goods-name {
font-size: 30rpx;
}
/* 商品分享 */
.goods-share__line {
border-left: 1rpx solid #f4f4f4;
height: 60rpx;
margin: 0 30rpx;
}
.goods-share .share-btn {
line-height: normal;
padding: 0;
background: none;
border-radius: 0;
box-shadow: none;
font-size: 8pt;
border: none;
color: #191919;
}
.goods-share .share-btn::after {
border: none;
}
.goods-share .share__icon {
font-size: 40rpx;
margin-bottom: 5rpx;
}
/* 商品卖点 */
.info-item_selling-point {
margin-top: 8rpx;
font-size: 24rpx;
color: #808080;
}
// 选择商品规格
.goods-choice {
padding: 26rpx 30rpx;
font-size: 28rpx;
.spec-list {
display: flex;
align-items: center;
.spec-name {
margin-right: 10rpx;
}
}
}
/* 商品详情 */
.goods-content .item-title {
padding: 26rpx 30rpx;
font-size: 28rpx;
}
/* 底部操作栏 */
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
display: flex;
z-index: 11;
box-shadow: 0 -4rpx 40rpx 0 rgba(151, 151, 151, 0.24);
background: #fff;
// 设置ios刘海屏底部横线安全区域
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.footer-container {
width: 100%;
display: flex;
height: 106rpx;
}
// 快捷菜单
.foo-item-fast {
box-sizing: border-box;
width: 256rpx;
line-height: 1;
display: flex;
justify-content: center;
align-items: center;
.fast-item {
position: relative;
padding: 4rpx 10rpx;
line-height: 1;
// text-align: center;
.fast-icon {
margin-bottom: 6rpx;
}
&--home {
margin-right: 30rpx;
}
&--cart {
.fast-icon { padding-left: 3px; }
}
// 角标
.fast-badge {
display: inline-block;
box-sizing: border-box;
min-width: 16px;
padding: 0 3px;
color: #fff;
font-weight: 500;
font-size: 12px;
font-family: -apple-system-font, Helvetica Neue, Arial, sans-serif;
line-height: 1.2;
text-align: center;
background-color: #ee0a24;
border: 1px solid #fff;
border-radius: 999px;
}
.fast-badge--fixed {
position: absolute;
top: 0;
right: 0;
transform-origin: 100%
}
.fast-icon {
font-size: 46rpx;
}
.fast-text {
font-size: 24rpx;
}
}
}
// 操作按钮
.foo-item-btn {
flex: 1;
.btn-wrapper {
height: 100%;
display: flex;
align-items: center;
}
.btn-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
border-radius: 0;
}
// 立即砍价
.btn-item {
flex: 1;
height: 100%;
font-size: 30rpx;
display: flex;
justify-content: center;
align-items: center;
&.btn--main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
}
&.btn--gray {
background-color: #ccc;
color: #fff;
}
}
}
// 活动状态
.info-item_status {
margin-top: 20rpx;
padding: 15rpx 20rpx;
font-size: 24rpx;
border-radius: 6rpx;
}
.info-item_status .countdown-icon {
font-size: 28rpx;
margin-right: 15rpx;
}
// 活动倒计时
.info-item_countdown {
background: #f0f9ff;
color: #8f8f8f;
}
.info-item_countdown .countdown-icon {
color: #1397d8;
}
// 活动已结束
.info-item_end {
background: #ccc;
color: #fff;
}
// 砍价玩法
.bargain-rules {
padding: 20rpx 0;
font-size: 29rpx;
.item-title {
padding: 0 30rpx;
}
.rule-simple {
margin-top: 35rpx;
color: #737373;
}
.i-number {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-bottom: 15rpx;
border: 1rpx dashed #c0c0c0;
}
}
// 砍价规则(弹窗)
.pops-content {
padding: 30rpx 48rpx;
font-size: 28rpx;
line-height: 44rpx;
text-align: left;
color: #606266;
min-height: 320rpx;
max-height: 640rpx;
box-sizing: border-box;
}
+460
View File
@@ -0,0 +1,460 @@
<template>
<view class="container" :style="appThemeStyle">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ native: true, auto: false }" @down="downCallback" :up="upOption"
@up="upCallback">
<!-- 秒杀会场场次tab -->
<view class="sharp-tabs">
<scroll-view :scroll-x="true" :scroll-left="scrollLeft" @scroll="scroll">
<view class="sharp-tabs--container dis-flex">
<view v-for="(item, index) in tabbar" :key="index" class="tabs-item dis-flex flex-dir-column flex-x-center flex-y-center"
:class="{ active: curTabIndex == index }" @click="handleTab(index)">
<block v-if="item.status == ActiveStatusEnum.STATE_NOTICE.value">
<view class="item-title">{{ item.status_text }}</view>
</block>
<block v-else>
<view class="item-time">{{ item.active_time }}</view>
<view class="item-status">{{ item.status_text }}</view>
</block>
</view>
</view>
</scroll-view>
</view>
<!-- 秒杀活动 -->
<view v-if="tabbar.length" class="sharp-active dis-flex flex-dir-column flex-y-center">
<!-- 活动状态 -->
<view class="active-status">
<text class="active-status--icon iconfont icon-artboard"></text>
<text v-if="tabbar[curTabIndex].status != ActiveStatusEnum.STATE_NOTICE.value"
class="active-status--time">{{ tabbar[curTabIndex].active_time }}</text>
<text class="active-status--text">{{ tabbar[curTabIndex].status_text2 }}</text>
</view>
<!-- 倒计时 -->
<view class="active--count-down dis-flex flex-y-center">
<text class="m-r-10">{{ tabbar[curTabIndex].status == ActiveStatusEnum.STATE_BEGIN.value ? '距结束' : '距开始' }}</text>
<count-down :date="tabbar[curTabIndex].count_down_time" separator="colon" theme="custom" />
</view>
</view>
<!-- 秒杀商品列表 -->
<view class="goods-hall">
<view class="goods-item" v-for="(item, index) in goodsList.data" :key="index" @click="handleTargetGoods(item.sharp_goods_id)">
<view class="goods-item--container dis-flex">
<!-- 商品图片 -->
<view class="goods-image">
<image :src="item.goods_image"></image>
</view>
<view class="goods-info">
<!-- 商品名称 -->
<view class="goods-name">
<text class="twoline-hide">{{ item.goods_name }}</text>
</view>
<!-- 秒杀进度条 -->
<view class="sharp-progress dis-flex flex-y-center">
<view class="yoo-progress" :style="{ backgroundColor: progressBackgroundColor }">
<view class="yoo-progress--portion" :style="{ width: `${item.progress}%` }">
</view>
<text class="yoo-progress--text">{{ item.progress }}%</text>
</view>
<view class="sharp-sales">已抢{{ item.sales_actual }}</view>
</view>
<!-- 秒杀活动价格 -->
<view class="sharp-price dis-flex flex-y-end">
<view class="seckill-price">
<text class="f-24"></text>
<text class="money">{{ item.seckill_price_min }}</text>
</view>
<view class="original-price">
<text class="f-24"></text>
<text class="money">{{ item.original_price }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="opt-touch">
<view class="touch-btn">
<text>{{ tabbar[curTabIndex].status == ActiveStatusEnum.STATE_BEGIN.value ? '马上抢' : '查看商品' }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import CountDown from '@/components/countdown'
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import { hex2rgba } from '@/utils/color'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
import { ActiveStatusEnum } from '@/common/enum/sharp'
import * as HomeApi from '@/api/sharp/home'
import * as GoodsApi from '@/api/sharp/goods'
const pageSize = 15
export default {
components: {
MescrollBody,
CountDown
},
mixins: [MescrollMixin],
data() {
return {
//
isLoading: true,
// tab
curTabIndex: 0,
// tab
scrollLeft: 0,
//
upOption: {
//
auto: false,
// ; 10
page: { size: pageSize },
// 3
noMoreSize: 3,
},
//
ActiveStatusEnum,
//
tabbar: [],
//
goodsList: [],
}
},
computed: {
//
progressBackgroundColor() {
return hex2rgba(this.appTheme.mainBg, 0.2)
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.onRefreshPage()
},
methods: {
//
onRefreshPage() {
const app = this
return new Promise((resolve, reject) => {
HomeApi.data()
.then(result => {
app.tabbar = result.data.tabbar
app.goodsList = result.data.goodsList
app.curTabIndex = 0
app.scrollLeft = 0
if (!app.goodsList.data.length) {
app.mescroll.showEmpty()
}
resolve()
})
.catch(reject)
})
},
/**
* 获取商品列表
* @param {Number} pageNo 页码
*/
getListData(pageNo = 1) {
const app = this
const activeTimeId = app.getCurTabbarId()
return new Promise((resolve, reject) => {
GoodsApi.list(activeTimeId, { page: pageNo }, { load: false })
.then(result => {
//
const newList = result.data.list
app.goodsList.data = getMoreListData(newList, app.goodsList, pageNo)
resolve(newList)
})
.catch(reject)
})
},
//
downCallback() {
this.onRefreshPage()
.finally(() => this.mescroll.endSuccess())
},
/**
* 上拉加载的回调
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
//
app.getListData(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
// tab
scroll({ detail }) {
this.scrollLeft = detail.scrollLeft
},
// ()
handleTab(index) {
const app = this
app.curTabIndex = index
//
app.goodsList = getEmptyPaginateObj()
app.mescroll.resetUpScroll()
},
//
getCurTabbar() {
return this.tabbar[this.curTabIndex]
},
// ID
getCurTabbarId() {
const curTabbar = this.getCurTabbar()
return curTabbar ? curTabbar.active_time_id : 0
},
//
handleTargetGoods(sharpGoodsId) {
this.$navTo('pages/sharp/goods/index', {
activeTimeId: this.getCurTabbarId(),
sharpGoodsId
})
},
},
/**
* 分享当前页面
*/
onShareAppMessage() {
//
const params = this.$getShareUrlParams()
return {
title: '整点秒杀会场',
path: `/pages/sharp/index?${params}`
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本暂只在 Android 平台支持详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
//
const params = this.$getShareUrlParams()
return {
title: '整点秒杀会场',
path: `/pages/sharp/index?${params}`
}
},
}
</script>
<style lang="scss">
page {
background: #efeff4;
}
</style>
<style lang="scss" scoped>
.container {
background: #efeff4;
}
// ()
.sharp-tabs {
background: #fff;
.sharp-tabs--container {
background: #30353c;
}
// .sharp-tabs--empty {
// padding-bottom: 30rpx;
// }
.tabs-item {
position: relative;
min-width: 170rpx;
height: 110rpx;
background: #30353c;
color: #fff;
padding: 15rpx 45rpx;
text-align: center;
box-sizing: border-box;
white-space: nowrap;
.item-time {
font-size: 32rpx;
}
.item-status {
font-size: 24rpx;
}
.item-title {
font-size: 30rpx;
}
&.active {
background: $main-bg;
&::after {
content: "";
display: block;
position: absolute;
z-index: 999;
bottom: -15rpx;
left: 50%;
margin-left: -12rpx;
width: 0;
height: 0;
border: 20rpx solid $main-bg;
border-left-color: transparent;
border-right-color: transparent;
border-bottom-color: transparent;
border-bottom-width: 0;
}
}
}
}
//
.sharp-active {
background: #fff;
padding: 26rpx 0;
.active-status {
font-size: 32rpx;
color: $main-bg;
margin-bottom: 20rpx;
.active-status--icon {
margin-right: 10rpx;
}
.active-status--time {
margin-right: 10rpx;
}
}
}
//
.active--count-down {
font-size: 26rpx;
height: 40rpx;
}
//
.goods-hall {
padding-top: 20rpx;
.goods-item {
background: #fff;
padding: 30rpx 16rpx;
border-bottom: 1rpx solid #e7e7e7;
.goods-image {
image {
display: block;
width: 220rpx;
height: 220rpx;
}
}
.goods-info {
width: 498rpx;
padding-top: 8rpx;
margin-left: 15rpx;
position: relative;
.goods-name {
font-size: 28rpx;
min-height: 72rpx;
margin-bottom: 20rpx;
}
}
}
}
//
.yoo-progress {
position: relative;
width: 70%;
height: 28rpx;
border-radius: 12rpx;
background: #f8b6b6;
&--portion {
width: 0%;
height: 100%;
border-radius: 12rpx;
background: linear-gradient(to right, $main-bg2, $main-bg);
}
&--text {
color: #fff;
font-size: 24rpx;
line-height: 1.6;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
//
.sharp-sales {
margin-left: 30rpx;
font-size: 24rpx;
color: $main-bg;
white-space: nowrap;
}
//
.sharp-price {
margin-top: 40rpx;
line-height: 1;
.seckill-price {
font-size: 32rpx;
color: $main-bg;
margin-bottom: -2rpx;
}
.original-price {
margin-left: 5rpx;
font-size: 24rpx;
color: #818181;
text-decoration: line-through;
}
}
//
.opt-touch {
position: absolute;
bottom: 0;
right: 10rpx;
.touch-btn {
font-size: 26rpx;
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
border-radius: 30rpx;
padding: 10rpx 30rpx;
}
}
</style>
+198
View File
@@ -0,0 +1,198 @@
<template>
<view v-if="!isLoading" class="container">
<view class="header">
<view class="shop-logo">
<image class="image" :src="detail.logo_url"></image>
</view>
<view class="shop-name">
<text>{{ detail.shop_name }}</text>
</view>
<view v-if="detail.summary" class="shop-summary dis-flex">
<text>门店简介{{ detail.summary }}</text>
</view>
</view>
<view class="content">
<view class="content-item dis-flex flex-y-center">
<view class="content-item__icon dis-flex">
<text class="iconfont icon-shijian"></text>
</view>
<view class="content-item__text flex-box dis-flex">
<text class="f-26">{{ detail.shop_hours }}</text>
</view>
</view>
<view class="content-item dis-flex flex-y-center" @click="onOpenLocation()">
<view class="content-item__icon dis-flex">
<text class="iconfont icon-dingwei"></text>
</view>
<view class="content-item__text flex-box dis-flex">
<text
class="f-26">{{ detail.region.province }}{{ detail.region.city }}{{ detail.region.region }}{{ detail.address }}</text>
</view>
<view class="content-item__arrow dis-flex">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
<view class="content-item dis-flex flex-y-center" @click="onMakePhoneCall()">
<view class="content-item__icon dis-flex">
<text class="iconfont icon-dianhua"></text>
</view>
<view class="content-item__text flex-box dis-flex">
<text class="f-26">{{ detail.phone }}</text>
</view>
<view class="content-item__arrow dis-flex">
<text class="iconfont icon-arrow-right"></text>
</view>
</view>
</view>
</view>
</template>
<script>
import * as ShopApi from '@/api/shop'
export default {
data() {
return {
//
isLoading: true,
// ID
shopId: undefined,
//
detail: null
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
// ID
this.shopId = options.shopId
//
this.getShopDetail()
},
methods: {
//
getShopDetail() {
const app = this
app.isLoading = true
ShopApi.detail(app.shopId)
.then(result => app.detail = result.data.detail)
.finally(() => app.isLoading = false)
},
//
onMakePhoneCall() {
const app = this
uni.makePhoneCall({
phoneNumber: app.detail.phone
})
},
//
onOpenLocation() {
const app = this
const { detail } = app
uni.openLocation({
name: detail.shop_name,
address: detail.region.province + detail.region.city + detail.region.region + detail.address,
longitude: Number(detail.longitude),
latitude: Number(detail.latitude),
scale: 15
})
},
},
/**
* 分享当前页面
*/
onShareAppMessage() {
const app = this
//
const params = app.$getShareUrlParams({ shopId: app.shopId })
return {
title: app.detail.shop_name,
path: "/pages/shop/detail?" + params
}
},
/**
* 分享到朋友圈
* 本接口为 Beta 版本暂只在 Android 平台支持详见分享到朋友圈 (Beta)
* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html
*/
onShareTimeline() {
const app = this
//
const params = app.$getShareUrlParams({ shopId: app.shopId })
return {
title: app.detail.shop_name,
path: "/pages/shop/detail?" + params
}
},
}
</script>
<style lang="scss">
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
.container {
background: #fff;
padding: 0 30rpx;
}
.header {
padding: 30rpx 0;
border-bottom: 1rpx solid #f1f1f1;
.shop-logo,
.shop-name {
text-align: center;
}
.shop-logo {
.image {
width: 130rpx;
height: 130rpx;
border-radius: 50%;
box-shadow: 0 0 30rpx rgba(0, 0, 0, 0.1);
}
}
.shop-name {
margin-top: 16rpx;
font-size: 32rpx;
}
.shop-summary {
padding: 20rpx;
margin-top: 30rpx;
font-size: 26rpx;
line-height: 1.6;
background: #f9f9f9;
border-radius: 6rpx;
}
}
.content {
margin-top: 30rpx;
.content-item {
padding: 12rpx 0;
.content-item__text {
padding: 0 20rpx;
}
}
}
</style>
+198
View File
@@ -0,0 +1,198 @@
<template>
<view class="container b-f">
<!-- 门店列表 -->
<view class="shop-list">
<view v-for="(item, index) in shopList" :key="index" @click="onSelectedShop(item.shop_id)" class="shop-item dis-flex flex-y-center">
<view class="shop-item__content flex-box">
<view class="shop-item__title">
<text>{{ item.shop_name }}</text>
</view>
<view class="shop-item__address">
<text>地址{{ item.region.province }}{{ item.region.city }}{{ item.region.region }}{{ item.address }}</text>
</view>
<view class="shop-item__phone">
<text>联系电话{{ item.phone }}</text>
</view>
<view v-if="item.distance" class="shop-item__distance">
<text class="iconfont icon-dingwei"></text>
<text class="f-24">{{ item.distance_unit }}</text>
</view>
</view>
<!-- 选中状态 -->
<view v-if="item.shop_id == selectedId" class="shop-item__right">
<text class="iconfont icon-check1"></text>
</view>
</view>
</view>
<!-- 定位按钮 -->
<view v-if="!isAuthor" class="widget-location dis-flex flex-x-center flex-y-center" @click="onAuthorize()">
<text class="iconfont icon-locate"></text>
</view>
<empty v-if="!shopList.length" :isLoading="isLoading" tips="亲,暂无自提门店哦" />
</view>
</template>
<script>
import * as ShopApi from '@/api/shop'
import Empty from '@/components/empty'
export default {
components: {
Empty
},
data() {
return {
//
isLoading: true,
//
isAuthor: true,
// ID
selectedId: null,
//
shopList: []
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad({ selectedId }) {
const app = this
// ID
app.selectedId = selectedId ? selectedId : null
//
app.getShopList()
//
app.getLocation(res => {
app.getShopList(res.longitude, res.latitude)
})
},
methods: {
//
getShopList(longitude, latitude) {
const app = this
app.isLoading = true
ShopApi.list({
isCheck: 1,
longitude: longitude ? longitude : '',
latitude: latitude ? latitude : ''
})
.then(result => app.shopList = result.data.list)
.finally(() => app.isLoading = false)
},
//
// https://uniapp.dcloud.io/api/location/location?id=getlocation
getLocation(callback) {
const app = this
uni.getLocation({
type: 'wgs84',
success: callback,
fail() {
app.$toast('获取定位失败,请点击右下角按钮重新尝试定位')
app.isAuthor = false
}
})
},
//
onAuthorize() {
const app = this
// #ifdef MP
uni.openSetting({
success(res) {
if (res.authSetting['scope.userLocation']) {
console.log('定位权限授权成功')
app.isAuthor = true
setTimeout(() => {
//
app.getLocation(res => {
app.getShopList(res.longitude, res.latitude)
})
}, 1000)
}
}
})
// #endif
// #ifdef H5
//
app.getLocation(res => {
app.getShopList(res.longitude, res.latitude)
})
// #endif
},
/**
* 选择门店
*/
onSelectedShop(selectedId) {
const app = this
// id
app.selectedId = selectedId
// :
uni.$emit('syncSelectedId', selectedId)
//
uni.navigateBack({
delta: 1
})
},
}
}
</script>
<style lang="scss" scoped>
.shop-list .shop-item {
padding: 20rpx 30rpx;
min-height: 180rpx;
font-size: 26rpx;
line-height: 1.5;
border-bottom: 1px solid #eee;
}
.shop-item__title {
font-size: 30rpx;
color: #535353;
margin-bottom: 10rpx;
}
.shop-item__address,
.shop-item__phone {
color: #919396;
}
.shop-item__distance {
margin-top: 10rpx;
color: #c1c1c1;
height: 40rpx;
}
.shop-item__distance .iconfont {
color: #81838e;
margin-right: 5rpx;
}
//
.shop-item__right {
margin-left: 20rpx;
color: #535353;
font-size: 38rpx;
}
//
.widget-location {
position: fixed;
right: calc(var(--window-right) + 40rpx);
bottom: calc(var(--window-bottom) + 70rpx);
width: 72rpx;
height: 72rpx;
z-index: 200;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.2);
color: #555;
font-size: 40rpx;
}
</style>
+311
View File
@@ -0,0 +1,311 @@
<template>
<view class="container" :style="appThemeStyle">
<!-- 页面头部 -->
<view class="header">
<view class="title">
<text>绑定您的手机号</text>
</view>
<view class="sub-title">
<text>为了更好的服务您请绑定手机号</text>
</view>
</view>
<!-- 表单 -->
<view class="submit-form">
<!-- 手机号 -->
<view class="form-item">
<input class="form-item--input" type="number" v-model="mobile" maxlength="11" placeholder="请输入手机号码" />
</view>
<!-- 图形验证码 -->
<view class="form-item">
<input class="form-item--input" type="text" v-model="captchaCode" maxlength="5" placeholder="请输入图形验证码" />
<view class="form-item--parts">
<view class="captcha" @click="getCaptcha()">
<image class="image" :src="captcha.base64"></image>
</view>
</view>
</view>
<!-- 短信验证码 -->
<view class="form-item">
<input class="form-item--input" type="number" v-model="smsCode" maxlength="6" placeholder="请输入短信验证码" />
<view class="form-item--parts">
<view class="captcha-sms" @click="handelSmsCaptcha()">
<text v-if="!smsState" class="activate">获取验证码</text>
<text v-else class="un-activate">重新发送({{ times }})</text>
</view>
</view>
</view>
<!-- 确认绑定 -->
<view class="submit-button" @click="handleSubmit()">
<text>确认绑定</text>
</view>
</view>
</view>
</template>
<script>
import store from '@/store'
import * as UserApi from '@/api/user'
import * as CaptchaApi from '@/api/captcha'
import * as Verify from '@/utils/verify'
// ()
const times = 60
//
const GET_CAPTCHA = 10
const FORM_SUBMIT = 20
export default {
data() {
return {
//
isLoading: false,
//
captcha: {},
//
smsState: false,
//
times,
//
mobile: '',
//
captchaCode: '',
//
smsCode: ''
}
},
/**
* 生命周期函数--监听页面加载
*/
created() {
//
this.getCaptcha()
},
methods: {
//
getCaptcha() {
const app = this
CaptchaApi.image().then(result => app.captcha = result.data)
},
//
handelSmsCaptcha() {
const app = this
if (!app.isLoading && !app.smsState && app.formValidation(GET_CAPTCHA)) {
app.sendSmsCaptcha()
app.getCaptcha()
}
},
//
formValidation(scene = GET_CAPTCHA) {
const app = this
//
if (scene === GET_CAPTCHA) {
if (!app.validteMobile(app.mobile) || !app.validteCaptchaCode(app.captchaCode)) {
return false
}
}
//
if (scene === FORM_SUBMIT) {
if (!app.validteMobile(app.mobile) || !app.validteSmsCode(app.smsCode)) {
return false
}
}
return true
},
//
validteMobile(str) {
if (Verify.isEmpty(str)) {
this.$toast('请先输入手机号')
return false
}
if (!Verify.isMobile(str)) {
this.$toast('请输入正确格式的手机号')
return false
}
return true
},
//
validteCaptchaCode(str) {
if (Verify.isEmpty(str)) {
this.$toast('请先输入图形验证码')
return false
}
return true
},
//
validteSmsCode(str) {
if (Verify.isEmpty(str)) {
this.$toast('请先输入短信验证码')
return false
}
return true
},
//
sendSmsCaptcha() {
const app = this
app.isLoading = true
CaptchaApi.sendSmsCaptcha({
form: {
captchaKey: app.captcha.key,
captchaCode: app.captchaCode,
mobile: app.mobile
}
})
.then(result => {
//
app.$toast(result.message)
//
app.timer()
})
.finally(() => app.isLoading = false)
},
//
timer() {
const app = this
app.smsState = true
const inter = setInterval(() => {
app.times = app.times - 1
if (app.times <= 0) {
app.smsState = false
app.times = times
clearInterval(inter)
}
}, 1000)
},
//
handleSubmit() {
const app = this
if (!app.isLoading && app.formValidation(FORM_SUBMIT)) {
app.onSubmitEvent()
}
},
//
onSubmitEvent() {
const app = this
app.isLoading = true
UserApi.bindMobile({ form: { smsCode: app.smsCode, mobile: app.mobile } })
.then(result => {
//
app.$toast(result.message)
//
setTimeout(() => {
app.onNavigateBack(1)
}, 2000)
})
.finally(() => app.isLoading = false)
},
/**
* 提交成功-跳转回原页面
*/
onNavigateBack(delta) {
uni.navigateBack({
delta: Number(delta || 1)
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 100rpx 60rpx;
min-height: 100vh;
background-color: #fff;
}
//
.header {
margin-bottom: 50rpx;
.title {
color: #191919;
font-size: 50rpx;
}
.sub-title {
margin-top: 20rpx;
color: #b3b3b3;
font-size: 25rpx;
}
}
//
.form-item {
display: flex;
padding: 18rpx;
border-bottom: 1rpx solid #f3f1f2;
margin-bottom: 25rpx;
height: 96rpx;
&--input {
font-size: 26rpx;
letter-spacing: 1rpx;
flex: 1;
height: 100%;
}
&--parts {
min-width: 100rpx;
height: 100%;
}
//
.captcha {
height: 100%;
.image {
display: block;
width: 192rpx;
height: 100%;
}
}
//
.captcha-sms {
font-size: 22rpx;
line-height: 50rpx;
padding-right: 20rpx;
.activate {
color: #cea26a;
}
.un-activate {
color: #9e9e9e;
}
}
}
//
.submit-button {
width: 100%;
height: 86rpx;
margin-top: 70rpx;
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
border-radius: 80rpx;
box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.1);
letter-spacing: 5rpx;
display: flex;
justify-content: center;
align-items: center;
}
</style>
+355
View File
@@ -0,0 +1,355 @@
<template>
<view class="container" :style="appThemeStyle">
<view class="addres-list">
<view class="address-item" v-for="(item, index) in list" :key="index">
<view class="title">
<text>设备编号{{ item.equipmentCode }}</text>
<u-tag v-if="item.isCtive == '已激活'" :text="item.isCtive" plain size="mini" type="success"></u-tag>
<u-tag v-else :text="item.isCtive" plain size="mini" type="warning"></u-tag>
</view>
<view class="line"></view>
<view class="contacts">
<view class="bms-info">
<text class="item">电池型号{{ item.batteryModel }}</text>
<text class="item">BMS模块{{ item.bms }}</text>
<text class="item">工作状态{{ item.workingStatus }}</text>
<text class="item">电池状态健康</text>
</view>
<view class="bms-image">
<view class="bms-image-box">
<image class="image" src="/static/battery.png"></image>
<text class="bms-image-fbf">67%</text>
</view>
<u-tag text="离线" plain size="mini" type="info"></u-tag>
</view>
</view>
<view class="line"></view>
<view class="item-option">
<view class="_left">
<u-icon label="操作" size="40" name="map" @click="navTo('package/user/equipment/operation?equipmentId='+item.equipmentId)"></u-icon>
</view>
<view class="line-align"></view>
<view class="_right">
<view class="events">
<u-icon label="详情" size="40" name="file-text" @click="navTo('package/user/equipment/detail?equipmentId='+item.equipmentId)"></u-icon>
<!-- <view class="event-item" @click="handleUpdate(item.address_id)">
<text class="iconfont icon-edit"></text>
<text class="title">编辑</text>
</view>
<view class="event-item" @click="handleRemove(item.address_id)">
<text class="iconfont icon-delete"></text>
<text class="title">删除</text>
</view> -->
</view>
</view>
</view>
</view>
</view>
<empty v-if="!list.length" tips="暂无设备" />
<!-- 底部操作按钮 -->
<!-- <view class="footer-fixed">
<view class="btn-wrapper">
<view class="btn-item btn-item-main" @click="handleCreate()">添加新地址</view>
</view>
</view> -->
</view>
</template>
<script>
import * as EquipmentApi from '@/websoft/api/equipment.js'
import Empty from '@/components/empty'
export default {
components: {
Empty
},
data() {
return {
//
options: {},
//
isLoading: true,
//
list: [],
//
defaultId: null
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
//
this.options = options
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
//
this.getPageData()
},
methods: {
//
getPageData() {
const app = this
app.isLoading = true
Promise.all([app.getDefaultId(), app.getEquipment()])
.then(() => {
//
app.onReorder()
})
.finally(() => app.isLoading = false)
},
//
getEquipment() {
const app = this
EquipmentApi.pageEquipment({userId: uni.getStorageSync('userId')}).then(res => {
console.log("res: ",res);
app.list = res.data.list
})
},
//
getDefaultId() {
return new Promise((resolve, reject) => {
const app = this
// AddressApi.defaultId()
// .then(result => {
// app.defaultId = result.data.defaultId
// resolve(result)
// })
// .catch(reject)
})
},
//
onReorder() {
const app = this
app.list.sort(item => {
return item.address_id == app.defaultId ? -1 : 1
})
},
navTo(url){
this.$navTo(url)
},
/**
* 添加新地址
*/
handleCreate() {
this.$navTo('pages/address/create')
},
/**
* 编辑地址
* @param {int} addressId 收货地址ID
*/
handleUpdate(addressId) {
this.$navTo('pages/address/update', { addressId })
},
/**
* 删除收货地址
* @param {int} addressId 收货地址ID
*/
handleRemove(addressId) {
const app = this
uni.showModal({
title: "提示",
content: "您确定要删除当前收货地址吗?",
success({ confirm }) {
confirm && app.onRemove(addressId)
}
});
},
/**
* 确认删除收货地址
* @param {int} addressId 收货地址ID
*/
onRemove(addressId) {
const app = this
AddressApi.remove(addressId)
.then(result => {
app.getPageData()
})
},
/**
* 设置为默认地址
* @param {Object} addressId
*/
handleSetDefault(addressId) {
const app = this
AddressApi.setDefault(addressId)
.then(result => {
app.defaultId = addressId
app.options.from === 'checkout' && uni.navigateBack()
})
}
}
}
</script>
<style lang="scss" scoped>
.addres-list {
padding-bottom: calc(constant(safe-area-inset-bottom) + 140rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
padding-top: 20rpx;
}
//
.address-item {
margin: 0 auto 20rpx auto;
padding: 30rpx 40rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0 rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
background: #fff;
.title{
display: flex;
justify-content: space-between;
}
}
.contacts {
margin-bottom: 16rpx;
display: flex;
justify-content: space-between;
.bms-info{
display: flex;
flex-direction: column;
color: #666666;
.item{
margin-right: 16rpx;
}
.name {
margin-right: 16rpx;
}
}
.bms-image{
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
width: 120rpx;
.bms-image-box{
display: flex;
font-size: 22rpx;
margin-bottom: 18rpx;
color: #00af00;
.bms-image-fbf{
margin-top: 22rpx;
}
}
.image{
width: 50rpx; height: 60rpx;
}
}
}
.address {
font-size: 28rpx;
.region {
margin-right: 10rpx;
}
}
.line {
margin: 20rpx 0;
border-bottom: 1rpx solid #f3f3f3;
}
.line-align{
width: 6rpx;
height: 48rpx;
border-right: 1rpx solid #f3f3f3;
}
.item-option {
display: flex;
justify-content: space-around;
align-items: center;
height: 48rpx;
//
.item-radio {
font-size: 28rpx;
.radio {
vertical-align: middle;
transform: scale(0.76)
}
.text {
vertical-align: middle;
}
}
//
.events {
display: flex;
align-items: center;
line-height: 48rpx;
.event-item {
font-size: 28rpx;
margin-right: 26rpx;
color: #4c4c4c;
&:last-child {
margin-right: 0;
}
.title {
margin-left: 8rpx;
}
}
}
}
//
.footer-fixed {
position: fixed;
bottom: var(--window-bottom);
left: 0;
right: 0;
z-index: 11;
// ios线
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.btn-wrapper {
height: 120rpx;
padding: 0 40rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 86rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 1rpx 5rpx 0 rgba(0, 0, 0, 0.05);
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
}
}
</style>
+759
View File
@@ -0,0 +1,759 @@
<template>
<view v-if="!isFirstload" class="container" :style="appThemeStyle">
<!-- 页面头部 -->
<view class="main-header" :style="{ height: platform == 'H5' ? '260rpx' : '320rpx', paddingTop: platform == 'H5' ? '0' : '80rpx' }">
<image class="bg-image" src="/static/background/user-header2.png" mode="scaleToFill"></image>
<!-- 用户信息 -->
<view v-if="isLogin" class="user-info">
<view class="user-avatar" @click="handlePersonal()">
<avatar-image :url="userInfo.avatar_url" :width="100" />
</view>
<view class="user-content">
<!-- 会员昵称 -->
<view class="nick-name oneline-hide" @click="handlePersonal()">{{ userInfo.nick_name }}</view>
<!-- 会员等级 -->
<view v-if="userInfo.grade_id > 0 && userInfo.grade" class="user-grade">
<view class="user-grade_icon">
<image class="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAA0lBMVEUAAAD/tjL/tzH/uDP/uC7/tjH/tzH/tzL/tTH+tTL+tjP/tDD/tTD+tzD/tjL/szD/uDH/tjL/tjL+tjD/tjT/szb/tzL/tTL+uTH+tjL/tjL/tjL/tTT/tjL/tjL+tjH/uTL/vDD/tjL/tjH/tzL9uS//tTL/nBr/sS7/tjH/ujL/szD/uTv+rzf/tzL+tzH+vDP+uzL+tjP+ry7+tDL9ki/7szf/sEX/tTL/tjL+tjL/tTH/tTT/tzH/tzL/tjP/sTX/uTP/wzX+rTn/vDX9vC8m8ckhAAAAOXRSTlMAlnAMB/vjxKWGMh0S6drMiVxPRkEY9PLy0ru0sKagmo5+dGtgVCMgBP716eXWyMGxqJGRe2o5KSmFNjaYAAABP0lEQVQ4y8XS13KDMBAF0AWDDe4t7r3ETu9lVxJgJ/n/X8rKAzHG5TE+Twz3zki7I/g/KXdghIbGJewrU4yzn08Ebgl6TuZzzuOC6W5es3HX6qsSz3NFShRU0MpucytDmOSpu3yULx3CA9RD1HjVedc0jSjqm6ZzhUjDsFDQhSp/OKj5GQvg0+ZCOixsbtDLAeTTOm/yGi8GyIphIVsgH737FEDV44LJa88IRKK/SetrwT9G/GUIr6vXjoy4GXn7+RboVXnghuSjaoGecwQxL2su3CwAKlO+QFoqxI4FMctHQhQd2OhxTu184jWUlI+rMTBTn1/IQcJHQ6GQdZ7pWiDaNdhTt330efISeiqYwQEzQpTlsURJLhzkEmpCPsERfeIUVyXr6MNuIyp5uziW6xURtt7hhGwzmMNJExfO4Bd9X0ZPqAxdNwAAAABJRU5ErkJggg==">
</image>
</view>
<view class="user-grade_name">
<text>{{ userInfo.grade.name }}</text>
</view>
</view>
<!-- 会员无等级时显示手机号 -->
<view v-else class="mobile">{{ userInfo.mobile }}</view>
</view>
</view>
<!-- 未登录 -->
<view v-else class="user-info" @click="handleLogin">
<view class="user-avatar">
<avatar-image :width="100" />
</view>
<view class="user-content">
<view class="nick-name">未登录</view>
<view class="login-tips">点击登录账号</view>
</view>
</view>
</view>
<!-- 绑定手机号 -->
<view v-if="isLogin && !userInfo.mobile && setting[SettingKeyEnum.REGISTER.value].isManualBind" class="my-mobile" @click="handleBindMobile()">
<view class="info">点击绑定手机号确保账户安全</view>
<view class="btn-bind">去绑定</view>
</view>
<!-- 我的钱包 -->
<view class="my-asset">
<view class="asset-left flex-box dis-flex flex-x-around">
<view class="asset-left-item" style="max-width: 200rpx;" @click="onTargetWallet">
<view class="item-value dis-flex flex-x-center">
<text class="oneline-hide">{{ isLogin ? assets.balance : '--' }}</text>
</view>
<view class="item-name dis-flex flex-x-center">
<text>账户余额</text>
</view>
</view>
<view class="asset-left-item" @click="onTargetPoints">
<view class="item-value dis-flex flex-x-center">
<text class="oneline-hide">{{ isLogin ? assets.points : '--' }}</text>
</view>
<view class="item-name dis-flex flex-x-center">
<text>{{ setting[SettingKeyEnum.POINTS.value].points_name }}</text>
</view>
</view>
<view class="asset-left-item" @click="onTargetMyCoupon">
<view class="item-value dis-flex flex-x-center">
<text class="oneline-hide">{{ isLogin ? assets.coupon : '--' }}</text>
</view>
<view class="item-name dis-flex flex-x-center">
<text>优惠券</text>
</view>
</view>
</view>
<view class="asset-right">
<view class="asset-right-item" @click="onTargetWallet">
<view class="item-icon dis-flex flex-x-center">
<text class="iconfont icon-qianbao"></text>
</view>
<view class="item-name dis-flex flex-x-center">
<text>我的钱包</text>
</view>
</view>
</view>
</view>
<!-- 绑定手机号 (第2种样式) -->
<!-- <view class="my-mobile2" @click="handleBindMobile()">
<view class="info">点击绑定手机号确保账户安全</view>
<view class="btn-bind">去绑定</view>
</view> -->
<!-- 订单操作 -->
<view class="order-navbar">
<view class="order-navbar-item" v-for="(item, index) in orderNavbar" :key="index" @click="onTargetOrder(item)">
<view class="item-icon">
<text class="iconfont" :class="[`icon-${item.icon}`]"></text>
</view>
<view class="item-name">{{ item.name }}</view>
<view class="item-badge" v-if="item.count && item.count > 0">
<text v-if="item.count <= 99" class="text">{{ item.count }}</text>
<text v-else class="text">99+</text>
</view>
</view>
</view>
<!-- 我的服务 -->
<view class="my-service">
<view class="service-title">我的服务</view>
<view class="service-content clearfix">
<block v-for="(item, index) in service" :key="index">
<view v-if="item.type == 'link' && item.enabled" class="service-item" @click="handleService(item)">
<view class="item-icon">
<text class="iconfont" :class="[`icon-${item.icon}`]"></text>
</view>
<view class="item-name">{{ item.name }}</view>
<view class="item-badge" v-if="item.count && item.count > 0">
<text v-if="item.count <= 99" class="text">{{ item.count }}</text>
<text v-else class="text">99+</text>
</view>
</view>
<!-- 在线客服 -->
<view v-if="item.type == 'contact' && item.enabled" class="service-item">
<customer-btn>
<view class="item-icon">
<text class="iconfont" :class="[`icon-${item.icon}`]"></text>
</view>
<view class="item-name">{{ item.name }}</view>
</customer-btn>
</view>
</block>
</view>
</view>
<!-- 退出登录 -->
<view v-if="isLogin" class="my-logout">
<view class="logout-btn" @click="handleLogout()">
<text>退出登录</text>
</view>
</view>
<!-- 商品推荐 -->
<recommended />
</view>
</template>
<script>
import store from '@/store'
import { inArray } from '@/utils/util'
import AvatarImage from '@/components/avatar-image'
import Recommended from '@/components/recommended'
import CustomerBtn from '@/components/customer-btn'
import { setCartTabBadge } from '@/core/app'
import SettingKeyEnum from '@/common/enum/setting/Key'
import SettingModel from '@/common/model/Setting'
import * as UserApi from '@/api/user'
import * as OrderApi from '@/api/order'
import { checkLogin } from '@/core/app'
//
const orderNavbar = [
{ id: 'all', name: '全部订单', icon: 'qpdingdan' },
{ id: 'payment', name: '待支付', icon: 'daifukuan', count: 0 },
{ id: 'delivery', name: '待发货', icon: 'daifahuo', count: 0 },
{ id: 'received', name: '待收货', icon: 'daishouhuo', count: 0 },
]
/**
* 我的服务
* id: 标识; name: 标题名称; icon: 图标; type 类型(link和button); url: 跳转的链接
*/
const service = [
{ id: 'address', name: '收货地址', icon: 'shouhuodizhi', type: 'link', url: 'pages/address/index' },
{ id: 'coupon', name: '领券中心', icon: 'lingquan', type: 'link', url: 'pages/coupon/index' },
{ id: 'myCoupon', name: '优惠券', icon: 'youhuiquan', type: 'link', url: 'pages/my-coupon/index' },
{ id: 'refund', name: '退换/售后', icon: 'shouhou', type: 'link', url: 'pages/refund/index', count: 0 },
{ id: 'help', name: '我的帮助', icon: 'bangzhu', type: 'link', url: 'pages/help/index' },
{ id: 'contact', name: '在线客服', icon: 'kefu', type: 'contact' },
{ id: 'points', name: '我的积分', icon: 'jifen', type: 'link', url: 'pages/points/log' },
{ id: 'dealer', name: '分销中心', icon: 'fenxiao', type: 'link', url: 'pages/dealer/index' },
{ id: 'groupon', name: '我的拼团', icon: 'pintuan', type: 'link', url: 'pages/groupon/index?tab=1' },
{ id: 'bargain', name: '我的砍价', icon: 'kanjia', type: 'link', url: 'pages/bargain/index?tab=1' },
]
export default {
components: {
AvatarImage,
Recommended,
CustomerBtn
},
data() {
return {
inArray,
//
SettingKeyEnum,
//
isLoading: true,
//
isFirstload: true,
//
isLogin: false,
//
setting: {},
//
userInfo: {},
//
assets: { balance: '--', points: '--', coupon: '--' },
//
service,
//
orderNavbar,
//
todoCounts: { payment: 0, deliver: 0, received: 0 }
}
},
/**
* 生命周期函数--监听页面显示
*/
onLoad(options) {},
/**
* 生命周期函数--监听页面显示
*/
onShow(options) {
this.onRefreshPage()
},
methods: {
//
onRefreshPage() {
//
setCartTabBadge()
//
this.isLogin = checkLogin()
//
this.getPageData()
},
//
getPageData(callback) {
const app = this
app.isLoading = true
Promise.all([app.getSetting(), app.getUserInfo(), app.getUserAssets(), app.getTodoCounts()])
.then(result => {
app.isFirstload = false
//
app.initService()
//
app.initOrderTabbar()
//
callback && callback()
})
.catch(err => console.log('catch', err))
.finally(() => {
app.isLoading = false
})
},
//
async initService() {
const app = this
const isShowCustomerBtn = await SettingModel.isShowCustomerBtn()
const newService = []
service.forEach(item => {
//
item.enabled = true
//
if (item.id === 'points') {
item.name = '我的' + app.setting[SettingKeyEnum.POINTS.value].points_name
}
//
if (item.id === 'dealer' && !app.setting._other.isEnabledDealer) {
item.enabled = false
}
//
if (item.id === 'contact' && !isShowCustomerBtn) {
item.enabled = false
}
//
if (item.count != undefined) {
item.count = app.todoCounts[item.id]
}
newService.push(item)
})
app.service = newService
},
//
initOrderTabbar() {
const app = this
const newOrderNavbar = []
orderNavbar.forEach(item => {
if (item.count != undefined) {
item.count = app.todoCounts[item.id]
}
newOrderNavbar.push(item)
})
app.orderNavbar = newOrderNavbar
},
//
getSetting() {
const app = this
return new Promise((resolve, reject) => {
SettingModel.data()
.then(setting => {
app.setting = setting
resolve(setting)
})
.catch(reject)
})
},
//
getUserInfo() {
const app = this
return new Promise((resolve, reject) => {
!app.isLogin ? resolve(null) : UserApi.info({}, { load: app.isFirstload })
.then(result => {
app.userInfo = result.data.userInfo
resolve(app.userInfo)
})
.catch(err => {
if (err.result && err.result.status == 401) {
app.isLogin = false
resolve(null)
} else {
reject(err)
}
})
})
},
//
getUserAssets() {
const app = this
return new Promise((resolve, reject) => {
!app.isLogin ? resolve(null) : UserApi.assets({}, { load: app.isFirstload })
.then(result => {
app.assets = result.data.assets
resolve(app.assets)
})
.catch(err => {
if (err.result && err.result.status == 401) {
app.isLogin = false
resolve(null)
} else {
reject(err)
}
})
})
},
//
getTodoCounts() {
const app = this
return new Promise((resolve, reject) => {
!app.isLogin ? resolve(null) : OrderApi.todoCounts({}, { load: app.isFirstload })
.then(result => {
app.todoCounts = result.data.counts
resolve(app.todoCounts)
})
.catch(err => {
if (err.result && err.result.status == 401) {
app.isLogin = false
resolve(null)
} else {
reject(err)
}
})
})
},
//
handleLogin() {
!this.isLogin && this.$navTo('pages/login/index')
},
//
handleBindMobile() {
this.$navTo('pages/user/bind/index')
},
//
handlePersonal() {
this.$navTo('pages/user/personal/index')
},
// 退
handleLogout() {
const app = this
uni.showModal({
title: '友情提示',
content: '您确定要退出登录吗?',
success(res) {
if (res.confirm) {
store.dispatch('Logout', {})
.then(result => app.onRefreshPage())
}
}
})
},
//
onTargetWallet() {
this.$navTo('pages/wallet/index')
},
//
onTargetOrder(item) {
this.$navTo('pages/order/index', { dataType: item.id })
},
//
onTargetPoints() {
this.$navTo('pages/points/log')
},
//
onTargetMyCoupon() {
this.$navTo('pages/my-coupon/index')
},
//
handleService({ url }) {
this.$navTo(url)
},
// 线
handleContact() {
//
const setting = this.setting[SettingKeyEnum.CUSTOMER.value]
//
if (setting.provider == 'wxqykf') {
if (!setting.config.wxqykf.url || !setting.config.wxqykf.corpId) {
this.$toast('客服链接和企业ID不能为空')
return
}
// #ifdef H5
window.open(setting.config.wxqykf.url)
// #endif
// #ifdef MP-WEIXIN
wx.openCustomerServiceChat({
extInfo: { url: setting.config.wxqykf.url },
corpId: setting.config.wxqykf.corpId,
success(res) {}
})
// #endif
}
}
},
/**
* 下拉刷新
*/
onPullDownRefresh() {
//
this.getPageData(() => {
uni.stopPullDownRefresh()
})
},
}
</script>
<style lang="scss" scoped>
.container {
padding-bottom: 60rpx;
}
//
.main-header {
// background-color: #FBF7EF;
position: relative;
width: 100%;
height: 280rpx;
background-size: 100% 100%;
overflow: hidden;
display: flex;
align-items: center;
padding-left: 30rpx;
.bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.user-info {
display: flex;
height: 100rpx;
z-index: 1;
.user-content {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 30rpx;
color: #c59a46;
.nick-name {
font-size: 34rpx;
font-weight: bold;
max-width: 270rpx;
}
.mobile {
margin-top: 15rpx;
font-size: 28rpx;
}
.user-grade {
align-self: baseline;
display: flex;
align-items: center;
background: #3c3c3c;
margin-top: 12rpx;
border-radius: 10rpx;
padding: 4rpx 12rpx;
.user-grade_icon .image {
display: block;
width: 32rpx;
height: 32rpx;
}
.user-grade_name {
margin-left: 5rpx;
font-size: 26rpx;
color: #EEE0C3;
}
}
.login-tips {
margin-top: 12rpx;
font-size: 30rpx;
}
}
}
}
//
.item-badge {
position: absolute;
top: 0;
right: 55rpx;
// background: $main-bg;
background: #fa2209;
color: #fff;
border-radius: 100%;
min-width: 38rpx;
height: 38rpx;
display: flex;
justify-content: center;
align-items: center;
padding: 1rpx;
font-size: 24rpx;
}
//
.my-asset {
display: flex;
background: #fff;
padding: 40rpx 0;
.asset-right {
width: 200rpx;
border-left: 1rpx solid #eee;
}
.asset-right-item {
text-align: center;
color: #545454;
.item-icon {
font-size: 44rpx;
}
.item-name {
margin-top: 14rpx;
font-size: 28rpx;
}
}
.asset-left-item {
max-width: 183rpx;
text-align: center;
color: #666;
padding: 0 16rpx;
.item-value {
font-size: 34rpx;
color: $main-bg;
}
.item-name {
margin-top: 14rpx;
font-size: 28rpx;
}
}
}
//
.order-navbar {
display: flex;
margin: 20rpx auto 20rpx auto;
padding: 20rpx 0 26rpx 0;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
font-size: 30rpx;
border-radius: 5rpx;
background: #fff;
&-item {
position: relative;
width: 25%;
.item-icon {
text-align: center;
margin: 0 auto;
padding: 10rpx 0;
color: #545454;
font-size: 44rpx;
}
.item-name {
font-size: 28rpx;
color: #545454;
text-align: center;
margin-right: 10rpx;
}
}
}
//
.my-service {
margin: 22rpx auto 22rpx auto;
padding: 22rpx 0;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
border-radius: 5rpx;
background: #fff;
.service-title {
padding-left: 24rpx;
margin-bottom: 20rpx;
font-size: 30rpx;
}
.service-content {
margin-bottom: -20rpx;
.service-item {
position: relative;
width: 25%;
float: left;
margin-bottom: 30rpx;
.item-icon {
text-align: center;
margin: 0 auto;
padding: 14rpx 0;
color: $main-bg;
font-size: 44rpx;
}
.item-name {
font-size: 28rpx;
color: #545454;
text-align: center;
}
}
}
}
// 退
.my-logout {
display: flex;
justify-content: center;
margin-top: 50rpx;
.logout-btn {
width: 60%;
margin: 0 auto;
font-size: 28rpx;
color: #616161;
border-radius: 20rpx;
border: 1px solid #dcdcdc;
padding: 16rpx 0;
text-align: center;
}
}
// 1
.my-mobile {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 40rpx;
background: #FCEBD1;
.info {
color: #cd8c0c;
font-size: 28rpx;
}
.btn-bind {
padding: 8rpx 24rpx;
background-color: #EAB766;
color: #fff;
border-radius: 30rpx;
font-size: 26rpx;
text-align: center;
}
}
// 2
.my-mobile2 {
display: flex;
justify-content: space-between;
align-items: center;
margin: 20rpx auto 20rpx auto;
padding: 12rpx 40rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
font-size: 30rpx;
border-radius: 5rpx;
background: #fff;
.info {
// color: #cd8c0c;
font-size: 26rpx;
}
.btn-bind {
padding: 8rpx 24rpx;
background-color: #EAB766;
color: #fff;
border-radius: 30rpx;
font-size: 26rpx;
text-align: center;
}
}
</style>
+235
View File
@@ -0,0 +1,235 @@
<template>
<view class="container" :style="appThemeStyle">
<!-- 标题 -->
<view class="page-title">个人信息</view>
<!-- 表单组件 -->
<view class="form-wrapper">
<u-form :model="form" ref="uForm" label-width="140rpx">
<u-form-item label="头像">
<button class="btn-normal" open-type="chooseAvatar" @click="onClickAvatar()" @chooseavatar="onChooseAvatar">
<avatar-image :url="avatarUrl" :width="100" />
</button>
</u-form-item>
<u-form-item label="昵称" prop="nickName">
<u-input v-model="form.nickName" type="nickname" maxlength="12" placeholder="请输入昵称" @input="onInputNickName" @blur="onInputNickName" />
</u-form-item>
</u-form>
</view>
<!-- 操作按钮 -->
<view class="footer">
<view class="btn-wrapper">
<view class="btn-item btn-item-main" :class="{ disabled }" @click="handleSubmit()">保存</view>
</view>
</view>
</view>
</template>
<script>
import store from '@/store'
import AvatarImage from '@/components/avatar-image'
import * as UserApi from '@/api/user'
import * as UploadApi from '@/api/upload'
//
const rules = {
nickName: [{
required: true,
message: '请输入用户昵称',
trigger: ['blur', 'change']
}]
}
export default {
components: {
AvatarImage
},
data() {
return {
//
disabled: false,
// ()
avatarUrl: '',
// ()
tempFile: null,
//
form: {
avatarId: '',
nickName: ''
},
//
rules,
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad() {
//
this.getUserInfo()
},
// onReadyonLoad
onReady() {
this.$refs.uForm.setRules(this.rules)
},
methods: {
//
getUserInfo() {
const app = this
UserApi.info()
.then(result => {
const userInfo = result.data.userInfo
app.avatarUrl = userInfo.avatar_url
app.form.avatarId = userInfo.avatar_id
app.form.nickName = userInfo.nick_name
})
},
//
onClickAvatar() {
// #ifdef MP-WEIXIN
return
// #endif
this.chooseImage()
},
// -
// #ifdef MP-WEIXIN
onChooseAvatar({ detail }) {
const app = this
app.avatarUrl = detail.avatarUrl
app.tempFile = { path: app.avatarUrl }
},
// #endif
//
chooseImage() {
const app = this
//
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success({ tempFiles }) {
app.tempFile = tempFiles[0]
app.avatarUrl = app.tempFile.path
}
});
},
//
uploadFile() {
const app = this
return UploadApi.image([app.tempFile])
.then(fileIds => {
app.form.avatarId = fileIds[0]
app.tempFile = null
})
},
//
async handleSubmit() {
const app = this
//
if (app.disabled === true) return
app.$refs.uForm.validate(async valid => {
if (valid) {
//
app.disabled = true
//
if (app.tempFile) {
await app.uploadFile()
}
//
UserApi.personal({ form: app.form })
.then(result => {
app.$toast(result.message)
setTimeout(() => {
app.disabled = false
uni.navigateBack()
}, 1500)
})
.catch(err => app.disabled = false)
}
})
},
// ()
onInputNickName(val) {
if (val) {
this.form.nickName = val
}
}
}
}
</script>
<style>
page {
background: #f7f8fa;
}
</style>
<style lang="scss" scoped>
.container {}
.page-title {
width: 94%;
margin: 0 auto;
padding-top: 40rpx;
font-size: 28rpx;
color: rgba(69, 90, 100, 0.6);
}
.form-wrapper {
margin: 20rpx auto 20rpx auto;
padding: 0 40rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
border-radius: 16rpx;
background: #fff;
}
/* 底部操作栏 */
.footer {
margin-top: 80rpx;
.btn-wrapper {
height: 100%;
// display: flex;
// align-items: center;
padding: 0 20rpx;
}
.btn-item {
flex: 1;
font-size: 28rpx;
height: 86rpx;
color: #fff;
border-radius: 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
.btn-item-wechat {
background: #0ba90b;
margin-bottom: 26rpx;
}
.btn-item-main {
background: linear-gradient(to right, $main-bg, $main-bg2);
color: $main-text;
//
&.disabled {
opacity: 0.6;
}
}
}
</style>
+767
View File
@@ -0,0 +1,767 @@
<template>
<view>
<view class="main-header">
<image class="bg-image" :src="bgImage" mode="scaleToFill"></image>
<image src="@/static/star1.svg" class="star star1" mode="widthFix"></image>
<image src="@/static/star1.svg" class="star star2" mode="widthFix"></image>
<image src="@/static/star1.svg" class="star star3" mode="widthFix"></image>
<!-- 用户信息 -->
<view class="user-info" @click="onLogin">
<u-avatar :src="userInfo.avatar" :size="120" img-mode="aspectFill" show-level></u-avatar>
<view class="user-content">
<view class="nick-name">{{ userInfo.nickname }}</view>
<view class="login-tips" v-for="(item,index) in userInfo.roles" :key="index">
<u-tag :text="userInfo.mobile ? userInfo.mobile : item.roleName" plain size="mini" type="info"></u-tag>
<!-- <u-tag v-if="userInfo.phone" :text="userInfo.mobile" plain size="mini" type="info"></u-tag> -->
</view>
</view>
</view>
</view>
<!-- 绑定手机号 -->
<view class="my-mobile" v-if="userInfo.userId && !userInfo.phone">
<view class="info">点击绑定手机号确保账户安全</view>
<button class="btn-bind" open-type="getAuthorize" size="mini" scope="phoneNumber" @click="bindPhone">去绑定</button>
</view>
<view class="my-mobile" v-if="userInfo.userId && !userInfo.avatar">
<view class="info">点击获取头像昵称</view>
<button
open-type="getAuthorize"
scope="userInfo"
onGetAuthorize="getOpenUserInfo"
onError="handleAuthError"
size="mini"
class="btn-bind"
v-if="userInfo.username && userInfo.username !== 'www'"
@click="onUpdateAvatar"
>
去获取
</button>
<!-- <button class="btn-bind" open-type="getAuthorize" size="mini" scope="phoneNumber" @click="bindPhone">获取头像昵称</button> -->
</view>
<!-- 我的钱包 -->
<view class="my-asset">
<view class="asset-left flex-box dis-flex flex-x-around">
<view class="asset-left-item">
<view class="item-value dis-flex flex-x-center">
<text>{{ userInfo.userId ? userInfo.balance : '--' }}</text>
</view>
<view class="item-name dis-flex flex-x-center">
<text>可提现</text>
</view>
</view>
<!-- <view class="asset-left-item" style="display: flex;align-items: center;">
<u-button shape="circle" size="mini"
@click="navTo('package/dealer/withdraw/apply')">查看明细</u-button>
</view> -->
<view class="asset-left-item" style="display: flex;align-items: center;">
<u-button shape="circle" type="primary" size="mini" @click="onApply">去提现</u-button>
</view>
</view>
</view>
<view class="my-service">
<view class="service-title">我的服务</view>
<view class="service-content clearfix">
<block v-for="(item, index) in service" :key="index">
<view class="service-item" @click="handleService(item)">
<view class="item-icon">
<text class="iconfont" :class="[`icon-${item.icon}`]"></text>
</view>
<view class="item-name">{{ item.name }}</view>
<view class="item-badge" v-if="item.count && item.count > 0">
<text v-if="item.count <= 99" class="text">{{ item.count }}</text>
<text v-else class="text">99+</text>
</view>
</view>
<!-- <view v-if="item.type == 'button' && platform == 'MP-WEIXIN' && item.enabled" class="service-item">
<button class="btn-normal" :open-type="item.openType">
<view class="item-icon">
<text class="iconfont" :class="[`icon-${item.icon}`]"></text>
</view>
<view class="item-name">{{ item.name }}</view>
</button>
</view> -->
</block>
</view>
</view>
<view class="my-service">
<view class="service-title">本月收入</view>
<view class="service-content clearfix">
<view class="service-info">
<view class="item">
<view class="desc">已到账收入</view>
<!-- <u-icon name="question"></u-icon> -->
<view><text class="money">0.00</text></view>
</view>
<view class="item">
<view class="desc">待结算收入</view>
<view><text class="money">0.00</text></view>
</view>
</view>
</view>
</view>
<view class="my-service" @click="navTo('pages/dealer/team')">
<view class="service-title">我的邀请</view>
<view class="service-content clearfix">
<view class="service-info">
<view class="item">
<view class="desc">接受邀请</view>
<view><text class="money">0</text></view>
</view>
<view class="item">
<view class="desc">邀请成功</view>
<view><text class="money">0</text></view>
</view>
</view>
</view>
</view>
<!-- 绑定手机号 (第2种样式) -->
<!-- <view class="my-mobile2" @click="handleBindMobile()">
<view class="info">点击绑定手机号确保账户安全</view>
<view class="btn-bind">去绑定</view>
</view> -->
<!-- 退出登录 -->
<!-- <view v-if="isLogin" class="my-logout">
<view class="logout-btn" @click="handleLogout()">
<text>退出登录</text>
</view>
</view> -->
<u-toast ref="uToast" />
</view>
</template>
<script>
import store from '@/store/index.js'
import { getUser, updateUser } from '@/websoft/api/user.js'
import { getAuthCode, getPhoneNumber, login, register } from '@/websoft/api/login.js'
import { tenantId, roleId, username, password, appId } from '@/config.js';
import { ACCESS_TOKEN, USER_ID } from '@/store/mutation-types'
import storage from '@/utils/storage'
import { getMobile } from '@/utils/util.js'
import http from '@/websoft/api'
//
const orderNavbar = [
// { id: 'all', name: '', icon: 'qpdingdan' },
{
id: 'payment',
name: '待付款',
icon: 'daifukuan',
count: 0
},
{
id: 'delivery',
name: '待配送',
icon: 'daifahuo',
count: 0
},
{
id: 'received',
name: '待收货',
icon: 'daishouhuo',
count: 0
},
{
id: 'comment',
name: '待评价',
icon: 'lingquan',
count: 0
},
]
/**
* 我的服务
* id: 标识; name: 标题名称; icon: 图标; type 类型(link和button); url: 跳转的链接
*/
const service = [
{id: 'get_order',name: '订单管理',icon: 'qpdingdan',type: 'link',url: 'pages/order/index'},
{id: 'equipment',name: '设备管理',icon: 'shouhuodizhi',type: 'link',url: 'pages/user/equipment/index'},
{id: 'help',name: '使用帮助',icon: 'bangzhu',type: 'link',url: 'pages/help/index'},
{id: 'administration',name: '关于我们',icon: 'sy-yh',type: 'link',url: 'pages/help/about'},
]
export default {
data() {
return {
bgImage: 'https://file.wsdns.cn/20230313/778ccc230d644b6ebc3c768b7d45229d.png',
// #ifdef MP-ALIPAY
canIUseAuthButton: my.canIUse('button.open-type.getAuthorize'),
// #endif
//
isFirstload: true,
//
isLogin: false,
//
setting: {},
//
userInfo: {
nickname: '未登录'
},
form: {},
//
assets: {
balance: '--',
points: '--',
coupon: '--',
browse: '--',
collection: '--',
follow: '--'
},
//
service,
//
orderNavbar,
//
todoCounts: {
payment: 0,
deliver: 0,
received: 0,
notice: 10
}
}
},
onLoad() {
// navbar
uni.setNavigationBarColor({
frontColor: '#000000',
backgroundColor: '#ffffff'
})
},
onShow() {
this.getUserInfo()
},
methods: {
getUserInfo() {
const { form } = this
const app = this
getUser().then(res => {
if( res.code == 0 && res.data.username != 'www') {
console.log("获取用户信息: ",res.data);
app.form = res.data
app.userInfo = res.data
store.dispatch('setUserInfo',res.data)
app.isLogin = true
}else{
app.isLogin = false
app.handleLogout()
}
})
},
//
handleService({
url
}) {
if (url.slice(0, 4) == 'http') {
wx.openCustomerServiceChat({
extInfo: {
url: 'https://work.weixin.qq.com/kfid/kfc1693a8d29b84bc5e'
},
corpId: 'ww1c3f872ba0a39228',
success(res) {}
})
return;
}
if(!this.isLogin){
return false;
}
console.log("url: ",url);
this.$navTo(url)
},
onLogin(){
const app = this
//
if(!app.isLogin){
return this.$navTo('pages/login/login')
}
// #ifdef H5
//
return this.$navTo('pages/login/login')
// #endif
},
onUpdateAvatar() {
const { form, isLogin } = this
const app = this
// #ifdef MP-ALIPAY
console.log("// 自动获取头像昵称: ");
return false;
//
my.getOpenUserInfo({
fail: (res) => {
console.log("res1: ",res);
},
success: (res) => {
let user = JSON.parse(res.response).response // response
//
if(user && user.avatar){
form.avatar = user.avatar
form.nickname = user.nickName
updateUser(form).then(res => {
console.log("res3: ",res);
app.userInfo.avatar = user.avatar
app.userInfo.nickname = user.nickName
app.showToast('更新成功')
})
}
}
});
// #endif
},
// toast
showToast(title, duration = 2000) {
this.$refs.uToast.show({ title, duration })
},
//
bindPhone() {
const app = this
const {
form
} = this
my.getPhoneNumber({
success: (res) => {
let encryptedData = res.response;
getPhoneNumber({encryptedData}).then(response => {
console.log("授权手机号码: ",response);
const json = JSON.parse(response.data)
if(json.mobile){
//
updateUser({phone: json.mobile}).then(res => {
app.$toast('绑定成功')
app.userInfo.phone = json.mobile
})
}else{
app.bindPhone()
}
})
},
fail: (res) => {
console.log(res);
},
});
},
handleLogout(){
http.setConfig((config) => {
config.header = {};
config.header = {
AppId: appId,
tenantId: tenantId
};
return config
})
uni.clearStorage()
uni.clearStorageSync()
uni.redirectTo({
url: '/pages/login/login'
})
},
onApply(){
if(!this.isLogin){
return false;
}
this.$navTo('package/dealer/withdraw/apply')
}
}
}
</script>
<style lang="scss" scoped>
.container {
padding-bottom: 60rpx;
}
//
.main-header {
// background-color: #FBF7EF;
position: relative;
width: 100%;
height: 280rpx;
background-size: 100% 100%;
overflow: hidden;
display: flex;
align-items: center;
padding-left: 30rpx;
.bg-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.star {
position: absolute;
opacity: 0.1;
color: #fff;
}
.star1 {
width: 150rpx;
height: 150rpx;
right: 80rpx;
top: 34rpx;
}
.star2 {
width: 100rpx;
height: 100rpx;
left: 40rpx;
top: 10rpx;
}
.star3 {
width: 70rpx;
height: 70rpx;
left: 140rpx;
bottom: 34rpx;
}
.user-info {
display: flex;
height: 120rpx;
z-index: 1;
.user-content {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 30rpx;
color: #ffffff;
.nick-name {
font-size: 34rpx;
font-weight: bold;
max-width: 270rpx;
}
.mobile {
margin-top: 15rpx;
font-size: 28rpx;
}
.user-grade {
align-self: baseline;
display: flex;
align-items: center;
background: #3c3c3c;
margin-top: 12rpx;
border-radius: 10rpx;
padding: 4rpx 12rpx;
.user-grade_icon .image {
display: block;
width: 32rpx;
height: 32rpx;
}
.user-grade_name {
margin-left: 5rpx;
font-size: 26rpx;
color: #EEE0C3;
}
}
.login-tips {
margin-top: 12rpx;
font-size: 30rpx;
}
}
}
}
//
.item-badge {
position: absolute;
top: 0;
right: 55rpx;
// background: $main-bg;
background: #fa2209;
color: #fff;
border-radius: 100%;
min-width: 38rpx;
height: 38rpx;
display: flex;
justify-content: center;
align-items: center;
padding: 1rpx;
font-size: 24rpx;
}
//
.my-asset {
display: flex;
margin: 22rpx auto 22rpx auto;
padding: 22rpx 0;
width: 92%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
border-radius: 24rpx;
background: #fff;
.asset-right {
width: 170rpx;
border-left: 1rpx solid #eee;
}
.asset-right-item {
text-align: center;
color: #545454;
.item-icon {
font-size: 44rpx;
}
.item-name {
margin-top: 14rpx;
font-size: 28rpx;
}
}
.asset-left-item {
text-align: center;
color: #666;
padding: 0 42rpx;
.item-value {
font-size: 34rpx;
color: $main-bg;
}
.item-name {
margin-top: 14rpx;
font-size: 28rpx;
}
}
}
//
.order-navbar {
display: flex;
margin: 20rpx auto 20rpx auto;
padding: 0rpx 0 6rpx 0;
width: 94%;
// box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
font-size: 30rpx;
border-radius: 5rpx;
background: #fff;
&-item {
position: relative;
width: 25%;
.item-icon {
text-align: center;
margin: 0 auto;
padding: 10rpx 0;
color: #545454;
font-size: 44rpx;
}
.item-name {
font-size: 28rpx;
color: #545454;
text-align: center;
margin-right: 10rpx;
}
}
}
//
.my-service {
margin: 22rpx auto 22rpx auto;
padding: 22rpx 0;
width: 92%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
border-radius: 24rpx;
background: #fff;
.service-title {
padding-left: 24rpx;
font-weight: 500;
margin-bottom: 20rpx;
font-size: 34rpx;
}
.service-content {
.service-info {
display: flex;
justify-content: space-around;
.item {
display: flex;
flex-direction: column;
line-height: 52rpx;
color: #999999;
.desc {
color: #999999;
}
.money {
color: #3c3c3c;
font-weight: bold;
font-size: 50rpx;
}
}
}
margin-bottom: -20rpx;
.shop-service-item {
position: relative;
width: 33.33%;
float: left;
margin-bottom: 30rpx;
.item-icon {
text-align: center;
margin: 0 auto;
padding: 14rpx 0;
color: $main-bg;
font-size: 44rpx;
}
.item-name {
font-size: 28rpx;
color: #545454;
text-align: center;
}
}
.service-item {
position: relative;
width: 25%;
float: left;
margin-bottom: 30rpx;
.item-icon {
text-align: center;
margin: 0 auto;
padding: 14rpx 0;
color: $main-bg;
font-size: 44rpx;
}
.item-name {
font-size: 28rpx;
color: #545454;
text-align: center;
}
}
}
}
// 退
.my-logout {
display: flex;
justify-content: center;
margin-top: 50rpx;
.logout-btn {
width: 60%;
margin: 0 auto;
font-size: 28rpx;
color: #616161;
border-radius: 20rpx;
border: 1px solid #dcdcdc;
padding: 16rpx 0;
text-align: center;
}
}
// 1
.my-mobile {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 40rpx;
background: #FCEBD1;
.info {
color: #cd8c0c;
font-size: 28rpx;
}
.btn-bind {
list-style-type: none;
padding: 0px 16px !important;
background-color: #EAB766;
color: #fff;
border-radius: 30rpx;
font-size: 26rpx;
text-align: center;
}
}
// 2
.my-mobile2 {
display: flex;
justify-content: space-between;
align-items: center;
margin: 20rpx auto 20rpx auto;
padding: 12rpx 40rpx;
width: 94%;
box-shadow: 0 1rpx 5rpx 0px rgba(0, 0, 0, 0.05);
font-size: 30rpx;
border-radius: 5rpx;
background: #fff;
.info {
// color: #cd8c0c;
font-size: 26rpx;
}
.btn-bind {
padding: 8rpx 24rpx;
background-color: #EAB766;
color: #fff;
border-radius: 30rpx;
font-size: 26rpx;
text-align: center;
button::after{
border: none;
}
}
}
.flex-box {
display: flex;
justify-content: space-between;
.more {
padding-right: 20rpx;
font-size: 26rpx;
color: #999999;
}
}
.main-header-btn {
width: 700rpx;
position: absolute;
bottom: 0;
z-index: 9999;
clear: both;
display: flex;
color: #ffffff;
justify-content: space-around;
padding: 12rpx 0;
}
</style>
+127
View File
@@ -0,0 +1,127 @@
<template>
<view class="container">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption"
@up="upCallback">
<view class="log-list">
<view v-for="(item, index) in list.data" :key="index" class="log-item">
<view class="item-left flex-box">
<view class="rec-status">
<text>{{ item.describe }}</text>
</view>
<view class="rec-time">
<text>{{ item.create_time }}</text>
</view>
</view>
<view class="item-right">
<text>{{ item.money > 0 ? '+' : '' }}{{ item.money }}</text>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import * as LogApi from '@/api/balance/log'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
const pageSize = 15
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
//
list: getEmptyPaginateObj(),
//
upOption: {
//
auto: true,
// ; 10
page: { size: pageSize },
// 12
noMoreSize: 12,
//
empty: {
tip: '亲,暂无账单明细'
}
}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
//
app.getLogList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
//
getLogList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
LogApi.list({ page: pageNo })
.then(result => {
//
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
})
}
}
}
</script>
<style lang="scss" scoped>
page,
.container {
background: #fff;
}
.log-list {
padding: 0 30rpx;
}
.log-item {
font-size: 28rpx;
padding: 20rpx 20rpx;
line-height: 1.8;
border-bottom: 1rpx solid rgb(238, 238, 238);
display: flex;
justify-content: center;
align-items: center;
}
.rec-status {
color: #333;
.rec-time {
color: rgb(160, 160, 160);
font-size: 26rpx;
}
}
</style>
+167
View File
@@ -0,0 +1,167 @@
<template>
<view class="container" v-if="!isLoading">
<view class="space-upper">
<view class="wallet-image">
<image src="/static/wallet.png" mode="widthFix"></image>
</view>
<view class="wallet-account">
<view class="wallet-account_balance">
<text>{{ userInfo.balance }}</text>
</view>
<view class="wallet-account_lable">
<text>账户余额()</text>
</view>
</view>
</view>
<view class="space-lower">
<view v-if="setting.is_entrance" class="space-lower_item btn-recharge">
<view class="btn-submit" @click="onTargetRecharge()"> </view>
</view>
<view class="space-lower_item item-lable dis-flex flex-x-around">
<view class="lable-text" @click="onTargetRechargeOrder()">
<text>充值记录</text>
</view>
<view class="lable-text" @click="onTargetBalanceLog()">
<text>账单详情</text>
</view>
</view>
</view>
</view>
</template>
<script>
import * as UserApi from '@/api/user'
import SettingModel from '@/common/model/Setting'
import SettingKeyEnum from '@/common/enum/setting/Key'
export default {
data() {
return {
//
isLoading: true,
//
userInfo: {},
//
setting: {},
}
},
/**
* 生命周期函数--监听页面加载
*/
onShow(options) {
//
this.getPageData()
},
methods: {
//
getPageData() {
const app = this
app.isLoading = true
Promise.all([app.getUserInfo(), app.getSetting()])
.then(() => app.isLoading = false)
},
//
getUserInfo() {
const app = this
return new Promise((resolve, reject) => {
UserApi.info()
.then(result => {
app.userInfo = result.data.userInfo
resolve(app.userInfo)
})
})
},
//
getSetting() {
const app = this
return new Promise((resolve, reject) => {
SettingModel.item(SettingKeyEnum.RECHARGE.value, false)
.then(data => {
app.setting = data
resolve(data)
})
})
},
//
onTargetRecharge() {
this.$navTo('pages/wallet/recharge/index')
},
//
onTargetRechargeOrder() {
this.$navTo('pages/wallet/recharge/order')
},
//
onTargetBalanceLog() {
this.$navTo('pages/wallet/balance/log')
}
}
}
</script>
<style>
page {
background: #fff;
}
</style>
<style lang="scss" scoped>
.container {
background: #fff;
}
.space-upper {
padding: 150rpx 0;
text-align: center;
}
.wallet-image image {
width: 360rpx;
height: 261.72rpx;
}
.wallet-account {
margin-top: 20rpx;
}
.wallet-account_balance {
font-size: 56rpx;
}
.wallet-account_lable {
margin-top: 14rpx;
color: #cec1c1;
font-size: 26rpx;
}
.space-lower {
margin-top: 30rpx;
padding: 0 110rpx;
}
.btn-recharge .btn-submit {
width: 460rpx;
height: 84rpx;
margin: 0 auto;
border-radius: 50rpx;
background: #786cff;
color: white;
font-size: 30rpx;
display: flex;
justify-content: center;
align-items: center;
}
.item-lable {
margin-top: 80rpx;
font-size: 28rpx;
color: rgb(94, 94, 94);
padding: 0 100rpx;
}
</style>
+507
View File
@@ -0,0 +1,507 @@
<template>
<view class="container" v-if="personal.user_id">
<view class="account-panel dis-flex flex-y-center">
<view class="panel-lable">
<text>账户余额</text>
</view>
<view class="panel-balance flex-box">
<text>{{ personal.balance }}</text>
</view>
</view>
<view class="recharge-panel">
<view class="recharge-label">
<text>充值金额</text>
</view>
<view class="recharge-plan clearfix">
<block v-for="(item, index) in planList" :key="index">
<view class="recharge-plan_item" :class="{ active: selectedPlanId == item.plan_id }"
@click="onSelectPlan(item.plan_id)">
<view class="plan_money">
<text>{{ item.money }}</text>
</view>
<view class="plan_gift" v-if="item.gift_money > 0">
<text>{{ item.gift_money }}</text>
</view>
</view>
</block>
</view>
<!-- 手动充值输入框 -->
<view class="recharge-input" v-if="setting.is_custom == 1">
<input class="input" type="digit" placeholder="可输入自定义充值金额" v-model="inputValue" @input="onChangeMoney" />
</view>
<!-- 支付方式 -->
<view class="recharge-label m-top60">
<text>支付方式</text>
</view>
<view class="payment-method">
<view v-for="(item, index) in methods" :key="index" class="pay-item dis-flex flex-x-between"
@click="handleSelectPayType(index)">
<view class="item-left dis-flex flex-y-center">
<view class="item-left_icon" :class="[item.method]">
<text class="iconfont" :class="[PayMethodIconEnum[item.method]]"></text>
</view>
<view class="item-left_text">
<text>{{ PayMethodEnum[item.method].name }}</text>
</view>
</view>
<view class="item-right col-m" v-if="curPaymentItem && curPaymentItem.method == item.method">
<text class="iconfont icon-check"></text>
</view>
</view>
</view>
<!-- 确认按钮 -->
<view class="recharge-submit btn-submit">
<form @submit="onSubmit">
<button class="button" formType="submit" :disabled="disabled">立即充值</button>
</form>
</view>
</view>
<!-- 充值描述 -->
<view class="describe-panel">
<view class="recharge-label">
<text>充值说明</text>
</view>
<view class="content">
<text space="ensp">{{ setting.describe }}</text>
</view>
</view>
<!-- 支付确认弹窗 -->
<!-- #ifdef H5 -->
<u-modal v-if="tempUnifyData" v-model="showConfirmModal" title="支付确认" show-cancel-button confirm-text="已完成支付"
:confirm-color="appTheme.mainBg" negative-top="100" :asyncClose="true"
@confirm="onTradeQuery(tempUnifyData.outTradeNo, tempUnifyData.method)">
<view class="modal-content">
<text>请在{{ PayMethodClientNameEnum[tempUnifyData.method] }}内完成支付如果您已经支付成功请点击已完成支付按钮</text>
</view>
</u-modal>
<!-- #endif -->
</view>
</template>
<script>
import * as RechargeApi from '@/api/recharge'
import { PayMethodEnum } from '@/common/enum/payment'
import { inArray, urlEncode } from '@/utils/util'
import { Alipay, Wechat } from '@/core/payment'
//
const PayMethodIconEnum = {
[PayMethodEnum.WECHAT.value]: 'icon-wechat-pay',
[PayMethodEnum.ALIPAY.value]: 'icon-alipay',
}
//
const PayMethodClientNameEnum = {
[PayMethodEnum.WECHAT.value]: '微信',
[PayMethodEnum.ALIPAY.value]: '支付宝'
}
export default {
data() {
return {
//
isLoading: true,
//
disabled: false,
//
PayMethodEnum,
PayMethodIconEnum,
PayMethodClientNameEnum,
//
personal: { balance: '0.00' },
//
setting: {},
//
planList: [],
// platform
methods: [],
// ID
selectedPlanId: 0,
//
inputValue: '',
//
curPaymentItem: null,
//
showConfirmModal: false,
// #ifdef H5
// (, H5)
tempUnifyData: { outTradeNo: '', method: '' },
// #endif
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
//
this.getPageData()
},
methods: {
//
onSelectPlan(planId) {
this.selectedPlanId = planId
this.inputValue = ''
},
//
onChangeMoney(e) {
this.inputValue = e.target.value
this.selectedPlanId = 0
},
//
handleSelectPayType(index) {
this.curPaymentItem = this.methods[index]
},
//
getPageData() {
const app = this
app.isLoading = true
return new Promise((resolve, reject) => {
RechargeApi.center({ client: app.platform })
.then(result => {
app.setting = result.data.setting
app.personal = result.data.personal
app.planList = result.data.planList
app.methods = result.data.paymentMethods
app.isLoading = false
//
app.handleSelectPayType(0)
// #ifdef H5
//
this.performance()
// #endif
})
})
},
//
// #ifdef H5
performance() {
this.alipayPerformance()
this.wechatPerformance()
},
// H5
alipayPerformance() {
const app = this
app.tempUnifyData = Alipay.performance()
if (app.tempUnifyData) {
app.onTradeQuery(app.tempUnifyData.outTradeNo, app.tempUnifyData.method)
}
},
// H5
wechatPerformance() {
const app = this
app.tempUnifyData = Wechat.performance('recharge')
console.log('wechatPerformance', app.tempUnifyData)
if (app.tempUnifyData) {
app.showConfirmModal = true
}
},
// #endif
//
onSubmit(e) {
const app = this
//
if (!app.curPaymentItem) {
app.$toast('您还没有选择支付方式')
return
}
//
if (app.disabled) return
app.disabled = true
//
RechargeApi.submit({
planId: app.selectedPlanId,
customMoney: app.inputValue,
method: app.curPaymentItem.method,
client: app.platform,
extra: app.getExtraAsUnify(app.curPaymentItem.method)
})
.then(result => app.onSubmitCallback(result))
.finally(err => {
setTimeout(() => app.disabled = false, 10)
})
},
//
getExtraAsUnify(method) {
if (method === PayMethodEnum.ALIPAY.value) {
return Alipay.extraAsUnify()
}
if (method === PayMethodEnum.WECHAT.value) {
return Wechat.extraAsUnify()
}
return {}
},
//
onSubmitCallback(result) {
const app = this
const method = app.curPaymentItem.method
const paymentData = result.data.payment
//
if (method === PayMethodEnum.BALANCE.value) {
app.onShowSuccess(result)
}
//
if (method === PayMethodEnum.ALIPAY.value) {
console.log('paymentData', paymentData)
Alipay.payment(paymentData)
.then(res => app.onPaySuccess(res))
.catch(err => app.onPayFail(err))
}
//
if (method === PayMethodEnum.WECHAT.value) {
console.log('paymentData', paymentData)
Wechat.payment({ orderKey: 'recharge', ...paymentData })
.then(res => app.onPaySuccess(res))
.catch(err => app.onPayFail(err))
}
},
//
// apisuccess,
onPaySuccess({ res, option: { isRequireQuery, outTradeNo, method } }) {
const app = this
//
// isRequireQuerytrue
if (isRequireQuery) {
app.onTradeQuery(outTradeNo, method)
return true
}
this.onShowSuccess(res)
},
//
onShowSuccess({ message }) {
this.$toast(message || '订单支付成功')
this.onSuccessNav()
},
//
onPayFail(err) {
console.log('onPayFail', err)
const errMsg = err.message || '订单未支付'
this.$error(errMsg)
},
// :
onTradeQuery(outTradeNo, method) {
const app = this
//
//
RechargeApi.tradeQuery({ outTradeNo, method, client: app.platform })
.then(result => result.data.isPay ? app.onShowSuccess(result) : app.onPayFail(result))
.finally(() => app.showConfirmModal = false)
},
//
onSuccessNav() {
const pages = getCurrentPages()
const lastPage = pages.length < 2 ? null : pages[pages.length - 2]
const backRoutes = ['pages/wallet/index']
if (lastPage && inArray(lastPage.route, backRoutes)) {
setTimeout(() => uni.navigateBack(), 1000)
} else {
setTimeout(() => {
this.$navTo('pages/wallet/index', {}, 'redirectTo')
}, 1200)
}
},
}
}
</script>
<style lang="scss" scoped>
page,
.container {
background: #fff;
}
.container {
padding-bottom: 70rpx;
}
.m-top60 {
margin-top: 60rpx;
}
//
.account-panel {
width: 650rpx;
height: 180rpx;
margin: 50rpx auto;
padding: 0 60rpx;
box-sizing: border-box;
border-radius: 12rpx;
color: #fff;
background: linear-gradient(-125deg, #a46bff, #786cff);
box-shadow: 0 5px 22px 0 rgba(0, 0, 0, 0.26);
}
.panel-lable {
font-size: 32rpx;
}
.recharge-label {
color: rgb(51, 51, 51);
font-size: 30rpx;
margin-bottom: 25rpx;
}
.panel-balance {
text-align: right;
font-size: 46rpx;
}
.recharge-panel {
margin-top: 60rpx;
padding: 0 60rpx;
}
//
.recharge-plan {
margin-bottom: -20rpx;
.recharge-plan_item {
width: 192rpx;
padding: 15rpx 0;
float: left;
text-align: center;
color: #888;
border: 1rpx solid rgb(228, 228, 228);
border-radius: 10rpx;
margin: 0 20rpx 20rpx 0;
&:nth-child(3n + 0) {
margin-right: 0;
}
&.active {
color: #786cff;
border: 1rpx solid #786cff;
.plan_money {
color: #786cff;
}
}
}
}
.plan_money {
font-size: 32rpx;
color: rgb(82, 82, 82);
}
.plan_gift {
font-size: 25rpx;
}
.recharge-input {
margin-top: 40rpx;
.input {
border: 1rpx solid rgb(228, 228, 228);
border-radius: 10rpx;
padding: 20rpx 26rpx;
font-size: 28rpx;
}
}
//
.recharge-submit {
margin-top: 70rpx;
}
.btn-submit {
.button {
font-size: 30rpx;
background: #786cff;
border: none;
color: white;
border-radius: 50rpx;
padding: 0 120rpx;
line-height: 3;
}
.button[disabled] {
background: #a098ff;
border-color: #a098ff;
color: white;
}
}
//
.describe-panel {
margin-top: 50rpx;
padding: 0 60rpx;
.content {
font-size: 26rpx;
line-height: 1.6;
color: #888;
}
}
//
.payment-method {
.pay-item {
padding: 14rpx 0;
font-size: 26rpx;
.item-left_icon {
margin-right: 20rpx;
font-size: 44rpx;
&.wechat {
color: #00c800;
}
&.alipay {
color: #009fe8;
}
}
.item-left_text {
font-size: 30rpx;
}
.item-right {
font-size: 30rpx;
}
.user-balance {
margin-left: 20rpx;
font-size: 26rpx;
}
}
}
//
.modal-content {
padding: 40rpx 48rpx;
font-size: 30rpx;
line-height: 50rpx;
text-align: left;
color: #606266;
// height: 620rpx;
box-sizing: border-box;
}
</style>
+127
View File
@@ -0,0 +1,127 @@
<template>
<view class="container">
<mescroll-body ref="mescrollRef" :sticky="true" @init="mescrollInit" :down="{ use: false }" :up="upOption"
@up="upCallback">
<view class="log-list">
<view v-for="(item, index) in list.data" :key="index" class="log-item">
<view class="item-left flex-box">
<view class="rec-status">
<text>{{ '充值成功' }}</text>
</view>
<view class="rec-time">
<text>{{ item.pay_time }}</text>
</view>
</view>
<view class="item-right">
<text>+{{ item.actual_money }}</text>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollBody from '@/components/mescroll-uni/mescroll-body.vue'
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins'
import * as OrderApi from '@/api/recharge/order'
import { getEmptyPaginateObj, getMoreListData } from '@/core/app'
const pageSize = 15
export default {
components: {
MescrollBody
},
mixins: [MescrollMixin],
data() {
return {
//
list: getEmptyPaginateObj(),
//
upOption: {
//
auto: true,
// ; 10
page: { size: pageSize },
// 12
noMoreSize: 12,
//
empty: {
tip: '亲,暂无充值记录'
}
}
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {},
methods: {
/**
* 上拉加载的回调 (页面初始化时也会执行一次)
* 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10
* @param {Object} page
*/
upCallback(page) {
const app = this
//
app.getLogList(page.num)
.then(list => {
const curPageLen = list.data.length
const totalSize = list.data.total
app.mescroll.endBySize(curPageLen, totalSize)
})
.catch(() => app.mescroll.endErr())
},
//
getLogList(pageNo = 1) {
const app = this
return new Promise((resolve, reject) => {
OrderApi.list({ page: pageNo })
.then(result => {
//
const newList = result.data.list
app.list.data = getMoreListData(newList, app.list, pageNo)
resolve(newList)
})
})
}
}
}
</script>
<style lang="scss" scoped>
page,
.container {
background: #fff;
}
.log-list {
padding: 0 30rpx;
}
.log-item {
font-size: 28rpx;
padding: 20rpx 20rpx;
line-height: 1.8;
border-bottom: 1rpx solid rgb(238, 238, 238);
display: flex;
justify-content: center;
align-items: center;
}
.rec-status {
color: #333;
.rec-time {
color: rgb(160, 160, 160);
font-size: 26rpx;
}
}
</style>