Commit dbf68494 authored by jyx's avatar jyx

代码优化

parent abb53a01
......@@ -41,17 +41,10 @@ function gotoBookCoverPage(bookId) {
})
}
// 阅读历史
function gotoReadHistoryPage() {
uni.navigateTo({
url: `/page-subs/sub_A/read-history/read-history`
})
}
// vip申请
function gotoVIPApplyPage() {
uni.navigateTo({
url: `/page-subs/sub_B/vip-apply/vip-apply`,
url: `/pagesA//vipPay/vipPay`,
})
}
......@@ -67,50 +60,13 @@ function gotoUserEditPage(userInfo) {
})
}
// 消息
function gotoMessagePage(type = ENUM_MESSAGE_PAGE_TYPE.SYS.value) {
uni.navigateTo({
url: `/page-subs/sub_B/message/message`,
success: (res) => {
res.eventChannel.emit("openMessagePage", {
type
})
}
})
}
// 阅读喜好
function gotoReadPreferencePage() {
uni.navigateTo({
url: `/page-subs/sub_B/read-preference/read-preference`,
})
}
// 书豆明细
function gotoBookBeanDetailPage() {
uni.navigateTo({
url: `/page-subs/sub_B/book-bean-detail/book-bean-detail`
})
}
// 书豆充值
function gotoBookBeanRechargePage() {
uni.navigateTo({
url: `/page-subs/sub_B/book-bean-recharge/book-bean-recharge`
})
}
module.exports = {
/** sub_A */
gotoBookSearchPage,
gotoBookContentPage,
gotoBookCoverPage,
gotoReadHistoryPage,
/** sub_B */
gotoVIPApplyPage,
gotoUserEditPage,
gotoMessagePage,
gotoReadPreferencePage,
gotoBookBeanDetailPage,
gotoBookBeanRechargePage
}
\ No newline at end of file
......@@ -148,7 +148,7 @@ function postPhone(data, callback) {
function requestToken(data, callback) {
let url = `${config["BASE_URL"]}/user/ttLogin`;
let header = {
pkgname: PAKEAGE_NAME,
pkgName: PAKEAGE_NAME,
token: ``
}
uni.request({
......@@ -168,7 +168,6 @@ function requestToken(data, callback) {
// 请求用户数据
function requestUserInfo(callback) {
let url = `${config['BASE_URL']}/user/baseMsg`;
let header = {}
let uniChannel = 'wechat';
......@@ -181,8 +180,8 @@ function requestUserInfo(callback) {
// #endif
Object.assign(header, {
token: uni.getStorageSync('token'),
pkgName: app.globalData.pkgName,
token: readToken(),
pkgName: PAKEAGE_NAME,
proChannel: uniChannel
})
......
<template>
<view>
<uni-popup type="center" ref="adPop" :maskClick="false" :isMaskClick="false">
<view class="container">
<!-- <view class="ad-view">
<ad :unit-id="adUnitId" @load="onload" @close="onclose" @error="onerror"></ad>
</view> -->
<view class="content">
<text class="title">恭喜你,获得免费看剧名额</text>
<text class="button" @click="handleClose">看视频免费解锁1集</text>
<text class="downtext">{{countDown}}s后自动进入广告,阅读完成解锁第{{vedioIndex+1}}集剧情</text>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
EXPRESS_ID
} from "@/utils/adConstant.js"
export default {
name: 'adPopup',
props: {
show: {
type: Boolean,
default: false,
},
vedioIndex: {
type: [Number, String],
default: 0
}
},
data() {
return {
adUnitId: EXPRESS_ID,
countDown: 3,
};
},
methods: {
handleShow() {
this.$refs.adPop.open('center');
this.startCountdown()
},
startCountdown() {
let that = this;
var countDownSeconds = 3
that.countDown = countDownSeconds
const timer = setInterval(() => {
if (countDownSeconds > 0) {
that.countDown = countDownSeconds
countDownSeconds--
} else {
clearInterval(timer)
that.handleClose()
}
}, 1000)
},
handleClose() {
this.$refs.adPop.close('center');
this.$emit('close');
},
onload(e) {
console.log("onload");
},
onclose(e) {
console.log("onclose: " + e.detail);
},
onerror(e) {
console.log("onerror: " + e.detail.errCode + " message:: " + e.detail.errMsg);
}
},
watch: {
show: {
handler: function(newVal, oldVal) {
if (newVal) {
this.handleShow();
}
},
immediate: true
}
}
};
</script>
<style lang="scss">
.container {
width: 600rpx;
height: 800rpx;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
}
.ad-view {
width: 600rpx;
border-radius: 20rpx;
// background-color: white;
margin-bottom: 10px;
}
.content {
border-radius: 20rpx;
background-color: white;
display: flex;
width: 600rpx;
padding: 30rpx 0;
flex-direction: column;
align-items: center;
.title {
color: black;
font-size: 40rpx;
font-weight: bold;
margin-bottom: 30rpx;
}
.button {
margin: 20rpx 20rpx;
border-radius: 10rpx;
background-color: orange;
color: white;
font-size: 36rpx;
}
.downtext{
color: darkgray;
font-size: 26rpx;
}
}
</style>
\ No newline at end of file
<template>
<view>
<view :class="'alert-mask ' + (showClone ? 'alert-mask-active' : '')" @click="handleClose" @click.stop.prevent=""></view>
<view :class="'alert ' + (showClone ? 'alert-active' : '')">
>
<icons class="alert-close" icon="close" @click="handleClose" color="white"></icons>
<image v-if="isMarkImg" class="alert-mask-img" src="../../static/alert-mask.png" mode="scaleToFill"></image>
<view class="alert-content">
<image class="alert-title" :src="title" mode="scaleToFill"></image>
<view class="alert-wrap"><slot></slot></view>
<button v-if="isConfirmShow" class="alert-btn" :style="'color:' + color" @click="handleSuccess">{{ confirmText }}</button>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
},
show: {
type: Boolean,
default: false
},
confirmText: {
type: String,
default: '确定'
},
isConfirmShow: {
type: Boolean,
default: true
},
isMarkImg: {
type: Boolean,
default: true
},
color: {
type: String,
default: '#008cfb'
}
},
data() {
return {
showClone: false
};
},
methods: {
handleSuccess() {
this.handleClose();
this.$emit('success');
},
handleClose() {
this.showClone = false;
this.$emit('close');
}
},
watch: {
show: {
handler: function(newVal, oldVal) {
console.log(newVal);
this.showClone = newVal;
},
immediate: true
}
}
};
</script>
<style lang="scss">
.alert {
position: fixed;
z-index: 1002;
left: 75rpx;
right: 75rpx;
top: 50%;
padding: 0;
display: none;
transform: translate(0, -50%);
backface-visibility: hidden;
&-content {
border-radius: 16rpx;
overflow: hidden;
background-color: #fff;
}
&-mask {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 999;
background-color: rgba(0, 0, 0, 0.8);
opacity: 0;
transform: scale3d(1, 1, 0);
transition: all 0.3s;
&-active {
opacity: 1;
transform: scale3d(1, 1, 1);
}
&-img {
position: absolute;
z-index: 5;
width: 110%;
height: 110%;
left: -5%;
top: -5%;
}
}
&-active {
display: block;
}
&-close {
position: absolute;
top: 24rpx;
padding: 30rpx;
right: 0;
z-index: 10;
}
&-btn {
background-color: white;
width: 100% !important;
margin: 0 !important;
text-align: center;
padding: 0;
font-size: 32rpx;
line-height: 110rpx;
background: transparent;
outline: none;
z-index: 10;
&.button-hover {
background: #ececec !important;
}
}
&-title {
width: 100%;
height: 138rpx;
}
&-wrap {
padding: 45rpx 66rpx;
border-bottom: 1rpx solid #ddd;
}
}
</style>
<template>
<view class="divider flex"><slot></slot></view>
</template>
<script>
export default {
name: 'Divider'
};
</script>
<style lang="scss">
@import '@/scss/uni.scss';
.divider {
font-size: 24rpx;
color: #868686;
&::before,
&::after {
content: '';
background-color: #868686;
height: 1rpx;
flex: 1;
}
&::before {
margin-right: 28rpx;
}
&::after {
margin-left: 28rpx;
}
}
</style>
<template>
<view class="number-box">
<view @click="_calcValue('minus')" class="number-box-btns" :class="{ 'number-box-disabled': inputValue <= min || disabled }">
<icons icon="minus" :color="inputValue <= min || disabled ? '#868686' : '#299FEF'" size="40" />
</view>
<input :disabled="disabled" @focus="_onFocus" @blur="_onBlur" class="number-box-input" type="number" v-model="inputValue" />
<view @click="_calcValue('plus')" class="number-box-btns"><icons icon="plus" :color="inputValue >= max || disabled ? '#868686' : '#299FEF'" size="40" /></view>
</view>
</template>
<script>
/**
* NumberBox 数字输入框
* @description 带加减按钮的数字输入框
* @tutorial https://ext.dcloud.net.cn/plugin?id=31
* @property {Number} value 输入框当前值
* @property {Number} min 最小值
* @property {Number} max 最大值
* @property {Number} step 每次点击改变的间隔大小
* @property {String} color 字体颜色(前景色)
* @property {Boolean} disabled = [true|false] 是否为禁用状态
* @event {Function} change 输入框值改变时触发的事件,参数为输入框当前的 value
* @event {Function} focus 输入框聚焦时触发的事件,参数为 event 对象
* @event {Function} blur 输入框失焦时触发的事件,参数为 event 对象
*/
export default {
name: 'NumberBox',
emits: ['change', 'input', 'update:modelValue', 'blur', 'focus'],
props: {
value: {
type: [Number, String],
default: 1
},
modelValue: {
type: [Number, String],
default: 1
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
step: {
type: Number,
default: 1
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
inputValue: 0
};
},
watch: {
value(val) {
this.inputValue = +val;
},
modelValue(val) {
this.inputValue = +val;
}
},
created() {
if (this.value === 1) {
this.inputValue = +this.modelValue;
}
if (this.modelValue === 1) {
this.inputValue = +this.value;
}
},
methods: {
_calcValue(type) {
if (this.disabled) {
return;
}
const scale = this._getDecimalScale();
let value = this.inputValue * scale;
let step = this.step * scale;
if (type === 'minus') {
value -= step;
if (value < this.min * scale) {
return;
}
if (value > this.max * scale) {
value = this.max * scale;
}
}
if (type === 'plus') {
value += step;
if (value > this.max * scale) {
return;
}
if (value < this.min * scale) {
value = this.min * scale;
}
}
this.inputValue = (value / scale).toFixed(String(scale).length - 1);
this.$emit('change', +this.inputValue);
// TODO vue2 兼容
this.$emit('input', +this.inputValue);
// TODO vue3 兼容
this.$emit('update:modelValue', +this.inputValue);
},
_getDecimalScale() {
let scale = 1;
// 浮点型
if (~~this.step !== this.step) {
scale = Math.pow(10, String(this.step).split('.')[1].length);
}
return scale;
},
_onBlur(event) {
this.$emit('blur', event);
let value = event.detail.value;
if (!value) {
// this.inputValue = 0;
return;
}
value = +value;
if (value > this.max) {
value = this.max;
} else if (value < this.min) {
value = this.min;
}
const scale = this._getDecimalScale();
this.inputValue = value.toFixed(String(scale).length - 1);
this.$emit('change', +this.inputValue);
this.$emit('input', +this.inputValue);
},
_onFocus(event) {
this.$emit('focus', event);
}
}
};
</script>
<style lang="scss">
.number-box {
display: inline-flex;
align-items: center;
margin-right: -16rpx;
&-btns {
line-height: 1;
vertical-align: bottom;
display: inline-block;
padding: 16rpx;
cursor: pointer;
}
&-input {
margin: 0 4rpx;
width: 50rpx;
text-align: center;
color: black;
}
}
</style>
<template>
<view>
<block v-for="(item, index) in list" :key="index">
<view @click="jump(item)">
<view class="post-item">
<view class="post-item-top-user">
<view @click.stop="toUcenter(item.uid)">
<u-avatar class="avatar" :src="item.userInfo.avatarUrl"
:show-level="item.userInfo.type == 1" level-bg-color="#8072f3"></u-avatar>
</view>
<view class="center">
<view style="display: flex;align-items: center;">
<text v-if="item.userInfo.type == 1" class="official">官方</text>
<text v-if="item.userInfo.vip_expire_time > timestamp" style="color: red;"
class="username">{{ item.userInfo.nickName.substring(0, 12) }}</text>
<text v-else class="username">{{ item.userInfo.nickName.substring(0, 12) }}</text>
</view>
<view>
<text class="time">{{ item.createTime | timeFrom }}</text>
</view>
</view>
<view v-if="item.type==0&&(index<3)" v-for="index2 in (3-index)>0?(3-index):0">
<image style="width: 30rpx;margin-right: 10rpx;" mode="widthFix"
src="../../static/moments/ic_fire.png"></image>
</view>
</view>
<view class="post-content">
<rich-text class="post-text" :nodes="item.content"></rich-text>
<!-- 帖子类型 -->
<block v-if="(item.mediaType==0||item.mediaType==1)&&item.media!=null">
<!--一张图片-->
<block v-if="item.media.length==1">
<image :lazy-load="true" mode="aspectFill" class="img-style-1" :src="item.media[0]"
@tap.stop="previewImage(item.media[0], item.media, item.integral, item.id)"></image>
</block>
<!--二张图片-->
<block v-if="item.media.length==2">
<view class="img-style-2">
<image :lazy-load="true" v-for="(mediaItem, index2) in item.media" :key="index2"
@tap.stop="previewImage(mediaItem, item.media, item.integral, item.id)"
mode="aspectFill" :src="mediaItem"></image>
</view>
</block>
<!--三张以上图片-->
<block v-if="item.media.length>2">
<view class="img-style-3">
<image :lazy-load="true" v-for="(mediaItem, index2) in item.media" :key="index2"
@tap.stop="previewImage(mediaItem, item.media, item.integral, item.id)"
mode="aspectFill" :src="mediaItem"></image>
</view>
</block>
</block>
<!-- 视频 -->
<view class="video-wrap" v-if="item.mediaType==2&&item.media.length>0">
<image @tap.stop="previewMedia(item.media[0],item.userLike,item.likes,item.id)" class="icon"
src="../../static/moments/play.png"></image>
<image mode="aspectFill" :src="item.media[0] + '?x-oss-process=video/snapshot,t_0,f_jpg'">
</image>
</view>
</view>
<!-- 底部 -->
<view class="p-footer">
<view class="p-item ">
<button @click.stop="showShares(index)" class="u-reset-button" open-type="share">
<u-icon name="share-square" size="24"></u-icon>
<text class="count">转发</text>
</button>
</view>
<view v-show="item.userLike==1" class="p-item" @click.stop="cancelCollection(item.id, index)">
<u-icon name="thumb-up-fill" size="24" color="#cc0000"></u-icon>
<text class="count">{{ item.likes }}</text>
</view>
<view v-show="item.userLike==0" class="p-item" @click.stop="addCollection(item.id, index)">
<u-icon name="thumb-up" size="24"></u-icon>
<text class="count">{{item.likes>0?item.likes:'点赞'}}</text>
</view>
</view>
</view>
</view>
</block>
</view>
</template>
<script>
import common from '@/mixins/common';
const app = getApp();
export default {
name: 'post-list',
mixins: [common],
props: {
list: Array,
uid: Number,
},
data() {
return {
currentIndex: 0,
path: '/pages/loading',
};
},
computed: {
timestamp() {
return Date.parse(new Date()) / 1000;
}
},
methods: {
showShares(index) {
this.currentIndex = index
},
cancelCollection(id, index) {
this.post({
url: '/comment/like',
data: {
commentId: id,
type: 0,
session: app.globalData.token,
},
showLoading: false,
success: ({
data
}) => {
this.list[index].userLike = 0
if (this.list[index].likes != 0) {
this.list[index].likes -= 1
}
}
})
},
addCollection(id, index) {
this.post({
url: '/comment/like',
data: {
commentId: id,
type: 1,
session: app.globalData.token,
},
showLoading: false,
success: ({
data
}) => {
this.list[index].userLike = 1
this.list[index].likes += 1
}
})
},
previewImage(url, urls, integral, post_id) {
let that = this;
uni.previewImage({
current: url, // 当前显示图片的http链接
urls: urls // 需要预览的图片http链接列表
});
},
previewMedia(url, userLike, likeCount, commentId) {
console.log(url + "--" + userLike)
uni.navigateTo({
url: '/pagesB/moments/video-detail?media=' + url +
"&userLike=" + userLike +
"&likes=" + likeCount +
"&commentId=" + commentId
});
},
jump(val) {
},
}
};
</script>
<style lang="scss" scoped>
// 分享弹窗
.share-wrap {
display: flex;
padding: 30rpx;
width: 50%;
margin: 0 auto;
.share-item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&:nth-child(1) {
margin-right: auto;
}
image {
width: 100rpx;
height: 100rpx;
}
text {
font-size: 24rpx;
margin-top: 20rpx;
}
}
}
.post-item {
border-radius: 20rpx;
background: #fff;
padding: 20rpx;
margin: 20rpx;
.post-content {
margin-top: 20rpx;
.img-style-1 {
display: block;
width: 100%;
max-height: 600rpx;
border-radius: 5px;
overflow: hidden;
}
.img-style-2 {
display: flex;
image {
margin: 5rpx;
border-radius: 5px;
width: 100%;
height: 294rpx;
}
}
.img-style-3 {
display: flex;
flex-wrap: wrap;
image {
width: 31.3%;
height: 200rpx;
margin: 1%;
border-radius: 5px;
}
}
}
.address {
display: flex;
font-size: 20rpx;
background-color: #f5f5f5;
border-radius: 20rpx;
display: inline-block;
padding: 5rpx 20rpx;
margin: 20rpx 0;
.icon {
margin-right: 5rpx;
}
}
}
.post-item-top-user {
display: flex;
align-items: center;
.avatar {
width: 85rpx;
height: 85rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.center {
flex: 1;
margin-left: 10rpx;
display: flex;
flex-direction: column;
font-size: 24rpx;
color: #999;
.username {
font-size: 32rpx;
font-weight: 600;
color: #616161;
}
.official {
display: inline-block;
font-size: 20rpx;
color: #fff;
background-color: $themes-color;
padding: 5rpx 10rpx;
border-radius: 10rpx;
margin-right: 10rpx;
}
.time {
float: left;
}
}
.right {
height: 85rpx;
.arrow-down {
color: #999;
}
}
}
.post-text {
font-size: 30rpx;
margin-bottom: 10rpx;
display: block;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
white-space: pre-wrap;
overflow: hidden;
}
.p-footer {
display: flex;
margin-top: 20rpx;
// margin: 20rpx 0;
.p-item {
width: 200rpx;
margin: 0 auto;
color: #616161;
display: flex;
align-items: center;
justify-content: center;
.count {
margin-left: 10rpx;
font-size: 32rpx;
}
}
.p-item[hidden] {
display: none !important;
}
}
.video-wrap {
display: flex;
justify-content: center;
align-items: center;
position: relative;
width: 100%;
height: 500rpx;
.icon {
width: 100rpx;
height: 100rpx;
z-index: 999;
}
image {
position: absolute;
}
}
.u-reset-button {
display: flex;
padding: 0;
background-color: transparent;
font-size: inherit;
line-height: inherit;
color: inherit;
justify-content: center;
}
</style>
<template>
<view>
<button
class="reset-button button"
@click="onClick"
hover-class="button-hover"
:class="[shape == 'circle' ? 'round-circle' : '']"
>
<slot></slot>
</button>
</view>
</template>
<script>
/**
* m-field button 按钮组件
* @description 常用按钮组件。
* @tutorial https://ui.ymeoo.cn
* @property {String} color 按钮主题色
* @property {String} shape 设置为circle,则按钮两边为半圆形
* @event {Function} click 组件自定义点击事件
* @example <u-form-item label="姓名"><u-input v-model="form.name" /></u-form-item>
*/
export default {
name: 'q-button',
props: {
shape: {
type: String,
default: 'circle'
}
},
data() {
return {};
},
methods: {
onClick() {
this.$emit('click', '');
}
}
};
</script>
<style lang="scss" scoped>
// 去除button的所有默认样式
.reset-button {
padding: 0;
font-size: inherit;
line-height: inherit;
background-color: transparent;
color: inherit;
}
.reset-button::after {
border: none;
}
// button样式
.button {
display: block;
padding: 20rpx;
margin: 20rpx;
background-image: -moz-linear-gradient(135deg, rgb(0, 255, 255), rgb(29, 147, 251));
background-image: -webkit-linear-gradient(135deg, rgb(0, 255, 255), rgb(29, 147, 251));
background-image: linear-gradient(135deg, rgb(0, 255, 255), rgb(29, 147, 251));
color: #fff;
}
.button-hover {
background-color: #f5f5f5 !important;
}
.round-circle {
border-radius: 100rpx;
}
</style>
<template>
<button :disabled="runSecond > 0" :class="runSecond > 0 ? 'disabled' : ''" class="send-code" @click="handleClick">{{ tmpStr }}</button>
</template>
<script>
import { message } from '@/utils/fun';
import { isPhoneNumber } from '@/utils/validate.js';
export default {
name: 'send-code',
props: {
value: String,
code: String,
initStr: {
type: String,
default: '获取验证码'
},
second: {
type: Number,
default: 60
},
runStr: {
type: String,
default: '{%s}秒后重新获取'
},
resetStr: {
type: String,
default: '重新获取验证码'
}
},
data() {
return {
tmpStr: this.initStr,
runSecond: 0,
timer: null
};
},
mounted() {
uni.getStorage({
key: 'send-code-time',
success: data => {
const lastSecond = ~~((data - new Date().getTime()) / 1000);
if (lastSecond > 0) {
this.runSecond = lastSecond;
this.run();
}
}
});
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer);
}
},
methods: {
handleClick() {
if (!isPhoneNumber(this.value)) {
message.notify('请输入正确手机号');
return;
}
if (!this.code) {
message.notify('请输入图形验证码');
return;
}
this.run();
},
run() {
if (!this.runSecond) {
this.runSecond = this.second;
this.$emit('send');
uni.setStorage({
key: 'send-code-time',
data: new Date().getTime() + this.second * 1000
});
}
this.tmpStr = this.getStr();
this.timer = setInterval(() => {
this.runSecond = --this.runSecond;
this.tmpStr = this.getStr();
if (this.runSecond <= 0) {
this.timeout();
}
}, 1000);
},
timeout() {
this.tmpStr = this.resetStr;
this.runSecond = 0;
uni.removeStorage({ key: 'send-code-time' });
clearInterval(this.timer);
},
getStr() {
return this.runStr.replace(/\{([^{]*?)%s(.*?)\}/g, this.runSecond);
}
}
};
</script>
<style lang="scss">
@import '@/scss/uni.scss';
.send-code {
line-height: 1;
padding: 18rpx 38rpx;
background-color: #c6c6c6;
color: $bg-color;
border: none;
font-size: 24rpx;
width: auto !important;
// margin: 0 !important;
font-weight: normal;
border-radius: 70rpx;
&::after {
content: none;
}
&.disabled {
background-color: darken($color: #c6c6c6, $amount: 15%) !important;
color: $bg-color !important;
}
}
</style>
<template>
<u-popup customStyle="background-color:#00000000;" :show="showDialog" mode="center" round="20rpx"
:closeOnClickOverlay="true" @close="close">
<view class="container">
<view class="img-wrap">
<u-text align="center" text="长按识别二维码" color="#63656D" size="18"></u-text>
<u-text align="center" text="添加客服" color="#B6B8BA" size="18"></u-text>
<image class="mt-20" :show-menu-by-longpress="true" :src="serviceUrl"></image>
</view>
<u-icon name="close-circle" color="#fff" size="30" @click="close"></u-icon>
</view>
</u-popup>
</template>
<script>
import {
message
} from '@/utils/fun';
export default {
name: "service-img-dialog",
props: {
url: String,
show: {
type: Boolean,
default: false
},
},
data() {
return {
showDialog: this.show,
serviceUrl: this.url
};
},
watch: {
show: {
handler: function(newVal, oldVal) {
this.showDialog = newVal;
}
},
url: {
handler: function(newVal, oldVal) {
this.serviceUrl = newVal
}
}
},
methods: {
close() {
this.showDialog = false
this.$emit('dismiss');
}
},
}
</script>
<style lang="scss">
.container {
display: flex;
flex-direction: column;
align-items: center;
.img-wrap {
margin-top: 100rpx;
border-radius: 10rpx;
padding: 20rpx;
width: 500rpx;
background-color: #FAFBFE;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20rpx;
}
image {
width: 400rpx;
height: 400rpx;
}
}
</style>
<template>
<view class="time-down">{{ text }}</view>
</template>
<script>
export default {
name: 'time-down',
props: {
time: String
},
data() {
return {
text: '00:00:00',
timespan: 0,
timer: undefined
};
},
// watch: {
// value: {
// immediate: true,
// handler() {
// this.create();
// }
// }
// },
created() {
this.timespan = new Date(this.time).getTime() - new Date().getTime();
this.countdown();
},
beforeDestroy() {
clearInterval(this.timer);
},
methods: {
countdown() {
if (this.timespan < 0) {
this.text = '已上架'
} else {
this.timer = setInterval(() => {
let {
timespan
} = this;
this.timespan -= 1000;
if (this.timespan <= 0) {
this.text = '已上架'
} else {
let leftd = Math.floor(timespan / (1000 * 60 * 60 * 24)), //计算天数
lefth = ('00' + Math.floor((timespan / (1000 * 60 * 60)) % 24)).slice(-2), //计算小时数
leftm = ('00' + Math.floor((timespan / (1000 * 60)) % 60)).slice(-2), //计算分钟数
lefts = ('00' + Math.floor((timespan / 1000) % 60)).slice(-2); //计算秒数
this.text = "即将开售" + (leftd > 0 ? leftd + '天' : '') + lefth + ':' + leftm + ':' +
lefts; //返回倒计时的字符串
}
}, 1000);
}
}
}
};
</script>
<style lang="scss">
@import '@/scss/uni.scss';
.time-down {
display: inline-block;
margin-left: 10rpx;
}
</style>
'use strict';
const ERR_MSG_OK = 'chooseAndUploadFile:ok';
const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
function chooseImage(opts) {
const {
count,
sizeType = ['original', 'compressed'],
sourceType = ['album', 'camera'],
extension
} = opts
return new Promise((resolve, reject) => {
uni.chooseImage({
count,
sizeType,
sourceType,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res, 'image'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseVideo(opts) {
const {
camera,
compressed,
maxDuration,
sourceType = ['album', 'camera'],
extension
} = opts;
return new Promise((resolve, reject) => {
uni.chooseVideo({
camera,
compressed,
maxDuration,
sourceType,
extension,
success(res) {
const {
tempFilePath,
duration,
size,
height,
width
} = res;
resolve(normalizeChooseAndUploadFileRes({
errMsg: 'chooseVideo:ok',
tempFilePaths: [tempFilePath],
tempFiles: [
{
name: (res.tempFile && res.tempFile.name) || '',
path: tempFilePath,
size,
type: (res.tempFile && res.tempFile.type) || '',
width,
height,
duration,
fileType: 'video',
cloudPath: '',
}, ],
}, 'video'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseAll(opts) {
const {
count,
extension
} = opts;
return new Promise((resolve, reject) => {
let chooseFile = uni.chooseFile;
if (typeof wx !== 'undefined' &&
typeof wx.chooseMessageFile === 'function') {
chooseFile = wx.chooseMessageFile;
}
if (typeof chooseFile !== 'function') {
return reject({
errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。',
});
}
chooseFile({
type: 'all',
count,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL),
});
},
});
});
}
function normalizeChooseAndUploadFileRes(res, fileType) {
res.tempFiles.forEach((item, index) => {
if (!item.name) {
item.name = item.path.substring(item.path.lastIndexOf('/') + 1);
}
if (fileType) {
item.fileType = fileType;
}
item.cloudPath =
Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.'));
});
if (!res.tempFilePaths) {
res.tempFilePaths = res.tempFiles.map((file) => file.path);
}
return res;
}
function uploadCloudFiles(files, max = 5, onUploadProgress) {
files = JSON.parse(JSON.stringify(files))
const len = files.length
let count = 0
let self = this
return new Promise(resolve => {
while (count < max) {
next()
}
function next() {
let cur = count++
if (cur >= len) {
!files.find(item => !item.url && !item.errMsg) && resolve(files)
return
}
const fileItem = files[cur]
const index = self.files.findIndex(v => v.uuid === fileItem.uuid)
fileItem.url = ''
delete fileItem.errMsg
uniCloud
.uploadFile({
filePath: fileItem.path,
cloudPath: fileItem.cloudPath,
fileType: fileItem.fileType,
onUploadProgress: res => {
res.index = index
onUploadProgress && onUploadProgress(res)
}
})
.then(res => {
fileItem.url = res.fileID
fileItem.index = index
if (cur < len) {
next()
}
})
.catch(res => {
fileItem.errMsg = res.errMsg || res.message
fileItem.index = index
if (cur < len) {
next()
}
})
}
})
}
function uploadFiles(choosePromise, {
onChooseFile,
onUploadProgress
}) {
return choosePromise
.then((res) => {
if (onChooseFile) {
const customChooseRes = onChooseFile(res);
if (typeof customChooseRes !== 'undefined') {
return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ?
res : chooseRes);
}
}
return res;
})
.then((res) => {
if (res === false) {
return {
errMsg: ERR_MSG_OK,
tempFilePaths: [],
tempFiles: [],
};
}
return res
})
}
function chooseAndUploadFile(opts = {
type: 'all'
}) {
if (opts.type === 'image') {
return uploadFiles(chooseImage(opts), opts);
}
else if (opts.type === 'video') {
return uploadFiles(chooseVideo(opts), opts);
}
return uploadFiles(chooseAll(opts), opts);
}
export {
chooseAndUploadFile,
uploadCloudFiles
};
This diff is collapsed.
<template>
<view class="uni-file-picker__files">
<view v-if="!readonly" class="files-button" @click="choose">
<slot></slot>
</view>
<!-- :class="{'is-text-box':showType === 'list'}" -->
<view v-if="list.length > 0" class="uni-file-picker__lists is-text-box" :style="borderStyle">
<!-- ,'is-list-card':showType === 'list-card' -->
<view class="uni-file-picker__lists-box" v-for="(item ,index) in list" :key="index" :class="{
'files-border':index !== 0 && styles.dividline}"
:style="index !== 0 && styles.dividline &&borderLineStyle">
<view class="uni-file-picker__item">
<!-- :class="{'is-text-image':showType === 'list'}" -->
<!-- <view class="files__image is-text-image">
<image class="header-image" :src="item.logo" mode="aspectFit"></image>
</view> -->
<view class="files__name">
<video :src="item.url"></video>
</view>
<view v-if="delIcon&&!readonly" class="icon-del-box icon-files" @click="delFile(index)">
<view class="icon-del icon-files"></view>
<view class="icon-del rotate"></view>
</view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.status === 'error'" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadFile",
emits:['uploadFiles','choose','delFile'],
props: {
filesList: {
type: Array,
default () {
return []
}
},
delIcon: {
type: Boolean,
default: true
},
limit: {
type: [Number, String],
default: 9
},
showType: {
type: String,
default: ''
},
listStyles: {
type: Object,
default () {
return {
// 是否显示边框
border: true,
// 是否显示分隔线
dividline: true,
// 线条样式
borderStyle: {}
}
}
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
list() {
let files = []
this.filesList.forEach(v => {
files.push(v)
})
return files
},
styles() {
let styles = {
border: true,
dividline: true,
'border-style': {}
}
return Object.assign(styles, this.listStyles)
},
borderStyle() {
let {
borderStyle,
border
} = this.styles
let obj = {}
if (!border) {
obj.border = 'none'
} else {
let width = (borderStyle && borderStyle.width) || 1
width = this.value2px(width)
let radius = (borderStyle && borderStyle.radius) || 5
radius = this.value2px(radius)
obj = {
'border-width': width,
'border-style': (borderStyle && borderStyle.style) || 'solid',
'border-color': (borderStyle && borderStyle.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
},
borderLineStyle() {
let obj = {}
let {
borderStyle
} = this.styles
if (borderStyle && borderStyle.color) {
obj['border-color'] = borderStyle.color
}
if (borderStyle && borderStyle.width) {
let width = borderStyle && borderStyle.width || 1
let style = borderStyle && borderStyle.style || 0
if (typeof width === 'number') {
width += 'px'
} else {
width = width.indexOf('px') ? width : width + 'px'
}
obj['border-width'] = width
if (typeof style === 'number') {
style += 'px'
} else {
style = style.indexOf('px') ? style : style + 'px'
}
obj['border-top-style'] = style
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", {
item,
index
})
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__files {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: flex-start;
}
.files-button {
// border: 1px red solid;
}
.uni-file-picker__lists {
position: relative;
margin-top: 5px;
overflow: hidden;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 14px;
background-color: rgba(0, 0, 0, 0.4);
}
.uni-file-picker__lists-box {
position: relative;
}
.uni-file-picker__item {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
padding: 8px 10px;
padding-right: 5px;
padding-left: 10px;
}
.files-border {
border-top: 1px #eee solid;
}
.files__name {
flex: 1;
font-size: 14px;
color: #666;
margin-right: 25px;
/* #ifndef APP-NVUE */
word-break: break-all;
word-wrap: break-word;
/* #endif */
}
.icon-files {
/* #ifndef APP-NVUE */
position: static;
background-color: initial;
/* #endif */
}
// .icon-files .icon-del {
// background-color: #333;
// width: 12px;
// height: 1px;
// }
.is-list-card {
border: 1px #eee solid;
margin-bottom: 5px;
border-radius: 5px;
box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.1);
padding: 5px;
}
.files__image {
width: 40px;
height: 40px;
margin-right: 10px;
}
.header-image {
width: 100%;
height: 100%;
}
.is-text-box {
border: 1px #eee solid;
border-radius: 5px;
}
.is-text-image {
width: 25px;
height: 25px;
margin-left: 5px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
margin: auto 0;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 0px;
bottom: 0;
right: 5px;
height: 26px;
width: 26px;
// border-radius: 50%;
// background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 1px;
background-color: #333;
// border-radius: 1px;
}
/* #ifdef H5 */
@media all and (min-width: 768px) {
.uni-file-picker__files {
max-width: 375px;
}
}
/* #endif */
</style>
<template>
<view class="uni-file-picker__container">
<view class="file-picker__box" v-for="(item,index) in filesList" :key="index" :style="boxStyle">
<view class="file-picker__box-content" :style="borderStyle">
<image class="file-image" :src="item.url" mode="aspectFill" @click.stop="prviewImage(item,index)"></image>
<view v-if="delIcon && !readonly" class="icon-del-box" @click.stop="delFile(index)">
<view class="icon-del"></view>
<view class="icon-del rotate"></view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.errMsg" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
<view v-if="filesList.length < limit && !readonly" class="file-picker__box" :style="boxStyle">
<view class="file-picker__box-content is-add" :style="borderStyle" @click="choose">
<slot>
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadImage",
emits:['uploadFiles','choose','delFile'],
props: {
filesList: {
type: Array,
default () {
return []
}
},
disabled:{
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
limit: {
type: [Number, String],
default: 9
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto',
border: {}
}
}
},
delIcon: {
type: Boolean,
default: true
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
styles() {
let styles = {
width: 'auto',
height: 'auto',
border: {}
}
return Object.assign(styles, this.imageStyles)
},
boxStyle() {
const {
width = 'auto',
height = 'auto'
} = this.styles
let obj = {}
if (height === 'auto') {
if (width !== 'auto') {
obj.height = this.value2px(width)
obj['padding-top'] = 0
} else {
obj.height = 0
}
} else {
obj.height = this.value2px(height)
obj['padding-top'] = 0
}
if (width === 'auto') {
if (height !== 'auto') {
obj.width = this.value2px(height)
} else {
obj.width = '33.3%'
}
} else {
obj.width = this.value2px(width)
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
},
borderStyle() {
let {
border
} = this.styles
let obj = {}
if (typeof border === 'boolean') {
obj.border = border ? '1px #eee solid' : 'none'
} else {
let width = (border && border.width) || 1
width = this.value2px(width)
let radius = (border && border.radius) || 5
radius = this.value2px(radius)
obj = {
}
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", item)
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
prviewImage(img, index) {
let urls = []
if(Number(this.limit) === 1&&this.disablePreview&&!this.disabled){
this.$emit("choose")
}
if(this.disablePreview) return
this.filesList.forEach(i => {
urls.push(i.url)
})
uni.previewImage({
urls: urls,
current: index
});
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
if (value.indexOf('%') === -1) {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__container {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-wrap: wrap;
margin: -5px;
}
.file-picker__box {
position: relative;
// flex: 0 0 33.3%;
width: 33.3%;
height: 0;
padding-top: 33.33%;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.file-picker__box-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: 5px;
border-radius: 8px;
overflow: hidden;
}
.file-picker__progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
/* border: 1px red solid; */
z-index: 2;
}
.file-picker__progress-item {
width: 100%;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.4);
}
.file-image {
width: 100%;
height: 100%;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 5px;
right: 5px;
height: 26px;
width: 26px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 2px;
background-color: #fff;
border-radius: 2px;
}
</style>
/**
* 获取文件名和后缀
* @param {String} name
*/
export const get_file_ext = (name) => {
const last_len = name.lastIndexOf('.')
const len = name.length
return {
name: name.substring(0, last_len),
ext: name.substring(last_len + 1, len)
}
}
/**
* 获取扩展名
* @param {Array} fileExtname
*/
export const get_extname = (fileExtname) => {
if (!Array.isArray(fileExtname)) {
let extname = fileExtname.replace(/(\[|\])/g, '')
return extname.split(',')
} else {
return fileExtname
}
return []
}
/**
* 获取文件和检测是否可选
*/
export const get_files_and_is_max = (res, _extname) => {
let filePaths = []
let files = []
if(!_extname || _extname.length === 0){
return {
filePaths,
files
}
}
res.tempFiles.forEach(v => {
let fileFullName = get_file_ext(v.name)
const extname = fileFullName.ext.toLowerCase()
if (_extname.indexOf(extname) !== -1) {
files.push(v)
filePaths.push(v.path)
}
})
if (files.length !== res.tempFiles.length) {
uni.showToast({
title: `当前选择了${res.tempFiles.length}个文件 ,${res.tempFiles.length - files.length} 个文件格式不正确`,
icon: 'none',
duration: 5000
})
}
return {
filePaths,
files
}
}
/**
* 获取图片信息
* @param {Object} filepath
*/
export const get_file_info = (filepath) => {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: filepath,
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
})
})
}
/**
* 获取封装数据
*/
export const get_file_data = async (files, type = 'image') => {
// 最终需要上传数据库的数据
let fileFullName = get_file_ext(files.name)
const extname = fileFullName.ext.toLowerCase()
let filedata = {
name: files.name,
uuid: files.uuid,
extname: extname || '',
cloudPath: files.cloudPath,
fileType: files.fileType,
url: files.path || files.path,
size: files.size, //单位是字节
image: {},
path: files.path,
video: {}
}
if (type === 'image') {
const imageinfo = await get_file_info(files.path)
delete filedata.video
filedata.image.width = imageinfo.width
filedata.image.height = imageinfo.height
filedata.image.location = imageinfo.path
} else {
delete filedata.image
}
return filedata
}
<template>
<view class="verify-code">
<!-- 输入框 -->
<input
:value="code"
class="verify-code-input"
:focus="isFocus"
:password="isPassword"
:type="inputType"
:maxlength="itemSize"
@input="input"
@focus="inputFocus"
@blur="inputBlur"
/>
<!-- 光标 -->
<view
v-if="cursorVisible && type !== 'middle'"
class="verify-code-cursor"
:style="{ left: codeCursorLeft[code.length] + 'px', height: cursorHeight + 'px', backgroundColor: cursorColor }"
></view>
<!-- 输入框 - 组 -->
<view class="verify-code-ground">
<template v-for="(item, index) in itemSize">
<view
:key="index"
:style="{ borderColor: code.length === index && cursorVisible ? boxActiveColor : boxNormalColor }"
:class="['verify-code-box', `verify-code-box-${type + ''}`, `verify-code-box::after`]"
>
<view :style="{ borderColor: boxActiveColor }" class="verify-code-line" v-if="type === 'middle' && !code[index]"></view>
<text class="verify-code-text">{{ code[index] | codeFormat(isPassword) }}</text>
</view>
</template>
</view>
</view>
</template>
<script>
/**
* @description 输入验证码组件
* @property {string} type = [box|middle|bottom] - 显示类型 默认:box -eg:bottom
* @property {string} inputType = [text|number] - 输入框类型 默认:number -eg:number
* @property {number} size = [1|2|3|4|5|6] - 支持的验证码数量 默认:6 -eg:6
* @property {boolean} isFocus - 是否立即聚焦 默认:true
* @property {boolean} isPassword - 是否以密码形式显示 默认false -eg:false
* @property {string} cursorColor - 光标颜色 默认:#cccccc
* @property {string} boxNormalColor - 光标未聚焦到的框的颜色 默认:#cccccc
* @property {string} boxActiveColor - 光标聚焦到的框的颜色 默认:#000000
* @event {Function(data)} confirm - 输入完成
*/
export default {
name: 'xt-verify-code',
props: {
value: {
type: String,
default: () => ''
},
type: {
type: String,
default: () => 'box'
},
inputType: {
type: String,
default: () => 'number'
},
size: {
type: Number,
default: () => 6
},
isFocus: {
type: Boolean,
default: () => false
},
isPassword: {
type: Boolean,
default: () => false
},
cursorColor: {
type: String,
default: () => '#cccccc'
},
boxNormalColor: {
type: String,
default: () => '#9f9f9f'
},
boxActiveColor: {
type: String,
default: () => '#ffffff'
}
},
model: {
prop: 'value',
event: 'input'
},
data() {
return {
cursorVisible: false,
cursorHeight: 35,
code: '', // 输入的验证码
codeCursorLeft: [], // 向左移动的距离数组,
itemSize: 6
};
},
created() {
this.cursorVisible = this.isFocus;
this.validatorSize();
},
mounted() {
this.init();
},
methods: {
/**
*
*/
validatorSize() {
if (this.size <= 6 && this.size > 0) {
this.itemSize = Math.floor(this.size);
} else {
this.itemSize = 6;
}
},
/**
* @description 初始化
*/
init() {
this.getCodeCursorLeft();
this.setCursorHeight();
},
/**
* @description 获取元素节点
* @param {string} elm - 节点的id、class 相当于 document.querySelect的参数 -eg: #id
* @param {string} type = [single|array] - 单个元素获取多个元素 默认是单个元素
* @param {Function} callback - 回调函数
*/
getElement(elm, type = 'single', callback) {
uni
.createSelectorQuery()
.in(this)
[type === 'array' ? 'selectAll' : 'select'](elm)
.boundingClientRect()
.exec(data => {
callback(data[0]);
});
},
/**
* @description 计算光标的高度
*/
setCursorHeight() {
this.getElement('.verify-code-box', 'single', boxElm => {
this.cursorHeight = boxElm.height * 0.6;
});
},
/**
* @description 获取光标在每一个box的left位置
*/
getCodeCursorLeft() {
// 获取父级框的位置信息
this.getElement('.verify-code-ground', 'single', parentElm => {
const parentLeft = parentElm.left;
// 获取各个box信息
this.getElement('.verify-code-box', 'array', elms => {
this.codeCursorLeft = [];
elms.forEach(elm => {
this.codeCursorLeft.push(elm.left - parentLeft + elm.width / 2);
});
});
});
},
// 输入框输入变化的回调
input(e) {
const value = e.detail.value;
this.cursorVisible = value.length !== this.itemSize;
this.$emit('input', value);
this.inputSuccess(value);
},
// 输入完成回调
inputSuccess(value) {
if (value.length === this.itemSize) {
this.$emit('confirm', value);
}
},
// 输入聚焦
inputFocus() {
this.cursorVisible = this.code.length !== this.itemSize;
},
// 输入失去焦点
inputBlur() {
this.cursorVisible = false;
}
},
watch: {
value(val) {
this.code = val;
}
},
filters: {
codeFormat(val, isPassword) {
let value = '';
if (val) {
value = isPassword ? '*' : val;
}
return value;
}
}
};
</script>
<style lang="scss" scoped>
.verify-code {
position: relative;
width: 100%;
box-sizing: border-box;
&-input {
height: 100%;
width: 200%;
position: absolute;
left: -100%;
z-index: 1;
}
&-cursor {
position: absolute;
top: 50%;
transform: translateY(-50%);
display: inline-block;
width: 2px;
animation-name: cursor;
animation-duration: 0.8s;
animation-iteration-count: infinite;
}
&-ground {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
box-sizing: border-box;
}
&-box {
position: relative;
display: inline-block;
color: white;
width: 100rpx;
height: 120rpx;
&-bottom {
border-bottom-width: 2px;
border-bottom-style: solid;
}
&-box {
border-width: 2rpx;
border-style: solid;
border-radius: 16rpx;
}
&-middle {
border: none;
}
}
&-line {
position: absolute;
top: 50%;
left: 50%;
width: 50%;
transform: translate(-50%, -50%);
border-bottom-width: 2px;
border-bottom-style: solid;
}
&-text {
position: absolute;
top: 50%;
left: 50%;
font-size: 80rpx;
transform: translate(-50%, -50%);
}
}
@keyframes cursor {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
</style>
<template>
<u-popup bgColor="transparent" :show="showLoading" mode="center" :closeOnClickOverlay="false" @close="close">
<view class="container">
<u-icon name="close-circle" color="#fff" size="30" @click="close"></u-icon>
<video class="video-wrap" objectFit="cover" :controls="false" :autoplay="true" :loop="true"
@loadedmetadata="videoMeta" :style="'width:'+videoWidth+'px;height:'+videoHeight+'px'" :src="videoUrl" />
<image class="mt-30" mode="widthFix" :style="'width:'+videoWidth+'px;'" :src="imageUrl"></image>
</view>
</u-popup>
</template>
<script>
export default {
name: "video-loading",
props: {
show: {
type: Boolean,
default: false
},
imgUrl: {
type: String,
default: ''
},
},
data() {
return {
showLoading: this.show,
videoUrl: 'http://mints-sh.oss-cn-shanghai.aliyuncs.com/video/process.mp4',
videoWidth: '',
videoHeight: '',
imageUrl: this.imgUrl,
};
},
watch: {
show: {
handler: function(newVal, oldVal) {
this.showLoading = newVal;
}
},
imgUrl: {
handler: function(newVal, oldVal) {
this.imageUrl = newVal;
}
}
},
methods: {
close() {
this.showLoading = false
this.$emit('dismiss');
},
videoMeta: function(e) {
var that = this;
//获取系统信息
uni.getSystemInfo({
success(res) {
//算出视频的比例
var proportion = e.detail.height / e.detail.width;
//res.windowWidth为手机屏幕的宽。
var windowWidth = res.windowWidth;
var windowHeight = res.windowHeight;
//算出当前宽度下高度的数值
that.videoHeight = proportion * windowWidth;
that.videoWidth = windowWidth * (that.videoHeight / windowWidth);
}
})
},
}
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
// justify-content: center;
flex-direction: column;
max-height: 1000rpx;
border-top-left-radius: 50rpx;
border-top-right-radius: 50rpx;
}
.video-wrap {
margin: 50rpx 100rpx;
border-radius: 30rpx;
}
image{
margin: 20rpx 100rpx;
}
</style>
import {
navigateTo,
redirectTo,
loading,
message,
confirm,
alert
} from '../utils/fun.js';
const app = getApp();
export default {
data() {
return {
xhrPool: new Set(),
bottomSafePadding: app.globalData.bottomSafePadding,
options: {},
auth: false, // 登录验证
paying: false, // 支付按钮状态
};
},
onLoad(options) {
this.options = options;
},
onShow() {
this.loadData();
},
onHide() {
if (this.xhrPool.size) {
this.xhrPool.forEach((requestTask) => {
requestTask.abort();
})
}
},
methods: {
authTo(url) {//登录校验
},
authToNs(url) {//登录校验不保留当前页面
},
loadData() {},
startPay() {
loading.show();
this.paying = true;
},
stopPay() {
loading.hide();
this.paying = false;
},
/**
* @param {Object} url 接口请求地址
* @param {Object} method 接口请求方式
* @param {Object} data 接口请求参数
*/
$http(url, method, data = {}, showLoading = false) {
return new Promise((resolve, reject) => {
this[method.toLowerCase()]({
url: url,
data: data,
showLoading: showLoading,
success: (res) => {
resolve(res)
},
fail: (e) => {
reject(e)
}
});
})
},
login() {
// navigateTo('user/login');
// let redirect = this.$scope.$page.fullPath.replace('/pages', '');
// console.log(redirect)
// navigateTo('user/login?redirect=' + encodeURIComponent(redirect));
},
logout() {
uni.removeStorageSync('token');
},
put(options) {
options = Object.assign({
showLoading: true
}, options, {
method: 'PUT'
})
this.req(options);
},
post(options) {
options = Object.assign({
showLoading: true
}, options, {
method: 'POST'
})
this.req(options);
},
get(options) {
options = Object.assign({
showLoading: false
}, options, {
method: 'GET'
})
this.req(options);
},
req(options) {
let {
showLoading,
url,
data,
auth
} = options;
const success = options?.success;
const fail = options?.fail;
const header = options?.header ?? {};
showLoading && loading.show();
let requestTask;
let uniChannel='wechat';
// #ifdef MP-KUAISHOU
uniChannel='kuaishou';
// #endif
// #ifdef MP-TOUTIAO
uniChannel='douyin';
// #endif
Object.assign(header, {
token: uni.getStorageSync('token'),
pkgName: app.globalData.pkgName,
proChannel:uniChannel
})
options = Object.assign(options, {
url: `${app.globalData.baseUrl}${url}`,
header,
data,
success: (res) => {
showLoading && loading.hide();
switch (res.data.status) {
case 200:
if (success) {
success(res.data);
}
break;
default:
if (fail) {
fail(res.data.message);
}
message.notify(res.data.message);
break;
}
},
fail: (e) => {
message.notify('服务器开小差了');
// alert({
// content: JSON.stringify(e)
// });
if (fail) {
fail(e);
}
},
complete: () => {
this.xhrPool.delete(requestTask)
}
})
requestTask = uni.request(options);
this.xhrPool.add(requestTask)
}
}
};
import {
navigateTo,
redirectTo,
loading,
message,
confirm,
alert
} from '../utils/fun.js';
import {
apiPOST
} from '../common/utils/apiRequest.js'
const app = getApp();
export default {
data() {
return {
xhrPool: new Set(),
bottomSafePadding: app.globalData.bottomSafePadding,
options: {},
auth: false, // 登录验证
paying: false, // 支付按钮状态
};
},
onLoad(options) {
this.options = options;
},
onShow() {
this.loadData();
},
onHide() {
if (this.xhrPool.size) {
this.xhrPool.forEach((requestTask) => {
requestTask.abort();
})
}
},
methods: {
authTo(url) { //登录校验
},
authToNs(url) { //登录校验不保留当前页面
},
loadData() {},
startPay() {
loading.show();
this.paying = true;
},
stopPay() {
loading.hide();
this.paying = false;
},
/**
* @param {Object} url 接口请求地址
* @param {Object} method 接口请求方式
* @param {Object} data 接口请求参数
*/
$http(url, method, data = {}, showLoading = false) {
return new Promise((resolve, reject) => {
this[method.toLowerCase()]({
url: url,
data: data,
showLoading: showLoading,
success: (res) => {
resolve(res)
},
fail: (e) => {
reject(e)
}
});
})
},
login() {
// navigateTo('user/login');
// let redirect = this.$scope.$page.fullPath.replace('/pages', '');
// console.log(redirect)
// navigateTo('user/login?redirect=' + encodeURIComponent(redirect));
},
logout() {
uni.removeStorageSync('token');
},
post(options) {
options = Object.assign({
showLoading: true
}, options, {
method: 'POST'
})
this.req(options);
},
// get(options) {
// options = Object.assign({
// showLoading: false
// }, options, {
// method: 'GET'
// })
// this.req(options);
// },
req(options) {
let {
showLoading,
url,
data,
auth
} = options;
const success = options?.success;
const fail = options?.fail;
const header = options?.header ?? {};
showLoading && loading.show();
apiPOST({
url: `${url}`,
data: data,
callback: (_success, _data) => {
showLoading && loading.hide();
if (_success) {
success({
data: _data
})
} else {
fail(_data)
message.notify(res.data.message);
}
}
})
}
}
};
\ No newline at end of file
<template>
<view class="history-item" @click="tapItem">
<view class="row">
<view class="item c-flex_1">
<view class="title">
{{item.title}}
</view>
</view>
</view>
<view class="row">
<view class="item c-flex_1">
<view class="desc">
{{item.summary}}
</view>
</view>
</view>
<view class="row c-justify_between">
<view class="item">
<view class="author-box">
<uni-icons type='icon-author' custom-prefix="readiconfont" size='20' color="#378eff"></uni-icons>
<view class="name">
{{item.author}}
</view>
</view>
</view>
<view class="item" @click.stop="tapCollection(item.isCollect)">
<view class="collection-button collected" v-if='item.isCollect'>
<uni-icons type='checkmarkempty' size='10' color="#999"></uni-icons>
<view class="title">
已添加
</view>
</view>
<view class="collection-button" v-else>
<uni-icons type='icon-collection' custom-prefix="readiconfont" size='10'
color='#378eff'></uni-icons>
<view class="title">
加入书架
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
item: {
type: Object,
default: function() {
return {}
}
}
},
methods: {
tapItem() {
this.$emit('tapItem', {
detail: {
data: this.item
}
})
},
tapCollection(isCollect) {
this.$emit('changeCollection', {
detail: {
data: this.item,
isCollection: !isCollect
}
})
}
}
}
</script>
<style lang="scss" scoped>
.history-item {
padding: 30rpx;
display: flex;
flex-direction: column;
.row {
margin-bottom: 20rpx;
display: flex;
flex-direction: row;
}
.row:last-child {
margin-bottom: 0;
}
.item {
margin-right: 20rpx;
display: flex;
flex-direction: column;
}
.item:last-child {
margin-right: 0;
}
.title {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
.desc {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 24rpx;
color: #999
}
.author-box {
display: flex;
flex-direction: row;
align-items: center;
height: 50rpx;
line-height: 50rpx;
.name {
font-size: 26rpx;
color: #378eff;
margin-left: 15rpx;
}
}
.collection-button {
height: 50rpx;
line-height: 50rpx;
width: 150rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #a0c9ff;
border-radius: 10rpx;
.title {
font-size: 22rpx;
color: #378eff;
margin-left: 10rpx;
}
}
.collected {
background-color: #ededed;
.title {
color: #999;
}
}
}
</style>
\ No newline at end of file
import Book from "../../../../common/models/Book";
export default class ReadHistory extends Book {
constructor(param) {
super(param)
}
}
\ No newline at end of file
<template>
<view>
<c-list ref='list' flag='readHistory' :needLogin="true" method="POST" url='/readSystem/system/readRecord/list'
:param="requestParam" @change='changeData'>
<history-item v-for="(item, index) in dataList" :key="index" :item='item' @tapItem='tapItem($event, index)'
@changeCollection='changeCollection($event, index)'></history-item>
</c-list>
<c-login></c-login>
</view>
</template>
<script>
import {
isEmpty
} from '../../../common/utils/util';
import ReadHistory from './models/ReadHistory';
import HistoryItem from "./components/history-item.vue";
import {
gotoBookContentPage
} from '../../../common/services/page-route';
import {
collectionBook
} from "../../../common/services/index.js"
import {
watchUserInfoChange,
removeUserInfoChangeWatch,
} from "../../../common/services/userServices.js";
import {
noticeCollectionListChange,
removeCollectionChangeWatch,
watchCollectionChange
} from '../../../common/services';
export default {
components: {
HistoryItem
},
data() {
return {
dataList: [],
isFirstRequest: false
};
},
computed: {
requestParam: function() {
return {}
}
},
onReady() {
watchUserInfoChange((info) => {
if (info.userInfo) {
this.initRefresh();
}
}, this)
watchCollectionChange((info) => {
let index = 0;
let item;
let changeItem;
for (; index < this.dataList.length; index++) {
item = this.dataList[index];
if (item.id == info.bookId) {
item.isCollect = info.isCollect;
changeItem = item;
break;
}
}
if (changeItem) {
this.$set(this.dataList, index, changeItem);
}
}, this)
},
onUnload() {
removeUserInfoChangeWatch(this);
removeCollectionChangeWatch(this);
},
methods: {
initRefresh() {
if (isEmpty(this.dataList)) {
this.refreshList();
}
},
refreshList() {
let ref = this.$refs.list;
if (ref) {
ref.onPullRefreshing();
}
},
changeData(e) {
this.dataList = e.detail.data.map(item => {
return new ReadHistory(item)
})
},
tapItem(e, index) {
let item = e.detail.data;
gotoBookContentPage(item.id);
},
changeCollection(e, index) {
let item = e.detail.data;
let target = e.detail.isCollection;
// TODO 发请求,改变收藏状态,success 后修改页面
collectionBook(target, item.id, (success, data) => {
if (success) {
item.isCollect = target;
this.$set(this.dataList, index, new ReadHistory(item));
noticeCollectionListChange(item.id, target);
}
})
}
}
}
</script>
<style lang="scss">
</style>
\ No newline at end of file
<template>
<view>
<book-bean-header id='header' :beanCount='beanCount'></book-bean-header>
<view id='title' class="c-flex_row c-align_center">
<view class="section-title">
书豆明细
</view>
<view class="recharge-button" @click="tapRecharge">
前往充值
</view>
</view>
<c-list ref='list' flag='bookBeanDetail' :needLogin="true" :height="listHeight"
url='//system/bookLegumesRecord/list' method="POST" bgColor="transparent" :param="{}"
@change='changeData'>
<view class="record-item" v-for='(item, index) in dataList' :key="index" @click="tapRecord(item)">
<view class="row">
<view class="desc">
{{item.recordDescription}}
</view>
<view class="value">
{{item.bookLegumes}}
</view>
</view>
<view class="row">
<view class="time">
{{item.createTime}}
</view>
</view>
</view>
</c-list>
<c-login></c-login>
</view>
</template>
<script>
import BookBeanHeader from "./components/book-bean-header.vue"
import SystemInfoMixin from "../../../common/mixins/system-info-mixin.js";
import {
watchUserInfoChange,
removeUserInfoChangeWatch,
} from "../../../common/services/userServices.js"
import BookBeanRecordItem from "./models/BookBeanRecordItem";
import {
gotoBookBeanRechargePage,
gotoBookContentPage
} from "../../../common/services/page-route";
export default {
mixins: [SystemInfoMixin],
components: {
BookBeanHeader
},
data() {
return {
userInfo: null,
listHeight: 0,
dataList: []
}
},
computed: {
beanCount: function() {
if (this.userInfo) return this.userInfo.bookLegumes;
return 0;
}
},
onReady() {
watchUserInfoChange((info) => {
this.userInfo = info.userInfo
// 已登录,自动刷新
if (this.userInfo) {
this.refreshList();
}
}, this)
this.$nextTick(() => {
this.initListHeight();
})
},
onUnload() {
removeUserInfoChangeWatch(this);
},
methods: {
// 初始化列表高度
initListHeight() {
const query = uni.createSelectorQuery().in(this);
query.select("#header").boundingClientRect();
query.select("#title").boundingClientRect();
query.exec((res) => {
let result = this.windowHeight;
res.forEach(item => {
result = result - item.height;
})
this.listHeight = result;
})
},
// 开始列表刷新
refreshList() {
let ref = this.$refs.list;
if (ref) {
ref.onPullRefreshing();
}
},
// 列表数据变动
changeData(e) {
this.dataList = e.detail.data.map(item => {
return new BookBeanRecordItem(item);
})
},
// 点击充值
tapRecharge() {
gotoBookBeanRechargePage()
},
// 点击记录
tapRecord(record) {
if (record.articleId) {
gotoBookContentPage(record.articleId);
}
}
}
}
</script>
<style lang="scss" scoped>
.section-title {
flex: 1;
margin: 20rpx 30rpx;
font-weight: 700;
}
.recharge-button {
margin: 20rpx 30rpx;
height: 60rpx;
line-height: 60rpx;
width: 150rpx;
font-size: 28rpx;
background: #fff6cd;
color: #b26605;
border-radius: 30rpx;
text-align: center;
}
.record-item {
margin: 30rpx;
margin-bottom: 0;
background: #fff;
.row {
margin: 20rpx;
margin-bottom: 0;
display: flex;
flex-direction: row;
align-items: center;
}
.row:last-child {
margin-bottom: 20rpx;
}
.desc {
font-size: 30rpx;
color: #333;
flex: 1;
}
.value {
font-size: 36rpx;
color: #ff2525;
font-weight: 700;
}
.value::before {
content: "-"
}
.time {
font-size: 26rpx;
color: #999;
}
}
.recommond-item:last-child {
margin-bottom: 30rpx;
}
</style>
\ No newline at end of file
<template>
<view class="book-bean-header">
<view class="bg">
<view class="bg-1 bg-cricle"></view>
<view class="bg-2 bg-cricle"></view>
<view class="bg-3 bg-cricle"></view>
<view class="bean-logo">
<image class="bean" :src="beanLogoSrc" mode="aspectFit"></image>
</view>
</view>
<view class="content">
<view class="title row" v-if='ready'>
书豆余额
</view>
<view class="value row" v-if='ready'>
{{beanCount}}
</view>
<view class="footter row" v-if='ready'>
<slot></slot>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
beanLogoSrc: {
type: String,
default: "/static/images/book-bean/book-bean-2.png"
},
beanCount: {
type: Number,
default: 0
}
},
data: function() {
return {
ready: false
}
},
mounted() {
setTimeout(() => {
this.ready = true
}, 500)
}
}
</script>
<style lang="scss" scoped>
.book-bean-header {
margin: 30rpx;
border-radius: 40rpx;
background: transparent;
box-shadow: 0 20rpx 20rpx #88888844;
position: relative;
.bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff6cd;
border-radius: 40rpx;
overflow: hidden;
.bg-cricle {
position: absolute;
bottom: 0;
right: 0;
border-radius: 50%;
transform: translate(50%, 50%);
z-index: 0;
}
.bg-1 {
height: 900rpx;
width: 900rpx;
background: #fff4b0;
}
.bg-2 {
height: 700rpx;
width: 700rpx;
background: #ffefaf;
}
.bg-3 {
height: 500rpx;
width: 500rpx;
background: #ffef9c;
}
.bean-logo {
position: absolute;
padding: 40rpx;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.bean {
width: 200rpx;
height: 200rpx;
}
}
}
.content {
padding: 60rpx 40rpx;
color: #372705;
position: relative;
min-height: 200rpx;
background: transparent;
.row {
margin-bottom: 30rpx;
z-index: 5;
}
.row:last-child {
margin-bottom: 0;
}
.title {}
.value {
font-size: 56rpx;
font-weight: 700;
}
.footer {}
}
}
</style>
\ No newline at end of file
export default class BookBeanRecordItem {
constructor(param) {
const {
id,
userId,
articleId,
bookLegumes,
recordDescription,
createTime
} = param || {}
this.id = id;
this.userId = userId;
this.articleId = articleId;
this.bookLegumes = bookLegumes;
this.recordDescription = recordDescription;
this.createTime = createTime;
}
}
\ No newline at end of file
<template>
<view>
<book-bean-header id='header' beanLogoSrc='/static/images/book-bean/book-bean-1.png' :beanCount='beanCount'>
<view class="desc">
使用书豆可以单独购买指定书籍
</view>
</book-bean-header>
<view class="section">
<view class="title">
选择套餐
</view>
<view class="pack-box">
<view class="pack-item" :class="[{active: index==selectedIndex}]" v-for='(item, index) in packData'
:key='index' @click="choosePack(item, index)">
<view class="name row">
{{item.title}}
</view>
<view class="price row">
{{item.price}}
</view>
<view class="cut-down" v-if='item.giveNumber'>
赠送 {{item.giveNumber}} 书豆
</view>
</view>
</view>
</view>
<view class="section">
<button class="c-button_clear c-button-size_lg c-button-width_full apply-button" :disabled="loading"
:loading="loading" @click="tapPay">立即购买</button>
</view>
<c-login></c-login>
</view>
</template>
<script>
import {
watchUserInfoChange,
removeUserInfoChangeWatch,
showLoginView,
refreshUserInfo
} from "../../../common/services/userServices.js"
import BookBeanHeader from "../book-bean-detail/components/book-bean-header.vue"
import {
getBookBeanPackData,
getOpenId,
getPayInfo,
ENUM_PAY_TYPE
} from "../../../common/services/index.js"
import PayInfo from "../../../common/models/PayInfo.js"
import BookBeanPack from "../../../common/models/BookBeanPack.js"
import {
toastMessage
} from "../../../common/utils/toastUtil.js";
export default {
components: {
BookBeanHeader
},
data() {
return {
userInfo: null,
packData: [],
selectedIndex: 0,
loading: false
}
},
computed: {
beanCount: function() {
if (this.userInfo) return this.userInfo.bookLegumes;
return 0;
}
},
onReady() {
watchUserInfoChange((info) => {
this.userInfo = info.userInfo;
}, this)
uni.startPullDownRefresh({
})
},
onUnload() {
removeUserInfoChangeWatch(this);
},
onPullDownRefresh() {
this.requestPackData();
},
methods: {
requestPackData() {
getBookBeanPackData((success, data) => {
uni.stopPullDownRefresh()
if (success) {
this.packData = data.map(item => {
return new BookBeanPack(item)
})
}
})
},
choosePack(item, index) {
this.selectedIndex = index;
},
tapPay() {
let isIOS = uni.getSystemInfoSync().platform == "ios" && false
if (isIOS) {
uni.showModal({
title: "提示",
content: "由于相关规范,iOS功能暂不可用"
})
} else {
if (!this.userInfo) {
uni.showModal({
title: "登录",
content: "购买前请前往登录系统",
success: (res) => {
if (res.confirm) {
showLoginView()
}
}
})
return;
}
if (this.loading) return;
let pack = this.packData[this.selectedIndex];
this.loading = true;
let sysLoginFn = (successCB) => {
uni.login({
provider: "weixin",
onlyAuthorize: true,
success: (res) => {
if (res) {
if (typeof successCB == 'function') successCB(res);
} else {
this.loading = false;
}
},
fail: (error) => {
this.loading = false;
}
})
}
let getOpenIdFn = (code, successCB) => {
getOpenId(code, (success, data) => {
if (success) {
if (typeof successCB == 'function') successCB(data);
} else {
this.loading = false;
}
})
}
let getPayInfoFn = (openId, successCB) => {
getPayInfo(pack.id, pack.price, openId, (success, data) => {
if (success) {
if (typeof successCB == 'function') successCB(new PayInfo(data));
} else {
this.loading = false;
}
}, ENUM_PAY_TYPE.BEAN.value)
}
let payOrderFn = (payInfo, successCB) => {
uni.requestPayment({
timeStamp: payInfo.timeStamp,
nonceStr: payInfo.nonceStr,
package: payInfo.packageStr,
signType: payInfo.signType,
paySign: payInfo.paySign,
success: (res) => {
if (typeof successCB == 'function') successCB(res);
},
fail: (error) => {
this.loading = false;
}
})
}
sysLoginFn((code) => {
getOpenIdFn(code.code, (openId) => {
getPayInfoFn(openId, (payInfo) => {
payOrderFn(payInfo, (data) => {
this.loading = false;
toastMessage('购买成功')
refreshUserInfo();
})
})
})
})
}
}
}
}
</script>
<style lang="scss" scoped>
.desc {
font-size: 24rpx;
}
.section {
padding: 0 30rpx;
display: flex;
flex-direction: column;
background: transparent;
.title {
font-size: 32rpx;
font-weight: 700;
color: #333;
margin-bottom: 30rpx;
}
.pack-box {
margin-top: 25rpx;
margin-left: 40rpx;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
.active {
border: 6rpx solid #fd5350 !important;
}
.pack-item {
margin-bottom: 25rpx;
margin-right: 40rpx;
width: calc(31% - 40rpx);
height: 200rpx;
display: flex;
flex-direction: column;
justify-content: space-around;
background: #fff;
border: 6rpx solid #fff;
border-radius: 10rpx;
position: relative;
.row {
margin: 8rpx 15rpx;
marign-bottom: 0;
}
.row:last-child {
margin-bottom: 8rpx;
}
.name {
font-size: 26rpx;
font-weight: 700;
color: #333;
}
.price {
font-size: 30rpx;
color: #fd5350;
font-weight: 700;
}
.origin {
font-size: 22rpx;
color: #333;
text-decoration: line-through;
}
.cut-down {
position: absolute;
top: 0;
right: 0;
color: #fff;
background: #ff502f;
font-size: 22rpx;
border-radius: 15rpx;
height: 30rpx;
line-height: 30rpx;
padding: 0 10rpx;
transform: translate(0, -50%);
}
}
}
.apply-button {
border-radius: 50rpx;
background: #e8c8ae;
color: #8d5a29;
margin: 30rpx auto;
}
}
</style>
\ No newline at end of file
<template>
<view>
<view style="margin: 24rpx;font-size: 30rpx;color: black;">请输入要反馈的内容:</view>
<view style="margin: 20rpx;">
<uni-easyinput type="textarea" @input="input" v-model="inputClearValue" placeholder="请留下您的批评,表扬或者建议,我们会虚心听取,认真改正."></uni-easyinput>
</view>
<button class="apply-button" @click="cmtFeedback()">提交</button>
</view>
</template>
<script>
import {
toastMessage
} from "../../../common/utils/toastUtil.js";
import {
getFeedback
} from "../../../common/services/index.js";
export default {
data() {
return {
inputClearValue: ''
};
},
methods: {
input(e) {
this.inputClearValue = e;
},
cmtFeedback(){
if(this.inputClearValue.length==0){
toastMessage('请输入内容')
return;
}
getFeedback(this.inputClearValue,(success, data) => {
if (success) {
toastMessage('反馈成功');
this.inputClearValue = '';
}
})
}
}
}
</script>
<style>
.apply-button {
border-radius: 50rpx;
background: #e8c8ae;
color: #8d5a29;
margin: 60rpx;
}
</style>
\ No newline at end of file
<template>
<view>
<c-tabs id='tabs' :tabs="typeList" :value='typeIndex' nameKey='name' @change='changeType'></c-tabs>
<c-list :height="listHeight" :ableRefresh='false' :ableLoadMore="false" :needLogin='true'></c-list>
<c-login></c-login>
</view>
</template>
<script>
import {
ENUM_MESSAGE_PAGE_TYPE
} from '../../../static/enums/enum_value';
export default {
data() {
return {
typeList: Object.keys(ENUM_MESSAGE_PAGE_TYPE).map(key => {
return ENUM_MESSAGE_PAGE_TYPE[key]
}),
typeValue: ENUM_MESSAGE_PAGE_TYPE.SYS.value,
typeIndex: 0,
listHeight: 0
};
},
watch: {
typeValue: {
handler: function(n) {
this.typeList.forEach((item, index) => {
if (item.value == n) {
this.typeIndex = index;
uni.setNavigationBarTitle({
title: item.name
})
}
})
}
}
},
onReady() {
uni.setNavigationBarTitle({
title: ENUM_MESSAGE_PAGE_TYPE.SYS.name
})
const eventChannel = this.getOpenerEventChannel();
eventChannel.on("openMessagePage", (info) => {
this.typeValue = info.type;
})
this.initListHeight();
},
methods: {
initListHeight() {
const query = uni.createSelectorQuery().in(this);
query.select("#tabs").boundingClientRect().exec((res) => {
let result = uni.getSystemInfoSync().windowHeight;
res.forEach(item => {
if (item && item.height) {
result = result - item.height
}
})
this.listHeight = result;
})
},
changeType(e) {
this.typeValue = e.value;
}
}
}
</script>
<style lang="scss">
</style>
\ No newline at end of file
<template>
<view>
<view class="sex-box" :class="{active: maleActive}" @click="tapSexBox('male')">
<view class="content-box">
<view class="title-box">
<view class="title">
男生小说
</view>
</view>
<image class="logo" src="/static/images/read-preference/male.png" mode="aspectFit"></image>
</view>
<view class="selected-icon" v-show="maleActive">
<uni-icons type='checkbox-filled' size='32' color="#FECF02"></uni-icons>
</view>
</view>
<view class="sex-box" :class="{active: femaleActive}" @click="tapSexBox('female')">
<view class="content-box">
<view class="title-box">
<view class="title">
女生小说
</view>
</view>
<image class="logo" src="/static/images/read-preference/female.png" mode="aspectFit"></image>
</view>
<view class="selected-icon" v-show="femaleActive">
<uni-icons type='checkbox-filled' size='32' color="#FECF02"></uni-icons>
</view>
</view>
<c-login></c-login>
</view>
</template>
<script>
import {
readStorage,
saveStorage
} from '../../../common/utils/storageUtil';
import {
ENUM_SEX_TYPE
} from '../../../static/enums/enum_value';
export default {
computed: {
maleActive: function() {
return this.sex == ENUM_SEX_TYPE.MALE.value
},
femaleActive: function() {
return this.sex == ENUM_SEX_TYPE.FEMALE.value
}
},
data() {
return {
sex: ENUM_SEX_TYPE.MALE.value
};
},
onReady() {
let storageResult = readStorage('readPreference');
if (!storageResult) {
this.sex = ENUM_SEX_TYPE.MALE.value
} else {
this.sex = storageResult;
}
},
methods: {
tapSexBox(type) {
switch (type) {
case 'male':
this.sex = ENUM_SEX_TYPE.MALE.value
break;
case 'female':
this.sex = ENUM_SEX_TYPE.FEMALE.value
break;
default:
break;
}
saveStorage('readPreference', this.sex);
uni.showToast({
title: "设置成功"
})
}
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #fff;
}
.sex-box {
position: relative;
width: 270rpx;
height: 270rpx;
border-radius: 135rpx;
background: #e4e4e4;
margin: 100rpx auto 0;
.content-box {
width: 260rpx;
height: 260rpx;
border-radius: 130rpx;
overflow: hidden;
display: flex;
flex-direction: column;
border: 5rpx solid #f2f5f8;
.title-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.title {
font-size: 30rpx;
color: #333;
text-align: center;
}
}
.logo {
width: 150rpx;
height: 150rpx;
margin: auto;
}
}
.selected-icon {
position: absolute;
bottom: 0;
right: 0;
border-radius: 50%;
background: #fff;
}
}
.sex-box:first-child {
margin-top: 150rpx;
}
.active {
border: 5rpx solid #FECF02 !important;
background: #f2f5f8;
}
</style>
\ No newline at end of file
<template>
<uni-popup ref='pop' type="center" :is-mask-click='false' :safe-area='false' @maskClick='tapMask'>
<view class="input-box">
<view class="row">
<uni-easyinput v-model="inputValue" :autoHeight='true' :trim='true' :maxlength="maxLength"
:placeholder="placeholder" />
</view>
<view class="bottom-button-box row">
<button class="c-button_clear c-button-size_base c-button-width_half cancel"
@click="tapMask">取消编辑</button>
<button class="c-button_clear c-button-size_base c-button-width_half confirm"
@click="confirm">确认编辑</button>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ""
},
placeholder: {
type: String,
default: ""
},
type: {
type: String,
default: ""
},
maxLength: {
type: Number,
default: 35
},
show: {
type: Boolean,
default: false
}
},
data: function() {
return {
showPop: false,
inputValue: "",
}
},
watch: {
show: function(n) {
this.showPop = n;
},
showPop: function(n, o) {
if (n == o) return;
if (n) {
this.open();
} else {
this.close();
}
},
value: {
handler: function(n) {
this.inputValue = n;
},
immediate: true
}
},
methods: {
open() {
this.$refs.pop.open();
},
close() {
this.$emit('close')
this.$refs.pop.close();
},
tapMask() {
this.showPop = false;
},
confirm() {
this.$emit("changeValue", {
detail: {
value: this.inputValue,
type: this.type
}
})
this.tapMask();
}
},
}
</script>
<style lang="scss" scoped>
.input-box {
display: flex;
flex-direction: column;
justify-content: space-around;
padding: 30rpx;
background: #fff;
border-radius: 20rpx;
min-width: 600rpx;
min-height: 400rpx;
.row {
margin-bottom: 20rpx;
}
.row:last-child {
margin-bottom: 0;
}
.bottom-button-box {
display: flex;
flex-direction: row;
align-items: center;
.cancel {
background: #dd524d;
color: #fff;
}
.confirm {
background: #FECF02;
color: #333;
}
}
}
</style>
\ No newline at end of file
import {
apiPOST,
apiUPLOAD
} from "../../../../common/utils/apiRequest.js"
import {
getSystemDict,
ENUM_DICT_NAME
} from "../../../../common/services/index.js"
import config from "../../../../config/index.js";
function editUserInfo(userInfo, callback) {
let data = {
...userInfo,
nickname: userInfo.nickName,
avatar: userInfo.avater,
intro: userInfo.sign,
status: userInfo.status,
sex: userInfo.sex
}
apiPOST({
url: `/system/user/update`,
data,
callback
})
}
import {
readToken
} from "../../../../common/services/userServices.js"
function uploadAvater(filePath, callback) {
const fileManager = uni.getFileSystemManager();
fileManager.readFile({
filePath,
position: 0,
success(res) {
console.log(res.data)
uni.request({
url: `${config["BASE_URL"]}/file/upload`,
header: {
Authorization: `${readToken()}`,
"content-type": "multipart/form-data;",
},
data: res.data,
method: "POST"
})
},
})
// apiUPLOAD({
// url: `/file/upload`,
// filePath,
// name: 'file',
// callback
// })
}
function getSexDict(callback) {
getSystemDict([ENUM_DICT_NAME.SEX], (success, data) => {
if (success) {
if (typeof callback == 'function') callback(true, data[ENUM_DICT_NAME.SEX].list)
} else {
if (typeof callback == 'function') callback(false)
}
});
}
module.exports = {
editUserInfo,
uploadAvater,
getSexDict
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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