Commit 1842bd1a by yewills

feat: 初始项目,叠加业务需求,新增的文件

parent c1dbe7b8
<template>
<view class="comment-item">
<view class="top-box">
<image
class="head-image"
:src="comment.head || 'https://static.ydlcdn.com/m/images/global/default.png'"
/>
<view class="top-right-box">
<view class="name-box">
<view class="name">
<text>{{ nickName }}</text>
<image
v-if="comment.sequence > 0"
mode="widthFix"
class="great-image"
src="//static.ydlcdn.com/m/images/zx/experts/detail/label_quality_cmt.png"
/>
</view>
<view class="create-time">
{{ $dayjs(comment.createTime).format('YYYY-MM-DD') }}
</view>
</view>
<view class="theme-box">
<text class="label">评价:</text>
<text class="value">{{ commentResultText }}</text>
<text class="label">咨询主题:</text>
<text class="value">{{ comment.categoryName }}</text>
</view>
</view>
</view>
<view class="comment-content">
<toggle-fold-text
:line="5"
:line-height="22"
:text="comment.content || ''"
>
content
</toggle-fold-text>
</view>
<view
v-if="comment.tags"
class="tag-box"
>
<text
v-for="(tag, index) in comment.tags.split(',')"
:key="index"
class="tag-item"
>
{{ tag }}
</text>
</view>
<view
v-if="comment.expertReply === 2"
class="reply-box"
>
<toggle-fold-text
:line="5"
:line-height="22"
:text="`${comment.replyName} 回复: ${comment.replycontent}`"
>
content
</toggle-fold-text>
</view>
</view>
</template>
<script>
import { ToggleFoldText } from '@/components/toggle-fold-text.vue'
export default {
name: 'CommentItem',
components: {
ToggleFoldText,
},
props: {
comment: {
type: Object,
required: true,
},
},
data() {
return {}
},
computed: {
nickName() {
if (this.comment.nickName === '匿名用户') return this.comment.nickName
return `${this.comment.nickName.slice(0, 1)}**`
},
commentResultText() {
const { type, pleased } = this.comment
if (type === 2) {
return pleased === 1 ? '满意' : '不满意'
} else if (type === 1) {
return pleased === 1 || pleased === 2 || pleased === 3 ? '满意' : '不满意'
} else {
return ''
}
},
},
mounted() {},
methods: {},
}
</script>
<style lang="less" scoped>
.comment-item {
padding: 20px 0;
.top-box {
display: flex;
align-items: center;
.head-image {
width: 36px;
height: 36px;
border-radius: 50%;
margin-right: 8px;
}
.top-right-box {
flex: 1;
.name-box {
display: flex;
align-items: center;
justify-content: space-between;
.name {
font-size: 13px;
line-height: 20px;
margin-right: 8px;
color: #9d9ea7;
display: flex;
align-items: center;
.great-image {
width: 70px;
margin-left: 4px;
}
}
.create-time {
font-size: 12px;
color: #9d9ea7;
opacity: 0.5;
}
}
.theme-box {
display: flex;
font-size: 13px;
line-height: 20px;
.label {
color: #9d9ea7;
}
.value {
color: #1c1f28;
+ .label {
margin-left: 24px;
}
}
}
}
}
.comment-content {
margin-top: 7px;
/deep/ .toggle-fold-text {
font-size: 14px;
line-height: 22px;
color: #1c1f28;
}
}
.tag-box {
margin-top: 5px;
.tag-item {
color: #999;
font-size: 12px;
font-weight: 400;
margin: 4px 16px 0 0;
}
}
.reply-box {
margin-top: 14px;
background: #f6f7f9;
padding: 10px 14px;
border-radius: 4px;
color: #1c1f28;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
+ .comment-item {
border-top: 1px solid #efeff1;
}
}
</style>
<template>
<view class="consult">
<view class="consult-item">
<view class="head-box">
<image
class="head-cover"
mode="aspectFill"
:src="item.confidedIcon"
/>
<view class="p-line-status">
<!-- 1 在线 2离线 -->
{{ lineStatus[item.confideLine] }}
</view>
<!-- 播放按钮 -->
<view
v-if="item.confideVoice"
class="player-btn"
@click.stop="playAudio(item)"
>
<img
class="player-icon"
:src="
isPlayer
? 'https://static.ydlcdn.com/mini/mini_confide/confide_icon_pause.png'
: 'https://static.ydlcdn.com/mini/mini_confide/confide_icon_music.png'
"
alt=""
/>
</view>
</view>
<view class="item-con">
<view class="h2">
<view class="item-con-title">
<text class="dib vm fs32 c-24 mr10 b">{{ item.confidedName }}</text>
<!-- 是否实习 -->
<img
v-if="item.abilityLevel === 1"
class="internship-icon"
src="https://static.ydlcdn.com/m/images/zx/experts/detail/internship.png"
alt=""
/>
</view>
</view>
<view class="p-comment">
<view class="comment">
<text class="num">{{ item.confideConnection }}</text>
<text class="c">接通率</text>
</view>
<view class="comment">
<text class="num">{{ item.listenOrderNum }}</text>
<text class="c">服务人次</text>
</view>
<view class="comment">
<text class="num">{{ item.confidePraiseScore }}</text>
<text class="c">评分</text>
</view>
</view>
<view class="tags-box dix alc">
<!-- {{ JSON.stringify(item.feature_tags) }} -->
<view
v-if="item.confidedTag && item.confidedTag.length > 0"
class="tags"
>
{{ item.confidedTag.join(' | ') }}
</view>
<view
v-if="item.confideFee"
class="way"
>
<view class="price">
<text class="num">{{ item.confideFee }}</text>
<text class="unit"></text>
<text class="time">/25分钟</text>
</view>
</view>
</view>
<view
class="chat"
:style="{ background: chat_status_color[item.chat_status] }"
>
<text
v-if="item.confideLine === 1"
class="chat-btn on-line"
></text>
<text
v-else-if="item.confideLine === 2"
class="chat-btn off-line"
>
{{ item.confideSex === 1 ? '他' : '她' }}上线
</text>
<text
v-else-if="item.confideLine === 3"
class="chat-btn on-call"
>
通话中
</text>
</view>
</view>
</view>
<!-- 倾诉语 -->
<view
v-if="item.confideContent"
class="desc"
>
{{ item.confideContent }}
</view>
</view>
</template>
<script>
export default {
props: {
item: {
type: Object,
default() {
return {}
},
},
currentDoctor: {
type: Number,
default: -1,
},
playStatus: {
type: Boolean,
default: false,
},
},
data() {
return {
lineStatus: {
1: '在线',
2: '离线',
3: '通话中',
},
isPlayer: false,
}
},
watch: {
// 播放音频倾诉师和当前倾诉师相同时,更新播放状态
currentDoctor(v) {
if (v === this.item.confidedId) {
this.isPlayer = this.playStatus
} else {
this.isPlayer = false
}
},
// 播放状态更新时若播放音频倾诉师和当前倾诉师相则更新
playStatus(v) {
if (this.currentDoctor === this.item.confidedId) {
this.isPlayer = this.playStatus
} else {
this.isPlayer = false
}
},
},
methods: {
playAudio(item) {
this.$emit('player', {
...item,
isPlayer: this.isPlayer,
})
this.isPlayer = !this.isPlayer
},
},
}
</script>
<style lang="less" scoped>
.consult {
padding: 20px 0;
border-bottom: 0.5px solid #efeff0;
.consult-item {
display: flex;
position: relative;
.head {
&-cover {
width: 90px;
height: 90px;
border-radius: 8px;
display: block;
}
&-box {
position: relative;
width: 90px;
height: 90px;
flex-shrink: 0;
.p-line-status {
position: absolute;
top: 0;
left: 0;
opacity: 0.95;
min-width: 40px;
background-color: rgba(0, 0, 0, 0.5);
font-size: 11px;
text-align: center;
color: white;
border-radius: 8px 0 8px 0;
line-height: 18px;
font-weight: 300;
}
.player-btn {
position: absolute;
bottom: 6px;
right: 6px;
display: flex;
align-items: center;
justify-content: center;
background: -webkit-gradient(linear, 0% 0%, 100% 0%, from(#ff62a5), to(#ff8960));
width: 24px;
height: 24px;
border-radius: 100%;
}
.player-icon {
width: 16px;
height: 16px;
vertical-align: middle;
}
}
}
.item-con {
position: relative;
margin-left: 12px;
flex-grow: 1;
.sex-icon {
display: inline-block;
width: 13px;
height: 13px;
vertical-align: middle;
}
.internship-icon {
display: inline-block;
width: 28px;
height: 17px;
vertical-align: middle;
}
.h2 {
display: flex;
align-items: center;
margin-bottom: 4px;
display: flex;
justify-content: space-between;
}
.lab {
border-radius: 2px;
background-color: #f6f6f7;
padding: 0 4px;
font-size: 10px;
color: #62636f;
font-weight: 300;
}
.place {
display: flex;
align-items: center;
image {
width: 12px;
height: 12px;
}
.area1 {
color: #62636f;
font-size: 11px;
font-weight: 300;
}
}
.p-personal {
margin-bottom: 4px;
line-height: 18px;
color: rgba(130, 131, 140, 100);
font-size: 13px;
overflow: hidden;
display: -webkit-box;
/*! autoprefixer: off */
-webkit-box-orient: vertical;
/* autoprefixer: on */
-webkit-line-clamp: 2;
word-break: break-all;
}
.p-comment {
display: flex;
.comment {
color: #1c1f28;
margin-right: 16px;
text-align: center;
.num {
font-size: 14px;
font-weight: 700;
display: block;
margin-bottom: 2px;
}
.c {
margin-left: 2px;
color: #82838c;
font-size: 11px;
}
}
}
}
.tags-box {
margin-top: 8px;
justify-content: space-between;
.tags {
height: 18px;
border-radius: 4px;
line-height: 17px;
color: #9d9ea9;
font-size: 11px;
white-space: nowrap;
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
}
}
.way {
display: flex;
align-items: center;
position: relative;
.price {
color: #82838c;
margin-right: 12px;
font-size: 12px;
white-space: nowrap;
.num {
font-size: 18px;
color: #ff5b06;
}
.unit {
color: #ff5b06;
margin: 0 1px;
}
}
}
.chat {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
.chat-btn {
display: block;
width: 40px;
height: 40px;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.on-line {
background: #32d296 url('https://static.ydlcdn.com/mini/mini_confide/confide_phone.png')
no-repeat center;
background-size: 24px 24px;
}
.off-line {
font-size: 11px;
line-height: 15px;
padding: 4px;
border: 0.5px solid #efeff0;
}
.on-call {
font-size: 12px;
line-height: 20px;
color: #ffffff;
background: #ff8c00;
}
}
}
.desc {
margin: 16px auto 0 auto;
font-size: 14px;
line-height: 18px;
max-width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
color: #999;
}
}
</style>
<template>
<view class="toggle-fold-text">
<view
:id="id"
class="text-content"
:style="{
'white-space': 'pre',
overflow: foldStatus ? 'hidden' : 'auto',
'text-overflow': 'ellipsis',
display: '-webkit-box',
'-webkit-line-clamp': foldStatus ? `${line}` : '',
'-webkit-box-orient': 'vertical',
}"
>
{{ text }}
</view>
<view class="fold-box">
<view
v-if="foldVisible"
class="fold-content"
@click="toggleFold"
>
<text>{{ foldStatus ? '展开' : '收起' }}</text>
<image
mode="widthFix"
:class="{
arrow: true,
reverse: !foldStatus,
}"
src="//static.ydlcdn.com/m/images/zx/experts/detail/comment-arrow-down.png"
></image>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'ToggleFoldText',
props: {
lineHeight: {
required: true,
type: Number,
},
line: {
required: true,
type: Number,
},
text: {
required: true,
type: String,
},
},
data() {
return {
// 是否是收起状态
foldStatus: false,
// 是否显示展开收起按钮
foldVisible: false,
id: `text-${+new Date()}`,
}
},
mounted() {
// 获取高度判断是否要收起
const query = uni.createSelectorQuery().in(this)
query
.select(`#${this.id}`)
.boundingClientRect(data => {
this.foldVisible = data.height > this.lineHeight * this.line
this.foldStatus = true
})
.exec()
},
methods: {
toggleFold() {
this.foldStatus = !this.foldStatus
},
},
}
</script>
<style lang="less" scoped>
.toggle-fold-text {
.fold-box {
margin-top: 4px;
display: flex;
justify-content: flex-end;
.fold-content {
width: 60px;
display: flex;
align-items: center;
font-size: 14px;
color: #1da1f2;
.arrow {
width: 12px;
margin-left: 4px;
margin-bottom: 4px;
&.reverse {
margin-top: 4px;
margin-bottom: 0;
transform: rotate(180deg);
}
}
}
}
}
</style>
<template>
<view class="box-c">
<!-- 搜索框 -->
<view class="search">
<view class="search-bd">
<image
mode="aspectFill"
class="icon-glass"
src="https://static.ydlcdn.com/weixin/image/icon_search.png"
/>
<input
v-model="searchWord"
:contenteditable="true"
placeholder-style="color:#adb4be"
type="text"
confirm-type="search"
placeholder="请输入倾诉师姓名"
focus
@confirm="handleSearch"
@input="handleChange"
/>
<view
v-if="searchWord"
class="clear"
@tap="handleClear"
>
<image
src="https://static.ydlcdn.com/weixin/image/close.png"
mode="aspectFill"
/>
</view>
</view>
<view
class="search-ft"
@tap="cancelSearch"
>
<text class="btn">取消</text>
</view>
</view>
<!-- 搜索结果 -->
<view
v-if="result.length"
class="result"
>
<view
v-for="item in result"
:key="item.id"
:data-item="item.doctor_name"
class="item"
@tap="handleSelect"
>
<image
mode="aspectFill"
class="icon-glass"
src="https://static.ydlcdn.com/weixin/image/icon_search.png"
/>
<rich-text
class="text"
:nodes="item.keyword"
></rich-text>
</view>
</view>
<!-- 搜索历史&热门搜索 -->
<div
v-else
class="recommend"
>
<div
v-if="searchHistory.length > 0"
class="history"
>
<div class="recommend-title">
<span>搜索历史</span>
<image
class="icon-delete"
src="https://static.ydlcdn.com/hlwyyCt/images/address/icon_del.png"
mode="aspectFill"
@click="deleteSearchHistory"
/>
</div>
<div class="recommend-list">
<view
v-for="item in searchHistory.slice(0, 9)"
:key="item"
:data-item="item"
class="item"
@tap="handleSelect"
>
{{ item }}
</view>
</div>
</div>
</div>
</view>
</template>
<script>
/* eslint-disable no-undef */
export default {
data() {
return {
searchWord: '',
searchHistory: [], // 搜索历史
hotSearch: [], // 热门搜索
result: [],
}
},
onLoad(options) {
// 做回显
if (options.searchWord) {
this.searchWord = options.searchWord
}
const searchHistory = uni.getStorageSync('expertSearchHistory')
if (searchHistory && searchHistory !== 'undefined') {
this.searchHistory = searchHistory
}
},
methods: {
// 获取关联词
getkeywordList() {
const keyword = this.searchWord
this.$request
.post('smart-rank/v1/search', {
filter: {
__keywords: keyword,
},
limit: 20,
options: {
search_scene_id: 'listener_suggest_search',
},
count: true,
})
.then(res => {
const { objects = [] } = res
this.result = objects.map(n => {
return {
...n,
keyword: n.doctor_name.replace(
new RegExp(keyword, 'gi'),
`<em class="keyword">${keyword}</em>`,
),
}
})
})
},
// 输入的搜索内容跳转结果页
jumpSearch(search) {
uni.reLaunch({
url: `/pages/home/home?searchWord=${search}`,
})
// 保存选择的搜索结果,用于展示搜索历史
let searchHistory = uni.getStorageSync('expertSearchHistory')
const searchWord = search
// 若搜索内容为空,不记录
if (!searchWord) {
return
}
if (searchHistory) {
const search = searchHistory.find(w => w === searchWord)
if (!search) {
searchHistory.unshift(searchWord)
}
} else {
searchHistory = []
searchHistory.unshift(searchWord)
}
uni.setStorageSync('expertSearchHistory', searchHistory)
},
// 删除搜索历史
deleteSearchHistory() {
uni.removeStorageSync('expertSearchHistory')
this.searchHistory = []
},
// 输入,防抖
handleChange(e) {
const searchWord = e.detail.value.trim()
this.searchWord = searchWord
this.timer && clearTimeout(this.timer)
this.timer = setTimeout(() => {
if (searchWord === '') {
this.result = []
} else {
this.getkeywordList()
}
}, 100)
},
// 清空
handleClear() {
this.searchWord = ''
this.result = []
},
// 取消搜索
cancelSearch() {
uni.navigateBack()
},
// 搜索
handleSearch() {
const val = this.searchWord.trim()
this.jumpSearch(val)
},
// 联想词点击
handleSelect(e) {
const item = e.currentTarget.dataset.item
this.jumpSearch(item)
},
},
}
</script>
<style lang="less" scoped>
.search {
display: flex;
align-items: center;
padding-top: 28rpx;
.search-bd {
display: flex;
align-items: center;
flex: 1;
background: #f5f6f9;
padding: 14rpx 30rpx;
padding-right: 0;
border-radius: 38rpx;
input {
margin-left: 16rpx;
width: 100%;
font-size: 28rpx;
caret-color: #1da1f2;
input::first-line {
color: #1da1f2;
}
}
.icon-glass {
min-width: 32rpx;
max-width: 32rpx;
height: 32rpx;
}
.clear {
display: flex;
align-items: center;
padding-left: 16rpx;
padding-right: 30rpx;
image {
width: 32rpx;
height: 32rpx;
}
}
}
.search-ft {
height: 72rpx;
line-height: 72rpx;
margin-left: 16rpx;
.btn {
font-size: 30rpx;
}
}
}
.result {
margin-top: 20rpx;
.item {
display: flex;
align-items: center;
padding-top: 24rpx;
padding-bottom: 24rpx;
font-size: 28rpx;
.icon-glass {
min-width: 24rpx;
max-width: 24rpx;
height: 24rpx;
}
.text {
padding-left: 8rpx;
max-width: 98%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
</style>
<style lang="less">
.result .item .keyword {
color: #1da1f2;
font-style: normal;
}
.recommend {
padding-top: 60rpx;
.history {
padding-bottom: 20rpx;
}
&-title {
font-size: 28rpx;
line-height: 40rpx;
font-weight: 700;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10rpx;
margin-bottom: 20rpx;
.icon-delete {
width: 32rpx;
height: 34rpx;
opacity: 0.6;
}
}
&-list {
display: flex;
flex-wrap: wrap;
max-height: 170rpx;
overflow: hidden;
.item {
box-sizing: border-box;
height: 68rpx;
padding: 0 20rpx;
display: inline-block;
line-height: 68rpx;
text-align: center;
color: #242424;
margin: 0 10rpx 20rpx;
font-size: 28rpx;
background: #f7f7f7;
border-radius: 8rpx;
border: 1rpx solid #f7f7f7;
max-width: 270rpx;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
&.hot-list {
max-height: 250rpx;
}
}
}
</style>
<template>
<view class="login-box">
<u-navbar
title="登录注册"
:title-style="{ 'font-weight': 'bold', 'font-size': '18px' }"
fixed
placeholder
@leftClick="handleBack"
></u-navbar>
<image
class="logo"
src="https://static.ydlcdn.com/weixin/image/ydl_consult_logo.png"
></image>
<text class="title">壹点倾诉</text>
<view class="protocol">
<image
:src="
checked
? 'https://static.ydlcdn.com/weixin/image/checked.png'
: 'https://static.ydlcdn.com/weixin/image/check.png'
"
@click="switchChecked"
></image>
<text>阅读并同意</text>
<text
class="link"
@click="handleUserProtocolClick"
>
《壹点灵用户使用协议》
</text>
<text></text>
<text
class="link"
@click="handlePrivateProtocolClick"
>
《隐私保护政策》
</text>
</view>
<view class="button-box">
<phone-login
v-if="checked"
class="login"
text="微信授权登录"
@success="handleLoginSuccess"
></phone-login>
<button
v-else
class="login"
@click="showLoginTip"
>
微信授权登录
</button>
</view>
</view>
</template>
<script>
/* eslint-disable no-undef */
import { hostPrefix, ydlH5Prefix } from '@/config.js'
import PhoneLogin from '@/components/phone-login.vue'
export default {
name: 'LoginPage',
components: {
PhoneLogin,
},
data() {
return {
checked: true, // 是否勾选协议
required: false, // 来源页面是否强制登录
}
},
onLoad(options) {
this.required = options.required === '1'
},
methods: {
// 勾选切换
switchChecked() {
this.checked = !this.checked
},
// 跳转用户协议
handleUserProtocolClick() {
uni.navigateTo({
url: `/pages/web/web?title=用户注册协议&loadUrl=${encodeURIComponent(
`${ydlH5Prefix}/SDUserProtol`,
)}`,
})
},
// 跳转隐私协议
handlePrivateProtocolClick() {
uni.navigateTo({
url: `/pages/web/web?title=隐私保护政策&loadUrl=${encodeURIComponent(
`${hostPrefix}/Protol/yinsi-m`,
)}`,
})
},
// 未勾选提示
showLoginTip() {
uni.showToast({
title: '请同意服务条款',
icon: 'none',
})
},
// 登录成功,更新store数据
async handleLoginSuccess() {
const appId = uni.getAccountInfoSync().miniProgram.appId
const res = await this.$request.get(`/mini/wx/user/${appId}/loginUser`)
this.$store.commit('user/setUserInfo', res)
uni.$emit('loginSuccess')
uni.showToast({
title: '登录成功',
duration: 1500,
icon: 'success',
mask: true,
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
},
// 导航栏返回
handleBack() {
uni.navigateBack({
delta: this.required ? 2 : 1,
})
},
},
}
</script>
<style lang="scss" scoped>
.login-box {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 10px;
height: 100%;
.logo {
width: 70px;
height: 70px;
margin-top: 70px;
}
.title {
margin-top: 20px;
line-height: 20px;
color: #0c1d31;
font-size: 20px;
font-weight: 600;
}
.protocol {
margin-top: 63px;
display: flex;
align-items: center;
flex-wrap: wrap;
font-size: 12px;
color: #495c72;
line-height: 17px;
}
.protocol image {
width: 15px;
height: 15px;
padding: 4px;
transform: translateY(-1px);
}
.protocol .link {
color: #1ea1f1;
}
.button-box {
margin-top: 14px;
width: 100%;
.login,
::v-deep .login button {
max-width: 324px;
width: 100%;
height: 48px;
line-height: 48px;
border-radius: 24px;
background-color: #1ea1f1 !important;
color: #fff;
font-size: 16px;
}
}
}
</style>
<template>
<view class="order-page">
<u-empty
v-if="initStatus && orderList.length === 0"
mode="list"
icon="https://static.ydlcdn.com/mini/mini_confide/list_empty.png"
text="暂无数据"
margin-top="40px"
text-color="rgb(96, 98, 102)"
></u-empty>
<view
v-else
class="content-box"
>
<order-item
v-for="item in orderList"
:key="item.id"
:order="item"
@cancel="handleCancel"
@pay="handlePay"
></order-item>
<view class="loadmore-box">
<u-loadmore :status="loadingStatus" />
</view>
</view>
<u-modal
:show="cancelVisible"
title="取消倾诉订单"
confirm-text="确定取消"
cancel-text="再想想"
:show-cancel-button="true"
width="72vw"
@cancel="cancelVisible = false"
@confirm="handleCancelConfirm"
>
<view class="slot-content">
<view class="logout-tip-text">{{ cancelTips }}</view>
</view>
</u-modal>
</view>
</template>
<script>
import OrderItem from '@/components/order-item.vue'
import { setTrackData } from '@/utils/util'
export default {
name: 'OrderPage',
components: {
OrderItem,
},
data() {
return {
orderList: [], // 订单列表
loadingStatus: 'loadmore', // 接口状态:loading: 加载中 loadmore: 加载更多 nomore: 没有更多
pageNo: 1,
pageSize: 10,
cancelVisible: false, // 取消订单提示弹窗
cancelTips: '', // 提示内容
order: {}, // 当前操作的订单
initStatus: false, // 初始化接口加载是否完成
listenerId: '', // 倾诉id
}
},
// 监听支付成功事件
onLoad() {
uni.$on('paySuccess', this.handlePaySuccess)
},
// 移除支付成功事件
onUnload() {
uni.$off('paySuccess', this.handlePaySuccess)
},
mounted() {
// 页面访问埋点
setTrackData({
events: [
{
event_id: 'page_visit',
event_custom_properties: {
part: 'my_listen_order',
position: '',
element: '',
},
},
],
})
// 获取订单列表
this.getOrderList()
},
methods: {
// 订单列表
async getOrderList() {
if (this.loadingStatus !== 'loadmore') return
this.loadingStatus = 'loading'
const { pageNo, pageSize } = this
const { uid, accessToken } = this.$store.state.user
try {
const { total, list } = await this.$request.get('/auth/order/listen/list', {
params: {
pageNo,
pageSize,
uid,
accessToken,
},
})
this.initStatus = true
if (pageNo === 1) {
this.orderList = [...list]
} else {
this.orderList.push(...list)
}
this.loadingStatus = this.orderList.length < total ? 'loadmore' : 'nomore'
} catch (e) {
console.log(e)
this.loadingStatus = 'nomore'
}
},
// 取消订单
handleCancel({ cancelTips, order }) {
this.cancelTips = cancelTips
this.order = order
this.cancelVisible = true
},
// 确认取消订单
async handleCancelConfirm() {
uni.showLoading({
title: '加载中',
mask: true,
icon: 'none',
})
try {
await this.$request.get('/auth/order/listen/orderCancel', {
params: {
listenOrderId: this.order.id,
},
})
this.$set(
this.orderList.find(item => item.id === this.order.id),
'lifecycle',
5,
)
uni.showToast({
title: '订单取消成功',
icon: 'none',
})
this.cancelVisible = false
} catch (e) {
console.log(e)
uni.hideLoading()
}
},
// 接收从订单组件传过来的 listenerId, 用于支付成功的回调中传参
handlePay(listenerId) {
this.listenerId = listenerId
},
// 支付成功回调
handlePaySuccess(from) {
if (from === 'orderList') {
setTimeout(() => {
uni.reLaunch({
url: `/pages/confide/confide?listenerId=${this.listenerId}&call=1`,
})
}, 1500)
}
},
},
// 下拉刷新,这里不要和 scrollView 一起使用,scrollView会限制高度,和下拉刷新冲突
async onPullDownRefresh() {
// 字段重置
this.pageNo = 1
this.loadingStatus = 'loadmore'
// 重新请求接口
await this.getOrderList()
// 停止下拉刷新
uni.stopPullDownRefresh()
},
// 上拉加载下一页
onReachBottom() {
console.log('bottom')
if (this.loadingStatus === 'loadmore') {
this.pageNo++
this.getOrderList()
}
},
}
</script>
<style lang="less" scoped>
.order-page {
.content-box {
padding: 10px 16px;
}
.loadmore-box {
margin-top: 20px;
}
}
</style>
<style>
page {
background: #f0f0f0;
}
</style>
<template>
<view class="pay-page">
<view class="order-info">
<view class="info-item">
<view class="label">倾诉服务</view>
<text class="value">{{ price }}</text>
</view>
<view class="info-item">
<view class="label">优惠</view>
<text class="value">{{ coupon > 0 ? `-¥${coupon}` : '暂无可用优惠' }}</text>
</view>
<view class="info-item">
<view class="label">可用余额</view>
<text class="value">{{ balance }}</text>
</view>
<view class="info-item">
<view class="label">还需支付</view>
<text class="value finally">{{ payAmount }}</text>
</view>
</view>
<view class="tips">
<view class="tips-content">付款保障</view>
<view class="tips-content">1.专业可信赖,国家认证团队,7*24小时在线</view>
<view class="tips-content">2.11指导,量身定制解决方案,服务客户300多万</view>
<view class="tips-content">3.安全保障,严格隐私保护,不满意100%退款</view>
</view>
<view class="footer">
<button
class="pay-button"
:loading="loading"
@click="pay"
>
确认支付
</button>
</view>
</view>
</template>
<script>
import { setTrackData } from '@/utils/util'
export default {
name: 'OrderPay',
data() {
return {
price: '0', // 原价
coupon: '0', // 优惠金额
balance: '0', // 余额
orderId: '', // 订单id
payId: '', // 支付id
loading: false,
from: '', // 来源页面
}
},
computed: {
// 实际支付金额
payAmount() {
return Math.max(this.price - this.coupon - this.balance, 0).toFixed(2)
},
},
onLoad(options) {
// 赋值
const { price, coupon, balance, orderId, payId, from } = options
this.price = price
this.coupon = coupon
this.balance = balance
this.orderId = orderId
this.payId = payId
this.from = from
// 页面访问埋点
setTrackData({
events: [
{
event_id: 'page_visit',
event_custom_properties: {
part: 'order_middle_page',
position: '',
element: '',
},
},
],
})
},
methods: {
// 调用后端接口得到支付参数
async pay() {
if (this.loading) {
return
}
setTrackData({
events: [
{
event_id: 'content_click',
event_custom_properties: {
part: 'order_middle_page',
position: 'foot_column',
element: 'confirm_payment',
content: this.orderId,
},
},
],
})
this.loading = true
const { uid, accessToken, openId } = this.$store.state.user
// 组合支付从余额里面扣除的金额
const payBalance = Math.min(Math.max(0, this.price - this.coupon), this.balance)
// 支付方式,1: 余额 5: 微信 7: 微信 + 余额
const payType = this.payAmount > 0 ? (this.balance > 0 ? 7 : 5) : 1
try {
const wxUrl = encodeURIComponent('http://m.ydl.com?backPayId=' + this.payId)
const res = await this.$request.post(
'/auth/cashierV2/unityPay',
{
payId: this.payId,
orderId: this.orderId,
payAmount: this.payAmount,
openId: openId,
payType,
payBalance,
payChannel: 'WX_MINI_APP',
quitUrl: wxUrl,
returnUrl: wxUrl,
uid: uid || '',
accessToken: accessToken || '',
},
{
raw: true,
},
)
this.loading = false
if (+res.code === 200) {
// 金额为0,直接调用成功方法
if (+this.payAmount === 0) {
this.handleSuccess()
return
}
// 唤起微信支付
this.requestPayment(res.data.content)
} else if (res.msg === '201 商户订单号重复') {
// 用户余额发生变化导致实际支付金额发生变化,需要取消订单后重新下单,后期等后端优化
uni.showModal({
title: '提示',
content: '金额发生变化,请取消订单后重新下单',
confirmText: '好的',
showCancel: false,
})
} else {
uni.showToast({
title: res.msg || res.errMsg,
icon: 'none',
})
}
} catch (e) {
this.loading = false
}
},
// 微信支付
requestPayment(data) {
const { nonceStr, signType, timeStamp, paySign } = data
uni.requestPayment({
timeStamp,
nonceStr,
signType,
package: data.package,
paySign: paySign,
success: () => {
this.handleSuccess()
},
fail: () => {
uni.showToast({
title: '支付失败',
icon: 'none',
})
},
})
},
handleSuccess() {
// 支付成功埋点
setTrackData({
events: [
{
event_id: 'payment_succ_page_visit',
event_custom_properties: {
part: 'order_middle_page',
position: '',
element: '',
order_id: this.orderId,
},
},
],
})
// 触发支付成功事件
uni.$emit('paySuccess', this.from)
uni.showToast({
title: '支付成功',
duration: 1500,
})
},
},
}
</script>
<style lang="less" scoped>
.pay-page {
padding: 10px 0;
height: 100%;
background: #f8f8f8;
overflow: auto;
}
.order-info {
margin: 0 16px;
border-radius: 8px;
background: #fff;
padding: 10px 16px;
.info-item {
display: flex;
align-items: center;
justify-content: space-between;
color: #666;
font-size: 13px;
line-height: 30px;
.value {
&.finally {
font-size: 18px;
color: red;
}
}
}
}
.tips {
margin: 16px;
.tips-content {
font-size: 13px;
line-height: 18px;
font-weight: 500;
color: #999;
margin-bottom: 8px;
}
}
.footer {
height: 99px;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
box-sizing: border-box;
padding-top: 19px;
box-shadow: 0px -1px 0px 0px rgba(235, 235, 235, 1);
.pay-button {
background-color: #ff6b5d;
border: none;
color: #fff;
font-size: 17px;
font-weight: 500;
border-radius: 8px;
margin: 0 16px;
&:after {
border: none;
}
}
}
</style>
<template>
<web-view
v-if="!!loadUrl"
:src="loadUrl"
></web-view>
</template>
<script>
export default {
name: 'WebPage',
data() {
return {
loadUrl: '',
options: {},
}
},
onLoad(options) {
// 监听登录成功事件
uni.$on('loginSuccess', this.handleLoginSuccess)
this.options = options || {}
// 设置标题
if (this.options.title) {
uni.setNavigationBarTitle({
title: this.options.title,
})
}
},
// 移除登录成功事件
onUnload() {
uni.$off('loginSuccess', this.handleLoginSuccess)
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
// 判断即将进入的页面是否需要登录
if (this.options.login && !this.$store.getters.isLogin) {
uni.navigateTo({
url: '/pages/login/login?required=1',
})
} else {
// 配置 webview 的 url 地址
let url = decodeURIComponent(this.options.loadUrl || this.options.load_url)
const query = {
uid: this.$store.state.user.uid || '',
accessToken: this.$store.state.user.accessToken || '',
appId: uni.getAccountInfoSync().miniProgram.appId,
openId: this.$store.state.user.openId,
isFromMin: 'weapp',
miniType: 'confide',
timestamp: +new Date(),
}
// 更新 url 中的参数
Object.keys(query).forEach(prop => {
url = this.changeURLArg(url, prop, query[prop])
})
this.loadUrl = url
console.log(this.loadUrl)
}
},
// 更新 url 中的参数
changeURLArg(url, arg, value) {
const pattern = arg + '=([^&]*)'
const replaceText = arg + '=' + value
if (url.match(pattern)) {
const exp = new RegExp(`(${arg}=)([^&]*)`, 'gi')
return url.replace(exp, replaceText)
} else {
if (url.match('[\?]')) {
return url + '&' + replaceText
} else {
return url + '?' + replaceText
}
}
},
// 登录成功后重新初始化
handleLoginSuccess() {
this.init()
},
},
}
</script>
export default {
split: function (str, reg) {
return typeof str !== 'string' ? [] : str.trim().length === 0 ? [] : str.split(reg)
},
toFixed: function (str, num) {
return parseFloat(str).toFixed(num)
},
floor: function (num) {
return Math.floor(parseFloat(num))
},
isIncludes: function (arr, str) {
return arr.indexOf(str) > -1
},
// 截取字符
geSliceStr: function (v) {
return v.length > 5 ? v.slice(0, 5) + '...' : v
},
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment