first commit
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeshop开源商城系统
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee
|
||||
// | github下载:https://github.com/likeshop-github
|
||||
// | 访问官网:https://www.likeshop.cn
|
||||
// | 访问社区:https://home.likeshop.cn
|
||||
// | 访问手册:http://doc.likeshop.cn
|
||||
// | 微信公众号:likeshop技术社区
|
||||
// | likeshop系列产品在gitee、github等公开渠道开源版本可免费商用,未经许可不能去除前后端官方版权标识
|
||||
// | likeshop系列产品收费版本务必购买商业授权,购买去版权授权后,方可去除前后端官方版权标识
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布
|
||||
// | likeshop团队版权所有并拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeshop.cn.team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
<template>
|
||||
<view :class="'active-area ' + type">
|
||||
<view class="a-header">
|
||||
<slot name="header"></slot>
|
||||
</view>
|
||||
<view class="public-class a-main">
|
||||
<slot name="main-header"></slot>
|
||||
<view v-if="type == 'hot'">
|
||||
<goods-list type="home-hot" :list="lists"></goods-list>
|
||||
</view>
|
||||
<scroll-view v-else style="white-space: nowrap;" scroll-x="true" scroll-with-animation="true">
|
||||
<navigator v-for="(item, index) in lists" :key="index"
|
||||
:style="{background: type == 'seckill' && '#f8f8f8'}" class="goods-item bg-white" hover-class="none"
|
||||
:url="'/pages/goods_details/goods_details?id=' + item.id">
|
||||
<custom-image width="240rpx" height="240rpx" radius="10rpx" lazy-load :src="item.image">
|
||||
</custom-image>
|
||||
<view style="padding: 8rpx">
|
||||
<view class="goods-name line1">{{item.name}}</view>
|
||||
<view class="row wrap">
|
||||
<price-format class="mr10" :weight="500" :price="item.seckill_price || item.price" :first-size="32"
|
||||
:second-size="22" :subscript-size="22" color="#FF2C3C"></price-format>
|
||||
<price-format :price="item.min_price || item.market_price" color="#999999" :second-size="22"
|
||||
:first-size="22" :subscript-size="22" :line-through="true"></price-format>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
trottle,
|
||||
getRect
|
||||
} from "@/utils/tools";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentSwiper: 0,
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
lists: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"lists": function(value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
beforeMount: function() {
|
||||
|
||||
},
|
||||
destroyed: function() {},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.active-area {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% auto;
|
||||
border-radius: 10rpx;
|
||||
padding-bottom: 20rpx;
|
||||
&.seckill {
|
||||
background-color: $-color-white;
|
||||
background-image: url(../../static/images/home_seckill_bg.png);
|
||||
}
|
||||
|
||||
.a-main {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
.goods-item {
|
||||
width: 240rpx;
|
||||
margin-left: 16rpx;
|
||||
display: inline-block;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<view class="address row bg-white">
|
||||
<image class="icon-md mr20" src="/static/images/icon_address.png"></image>
|
||||
<view class="flex1 mr20">
|
||||
<view class="black md" v-if="!address.contact && isSelect">设置收货地址</view>
|
||||
<view v-else>
|
||||
<text class="name md mr10">{{address.contact}}</text>
|
||||
<text class="phone md">{{address.telephone}}</text>
|
||||
<view class="area sm mt10 lighter">
|
||||
{{address.province}} {{address.city}} {{address.district}} {{address.address}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<image v-if="isSelect" class="icon-sm" src="/static/images/arrow_right.png"></image>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props:{
|
||||
isSelect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
address: {
|
||||
type: [Object, Array],
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.address {
|
||||
min-height: 164rpx;
|
||||
padding: 0 24rpx;
|
||||
border-radius: 14rpx;
|
||||
margin: 20rpx 20rpx 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,423 @@
|
||||
<template>
|
||||
<view>
|
||||
<view>
|
||||
<view class="sale-list" v-if="type == 'normal'">
|
||||
<view
|
||||
v-for="(items, index) in lists"
|
||||
:key="index"
|
||||
class="sale-item bg-white mt20"
|
||||
>
|
||||
<!-- <view class="sale-header row">
|
||||
<view class="store-name nr ml10 normal">成交时间: {{items.time}}</view>
|
||||
</view> -->
|
||||
<view
|
||||
v-for="(item, index2) in items.order_goods"
|
||||
:key="index2"
|
||||
class="goods-item"
|
||||
>
|
||||
<view class="sale-goods-show row">
|
||||
<view class="goods-img">
|
||||
<custom-image
|
||||
width="100%"
|
||||
height="100%"
|
||||
radius="6rpx"
|
||||
lazy-load
|
||||
:src="item.image"
|
||||
/>
|
||||
</view>
|
||||
<view class="goods-desc">
|
||||
<view class="goods-name line2 nr">{{ item.goods_name }}</view>
|
||||
<view class="row-between mt20">
|
||||
<price-format
|
||||
class="sm"
|
||||
:firstSize="30"
|
||||
:secondSize="30"
|
||||
:showSubscript="true"
|
||||
:subscriptSize="26"
|
||||
:price="item.goods_price"
|
||||
/>
|
||||
<view class="nr">x{{ item.goods_num }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
:class="
|
||||
'sale-footer row-end ' +
|
||||
(items.after_sale.able_apply == 1 ? '' : 'bottom-opacity')
|
||||
"
|
||||
>
|
||||
<view
|
||||
class="btn row-center bd-primary primary br60"
|
||||
@click="
|
||||
goPage(
|
||||
$event,
|
||||
items.after_sale.able_apply,
|
||||
'/bundle/pages/apply_refund/apply_refund',
|
||||
items.order_id,
|
||||
item.item_id
|
||||
)
|
||||
"
|
||||
>
|
||||
申请售后
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="sale-list" v-else-if="type == 'apply'">
|
||||
<view
|
||||
v-for="(items, index) in lists"
|
||||
:key="index"
|
||||
class="sale-item bg-white mt20"
|
||||
>
|
||||
<view class="sale-header row-between">
|
||||
<view class="row">
|
||||
<view class="store-name nr ml10 mr10"
|
||||
>申请时间: {{ items.time }}</view
|
||||
>
|
||||
</view>
|
||||
<view class="primary nr">{{ items.after_sale.type_text }}</view>
|
||||
</view>
|
||||
<navigator
|
||||
v-for="(item, index2) in items.order_goods"
|
||||
:key="index2"
|
||||
hover-class="none"
|
||||
class="sale-goods-show"
|
||||
:url="
|
||||
'/bundle/pages/after_sales_detail/after_sales_detail?afterSaleId=' +
|
||||
items.after_sale.after_sale_id +
|
||||
'&order_id=' +
|
||||
items.order_id
|
||||
"
|
||||
>
|
||||
<view class="row">
|
||||
<view class="goods-img">
|
||||
<custom-image
|
||||
width="100%"
|
||||
height="100%"
|
||||
radius="6rpx"
|
||||
lazy-load
|
||||
:src="item.image"
|
||||
/>
|
||||
</view>
|
||||
<view class="goods-desc">
|
||||
<view class="goods-name line2 nr">{{ item.goods_name }}</view>
|
||||
<view class="row-between mt20">
|
||||
<view>
|
||||
<price-format
|
||||
:firstSize="26"
|
||||
:price="item.goods_price"
|
||||
weight="600"
|
||||
:showSubscript="true"
|
||||
/>
|
||||
</view>
|
||||
<view class="nr">x{{ item.goods_num }}</view>
|
||||
</view>
|
||||
<view class="row-between mt20">
|
||||
<view>
|
||||
<price-format
|
||||
:firstSize="26"
|
||||
:price="items.after_sale.refund_price"
|
||||
weight="600"
|
||||
:showSubscript="true"
|
||||
color="red"
|
||||
/>
|
||||
</view>
|
||||
<view class="nr">退款金额</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="sale-status mt20 row">
|
||||
<view class="nr" style="font-weight: bold">申请状态</view>
|
||||
<view class="nr ml20">{{ items.after_sale.desc }}</view>
|
||||
</view>
|
||||
</navigator>
|
||||
<view class="sale-footer row-end">
|
||||
<view
|
||||
class="row-center normal br60 mr20 grey-btn nr"
|
||||
@tap="showDialog(items.after_sale.after_sale_id)"
|
||||
>撤销申请</view
|
||||
>
|
||||
<navigator
|
||||
hover-class="none"
|
||||
:url="
|
||||
'/bundle/pages/input_express_info/input_express_info?id=' +
|
||||
items.after_sale.after_sale_id
|
||||
"
|
||||
class="row-center normal br60 grey-btn nr"
|
||||
:hidden="items.after_sale.status != 2"
|
||||
>填写快递单号
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="sale-list" v-else>
|
||||
<navigator
|
||||
v-for="(items, index) in lists"
|
||||
:key="index"
|
||||
hover-class="none"
|
||||
class="sale-item bg-white mt20"
|
||||
:url="
|
||||
'/bundle/pages/after_sales_detail/after_sales_detail?afterSaleId=' +
|
||||
items.after_sale.after_sale_id
|
||||
"
|
||||
>
|
||||
<view class="sale-header row-between">
|
||||
<view class="row">
|
||||
<!-- <image style="width: 40rpx;height: 40rpx" src="/images/icon_shop.png"></image> -->
|
||||
<view class="store-name nr ml10 mr10"
|
||||
>申请时间: {{ items.time }}</view
|
||||
>
|
||||
</view>
|
||||
<view class="primary nr">{{ items.after_sale.type_text }}</view>
|
||||
</view>
|
||||
<view
|
||||
v-for="(item, index2) in items.order_goods"
|
||||
:key="index2"
|
||||
class="sale-goods-show"
|
||||
>
|
||||
<view class="row">
|
||||
<view class="goods-img">
|
||||
<custom-image
|
||||
lazy-load
|
||||
width="100%"
|
||||
height="100%"
|
||||
radius="6rpx"
|
||||
:src="item.image"
|
||||
/>
|
||||
</view>
|
||||
<view class="goods-desc">
|
||||
<view class="goods-name line2 nr">{{ item.goods_name }}</view>
|
||||
<view class="row-between mt20 row-between">
|
||||
<price-format
|
||||
:firstSize="26"
|
||||
:price="item.goods_price"
|
||||
weight="600"
|
||||
:showSubscript="true"
|
||||
/>
|
||||
<view class="nr">x{{ item.goods_num }}</view>
|
||||
</view>
|
||||
<view class="row-between mt20">
|
||||
<view>
|
||||
<price-format
|
||||
:firstSize="26"
|
||||
:price="items.after_sale.refund_price"
|
||||
weight="600"
|
||||
:showSubscript="true"
|
||||
color="red"
|
||||
/>
|
||||
</view>
|
||||
<view class="nr">退款金额</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="sale-status mt20 row">
|
||||
<view class="nr" style="font-weight: bold">申请状态</view>
|
||||
<view class="nr ml20">{{ items.after_sale.desc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
</view>
|
||||
<loading-footer :status="loadingStatus" slotEmpty>
|
||||
<view class="data-null column-center" slot="empty">
|
||||
<image class="img-null" src="/static/images/order_null.png"></image>
|
||||
<text class="nr muted">暂无售后数据~</text>
|
||||
</view>
|
||||
</loading-footer>
|
||||
</view>
|
||||
<u-modal
|
||||
v-model="confirmDialog"
|
||||
confirm-text="确定"
|
||||
:showCancelButton="true"
|
||||
:show-title="false"
|
||||
confirm-color="#FF2C3C"
|
||||
@confirm="cancelApplyFun"
|
||||
@cancel="hideDialog"
|
||||
>
|
||||
<view class="column-center tips-dialog" style="padding: 20rpx 0">
|
||||
<image class="icon-lg" src="/static/images/icon_warning.png"></image>
|
||||
<view style="margin-top: 30rpx">是否要撤销申请?</view>
|
||||
</view>
|
||||
</u-modal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | LikeShop100%开源免费商用电商系统
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,保留版权即可
|
||||
// | 商业版本务必购买商业授权,以免引起法律纠纷
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布
|
||||
// | Gitee下载:https://gitee.com/likeshop_gitee/likeshop
|
||||
// | 访问官网:https://www.likemarket.net
|
||||
// | 访问社区:https://home.likemarket.net
|
||||
// | 访问手册:http://doc.likemarket.net
|
||||
// | 微信公众号:好象科技
|
||||
// | 好象科技开发团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: LikeShopTeam
|
||||
// +----------------------------------------------------------------------
|
||||
import { AfterSaleType, loadingType } from "@/utils/type";
|
||||
import {
|
||||
getAfterSaleList,
|
||||
cancelApply,
|
||||
afterSaleDetail,
|
||||
applyAgain,
|
||||
} from "@/api/user";
|
||||
import { loadingFun } from "@/utils/tools";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
lists: [],
|
||||
page: 1,
|
||||
loadingStatus: loadingType.LOADING,
|
||||
confirmDialog: false,
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: AfterSaleType.NORMAL,
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
console.log("type", this.type);
|
||||
uni.$on("refreshsale", () => {
|
||||
this.reflesh();
|
||||
});
|
||||
},
|
||||
|
||||
beforeMount() {
|
||||
this.getAfterSaleListFun();
|
||||
},
|
||||
destroyed: function () {
|
||||
uni.$off("refreshsale");
|
||||
},
|
||||
methods: {
|
||||
cancelApplyFun() {
|
||||
console.log(this.id, "id");
|
||||
cancelApply({
|
||||
id: this.id,
|
||||
}).then((res) => {
|
||||
if (res.code == 1) {
|
||||
this.$toast({
|
||||
title: res.msg,
|
||||
});
|
||||
uni.$emit("refreshsale");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getAfterSaleListFun() {
|
||||
let { lists, loadingStatus, page } = this;
|
||||
loadingFun(getAfterSaleList, page, lists, loadingStatus, {
|
||||
type: this.type,
|
||||
}).then((res) => {
|
||||
console.log(res, "res");
|
||||
if (res) {
|
||||
this.page = res.page;
|
||||
this.loadingStatus = res.status;
|
||||
this.lists = res.dataList;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
goPage(e, able_apply, url, order_id, item_id) {
|
||||
if (able_apply != 1) {
|
||||
return;
|
||||
}
|
||||
url = url + "?order_id=" + order_id + "&item_id=" + item_id;
|
||||
uni.navigateTo({
|
||||
url: url,
|
||||
});
|
||||
},
|
||||
|
||||
reflesh() {
|
||||
this.page = 1;
|
||||
this.lists = [];
|
||||
this.loadingStatus = loadingType.LOADING;
|
||||
this.getAfterSaleListFun();
|
||||
},
|
||||
|
||||
showDialog(id) {
|
||||
console.log(id, "showDialog");
|
||||
this.id = id;
|
||||
this.confirmDialog = true;
|
||||
},
|
||||
|
||||
hideDialog() {
|
||||
this.comfirmDialog = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.sale-list {
|
||||
/* padding: 20rpx 0; */
|
||||
.sale-item {
|
||||
.sale-goods-show {
|
||||
padding: 20rpx 24rpx;
|
||||
|
||||
.goods-img {
|
||||
height: 160rpx;
|
||||
width: 160rpx;
|
||||
}
|
||||
|
||||
.goods-desc {
|
||||
margin-left: 24rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sale-status {
|
||||
padding: 20rpx 40rpx;
|
||||
background-color: #f6f6f6;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.sale-header {
|
||||
padding: 20rpx 24rpx;
|
||||
|
||||
.store-name {
|
||||
font-family: PingFang SC;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.sale-footer {
|
||||
padding: 0 24rpx 22rpx;
|
||||
|
||||
.btn {
|
||||
padding: 9rpx 34rpx;
|
||||
font-family: PingFang SC;
|
||||
border: 1px solid $-color-primary;
|
||||
}
|
||||
|
||||
.grey-btn {
|
||||
border: 1px solid #cccccc;
|
||||
padding: 9rpx 34rpx;
|
||||
font-family: PingFang SC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-opacity {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.data-null {
|
||||
padding-top: 150rpx;
|
||||
}
|
||||
|
||||
.tips-dialog {
|
||||
height: 230rpx;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<view class="bargain-list-container">
|
||||
<view class="bargain-list mt20" v-for="(item, index) in lists" :key="item.id">
|
||||
<view class="header row-between">
|
||||
<view class="count-down-container row">
|
||||
<text class="muted xs" style="margin-left: 8rpx;">发起时间:{{item.create_time}}</text>
|
||||
</view>
|
||||
<view class="primary sm">
|
||||
{{item.status_text}}
|
||||
</view>
|
||||
</view>
|
||||
<navigator class="content row" hover-class="none" :url="'/bundle/pages/bargain_process/bargain_process?bargainId='+item.id">
|
||||
<custom-image width="180rpx" height="180rpx" :src="item.image" radius="10rpx" />
|
||||
<view class="goods-info">
|
||||
<view class="goods-name line2 nr normal">
|
||||
{{item.name}}
|
||||
</view>
|
||||
<view class="mt10 row">
|
||||
<view class="sm primary">
|
||||
已砍至<text class="sm" style="font-weight: 500;line-height: 48rpx;">¥<text class="xl">{{item.current_price}}</text></text>
|
||||
</view>
|
||||
<view class="xs muted ml20">
|
||||
原价<text style="text-decoration: line-through;"> ¥{{item.price}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="xxs" style="color: #FFA200;line-height: 30rpx;margin-top: 4rpx;">
|
||||
{{item.bargain_tips}}
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
<view class="footer row-end">
|
||||
<!-- <view hover-class="none" class="purchase-btn primary row-center br60 footer-btn" v-if="item.buy_btn" @click="onPurchaseClick(item.id, item.item_id)">
|
||||
直接购买
|
||||
</view> -->
|
||||
<navigator hover-class="none" :url="'/bundle/pages/bargain_process/bargain_process?bargainId='+item.id" class="bargain-btn footer-btn white row-center br60" v-if="item.btn_tips != ''">
|
||||
{{item.btn_tips}}
|
||||
</navigator>
|
||||
<!-- <navigator hover-class="none" class="bargain-btn white row-center br60 footer-btn" v-if="item.pay_btn" :url="'/pages/order_details/order_details?id=' + item.order_id">
|
||||
去支付
|
||||
</navigator> -->
|
||||
</view>
|
||||
</view>
|
||||
<loading-footer :status="loadingStatus" slotEmpty>
|
||||
<view class="data-null column-center" slot="empty">
|
||||
<image class="img-null" src="/static/images/order_null.png" />
|
||||
<view class="xs muted">
|
||||
暂无砍价记录~
|
||||
</view>
|
||||
</view>
|
||||
</loading-footer>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {loadingType} from '@/utils/type'
|
||||
import {getBargainActivityList} from "@/api/activity"
|
||||
import {loadingFun} from '@/utils/tools'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loadingStatus: loadingType.LOADING,
|
||||
page: 1,
|
||||
lists: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
bargainType: {
|
||||
type: Number,
|
||||
default: -1
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$getBargainActivityList()
|
||||
},
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
$getBargainActivityList() {
|
||||
let {page, lists, loadingStatus, bargainType} = this;
|
||||
loadingFun(getBargainActivityList, page, lists, loadingStatus, {type: bargainType}).then(res => {
|
||||
if(res) {
|
||||
this.page = res.page;
|
||||
this.lists = res.dataList;
|
||||
this.loadingStatus = res.status;
|
||||
}
|
||||
})
|
||||
},
|
||||
onPurchaseClick(bargainId, itemId) {
|
||||
let goods = [{
|
||||
num: 1,
|
||||
item_id: itemId
|
||||
}]
|
||||
let params = {
|
||||
goods
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pages/confirm_order/confirm_order?data=' + encodeURIComponent(JSON.stringify(params)) + "&bargain_launch_id=" + bargainId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.bargain-list-container {
|
||||
.bargain-list {
|
||||
background-color: $-color-white;
|
||||
.header {
|
||||
height: 80rpx;
|
||||
padding: 20rpx 20rpx;
|
||||
}
|
||||
.content {
|
||||
padding: 30rpx 24rpx 18rpx;
|
||||
.goods-info {
|
||||
margin-left: 24rpx;
|
||||
flex: 1;
|
||||
.goods-name {
|
||||
line-height: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
padding: 19rpx 24rpx;
|
||||
.bargain-btn {
|
||||
// 继续砍价 按钮
|
||||
height: 62rpx;
|
||||
padding: 0 44rpx;
|
||||
background-color: $-color-primary;
|
||||
}
|
||||
.purchase-btn {
|
||||
// 直接购买
|
||||
height: 62rpx;
|
||||
padding: 0 44rpx;
|
||||
background-color: rgba(255, 44, 60, 0.1);
|
||||
}
|
||||
.footer-btn {
|
||||
&:not(:last-child) {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.data-null {
|
||||
margin-top: 150rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<view class="bubble-tips-container" :style="{top: top, left: left}">
|
||||
<view class="bubble-content row" v-show="showBubble" v-for="item in currentList" :key="item.id">
|
||||
<image class="bubble-img" :src="item.user.avatar" />
|
||||
<view class="xs">
|
||||
{{item.template}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getBubbleLists
|
||||
} from "@/api/app";
|
||||
import Cache from "@/utils/cache"
|
||||
export default {
|
||||
name: "BubbleTips",
|
||||
props: {
|
||||
// 是否停止切换
|
||||
discharge: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: "40rpx"
|
||||
},
|
||||
left: {
|
||||
type: String,
|
||||
default: "20rpx"
|
||||
},
|
||||
updateTime: {
|
||||
type: Number,
|
||||
default: 5 * 60 * 1000
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
index: Cache.get("currentIndex") || 0,
|
||||
list: [],
|
||||
currentList: [],
|
||||
timer: null,
|
||||
showBubble: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
index(newValue, oldValue) {
|
||||
if (this.index - this.list.length >= 0) {
|
||||
this.showBubble = false;
|
||||
let timer = setTimeout(() => {
|
||||
Cache.set("currentIndex", 0)
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.fadeUpBubble();
|
||||
clearTimeout(timer)
|
||||
}, 2000)
|
||||
} else {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.fadeUpBubble();
|
||||
return;
|
||||
}
|
||||
},
|
||||
discharge() {
|
||||
// 为了让活性的页面停止计时器
|
||||
if (this.discharge) {
|
||||
// 停止
|
||||
Cache.set("currentIndex", this.index);
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
return false;
|
||||
} else {
|
||||
// 继续切换
|
||||
let currentInex = Cache.get("currentInex") || this.list.length;
|
||||
if (currentInex - this.list.length < 0) {
|
||||
if (this.timer) {
|
||||
setInterval(this.timer)
|
||||
this.timer = null;
|
||||
}
|
||||
this.fadeUpBubble()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
$getBubbleLists() {
|
||||
getBubbleLists().then(res => {
|
||||
if (res.code == 1) {
|
||||
this.list = res.data.lists;
|
||||
var requestTime = res.data.time * 1000;
|
||||
Cache.set("bubbleList", JSON.stringify(this.list), 300);
|
||||
Cache.set("requestTime", requestTime);
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
this.fadeUpBubble()
|
||||
}
|
||||
})
|
||||
},
|
||||
fadeUpBubble() {
|
||||
let requestTime = Cache.get("requestTime");
|
||||
let currentTime = new Date();
|
||||
this.showBubble = true;
|
||||
this.index = Cache.get("currentIndex") || 0;
|
||||
this.list = Cache.get("bubbleList") ? JSON.parse(Cache.get("bubbleList")) : [];
|
||||
if (currentTime.getTime() - requestTime >= this.updateTime) {
|
||||
this.$getBubbleLists();
|
||||
Cache.set("currentIndex", 0, 300)
|
||||
return;
|
||||
}
|
||||
this.timer = setInterval(() => {
|
||||
this.currentList = this.list.slice(this.index, this.index + 1);
|
||||
Cache.set("currentIndex", ++this.index);
|
||||
}, 4000);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
var index = Cache.get("currentIndex") || 0;
|
||||
var requestTime = Cache.get("requestTime");
|
||||
var currentTime = new Date();
|
||||
var currentList = Cache.get("bubbleList") ? JSON.parse(Cache.get("bubbleList")) : [];
|
||||
if (currentList.length <= 0) {
|
||||
this.$getBubbleLists();
|
||||
Cache.set("currentIndex", 0)
|
||||
} else if (index - currentList.length >= 0) {
|
||||
Cache.set("currentIndex", 0);
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.fadeUpBubble();
|
||||
} else if (currentTime.getTime() - requestTime >= this.updateTime) {
|
||||
this.$getBubbleLists();
|
||||
Cache.set("currentIndex", 0)
|
||||
} else {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
this.fadeUpBubble()
|
||||
}
|
||||
},
|
||||
onLoad() {},
|
||||
destroyed() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.bubble-tips-container {
|
||||
position: absolute;
|
||||
z-index: 98;
|
||||
|
||||
.bubble-content {
|
||||
padding: 4rpx 20rpx 4rpx 10rpx;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
border-radius: 120rpx;
|
||||
|
||||
.bubble-img {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<view class="cate-four row">
|
||||
<view class="aside">
|
||||
<scroll-view style="height: 100%" scroll-y="true" scroll-with-animation="true">
|
||||
<block v-for="(item, index) in cateList" :key="index">
|
||||
<view :class="'one-item sm ' + (index == selectIndex ? 'active' : '')" @click="changeActive(index)">
|
||||
<text class="name">{{ item.name }}</text>
|
||||
<view v-if="index == selectIndex" class="active-line bg-primary"></view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="main bg-body">
|
||||
<scroll-view style="height: 100%" scroll-y="true" scroll-with-animation="true" @scrolltolower="getGoodsSearchFun">
|
||||
<view class="main-wrap">
|
||||
<view style="border-radius: 10rpx;">
|
||||
<swipers :pid="4" height="200rpx" previous-margin="0" padding="20rpx 0 0" radius="10rpx">
|
||||
</swipers>
|
||||
</view>
|
||||
<view class="cate-two mt20" v-if="currentType">
|
||||
<view v-for="(sitem, sindex) in cateTwoList" :key="sindex" class="two-item bg-white mb20">
|
||||
<navigator class="title row-between" v-if="sitem.type == 1" hover-class="none"
|
||||
:url="`/pages/goods_search/goods_search?id=${sitem.id}&name=${sitem.name}&type=${sitem.type}`">
|
||||
<text class="name bold sm">{{sitem.name}}</text>
|
||||
<u-icon name="arrow-right"></u-icon>
|
||||
</navigator>
|
||||
<view class="title row-between" v-else>
|
||||
<text class="name bold sm">{{sitem.name}}</text>
|
||||
</view>
|
||||
<view class="three-list row wrap">
|
||||
<navigator v-for="(titem, tindex) in sitem.sons" :key="tindex"
|
||||
class="three-item column-center mb20" hover-class="none"
|
||||
:url="`/pages/goods_search/goods_search?id=${titem.id}&name=${titem.name}&type=${titem.type}`">
|
||||
<u-image mode="aspectFit" width="150rpx" height="150rpx" :src="titem.image">
|
||||
</u-image>
|
||||
<view class="text mt20 xs">{{ titem.name }}</view>
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods" v-else>
|
||||
<view class="row condition bg-white mt20">
|
||||
<view :class="'tag row-center ' + (comprehensive ? 'primary' : '')" @tap="onNormal">综合</view>
|
||||
<view class="tag row-center" data-type="priceSort" @tap="onPriceSort">
|
||||
<text :class="priceSort ? 'primary' : ''">价格</text>
|
||||
<view>
|
||||
<trigonometry direction="up" size="small" :color="priceSort == 'desc' ? '#FF5058' : '#333'">
|
||||
</trigonometry>
|
||||
<trigonometry size="small" :color="priceSort == 'asc' ? '#FF5058' : '#333'"></trigonometry>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tag row-center" data-type="saleSort" @tap="onSaleSort">
|
||||
<text :class="saleSort ? 'primary' : ''">销量</text>
|
||||
<view>
|
||||
<trigonometry direction="up" size="small" :color="saleSort == 'desc' ? '#FF5058' : '#333'">
|
||||
</trigonometry>
|
||||
<trigonometry size="small" :color="saleSort == 'asc' ? '#FF5058' : '#333'"></trigonometry>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-list">
|
||||
<navigator hover-class="none" class="row item bg-white mt20" v-for="(item, index) in goodsList" :key="index" :url="`/pages/goods_details/goods_details?id=${item.id}`">
|
||||
<u-image width="200rpx" height="200rpx" border-radius="14rpx" :src="item.image"></u-image>
|
||||
<view class="flex1 ml20 mr10">
|
||||
<view class="line2">{{item.name}}</view>
|
||||
<view class="muted" >
|
||||
<text class="xxs">原价</text> <price-format :subscriptSize="22" :firstSize="22" :secondSize="22" :price="item.market_price"></price-format>
|
||||
</view>
|
||||
<view class="primary mt10">
|
||||
<price-format :price="item.price" :subscriptSize="22" :firstSize="34" :secondSize="26"></price-format>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
</view>
|
||||
<loading-footer :status="status" :slot-empty="true">
|
||||
<view slot="empty" class="column-center" style="padding: 200rpx 0 0">
|
||||
<image class="img-null" src="/static/images/goods_null.png"></image>
|
||||
<text class="lighter sm">暂无商品</text>
|
||||
</view>
|
||||
</loading-footer>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
arraySlice,
|
||||
trottle,
|
||||
loadingFun
|
||||
} from '@/utils/tools'
|
||||
import {
|
||||
getGoodsSearch,
|
||||
} from '@/api/store';
|
||||
import {
|
||||
loadingType
|
||||
} from '@/utils/type';
|
||||
export default {
|
||||
name: "cate-four",
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page:1,
|
||||
status: loadingType.LOADING,
|
||||
selectIndex: 0,
|
||||
cateList: [],
|
||||
cateTwoList: [],
|
||||
goodsList: [],
|
||||
priceSort: '',
|
||||
saleSort: '',
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.onNormal = trottle(this.onNormal, 500, this);
|
||||
this.onPriceSort = trottle(this.onPriceSort, 500, this);
|
||||
this.onSaleSort = trottle(this.onSaleSort, 500, this);
|
||||
},
|
||||
methods: {
|
||||
changeActive(index) {
|
||||
const {
|
||||
cateList
|
||||
} = this
|
||||
this.selectIndex = index
|
||||
this.cateTwoList = cateList[this.selectIndex].sons || []
|
||||
this.onRefresh()
|
||||
},
|
||||
onRefresh() {
|
||||
this.page = 1
|
||||
this.goodsList = []
|
||||
this.status = loadingType.LOADING
|
||||
this.$nextTick(() => {
|
||||
this.getGoodsSearchFun();
|
||||
});
|
||||
},
|
||||
onNormal() {
|
||||
this.priceSort = ''
|
||||
this.saleSort = ''
|
||||
this.onRefresh();
|
||||
},
|
||||
|
||||
onPriceSort() {
|
||||
let {
|
||||
priceSort
|
||||
} = this;
|
||||
this.saleSort = ''
|
||||
this.priceSort = priceSort == 'asc' ? 'desc' : 'asc'
|
||||
this.onRefresh();
|
||||
},
|
||||
|
||||
onSaleSort() {
|
||||
let {
|
||||
saleSort
|
||||
} = this;
|
||||
this.priceSort = ''
|
||||
this.saleSort = saleSort == 'desc' ? 'asc' : 'desc'
|
||||
this.onRefresh();
|
||||
},
|
||||
async getGoodsSearchFun() {
|
||||
let {
|
||||
page,
|
||||
goodsList,
|
||||
priceSort,
|
||||
saleSort,
|
||||
status,
|
||||
cateList,
|
||||
selectIndex
|
||||
} = this;
|
||||
const item = cateList[selectIndex]
|
||||
if(item.type == 0) return
|
||||
if (status == loadingType.FINISHED) return;
|
||||
const params = {
|
||||
category_id: item.id,
|
||||
page_no: page,
|
||||
price: priceSort,
|
||||
sales_sum: saleSort
|
||||
}
|
||||
const data = await loadingFun(getGoodsSearch, page, goodsList, status, params)
|
||||
if (!data) return
|
||||
this.page = data.page
|
||||
this.goodsList = data.dataList
|
||||
this.status = data.status
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
handler(val) {
|
||||
if(!val.length) return
|
||||
let index = val.findIndex((item) => item.type == 1)
|
||||
|
||||
this.selectIndex = index == -1 ? 0 : index
|
||||
this.cateList = val
|
||||
this.cateTwoList = val[this.selectIndex] ? val[this.selectIndex].sons : []
|
||||
this.getGoodsSearchFun()
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
comprehensive() {
|
||||
const {
|
||||
priceSort,
|
||||
saleSort
|
||||
} = this
|
||||
if (priceSort == '' && saleSort == '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
currentType() {
|
||||
const {
|
||||
cateList,
|
||||
selectIndex
|
||||
} = this
|
||||
return cateList[selectIndex] ? cateList[selectIndex].type == 0 : true
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$header-height: 94rpx;
|
||||
.cate-four {
|
||||
height: calc(100vh - #{$header-height} - var(--window-top) - var(--window-bottom));
|
||||
background-color: #fff;
|
||||
|
||||
.aside {
|
||||
width: 180rpx;
|
||||
flex: none;
|
||||
height: 100%;
|
||||
|
||||
.one-item {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: 26rpx 10rpx;
|
||||
|
||||
&.active {
|
||||
color: $-color-primary;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.active-line {
|
||||
position: absolute;
|
||||
width: 6rpx;
|
||||
height: 30rpx;
|
||||
left: 4rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
|
||||
.main-wrap {
|
||||
position: relative;
|
||||
padding: 0 20rpx;
|
||||
.goods {
|
||||
.item {
|
||||
border-radius: 14rpx;
|
||||
overflow: hidden;
|
||||
.add {
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
line-height: 46rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.two-item {
|
||||
border-radius: 10rpx;
|
||||
|
||||
.title {
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.line {
|
||||
width: 40rpx;
|
||||
height: 1px;
|
||||
background-color: #BBBBBB;
|
||||
}
|
||||
}
|
||||
|
||||
.three-list {
|
||||
align-items: flex-start;
|
||||
padding: 0 10rpx;
|
||||
|
||||
.three-item {
|
||||
width: 33%;
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.condition {
|
||||
height: 80rpx;
|
||||
border-radius: 10rpx;
|
||||
|
||||
.tag {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<scroll-view scroll-y="true" class="cate-scroll" @scrolltolower="getGoodsSearchFun">
|
||||
<view class="cate-list">
|
||||
<view class="cate-nav bg-white" v-if="navList.length && type == 2 || cate.type == 0">
|
||||
<swiper :style="'height:' + navSwiperH + 'rpx;'" @change="swiperChange">
|
||||
<swiper-item v-for="(items, index) in navList" :key="index">
|
||||
<view class="nav-list row wrap">
|
||||
<view v-for="(item, index2) in items" :key="index2" @tap="changeCate(item)"
|
||||
class="nav-item column-center">
|
||||
<view class="icon-wrap" :class="{active: id == item.id}">
|
||||
<u-image width="82rpx" height="82rpx" border-radius="50%" :src="item.image"></u-image>
|
||||
</view>
|
||||
<view style="width: 90%;" class="name xs line1 u-text-center" :class="{primary: id == item.id}">{{item.name}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="dots" v-if="navList.length > 1">
|
||||
<view v-for="(item, index) in navList" :key="index"
|
||||
:class="'dot ' + (index == currentSwiper ? 'active' : '')"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<swipers :pid="4" height="267rpx" previous-margin="0" padding="20rpx 20rpx 0" radius="10rpx"></swipers>
|
||||
<view v-if="cate.type !== 0">
|
||||
<view class="row condition bg-white mt20">
|
||||
<view :class="'tag row-center ' + (comprehensive ? 'primary' : '')" @tap="onNormal">综合</view>
|
||||
<view class="tag row-center" data-type="priceSort" @tap="onPriceSort">
|
||||
<text :class="priceSort ? 'primary' : ''">价格</text>
|
||||
<view>
|
||||
<trigonometry direction="up" size="small" :color="priceSort == 'desc' ? '#FF5058' : '#333'">
|
||||
</trigonometry>
|
||||
<trigonometry size="small" :color="priceSort == 'asc' ? '#FF5058' : '#333'"></trigonometry>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tag row-center" data-type="saleSort" @tap="onSaleSort">
|
||||
<text :class="saleSort ? 'primary' : ''">销量</text>
|
||||
<view>
|
||||
<trigonometry direction="up" size="small" :color="saleSort == 'desc' ? '#FF5058' : '#333'">
|
||||
</trigonometry>
|
||||
<trigonometry size="small" :color="saleSort == 'asc' ? '#FF5058' : '#333'"></trigonometry>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tag row-center" @tap="changeType">
|
||||
<image class="icon-sm"
|
||||
:src=" goodsType === 'one' ? '/static/images/icon_double.png' : '/static/images/icon_one.png'">
|
||||
</image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods">
|
||||
<view class="goods-list">
|
||||
<view v-show="goodsType == 'double'" class="double">
|
||||
<goods-list type="double" :list="goodsList"></goods-list>
|
||||
</view>
|
||||
<view v-show="goodsType == 'one'" class="one" style="padding: 0 20rpx">
|
||||
<goods-list :list="goodsList" type="one"></goods-list>
|
||||
</view>
|
||||
</view>
|
||||
<loading-footer :status="status" :slot-empty="true">
|
||||
<view slot="empty" class="column-center" style="padding: 200rpx 0">
|
||||
<image class="img-null" src="/static/images/goods_null.png"></image>
|
||||
<text class="lighter">暂无商品</text>
|
||||
</view>
|
||||
</loading-footer>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapGetters,
|
||||
mapActions
|
||||
} from 'vuex'
|
||||
import {
|
||||
arraySlice,
|
||||
trottle,
|
||||
loadingFun
|
||||
} from '@/utils/tools'
|
||||
import {
|
||||
getGoodsSearch,
|
||||
} from '@/api/store';
|
||||
import {
|
||||
loadingType
|
||||
} from '@/utils/type';
|
||||
export default {
|
||||
props: {
|
||||
cate: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
navSwiperH: 374,
|
||||
navList: [],
|
||||
currentSwiper: 0,
|
||||
status: loadingType.LOADING,
|
||||
page: 1,
|
||||
goodsType: 'double',
|
||||
goodsList: [],
|
||||
priceSort: '',
|
||||
saleSort: '',
|
||||
id: '',
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.onNormal = trottle(this.onNormal, 500, this);
|
||||
this.onPriceSort = trottle(this.onPriceSort, 500, this);
|
||||
this.onSaleSort = trottle(this.onSaleSort, 500, this);
|
||||
this.getGoodsSearchFun()
|
||||
},
|
||||
watch: {
|
||||
cate: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
let navList = []
|
||||
console.log(val)
|
||||
if (val.type === 0) {
|
||||
navList = val.sons[0].sons || []
|
||||
} else {
|
||||
navList = val.sons || []
|
||||
}
|
||||
if (navList.length <= 5) {
|
||||
this.navSwiperH = 200
|
||||
} else {
|
||||
this.navSwiperH = 374
|
||||
}
|
||||
this.id = val.id
|
||||
this.navList = arraySlice(navList)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["appConfig"]),
|
||||
comprehensive() {
|
||||
const {
|
||||
priceSort,
|
||||
saleSort
|
||||
} = this
|
||||
if (priceSort == '' && saleSort == '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeCate(item) {
|
||||
if(this.cate.type === 0) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/goods_search/goods_search?id=${item.id}&name=${item.name}&type=${item.type}`
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if(this.id == item.id) {
|
||||
this.id = this.cate.id
|
||||
this.onRefresh()
|
||||
return
|
||||
}
|
||||
this.id = item.id
|
||||
this.onRefresh()
|
||||
},
|
||||
swiperChange(e) {
|
||||
this.currentSwiper = e.detail.current
|
||||
},
|
||||
changeType() {
|
||||
this.goodsType = this.goodsType === 'one' ? 'double' : 'one'
|
||||
},
|
||||
|
||||
onNormal() {
|
||||
this.priceSort = ''
|
||||
this.saleSort = ''
|
||||
this.onRefresh();
|
||||
},
|
||||
|
||||
onPriceSort() {
|
||||
let {
|
||||
priceSort
|
||||
} = this;
|
||||
this.saleSort = ''
|
||||
this.priceSort = priceSort == 'asc' ? 'desc' : 'asc'
|
||||
this.onRefresh();
|
||||
},
|
||||
|
||||
onSaleSort() {
|
||||
let {
|
||||
saleSort
|
||||
} = this;
|
||||
this.priceSort = ''
|
||||
this.saleSort = saleSort == 'desc' ? 'asc' : 'desc'
|
||||
this.onRefresh();
|
||||
},
|
||||
|
||||
|
||||
onRefresh() {
|
||||
this.showHistory = false
|
||||
this.page = 1
|
||||
this.goodsList = []
|
||||
this.status = loadingType.LOADING
|
||||
this.$nextTick(() => {
|
||||
this.getGoodsSearchFun();
|
||||
});
|
||||
},
|
||||
|
||||
async getGoodsSearchFun() {
|
||||
let {
|
||||
page,
|
||||
goodsList,
|
||||
priceSort,
|
||||
saleSort,
|
||||
status
|
||||
} = this;
|
||||
if (status == loadingType.FINISHED) return;
|
||||
const params = {
|
||||
category_id: this.id,
|
||||
page_no: page,
|
||||
price: priceSort,
|
||||
sales_sum: saleSort
|
||||
}
|
||||
const data = await loadingFun(getGoodsSearch, page, goodsList, status, params)
|
||||
if (!data) return
|
||||
this.page = data.page
|
||||
this.goodsList = data.dataList
|
||||
this.status = data.status
|
||||
},
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.cate-scroll {
|
||||
height: calc(100vh - 174rpx - var(--window-bottom));
|
||||
}
|
||||
.cate-list {
|
||||
|
||||
.cate-nav {
|
||||
position: relative;
|
||||
border-radius: 20rpx;
|
||||
margin: 20rpx 20rpx 0;
|
||||
|
||||
.nav-item {
|
||||
width: 20%;
|
||||
margin-top: 30rpx;
|
||||
.icon-wrap {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 50%;
|
||||
&.active {
|
||||
border-color: $-color-primary;
|
||||
}
|
||||
}
|
||||
.name {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.dots {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 20rpx;
|
||||
display: flex;
|
||||
|
||||
.dot {
|
||||
width: 10rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 6rpx;
|
||||
margin-right: 10rpx;
|
||||
background-color: rgba(255, 44, 60, 0.4);
|
||||
|
||||
&.active {
|
||||
width: 20rpx;
|
||||
background-color: $-color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.condition {
|
||||
height: 80rpx;
|
||||
margin: 20rpx 20rpx 0;
|
||||
border-radius: 10rpx;
|
||||
|
||||
.tag {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<view class="cate-one">
|
||||
<tabs :active="selectIndex" @change="changeShow" :config="{itemWidth: 150}">
|
||||
<tab v-for="(item, index) in cateList" :key="index" :title="item.name">
|
||||
<cate-list v-if="item.isShow" :cate="item"></cate-list>
|
||||
</tab>
|
||||
</tabs>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:"cate-one",
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectIndex: 0,
|
||||
cateList: []
|
||||
};
|
||||
},
|
||||
methods:{
|
||||
changeShow(index) {
|
||||
this.selectIndex = index
|
||||
this.cateList[this.selectIndex].isShow = true
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
let index = val.findIndex((item) => item.type == 1)
|
||||
this.selectIndex = index == -1 ? 0 : index
|
||||
val.forEach((item, index) => item.isShow = this.selectIndex == index ? true : false)
|
||||
this.cateList = val
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<view class="cate-three row">
|
||||
<view class="aside">
|
||||
<scroll-view style="height: 100%" scroll-y="true" scroll-with-animation="true">
|
||||
<block v-for="(item, index) in cateList" :key="index">
|
||||
<view :class="'one-item sm ' + (index == selectIndex ? 'active' : '')" @click="changeActive(index)">
|
||||
<text class="name">{{ item.name }}</text>
|
||||
<view v-if="index == selectIndex" class="active-line bg-primary"></view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="main bg-body">
|
||||
<scroll-view style="height: 100%" scroll-y="true" scroll-with-animation="true">
|
||||
<view class="main-wrap">
|
||||
<view style="border-radius: 10rpx;">
|
||||
<swipers :pid="4" height="200rpx" previous-margin="0" padding="20rpx 0 0" radius="10rpx">
|
||||
</swipers>
|
||||
</view>
|
||||
<view class="cate-two mt20">
|
||||
<view v-for="(sitem, sindex) in cateTwoList" :key="sindex" class="two-item bg-white mb20">
|
||||
<navigator class="title row-between" v-if="sitem.type == 1" hover-class="none"
|
||||
:url="`/pages/goods_search/goods_search?id=${sitem.id}&name=${sitem.name}&type=${sitem.type}`">
|
||||
<text class="name bold sm">{{sitem.name}}</text>
|
||||
<u-icon name="arrow-right"></u-icon>
|
||||
</navigator>
|
||||
<view class="title row-between" v-else>
|
||||
<text class="name bold sm">{{sitem.name}}</text>
|
||||
</view>
|
||||
<view class="three-list row wrap">
|
||||
<navigator v-for="(titem, tindex) in sitem.sons" :key="tindex"
|
||||
class="three-item column-center mb20" hover-class="none"
|
||||
:url="`/pages/goods_search/goods_search?id=${titem.id}&name=${titem.name}&type=${titem.type}`">
|
||||
<u-image mode="aspectFit" width="150rpx" height="150rpx" :src="titem.image">
|
||||
</u-image>
|
||||
<view class="text mt20 xs">{{ titem.name }}</view>
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "cate-three",
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectIndex: 0,
|
||||
cateList: [],
|
||||
cateTwoList: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
changeActive(index) {
|
||||
const {
|
||||
cateList
|
||||
} = this
|
||||
this.selectIndex = index
|
||||
this.cateTwoList = cateList[this.selectIndex].sons
|
||||
},
|
||||
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
let index = val.findIndex((item) => item.type == 1)
|
||||
this.selectIndex = index == -1 ? 0 : index
|
||||
this.cateList = val
|
||||
this.cateTwoList = val[this.selectIndex] ? val[this.selectIndex].sons : []
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$header-height: 94rpx;
|
||||
.cate-three {
|
||||
height: calc(100vh - #{$header-height} - var(--window-top) - var(--window-bottom));
|
||||
background-color: #fff;
|
||||
|
||||
.aside {
|
||||
width: 180rpx;
|
||||
flex: none;
|
||||
height: 100%;
|
||||
|
||||
.one-item {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: 26rpx 10rpx;
|
||||
|
||||
&.active {
|
||||
color: $-color-primary;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.active-line {
|
||||
position: absolute;
|
||||
width: 6rpx;
|
||||
height: 30rpx;
|
||||
left: 4rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
|
||||
.main-wrap {
|
||||
position: relative;
|
||||
|
||||
|
||||
|
||||
padding: 0 20rpx;
|
||||
|
||||
.two-item {
|
||||
border-radius: 10rpx;
|
||||
|
||||
.title {
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.line {
|
||||
width: 40rpx;
|
||||
height: 1px;
|
||||
background-color: #BBBBBB;
|
||||
}
|
||||
}
|
||||
|
||||
.three-list {
|
||||
align-items: flex-start;
|
||||
padding: 0 10rpx;
|
||||
|
||||
.three-item {
|
||||
width: 33%;
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<view class="cate-two">
|
||||
<tabs :active="selectIndex" @change="changeShow" :config="{itemWidth: 150}">
|
||||
<tab v-for="(item, index) in cateList" :key="index" :title="item.name">
|
||||
<cate-list v-if="item.isShow" :type="2" :cate="item"></cate-list>
|
||||
</tab>
|
||||
</tabs>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:"cate-two",
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectIndex: 0,
|
||||
cateList: []
|
||||
};
|
||||
},
|
||||
methods:{
|
||||
changeShow(index) {
|
||||
this.selectIndex = index
|
||||
this.cateList[this.selectIndex].isShow = true
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
let index = val.findIndex((item) => item.type == 1)
|
||||
this.selectIndex = index == -1 ? 0 : index
|
||||
val.forEach((item, index) => item.isShow = this.selectIndex == index ? true : false)
|
||||
this.cateList = val
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<!-- components/comment_list/comment_list.wxml -->
|
||||
<view height="calc(100vh - 80rpx)">
|
||||
<view class="comment-list">
|
||||
<view v-for="(item, index) in list" :key="index" class="comment-item bg-white mt20">
|
||||
<view class="xs muted ml20" style="padding-top: 20rpx" v-if="item.create_time"
|
||||
>评价时间:{{ item.create_time }}</view
|
||||
>
|
||||
<navigator
|
||||
class="comment-goods row"
|
||||
:url="'/pages/goods_details/goods_details?id=' + item.goods_id"
|
||||
hover-class="none"
|
||||
>
|
||||
<custom-image
|
||||
width="160rpx"
|
||||
height="160rpx"
|
||||
radius="6rpx"
|
||||
lazy-load
|
||||
:src="item.image"
|
||||
/>
|
||||
<view class="goods-desc">
|
||||
<view class="goods-name line2 nr">{{ item.goods_name }}</view>
|
||||
<view class="row-between mt20" v-show="!(type == 2)">
|
||||
<price-format
|
||||
:price="item.goods_price"
|
||||
:showSubscript="true"
|
||||
:subscriptSize="26"
|
||||
:firstSize="30"
|
||||
:secondSize="30"
|
||||
/>
|
||||
<view class="nr">x{{ item.goods_num }}</view>
|
||||
</view>
|
||||
<view v-show="!(type == 1)" class="row mt20">
|
||||
<view class="sm mr10">评分</view>
|
||||
<u-rate
|
||||
:disabled="true"
|
||||
v-model="item.goods_comment"
|
||||
active-color="#FF2C3C"
|
||||
:size="24"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
<view
|
||||
v-show="!(type == 2)"
|
||||
class="evaluate-footer row-end"
|
||||
@click="goCommentGoods(item)"
|
||||
>
|
||||
<view class="btn row-center primary br60">评价商品</view>
|
||||
</view>
|
||||
<!-- <navigator v-show="!(type == 2)" class="evaluate-footer row-end" hover-class="none" :url="'/bundle/pages/goods_reviews/goods_reviews?id=' + item.id">
|
||||
<view class="btn row-center primary br60">评价商品</view>
|
||||
</navigator> -->
|
||||
<view v-show="!(type == 1 || !item.comment)" class="evaluate-footer">
|
||||
<view class="preview-evaluate">{{ item.comment }}</view>
|
||||
</view>
|
||||
<view
|
||||
v-show="item.comment_image && !(type == 1 || !item.comment_image.length)"
|
||||
class="row wrap"
|
||||
style="padding: 20rpx 10rpx"
|
||||
>
|
||||
<view
|
||||
v-for="(i, idx) in item.comment_image"
|
||||
:key="idx"
|
||||
class="comment-img"
|
||||
:data-current="idx"
|
||||
:data-uri="item"
|
||||
@tap="previewImage"
|
||||
>
|
||||
<custom-image width="160rpx" height="160rpx" radius="6rpx" :src="i" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<loading-footer :status="status" slotEmpty>
|
||||
<view class="data-null column-center" slot="empty">
|
||||
<image class="img-null" src="/static/images/news_null.png"></image>
|
||||
<text class="sm muted">暂无其他评价~</text>
|
||||
</view>
|
||||
</loading-footer>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeshop开源商城系统
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee
|
||||
// | github下载:https://github.com/likeshop-github
|
||||
// | 访问官网:https://www.likeshop.cn
|
||||
// | 访问社区:https://home.likeshop.cn
|
||||
// | 访问手册:http://doc.likeshop.cn
|
||||
// | 微信公众号:likeshop技术社区
|
||||
// | likeshop系列产品在gitee、github等公开渠道开源版本可免费商用,未经许可不能去除前后端官方版权标识
|
||||
// | likeshop系列产品收费版本务必购买商业授权,购买去版权授权后,方可去除前后端官方版权标识
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布
|
||||
// | likeshop团队版权所有并拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeshop.cn.team
|
||||
// +----------------------------------------------------------------------
|
||||
import { loadingType } from '../../utils/type'
|
||||
import { getOrderCommentList } from '../../api/store'
|
||||
import { loadingFun } from '@/utils/tools'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
status: loadingType.LOADING,
|
||||
page: 1
|
||||
}
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
type: {
|
||||
type: Number | String
|
||||
}
|
||||
},
|
||||
|
||||
beforeMount() {
|
||||
console.log(this.type)
|
||||
this.getOrderCommentListFun()
|
||||
},
|
||||
|
||||
methods: {
|
||||
getOrderCommentListFun() {
|
||||
let { page, type, status, list } = this
|
||||
|
||||
loadingFun(getOrderCommentList, page, list, status, { type: type }).then((res) => {
|
||||
if (res) {
|
||||
this.page = res.page
|
||||
this.list = res.dataList
|
||||
this.status = res.status
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
goCommentGoods({ id, del }) {
|
||||
if (+del) return
|
||||
uni.navigateTo({
|
||||
url: `/bundle/pages/goods_reviews/goods_reviews?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
goPage(e) {
|
||||
let { url } = e.currentTarget.dataset
|
||||
|
||||
if (this.type == 1) {
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
})
|
||||
}
|
||||
},
|
||||
previewImage(e) {
|
||||
const { current, uri } = e.currentTarget.dataset
|
||||
let urls = [uri]
|
||||
uni.previewImage({
|
||||
current,
|
||||
// 当前显示图片的http链接
|
||||
urls // 需要预览的图片http链接列表
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
/* components/comment_list/comment_list.wxss */
|
||||
.comment-list {
|
||||
.comment-goods {
|
||||
padding: 20rpx 24rpx;
|
||||
.goods-desc {
|
||||
margin-left: 24rpx;
|
||||
flex: 1;
|
||||
}
|
||||
.btn {
|
||||
padding: 6rpx 34rpx;
|
||||
border: 1px solid $-color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-item {
|
||||
.comment-img {
|
||||
// &:not(:nth-last-of-type(4n + 1)) {
|
||||
// margin-right: 20rpx;
|
||||
// }
|
||||
margin-right: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.evaluate-footer {
|
||||
padding: 0 24rpx 28rpx;
|
||||
.btn {
|
||||
width: 178rpx;
|
||||
height: 52rpx;
|
||||
border: 1px solid $-color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data-null {
|
||||
padding-top: 200rpx;
|
||||
}
|
||||
|
||||
.evaluate-footer .preview-evaluate {
|
||||
background-color: #f6f6f6;
|
||||
padding: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
|
||||
<view class="coupon-list">
|
||||
<view v-for="(item, index) in list" :key="index" class="mb20">
|
||||
<view :class="'coupon-item row ' + (btnType == 1 || btnType == 2 ? 'gray': '')">
|
||||
<view class="price white column-center">
|
||||
<view class="xl">
|
||||
<price-format :first-size="60" :second-size="50" :subscript-size="34" :price="item.money" :weight="500" />
|
||||
</view>
|
||||
<view class="sm" style="text-align: center">{{item.use_condition}}</view>
|
||||
</view>
|
||||
<view class="info ml20">
|
||||
<view class="bold lg mb10">{{item.name}}</view>
|
||||
<view class="xs lighter mb20">{{item.use_time_tips}}</view>
|
||||
<view class="xs lighter ">{{item.coupon_type}}</view>
|
||||
</view>
|
||||
<button type="primary" :class="'btn br60 white xs ' + (btnType != 3 ? 'plain': '')" @tap="onHandle(item.id)">
|
||||
{{getBtn}}
|
||||
</button>
|
||||
<image v-if="item.is_get" class="receive" src="/static/images/coupon_receive.png"></image>
|
||||
</view>
|
||||
<view style="padding: 14rpx 20rpx" class="bg-white" v-if="item.tips" @tap="onShowTips(index)">
|
||||
<view class="row-between">
|
||||
<view class="xs">使用说明</view>
|
||||
<u-icon :class="showTips[index] ? 'rotate' : ''" name="arrow-down" />
|
||||
</view>
|
||||
<view v-show="showTips[index]" class="mt10 xs">{{item.tips}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeshop开源商城系统
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee
|
||||
// | github下载:https://github.com/likeshop-github
|
||||
// | 访问官网:https://www.likeshop.cn
|
||||
// | 访问社区:https://home.likeshop.cn
|
||||
// | 访问手册:http://doc.likeshop.cn
|
||||
// | 微信公众号:likeshop技术社区
|
||||
// | likeshop系列产品在gitee、github等公开渠道开源版本可免费商用,未经许可不能去除前后端官方版权标识
|
||||
// | likeshop系列产品收费版本务必购买商业授权,购买去版权授权后,方可去除前后端官方版权标识
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布
|
||||
// | likeshop团队版权所有并拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeshop.cn.team
|
||||
// +----------------------------------------------------------------------
|
||||
import { getCoupon } from '../../api/user';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
showTips: []
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
btnType: {
|
||||
// 0 去使用 1已使用 2已过期 3领取
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
handler: function (val) {
|
||||
let arr = val.map(item => {
|
||||
return 0;
|
||||
});
|
||||
this.showTips = arr
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
computed: {
|
||||
getBtn() {
|
||||
var text = ''
|
||||
switch (this.btnType) {
|
||||
|
||||
case 0:
|
||||
text = '去使用';
|
||||
break;
|
||||
case 1:
|
||||
text = '已使用';
|
||||
break;
|
||||
case 2:
|
||||
text = '已过期';
|
||||
break;
|
||||
case 3:
|
||||
text = '领取';
|
||||
break;
|
||||
}
|
||||
return text
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onHandle(id) {
|
||||
this.id = id;
|
||||
const {
|
||||
btnType
|
||||
} = this;
|
||||
|
||||
switch (btnType) {
|
||||
case 0:
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// text = '去使用';
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// text = '已使用';
|
||||
break;
|
||||
|
||||
case 3:
|
||||
this.getCouponFun();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onShowTips(index) {
|
||||
const {
|
||||
showTips
|
||||
} = this;
|
||||
|
||||
this.showTips[index] = showTips[index] ? 0 : 1
|
||||
// 拷贝数组
|
||||
this.showTips = Object.assign([], this.showTips);
|
||||
},
|
||||
|
||||
getCouponFun() {
|
||||
getCoupon(this.id).then(res => {
|
||||
if (res.code == 1) {
|
||||
this.$toast({title: res.msg})
|
||||
this.$emit('reflash');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" >
|
||||
/* components/coupon-list/coupon-list.wxss */
|
||||
.coupon-list {
|
||||
padding: 20rpx 24rpx;
|
||||
.coupon-item {
|
||||
position: relative;
|
||||
height: 200rpx;
|
||||
background-image: url(../../static/images/coupon_bg.png);
|
||||
background-size: 100% 100%;
|
||||
&.gray {
|
||||
background-image: url(../../static/images/coupon_bg_grey.png);
|
||||
.btn{
|
||||
&.plain {
|
||||
color: #CCCCCC;
|
||||
}
|
||||
}
|
||||
}
|
||||
.price {
|
||||
width: 200rpx;
|
||||
}
|
||||
.btn {
|
||||
line-height: 52rpx;
|
||||
height: 52rpx;
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
bottom: 20rpx;
|
||||
width: 120rpx;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
&.plain {
|
||||
background-color: #fff;
|
||||
color: var(--primary-color);
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
}
|
||||
.receive {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 0rpx;
|
||||
width: 99rpx;
|
||||
height: 77rpx;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
transition: all 0.4s;
|
||||
}
|
||||
.rotate {
|
||||
transform: rotateZ(-180deg);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<view>
|
||||
<scroll-view style="height: 640rpx;background-color: #F6F6F6" scroll-y="true">
|
||||
<view class="coupon-obj">
|
||||
<view v-for="(item, index) in coupons" :key="index" class="mb20 bg-white" @tap="onSelect(item.id)">
|
||||
<view class="coupon-item row">
|
||||
<view class="price white column-center">
|
||||
<view class="xl">
|
||||
<price-format :subscript-size="34" :first-size="60" :second-size="50" :price="item.money" :weight="500"></price-format>
|
||||
</view>
|
||||
<view class="nr">{{item.use_condition}}</view>
|
||||
</view>
|
||||
<view class="row-between" style="flex: 1">
|
||||
<view class="info ml20">
|
||||
<view class="bold md bold mb10 line1">{{item.name}}</view>
|
||||
<view class="xxs lighter mb10">{{item.coupon_type}}</view>
|
||||
<view class="xxs lighter">{{item.use_time_tips}}</view>
|
||||
</view>
|
||||
<checkbox v-if="type == 0" :checked="selectId == item.id" class="mr20"></checkbox>
|
||||
</view>
|
||||
</view>
|
||||
<view class="xs" v-if="item.tips" style="padding: 14rpx 20rpx">
|
||||
{{item.tips}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="coupons.length == 0" class="column-center" style="padding-top: 50rpx">
|
||||
<image class="img-null" src="/static/images/coupon_null.png"></image>
|
||||
<text class="muted">暂无优惠券~</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="column-center">
|
||||
<view class="bg-primary white row-center br60 mb10 lg" style="margin-top: 12rpx;width: 710rpx;height: 74rpx" @tap="onConfirmCoupon">
|
||||
确定
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
coupons: [],
|
||||
selectId: ""
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
type: {
|
||||
type: Number
|
||||
},
|
||||
couponId: [Number, String]
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
handler(val) {
|
||||
this.coupons = val
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
couponId: {
|
||||
handler(val) {
|
||||
this.selectId = val
|
||||
},
|
||||
immediate: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelect(id) {
|
||||
const {
|
||||
coupons,
|
||||
type,
|
||||
selectId
|
||||
} = this;
|
||||
if (type == 1) return;
|
||||
this.selectId = id == selectId ? "" : id
|
||||
|
||||
},
|
||||
onConfirmCoupon() {
|
||||
this.$emit("change", this.selectId)
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.coupon-obj {
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.coupon-obj .coupon-item {
|
||||
position: relative;
|
||||
height: 160rpx;
|
||||
background-image: url(../../static/images/coupon_bg.png);
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.coupon-obj .coupon-item .price {
|
||||
width: 200rpx;
|
||||
}
|
||||
|
||||
.coupon-obj .coupon-item .btn {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
bottom: 20rpx;
|
||||
padding: 0 49rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<view class="cu-progress" :style="'height: ' + progressHeight + '; width: ' + progressWidth + ';background-color: ' + progressColor">
|
||||
<view class="cu-progress-bar" :style="'background-color: ' + progressBarColor + ';left: ' + barLeft + 'rpx; width:' + barWidth + 'rpx'">
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentSwiper: 0,
|
||||
// 剩余滑行距离
|
||||
offset: 0,
|
||||
barLeft: 0
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
progressBarColor: {
|
||||
type: String,
|
||||
default: '#01B55B'
|
||||
},
|
||||
progressWidth: {
|
||||
type: Number,
|
||||
default: 90
|
||||
},
|
||||
progressHeight: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
progressColor: {
|
||||
type: String,
|
||||
default: '#E5E5E5'
|
||||
},
|
||||
left: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
barWidth: {
|
||||
type: Number,
|
||||
default: 30
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'left': function (value) {
|
||||
this.barLeft = value / 100 * this.offset
|
||||
}
|
||||
},
|
||||
|
||||
beforeMount: function () {
|
||||
this.offset = this.progressWidth - this.barWidth
|
||||
},
|
||||
destroyed: function () {},
|
||||
methods: {
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.cu-progress {
|
||||
background-color: #E5E5E5;
|
||||
height: 6rpx;
|
||||
width: 90rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cu-progress .cu-progress-bar {
|
||||
height: 100%;
|
||||
width: 30rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<view
|
||||
:style="[viewStyle]"
|
||||
:class="{ 'custom-image': true, 'image-round': round }"
|
||||
@click="onClick"
|
||||
>
|
||||
<image
|
||||
v-if="!error"
|
||||
:src="src"
|
||||
:mode="mode"
|
||||
:lazy-load="lazyLoad"
|
||||
class="image"
|
||||
:show-menu-by-longpress="showMenuByLongpress"
|
||||
@load="onLoaded"
|
||||
@error="onErrored"
|
||||
></image>
|
||||
<view v-if="loading && showLoading" class="loading-wrap image">
|
||||
<slot v-if="useLoadingSlot" name="loading"></slot>
|
||||
<u-icon color="#aaa" v-else name="photo-fill" size="45"></u-icon>
|
||||
</view>
|
||||
<view v-if="error && showError" class="error-wrap image">
|
||||
<slot v-if="useErrorSlot" name="error"></slot>
|
||||
<u-icon color="#aaa" v-else name="error-circle-fill" size="45"></u-icon>
|
||||
<text class="sm">加载失败</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
src: {
|
||||
type: String
|
||||
},
|
||||
round: Boolean,
|
||||
width: {
|
||||
type: null
|
||||
},
|
||||
height: {
|
||||
type: null
|
||||
},
|
||||
radius: null,
|
||||
lazyLoad: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
useErrorSlot: Boolean,
|
||||
useLoadingSlot: Boolean,
|
||||
showMenuByLongpress: Boolean,
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'scaleToFill'
|
||||
},
|
||||
showError: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showLoading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
loading: true,
|
||||
viewStyle: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.setStyle()
|
||||
},
|
||||
methods: {
|
||||
setStyle() {
|
||||
const { width, height, radius } = this
|
||||
let style = {}
|
||||
|
||||
if (width) {
|
||||
style.width = width
|
||||
}
|
||||
|
||||
if (height) {
|
||||
style.height = height
|
||||
}
|
||||
|
||||
if (radius) {
|
||||
style['overflow'] = 'hidden'
|
||||
style['border-radius'] = radius
|
||||
}
|
||||
this.viewStyle = style
|
||||
if (this.customStyle) {
|
||||
this.viewStyle = Object.assign(this.viewStyle, this.customStyle)
|
||||
}
|
||||
},
|
||||
onLoaded(event) {
|
||||
this.loading = false
|
||||
this.$emit('load', event.detail)
|
||||
},
|
||||
onErrored(event) {
|
||||
this.error = false
|
||||
this.loading = true
|
||||
this.$emit('error', event.detail)
|
||||
},
|
||||
onClick(event) {
|
||||
this.$emit('click', event.detail)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
src() {
|
||||
this.error = false
|
||||
this.loading = true
|
||||
},
|
||||
width() {
|
||||
this.setStyle()
|
||||
},
|
||||
height() {
|
||||
this.setStyle()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.custom-image {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&.image-round {
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-wrap,
|
||||
.error-wrap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #969799;
|
||||
font-size: 28rpx;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<view class="download-nav-container row-between" v-if="showDownload" :style="{top: top + 'px'}">
|
||||
<view class="sm white">
|
||||
{{appConfig.download_doc}}
|
||||
</view>
|
||||
<view class="row">
|
||||
<view class="white primary-btn row-center br60 xs" @click="go2DownLoad">
|
||||
下载APP
|
||||
</view>
|
||||
<u-icon name="close" size="32" color="#fff" @click="showDownload = false" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters} from 'vuex'
|
||||
export default {
|
||||
name:"download-nav",
|
||||
props: {
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDownload: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['appConfig'])
|
||||
},
|
||||
methods: {
|
||||
go2DownLoad() {
|
||||
// #ifdef H5
|
||||
uni.getSystemInfo({
|
||||
success: res => {
|
||||
res.platform == 'ios' ? window.open(this.appConfig.ios_download) : window.open(this.appConfig.android_download);
|
||||
},
|
||||
fail: res => {
|
||||
window.open(this.appConfig.android_download)
|
||||
}
|
||||
})
|
||||
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.download-nav-container {
|
||||
height: 80rpx;
|
||||
background-color: #333333;
|
||||
padding: 0 30rpx;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 871;
|
||||
top: 0;
|
||||
.primary-btn {
|
||||
background-color: $-color-primary;
|
||||
height: 54rpx;
|
||||
width: 154rpx;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<view class="goods-bargain-container">
|
||||
<view class="row-center" style="height: 100rpx;">
|
||||
<view class="bLine" />
|
||||
<view class="lg bold" style="margin: 0 30rpx;">
|
||||
更多砍价商品
|
||||
</view>
|
||||
<view class="bLine" />
|
||||
</view>
|
||||
<goods-list type="double" :list="lists" :isBargain="true"></goods-list>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getBargainList} from '@/api/activity'
|
||||
import {loadingType} from '@/utils/type'
|
||||
import {loadingFun} from '@/utils/tools'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
page: 1,
|
||||
loadingStatus: loadingType.LOADING,
|
||||
lists: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$getBargainList()
|
||||
},
|
||||
methods: {
|
||||
$getBargainList() {
|
||||
let {page, loadingStatus, lists} = this;
|
||||
loadingFun(getBargainList, page, lists, loadingStatus).then(res => {
|
||||
if(res) {
|
||||
this.page = res.page;
|
||||
this.lists = res.dataList;
|
||||
this.loadingStatus = res.status
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.goods-bargain-container {
|
||||
padding-bottom: 40rpx;
|
||||
.bLine {
|
||||
height: 2rpx;
|
||||
background-color: #CCCCCC;
|
||||
width: 58rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<view class="goods-like">
|
||||
<view class="title row-center">
|
||||
<text class="line"></text>
|
||||
<view class="row">
|
||||
<image class="mr20" src="/static/images/icon_like.png"></image>
|
||||
<text class="bold xxl">猜你喜欢</text>
|
||||
</view>
|
||||
<text class="line"></text>
|
||||
</view>
|
||||
<view class="goods">
|
||||
<scroll-view style="height: 340rpx; white-space: nowrap;" scroll-x="true" scroll-with-animation="true"
|
||||
@scroll="scrollBarChange">
|
||||
<navigator v-for="(item, index) in list" :key="index" hover-class="none"
|
||||
:url="'/pages/goods_details/goods_details?id=' + item.id" class="goods-item">
|
||||
<custom-image width="240rpx" height="240rpx" radius="10rpx" lazy-load :src="item.image">
|
||||
</custom-image>
|
||||
<view class="goods-name line1 mt10">{{item.name}}</view>
|
||||
<view class="price">
|
||||
<price-format color="#FF2C3C" :subscript-size="22" :second-size="22" :price="item.price"
|
||||
:weight="500"></price-format>
|
||||
<price-format class="ml10" :show-subscript="true" color="#999" :line-through="true"
|
||||
:subscript-size="22" :first-size="22" :second-size="22" :price="item.market_price">
|
||||
</price-format>
|
||||
</view>
|
||||
</navigator>
|
||||
</scroll-view>
|
||||
<view class="row-center mb10 mt20" v-if="list.length > 3">
|
||||
<cu-progress progressBarColor="#FF2C3C" :left="progressPer"></cu-progress>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getRect
|
||||
} from '@/utils/tools';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
progressPer: 0
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
handler: function() {
|
||||
this.$nextTick(function() {
|
||||
getRect(".goods-like", false, this).then(res => {
|
||||
this.rectWidth = res.width;
|
||||
});
|
||||
})
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
scrollBarChange(e) {
|
||||
let {
|
||||
progressPer
|
||||
} = this;
|
||||
let {
|
||||
scrollLeft,
|
||||
scrollWidth
|
||||
} = e.detail;
|
||||
progressPer = scrollLeft / (scrollWidth - this.rectWidth) * 100;
|
||||
progressPer = Number(progressPer.toFixed(0))
|
||||
this.progressPer = progressPer
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.goods-like .title {
|
||||
height: 100rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.goods-like .title .line {
|
||||
width: 58rpx;
|
||||
height: 1px;
|
||||
background-color: #ccc;
|
||||
margin: 0 22rpx;
|
||||
}
|
||||
|
||||
.goods-like .title image {
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
}
|
||||
|
||||
.goods-like .title .like {
|
||||
padding: 1rpx 11rpx;
|
||||
background: linear-gradient(180deg, rgba(255, 44, 60, 1) 0%, rgba(249, 95, 47, 1) 100%);
|
||||
border-radius: 100px 100px 100px 0px;
|
||||
}
|
||||
|
||||
.goods-like .goods {
|
||||
padding-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.goods-like .goods .goods-item {
|
||||
width: 240rpx;
|
||||
margin-left: 20rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.goods-like .goods .goods-item:last-of-type {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<view class="goods-list">
|
||||
<view v-if="type === 'double'" class="goods-double row-between">
|
||||
<navigator v-for="(item, index) in list" :key="index" class="item bg-white mt20" hover-class="none" open-type="navigate"
|
||||
:url="'/pages/goods_details/goods_details?id=' + (isBargain ? item.goods_id : item.id)">
|
||||
<view class="goods-img" style="width: 347rpx;height:347rpx;">
|
||||
<custom-image :lazy-load="true" width="347rpx" height="347rpx" radius="10rpx" lazy-load :src="item.image"></custom-image>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name line2">{{item.name}}</view>
|
||||
<view class="price mt10 row">
|
||||
<price-format color="#FF2C3C" class="mr10" :first-size="34" :second-size="26" :subscript-size="26" :price="item.price" :weight="500"></price-format>
|
||||
<price-format class="muted" :firstSize="24" :secondSize="24" :subscript-size="24" line-through :price="item.market_price || item.activity_price"></price-format>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
</view>
|
||||
<view v-if="type === 'hot'" class="goods-hot">
|
||||
<navigator v-for="(item, index) in list" :key="index" class="item bg-white mt20 row" hover-class="none" open-type="navigate"
|
||||
:url="'/pages/goods_details/goods_details?id=' + item.id">
|
||||
<view class="goods-img" style="width: 180rpx;height:180rpx;">
|
||||
<custom-image :lazy-load="true" width="180rpx" height="180rpx" radius="6rpx" lazy-load :src="item.image"></custom-image>
|
||||
</view>
|
||||
<view class="goods-info ml20">
|
||||
<view class="goods-name line2 mb10">{{item.name}}</view>
|
||||
<text class="sale br60 xxs">已有{{item.sales_sum}}人购买</text>
|
||||
<view class="row-between mt10">
|
||||
<view class="price mt10 row">
|
||||
<price-format color="#FF2C3C" class="mr10" :first-size="34" :second-size="26" :subscript-size="26" :price="item.price" :weight="500"></price-format>
|
||||
<price-format class="muted" :firstSize="24" :secondSize="24" :subscript-size="24" line-through :price="item.market_price"></price-format>
|
||||
</view>
|
||||
<image class="icon-md" src="/static/images/icon_go_red.png"></image>
|
||||
</view>
|
||||
</view>
|
||||
<image class="paixu" :src="'/static/images/No.' + (index < 3 ? index : 3) + '.png'"></image>
|
||||
<view class="number xxs">{{ index + 1 }}</view>
|
||||
</navigator>
|
||||
</view>
|
||||
<view v-if="type === 'home-hot'" class="goods-home-hot goods-hot">
|
||||
<navigator v-for="(item, index) in list" :key="index" class="item bg-white mb20 row" hover-class="none" open-type="navigate"
|
||||
:url="'/pages/goods_details/goods_details?id=' + item.id">
|
||||
<view class="goods-img">
|
||||
<custom-image :lazy-load="true" width="240rpx" height="240rpx" radius="6rpx" lazy-load :src="item.image"></custom-image>
|
||||
</view>
|
||||
<view class="goods-info ml20 mr20">
|
||||
<view class="goods-name line2 mb10">{{item.name}}</view>
|
||||
<text class="sale br60 xxs">已有{{item.sales_sum}}人购买</text>
|
||||
<view class="row-between mt10">
|
||||
<view class="price mt10 row">
|
||||
<price-format color="#FF2C3C" class="mr10" :first-size="34" :second-size="26" :subscript-size="26" :price="item.price" :weight="500"></price-format>
|
||||
<price-format class="muted" :firstSize="24" :secondSize="24" :subscript-size="24" line-through :price="item.market_price"></price-format>
|
||||
</view>
|
||||
<button type="primary" class="br60" size="xs">去购买</button>
|
||||
</view>
|
||||
</view>
|
||||
<image class="paixu" :src="'/static/images/No.' + (index < 3 ? index : 3) + '.png'"></image>
|
||||
<view class="number">{{ index + 1 }}</view>
|
||||
</navigator>
|
||||
</view>
|
||||
<view v-if="type === 'new'" class="goods-new">
|
||||
<navigator v-for="(item, index) in list" :key="index" class="item bg-white mt20 row" hover-class="none" open-type="navigate"
|
||||
:url="'/pages/goods_details/goods_details?id=' + item.id">
|
||||
<view class="goods-img">
|
||||
<custom-image :lazy-load="true" width="240rpx" height="240rpx" radius="10rpx" lazy-load :src="item.image"></custom-image>
|
||||
</view>
|
||||
<view class="goods-info ml20 mr20 flex1">
|
||||
<view class="goods-name line2 mb20">{{item.name}}</view>
|
||||
<view class="row-between muted xxs ">
|
||||
<view class="line-through">
|
||||
<text>原价</text>
|
||||
<price-format :second-size="22" :first-size="22" :subscript-size="22" :price="item.market_price"></price-format>
|
||||
</view>
|
||||
<view>{{item.sales_sum}}人购买</view>
|
||||
</view>
|
||||
<view class="row-between mt10">
|
||||
<price-format color="#FF2C3C" :first-size="38" :subscript-size="26" :second-size="26" :price="item.price"
|
||||
:weight="500"></price-format>
|
||||
<button type="primary" class="br60" size="xs">立即抢购</button>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
</view>
|
||||
<view v-if="type === 'one'" class="goods-one mt20">
|
||||
<navigator v-for="(item, index) in list" :key="index" class="item bg-white row" hover-class="none" open-type="navigate"
|
||||
:url="'/pages/goods_details/goods_details?id=' + item.id">
|
||||
<view class="goods-img" style="width: 200rpx;height:200rpx;">
|
||||
<custom-image :lazy-load="true" width="200rpx" height="200rpx" radius="6rpx" lazy-load :src="item.image"></custom-image>
|
||||
</view>
|
||||
<view class="goods-info ml20">
|
||||
<view class="goods-name line2 mb10">{{item.name}}</view>
|
||||
<view class="row-between mt10">
|
||||
<view class="price mt10 row">
|
||||
<price-format color="#FF2C3C" class="mr10" :first-size="34" :second-size="26" :subscript-size="26" :price="item.price" :weight="500"></price-format>
|
||||
<price-format class="muted" :firstSize="24" :secondSize="24" :subscript-size="24" line-through :price="item.market_price"></price-format>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'double'
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
isBargain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.goods-list {
|
||||
.goods-double {
|
||||
flex-wrap: wrap;
|
||||
padding: 0 20rpx;
|
||||
align-items: stretch;
|
||||
.item {
|
||||
width: 347rpx;
|
||||
border-radius: 10rpx;
|
||||
|
||||
.goods-info {
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-hot {
|
||||
&.goods-home-hot {
|
||||
.item {
|
||||
padding: 0;
|
||||
.paixu,
|
||||
.number {
|
||||
left: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.item {
|
||||
position: relative;
|
||||
padding: 30rpx 20rpx;
|
||||
border-radius: 10rpx;
|
||||
|
||||
.goods-info {
|
||||
width: 450rpx;
|
||||
|
||||
.sale {
|
||||
padding: 4rpx 18rpx;
|
||||
color: #F79C0C;
|
||||
background-color: rgba(247, 156, 12, .1);
|
||||
}
|
||||
}
|
||||
|
||||
.paixu,
|
||||
.number {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 27rpx;
|
||||
width: 50rpx;
|
||||
height: 54rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
color: #621E09;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-one .item {
|
||||
padding: 20rpx;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
.goods-new .item {
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.16);
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<view class="group-list">
|
||||
<navigator v-for="(item, index) in groupList" :key="index" class="item bg-white mt20" hover-class="none" :url="'/pages/order_details/order_details?id=' + item.order_id">
|
||||
<view class="group-header row-between">
|
||||
<view>
|
||||
<view v-if="item.team_end_time">{{item.team_end_time}}</view>
|
||||
<view v-else>
|
||||
<view class="row" v-if="getTeamCountTime(item) >= 0">
|
||||
<view class="sm mr10">距离结束</view>
|
||||
<u-count-down :timestamp="getTeamCountTime(item)" color="#fff" bg-color="#FF2C3C" separator-color="#FF2C3C" font-size="24"
|
||||
height="36" separator-size="26" @end="reflesh"></u-count-down>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view :class="item.status == 2 ? 'muted' : 'primary'">{{getGroupStatus(item.status)}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="group-con">
|
||||
<view v-if="item.type" class="team-chief xs white">团长</view>
|
||||
<order-goods :team="{need: item.need}" :list="[{name: item.name, spec_value_str: item.spec_value_str,image: item.spec_image,goods_num: item.total_num, goods_id: item.goods_id, goods_price: item.goods_price}]">
|
||||
</order-goods>
|
||||
<view class="all-price row-end">
|
||||
<text class="muted xs">共{{item.total_num}}件商品,总金额:</text>
|
||||
<price-format show-subscript :subscript-size="30" :first-size="30" :second-size="30" :price="item.order_amount"></price-format>
|
||||
</view>
|
||||
</view>
|
||||
<view class="group-footer row" v-if="item.pay_status == 0">
|
||||
<view style="flex: 1"></view>
|
||||
<view>
|
||||
<navigator v-if="item.pay_status == 0" hover-class="none" :url="'/pages/order_details/order_details?id=' + item.order_id">
|
||||
<button size="sm" class="br60 lighter btn" type="primary" hover-class="none">
|
||||
去付款
|
||||
</button>
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
<loading-footer :status="status" :slot-empty="true" @refresh="reload">
|
||||
<view slot="empty" class="column-center" style="padding-top: 200rpx">
|
||||
<image class="img-null" src="/static/images/goods_null.png"></image>
|
||||
<text class="lighter">暂无拼团</text>
|
||||
</view>
|
||||
</loading-footer>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import {
|
||||
loadingType
|
||||
} from '@/utils/type';
|
||||
import {
|
||||
loadingFun
|
||||
} from '@/utils/tools';
|
||||
import {
|
||||
getUserGroup
|
||||
} from '@/api/activity';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
page: 1,
|
||||
groupList: [],
|
||||
status: loadingType.LOADING
|
||||
};
|
||||
},
|
||||
|
||||
props: {
|
||||
groupType: {
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
created: function() {},
|
||||
beforeMount: function() {
|
||||
this.getUserGroupFun();
|
||||
},
|
||||
destroyed: function() { // 在组件实例被从页面节点树移除时执行
|
||||
|
||||
},
|
||||
methods: {
|
||||
reflesh() {
|
||||
this.page = 1
|
||||
this.groupList = []
|
||||
this.status = loadingType.LOADING;
|
||||
this.getUserGroupFun();
|
||||
},
|
||||
|
||||
reload() {
|
||||
this.status = loadingType.LOADING
|
||||
this.getUserGroupFun();
|
||||
},
|
||||
|
||||
async getUserGroupFun() {
|
||||
let {
|
||||
page,
|
||||
groupType,
|
||||
groupList,
|
||||
status
|
||||
} = this;
|
||||
const data = await loadingFun(getUserGroup, page, groupList, status, {
|
||||
status: groupType
|
||||
})
|
||||
if (!data) return
|
||||
this.page = data.page
|
||||
this.groupList = data.dataList
|
||||
this.status = data.status
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
computed: {
|
||||
getGroupStatus() {
|
||||
return (status) => {
|
||||
var text = ''
|
||||
switch (status) {
|
||||
case 0:
|
||||
text = '拼团中';
|
||||
break;
|
||||
case 1:
|
||||
text = '拼团成功';
|
||||
break;
|
||||
case 2:
|
||||
text = '拼团失败';
|
||||
break;
|
||||
}
|
||||
return text
|
||||
}
|
||||
},
|
||||
getTeamCountTime() {
|
||||
return (item) => item.count_time = Math.min(item.found_end_time, item.end_time) - Date.now()/1000
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.group-list {
|
||||
min-height: calc(100vh - 80rpx);
|
||||
padding: 0 20rpx;
|
||||
overflow: hidden;
|
||||
.item {
|
||||
border-radius: 10rpx;
|
||||
position: relative;
|
||||
.group-header {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
border-bottom: 1px dotted #E5E5E5;
|
||||
}
|
||||
.team-chief {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
top: 105rpx;
|
||||
padding: 4rpx 20rpx;
|
||||
border-radius: 0 60rpx 60rpx 0;
|
||||
background: linear-gradient(87deg, #F95F2F 0%, #FF2C3C 100%);
|
||||
}
|
||||
.all-price {
|
||||
text-align: right;
|
||||
padding: 0 24rpx 20rpx;
|
||||
}
|
||||
.group-footer {
|
||||
height: 100rpx;
|
||||
border-top: $-solid-border;
|
||||
padding: 0 24rpx;
|
||||
.btn {
|
||||
width: 244rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,218 @@
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeshop开源商城系统
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee
|
||||
// | github下载:https://github.com/likeshop-github
|
||||
// | 访问官网:https://www.likeshop.cn
|
||||
// | 访问社区:https://home.likeshop.cn
|
||||
// | 访问手册:http://doc.likeshop.cn
|
||||
// | 微信公众号:likeshop技术社区
|
||||
// | likeshop系列产品在gitee、github等公开渠道开源版本可免费商用,未经许可不能去除前后端官方版权标识
|
||||
// | likeshop系列产品收费版本务必购买商业授权,购买去版权授权后,方可去除前后端官方版权标识
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布
|
||||
// | likeshop团队版权所有并拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeshop.cn.team
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
<template>
|
||||
<view class="every-day-coupon bg-white mb20" v-if="list.length">
|
||||
<view class="coupon-header row-between" @tap="goPage">
|
||||
<view class="normal lg title">每日领券</view>
|
||||
<view class="row">
|
||||
<view class="muted sm mr5">进入领券中心</view>
|
||||
<image src="/static/images/arrow_right.png" class="icon-sm"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="coupon-scroll">
|
||||
<scroll-view style="heigth: 138rpx" scroll-x="true" scroll-with-animation="true" @scroll="scrollBarChange">
|
||||
<view class="coupon-contain row">
|
||||
<view v-for="(item, index) in list" :key="index" :class="'coupon-item mr20 row-between ' + (item.is_get ? 'recieve' : '')"
|
||||
@tap="onRecive(item.id)">
|
||||
<view class="coupon-left">
|
||||
<view class="row info">
|
||||
<price-format :subscript-size="30" :first-size="56" :second-size="50" :price="item.money"></price-format>
|
||||
<view class="sm ml10" style="align-self: flex-end;line-height: 50rpx;margin-bottom: 3rpx">
|
||||
优惠券
|
||||
</view>
|
||||
</view>
|
||||
<view class="desc xs">{{item.use_condition}}</view>
|
||||
</view>
|
||||
<view class="coupon-right xxs primary column-center">立即领取</view>
|
||||
<image v-if="item.is_get" class="coupon-img" src="/static/images/home_img_ receive.png"></image>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="row-center mt20" v-if="list.length > 2">
|
||||
<cu-progress progressBarColor="#FF2C3C" :left="progressPer"></cu-progress>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
toLogin
|
||||
} from '@/utils/login';
|
||||
import {
|
||||
getCoupon
|
||||
} from '@/api/user';
|
||||
import {
|
||||
getRect
|
||||
} from '@/utils/tools'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
progressPer: 0
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
handler() {
|
||||
this.$nextTick(function(){
|
||||
getRect(".coupon-scroll", false, this).then(res => {
|
||||
this.rectWidth = res.width;
|
||||
});
|
||||
})
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
scrollBarChange(e) {
|
||||
let {
|
||||
progressPer
|
||||
} = this;
|
||||
let {
|
||||
scrollLeft,
|
||||
scrollWidth
|
||||
} = e.detail;
|
||||
progressPer = scrollLeft / (scrollWidth - this.rectWidth) * 100;
|
||||
this.progressPer = Number(progressPer.toFixed(0));
|
||||
},
|
||||
|
||||
onRecive(id) {
|
||||
if (!this.isLogin) {
|
||||
toLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
getCoupon(id).then(res => {
|
||||
if (res.code == 1) {
|
||||
this.$toast({
|
||||
title: res.msg
|
||||
})
|
||||
this.$emit('reflash');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
goPage() {
|
||||
if (!this.isLogin) {
|
||||
toLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: '/pages/user_getcoupon/user_getcoupon'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.every-day-coupon {
|
||||
background-image: url(../../static/images/home_bg_coupon.png);
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
height: 264rpx;
|
||||
width: 710rpx;
|
||||
margin: 0 20rpx;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 14rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.coupon-header {
|
||||
height: 90rpx;
|
||||
|
||||
.title {
|
||||
margin-left: 20rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-contain {
|
||||
.coupon-item {
|
||||
height: 134rpx;
|
||||
width: 326rpx;
|
||||
flex: none;
|
||||
position: relative;
|
||||
background: url(../../static/images/home_bg_coupon_red.png);
|
||||
background-size: 100% 100%;
|
||||
|
||||
&.recieve {
|
||||
background-image: url(../../static/images/home_bg_coupon_gray.png);
|
||||
|
||||
.coupon-right {
|
||||
background-color: #E5E5E5;
|
||||
color: $-color-muted;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-left {
|
||||
width: 269rpx;
|
||||
padding-left: 20rpx;
|
||||
color: white;
|
||||
|
||||
.info {
|
||||
height: 70rpx;
|
||||
border-bottom: 3rpx dashed #fff;
|
||||
|
||||
.money-logo {
|
||||
font-weight: bold;
|
||||
align-self: flex-end;
|
||||
line-height: 50rpx;
|
||||
margin-right: 6rpx;
|
||||
margin-bottom: 4rpx
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.coupon-right {
|
||||
width: 57rpx;
|
||||
height: 100%;
|
||||
background-color: #FFE5E6;
|
||||
border-radius: 0rpx 14rpx 14rpx 0rpx;
|
||||
writing-mode: tb;
|
||||
|
||||
}
|
||||
|
||||
.coupon-img {
|
||||
height: 77rpx;
|
||||
width: 99rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 73rpx;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<view>
|
||||
<l-painter :css="`width: 600rpx;height: 960rpx;`" isCanvasToTempFilePath
|
||||
@success="handleSuccess" custom-style="position: fixed; left: 200%">
|
||||
<l-painter-image :src="config.poster"
|
||||
css="position:absolute; width: 600rpx;height: 960rpx;object-fit: cover; border-radius: 20rpx;" />
|
||||
<l-painter-view
|
||||
css="position:absolute;background-color: #ffffff;width:100%;height: 240rpx;border-radius: 14rpx; bottom: 0;left: 0;padding-top: 30rpx;">
|
||||
|
||||
<l-painter-view css="width: 330rpx; display: inline-block;padding-left: 30rpx;">
|
||||
<l-painter-view>
|
||||
<l-painter-image :src="config.avatar"
|
||||
css="width: 80rpx; height: 80rpx; border-radius: 50%;" />
|
||||
<l-painter-text :text="config.nickname"
|
||||
css="margin-top: 10rpx; margin-left: 20rpx; color: #333333; font-size: 34rpx;line-clamp:1;font-weight: bold;width:220rpx;" />
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top: 30rpx;line-height: 34rpx;">
|
||||
<l-painter-text text="邀请你一起来赚大钱" css="color: #333333; font-size: 28rpx;" />
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top: 20rpx;">
|
||||
<l-painter-text :text="`邀请码:${config.code}`" css="color: #FF2C3C; font-size: 28rpx;" />
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="display: inline-block;margin-top:10rpx;">
|
||||
<l-painter-view css="padding-left: 30rpx;">
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<l-painter-qrcode css="width: 180rpx; height: 180rpx;" :text="config.link">
|
||||
</l-painter-qrcode>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<l-painter-image :src="config.qrCode" css="width: 180rpx; height: 180rpx;" />
|
||||
<!-- #endif -->
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import lPainter from '@/components/lime-painter/components/l-painter/l-painter.vue'
|
||||
import lPainterImage from '@/components/lime-painter/components/l-painter-image/l-painter-image.vue'
|
||||
import lPainterText from '@/components/lime-painter/components/l-painter-text/l-painter-text.vue'
|
||||
import lPainterView from '@/components/lime-painter/components/l-painter-view/l-painter-view.vue'
|
||||
import lPainterQrcode from '@/components/lime-painter/components/l-painter-qrcode/l-painter-qrcode.vue'
|
||||
export default {
|
||||
name: "share-poster",
|
||||
components: {
|
||||
lPainter,
|
||||
lPainterImage,
|
||||
lPainterText,
|
||||
lPainterView,
|
||||
lPainterQrcode
|
||||
},
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
goodsId: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
qrcode: {
|
||||
type: [String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSuccess(val) {
|
||||
this.$emit('success', val)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<view class="root" :style="{width,height}">
|
||||
<image :style="{width,height}" class="posterImg" :src="posterUrl" mode="aspectFit"></image>
|
||||
<view :style="{width,height}" @click="state=!state" class="box">
|
||||
<image class="playIcon" src="/static/images/icon_play.png" mode="widthFix"></image>
|
||||
</view>
|
||||
<video :id="videoId" :style="{height,width:state?'750rpx':'1rpx'}" @pause="state=0" @timeupdate="timeupdate" @fullscreenchange="fullscreenchange" class="video" :src="url"></video>
|
||||
<!-- <progress :style="{'top':height,width}" class="progress" :percent="currentTime?parseInt(currentTime/duration*100):0" show-info border-radius="5" active></progress> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed:{
|
||||
posterUrl(){
|
||||
if(this.poster) return this.poster
|
||||
return this.url + '?x-oss-process=video/snapshot,t_'+(parseInt(this.currentTime*1000))+',f_jpg,w_800,m_fast'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.videoId = Date.now() + Math.ceil(Math.random()*10000000)+"";
|
||||
},
|
||||
mounted() {
|
||||
this.VideoContext = uni.createVideoContext(this.videoId)
|
||||
},
|
||||
methods:{
|
||||
fullscreenchange(e){
|
||||
this.state = e.detail.fullScreen
|
||||
},
|
||||
timeupdate(e){
|
||||
this.duration = e.detail.duration
|
||||
this.currentTime = e.detail.currentTime
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
state(state, oldValue) {
|
||||
//console.log(state,'state');
|
||||
if(!state){
|
||||
this.VideoContext.pause()
|
||||
}else{
|
||||
this.VideoContext.play()
|
||||
setTimeout(()=>{
|
||||
this.VideoContext.requestFullScreen({direction:this.direction})
|
||||
},10)
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
VideoContext:{},
|
||||
state:false,
|
||||
currentTime:0,
|
||||
duration:0,
|
||||
videoId:''
|
||||
};
|
||||
},
|
||||
props: {
|
||||
poster: {
|
||||
type: [String,Boolean],
|
||||
default(){
|
||||
return ''
|
||||
}
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default(){
|
||||
return ''
|
||||
}
|
||||
},
|
||||
direction: {
|
||||
type: Number,
|
||||
default(){
|
||||
return 0
|
||||
}
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default(){
|
||||
return "750rpx";
|
||||
}
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default(){
|
||||
return "450rpx";
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.root{
|
||||
position:relative;
|
||||
width: 750rpx;
|
||||
height: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.posterImg,.video,.box{
|
||||
display: flex;
|
||||
width: 750rpx;
|
||||
height: 300px;
|
||||
//border: solid 1px red;absolute
|
||||
position:absolute;
|
||||
}
|
||||
.video{
|
||||
margin-left: -2000px;
|
||||
}
|
||||
.posterImg{
|
||||
//border: solid red 1px;
|
||||
}
|
||||
.box{
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.playIcon{
|
||||
width: 100rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
## 1.8.5.5(2021-08-17)
|
||||
- chore: 更新文档,删除 replace
|
||||
- fix: 修复 text 值为 number时报错
|
||||
## 1.8.5.4(2021-08-16)
|
||||
- fix: 字节小程序兼容
|
||||
## 1.8.5.3(2021-08-15)
|
||||
- fix: 修复线性渐变与css现实效果不一致的问题
|
||||
- chore: 更新文档
|
||||
## 1.8.5.2(2021-08-13)
|
||||
- chore: 增加`background-image`、`background-repeat` 能力,主要用于背景纹理的绘制,并不是代替`image`。例如:大面积的重复平铺的水印
|
||||
- 注意:这个功能H5暂时无法使用,因为[官方的API有BUG](https://ask.dcloud.net.cn/question/128793),待官方修复!!!
|
||||
## 1.8.5.1(2021-08-10)
|
||||
- fix: 修复因`margin`报错问题
|
||||
## 1.8.5(2021-08-09)
|
||||
- chore: 增加margin支持`auto`,以达到居中效果
|
||||
## 1.8.4(2021-08-06)
|
||||
- chore: 增加判断缓存文件条件
|
||||
- fix: 修复css 多余空格报错问题
|
||||
## 1.8.3(2021-08-04)
|
||||
- tips: 1.6.x 以下的版本升级到1.8.x后要为每个元素都加上定位:position: 'absolute'
|
||||
- fix: 修复只有一个view子元素时不计算高度的问题
|
||||
## 1.8.2(2021-08-03)
|
||||
- fix: 修复 path-type 为 `url` 无效问题
|
||||
- fix: 修复 qrcode `text` 为空时报错问题
|
||||
- fix: 修复 image `src` 动态设置时不生效问题
|
||||
- feat: 增加 css 属性 `min-width` `max-width`
|
||||
## 1.8.1(2021-08-02)
|
||||
- fix: 修复无法加载本地图片
|
||||
## 1.8.0(2021-08-02)
|
||||
- chore 文档更新
|
||||
- 使用旧版的同学不要升级!
|
||||
## 1.8.0-beta(2021-07-30)
|
||||
- ## 全新布局方式 不兼容旧版!
|
||||
- chore: 布局方式变更
|
||||
- tips: 微信canvas 2d 不支持真机调试
|
||||
## 1.6.6(2021-07-09)
|
||||
- chore: 统一命名规范,无须主动引入组件
|
||||
## 1.6.5(2021-06-08)
|
||||
- chore: 去掉console
|
||||
## 1.6.4(2021-06-07)
|
||||
- fix: 修复 数字 为纯字符串时不转换的BUG
|
||||
## 1.6.3(2021-06-06)
|
||||
- fix: 修复 PC 端放大的BUG
|
||||
## 1.6.2(2021-05-31)
|
||||
- fix: 修复 报`adaptor is not a function`错误
|
||||
- fix: 修复 text 多行高度
|
||||
- fix: 优化 默认文字的基准线
|
||||
- feat: `@progress`事件,监听绘制进度
|
||||
## 1.6.1(2021-02-28)
|
||||
- 删除多余节点
|
||||
## 1.6.0(2021-02-26)
|
||||
- 调整为uni_modules目录规范
|
||||
- 修复:transform的rotate不能为负数问题
|
||||
- 新增:`pathType` 指定生成图片返回的路径类型,可选值有 `base64`、`url`
|
||||
@@ -0,0 +1,141 @@
|
||||
import {base64ToPath} from '../l-painter/utils.js'
|
||||
|
||||
const styles = (v = '') => v.split(';').filter(v => v && !/^[\n\s]+$/.test(v)).map(v => {
|
||||
const item = v.split(':');
|
||||
return {
|
||||
[item[0]
|
||||
.replace(/-([a-z])/g, function() {
|
||||
return arguments[1].toUpperCase()
|
||||
})
|
||||
.replace(/\s+/g, '')
|
||||
]: item?. [1]?.replace(/^\s+/, '')?.replace(/\s+$/, '') || ''
|
||||
}
|
||||
})
|
||||
export function parent(parent) {
|
||||
return {
|
||||
provide() {
|
||||
return {
|
||||
[parent]: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
el: {
|
||||
css: {},
|
||||
views: []
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
css: {
|
||||
handler(v) {
|
||||
if (this.canvasId) {
|
||||
this.el.css = typeof v == 'object' ? v : v && Object.assign(...styles(v)) || {}
|
||||
this.canvasWidth = this.el.css?.width || this.canvasWidth
|
||||
this.canvasHeight = this.el.css?.height || this.canvasHeight
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export function children(parent, options = {}) {
|
||||
const indexKey = options.indexKey || 'index'
|
||||
return {
|
||||
inject: {
|
||||
[parent]: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
el: {
|
||||
handler(v, o) {
|
||||
if (JSON.stringify(v) != JSON.stringify(o))
|
||||
this.bindRelation()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
src: {
|
||||
handler(v, o) {
|
||||
if (v != o)
|
||||
this.bindRelation()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
text: {
|
||||
handler(v, o) {
|
||||
if (v != o) this.bindRelation()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
css: {
|
||||
handler(v, o) {
|
||||
if (v != o)
|
||||
this.el.css = typeof v == 'object' ? v : v && Object.assign(...styles(v)) || {}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
replace: {
|
||||
handler(v, o) {
|
||||
if (JSON.stringify(v) != JSON.stringify(o))
|
||||
this.bindRelation()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
created() {
|
||||
Object.defineProperty(this, 'parent', {
|
||||
get: () => {
|
||||
return this[parent]
|
||||
},
|
||||
})
|
||||
Object.defineProperty(this, 'index', {
|
||||
get: () => {
|
||||
this.bindRelation();
|
||||
return this.parent?.el.views?.indexOf(this.el)
|
||||
},
|
||||
});
|
||||
this.el.type = this.type
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.parent) {
|
||||
this.parent.el.views = this.parent.el.views.filter(
|
||||
(item) => item._uid !== this._uid
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
bindRelation() {
|
||||
if (!this.el._uid) {
|
||||
this.el._uid = this._uid
|
||||
}
|
||||
if (['text', 'qrcode'].includes(this.type)) {
|
||||
this.el.text = this.$slots?.default?. [0]?.text || this.text?.replace(/\\n/g, '\n')
|
||||
}
|
||||
if (this.type == 'text' && this.replace) {
|
||||
this.el.replace = this.replace
|
||||
}
|
||||
if (this.type == 'image') {
|
||||
this.el.src = this.src
|
||||
}
|
||||
// || this.parent.el.views.indexOf(this.el) !== -1
|
||||
if (!this.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
let views = this.parent.el.views || [];
|
||||
if (views.indexOf(this.el) !== -1) {
|
||||
this.parent.el.views = views.map(v => v._uid == this._uid ? this.el : v)
|
||||
} else {
|
||||
this.parent.el.views = [...views, this.el];
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.bindRelation()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<view></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-image',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
css: [String, Object],
|
||||
src: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'image',
|
||||
el: {
|
||||
css: {},
|
||||
src: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<view></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-qrcode',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
css: [String, Object],
|
||||
text: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'qrcode',
|
||||
el: {
|
||||
css: {},
|
||||
text: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<text style="opacity: 0;"><slot/></text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-text',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
css: [String, Object],
|
||||
text: [String, Number],
|
||||
replace: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'text',
|
||||
el: {
|
||||
css: {},
|
||||
text: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<view><slot/></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-view',
|
||||
mixins:[children('painter'), parent('painter')],
|
||||
props: {
|
||||
css: [String, Object],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'view',
|
||||
el: {
|
||||
css: {},
|
||||
views:[]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,523 @@
|
||||
<template>
|
||||
<view v-if="canvasId && size" class="lime-painter" :style="size + customStyle">
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas>
|
||||
<canvas class="lime-painter__canvas" v-else :canvas-id="canvasId" :style="size" :id="canvasId" :width="boardWidth * dpr" :height="boardHeight * dpr"></canvas>
|
||||
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<web-view
|
||||
:style="size"
|
||||
ref="webview"
|
||||
class="lime-painter__canvas"
|
||||
@pagefinish="onPageFinish"
|
||||
@error="onError"
|
||||
@onPostMessage="onMessage"
|
||||
></web-view>
|
||||
<!-- #endif -->
|
||||
<slot/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { toPx, compareVersion, sleep, base64ToPath, pathToBase64, getImageInfo, isBase64 } from './utils';
|
||||
import {parent} from '../common/relation'
|
||||
// #ifndef APP-NVUE
|
||||
import {Painter} from './painter'
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
import painterScript from './nvue'
|
||||
// #endif
|
||||
export default {
|
||||
name: 'lime-painter',
|
||||
mixins:[parent('painter')],
|
||||
props: {
|
||||
board: Object,
|
||||
pathType: {
|
||||
type: String,
|
||||
// default: 'url'
|
||||
// 'base64'、'url'
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
default: 'png'
|
||||
},
|
||||
quality: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
css: [String, Object],
|
||||
width: [Number, String],
|
||||
height: [Number, String],
|
||||
pixelRatio: Number,
|
||||
customStyle: String,
|
||||
isCanvasToTempFilePath: Boolean,
|
||||
sleep: {
|
||||
type: Number,
|
||||
default: 1000 / 30
|
||||
},
|
||||
beforeDelay: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
afterDelay: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO||MP-ALIPAY
|
||||
type: {
|
||||
type: String,
|
||||
default: '2d'
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
use2dCanvas: true,
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
use2dCanvas: false,
|
||||
// #endif
|
||||
canvasHeight: 150,
|
||||
canvasWidth: null,
|
||||
isDrawIng: false,
|
||||
isPC: false,
|
||||
inited: false,
|
||||
name: 'view',
|
||||
progress: 0,
|
||||
// #ifdef APP-NVUE
|
||||
tempFilePath: [],
|
||||
// #endif
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canvasId() {
|
||||
return `l-painter${this._uid}`
|
||||
},
|
||||
size() {
|
||||
if(this.boardWidth && this.boardHeight) {
|
||||
return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
|
||||
}
|
||||
},
|
||||
dpr() {
|
||||
return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
|
||||
},
|
||||
boardWidth() {
|
||||
const {width = 0, canvasWidth = 0} = this
|
||||
const {width: boardWidth = 0} = this.board?.css || this.board || {}
|
||||
return Math.max(toPx(width || boardWidth), toPx(canvasWidth));
|
||||
},
|
||||
boardHeight() {
|
||||
const {height= 0, canvasHeight =0} = this
|
||||
const {height: boardHeight = 0} = this.board?.css || this.board || {}
|
||||
return Math.max(toPx(height || boardHeight), toPx(canvasHeight));
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
canvasWidth(v) {
|
||||
if(this.el.css && !this.el.css?.width) {
|
||||
this.el.css.width = v
|
||||
}
|
||||
},
|
||||
size(v) {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.use2dCanvas) {
|
||||
this.inited = false;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
this.inited = false;
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
created() {
|
||||
const { SDKVersion, version, platform, environment } = uni.getSystemInfoSync();
|
||||
// #ifdef MP-WEIXIN
|
||||
// ios wx7.0.20 createImage bug
|
||||
this.isPC = /windows/i.test(platform)
|
||||
this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '2.9.2') >= 0 && !((/ios/i.test(platform) && /7.0.20/.test(version)) || /wxwork/i.test(environment)) && !this.isPC;
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '1.78.0') >= 0;
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '2.8.0') >= 0;
|
||||
// #endif
|
||||
},
|
||||
// #endif
|
||||
mounted() {
|
||||
// #ifdef APP-NVUE
|
||||
this.webViewInit()
|
||||
// #endif
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
if(this.board) {
|
||||
this.$watch('board', this.watchRender, {deep: true,immediate: true});
|
||||
} else if(this.el.views.length) {
|
||||
this.$watch('el', this.watchRender, {deep: true,immediate: true});
|
||||
}
|
||||
},30)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async watchRender(val) {
|
||||
this.progress = 0
|
||||
if (JSON.stringify(val) === '{}' || !val) return;
|
||||
clearTimeout(this.rendertimer)
|
||||
this.rendertimer = setTimeout(() => {
|
||||
this.render(val);
|
||||
}, this.beforeDelay)
|
||||
},
|
||||
async setFilePath(path, isEmit) {
|
||||
let filePath = path
|
||||
const {pathType} = this
|
||||
if(pathType == 'base64' && !isBase64(path)) {
|
||||
filePath = await pathToBase64(path)
|
||||
} else if(pathType == 'url' && isBase64(path)) {
|
||||
filePath = await base64ToPath(path)
|
||||
}
|
||||
if(isEmit) {
|
||||
this.$emit('success', filePath);
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
// #ifdef APP-NVUE
|
||||
onError(e) {
|
||||
console.log('onError', e)
|
||||
},
|
||||
// onPagestart() {
|
||||
// console.log('onPagestart')
|
||||
// },
|
||||
// onPageFinish() {
|
||||
// this.$refs.webview.evalJS(`init()`)
|
||||
// },
|
||||
// onReceivedTitle() {
|
||||
// console.log('onReceivedTitle')
|
||||
// },
|
||||
onMessage(e) {
|
||||
const res = e?.detail?.data[0] || null;
|
||||
if (res?.event) {
|
||||
if(res.event == 'inited') {
|
||||
this.inited = true
|
||||
}
|
||||
if(res.event == 'layoutChange') {
|
||||
const data = JSON.parse(res.data)
|
||||
this.canvasWidth = data.width;
|
||||
this.canvasHeight = data.height;
|
||||
}
|
||||
if(res.event == 'progressChange') {
|
||||
this.progress = res.data * 1
|
||||
}
|
||||
if(res.event == 'file') {
|
||||
this.tempFilePath.push(res.data)
|
||||
if(this.tempFilePath.length > 7) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
return
|
||||
}
|
||||
if(res.event == 'success') {
|
||||
if(res.data) {
|
||||
this.tempFilePath.push(res.data)
|
||||
if(this.tempFilePath.length > 8) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
if(this.isCanvasToTempFilePath) {
|
||||
this.setFilePath(this.tempFilePath.join(''), true)
|
||||
}
|
||||
} else {
|
||||
this.$emit('fail')
|
||||
}
|
||||
return
|
||||
}
|
||||
this.$emit(res.event, JSON.parse(res.data));
|
||||
} else if (res?.file) {
|
||||
this.file = res.data;
|
||||
} else {
|
||||
console.error(res);
|
||||
}
|
||||
},
|
||||
async webViewInit() {
|
||||
await sleep(30)
|
||||
// await this.getWebViewInited()
|
||||
const webview = this.$refs.webview;
|
||||
webview.evalJS(painterScript)
|
||||
},
|
||||
getWebViewInited() {
|
||||
if(this.inited) return Promise.resolve(this.inited);
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'inited',
|
||||
async val => {if(val) {resolve(val)}},
|
||||
{immediate: true}
|
||||
);
|
||||
})
|
||||
},
|
||||
getTempFilePath() {
|
||||
if(this.tempFilePath.length == 8) return Promise.resolve(this.tempFilePath)
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'tempFilePath',
|
||||
async val => {if(val.length == 8) {resolve(val.join(''))}}
|
||||
);
|
||||
})
|
||||
},
|
||||
getWebViewDone() {
|
||||
if(this.progress == 1) return Promise.resolve(this.progress);
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'progress',
|
||||
async val => {
|
||||
if(val == 1) {
|
||||
this.$emit('done')
|
||||
resolve(val)
|
||||
}},
|
||||
{immediate: true}
|
||||
);
|
||||
})
|
||||
},
|
||||
async render(args) {
|
||||
const newNode = await this.calcImage(args);
|
||||
await this.getWebViewInited()
|
||||
const webview = this.$refs.webview;
|
||||
webview.evalJS(`source(${JSON.stringify(newNode)})`)
|
||||
if(this.isCanvasToTempFilePath) {
|
||||
await this.getWebViewDone()
|
||||
await sleep(this.afterDelay)
|
||||
const params = {fileType: this.fileType, quality: this.quality}
|
||||
webview.evalJS(`save(${JSON.stringify(params)})`)
|
||||
}
|
||||
},
|
||||
async calcImage(args) {
|
||||
let node = JSON.parse(JSON.stringify(args))
|
||||
const url = node.url || node.src
|
||||
if(node.type === "image" && url && !isBase64(url)) {
|
||||
const suffix = url.match(/\.(\w+)$/)[1]
|
||||
const {width = 0, height = 0, path, naturalSrc} = await getImageInfo(url)
|
||||
const src = await pathToBase64(path)
|
||||
node.src = src.replace(/^data:application[\w\/]+;base64/,'data:image/'+suffix+';base64')
|
||||
} else if(node.views?.length) {
|
||||
for (let i = 0; i < node.views.length; i++) {
|
||||
node.views[i] = await this.calcImage(node.views[i])
|
||||
}
|
||||
}
|
||||
return node
|
||||
},
|
||||
async canvasToTempFilePath(args = {}){
|
||||
this.tempFilePath = []
|
||||
this.$refs.webview.evalJS(`save(${JSON.stringify(args)})`)
|
||||
try{
|
||||
let tempFilePath = await this.getTempFilePath()
|
||||
tempFilePath = await this.setFilePath(tempFilePath)
|
||||
args.success({errMsg: "canvasToTempFilePath:ok", tempFilePath})
|
||||
}catch(e){
|
||||
args.fail({error: e})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
getParentWeith() {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`.lime-painter`)
|
||||
.boundingClientRect()
|
||||
.exec(res => {
|
||||
this.canvasWidth = Math.ceil(res[0].width)
|
||||
this.canvasHeight = res[0].height
|
||||
})
|
||||
},
|
||||
async update(args, single) {
|
||||
this.painter = null;
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.use2dCanvas) {
|
||||
this.ctx = null;
|
||||
this.inited = false;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
this.inited = false;
|
||||
// #endif
|
||||
this.isDrawIng = false;
|
||||
await new Promise(resolve => this.$nextTick(resolve));
|
||||
await sleep(200);
|
||||
return this.render(args, single);
|
||||
},
|
||||
async render(args = {}, single = false) {
|
||||
if (this.isDrawIng) {
|
||||
return this.update(args, single);
|
||||
}
|
||||
this.isDrawIng = true;
|
||||
const isArg = JSON.stringify(args) != '{}';
|
||||
const ctx = await this.getContext();
|
||||
let { use2dCanvas, boardWidth, boardHeight, canvas, afterDelay } = this;
|
||||
if (use2dCanvas && !canvas) {
|
||||
return Promise.reject(new Error('render: fail canvas has not been created'));
|
||||
}
|
||||
this.boundary = {
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: boardWidth,
|
||||
height: boardHeight
|
||||
};
|
||||
// if (!single) {
|
||||
// ctx.clearRect(0, 0, boardWidth, boardHeight);
|
||||
// }
|
||||
if(!this.painter) {
|
||||
this.painter = new Painter({context: ctx, canvas, width: boardWidth, height: boardHeight, pixelRatio: this.dpr}, this)
|
||||
}
|
||||
this.painter.listen('progressChange', (v) => {
|
||||
this.$emit('progress', v)
|
||||
})
|
||||
const {width, height} = await this.painter.source(args)
|
||||
this.canvasHeight = toPx(this.el.css?.height) || height
|
||||
this.canvasWidth = toPx(this.el.css?.width) || width
|
||||
this.boundary.height = this.canvasHeight
|
||||
this.boundary.width = this.canvasWidth
|
||||
await sleep(this.sleep);
|
||||
await this.painter.render()
|
||||
|
||||
await new Promise(resolve => this.$nextTick(resolve));
|
||||
if (!use2dCanvas && !single) {
|
||||
await this.canvasDraw();
|
||||
}
|
||||
if (afterDelay && use2dCanvas) {
|
||||
await sleep(afterDelay);
|
||||
}
|
||||
this.$emit('done');
|
||||
if (this.isCanvasToTempFilePath && !single && this.isDrawIng) {
|
||||
this.canvasToTempFilePath()
|
||||
.then(async res => {
|
||||
this.$emit('success', res.tempFilePath)
|
||||
})
|
||||
.catch(err => {
|
||||
this.$emit('fail', new Error(JSON.stringify(err)));
|
||||
});
|
||||
}
|
||||
this.isDrawIng = false;
|
||||
return Promise.resolve({ ctx, draw: this.painter, node: this.node });
|
||||
},
|
||||
async custom(cb) {
|
||||
const { ctx, draw } = await this.render({}, true);
|
||||
ctx.save();
|
||||
await cb(ctx, draw);
|
||||
ctx.restore();
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
async single(args = {}) {
|
||||
const res = await this.render(args, true);
|
||||
return Promise.resolve(res);
|
||||
},
|
||||
canvasDraw(flag = false) {
|
||||
return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this.afterDelay)));
|
||||
},
|
||||
async getContext() {
|
||||
if (this.ctx && this.inited) {
|
||||
return Promise.resolve(this.ctx);
|
||||
}
|
||||
this.getParentWeith()
|
||||
const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
|
||||
const _getContext = () => {
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${this.canvasId}`)
|
||||
.boundingClientRect()
|
||||
.exec(res => {
|
||||
if (res) {
|
||||
const ctx = uni.createCanvasContext(this.canvasId, this);
|
||||
if (!this.inited) {
|
||||
this.inited = true;
|
||||
this.use2dCanvas = false;
|
||||
this.canvas = res;
|
||||
}
|
||||
if(this.isPC) {
|
||||
ctx.scale(1/dpr, 1/dpr);
|
||||
}
|
||||
// #ifdef MP-ALIPAY
|
||||
ctx.scale(dpr, dpr);
|
||||
// #endif
|
||||
this.ctx = ctx
|
||||
resolve(this.ctx);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
// #ifndef MP-WEIXIN
|
||||
return _getContext();
|
||||
// #endif
|
||||
|
||||
if (!use2dCanvas) {
|
||||
return _getContext();
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${this.canvasId}`)
|
||||
.node()
|
||||
.exec(res => {
|
||||
let { node: canvas } = res[0];
|
||||
if (!canvas) {
|
||||
this.use2dCanvas = false;
|
||||
resolve(this.getContext());
|
||||
}
|
||||
const ctx = canvas.getContext(type);
|
||||
if (!this.inited) {
|
||||
this.inited = true;
|
||||
this.use2dCanvas = true;
|
||||
this.canvas = canvas;
|
||||
}
|
||||
this.ctx = ctx
|
||||
resolve(this.ctx);
|
||||
});
|
||||
});
|
||||
},
|
||||
canvasToTempFilePath(args = {}) {
|
||||
const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
|
||||
let destWidth = width * dpr;
|
||||
let destHeight = height * dpr;
|
||||
// #ifdef MP-ALIPAY
|
||||
width = destWidth;
|
||||
height = destHeight;
|
||||
// #endif
|
||||
const success = async (res) => {
|
||||
const tempFilePath = await this.setFilePath(res.tempFilePath)
|
||||
resolve(Object.assign(res, {tempFilePath}))
|
||||
}
|
||||
const copyArgs = Object.assign({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
destWidth,
|
||||
destHeight,
|
||||
canvasId,
|
||||
fileType,
|
||||
quality,
|
||||
success,
|
||||
fail: reject
|
||||
}, args);
|
||||
if (use2dCanvas) {
|
||||
delete copyArgs.canvasId;
|
||||
copyArgs.canvas = this.canvas;
|
||||
}
|
||||
uni.canvasToTempFilePath(copyArgs, this);
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.lime-painter, .lime-painter__canvas {
|
||||
// #ifndef APP-NVUE
|
||||
width: 100%;
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
flex: 1;
|
||||
// #endif
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
const painterContent = `
|
||||
var cache = [];
|
||||
var painter = null;
|
||||
var canvas = null;
|
||||
var context = null;
|
||||
var timer = null;
|
||||
var pixelRatio = 1;
|
||||
console.log = function(...args) {
|
||||
postMessage(args);
|
||||
};
|
||||
function stringify(key, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.indexOf(value) !== -1) {
|
||||
return;
|
||||
}
|
||||
cache.push(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
function emit(event, data) {
|
||||
let dataStr = typeof data !== 'object' && data !== null ? data : JSON.stringify(data, stringify);
|
||||
postMessage({
|
||||
event,
|
||||
data: dataStr
|
||||
});
|
||||
cache = [];
|
||||
};
|
||||
function postMessage(data) {
|
||||
uni.postMessage({
|
||||
data
|
||||
});
|
||||
};
|
||||
function init() {
|
||||
canvas = document.querySelector('#lime-painter');
|
||||
context = canvas.getContext('2d');
|
||||
pixelRatio = window.devicePixelRatio;
|
||||
painter = new Painter({
|
||||
id: 'lime-painter',
|
||||
context,
|
||||
canvas,
|
||||
pixelRatio,
|
||||
width: canvas.offsetWidth,
|
||||
height: canvas.offsetHeight
|
||||
});
|
||||
emit('inited', true);
|
||||
painter.listen('progressChange', (v) => {
|
||||
emit('progressChange', v);
|
||||
});
|
||||
};
|
||||
function save(args) {
|
||||
delete args.success;
|
||||
delete args.fail;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
const path = painter.save(args);
|
||||
if(typeof path == 'string') {
|
||||
const index = Math.ceil(path.length / 8);
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if(i == 7) {
|
||||
emit('success', path.substr(i * index, index));
|
||||
} else {
|
||||
emit('file', path.substr(i * index, index));
|
||||
}
|
||||
};
|
||||
} else {
|
||||
emit('fail', '');
|
||||
};
|
||||
}, 30);
|
||||
};
|
||||
async function source(args) {
|
||||
let res = await painter.source(args);
|
||||
emit('layoutChange', res);
|
||||
await painter.render();
|
||||
};
|
||||
`
|
||||
export default `
|
||||
document.write("<canvas id='lime-painter'>不支持cavnas</canvas>");
|
||||
let meta = document.createElement('meta');
|
||||
meta.name = 'viewport';
|
||||
meta.content = 'width=device-width, initial-scale=1.0';
|
||||
document.head.appendChild(meta);
|
||||
let styleEl = document.createElement('style');
|
||||
styleEl.setAttribute('type', 'text/css');
|
||||
styleEl.textContent='html,body,#lime-painter{padding: 0; margin: 0; width:100%;height:100%}';
|
||||
document.head.appendChild(styleEl);
|
||||
|
||||
var script = document.createElement("script");
|
||||
script.language = "javascript";
|
||||
script.src = "https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js";
|
||||
script.onload = function() {
|
||||
var script = document.createElement("script");
|
||||
script.language = "javascript";
|
||||
script.src = "https://cdn.jsdelivr.net/gh/liangei/image@latest/lime-ui/lime-painter/painter.js";
|
||||
script.onload = function() {init()};
|
||||
document.head.appendChild(script);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
|
||||
var script = document.createElement("script");
|
||||
script.language = "javascript";
|
||||
script.text = "${painterContent}";
|
||||
document.body.appendChild(script);
|
||||
`
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,414 @@
|
||||
const networkReg = /^(http|\/\/)/;
|
||||
export const isBase64 = (path) => /^data:image\/(\w+);base64/.test(path);
|
||||
export function sleep(delay) {
|
||||
return new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
const isDev = ['devtools'].includes(uni.getSystemInfoSync().platform)
|
||||
// 缓存图片
|
||||
let cache = {}
|
||||
export function isNumber(value) {
|
||||
return /^-?\d+(\.\d+)?$/.test(value);
|
||||
}
|
||||
export function toPx(value, baseSize, isDecimal = false) {
|
||||
// 如果是数字
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
// 如果是字符串数字
|
||||
if (isNumber(value)) {
|
||||
return value * 1
|
||||
}
|
||||
// 如果有单位
|
||||
if (typeof value === 'string') {
|
||||
const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g
|
||||
const results = reg.exec(value);
|
||||
if (!value || !results) {
|
||||
return 0;
|
||||
}
|
||||
const unit = results[3];
|
||||
value = parseFloat(value);
|
||||
let res = 0;
|
||||
if (unit === 'rpx') {
|
||||
res = uni.upx2px(value);
|
||||
} else if (unit === 'px') {
|
||||
res = value * 1;
|
||||
} else if (unit === '%') {
|
||||
res = value * toPx(baseSize) / 100;
|
||||
} else if (unit === 'em') {
|
||||
res =value * toPx(baseSize || 14);
|
||||
}
|
||||
return isDecimal ? res.toFixed(2) * 1 : Math.round(res);
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 计算版本
|
||||
export function compareVersion(v1, v2) {
|
||||
v1 = v1.split('.')
|
||||
v2 = v2.split('.')
|
||||
const len = Math.max(v1.length, v2.length)
|
||||
while (v1.length < len) {
|
||||
v1.push('0')
|
||||
}
|
||||
while (v2.length < len) {
|
||||
v2.push('0')
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = parseInt(v1[i], 10)
|
||||
const num2 = parseInt(v2[i], 10)
|
||||
|
||||
if (num1 > num2) {
|
||||
return 1
|
||||
} else if (num1 < num2) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
// #ifdef MP
|
||||
export const prefix = () => {
|
||||
// #ifdef MP-TOUTIAO
|
||||
return tt
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
return wx
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
return swan
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
return my
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
return qq
|
||||
// #endif
|
||||
// #ifdef MP-360
|
||||
return qh
|
||||
// #endif
|
||||
}
|
||||
// #endif
|
||||
|
||||
const base64ToArrayBuffer = (data) => {
|
||||
// #ifndef MP-WEIXIN || APP-PLUS
|
||||
/**
|
||||
* Base64Binary.decode(base64_string);
|
||||
* Base64Binary.decodeArrayBuffer(base64_string);
|
||||
*/
|
||||
const Base64Binary = {
|
||||
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
|
||||
/* will return a Uint8Array type */
|
||||
decodeArrayBuffer(input) {
|
||||
const bytes = (input.length/4) * 3;
|
||||
const ab = new ArrayBuffer(bytes);
|
||||
this.decode(input, ab);
|
||||
return ab;
|
||||
},
|
||||
removePaddingChars(input) {
|
||||
const lkey = this._keyStr.indexOf(input.charAt(input.length - 1));
|
||||
if(lkey == 64){
|
||||
return input.substring(0,input.length - 1);
|
||||
}
|
||||
return input;
|
||||
},
|
||||
decode(input, arrayBuffer) {
|
||||
//get last chars to see if are valid
|
||||
input = this.removePaddingChars(input);
|
||||
input = this.removePaddingChars(input);
|
||||
|
||||
const bytes = parseInt((input.length / 4) * 3, 10);
|
||||
|
||||
let uarray;
|
||||
let chr1, chr2, chr3;
|
||||
let enc1, enc2, enc3, enc4;
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
|
||||
if (arrayBuffer)
|
||||
uarray = new Uint8Array(arrayBuffer);
|
||||
else
|
||||
uarray = new Uint8Array(bytes);
|
||||
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
|
||||
for (i=0; i<bytes; i+=3) {
|
||||
//get the 3 octects in 4 ascii chars
|
||||
enc1 = this._keyStr.indexOf(input.charAt(j++));
|
||||
enc2 = this._keyStr.indexOf(input.charAt(j++));
|
||||
enc3 = this._keyStr.indexOf(input.charAt(j++));
|
||||
enc4 = this._keyStr.indexOf(input.charAt(j++));
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
uarray[i] = chr1;
|
||||
if (enc3 != 64) uarray[i+1] = chr2;
|
||||
if (enc4 != 64) uarray[i+2] = chr3;
|
||||
}
|
||||
return uarray;
|
||||
}
|
||||
}
|
||||
return Base64Binary.decodeArrayBuffer(data)
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN || APP-PLUS
|
||||
return uni.base64ToArrayBuffer(data)
|
||||
// #endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* base64转路径
|
||||
* @param {Object} base64
|
||||
*/
|
||||
export function base64ToPath(base64) {
|
||||
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP
|
||||
const fs = uni.getFileSystemManager()
|
||||
//自定义文件名
|
||||
if (!format) {
|
||||
console.error('ERROR_BASE64SRC_PARSE')
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
let pre = prefix()
|
||||
const filePath = `${pre.env.USER_DATA_PATH}/${time}.${format}`
|
||||
//let buffer = base64ToArrayBuffer(bodyData)
|
||||
console.log(filePath)
|
||||
fs.writeFile({
|
||||
filePath,
|
||||
data: base64.replace(/^data:\S+\/\S+;base64,/, ''),
|
||||
encoding: 'base64',
|
||||
// data: buffer,
|
||||
// encoding: 'binary',
|
||||
success() {
|
||||
resolve(filePath)
|
||||
},
|
||||
fail(err) {
|
||||
|
||||
console.log(base64,'!!!!!!')
|
||||
console.error('获取base64图片失败', JSON.stringify(err))
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// mime类型
|
||||
let mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
|
||||
//base64 解码
|
||||
let byteString = atob(base64.split(',')[1]);
|
||||
//创建缓冲数组
|
||||
let arrayBuffer = new ArrayBuffer(byteString.length);
|
||||
//创建视图
|
||||
let intArray = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
intArray[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
resolve(URL.createObjectURL(new Blob([intArray], { type: mimeString })))
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
|
||||
bitmap.loadBase64Data(base64, () => {
|
||||
if (!format) {
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const filePath = `_doc/uniapp_temp/${time}.${format}`
|
||||
bitmap.save(filePath, {},
|
||||
() => {
|
||||
bitmap.clear()
|
||||
resolve(filePath)
|
||||
},
|
||||
(error) => {
|
||||
bitmap.clear()
|
||||
console.error(`${JSON.stringify(error)}`)
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
bitmap.clear()
|
||||
console.error(`${JSON.stringify(error)}`)
|
||||
reject(error)
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径转base64
|
||||
* @param {Object} string
|
||||
*/
|
||||
export function pathToBase64(path) {
|
||||
if(/^data:/.test(path)) return path
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef H5
|
||||
const _canvas = ()=> {
|
||||
let image = new Image();
|
||||
image.setAttribute("crossOrigin",'Anonymous');
|
||||
image.onload = function() {
|
||||
let canvas = document.createElement('canvas');
|
||||
// 获取图片原始宽高
|
||||
canvas.width = this.naturalWidth;
|
||||
canvas.height = this.naturalHeight;
|
||||
// 将图片插入画布并开始绘制
|
||||
canvas.getContext('2d').drawImage(image, 0, 0);
|
||||
let result = canvas.toDataURL('image/png')
|
||||
resolve(result);
|
||||
canvas.height = canvas.width = 0
|
||||
}
|
||||
image.src = path
|
||||
image.onerror = (error) => {
|
||||
console.error(`urlToBase64 error: ${path}`, JSON.stringify(error))
|
||||
reject(new Error('urlToBase64 error'));
|
||||
};
|
||||
}
|
||||
const _fileReader = (blob) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (e) => {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
fileReader.readAsDataURL(blob);
|
||||
fileReader.onerror = (error) => {
|
||||
console.error('blobToBase64 error:', JSON.stringify(error))
|
||||
reject(new Error('blobToBase64 error'));
|
||||
};
|
||||
}
|
||||
const isFileReader = typeof FileReader === 'function'
|
||||
if(networkReg.test(path) && isFileReader ) {
|
||||
window.URL = window.URL || window.webkitURL;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("get", path, true);
|
||||
xhr.timeout = 2000;
|
||||
xhr.responseType = "blob";
|
||||
xhr.onload = function() {
|
||||
if(this.status == 200) {
|
||||
_fileReader(this.response)
|
||||
} else {
|
||||
_canvas()
|
||||
}
|
||||
}
|
||||
xhr.onreadystatechange = function() {
|
||||
if(this.status === 0) {
|
||||
console.error('图片跨域了,得后端处理咯')
|
||||
}
|
||||
}
|
||||
xhr.send();
|
||||
} else if(/^blob/.test(path) && isFileReader){
|
||||
_fileReader(path)
|
||||
} else {
|
||||
_canvas()
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
if(uni.canIUse('getFileSystemManager')) {
|
||||
uni.getFileSystemManager().readFile({
|
||||
filePath: path,
|
||||
encoding: 'base64',
|
||||
success: (res) => {
|
||||
resolve('data:image/png;base64,' + res.data)
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('urlToBase64 error:', JSON.stringify(error))
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), (entry) => {
|
||||
entry.file((file) => {
|
||||
const fileReader = new plus.io.FileReader()
|
||||
fileReader.onload = (data) => { resolve(data.target.result)}
|
||||
fileReader.onerror = (error) => {
|
||||
console.error('pathToBase64 error:', JSON.stringify(error))
|
||||
reject(error)
|
||||
}
|
||||
fileReader.readAsDataURL(file)
|
||||
}, (error) => {
|
||||
console.error('pathToBase64 error:', JSON.stringify(error))
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
console.error('pathToBase64 error:', JSON.stringify(error))
|
||||
reject(error)
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const getLocalFilePath = (path)=> {
|
||||
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf('_downloads') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('file://') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/storage/emulated/0/') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/') === 0) {
|
||||
const localFilePath = plus.io.convertAbsoluteFileSystem(path)
|
||||
if (localFilePath !== path) {
|
||||
return localFilePath
|
||||
} else {
|
||||
path = path.substr(1)
|
||||
}
|
||||
}
|
||||
return '_www/' + path
|
||||
}
|
||||
// #endif
|
||||
|
||||
export function getImageInfo(img, isH5PathToBase64 = false) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// const base64Reg = /^data:image\/(\w+);base64/
|
||||
const localReg = /^\.|^\/(?=[^\/])/;
|
||||
// #ifdef H5
|
||||
if(networkReg.test(img) && isH5PathToBase64) {
|
||||
img = await pathToBase64(img)
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY
|
||||
if(isBase64(img)) {
|
||||
|
||||
if(isDev || !cache[img]) {
|
||||
const imgName = img
|
||||
img = await base64ToPath(img)
|
||||
cache[imgName] = img
|
||||
} else {
|
||||
img = cache[img]
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
if(cache[img] && cache[img].errMsg) {
|
||||
resolve(cache[img])
|
||||
} else {
|
||||
uni.getImageInfo({
|
||||
src: img,
|
||||
success: (image) => {
|
||||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
|
||||
image.path = localReg.test(img) ? `/${image.path}` : image.path;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
image.path = image.path.replace(/^\./, window.location.origin)
|
||||
// #endif
|
||||
image.naturalSrc = img
|
||||
if(isDev) {
|
||||
resolve(image)
|
||||
} else {
|
||||
cache[img] = image
|
||||
resolve(cache[img])
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
resolve({path: img})
|
||||
console.error(`getImageInfo:fail ${img} failed ${JSON.stringify(err)}`);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,756 @@
|
||||
# Painter 画板 测试版
|
||||
> uniapp 海报画板,更优雅的海报生成方案
|
||||
> [查看更多 站点1](https://limeui.qcoon.cn/#/painter)
|
||||
> [查看更多 站点2](http://liangei.gitee.io/limeui/#/painter)
|
||||
> Q群:806744170
|
||||
|
||||
## 平台兼容
|
||||
| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
|
||||
| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- |
|
||||
| √ | √ | √ | 未测 | √ | √ | √ |
|
||||
|
||||
|
||||
## 代码演示
|
||||
### 基本用法
|
||||
- 插件提供JSON及HTML的方式绘制海报
|
||||
- 插件参考了 css 块状流布局模拟css schema方式,放弃了之前使用的绝对定位布局。
|
||||
|
||||
#### 方式一 HTML
|
||||
- 插件提供了`l-painter-view`、`l-painter-text`、`l-painter-image`、`l-painter-qrcode`四种类型组件
|
||||
- 通过 `css` 属性绘制样式,与style使用方式保持一致。 因为style是保留字段,所以命名为`css`,如果有大佬知道如何破解请告之。
|
||||
|
||||
|
||||
```html
|
||||
<l-painter >
|
||||
<l-painter-view css="background: #07c160; height: 120rpx; width: 120rpx; display: inline-block"></l-painter-view>
|
||||
<l-painter-view css="background: #1989fa; height: 120rpx; width: 120rpx; border-top-right-radius: 60rpx; border-bottom-left-radius: 60rpx; display: inline-block; margin: 0 30rpx;"></l-painter-view>
|
||||
<l-painter-view css="background: #ff9d00; height: 120rpx; width: 120rpx; border-radius: 50%; display: inline-block"></l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
#### 方式二 JSON schema
|
||||
- 在json里四种类型组件的`type`为`view`、`text`、`image`、`qrcode`
|
||||
- 通过 `board` 设置海报所需的 JSON schema 数据进行绘制
|
||||
- 所有类型的schema都具有`css`字段,css的样式属性key值使用驼峰命名如:`lineHeight`
|
||||
|
||||
|
||||
```html
|
||||
<l-painter :board="poster" />
|
||||
```
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
poster: {
|
||||
css: {
|
||||
// json 方式务必填写画板宽度
|
||||
width: '750rpx'
|
||||
},
|
||||
views: [
|
||||
{
|
||||
css: {
|
||||
background: "#07c160",
|
||||
height: "120rpx",
|
||||
width: "120rpx",
|
||||
display: "inline-block"
|
||||
},
|
||||
type: "view"
|
||||
},
|
||||
{
|
||||
css: {
|
||||
background: "#1989fa",
|
||||
height: "120rpx",
|
||||
width: "120rpx",
|
||||
borderTopRightRadius: "60rpx",
|
||||
borderBottomLeftRadius: "60rpx",
|
||||
display: "inline-block",
|
||||
margin: "0 30rpx"
|
||||
},
|
||||
views: [],
|
||||
type: "view"
|
||||
},
|
||||
{
|
||||
css: {
|
||||
background: "#ff9d00",
|
||||
height: "120rpx",
|
||||
width: "120rpx",
|
||||
borderRadius: "50%",
|
||||
display: "inline-block"
|
||||
},
|
||||
views: [],
|
||||
type: "view"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### View 容器
|
||||
- 类似于 `div` 可以嵌套承载更多的 view、text、image,qrcode共同构建一颗完整的节点树
|
||||
- 在JSON schema里具有 `views` 的数组字段,用于嵌套承载节点。
|
||||
|
||||
|
||||
#### 方式一 HTML
|
||||
|
||||
|
||||
```html
|
||||
<l-painter >
|
||||
<l-painter-view css="background: #f0f0f0; padding-top: 100rpx;">
|
||||
<l-painter-view css="background: #d9d9d9; width: 33.33%; height: 100rpx; display: inline-block"></l-painter-view>
|
||||
<l-painter-view css="background: #bfbfbf; width: 66.66%; height: 100rpx; display: inline-block"></l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
#### 方式二 JSON schema
|
||||
|
||||
|
||||
```js
|
||||
{
|
||||
css: {
|
||||
width: '750rpx'
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#f0f0f0',
|
||||
paddingTop: '100rpx'
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#d9d9d9',
|
||||
width: '33.33%',
|
||||
height: '100rpx',
|
||||
display: 'inline-block'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#bfbfbf',
|
||||
width: '66.66%',
|
||||
height: '100rpx',
|
||||
display: 'inline-block'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Text 文本
|
||||
- 通过 `text` 属性填写文本内容。
|
||||
- 支持`\n`换行符
|
||||
- 支持省略号,使用css的`line-clamp`设置行数,当文字内容超过会显示省略号。
|
||||
- 支持`text-decoration`
|
||||
|
||||
#### 方式一 HTML
|
||||
```html
|
||||
<l-painter >
|
||||
<l-painter-view css="background: #e0e2db; padding: 30rpx; color: #222a29">
|
||||
<l-painter-text
|
||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"/>
|
||||
<l-painter-text
|
||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
||||
css="text-align:center; padding-top: 20rpx; text-decoration: line-through "
|
||||
/>
|
||||
<l-painter-text
|
||||
text="登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼"
|
||||
css="text-align:right; padding-top: 20rpx"
|
||||
/>
|
||||
<l-painter-text
|
||||
text="水调歌头\n明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。"
|
||||
css="line-clamp: 3; padding-top: 20rpx"
|
||||
/>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
#### 方式二 JSON schema
|
||||
```js
|
||||
// 基础用法
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
css: {
|
||||
// 设置居中对齐
|
||||
textAlign: 'center',
|
||||
// 设置中划线
|
||||
textDecoration: 'line-through'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
css: {
|
||||
// 设置右对齐
|
||||
textAlign: 'right',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '登鹳雀楼\n白日依山尽,黄河入海流\n欲穷千里目,更上一层楼',
|
||||
css: {
|
||||
// 设置行数,超出显示省略号
|
||||
lineClamp: 3,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Image 图片
|
||||
- 通过 `src` 属性填写图片路径。
|
||||
- 图片路径支持:网络图片,本地static里的图片路径,缓存路径
|
||||
- 通过 `css` 的 `object-fit`属性可以设置图片的填充方式,可选值见下方CSS表格。
|
||||
- 通过 `css` 的 `object-position`配合 `object-fit` 可以设置图片的对齐方式,类似于`background-position`,详情见下方CSS表格。
|
||||
- 使用网络图片时:小程序需要去公众平台配置 [downloadFile](https://mp.weixin.qq.com/) 域名
|
||||
- 使用网络图片时:**H5需要决跨域问题**
|
||||
|
||||
#### 方式一 HTML
|
||||
```html
|
||||
<l-painter >
|
||||
<!-- 基础用法 -->
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="width: 200rpx; height: 200rpx"
|
||||
/>
|
||||
<!-- 填充方式 -->
|
||||
<!-- css object-fit 设置 填充方式 见下方表格-->
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="width: 200rpx; height: 200rpx; object-fit: contain; background: #eee"
|
||||
/>
|
||||
<!-- css object-position 设置 图片的对齐方式-->
|
||||
<l-painter-image
|
||||
src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg"
|
||||
css="width: 200rpx; height: 200rpx; object-fit: contain; object-position: 50% 50%; background: #eee"
|
||||
/>
|
||||
|
||||
</l-painter>
|
||||
```
|
||||
#### 方式二 JSON schema
|
||||
```js
|
||||
// 基础用法
|
||||
{
|
||||
type: 'image',
|
||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx'
|
||||
}
|
||||
},
|
||||
// 填充方式
|
||||
// css objectFit 设置 填充方式 见下方表格
|
||||
{
|
||||
type: 'image',
|
||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx',
|
||||
objectFit: 'contain'
|
||||
}
|
||||
},
|
||||
// css objectPosition 设置 图片的对齐方式
|
||||
{
|
||||
type: 'image',
|
||||
src: 'https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx',
|
||||
objectFit: 'contain',
|
||||
objectPosition: '50% 50%'
|
||||
}
|
||||
}
|
||||
```
|
||||
### Qrcode 二维码
|
||||
- 通过`text`属性填写需要生成二维码的文本。
|
||||
- 通过 `css` 里的 `color` 可设置生成码点的颜色。
|
||||
- 通过 `css` 里的 `background`可设置背景色。
|
||||
- 通过 `css `里的 `width`、`height`设置尺寸。
|
||||
|
||||
|
||||
#### 方式一 HTML
|
||||
```html
|
||||
<l-painter>
|
||||
<l-painter-qrcode
|
||||
text="limeui.qcoon.cn"
|
||||
css="width: 200rpx; height: 200rpx"
|
||||
/>
|
||||
</l-painter>
|
||||
```
|
||||
#### 方式二 JSON schema
|
||||
```js
|
||||
{
|
||||
type: 'qrcode',
|
||||
text: 'limeui.qcoon.cn',
|
||||
css: {
|
||||
width: '200rpx',
|
||||
height: '200rpx',
|
||||
}
|
||||
}
|
||||
```
|
||||
### 生成图片
|
||||
- 1、通过设置`isCanvasToTempFilePath`自动生成图片并在 `@success` 事件里接收海报临时路径
|
||||
- 2、通过调用内部方法生成图片:
|
||||
|
||||
|
||||
```html
|
||||
<l-painter ref="painter">...code</l-painter>
|
||||
```
|
||||
```js
|
||||
// 主动调用方式只能在绘制完成之后
|
||||
// @done 事件表示绘制完成
|
||||
this.$refs.painter.canvasToTempFilePath({
|
||||
// 在nvue里是jpeg
|
||||
fileType: 'jpg',
|
||||
quality: 1,
|
||||
success: (res) => {
|
||||
console.log(res.tempFilePath)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### 海报示例
|
||||
- 提供一份示例,只把插件当成生成图片的工具,弹窗之类的功能另外寻找。
|
||||
- 通过设置`isCanvasToTempFilePath`主动生成图片,再由 `@success` 事件接收海报临时路径
|
||||
- 使用`custom-style`样式把画板移到屏幕之外,因为可能canvas的层级比较高,无法覆盖。
|
||||
- **注意**:海报画板不能隐藏 否则无法生成图片。
|
||||
|
||||
#### 方式一 HTML
|
||||
```html
|
||||
<image :src="path" mode="widthFix"></image>
|
||||
<l-painter isCanvasToTempFilePath @success="path = $event"
|
||||
custom-style="position: fixed; left: 200%"
|
||||
css="width: 750rpx; padding-bottom: 40rpx; background: linear-gradient(,#ff971b 0%, #ff5000 100%)">
|
||||
<l-painter-image src="https://cdn.jsdelivr.net/gh/liangei/image@latest/avatar-1.jpeg" css="margin-left: 40rpx; margin-top: 40rpx; width: 84rpx; height: 84rpx; border-radius: 50%;"/>
|
||||
<l-painter-view css="margin-top: 40rpx; padding-left: 20rpx; display: inline-block">
|
||||
<l-painter-text text="隔壁老王" css="display: block; padding-bottom: 10rpx; color: #fff; font-size: 32rpx; fontWeight: bold"/>
|
||||
<l-painter-text text="为您挑选了一个好物" css="color: rgba(255,255,255,.7); font-size: 24rpx"/>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-left: 40rpx; margin-top: 30rpx; padding: 32rpx; box-sizing: border-box; background: #fff; border-radius: 16rpx; width: 670rpx; box-shadow: 0 20rpx 58rpx rgba(0,0,0,.15)">
|
||||
<l-painter-image src="https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg" css="object-fit: cover; object-position: 50% 50%; width: 606rpx; height: 606rpx; border-radius: 12rpx;"/>
|
||||
<l-painter-view css="margin-top: 32rpx; color: #FF0000; font-weight: bold; font-size: 28rpx; line-height: 1em;">
|
||||
<l-painter-text text="¥" css="vertical-align: bottom"/>
|
||||
<l-painter-text text="39" css="vertical-align: bottom; font-size: 58rpx"/>
|
||||
<l-painter-text text=".39" css="vertical-align: bottom"/>
|
||||
<l-painter-text text="¥59.99" css="vertical-align: bottom; padding-left: 10rpx; font-weight: normal; text-decoration: line-through; color: #999999"/>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top: 32rpx; font-size: 26rpx; color: #8c5400">
|
||||
<l-painter-text text="自营" css="color: #212121; background: #ffb400;"/>
|
||||
<l-painter-text text="30天最低价" css="margin-left: 16rpx; background: #fff4d9; text-decoration: line-through;"/>
|
||||
<l-painter-text text="满减优惠" css="margin-left: 16rpx; background: #fff4d9"/>
|
||||
<l-painter-text text="超高好评" css="margin-left: 16rpx; background: #fff4d9"/>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top: 30rpx">
|
||||
<l-painter-text css="line-clamp: 2; color: #333333; line-height: 1.8em; font-size: 36rpx; width: 478rpx; padding-right:32rpx; box-sizing: border-box" text="360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝" :replace="{word: ['儿童', '9X'], color: 'red'}"></l-painter-text>
|
||||
<l-painter-qrcode css="width: 128rpx; height: 128rpx;" text="limeui.qcoon.cn"></l-painter-qrcode>
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
```
|
||||
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
path: ''
|
||||
}
|
||||
}
|
||||
```
|
||||
#### 方式二 JSON schema
|
||||
```html
|
||||
<image :src="path" mode="widthFix"></image>
|
||||
<l-painter
|
||||
:board="poster"
|
||||
isCanvasToTempFilePath
|
||||
@success="path = $event"
|
||||
custom-style="position: fixed; left: 200%"
|
||||
/>
|
||||
```
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
path: '',
|
||||
poster: {
|
||||
css: {
|
||||
width: "750rpx",
|
||||
paddingBottom: "40rpx",
|
||||
background: "linear-gradient(,#000 0%, #ff5000 100%)"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
src: "https://cdn.jsdelivr.net/gh/liangei/image@latest/avatar-1.jpeg",
|
||||
type: "image",
|
||||
css: {
|
||||
background: "#fff",
|
||||
objectFit: "cover",
|
||||
marginLeft: "40rpx",
|
||||
marginTop: "40rpx",
|
||||
width: "84rpx",
|
||||
border: "2rpx solid #fff",
|
||||
boxSizing: "border-box",
|
||||
height: "84rpx",
|
||||
borderRadius: "50%"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "view",
|
||||
css: {
|
||||
marginTop: "40rpx",
|
||||
paddingLeft: "20rpx",
|
||||
display: "inline-block"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
text: "隔壁老王",
|
||||
type: "text",
|
||||
css: {
|
||||
display: "block",
|
||||
paddingBottom: "10rpx",
|
||||
color: "#fff",
|
||||
fontSize: "32rpx",
|
||||
fontWeight: "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "为您挑选了一个好物",
|
||||
type: "text",
|
||||
css: {
|
||||
color: "rgba(255,255,255,.7)",
|
||||
fontSize: "24rpx"
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
css: {
|
||||
marginLeft: "40rpx",
|
||||
marginTop: "30rpx",
|
||||
padding: "32rpx",
|
||||
boxSizing: "border-box",
|
||||
background: "#fff",
|
||||
borderRadius: "16rpx",
|
||||
width: "670rpx",
|
||||
boxShadow: "0 20rpx 58rpx rgba(0,0,0,.15)"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
src: "https://m.360buyimg.com/babel/jfs/t1/196317/32/13733/288158/60f4ea39E6fb378ed/d69205b1a8ed3c97.jpg",
|
||||
type: "image",
|
||||
css: {
|
||||
objectFit: "cover",
|
||||
objectPosition: "50% 50%",
|
||||
width: "606rpx",
|
||||
height: "606rpx"
|
||||
},
|
||||
}, {
|
||||
css: {
|
||||
marginTop: "32rpx",
|
||||
color: "#FF0000",
|
||||
fontWeight: "bold",
|
||||
fontSize: "28rpx",
|
||||
lineHeight: "1em"
|
||||
},
|
||||
views: [{
|
||||
text: "¥",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom"
|
||||
},
|
||||
}, {
|
||||
text: "39",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom",
|
||||
fontSize: "58rpx"
|
||||
},
|
||||
}, {
|
||||
text: ".39",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom"
|
||||
},
|
||||
}, {
|
||||
text: "¥59.99",
|
||||
type: "text",
|
||||
css: {
|
||||
verticalAlign: "bottom",
|
||||
paddingLeft: "10rpx",
|
||||
fontWeight: "normal",
|
||||
textDecoration: "line-through",
|
||||
color: "#999999"
|
||||
}
|
||||
}],
|
||||
|
||||
type: "view"
|
||||
}, {
|
||||
css: {
|
||||
marginTop: "32rpx",
|
||||
fontSize: "26rpx",
|
||||
color: "#8c5400"
|
||||
},
|
||||
views: [{
|
||||
text: "自营",
|
||||
type: "text",
|
||||
css: {
|
||||
color: "#212121",
|
||||
background: "#ffb400"
|
||||
},
|
||||
}, {
|
||||
text: "30天最低价",
|
||||
type: "text",
|
||||
css: {
|
||||
marginLeft: "16rpx",
|
||||
background: "#fff4d9",
|
||||
textDecoration: "line-through"
|
||||
},
|
||||
}, {
|
||||
text: "满减优惠",
|
||||
type: "text",
|
||||
css: {
|
||||
marginLeft: "16rpx",
|
||||
background: "#fff4d9"
|
||||
},
|
||||
}, {
|
||||
text: "超高好评",
|
||||
type: "text",
|
||||
css: {
|
||||
marginLeft: "16rpx",
|
||||
background: "#fff4d9"
|
||||
},
|
||||
|
||||
}],
|
||||
|
||||
type: "view"
|
||||
}, {
|
||||
css: {
|
||||
marginTop: "30rpx"
|
||||
},
|
||||
views: [
|
||||
{
|
||||
text: "360儿童电话手表9X 智能语音问答定位支付手表 4G全网通20米游泳级防水视频通话拍照手表男女孩星空蓝",
|
||||
type: "text",
|
||||
css: {
|
||||
paddingRight: "32rpx",
|
||||
boxSizing: "border-box",
|
||||
lineClamp: 2,
|
||||
color: "#333333",
|
||||
lineHeight: "1.8em",
|
||||
fontSize: "36rpx",
|
||||
width: "478rpx"
|
||||
},
|
||||
}, {
|
||||
text: "limeui.qcoon.cn",
|
||||
type: "qrcode",
|
||||
css: {
|
||||
width: "128rpx",
|
||||
height: "128rpx",
|
||||
},
|
||||
|
||||
}],
|
||||
type: "view"
|
||||
}],
|
||||
type: "view"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 原生小程序
|
||||
- 插件里的`painter.js`支持在原生小程序中使用
|
||||
- new Painter之后在`source`里传入JSON schema
|
||||
- 再调用`render`绘制海报
|
||||
- 如需生成图片,请查看微信小程序cavnas的[canvasToTempFilePath](https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html)
|
||||
|
||||
```html
|
||||
<canvas type="2d" id="painter" style="width: 100%"></canvas>
|
||||
```
|
||||
```js
|
||||
import {Painter} from './painter'
|
||||
page({
|
||||
data: {
|
||||
poster: {
|
||||
css: {
|
||||
width: '750rpx'
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#d2d4c8',
|
||||
paddingTop: '100rpx'
|
||||
},
|
||||
views: [
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#5f7470',
|
||||
width: '33.33%',
|
||||
height: '100rpx',
|
||||
display: 'inline-block'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#889696',
|
||||
width: '33.33%',
|
||||
height: '100rpx',
|
||||
display: 'inline-block'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'view',
|
||||
css: {
|
||||
background: '#b8bdb5',
|
||||
width: '33.33%',
|
||||
height: '100rpx',
|
||||
display: 'inline-block'
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
async onLoad() {
|
||||
const res = await this.getCentext()
|
||||
const painter = new Painter(res)
|
||||
// 返回计算布局后的整个内容尺寸
|
||||
const {width, height} = await painter.source(this.data.poster)
|
||||
// 得到计算后的尺寸后 可给canvas尺寸赋值,达到动态响应效果
|
||||
// 渲染
|
||||
await painter.render()
|
||||
},
|
||||
// 获取canvas 2d
|
||||
// 非2d也可以使用这里只是举个例子
|
||||
getCentext() {
|
||||
return new Promise(resolve => {
|
||||
wx.createSelectorQuery()
|
||||
.select(`#painter`)
|
||||
.node()
|
||||
.exec(res => {
|
||||
let { node: canvas } = res[0];
|
||||
resolve({
|
||||
canvas,
|
||||
context: canvas.getContext('2d'),
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
pixelRatio: 2
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
### Nvue
|
||||
- 插件是通过 `web-view` 支持 `app-nvue`
|
||||
- 默认是远端的文件,如果需要本地化,请按下方步骤:
|
||||
- 1、去码云把[hybrid](https://gitee.com/liangei/lime-painter/tree/master/examples/uni/hybrid)目录的文件,放到自己项目的根目录。
|
||||
- 2、给插件文件`l-painter.vue`里`web-view`的`src`加上根目录的`hybrid路径`并注释`this.webViewInit()`
|
||||
|
||||
|
||||
```html
|
||||
// 只加上路径 其它参数不要改
|
||||
<web-view src="/hybrid/html/lime-ui/lime-painter/index.html" />
|
||||
```
|
||||
```js
|
||||
// 153行 注释该方法
|
||||
// this.webViewInit()
|
||||
```
|
||||
|
||||
### 旧版更新(1.6.x)
|
||||
- 由于1.8.x版放弃了以定位的方式,所以1.6.x版更新之后要每个样式都加上`position: absolute`
|
||||
- 旧版的 `image` mode 模式被放弃,使用`object-fit`
|
||||
- 旧版的 `isRenderImage` 改成 `is-canvas-to-temp-filePath`
|
||||
- 旧版的 `maxLines` 改成 `line-clamp`
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ------------- | ------------ | ---------------- | ------------ |
|
||||
| board | JSON schema方式的海报元素对象集 | <em>object</em> | - |
|
||||
| css | 海报最外层的样式,可以理解为`body` | <em>object</em> | 参数请向下看 |
|
||||
| custom-style | canvas自定义样式 | <em>string</em> | |
|
||||
| is-canvas-to-temp-filePath | 是否生成图片,在`@success`事件接收图片地址 | <em>boolean</em> | `false` |
|
||||
| after-delay | 生成图片错乱,可延时生成图片 | <em>number</em> | `100` |
|
||||
| type | canvas 类型,对微信头条支付宝小程序可有效,可选值:`2d`、`` | <em>string</em> | `2d` |
|
||||
| file-type | 生成图片的后缀类型, 可选值:`png`、`jpg`,在nvue里是`jpeg` | <em>string</em> | `png` |
|
||||
| path-type | 生成图片路径类型,可选值`url`、`base64` | <em>string</em> | `-` |
|
||||
| pixel-ratio | 生成图片的像素密度,默认为对应手机的像素密度 | <em>number</em> | `-` |
|
||||
|
||||
<!-- | width | 画板的宽度,一般只用于通过内部方法时加上 | <em>number</em> | `` |
|
||||
| height | 画板的高度 ,同上 | <em>number</em> | `` | -->
|
||||
|
||||
### css
|
||||
| 属性名 | 支持的值或类型 | 默认值 |
|
||||
| ------------- | ------------ | ---------------- |
|
||||
| (min\max)width | 支持`%`、`rpx`、`px` | - |
|
||||
| height | 同上 | - |
|
||||
| color | `string` | - |
|
||||
| position | 定位,可选值:`absolute`、`fixed` | - |
|
||||
| ↳ left、top、right、bottom | 配合`position`才生效,支持`%`、`rpx`、`px` | - |
|
||||
| margin | 可简写或各方向分别写,如:`margin-top`,支持`auto`、`rpx`、`px` | - |
|
||||
| padding | 可简写或各方向分别写,支持`rpx`、`px` | - |
|
||||
| border | 可简写或各个值分开写:`border-width`、`border-style` 、`border-color`,简写请按顺序写| - |
|
||||
| line-clamp | `number`,超过行数显示省略号 | - |
|
||||
| background | 支持渐变,但必须写百分比!如:`linear-gradient(,#ff971b 0%, #ff5000 100%)`、`radial-gradient(#0ff 15%, #f0f 60%)`,目前radial-gradient 渐变的圆心为元素中点,半径为最长边,不支持设置 | - |
|
||||
| vertical-align | 文字垂直对齐,可选值:`bottom`、`top`、`middle` | `middle` |
|
||||
| line-height | 文字行高,支持`rpx`、`px`、`em`| `1.4em` |
|
||||
| font-weight | 文字粗细,可选值:`normal`、`bold`| `normal` |
|
||||
| font-size | 文字大小,`string`,支持`rpx`、`px` | `14px` |
|
||||
| text-decoration | 文本修饰,可选值:`underline` 、`line-through`、`overline`| - |
|
||||
| text-align | 文本水平对齐,可选值:`right` 、`center`| - |
|
||||
| display | 框类型,可选值:`block`、`inline-block`、`none`,当为`none`时是不渲染该段 | - |
|
||||
| border-radius | 圆角边框,支持`%`、`rpx`、`px` | - |
|
||||
| box-sizing | 可选值:`border-box` | - |
|
||||
| box-shadow | 投影 | - |
|
||||
| background-image | view元素设置背景纹理,注意这里的背景纹理一般是用于纹理平铺,无法代替`image`元素。如:水印 | - |
|
||||
| background-repeat | 设置是否及如何重复背景纹理,可选值:`repeat`、`repeat-x`、`repeat-y`、`no-repeat` | `repeat` |
|
||||
| [object-fit](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-fit/) | 图片元素适应容器方式,类似于`mode`,可选值:`cover`、 `contain`、 `fill`、 `none` | - |
|
||||
| [object-position](https://developer.mozilla.org/zh-CN/docs/Web/CSS/object-position) | 图片的对齐方式,配合`object-fit`使用 | - |
|
||||
|
||||
### 图片填充模式 object-fit
|
||||
| 名称 | 含义 |
|
||||
| ------- | ------------ |
|
||||
| contain | 保持宽高缩放图片,使图片的长边能完全显示出来 |
|
||||
| cover | 保持宽高缩放图片,使图片的短边能完全显示出来,裁剪长边 |
|
||||
| fill | 拉伸图片,使图片填满元素 |
|
||||
| none | 保持图片原有尺寸 |
|
||||
|
||||
### 事件 Events
|
||||
|
||||
| 事件名 | 说明 | 回调 |
|
||||
| ------- | ------------ | -------------- |
|
||||
| success | 生成图片成功,若使用了`is-canvas-to-temp-filePath` 可以接收图片地址 | path |
|
||||
| fail | 生成图片失败 | {error: error} |
|
||||
| done | 绘制成功 | |
|
||||
| progress | 绘制进度 | number |
|
||||
|
||||
## 常见问题
|
||||
- 1、H5端使用网络图片需要解决跨域问题。
|
||||
- 2、小程序使用网络图片需要去公众平台增加下载白名单!二级域名也需要配!
|
||||
- 3、H5端生成图片是base64,有时显示只有一半可以使用原生标签`<IMG/>`
|
||||
- 4、发生保存图片倾斜变形或提示native buffer exceed size limit时,使用pixel-ratio="2"参数,降分辨率。
|
||||
- 5、h5保存图片不需要调接口,提示用户长按图片保存。
|
||||
- 6、IOS APP 请勿使用HBX2.9.3.20201014的版本!这个版本无法生成图片。
|
||||
- 7、组件不能隐藏,包含`v-if`,`v-show`、`display:none`、`opacity:0`
|
||||
- 8、微信小程序 canvas 2d不支持真机调试,请使用真机预览方式。
|
||||
- 9、华为手机APP上无法生成图片,请使用HBX2.9.11++
|
||||
- 10、苹果微信7.0.20存在闪退和图片无法onload为微信bug,请到码云上升级本插件
|
||||
|
||||
## 打赏
|
||||
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
|
||||
|
||||

|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<view class="loading-footer row-center" :style="'color: ' + color">
|
||||
<view v-if="status === 'loading' " class="loading row">
|
||||
<loading :color="color" class="mr20"></loading>
|
||||
<text :style="'color: ' + color">{{loadingText}}</text>
|
||||
</view>
|
||||
<view v-if="status === 'finished'" class="finished">{{ finishedText }}</view>
|
||||
<view v-if="status === 'error'" @click="onRefresh">{{ errorText }}</view>
|
||||
<view v-if="status === 'empty'" class="empty">
|
||||
<text v-if="!slotEmpty">暂无数据</text>
|
||||
<slot name="empty" v-else></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
default: 'loading'
|
||||
},
|
||||
errorText: {
|
||||
type: String,
|
||||
default: '加载失败,点击重新加载'
|
||||
},
|
||||
loadingText: {
|
||||
type: String,
|
||||
default: '加载中...'
|
||||
},
|
||||
finishedText: {
|
||||
type: String,
|
||||
default: '我可是有底线的~'
|
||||
},
|
||||
slotEmpty: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "#666"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onRefresh() {
|
||||
this.$emit('refresh');
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.loading-footer {
|
||||
padding: 30rpx 0;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<view :class="'loading ' + ( type == 'flex' ? 'flex' : '' )" :style="{backgroundColor, }">
|
||||
<loading :color="color" :size="size"></loading>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 40
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loading.flex {
|
||||
position: static;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<view :class="'loading ' + ( vertical ? 'loading--vertical' : '' )">
|
||||
<view :class="'loading__spinner loading__spinner--' + type" :style="{color, width: size + 'rpx', height: size + 'rpx'}">
|
||||
<view v-for="(item, index) in array12" :key="index" v-if="type === 'spinner'" class="loading__dot"></view>
|
||||
</view>
|
||||
<view class="loading__text" :style="{'font-size': textSize + 'rpx', color}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
color: String,
|
||||
vertical: Boolean,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'spinner'
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 40
|
||||
},
|
||||
textSize: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
array12: Array.from({
|
||||
length: 12
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:host {
|
||||
font-size: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #c8c9cc;
|
||||
}
|
||||
|
||||
.loading__spinner {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 45rpx;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
height: 45rpx;
|
||||
animation: rotate .8s linear infinite;
|
||||
}
|
||||
|
||||
.loading__spinner--spinner {
|
||||
-webkit-animation-timing-function: steps(12);
|
||||
animation-timing-function: steps(12);
|
||||
}
|
||||
|
||||
.loading__spinner--circular {
|
||||
border: 2rpx solid transparent;
|
||||
border-top-color: initial;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.loading__text {
|
||||
margin-left: 16rpx;
|
||||
color: #969799;
|
||||
font-size: 28rpx;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
|
||||
.loading__text:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loading--vertical {
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.loading--vertical .loading__text {
|
||||
margin: 16rpx 0 0;
|
||||
}
|
||||
|
||||
.loading__dot {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading__dot:before {
|
||||
display: block;
|
||||
width: 4rpx;
|
||||
height: 25%;
|
||||
margin: 0 auto;
|
||||
background-color: currentColor;
|
||||
border-radius: 40%;
|
||||
content: " ";
|
||||
}
|
||||
|
||||
.loading__dot:first-of-type {
|
||||
-webkit-transform: rotate(30deg);
|
||||
transform: rotate(30deg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(2) {
|
||||
-webkit-transform: rotate(60deg);
|
||||
transform: rotate(60deg);
|
||||
opacity: .9375;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(3) {
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
opacity: .875;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(4) {
|
||||
-webkit-transform: rotate(120deg);
|
||||
transform: rotate(120deg);
|
||||
opacity: .8125;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(5) {
|
||||
-webkit-transform: rotate(150deg);
|
||||
transform: rotate(150deg);
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(6) {
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
opacity: .6875;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(7) {
|
||||
-webkit-transform: rotate(210deg);
|
||||
transform: rotate(210deg);
|
||||
opacity: .625;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(8) {
|
||||
-webkit-transform: rotate(240deg);
|
||||
transform: rotate(240deg);
|
||||
opacity: .5625;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(9) {
|
||||
-webkit-transform: rotate(270deg);
|
||||
transform: rotate(270deg);
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(10) {
|
||||
-webkit-transform: rotate(300deg);
|
||||
transform: rotate(300deg);
|
||||
opacity: .4375;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(11) {
|
||||
-webkit-transform: rotate(330deg);
|
||||
transform: rotate(330deg);
|
||||
opacity: .375;
|
||||
}
|
||||
|
||||
.loading__dot:nth-of-type(12) {
|
||||
-webkit-transform: rotate(1turn);
|
||||
transform: rotate(1turn);
|
||||
opacity: .3125;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotate {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: rotate(1turn);
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: rotate(1turn);
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,188 @@
|
||||
<template name="protocol-popup">
|
||||
<view @touchmove.stop.prevent="clear" v-show="showPopup">
|
||||
<view class="popup_mask1" @touchmove.stop.prevent="clear"></view>
|
||||
<view class="popup_mask" @touchmove.stop.prevent="clear"></view>
|
||||
|
||||
<view class="popup_content">
|
||||
<view class="title">{{ title }}</view>
|
||||
<view class="content">
|
||||
请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。你可阅读
|
||||
<text class="link" @click="linkClick(1)">《服务协议》</text>和
|
||||
<text class="link" @click="linkClick(2)">《隐私政策》</text>
|
||||
了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。
|
||||
</view>
|
||||
<view class="btn">
|
||||
<view class="flex-1">
|
||||
<button class="plain" @click="back">拒绝,仅浏览</button>
|
||||
</view>
|
||||
<view class="flex-1">
|
||||
<button @click="confirm">同意并继续</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "lyg-popup",
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "用户使用及隐私保护政策提示"
|
||||
},
|
||||
// 协议路径
|
||||
protocolPath: {
|
||||
type: String
|
||||
},
|
||||
// 政策路径
|
||||
policyPath: {
|
||||
type: String
|
||||
},
|
||||
policyStorageKey: {
|
||||
type: String,
|
||||
default: "has_read_privacy"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showPopup: false
|
||||
};
|
||||
|
||||
},
|
||||
created: function() {
|
||||
var that = this;
|
||||
console.log("lyg-popup created");
|
||||
const platform = uni.getSystemInfoSync().osName
|
||||
const agree = uni.getStorageSync(this.policyStorageKey)
|
||||
if (platform === 'android' || agree) {
|
||||
this.showPopup = false;
|
||||
uni.showTabBar({})
|
||||
} else {
|
||||
this.showPopup = true;
|
||||
uni.hideTabBar({})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 查看隐私协议或服务协议
|
||||
linkClick (num) {
|
||||
switch (num) {
|
||||
case 1:
|
||||
// 跳转服务协议
|
||||
this.$Router.push(this.protocolPath)
|
||||
break
|
||||
case 2:
|
||||
// 跳转隐私政策
|
||||
this.$Router.push(this.policyPath)
|
||||
break
|
||||
}
|
||||
},
|
||||
// 禁止滚动
|
||||
clear() {
|
||||
return;
|
||||
},
|
||||
back() {
|
||||
this.$emit('popupState', false)
|
||||
// #ifdef APP-PLUS
|
||||
if (uni.getSystemInfoSync().platform == 'ios') {
|
||||
const threadClass = plus.ios.importClass("NSThread");
|
||||
const mainThread = plus.ios.invoke(threadClass, "mainThread");
|
||||
// plus.ios.invoke(mainThread, "exit");
|
||||
//上面的不起效果下面的
|
||||
plus.ios.import("UIApplication").sharedApplication().performSelector("exit")
|
||||
} else if (uni.getSystemInfoSync().platform == 'android') {
|
||||
plus.runtime.quit();
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
// 关闭弹框
|
||||
confirm() {
|
||||
this.showPopup = false;
|
||||
this.$emit('popupState', true);
|
||||
uni.showTabBar({})
|
||||
uni.setStorageSync(this.policyStorageKey, true)
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.popup_mask {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.3s;
|
||||
opacity: 0;
|
||||
z-index: 1299;
|
||||
}
|
||||
|
||||
.popup_mask {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.popup_mask1 {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1298;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.popup_content {
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 40upx 20upx 0 20upx;
|
||||
position: fixed;
|
||||
bottom: 30%;
|
||||
border-radius: 8px;
|
||||
left: 50%;
|
||||
margin-left: -40%;
|
||||
right: 0;
|
||||
min-height: 400upx;
|
||||
background: #ffffff;
|
||||
width: 80%;
|
||||
z-index: 1300;
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
line-height: 120rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 32rpx;
|
||||
text-indent: 1em;
|
||||
|
||||
.link {
|
||||
color: #4982db;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin-top: 30rpx;
|
||||
|
||||
>view {
|
||||
margin: 20rpx;
|
||||
flex: 1;
|
||||
}
|
||||
button {
|
||||
color: $-color-white;
|
||||
background-color: $-color-primary;
|
||||
}
|
||||
.plain {
|
||||
color: $-color-primary;
|
||||
background-color: $-color-white;
|
||||
border: 1px solid $-color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-popup
|
||||
v-model="showPop"
|
||||
mode="bottom"
|
||||
border-radius="14"
|
||||
:safe-area-inset-bottom="true"
|
||||
:maskCloseAble="false"
|
||||
>
|
||||
<view class="popup-content">
|
||||
<view class="popup-header">
|
||||
<image class="logo" mode="heightFix" :src="logo"></image>
|
||||
<text class="title">{{ title }}</text>
|
||||
</view>
|
||||
<view class="popup-tips">
|
||||
建议使用您的微信头像和昵称,以便获得更好的体验
|
||||
</view>
|
||||
<view class="popup-form">
|
||||
<form @submit="handleSubmit">
|
||||
<u-form-item required label="头像" :labelWidth="120">
|
||||
<button
|
||||
class="select-avatar"
|
||||
hover-class="none"
|
||||
open-type="chooseAvatar"
|
||||
@chooseavatar="onChooseAvatar"
|
||||
>
|
||||
<image
|
||||
class="avatar"
|
||||
:src="avatar"
|
||||
v-if="avatar"
|
||||
/>
|
||||
<div class="select-btn" v-else>
|
||||
<u-icon name="plus" :size="30" />
|
||||
添加图片
|
||||
</div>
|
||||
</button>
|
||||
</u-form-item>
|
||||
<u-form-item required label="昵称" :labelWidth="120">
|
||||
<input
|
||||
style="height: 60rpx"
|
||||
:value="userInfo.nickname"
|
||||
name="nickname"
|
||||
type="nickname"
|
||||
placeholder="请输入昵称"
|
||||
/>
|
||||
</u-form-item>
|
||||
<button
|
||||
class="submit-btn bg-primary"
|
||||
form-type="submit"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
<view class="close-btn" @tap="onClose">暂不登录</view>
|
||||
</form>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uploadFile } from "@/utils/tools";
|
||||
export default {
|
||||
name: "mplogin-popup",
|
||||
props: {
|
||||
logo: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
avatar: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onChooseAvatar(e) {
|
||||
const avatarUrl = e.detail.avatarUrl;
|
||||
if (!avatarUrl) {
|
||||
return;
|
||||
}
|
||||
uni.showLoading({
|
||||
title: "正在上传中...",
|
||||
mask: true,
|
||||
});
|
||||
uploadFile(avatarUrl)
|
||||
.then((res) => {
|
||||
uni.hideLoading();
|
||||
this.avatar = res.url;
|
||||
})
|
||||
.catch(() => {
|
||||
uni.hideLoading();
|
||||
this.$toast({
|
||||
title: "上传失败",
|
||||
});
|
||||
});
|
||||
},
|
||||
handleSubmit(e) {
|
||||
const { nickname } = e.detail.value
|
||||
const {avatar} = this
|
||||
if(!avatar) return this.$toast({
|
||||
title: '请添加头像'
|
||||
})
|
||||
if(!nickname) return this.$toast({
|
||||
title: '请输入昵称'
|
||||
})
|
||||
this.$emit('update', {
|
||||
avatar,
|
||||
nickname
|
||||
})
|
||||
},
|
||||
onClose() {
|
||||
this.showPop = false
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showPop: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.popup-content {
|
||||
padding: 40rpx 40rpx 80rpx;
|
||||
.popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.title {
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.logo {
|
||||
flex: none;
|
||||
height: 80rpx;
|
||||
margin-right: 20rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
.popup-tips {
|
||||
color: #999;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
.popup-form {
|
||||
margin-top: 55rpx;
|
||||
.select-avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
color: #999;
|
||||
.avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.select-btn {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
border: 1px dotted #ccc;
|
||||
font-size: 22rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
.submit-btn {
|
||||
height: 82rpx;
|
||||
line-height: 82rpx;
|
||||
border-radius: 41px;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
margin: 80rpx 35rpx 60rpx;
|
||||
}
|
||||
.close-btn {
|
||||
color: #999;
|
||||
font-size: 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<!-- components/my-coupons/my-coupons.wxml -->
|
||||
<view class="my-coupons">
|
||||
<coupon-list v-if="!showNull" :list="couponList" :btnType="type"></coupon-list>
|
||||
<view v-else class="column-center" style="padding-top: 200rpx">
|
||||
<image class="img-null" src="/static/images/coupon_null.png"></image>
|
||||
<text class="muted">暂无优惠券~</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeshop开源商城系统
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee
|
||||
// | github下载:https://github.com/likeshop-github
|
||||
// | 访问官网:https://www.likeshop.cn
|
||||
// | 访问社区:https://home.likeshop.cn
|
||||
// | 访问手册:http://doc.likeshop.cn
|
||||
// | 微信公众号:likeshop技术社区
|
||||
// | likeshop系列产品在gitee、github等公开渠道开源版本可免费商用,未经许可不能去除前后端官方版权标识
|
||||
// | likeshop系列产品收费版本务必购买商业授权,购买去版权授权后,方可去除前后端官方版权标识
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布
|
||||
// | likeshop团队版权所有并拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeshop.cn.team
|
||||
// +----------------------------------------------------------------------
|
||||
import { getMyCoupon } from '../../api/user';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
couponList: [],
|
||||
showNull: false
|
||||
};
|
||||
},
|
||||
|
||||
props: {
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
beforeMount: function () {
|
||||
this.getMyCouponFun();
|
||||
},
|
||||
methods: {
|
||||
getMyCouponFun() {
|
||||
const {
|
||||
type
|
||||
} = this;
|
||||
getMyCoupon({
|
||||
type
|
||||
}).then(res => {
|
||||
if (res.code == 1) {
|
||||
this.$emit('getnum', {
|
||||
detail: res.data.length
|
||||
});
|
||||
|
||||
if (res.data.length <= 0) {
|
||||
this.showNull = true;
|
||||
return;
|
||||
}
|
||||
this.couponList = res.data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
/* components/my-coupons/my-coupons.wxss */
|
||||
.my-coupons {
|
||||
min-height: calc(100vh - 80rpx);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="my-lottery row-between wrap" v-if="status">
|
||||
<view v-for="(item, index) in lotteryData" :key="index" class="lottery-item column-center" :class="{
|
||||
active: activeIndex == index,
|
||||
'lotty-btn': !item.id && item.name,
|
||||
}" @tap="onLotteryClick(item.id)">
|
||||
<image :src="item.url" style="width: 80rpx;height: 80rpx"></image>
|
||||
<text :class="!item.id ? 'xs mt20' : 'nr mt10'"
|
||||
:style="'color: ' + (!item.id ? '#ED3720' : '#743C3C') + ';font-weight: 600;'">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="activity-null row-center" v-else>
|
||||
活动暂未开始
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
userLottery
|
||||
} from "@/api/user"; // 顺序
|
||||
// 顺序
|
||||
const LOTTERY_ORDER = [0, 1, 2, 5, 8, 7, 6, 3];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 是否在抽奖中
|
||||
isLottery: false,
|
||||
// 表示位移的总次数
|
||||
currentIndex: 0,
|
||||
// 中奖索引
|
||||
luckyOrder: -1,
|
||||
// 转盘的旋转速度,实际是转盘旋转间隔时间setTimeout的值
|
||||
speed: 200,
|
||||
// 当前活跃的坐标
|
||||
activeIndex: -1
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
// 转盘数据
|
||||
lotteryData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 最少转动多少圈
|
||||
circleTimes: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
status: {
|
||||
type: Number | String,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
|
||||
beforeMount() {
|
||||
this.lotteryTimer = null;
|
||||
},
|
||||
|
||||
methods: {
|
||||
onLotteryClick(id) {
|
||||
if (!id) {
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
|
||||
userLotteryFun() {
|
||||
userLottery().then(res => {
|
||||
if (res.code == 1) {
|
||||
let {
|
||||
id
|
||||
} = res.data;
|
||||
let index = this.lotteryData.findIndex(item => {
|
||||
return item.id == id;
|
||||
});
|
||||
|
||||
switch (index) {
|
||||
case 0:
|
||||
index = 0;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
index = 1;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
index = 2;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
index = 7;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
index = 3;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
index = 6;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
index = 5;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
index = 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
index = -1;
|
||||
break;
|
||||
}
|
||||
this.luckyOrder = index
|
||||
this.result = res.data
|
||||
.text; // console.log("id:", id, "index", index, LOTTERY_ORDER[index], "####");
|
||||
// console.log(this.data.lotteryData, "data");
|
||||
|
||||
this.$emit("begin");
|
||||
this.startLotteryFun();
|
||||
} else {
|
||||
this.reset()
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
startLotteryFun() {
|
||||
let {
|
||||
currentIndex
|
||||
} = this;
|
||||
let activeIndex = this.getHighLightItemIndexFun(currentIndex);
|
||||
this.activeIndex = activeIndex
|
||||
const currentOrder = currentIndex % 8;
|
||||
this.currentIndex = ++currentIndex;
|
||||
if (currentIndex > this.circleTimes + 8 && this.luckyOrder == currentOrder) {
|
||||
if (this.lotteryTimer) {
|
||||
clearTimeout(this.lotteryTimer);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.stopCallbackFun(LOTTERY_ORDER[this.luckyOrder]);
|
||||
setTimeout(() => {
|
||||
this.reset();
|
||||
this.activeIndex = -1
|
||||
}, 1000);
|
||||
}, 500);
|
||||
} else {
|
||||
if (currentIndex < this.circleTimes) {
|
||||
this.speed -= 10
|
||||
} else if (currentIndex > this.circleTimes + 8 && this.luckyOrder == currentOrder + 1) {
|
||||
this.speed += 80
|
||||
} else {
|
||||
this.speed += 20
|
||||
}
|
||||
|
||||
if (this.speed < 50) {
|
||||
this.speed = 50
|
||||
}
|
||||
|
||||
this.lotteryTimer = setTimeout(this.startLotteryFun.bind(this), this.speed);
|
||||
}
|
||||
},
|
||||
|
||||
// luckyIndex: 中奖在列表中的index
|
||||
stopCallbackFun(luckyIndex) {
|
||||
this.$emit("finish", {
|
||||
detail: this.result
|
||||
});
|
||||
},
|
||||
|
||||
// 根据 currentIndex 获取当前应该高亮列表中的第几个奖品
|
||||
getHighLightItemIndexFun(currentIndex) {
|
||||
return LOTTERY_ORDER[currentIndex % LOTTERY_ORDER.length];
|
||||
},
|
||||
|
||||
// 根据奖品在数据中的index,获取奖品在转盘中的位置
|
||||
getLuckyItemOrderFun(index) {},
|
||||
|
||||
start(e) {
|
||||
// 如果还没开始转动,开始转动转盘
|
||||
if (!this.isLottery) {
|
||||
this.isLottery = true
|
||||
this.userLotteryFun();
|
||||
}
|
||||
},
|
||||
|
||||
// 停止转动
|
||||
stop(index, callback) {
|
||||
this.luckyOrder = this.getLuckyItemOrderFun(index);
|
||||
console.log("stop, ###", index);
|
||||
this._stopCallback = callback;
|
||||
},
|
||||
|
||||
// 重设转盘
|
||||
reset() {
|
||||
this.isLottery = false;
|
||||
this.currentIndex = 0;
|
||||
this.speed = 100;
|
||||
this.luckyOrder = -1
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.my-lottery {
|
||||
background-image: url(../../static/images/choujiang_light.png);
|
||||
width: 640rpx;
|
||||
height: 640rpx;
|
||||
background-size: 100% 100%;
|
||||
padding: 35rpx 40rpx;
|
||||
}
|
||||
|
||||
.my-lottery .lottery-item {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
background-image: url(../../static/images/choujiang_block.png);
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.my-lottery .lottery-item.lotty-btn {
|
||||
background-image: url(../../static/images/choujiang_button.png);
|
||||
}
|
||||
|
||||
.container .activity-null {
|
||||
color: #FEE5BF;
|
||||
font-size: 40rpx;
|
||||
width: 640rpx;
|
||||
height: 640rpx;
|
||||
}
|
||||
|
||||
.active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<view class="navbar" @tap.stop="showFloat = false">
|
||||
<u-navbar :background="background" :title="title" :title-color="titleColor" :border-bottom="borderBottom"
|
||||
:immersive="immersive" :title-bold="true" :is-back="false">
|
||||
<view class="navbar-left" slot="left">
|
||||
<u-icon :name="backIcon" :size="36" @click="goBack"></u-icon>
|
||||
<view class="line"></view>
|
||||
<view class="navbar-lists" @tap.stop="showFloat = !showFloat">
|
||||
<u-icon :name="require('@/static/images/icon_list.png')" :size="32"></u-icon>
|
||||
<view class="navbar-float" v-show="showFloat">
|
||||
<navigator v-for="(item, index) in navLists" :key="index" :url="item.url" :open-type="item.type"
|
||||
class="float-item" hover-class="none">
|
||||
<u-icon :name="item.icon" :size="44"></u-icon>
|
||||
<text class="ml20">{{ item.name }}</text>
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-navbar>
|
||||
<view class="mask" v-show="showFloat" @touchstart="showFloat = false">
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 导航内容
|
||||
title: String,
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
// 导航的背景颜色
|
||||
background: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
background: '#ffffff'
|
||||
})
|
||||
},
|
||||
// 是否显示底部边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
immersive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isIndex: false,
|
||||
navLists: [{
|
||||
url: '/pages/index/index',
|
||||
name: '首页',
|
||||
icon: require('@/static/images/icon_home.png'),
|
||||
type: 'switchTab'
|
||||
}, {
|
||||
url: '/pages/goods_search/goods_search',
|
||||
name: '搜索',
|
||||
icon: require('@/static/images/icon_search.png'),
|
||||
type: 'navigate'
|
||||
}, {
|
||||
url: '/pages/shop_cart/shop_cart',
|
||||
name: '购物车',
|
||||
icon: require('@/static/images/icon_carts.png'),
|
||||
type: 'switchTab'
|
||||
}, {
|
||||
url: '/pages/user/user',
|
||||
name: '个人中心',
|
||||
icon: require('@/static/images/icon_user.png'),
|
||||
type: 'switchTab'
|
||||
}],
|
||||
showFloat: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
if (!this.isIndex) {
|
||||
uni.navigateBack()
|
||||
return
|
||||
}
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backIcon() {
|
||||
const iconName = this.isIndex ? 'icon_home' : 'icon_back'
|
||||
return require(`@/static/images/${iconName}.png`)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
setTimeout(() => {
|
||||
let pages = getCurrentPages();
|
||||
if (pages.length == 1) {
|
||||
this.isIndex = true
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.navbar {
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
padding: 12rpx 25rpx;
|
||||
border-radius: 30px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border: 1rpx solid rgba(0, 0, 0, 0.1);
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 36rpx;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
margin: 0 25rpx;
|
||||
}
|
||||
|
||||
.navbar-lists {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
.navbar-float {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
width: 258rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 14rpx;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.06);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 14rpx solid transparent;
|
||||
border-bottom-color: #fff;
|
||||
transform: translate(-50%, -100%);
|
||||
}
|
||||
|
||||
.float-item {
|
||||
padding: 20rpx 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<u-modal v-model="show" :show-cancel-button ="true" :content="getTipsText" @confirm="onConfirm" confirm-color="#ff2c3c"></u-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
cancelOrder,
|
||||
delOrder,
|
||||
confirmOrder
|
||||
} from '@/api/order';
|
||||
export default {
|
||||
props: {
|
||||
type: Number,
|
||||
orderId: [Number, String]
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.show = true
|
||||
},
|
||||
close() {
|
||||
this.show = false
|
||||
},
|
||||
async onConfirm() {
|
||||
const {
|
||||
type,
|
||||
orderId
|
||||
} = this;
|
||||
let res = null;
|
||||
|
||||
switch (type) {
|
||||
case 0:
|
||||
res = await cancelOrder(orderId);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
res = await delOrder(orderId);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
res = await confirmOrder(orderId);
|
||||
break;
|
||||
}
|
||||
|
||||
if (res.code == 1) {
|
||||
this.close()
|
||||
this.$emit("refresh")
|
||||
this.$toast({
|
||||
title: res.msg
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
getTipsText() {
|
||||
const {
|
||||
type
|
||||
} = this
|
||||
switch (type) {
|
||||
case 0:
|
||||
return "确认取消订单吗?"
|
||||
case 1:
|
||||
return "确认删除订单吗?"
|
||||
case 2:
|
||||
return "确认收货吗?"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<view class="order-goods bg-white">
|
||||
<view v-for="(item, index) in list" :key="index" class="item-wrap">
|
||||
<view class="item row" @tap="toGoods(item.goods_id)">
|
||||
<view class="goods-img">
|
||||
<custom-image
|
||||
width="180rpx"
|
||||
radius="10rpx"
|
||||
height="180rpx"
|
||||
lazy-load
|
||||
:src="item.image_str || item.image"
|
||||
></custom-image>
|
||||
</view>
|
||||
<view class="goods-info ml20 flex1">
|
||||
<view class="goods-name line2 mb10">
|
||||
<u-tag
|
||||
class="mr10"
|
||||
v-if="team.need"
|
||||
:text="team.need + '人团'"
|
||||
size="mini"
|
||||
type="primary"
|
||||
mode="plain"
|
||||
/>
|
||||
{{ item.goods_name || item.name }}</view
|
||||
>
|
||||
<view class="goods-spec xs muted mb20">{{
|
||||
item.spec_value_str || item.spec_value
|
||||
}}</view>
|
||||
<view class="row-between">
|
||||
<view class="goods-price row">
|
||||
<view class="primary">
|
||||
<price-format
|
||||
v-if="!item.is_member && order_type === 0"
|
||||
:weight="500"
|
||||
:subscript-size="24"
|
||||
:first-size="34"
|
||||
:second-size="24"
|
||||
:price="item.original_price || item.goods_price"
|
||||
></price-format>
|
||||
</view>
|
||||
<view class="vip-price row" v-if="item.is_member && order_type === 0">
|
||||
<view class="price-name xxs">会员价</view>
|
||||
<view style="padding: 0 10rpx">
|
||||
<price-format
|
||||
:price="item.goods_price"
|
||||
:first-size="22"
|
||||
:second-size="22"
|
||||
:subscript-size="22"
|
||||
:weight="500"
|
||||
color="#7B3200"
|
||||
></price-format>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="vip-price row"
|
||||
v-if="order_type === 1 || order_type === 2 || order_type === 3"
|
||||
>
|
||||
<view class="price-name xxs" style="background-color: #e74346">
|
||||
<text v-if="order_type === 1">秒杀价</text>
|
||||
<text v-if="order_type === 2">拼团价</text>
|
||||
<text v-if="order_type === 3">砍价</text>
|
||||
</view>
|
||||
|
||||
<view style="padding: 0 10rpx">
|
||||
<price-format
|
||||
:price="item.goods_price"
|
||||
:first-size="22"
|
||||
:second-size="22"
|
||||
:subscript-size="22"
|
||||
:weight="500"
|
||||
color="#7B3200"
|
||||
></price-format>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-num sm">x{{ item.goods_num }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template v-if="mode === 'comfirm'">
|
||||
<view class="delivery" v-if="delivery === 1 && !item.is_express"
|
||||
>该商品不支持快递配送</view
|
||||
>
|
||||
<view class="delivery" v-if="delivery === 2 && !item.is_selffetch"
|
||||
>该商品不支持门店自提</view
|
||||
>
|
||||
</template>
|
||||
|
||||
<view class="goods-footer row" v-if="link">
|
||||
<view style="flex: 1"></view>
|
||||
<navigator
|
||||
class="mr20"
|
||||
hover-class="none"
|
||||
:url="'/bundle/pages/goods_reviews/goods_reviews?id=' + item.id"
|
||||
v-if="item.comment_btn"
|
||||
>
|
||||
<button size="xs" class="plain br60" hover-class="none">评价晒图</button>
|
||||
</navigator>
|
||||
<navigator
|
||||
v-if="item.refund_btn"
|
||||
hover-class="none"
|
||||
:url="
|
||||
'/bundle/pages/apply_refund/apply_refund?order_id=' +
|
||||
item.order_id +
|
||||
'&item_id=' +
|
||||
item.item_id
|
||||
"
|
||||
>
|
||||
<button size="xs" class="plain br60" hover-class="none">申请退款</button>
|
||||
</navigator>
|
||||
<view v-if="item.after_status_desc" style="color: orange">
|
||||
{{ item.after_status_desc }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
team: {
|
||||
type: [Object, Array],
|
||||
default: () => ({})
|
||||
},
|
||||
delivery: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// order | comfirm
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'order'
|
||||
},
|
||||
order_type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toGoods(id) {
|
||||
if (!this.link) return
|
||||
uni.navigateTo({
|
||||
url: `/pages/goods_details/goods_details?id=${id}`
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.order-goods {
|
||||
.item {
|
||||
padding: 20rpx 24rpx;
|
||||
.vip-price {
|
||||
// margin: 0 10rpx;
|
||||
background-color: #ffe9ba;
|
||||
line-height: 30rpx;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
.price-name {
|
||||
background-color: #101010;
|
||||
padding: 3rpx 10rpx;
|
||||
color: #ffd4b7;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
position: absolute;
|
||||
right: -15rpx;
|
||||
background-color: #ffe9ba;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.goods-footer {
|
||||
height: 70rpx;
|
||||
align-items: flex-start;
|
||||
padding: 0 24rpx;
|
||||
.plain {
|
||||
border: 1px solid #999;
|
||||
height: 52rpx;
|
||||
line-height: 52rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.delivery {
|
||||
display: inline-block;
|
||||
margin-left: calc(180rpx + 20rpx * 2);
|
||||
padding: 4rpx 15rpx;
|
||||
border-radius: 60px;
|
||||
font-size: 20rpx;
|
||||
background-color: #f4f4f4;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,479 @@
|
||||
// +---------------------------------------------------------------------- // |
|
||||
likeshop开源商城系统 //
|
||||
+---------------------------------------------------------------------- // |
|
||||
欢迎阅读学习系统程序代码,建议反馈是我们前进的动力 // |
|
||||
gitee下载:https://gitee.com/likeshop_gitee // |
|
||||
github下载:https://github.com/likeshop-github // |
|
||||
访问官网:https://www.likeshop.cn // | 访问社区:https://home.likeshop.cn // |
|
||||
访问手册:http://doc.likeshop.cn // | 微信公众号:likeshop技术社区 // |
|
||||
likeshop系列产品在gitee、github等公开渠道开源版本可免费商用,未经许可不能去除前后端官方版权标识
|
||||
// |
|
||||
likeshop系列产品收费版本务必购买商业授权,购买去版权授权后,方可去除前后端官方版权标识
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布 // |
|
||||
likeshop团队版权所有并拥有最终解释权 //
|
||||
+---------------------------------------------------------------------- // |
|
||||
author: likeshop.cn.team //
|
||||
+----------------------------------------------------------------------
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<view class="order-list">
|
||||
<navigator
|
||||
v-for="(item, index) in orderList"
|
||||
:key="index"
|
||||
hover-class="none"
|
||||
class="order-item bg-white mt20"
|
||||
:url="'/pages/order_details/order_details?id=' + item.id"
|
||||
>
|
||||
<view class="order-header row-between">
|
||||
<view class="row">
|
||||
<view v-if="item.delivery_type == 2" class="mr10">
|
||||
<u-tag
|
||||
text="自提"
|
||||
size="mini"
|
||||
type="primary"
|
||||
mode="dark"
|
||||
bg-color="#0cc21e"
|
||||
/>
|
||||
</view>
|
||||
<view v-if="item.order_type == 1" class="mr10">
|
||||
<u-tag text="秒杀" size="mini" type="primary" mode="plain" />
|
||||
</view>
|
||||
<view v-if="item.order_type == 2" class="mr10">
|
||||
<u-tag text="拼团" size="mini" type="primary" mode="plain" />
|
||||
</view>
|
||||
<view v-if="item.order_type == 3" class="mr10">
|
||||
<u-tag text="砍价" size="mini" type="primary" mode="plain" />
|
||||
</view>
|
||||
订单编号:{{ item.order_sn }}
|
||||
</view>
|
||||
<view :class="item.order_status == 4 ? 'muted' : 'primary'">{{
|
||||
item.order_status_desc
|
||||
}}</view>
|
||||
</view>
|
||||
<view class="order-con">
|
||||
<order-goods
|
||||
:list="item.order_goods"
|
||||
:order_type="item.order_type"
|
||||
></order-goods>
|
||||
<view class="all-price row-end">
|
||||
<text class="muted xs"
|
||||
>共{{ goodCount(item.order_goods) }}件商品,总金额:</text
|
||||
>
|
||||
<price-format
|
||||
:subscript-size="30"
|
||||
:first-size="30"
|
||||
:second-size="30"
|
||||
:price="item.order_amount"
|
||||
></price-format>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="order-footer row"
|
||||
v-if="
|
||||
item.pickup_btn ||
|
||||
item.cancel_btn ||
|
||||
item.delivery_btn ||
|
||||
item.take_btn ||
|
||||
item.del_btn ||
|
||||
item.pay_btn ||
|
||||
item.comment_btn
|
||||
"
|
||||
>
|
||||
<view style="flex: 1">
|
||||
<view
|
||||
class="primary sm row"
|
||||
style="line-height: 26rpx"
|
||||
v-if="getCancelTime(item.order_cancel_time) > 0"
|
||||
><u-count-down
|
||||
separator="zh"
|
||||
:timestamp="getCancelTime(item.order_cancel_time)"
|
||||
separator-color="#FF2C3C"
|
||||
color="#FF2C3C"
|
||||
:separator-size="26"
|
||||
:font-size="26"
|
||||
bg-color="transparent"
|
||||
@end="reflesh"
|
||||
></u-count-down
|
||||
></view>
|
||||
</view>
|
||||
<view v-if="item.cancel_btn">
|
||||
<button
|
||||
size="sm"
|
||||
class="plain br60 lighter"
|
||||
hover-class="none"
|
||||
@tap.stop="cancelOrder(item.id)"
|
||||
>
|
||||
取消订单
|
||||
</button>
|
||||
</view>
|
||||
<view
|
||||
v-if="item.delivery_btn"
|
||||
@tap.stop="
|
||||
goPage(
|
||||
'/bundle/pages/goods_logistics/goods_logistics?id=' + item.id
|
||||
)
|
||||
"
|
||||
>
|
||||
<button size="sm" class="btn plain br60 lighter" hover-class="none">
|
||||
查看物流
|
||||
</button>
|
||||
</view>
|
||||
<view v-if="item.del_btn">
|
||||
<button
|
||||
size="sm"
|
||||
class="btn plain br60 lighter"
|
||||
hover-class="none"
|
||||
@tap.stop="delOrder(item.id)"
|
||||
>
|
||||
删除订单
|
||||
</button>
|
||||
</view>
|
||||
<view v-if="item.pay_btn" class="ml20">
|
||||
<button
|
||||
size="sm"
|
||||
class="btn bg-primary br60 white"
|
||||
@tap.stop="payNow(item.id)"
|
||||
>
|
||||
立即付款
|
||||
</button>
|
||||
</view>
|
||||
<view v-if="item.comment_btn" class="ml20">
|
||||
<button
|
||||
size="sm"
|
||||
hover-class="none"
|
||||
class="btn plain btn br60 primary red"
|
||||
>
|
||||
去评价
|
||||
</button>
|
||||
</view>
|
||||
<view v-if="item.pickup_btn" class="ml20">
|
||||
<button
|
||||
size="sm"
|
||||
hover-class="none"
|
||||
class="btn plain btn br60 primary red"
|
||||
>
|
||||
查看提货码
|
||||
</button>
|
||||
</view>
|
||||
<view v-if="item.take_btn" class="ml20">
|
||||
<button
|
||||
size="sm"
|
||||
class="btn plain br60 primary red"
|
||||
hover-class="none"
|
||||
@tap.stop="comfirmOrder(item.id, item.pay_way)"
|
||||
>
|
||||
确认收货
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</navigator>
|
||||
<loading-footer :status="status" :slot-empty="true" @refresh="reload">
|
||||
<view slot="empty" class="column-center" style="padding-top: 200rpx">
|
||||
<image class="img-null" src="/static/images/goods_null.png"></image>
|
||||
<text class="lighter">暂无订单</text>
|
||||
</view>
|
||||
</loading-footer>
|
||||
</view>
|
||||
<order-dialog
|
||||
ref="orderDialog"
|
||||
:order-id="orderId"
|
||||
:type="type"
|
||||
@refresh="reflesh"
|
||||
></order-dialog>
|
||||
<loading-view
|
||||
v-if="showLoading"
|
||||
background-color="transparent"
|
||||
:size="50"
|
||||
></loading-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getOrderList,
|
||||
cancelOrder,
|
||||
delOrder,
|
||||
confirmOrder,
|
||||
getwxReceiveDetail,
|
||||
getwechatSyncCheck,
|
||||
} from "@/api/order";
|
||||
import { compareWeChatVersion } from "@/utils/tools";
|
||||
|
||||
import { prepay } from "@/api/app";
|
||||
import { loadingType } from "@/utils/type";
|
||||
|
||||
import { wxpay, alipay } from "@/utils/pay";
|
||||
import { loadingFun } from "@/utils/tools";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
page: 1,
|
||||
orderList: [],
|
||||
status: loadingType.LOADING,
|
||||
showCancel: false,
|
||||
type: 0,
|
||||
orderId: "",
|
||||
showLoading: false,
|
||||
pay_way: "",
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
orderType: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
uni.$on("refreshorder", () => {
|
||||
this.reflesh();
|
||||
});
|
||||
uni.$on("payment", (params) => {
|
||||
if (params.result) {
|
||||
this.reflesh();
|
||||
uni.navigateBack();
|
||||
setTimeout(() => this.$toast({ title: "支付成功" }), 0.5 * 1000);
|
||||
}
|
||||
});
|
||||
},
|
||||
beforeMount: function () {
|
||||
this.getOrderListFun();
|
||||
},
|
||||
destroyed: function () {
|
||||
uni.$off(["payment", "refreshorder"]);
|
||||
},
|
||||
methods: {
|
||||
reflesh() {
|
||||
this.page = 1;
|
||||
this.orderList = [];
|
||||
this.status = loadingType.LOADING;
|
||||
this.type = 0;
|
||||
this.getOrderListFun();
|
||||
},
|
||||
|
||||
reload() {
|
||||
this.status = loadingType.LOADING;
|
||||
this.getOrderListFun();
|
||||
},
|
||||
|
||||
orderDialog() {
|
||||
this.$refs.orderDialog.open();
|
||||
},
|
||||
|
||||
delOrder(id) {
|
||||
this.orderId = id;
|
||||
this.type = 1;
|
||||
this.$nextTick(() => {
|
||||
this.orderDialog();
|
||||
});
|
||||
},
|
||||
// 小程序确认收货
|
||||
comfirmReceive(transaction_id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.openBusinessView({
|
||||
businessType: "weappOrderConfirm",
|
||||
extraData: {
|
||||
transaction_id,
|
||||
},
|
||||
success({ extraData }) {
|
||||
if (extraData.status == "success") {
|
||||
resolve("确认收货");
|
||||
} else {
|
||||
resolve("取消收货");
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
//查询是否收货成功
|
||||
querycomfirmReceive(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getwechatSyncCheck({ id })
|
||||
.then(({ data }) => {
|
||||
if (data.order.order_state === 4) {
|
||||
resolve("已确认收货");
|
||||
} else {
|
||||
reject("未确认收货");
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
comfirmOrder(id, pay_way) {
|
||||
this.orderId = id;
|
||||
this.pay_way = pay_way;
|
||||
this.type = 2;
|
||||
this.$nextTick(async () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
let res = {};
|
||||
if (this.pay_way === 1) {
|
||||
res = await getwechatSyncCheck({ id: this.orderId });
|
||||
console.log(res);
|
||||
}
|
||||
if (
|
||||
compareWeChatVersion("2.6.0") === 1 &&
|
||||
wx.openBusinessView &&
|
||||
this.pay_way === 1 &&
|
||||
res.data.order.order_state !== 1
|
||||
) {
|
||||
try {
|
||||
const { data } = await getwxReceiveDetail({
|
||||
order_id: this.orderId,
|
||||
});
|
||||
await this.comfirmReceive(data.transaction_id);
|
||||
await this.querycomfirmReceive(this.orderId);
|
||||
await confirmOrder(this.orderId);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
this.reflesh();
|
||||
} else {
|
||||
this.orderDialog();
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
this.orderDialog();
|
||||
// #endif
|
||||
});
|
||||
},
|
||||
|
||||
cancelOrder(id) {
|
||||
this.orderId = id;
|
||||
this.type = 0;
|
||||
this.$nextTick(() => {
|
||||
this.orderDialog();
|
||||
});
|
||||
},
|
||||
|
||||
payNow(id) {
|
||||
// this.showLoading = true
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/payment/payment?from=${"order"}&order_id=${id}`,
|
||||
});
|
||||
|
||||
// prepay({
|
||||
// from: 'order',
|
||||
// order_id: id
|
||||
// }).then(res => {
|
||||
// let args = res.data;
|
||||
// this.showLoading = false
|
||||
// if (res.code == 1) {
|
||||
// wxpay(args).then((resPay) => {
|
||||
// if(resPay == 'success') {
|
||||
// this.$toast({
|
||||
// title: "支付成功"
|
||||
// })
|
||||
// uni.$emit("refreshorder")
|
||||
// }
|
||||
// })
|
||||
// }else if(res.code == 20001){
|
||||
// alipay(args).then((resPay) => {
|
||||
// if(resPay == 'success') {
|
||||
// this.$toast({
|
||||
// title: "支付成功"
|
||||
// })
|
||||
// uni.$emit("refreshorder")
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// });
|
||||
},
|
||||
|
||||
async getOrderListFun() {
|
||||
let { page, orderType, orderList, status } = this;
|
||||
const data = await loadingFun(getOrderList, page, orderList, status, {
|
||||
type: orderType,
|
||||
});
|
||||
if (!data) return;
|
||||
this.page = data.page;
|
||||
this.orderList = data.dataList;
|
||||
this.status = data.status;
|
||||
},
|
||||
goPage(url) {
|
||||
uni.navigateTo({
|
||||
url,
|
||||
});
|
||||
},
|
||||
goodCount(goodLists) {
|
||||
console.log(goodLists);
|
||||
let count = 0;
|
||||
goodLists.forEach((item) => {
|
||||
count += item.goods_num;
|
||||
});
|
||||
return count;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
getOrderStatus() {
|
||||
return (status) => {
|
||||
let text = "";
|
||||
switch (status) {
|
||||
case 0:
|
||||
text = "待支付";
|
||||
break;
|
||||
case 1:
|
||||
text = "待发货";
|
||||
break;
|
||||
case 2:
|
||||
text = "待收货";
|
||||
break;
|
||||
case 3:
|
||||
text = "已完成";
|
||||
break;
|
||||
case 4:
|
||||
text = "订单已关闭";
|
||||
break;
|
||||
}
|
||||
return text;
|
||||
};
|
||||
},
|
||||
getCancelTime() {
|
||||
return (time) => time - Date.now() / 1000;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.order-list {
|
||||
// min-height: calc(100vh - 80rpx);
|
||||
padding: 0 20rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.order-item {
|
||||
border-radius: 10rpx;
|
||||
|
||||
.order-header {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
border-bottom: 1px dotted #e5e5e5;
|
||||
}
|
||||
|
||||
.all-price {
|
||||
text-align: right;
|
||||
padding: 0 24rpx 20rpx;
|
||||
}
|
||||
|
||||
.order-footer {
|
||||
height: 100rpx;
|
||||
border-top: $-solid-border;
|
||||
padding: 0 24rpx;
|
||||
|
||||
.plain {
|
||||
border: 1px solid #bbbbbb;
|
||||
|
||||
&.red {
|
||||
border-color: $-color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<picker mode="multiSelector"
|
||||
:value="multiIndex"
|
||||
:range="multiArray"
|
||||
@change="handleValueChange"
|
||||
@columnchange="handleColumnChange">
|
||||
<slot></slot>
|
||||
</picker>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const CHINA_REGIONS = require('./regions.json')
|
||||
export default {
|
||||
props:{
|
||||
defaultRegions:{
|
||||
type:Array,
|
||||
default(){
|
||||
return []
|
||||
}
|
||||
},
|
||||
defaultRegionCode:{
|
||||
type:String
|
||||
},
|
||||
defaultRegion:[String,Array]
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cityArr:CHINA_REGIONS[0].child,
|
||||
districtArr:CHINA_REGIONS[0].child[0].child,
|
||||
multiIndex: [0, 0, 0],
|
||||
isInitMultiArray:true,
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
defaultRegion:{
|
||||
handler(region,oldRegion){
|
||||
if(Array.isArray(region)){
|
||||
// 避免传的是字面量的时候重复触发
|
||||
oldRegion = oldRegion || []
|
||||
if(region.join('')!==oldRegion.join('')){
|
||||
this.handleDefaultRegion(region)
|
||||
}
|
||||
}else if(region&®ion.length == 6){
|
||||
this.handleDefaultRegion(region)
|
||||
}else{
|
||||
console.warn('defaultRegion非有效格式')
|
||||
}
|
||||
},
|
||||
immediate:true,
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
multiArray(){
|
||||
return this.pickedArr.map(arr=>arr.map(item=>item.name))
|
||||
},
|
||||
pickedArr(){
|
||||
// 进行初始化
|
||||
if(this.isInitMultiArray){
|
||||
return [
|
||||
CHINA_REGIONS,
|
||||
CHINA_REGIONS[0].child,
|
||||
CHINA_REGIONS[0].child[0].child
|
||||
]
|
||||
}
|
||||
return [CHINA_REGIONS,this.cityArr,this.districtArr];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleColumnChange(e){
|
||||
// console.log(e);
|
||||
this.isInitMultiArray = false;
|
||||
const that = this;
|
||||
let col = e.detail.column;
|
||||
let row = e.detail.value;
|
||||
that.multiIndex[col] = row;
|
||||
try{
|
||||
switch(col){
|
||||
case 0:
|
||||
if(CHINA_REGIONS[that.multiIndex[0]].child.length==0){
|
||||
that.cityArr = that.districtArr = [CHINA_REGIONS[that.multiIndex[0]]]
|
||||
break;
|
||||
}
|
||||
that.cityArr = CHINA_REGIONS[that.multiIndex[0]].child
|
||||
that.districtArr = CHINA_REGIONS[that.multiIndex[0]].child[that.multiIndex[1]].child
|
||||
break;
|
||||
case 1:
|
||||
that.districtArr = CHINA_REGIONS[that.multiIndex[0]].child[that.multiIndex[1]].child
|
||||
break;
|
||||
case 2:
|
||||
break;
|
||||
}
|
||||
}catch(e){
|
||||
// console.log(e);
|
||||
that.districtArr = CHINA_REGIONS[that.multiIndex[0]].child[0].child
|
||||
}
|
||||
|
||||
},
|
||||
handleValueChange(e){
|
||||
// 结构赋值
|
||||
let [index0,index1,index2] = e.detail.value;
|
||||
let [arr0,arr1,arr2] = this.pickedArr;
|
||||
let address = [arr0[index0],arr1[index1],arr2[index2]];
|
||||
// console.log(address);
|
||||
this.$emit('getRegion',address)
|
||||
},
|
||||
handleDefaultRegion(region){
|
||||
const isCode = !Array.isArray(region)
|
||||
this.isInitMultiArray = false;
|
||||
let children = CHINA_REGIONS
|
||||
for(let i=0;i<3;i++){
|
||||
for(let j=0;j<children.length;j++){
|
||||
let condition = isCode?children[j].value==region.slice(0,(i+1)*2):children[j].name.includes(region[i]);
|
||||
if(condition){
|
||||
// 匹配成功进行赋值
|
||||
// console.log(i,j,children.length-1);
|
||||
children = children[j].child;
|
||||
if(i==0){
|
||||
this.cityArr = children
|
||||
}else if(i==1){
|
||||
this.districtArr = children
|
||||
}
|
||||
this.$set(this.multiIndex,i,j)
|
||||
// console.log(this.multiIndex);
|
||||
break;
|
||||
}else{
|
||||
// 首次匹配失败就用默认的初始化
|
||||
// console.log(i,j,children.length-1);
|
||||
if(i==0 && j==(children.length-1)){
|
||||
this.isInitMultiArray = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<text :style="{color, 'font-weight': weight}" :class="(lineThrough ? 'line-through' : '') + ' price-format'">
|
||||
<text v-if="showSubscript" :style="{'font-size': subscriptSize + 'rpx', 'margin-right': '2rpx'}">¥</text>
|
||||
<text :style="{'font-size': firstSize + 'rpx', 'margin-right': '1rpx'}">{{priceSlice.first}}</text>
|
||||
<text v-if="priceSlice.second" :style="{'font-size': secondSize + 'rpx'}">.{{priceSlice.second}}</text>
|
||||
</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
priceSlice: {
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {},
|
||||
props: {
|
||||
firstSize: {
|
||||
type: [String, Number],
|
||||
default: 28
|
||||
},
|
||||
secondSize: {
|
||||
type: [String, Number],
|
||||
default: 28
|
||||
},
|
||||
color: {
|
||||
type: String
|
||||
},
|
||||
weight: {
|
||||
type:[String, Number] ,
|
||||
default: 400
|
||||
},
|
||||
price: {
|
||||
type: [String, Number],
|
||||
default: ""
|
||||
},
|
||||
showSubscript: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
subscriptSize: {
|
||||
type: [String, Number],
|
||||
default: 28
|
||||
},
|
||||
lineThrough: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.priceFormat()
|
||||
},
|
||||
watch: {
|
||||
price(val) {
|
||||
this.priceFormat()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
priceFormat() {
|
||||
let {
|
||||
price
|
||||
} = this;
|
||||
let priceSlice = {}
|
||||
if(price !== null && price !== '' && price !== undefined) {
|
||||
price = parseFloat(price);
|
||||
price = String(price).split('.');
|
||||
priceSlice.first = price[0];
|
||||
priceSlice.second = price[1];
|
||||
this.priceSlice = priceSlice
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.price-format {
|
||||
font-family: Avenir, SourceHanSansCN, PingFang SC, Arial, Hiragino Sans GB, Microsoft YaHei, sans-serif;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
zIndex="9999"
|
||||
border-radius="14"
|
||||
:maskCloseAble="false"
|
||||
safeAreaInsetBottom
|
||||
>
|
||||
<view class="privacy-containter" v-if="appConfig">
|
||||
<view class="privacy-containter-tital"
|
||||
>{{ appConfig.name || "" }}隐私政策</view
|
||||
>
|
||||
<view class="privacy-containter-tip"
|
||||
>欢迎你使用{{
|
||||
appConfig.name || ""
|
||||
}}!请仔细阅读以下内容,并作出适当的选择:</view
|
||||
>
|
||||
<view class="privacy-containter-outline">隐私政策概要</view>
|
||||
<view class=""
|
||||
>主要说明:我们所处理的信息种类、方式和目的;你所享有的权益;第三方插件信息等。</view
|
||||
>
|
||||
<view class="privacy-containter-read primary" @click="handleOpen"
|
||||
>点击阅读《隐私政策(小程序)》完整版 ></view
|
||||
>
|
||||
|
||||
<button
|
||||
class="btn-comfirm"
|
||||
open-type="agreePrivacyAuthorization"
|
||||
@agreeprivacyauthorization="handleAgreePrivacyAuthorization"
|
||||
>
|
||||
同意并继续
|
||||
</button>
|
||||
<button class="btn-cancel" @click="handlecancel">取消</button>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters, mapActions } from "vuex";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["appConfig"]),
|
||||
},
|
||||
methods: {
|
||||
handleOpen() {
|
||||
wx.openPrivacyContract({
|
||||
success: (res) => {
|
||||
console.log(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err);
|
||||
},
|
||||
});
|
||||
},
|
||||
handlecancel() {
|
||||
this.$toast({
|
||||
title: "须同意后才可继续使用",
|
||||
});
|
||||
},
|
||||
handleAgreePrivacyAuthorization() {
|
||||
this.$emit("input", false);
|
||||
uni.showTabBar();
|
||||
// if (this.appConfig.is_open_nearby) {
|
||||
// // this.initLocationFunc();
|
||||
// }
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.privacy-containter {
|
||||
background-color: white;
|
||||
height: 750rpx;
|
||||
padding: 42rpx;
|
||||
&-tital {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
&-tip {
|
||||
margin: 50rpx 0;
|
||||
}
|
||||
&-outline {
|
||||
font-weight: 500;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
&-read {
|
||||
margin-top: 15rpx;
|
||||
}
|
||||
.btn-comfirm {
|
||||
background-color: $-color-primary;
|
||||
color: white;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
margin-top: 50rpx;
|
||||
}
|
||||
.btn-cancel {
|
||||
background-color: #f7f7f7;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<view class="swiper-wrap">
|
||||
<swiper
|
||||
class="swiper"
|
||||
ref="swiper"
|
||||
:autoplay="autoplay"
|
||||
:circular="circular"
|
||||
:interval="interval"
|
||||
:duration="duration"
|
||||
@change="swiperChange"
|
||||
>
|
||||
<block v-for="(item, index) in urls" :key="index">
|
||||
<swiper-item @tap="previewImage(index)">
|
||||
<view v-if="item.type == 'video'" class="video-wrap">
|
||||
<!-- #ifdef H5 || MP-WEIXIN -->
|
||||
<video
|
||||
id="myVideo"
|
||||
class="my-video"
|
||||
:enable-progress-gesture="false"
|
||||
:controls="showControls"
|
||||
:show-progress="true"
|
||||
:show-fullscreen-btn="showControls"
|
||||
:src="item.url"
|
||||
:show-center-play-btn="false"
|
||||
:muted="false"
|
||||
show-mute-btn="true"
|
||||
@error="videoErrorCallback"
|
||||
:show-play-btn="showControls"
|
||||
@click="play"
|
||||
@ended="playEnd"
|
||||
@fullscreenchange="fullscreenchange"
|
||||
></video>
|
||||
<image
|
||||
v-show="showPlay"
|
||||
@tap.stop="play"
|
||||
src="/static/images/icon_play.png"
|
||||
class="icon-play"
|
||||
>
|
||||
</image>
|
||||
|
||||
<!-- <image v-show="showPlay" style="right: 50rpx;bottom: 50rpx;" @tap.stop="play" src="/static/images/all.png" class="icon-play">
|
||||
</image> -->
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<j-video
|
||||
:url="item.url"
|
||||
height="750rpx"
|
||||
width="750rpx"
|
||||
:poster="urls[1].url"
|
||||
></j-video>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<u-image
|
||||
v-else
|
||||
width="750rpx"
|
||||
height="750rpx"
|
||||
:src="item.url"
|
||||
mode="aspectFill"
|
||||
></u-image>
|
||||
</swiper-item>
|
||||
</block>
|
||||
</swiper>
|
||||
<view class="dots black sm bg-white br60" id="bottom"
|
||||
>{{ currentSwiper + 1 }}/{{ urls.length }}</view
|
||||
>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var app = getApp();
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentSwiper: 0,
|
||||
urls: [],
|
||||
showPlay: true,
|
||||
showControls: false,
|
||||
autoplay: true,
|
||||
start: 0,
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
imgUrls: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
circular: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
interval: {
|
||||
type: Number,
|
||||
default: 3000,
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
},
|
||||
video: {
|
||||
type: String,
|
||||
},
|
||||
isShow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
imgUrls: {
|
||||
handler(val) {
|
||||
this.urls = val.map((item) => {
|
||||
return {
|
||||
url: item.uri,
|
||||
type: "image",
|
||||
};
|
||||
});
|
||||
if (this.video) {
|
||||
this.urls.unshift({
|
||||
url: this.video,
|
||||
type: "video",
|
||||
});
|
||||
this.autoplay = false;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.videoContext = uni.createVideoContext("myVideo", this);
|
||||
this.videoContexts = uni.createVideoContext("videos", this);
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
isShow: {
|
||||
handler(val) {
|
||||
if (val == true && !this.showPlay) {
|
||||
this.videoContext.stop();
|
||||
} else if (val == false && this.showPlay == false) {
|
||||
this.videoContext.play();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
swiperChange(e) {
|
||||
this.currentSwiper = e.detail.current;
|
||||
if (e.detail.current !== 0 && this.video != "") {
|
||||
try {
|
||||
this.showPlay = true;
|
||||
this.videoContext.stop();
|
||||
this.videoContexts.stop();
|
||||
} catch (error) {
|
||||
console.log("err==>", err);
|
||||
}
|
||||
}
|
||||
},
|
||||
videoErrorCallback(err) {
|
||||
console.log("err==>", err);
|
||||
},
|
||||
|
||||
previewImage(current) {
|
||||
let index = current;
|
||||
|
||||
if (current == 0 && this.video) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.previewMedia({
|
||||
current,
|
||||
indicator: "default",
|
||||
sources: this.urls,
|
||||
});
|
||||
//#endif
|
||||
// #ifdef H5 || APP-PLUS
|
||||
if (this.video) {
|
||||
index = current - 1;
|
||||
}
|
||||
if (this.urls[current].type == "video") {
|
||||
this.videoContext.requestFullScreen();
|
||||
} else {
|
||||
uni.previewImage({
|
||||
indicator: "default",
|
||||
index,
|
||||
urls: this.imgUrls.map((item) => item.uri),
|
||||
});
|
||||
}
|
||||
|
||||
//#endif
|
||||
},
|
||||
play() {
|
||||
if (this.start == 0) {
|
||||
this.start = 1;
|
||||
this.showPlay = false;
|
||||
this.videoContext.play();
|
||||
} else {
|
||||
this.start = 0;
|
||||
this.showPlay = true;
|
||||
this.videoContext.stop();
|
||||
}
|
||||
},
|
||||
|
||||
playEnd() {
|
||||
this.start = 0;
|
||||
this.showPlay = true;
|
||||
},
|
||||
|
||||
fullscreenchange(e) {
|
||||
const { fullScreen } = e.detail;
|
||||
!fullScreen && this.videoContext.stop();
|
||||
this.showControls = fullScreen ? true : false;
|
||||
this.showPlay = fullScreen ? false : true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.swiper-wrap {
|
||||
width: 100%;
|
||||
height: 750rpx;
|
||||
position: relative;
|
||||
|
||||
.close {
|
||||
width: 250rpx;
|
||||
height: 250rpx;
|
||||
top: 230rpx;
|
||||
right: 50rpx;
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
position: fixed;
|
||||
|
||||
.close-item {
|
||||
top: 0rpx;
|
||||
right: 0rpx;
|
||||
position: absolute;
|
||||
z-index: 1001;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-wrap .swiper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.swiper-wrap .swiper .slide-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.swiper-wrap .dots {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
bottom: 24rpx;
|
||||
display: flex;
|
||||
height: 34rpx;
|
||||
padding: 0 15rpx;
|
||||
}
|
||||
|
||||
.swiper-wrap .video-wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.swiper-wrap .my-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.swiper-wrap .icon-play {
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<!--components/recommend/recommend.wxml-->
|
||||
<view class="recommend" v-if="goodsList.length">
|
||||
<!-- <view class="header row-center">
|
||||
<image class="title" src="/images/recommend_title.png" />
|
||||
</view> -->
|
||||
<view class="goods-title row-center">
|
||||
<text class="line"></text>
|
||||
<view class="row">
|
||||
<image class="mr10" src="/static/images/icon_like.png"></image>
|
||||
<text class="bold xxl">好物优选</text>
|
||||
</view>
|
||||
<text class="line"></text>
|
||||
</view>
|
||||
<goods-list :list="goodsList"></goods-list>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeshop开源商城系统
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee
|
||||
// | github下载:https://github.com/likeshop-github
|
||||
// | 访问官网:https://www.likeshop.cn
|
||||
// | 访问社区:https://home.likeshop.cn
|
||||
// | 访问手册:http://doc.likeshop.cn
|
||||
// | 微信公众号:likeshop技术社区
|
||||
// | likeshop系列产品在gitee、github等公开渠道开源版本可免费商用,未经许可不能去除前后端官方版权标识
|
||||
// | likeshop系列产品收费版本务必购买商业授权,购买去版权授权后,方可去除前后端官方版权标识
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布
|
||||
// | likeshop团队版权所有并拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeshop.cn.team
|
||||
// +----------------------------------------------------------------------
|
||||
import { getBestList } from '../../api/store';
|
||||
import goodsList from "../goods-list/goods-list";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
goodsList: []
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
goodsList
|
||||
},
|
||||
props: {},
|
||||
beforeMount: function () {
|
||||
this.getBestListFun();
|
||||
},
|
||||
destroyed: function () {// 在组件实例被从页面节点树移除时执行
|
||||
},
|
||||
methods: {
|
||||
getBestListFun() {
|
||||
getBestList({
|
||||
page_no: 1,
|
||||
page_size: 6
|
||||
}).then(res => {
|
||||
if (res.code == 1) {
|
||||
this.goodsList = res.data.list
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
/* components/recommend/recommend.wxss */
|
||||
.recommend {
|
||||
padding-bottom: 50rpx;
|
||||
}
|
||||
.recommend .header .title {
|
||||
width: 468rpx;
|
||||
height: 45rpx;
|
||||
margin: 10rpx 0;
|
||||
}
|
||||
|
||||
.recommend .goods-title {
|
||||
height: 100rpx;
|
||||
}
|
||||
.recommend .goods-title .line {
|
||||
width: 58rpx;
|
||||
height: 1px;
|
||||
background-color: #ccc;
|
||||
margin: 0 22rpx;
|
||||
}
|
||||
.recommend .goods-title image {
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-modal
|
||||
:value="showTips"
|
||||
ref="uModal"
|
||||
show-cancel-button
|
||||
:confirm-color="primaryColor"
|
||||
async-close
|
||||
confirm-text="前往绑定"
|
||||
@confirm="toSetting"
|
||||
@cancel="goBack"
|
||||
>
|
||||
<view class="slot-content row-center" style="padding: 40rpx">
|
||||
您暂未绑定手机号,请先绑定
|
||||
</view>
|
||||
</u-modal>
|
||||
<u-modal
|
||||
ref="uModalSet"
|
||||
:value="!showTips && showSetPwd"
|
||||
title="设置转账密码"
|
||||
show-cancel-button
|
||||
:confirm-color="primaryColor"
|
||||
async-close
|
||||
confirm-text="确定"
|
||||
@confirm="onSetPwd"
|
||||
@cancel="goBack"
|
||||
>
|
||||
<view class="slot-content" style="padding: 40rpx">
|
||||
<view style="font-size: 42rpx">{{ mobile }}</view>
|
||||
<view class="mt20">
|
||||
<u-input
|
||||
type="password"
|
||||
v-model="setPwd"
|
||||
:border="true"
|
||||
placeholder="请设置您的转账密码"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</view>
|
||||
<view class="mt20">
|
||||
<u-input
|
||||
type="password"
|
||||
v-model="comfirmPwd"
|
||||
:border="true"
|
||||
placeholder="再次确认转账密码"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</u-modal>
|
||||
<u-modal
|
||||
ref="uModalInput"
|
||||
:value="showConfirm"
|
||||
show-cancel-button
|
||||
:confirm-color="primaryColor"
|
||||
async-close
|
||||
confirm-text="确定"
|
||||
@confirm="onConfirm"
|
||||
@cancel="toSetting"
|
||||
cancel-text="忘记密码"
|
||||
title="请输入密码"
|
||||
>
|
||||
<view class="slot-content row-center" style="padding: 40rpx">
|
||||
<u-icon class="icon-close" name="close" @click="showInputPwd"></u-icon>
|
||||
<u-input
|
||||
type="password"
|
||||
v-model="pwd"
|
||||
:border="true"
|
||||
placeholder="请输入您的转账密码"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</view>
|
||||
</u-modal>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { hasPayPassword, setPassword } from '@/api/user'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'set-pay-pwd',
|
||||
data() {
|
||||
return {
|
||||
showSetPwd: false,
|
||||
showConfirm: false,
|
||||
pwd: '',
|
||||
setPwd: '',
|
||||
comfirmPwd: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSetPwd() {
|
||||
let { setPwd, comfirmPwd } = this
|
||||
this.$refs.uModalSet.clearLoading()
|
||||
if (!setPwd) {
|
||||
this.$toast({
|
||||
title: '请填写转账密码'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!comfirmPwd) {
|
||||
this.$toast({
|
||||
title: '请填写确认密码'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (setPwd != comfirmPwd) {
|
||||
this.$toast({
|
||||
title: '两次密码输入不一致'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (setPwd.length < 4 || setPwd.length > 8) {
|
||||
this.$toast({
|
||||
title: '请输入长度为4-8位的密码'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let data = {
|
||||
pay_password: setPwd
|
||||
}
|
||||
|
||||
setPassword(data).then((res) => {
|
||||
if (res.code == 1) {
|
||||
this.$toast({
|
||||
title: res.msg
|
||||
})
|
||||
this.showSetPwd = false
|
||||
}
|
||||
})
|
||||
},
|
||||
goBack() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
toSetting() {
|
||||
uni.navigateTo({
|
||||
url: '/bundle/pages/user_profile/user_profile'
|
||||
})
|
||||
this.$refs.uModal.clearLoading()
|
||||
},
|
||||
hasPayWord() {
|
||||
console.log(11)
|
||||
|
||||
hasPayPassword().then((res) => {
|
||||
console.log(res)
|
||||
if (res.code == 0) {
|
||||
this.showSetPwd = true
|
||||
}
|
||||
})
|
||||
},
|
||||
onConfirm() {
|
||||
this.$refs.uModalInput.clearLoading()
|
||||
this.$emit('confirm', this.pwd)
|
||||
},
|
||||
showInputPwd() {
|
||||
setTimeout(() => {
|
||||
this.pwd = ''
|
||||
}, 1000)
|
||||
this.showConfirm = !this.showConfirm
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo']),
|
||||
mobile() {
|
||||
if (this.userInfo.mobile) {
|
||||
return this.userInfo.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||
}
|
||||
},
|
||||
showTips() {
|
||||
console.log(this.userInfo)
|
||||
return Boolean(!this.userInfo.mobile)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-close {
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
right: 30rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<view>
|
||||
<l-painter
|
||||
css="width: 640rpx; padding-bottom: 35rpx; background-color: #ffffff; box-shadow: 0 20rpx 58rpx rgba(0,0,0,.15);border-radius: 10rpx;"
|
||||
isCanvasToTempFilePath @success="handleSuccess" @fail="handleFail" custom-style="position: fixed; left: 200%">
|
||||
<l-painter-view css="padding-left: 40rpx;">
|
||||
<l-painter-image :src="config.avatar"
|
||||
css="margin-top: 15rpx; width: 72rpx; height: 72rpx; border-radius: 50%;" />
|
||||
<l-painter-view css="margin-top: 30rpx; padding-left: 20rpx; display: inline-block">
|
||||
<l-painter-text :text="`来自${config.nickname}的分享`"
|
||||
css="display: block; padding-bottom: 10rpx; color: #333333; font-size: 28rpx;line-clamp:1;width: 100%;" />
|
||||
</l-painter-view>
|
||||
<l-painter-image :src="config.image"
|
||||
css="object-fit: cover; object-position: center; width: 560rpx; height: 560rpx;margin-top: 15rpx;" />
|
||||
|
||||
<l-painter-view css="margin-top: 30rpx;">
|
||||
<!-- 商品分享海报文字内容 -->
|
||||
<l-painter-view :css="`display: ${type == 1 ? 'inline-block' : 'none'}; width: 400rpx;`">
|
||||
<l-painter-view
|
||||
:css="`vertical-align: bottom; color: #FF2C3C; font-size: 30rpx; line-height: 1em;`">
|
||||
<l-painter-text text="¥" css="vertical-align: bottom;font-size: 28rpx;" />
|
||||
<l-painter-text :text="price.prev" css="vertical-align: bottom; font-size: 38rpx;" />
|
||||
<l-painter-text :text="price.next" css="vertical-align: bottom; font-size: 30rpx;" />
|
||||
<l-painter-text :text="marketPrice"
|
||||
css="vertical-align: bottom; padding-left: 10rpx;font-size: 28rpx; font-weight: normal; text-decoration: line-through; color: #999999" />
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top:30rpx;">
|
||||
<l-painter-text
|
||||
css="line-clamp: 2; color: #333333; line-height: 1.5em;font-size: 30rpx; width: 378rpx; padding-right:22rpx; box-sizing: border-box"
|
||||
:text="config.name"></l-painter-text>
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
|
||||
<!-- 砍价分享海报文字内容 -->
|
||||
<l-painter-view :css="`display: ${type == 2 ? 'inline-block' : 'none'}; width: 400rpx;`">
|
||||
<l-painter-view>
|
||||
<l-painter-text
|
||||
:css="`line-clamp: 2; color: #FF2C3C; line-height: 1.5em;font-size: 32rpx; width: 375rpx; padding-right:22rpx; box-sizing: border-box`"
|
||||
:text="bShareTitle"></l-painter-text>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top:8rpx;">
|
||||
<l-painter-text
|
||||
css="line-clamp: 2; color: #F95F2F; line-height: 1.5em;font-size: 24rpx; width: 378rpx; padding-right:22rpx; box-sizing: border-box"
|
||||
:text="bShareIntro"></l-painter-text>
|
||||
</l-painter-view>
|
||||
<l-painter-view css="margin-top:8rpx;">
|
||||
<l-painter-text
|
||||
css="line-clamp: 2; color: #333333; line-height: 1.5em;font-size: 28rpx; width: 378rpx; padding-right:22rpx; box-sizing: border-box"
|
||||
:text="config.name"></l-painter-text>
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
|
||||
<l-painter-view css="display: inline-block;">
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<l-painter-qrcode css="width: 168rpx; height: 168rpx;" :text="link">
|
||||
</l-painter-qrcode>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<l-painter-image :src="qrcode" css="width: 168rpx; height: 168rpx;" />
|
||||
<!-- #endif -->
|
||||
<l-painter-text text="长按识别二维码"
|
||||
css="display: block; padding-top: 10rpx; color: #999999;font-size: 24rpx;" />
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter-view>
|
||||
</l-painter>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import lPainter from '@/components/lime-painter/components/l-painter/l-painter.vue'
|
||||
import lPainterImage from '@/components/lime-painter/components/l-painter-image/l-painter-image.vue'
|
||||
import lPainterText from '@/components/lime-painter/components/l-painter-text/l-painter-text.vue'
|
||||
import lPainterView from '@/components/lime-painter/components/l-painter-view/l-painter-view.vue'
|
||||
import lPainterQrcode from '@/components/lime-painter/components/l-painter-qrcode/l-painter-qrcode.vue'
|
||||
export default {
|
||||
name: "share-poster",
|
||||
components: {
|
||||
lPainter,
|
||||
lPainterImage,
|
||||
lPainterText,
|
||||
lPainterView,
|
||||
lPainterQrcode
|
||||
},
|
||||
props: {
|
||||
config: { // 相关配置
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
shareId: { // 分享id
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
qrcode: { // 二维码数据
|
||||
type: [String],
|
||||
default: ''
|
||||
},
|
||||
// pagePath:{
|
||||
// type: String,
|
||||
// default: ''
|
||||
// },
|
||||
link: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: { // 生成海报需使用 0-会员分享海报 1-商品详情 2-砍价活动
|
||||
type: [String, Number],
|
||||
default: 1,
|
||||
},
|
||||
|
||||
bShareTitle: { // 分享海报标题
|
||||
type: String,
|
||||
default: '我正在参与砍价 还差一步',
|
||||
},
|
||||
bShareIntro: { // 分享海报简介
|
||||
type: String,
|
||||
default: '帮忙砍一刀',
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
price() {
|
||||
let {
|
||||
price
|
||||
} = this.config
|
||||
if (price == undefined) return {}
|
||||
price = String(parseFloat(price)).split('.')
|
||||
return {
|
||||
prev: price[0],
|
||||
next: price[1] ? `.${price[1]}` : ''
|
||||
}
|
||||
},
|
||||
marketPrice() {
|
||||
return `¥${parseFloat(this.config.marketPrice)}`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSuccess(val) {
|
||||
this.$emit('success', val)
|
||||
},
|
||||
handleFail() {
|
||||
this.$emit('fail')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<view class="">
|
||||
<u-popup class="share-popup" v-model="showshare" mode="bottom" border-radius="14" :closeable="true"
|
||||
:safe-area-inset-bottom="true" :mask-close-able="false">
|
||||
<view class="row row-center md bold mt30 mb30">分享至</view>
|
||||
<view class="row row-around share-tab">
|
||||
<view class="column column-center" @tap="getPoster">
|
||||
<image mode="widthFix" class="share-icon" src="/static/images/icon_generate_poster.png"></image>
|
||||
<view class="" style="margin: 15rpx 0;">生成海报</view>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN-->
|
||||
<button open-type="share" class="column column-center" hover-class="none">
|
||||
<image class="share-icon" src="/static/images/icon_wechat.png"></image>
|
||||
<view class="">微信好友</view>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<view oclass="column column-center" @tap="shareWx">
|
||||
<image class="share-icon" src="/static/images/icon_wechat.png"></image>
|
||||
<view class="">微信好友</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="row row-center bg-body cancel xl" @tap="showshare=false">取消</view>
|
||||
</u-popup>
|
||||
<u-popup class="share-poster" v-model="showPoster" mode="center" :closeable="true"
|
||||
:safe-area-inset-bottom="true">
|
||||
<!-- #ifndef H5 -->
|
||||
<image style="width: 640rpx;" mode="widthFix" :src="poster"></image>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<img style="width: 640rpx;" :src="poster" />
|
||||
<!-- #endif -->
|
||||
<button class="row row-center save-btn" size="lg" @tap="savePoster">
|
||||
<!-- #ifndef H5 -->
|
||||
保存图片到相册
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
长按保存图片到相册
|
||||
<!-- #endif -->
|
||||
</button>
|
||||
</u-popup>
|
||||
<!-- #ifdef H5 -->
|
||||
<u-popup :custom-style="{'background': 'none'}" class="share-tips" v-model="showTips" mode="top">
|
||||
<view style="overflow: hidden;">
|
||||
<image src="/static/images/share_arrow.png" class="share-arrow" />
|
||||
<view class="white" style="text-align: center;margin-top: 280rpx;">
|
||||
<view class="bold lg">立即分享给好友吧</view>
|
||||
<view class="sm m-t-10">点击屏幕右上角将本页面分享给好友</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
<!-- #endif -->
|
||||
<poster v-if="enablePoster" :type="type" :share-id="shareId" :config="config"
|
||||
:qrcode="mnpQrcode" :link="getLink" @success="handleSuccess" @fail="handleFail"
|
||||
:b-share-title="bargainShare.share_title" :b-share-intro="bargainShare.share_intro"/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapGetters,
|
||||
} from 'vuex'
|
||||
import {
|
||||
// apiMnpQrCode,
|
||||
getShareMnQrcode
|
||||
} from "@/api/app"
|
||||
import {
|
||||
baseURL,
|
||||
basePath
|
||||
} from '@/config/app'
|
||||
import poster from './poster.vue'
|
||||
// import {TtAppNameEnum} from '@/utils/enum'
|
||||
export default {
|
||||
components: {
|
||||
poster
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean // 是否开启此弹窗
|
||||
},
|
||||
shareId: {
|
||||
type: [String, Number], // 商品id或其他活动id
|
||||
default: ''
|
||||
},
|
||||
config: { // 各种配置
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
pagePath: { // 跳转路径
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: { // 生成海报需使用 0-会员分享海报 1-商品详情 2-砍价活动
|
||||
type: [String, Number],
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
poster: "",
|
||||
enablePoster: false,
|
||||
showPoster: false,
|
||||
showTips: false,
|
||||
mnpQrcode: '',
|
||||
bargainShare: { // 砍价分享title和intro
|
||||
share_title: '',
|
||||
share_intro: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
getLink() {
|
||||
return `${baseURL}${basePath}/${this.pagePath}?id=${this.shareId}&invite_code=${this.$store.getters.inviteCode}`
|
||||
},
|
||||
showshare: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showPoster(val) {
|
||||
if (!val) {
|
||||
this.enablePoster = false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getPoster() {
|
||||
if (!this.isLogin) {
|
||||
return uni.navigateTo({
|
||||
url: '/pages/login/login'
|
||||
});
|
||||
}
|
||||
uni.showLoading({
|
||||
title: '正在生成中'
|
||||
})
|
||||
// #ifdef MP-WEIXIN
|
||||
if (!this.mnpQrcode) {
|
||||
// #ifdef MP-WEIXIN
|
||||
const res = await this.getMnpQrcode()
|
||||
// #endif
|
||||
this.mnpQrcode = res.data.qr_code.replaceAll("\r\n", "")
|
||||
|
||||
if(this.type == 2) {
|
||||
this.bargainShare = res.data.extra
|
||||
}
|
||||
}
|
||||
this.enablePoster = true
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS || H5
|
||||
this.enablePoster = true
|
||||
// #endif
|
||||
},
|
||||
// 获取商品页面二维码数据
|
||||
getMnpQrcode() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getShareMnQrcode({
|
||||
id: this.shareId, // 商品id或其他活动id
|
||||
url: this.pagePath, // 跳转页面路径
|
||||
type: this.type, // 0-会员分享海报 1-商品详情 2-砍价活动
|
||||
}).then((res) => {
|
||||
console.log('shareRes', res)
|
||||
resolve(res)
|
||||
}).catch(() => {
|
||||
reject()
|
||||
})
|
||||
})
|
||||
// return new Promise((resolve, reject) => {
|
||||
// resolve({qr_code: 'https://likeshop.yixiangonline.com/uploads/images/background/20201209/17ca8666f3122f0ea83801a7c333b58f.png'})
|
||||
// })
|
||||
},
|
||||
|
||||
handleSuccess(val) {
|
||||
this.poster = val
|
||||
uni.hideLoading()
|
||||
this.showPoster = true
|
||||
this.showshare = false
|
||||
},
|
||||
handleFail() {
|
||||
uni.hideLoading({
|
||||
success: () => {
|
||||
this.$toast({
|
||||
title: '生成失败'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
shareWx() {
|
||||
// #ifdef H5
|
||||
this.showTips = true
|
||||
this.showshare = false
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
uni.share({
|
||||
provider: "weixin",
|
||||
scene: "WXSceneSession",
|
||||
type: 0,
|
||||
href: this.getLink,
|
||||
title: this.config.name,
|
||||
summary: '',
|
||||
imageUrl: this.config.image,
|
||||
success: (res) => {
|
||||
console.log('分享成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
this.$toast({
|
||||
title: err.errMsg
|
||||
})
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
|
||||
async savePoster() {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: this.poster,
|
||||
success: res => {
|
||||
this.showPoster = false
|
||||
this.$toast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
this.$toast({
|
||||
title: '保存失败'
|
||||
});
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.share-popup {
|
||||
.share-tab {
|
||||
margin: 40rpx 0 140rpx;
|
||||
|
||||
.share-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel {
|
||||
height: 98rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.share-poster {
|
||||
.share-img {
|
||||
width: 640rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
// @include background_color();
|
||||
background-color: $-color-primary;
|
||||
color: #fff;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.share-tips .share-arrow {
|
||||
width: 140rpx;
|
||||
height: 250rpx;
|
||||
float: right;
|
||||
margin: 15rpx 31rpx 0 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,493 @@
|
||||
<template>
|
||||
<u-popup
|
||||
v-model="showPop"
|
||||
mode="bottom"
|
||||
border-radius="14"
|
||||
:closeable="true"
|
||||
@close="onClose"
|
||||
:safe-area-inset-bottom="true"
|
||||
>
|
||||
<view class="bg-white spec-contain">
|
||||
<view class="spec-header row">
|
||||
<custom-image
|
||||
class="goods-img mr20"
|
||||
radius="10rpx"
|
||||
@tap="previewImage(checkedGoods.image)"
|
||||
:src="checkedGoods.image"
|
||||
></custom-image>
|
||||
<view class="goods-info" v-if="!isBargain">
|
||||
<view class="primary row">
|
||||
<price-format
|
||||
:first-size="46"
|
||||
:second-size="32"
|
||||
:subscript-size="32"
|
||||
:price="group ? checkedGoods.team_price : checkedGoods.price"
|
||||
:weight="500"
|
||||
></price-format>
|
||||
<view
|
||||
class="vip-price row"
|
||||
v-if="!group && !isSeckill && checkedGoods.member_price"
|
||||
>
|
||||
<view class="price-name xxs">会员价</view>
|
||||
<view style="padding: 0 11rpx">
|
||||
<price-format
|
||||
:price="checkedGoods.member_price"
|
||||
:first-size="22"
|
||||
:second-size="22"
|
||||
:subscript-size="22"
|
||||
:weight="500"
|
||||
color="#7B3200"
|
||||
></price-format>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="sm" v-show="showStock">
|
||||
库存:{{ checkedGoods.stock }}件
|
||||
</view>
|
||||
<view class="sm">
|
||||
<!-- <text>已选择:{{checkedGoods.spec_value_str}},{{goodsNum}}件</text> -->
|
||||
<text>{{ specValueText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-info" v-else>
|
||||
<view class="xs">
|
||||
最低可砍至<text class="sm" style="color: #f95f2f"
|
||||
>¥{{ checkedGoods.activity_price }}</text
|
||||
>
|
||||
</view>
|
||||
<view class="muted xs mt10"> 原价 ¥{{ checkedGoods.price }} </view>
|
||||
<view class="sm lighter mt20">
|
||||
<!-- <text>已选择:{{checkedGoods.spec_value_str}}</text> -->
|
||||
<text>{{ specValueText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="spec-main">
|
||||
<scroll-view style="max-height: 600rpx" scroll-y="true">
|
||||
<view class="spec-list">
|
||||
<view v-for="(item, index) in specList" :key="index" class="spec">
|
||||
<view class="name mb20">
|
||||
{{ item.name }}
|
||||
<text class="primary xs ml20">{{
|
||||
checkedGoods.spec_value_ids_arr[index] == ""
|
||||
? "请选择" + item.name
|
||||
: ""
|
||||
}}</text>
|
||||
</view>
|
||||
<view class="row wrap">
|
||||
<!-- <view v-for="(specitem, index2) in item.spec_value" :key="index2" :class="'spec-item sm ' + ( specitem.checked ? 'checked' : '' )"
|
||||
@tap="choseSpecItem(item.id, specitem.id)">
|
||||
{{ specitem.value }}
|
||||
</view> -->
|
||||
<view
|
||||
v-for="(specitem, index2) in item.spec_value"
|
||||
:key="index2"
|
||||
:class="
|
||||
'spec-item sm ' +
|
||||
(checkedGoods.spec_value_ids_arr[index] == specitem.id
|
||||
? 'checked'
|
||||
: '') +
|
||||
(isDisable(specitem.id) ? 'disabled' : '')
|
||||
"
|
||||
@click="choseSpecItem(index, index2)"
|
||||
>
|
||||
{{ specitem.value }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="good-num row-between ml20 mr20">
|
||||
<view class="label">数量</view>
|
||||
<u-number-box
|
||||
v-model="goodsNum"
|
||||
:min="1"
|
||||
:max="checkedGoods.stock"
|
||||
:disabled="disabledNumberBox"
|
||||
></u-number-box>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="btns row-between bg-white"
|
||||
:class="
|
||||
specValueText.indexOf('请选择') != -1 || checkedGoods.stock == 0
|
||||
? 'disabled'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<button
|
||||
v-if="showAdd"
|
||||
class="add-cart br60 white btn"
|
||||
size="lg"
|
||||
@tap="onClick('addcart')"
|
||||
>
|
||||
{{ yellowBtnText }}
|
||||
</button>
|
||||
<button
|
||||
v-if="showBuy"
|
||||
class="bg-primary br60 white btn"
|
||||
size="lg"
|
||||
@tap="onClick('buynow')"
|
||||
>
|
||||
{{ redBtnText }}
|
||||
</button>
|
||||
<button
|
||||
v-if="showConfirm"
|
||||
class="bg-primary br60 white btn"
|
||||
size="lg"
|
||||
@tap="onClick('confirm')"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
checkedGoods: {
|
||||
stock: 0,
|
||||
}, //选中的
|
||||
outOfStock: [], //缺货的
|
||||
specList: [], //规格
|
||||
disable: [], //不可选择的
|
||||
goodsNum: 1,
|
||||
showPop: false,
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
},
|
||||
goods: {
|
||||
type: Object,
|
||||
},
|
||||
showAdd: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showBuy: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showConfirm: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
redBtnText: {
|
||||
type: String,
|
||||
default: "立即购买",
|
||||
},
|
||||
yellowBtnText: {
|
||||
type: String,
|
||||
default: "加入购物车",
|
||||
},
|
||||
group: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isSeckill: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showStock: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabledNumberBox: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isBargain: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
goodsType: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
|
||||
computed: {
|
||||
// 选择的规格参数等
|
||||
specValueText() {
|
||||
let arr = this.checkedGoods.spec_value_ids?.split(",");
|
||||
let spec_str = "";
|
||||
if (arr)
|
||||
arr.forEach((item, index) => {
|
||||
if (item == "") spec_str += this.specList[index].name + ",";
|
||||
});
|
||||
if (this.checkedGoods?.stock != 0 && spec_str == "")
|
||||
return `已选择 ${this.checkedGoods.spec_value_str} ${this.goodsNum} 件`;
|
||||
else return `请选择 ${spec_str.slice(0, spec_str.length - 1)}`;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
goods(value) {
|
||||
this.specList = value.goods_spec || [];
|
||||
let goodsItem = value.goods_item || [];
|
||||
this.outOfStock = goodsItem.filter((item) => item.stock == 0);
|
||||
// 找出库存不为0的
|
||||
const resultArr = goodsItem.filter((item) => item.stock != 0);
|
||||
if (resultArr.length != 0) {
|
||||
resultArr[0].spec_value_ids_arr =
|
||||
resultArr[0].spec_value_ids.split(",");
|
||||
this.checkedGoods = resultArr[0];
|
||||
} else {
|
||||
// 无法选择
|
||||
goodsItem[0].spec_value_ids_arr = [];
|
||||
|
||||
this.disable = goodsItem.map((item) => item.spec_value_ids.split(","));
|
||||
this.checkedGoods = goodsItem[0];
|
||||
}
|
||||
},
|
||||
|
||||
specList(value) {
|
||||
if (this.checkedGoods.stock == 0) return;
|
||||
|
||||
const res = this.goods.goods_item.filter((item) => {
|
||||
return this.checkedGoods.spec_value_ids === item.spec_value_ids;
|
||||
});
|
||||
|
||||
// 库存为0的规格
|
||||
const idsArr = this.checkedGoods.spec_value_ids_arr;
|
||||
const outOfStock = this.outOfStock;
|
||||
// 找出规格相同和规格不相同的余数
|
||||
const getArrGather = this.getArrResult(idsArr, outOfStock);
|
||||
// 计算出缺货的规格项
|
||||
this.disable = this.getOutOfStockArr(getArrGather, idsArr);
|
||||
|
||||
if (res.length != 0) {
|
||||
console.log(res, "-----");
|
||||
|
||||
let result = JSON.parse(JSON.stringify(res[0]));
|
||||
result.spec_value_ids_arr = result.spec_value_ids.split(",");
|
||||
if (this.goodsNum > result.stock) {
|
||||
this.goodsNum = result.stock;
|
||||
}
|
||||
this.checkedGoods = result;
|
||||
// 同步到父组件
|
||||
this.$emit("change", {
|
||||
detail: this.checkedGoods,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
show(val) {
|
||||
this.showPop = val;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
console.log("spec");
|
||||
},
|
||||
methods: {
|
||||
isDisable(e) {
|
||||
const res = this.disable.filter((item) => item == e);
|
||||
if (res.length != 0) return true;
|
||||
else return false;
|
||||
},
|
||||
|
||||
onClose() {
|
||||
this.$emit("close");
|
||||
},
|
||||
|
||||
onClick(type) {
|
||||
let { checkedGoods, goodsNum } = this;
|
||||
if (this.specValueText.indexOf("请选择") != -1)
|
||||
return this.$toast({
|
||||
title: this.specValueText,
|
||||
});
|
||||
if (checkedGoods.stock == 0)
|
||||
return this.$toast({
|
||||
title: "当前选择库存不足",
|
||||
});
|
||||
checkedGoods.goodsNum = goodsNum;
|
||||
this.$emit(type, {
|
||||
detail: checkedGoods,
|
||||
});
|
||||
},
|
||||
|
||||
// 选择规格
|
||||
choseSpecItem(index, index2) {
|
||||
const id = this.specList[index].spec_value[index2].id;
|
||||
|
||||
// 无法选择
|
||||
const disable = this.disable.filter((item) => item == id);
|
||||
if (disable.length != 0) return;
|
||||
|
||||
let idsArr = this.checkedGoods.spec_value_ids_arr;
|
||||
if (id == idsArr[index]) idsArr[index] = "";
|
||||
else idsArr[index] = id;
|
||||
//保存已选规格
|
||||
this.checkedGoods.spec_value_ids_arr = idsArr;
|
||||
this.checkedGoods.spec_value_ids = idsArr.join(",");
|
||||
// 重新渲染页面
|
||||
this.specList = [...this.specList];
|
||||
},
|
||||
|
||||
// 过滤出需要进行禁用的规格
|
||||
getOutOfStockArr(arr, arr1, result = []) {
|
||||
arr.forEach((el) => {
|
||||
if (el.num >= arr1.length - 1) {
|
||||
result.push(...el.different);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
// 匹配出缺货库存和已选中对比结果
|
||||
getArrIdentical(arr1, arr2, arr = [], num = 0) {
|
||||
arr1.forEach((el) => {
|
||||
arr2.forEach((el2) => {
|
||||
if (el == el2) {
|
||||
num += 1;
|
||||
arr.push(el);
|
||||
}
|
||||
});
|
||||
});
|
||||
return {
|
||||
num, //n个相同的
|
||||
different: this.getArrDifference(
|
||||
[...new Set(arr)].map(Number),
|
||||
arr2.map(Number)
|
||||
),
|
||||
identical: [...new Set(arr)],
|
||||
};
|
||||
},
|
||||
|
||||
// 匹配出已选择和缺库存的
|
||||
getArrResult(arr, outOfStock, result = []) {
|
||||
outOfStock.forEach((item) => {
|
||||
const res = this.getArrIdentical(arr, item.spec_value_ids.split(","));
|
||||
if (res != undefined && res.length != 0) {
|
||||
result.push(res);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
// 找出两个数组中参数不同的
|
||||
getArrDifference(arr1, arr2) {
|
||||
return arr1.concat(arr2).filter(function (v, i, arr) {
|
||||
return arr.indexOf(v) == arr.lastIndexOf(v);
|
||||
});
|
||||
},
|
||||
|
||||
// 查看商品图片
|
||||
previewImage(current) {
|
||||
uni.previewImage({
|
||||
current,
|
||||
urls: [current], // 需要预览的图片http链接列表
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.spec-contain {
|
||||
border-radius: 10rpx 10rpx 0 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 10rpx;
|
||||
top: 10rpx;
|
||||
}
|
||||
|
||||
.spec-header {
|
||||
padding: 30rpx 20rpx;
|
||||
padding-right: 70rpx;
|
||||
align-items: flex-start;
|
||||
border: $-solid-border;
|
||||
.vip-price {
|
||||
margin: 0 24rpx;
|
||||
background-color: #ffe9ba;
|
||||
line-height: 36rpx;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
.price-name {
|
||||
background-color: #101010;
|
||||
padding: 3rpx 12rpx;
|
||||
color: #ffd4b7;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
position: absolute;
|
||||
right: -15rpx;
|
||||
background-color: #ffe9ba;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
.goods-img {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
.spec-main {
|
||||
.spec-list {
|
||||
padding: 30rpx 20rpx;
|
||||
|
||||
.spec-item {
|
||||
line-height: 52rpx;
|
||||
padding: 0 20rpx;
|
||||
background-color: #f4f4f4;
|
||||
border-radius: 60rpx;
|
||||
margin: 0 20rpx 20rpx 0;
|
||||
border: 1px solid #f4f4f4;
|
||||
|
||||
// &.checked {
|
||||
// background-color: $-color-primary;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
&.checked {
|
||||
background-color: #ffe9eb;
|
||||
color: $-color-primary;
|
||||
border-color: $-color-primary;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部按钮无法选择
|
||||
.disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.btns {
|
||||
height: 120rpx;
|
||||
padding: 0 30rpx;
|
||||
margin-top: 80rpx;
|
||||
|
||||
.add-cart {
|
||||
background-color: #ff9e1e;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 0 10rpx;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<view class="spread-order">
|
||||
<view class="content">
|
||||
<view class="order-container">
|
||||
<view v-for="(item, index) in lists" :key="item.order_sn" class="order-item bg-white mt20">
|
||||
<view class="order-header row-between">
|
||||
<view>订单编号:{{item.order_sn}}</view>
|
||||
<view class="white guide-shop-btn row-center">{{item.status}}</view>
|
||||
</view>
|
||||
<view class="order-content row">
|
||||
<view class="order-goods-img">
|
||||
<u-image width="100%" height="100%" radius="6px" :src="item.image" />
|
||||
</view>
|
||||
<view class="order-goods-info ml20">
|
||||
<view class="name row sm">{{item.goods_name}}</view>
|
||||
<view class="row-between">
|
||||
<view class="xs">数量<text class="normal nr">{{item.goods_num}}</text></view>
|
||||
<view class="xs">
|
||||
付款金额
|
||||
<price-format showSubscript :subScriptSize="28" :firstSize="28" :secondSize="28" weight="bold" :price="item.pay_price" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="pre-income muted">预估收益
|
||||
<price-format showSubscript :subScriptSize="28" :firstSize="28" :secondSize="28" color="#FF2C3C" weight="bold" :price="item.money" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="order-footer row-between">
|
||||
<view class="time muted sm">{{item.create_time}}</view>
|
||||
<view class="static sm" :style="{color: item.status == '待返佣' ? '#FF2C3C' : item.status == '已失效' ? '#909090' : '#00c735'}">{{item.status}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<loading-footer slotEmpty :status="loadingStatus">
|
||||
<view slot="empty" class="data-null column-center">
|
||||
<image class="img-null" src="/static/images/news_null.png"></image>
|
||||
<text class="sm muted">暂无其他评价~</text>
|
||||
</view>
|
||||
</loading-footer>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeshop开源商城系统
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee
|
||||
// | github下载:https://github.com/likeshop-github
|
||||
// | 访问官网:https://www.likeshop.cn
|
||||
// | 访问社区:https://home.likeshop.cn
|
||||
// | 访问手册:http://doc.likeshop.cn
|
||||
// | 微信公众号:likeshop技术社区
|
||||
// | likeshop系列产品在gitee、github等公开渠道开源版本可免费商用,未经许可不能去除前后端官方版权标识
|
||||
// | likeshop系列产品收费版本务必购买商业授权,购买去版权授权后,方可去除前后端官方版权标识
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布
|
||||
// | likeshop团队版权所有并拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeshop.cn.team
|
||||
// +----------------------------------------------------------------------
|
||||
import { userOrderPromoteOrder } from "@/utils/type";
|
||||
import { loadingType } from "@/utils/type";
|
||||
import { getPromoteOrder } from "@/api/user";
|
||||
import {loadingFun} from '@/utils/tools'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
lists: [],
|
||||
page: 1,
|
||||
loadingStatus: loadingType.LOADING,
|
||||
confirmDialog: false
|
||||
};
|
||||
},
|
||||
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: Number | String,
|
||||
default: userOrderPromoteOrder.ALL
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$on('RESET_LIST', this.reflesh, this);
|
||||
},
|
||||
|
||||
beforeMount() {
|
||||
this.getPromoteOrderFun(); // this.$getAfterSaleList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
reflesh() {
|
||||
this.page = 1;
|
||||
this.lists = [];
|
||||
this.loadingStatus = loadingType.LOADING; // this.$getAfterSaleList();
|
||||
},
|
||||
|
||||
getPromoteOrderFun() {
|
||||
let {
|
||||
loadingStatus,
|
||||
lists,
|
||||
page
|
||||
} = this;
|
||||
loadingFun(getPromoteOrder, page, lists, loadingStatus, {status: this.type}).then(res => {
|
||||
if(res) {
|
||||
this.page = res.page;
|
||||
this.lists = res.dataList
|
||||
this.loadingStatus = res.status
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.spread-order {
|
||||
.spread-header {
|
||||
height: 240rpx;
|
||||
background-color: var(--primary-color);
|
||||
padding-top: 60rpx;
|
||||
.deal-num {
|
||||
flex: 1;
|
||||
align-self: flex-start;
|
||||
.num {
|
||||
font-size: 42rpx;
|
||||
}
|
||||
.explain {
|
||||
line-height: 34rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
}
|
||||
.income-num {
|
||||
flex: 1;
|
||||
align-self: flex-start;
|
||||
.explain {
|
||||
line-height: 34rpx;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding: 0 20rpx;
|
||||
/* margin-top: -120rpx; */
|
||||
.order-container {
|
||||
.order-item {
|
||||
border-radius: 14rpx;
|
||||
.order-header {
|
||||
padding: 20rpx 30rpx;
|
||||
border-bottom: var(--border);
|
||||
.status {
|
||||
/* background: linear-gradient(80deg, #F95F2F 0%, #FF2C3C 100%);
|
||||
border-radius: 4rpx;
|
||||
width: 134rpx;
|
||||
height: 42rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 34rpx; */
|
||||
color: #F95F2F;
|
||||
}
|
||||
}
|
||||
.order-content {
|
||||
padding: 20rpx 30rpx 20rpx 20rpx;
|
||||
border-bottom: var(--border);
|
||||
.order-goods-img {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
flex: none;
|
||||
}
|
||||
.order-goods-info {
|
||||
text-align: left;
|
||||
flex: 1;
|
||||
.name {
|
||||
line-height: 36rpx;
|
||||
}
|
||||
.pre-income {
|
||||
line-height: 34rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.order-footer {
|
||||
padding: 20rpx 30rpx 20rpx 20rpx;
|
||||
.static {
|
||||
color: #F95F2F;
|
||||
}
|
||||
.wait-return {
|
||||
color: #F95F2F;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.data-null {
|
||||
padding-top: 200rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<view class="swiper-wrap" :style="{height, padding}" v-if="lists.length">
|
||||
<view class="swiper-con" :style="{borderRadius: radius}">
|
||||
<template v-if="isSwiper">
|
||||
<swiper class="swiper" :autoplay="autoplay" :circular="circular" @change="swiperChange"
|
||||
:previous-margin="previousMargin" display-multiple-items="1">
|
||||
<swiper-item v-for="(item, index) in lists" :key="index">
|
||||
<view :data-item="item" style="width:100%;height:100%;" @click="goPage(item)">
|
||||
<u-image mode="aspectFill" :width="'calc(100% - ' + previousMargin + ')'" height="100%"
|
||||
:border-radius="radius" :src="item.image"></u-image>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="dots" v-if="lists.length > 1">
|
||||
<view v-for="(item, index) in lists" :key="index"
|
||||
:class="'dot ' + (index == currentSwiper ? 'active' : '')"></view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-lese v-for="(item, index) in lists" >
|
||||
<view :key="index" :data-item="item" style="width:100%;height:100%;" @click="goPage(item)" v-if="index < 1">
|
||||
<u-image mode="aspectFill" :width="'calc(100% - ' + previousMargin + ')'" height="100%"
|
||||
:border-radius="radius" :src="item.image"></u-image>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getAdList
|
||||
} from '@/api/store';
|
||||
import {
|
||||
paramsToStr
|
||||
} from '@/utils/tools'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
lists: [],
|
||||
currentSwiper: 0
|
||||
};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
pid: {
|
||||
type: Number
|
||||
},
|
||||
circular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
height: {
|
||||
type: String
|
||||
},
|
||||
radius: {
|
||||
type: String,
|
||||
default: '10rpx'
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '0rpx'
|
||||
},
|
||||
previousMargin: {
|
||||
type: String,
|
||||
default: "0rpx"
|
||||
},
|
||||
isSwiper: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getAdListFun();
|
||||
},
|
||||
watch: {
|
||||
'pid': function(value) {
|
||||
this.getAdListFun();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getAdListFun() {
|
||||
const {
|
||||
code,
|
||||
data
|
||||
} = await getAdList({
|
||||
pid: this.pid,
|
||||
client: 1
|
||||
})
|
||||
if (code == 1) {
|
||||
this.lists = data
|
||||
}
|
||||
},
|
||||
|
||||
swiperChange(e) {
|
||||
this.currentSwiper = e.detail.current
|
||||
},
|
||||
goPage(item) {
|
||||
let {
|
||||
link,
|
||||
link_type,
|
||||
params,
|
||||
is_tab
|
||||
} = item;
|
||||
switch (link_type) {
|
||||
case 1:
|
||||
""
|
||||
case 2:
|
||||
if (is_tab) {
|
||||
uni.switchTab({
|
||||
url: link
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: link + paramsToStr(params)
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
uni.navigateTo({
|
||||
url: '/pages/webview/webview?url=' + link
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.swiper-wrap {
|
||||
overflow: hidden;
|
||||
box-sizing: content-box;
|
||||
|
||||
.swiper-con {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.swiper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.slide-image {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.dots {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 20rpx;
|
||||
display: flex;
|
||||
|
||||
.dot {
|
||||
width: 8rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 10rpx;
|
||||
background-color: #fff;
|
||||
|
||||
&.active {
|
||||
width: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color: $-color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<view :class="{ active, inactive: !active, tab: true }" :style="shouldShow ? '' : 'display: none;'">
|
||||
<slot v-if="shouldRender"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
dot: {
|
||||
type: Boolean,
|
||||
},
|
||||
info: {
|
||||
type: null,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
titleStyle: {
|
||||
type: String,
|
||||
},
|
||||
name: {
|
||||
type: [Number, String],
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
inject: ['tabs'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
shouldShow: false,
|
||||
shouldRender: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.tabs.childrens.push(this)
|
||||
},
|
||||
mounted() {
|
||||
this.update()
|
||||
},
|
||||
methods: {
|
||||
getComputedName: function() {
|
||||
if (this.data.name !== '') {
|
||||
return this.data.name;
|
||||
}
|
||||
|
||||
return this.index;
|
||||
},
|
||||
updateRender: function(active, parent) {
|
||||
|
||||
let parentData = parent;
|
||||
this.inited = this.inited || active;
|
||||
this.active = active
|
||||
this.shouldRender = this.inited
|
||||
this.shouldShow = active
|
||||
|
||||
},
|
||||
update: function() {
|
||||
if (this.tabs) {
|
||||
this.tabs.updateTabs();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
changeData() {
|
||||
const {
|
||||
dot,
|
||||
info,
|
||||
title,
|
||||
titleStyle
|
||||
} = this
|
||||
return {
|
||||
dot,
|
||||
info,
|
||||
title,
|
||||
titleStyle
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
changeData(value) {
|
||||
this.update()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tab.active {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.tab.inactive {
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<view class="_tab-box" :style="{fontSize: defaultConfig.fontSize + 'rpx', color: defaultConfig.color,}">
|
||||
<scroll-view id="_scroll" :scroll-x="true" class="scroll-view-h" scroll-with-animation :scroll-left="slider.scrollLeft" :style="{ backgroundColor: defaultConfig.bgColor}">
|
||||
<view class="_scroll-content">
|
||||
<view class="_tab-item-box" :class="[defaultConfig.itemWidth ? '_clamp' : '_flex']">
|
||||
<block v-for="(item, index) in tabList" :key="index">
|
||||
<view class="_item" :id="'_tab_'+index" :class="{ '_active': tagIndex === index }" :style="{color: tagIndex == index ? defaultConfig.activeColor : defaultConfig.color, 'width': defaultConfig.itemWidth ? defaultConfig.itemWidth + 'rpx' : ''}"
|
||||
@click="tabClick(index)">{{ item.title }}</view>
|
||||
</block>
|
||||
</view>
|
||||
<view class="_underline" :style="{
|
||||
transform: 'translateX(' + slider.left + 'px)',
|
||||
width: slider.width + 'px',
|
||||
height: defaultConfig.underLineHeight + 'rpx',
|
||||
backgroundColor: defaultConfig.underLineColor,
|
||||
}" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="tab-content">
|
||||
<view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tabs',
|
||||
props: {
|
||||
active: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
tabs: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabList: [],
|
||||
tagIndex: 0,
|
||||
slider: {
|
||||
left: 0,
|
||||
width: 0,
|
||||
scrollLeft: 0
|
||||
},
|
||||
scorll: {},
|
||||
defaultConfig: {
|
||||
bgColor: "#fff",
|
||||
// 要显示的key
|
||||
// 字体大小 rpx
|
||||
fontSize: 26,
|
||||
// 字体颜色
|
||||
color: '#333',
|
||||
// 激活字体颜色
|
||||
activeColor: '#FF2C3C',
|
||||
// item宽度 0为自动
|
||||
itemWidth: 0,
|
||||
// 下划线左右边距,文字宽度加边距 rpx
|
||||
underLinePadding: 10,
|
||||
// 下划线宽度 rpx 注意:设置了此值 underLinePadding 失效
|
||||
underLineWidth: 0,
|
||||
// 下划线高度 rpx
|
||||
underLineHeight: 4,
|
||||
// 下划线颜色
|
||||
underLineColor: '#FF2C3C',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
},
|
||||
mounted() {
|
||||
this.updateTabs();
|
||||
|
||||
},
|
||||
methods: {
|
||||
updateTabs() {
|
||||
this.tabList = this.childrens.map((item) => {
|
||||
const {
|
||||
title,
|
||||
info,
|
||||
name,
|
||||
dot,
|
||||
titleStyle,
|
||||
active,
|
||||
updateRender
|
||||
} = item
|
||||
return {
|
||||
title,
|
||||
info,
|
||||
name,
|
||||
dot,
|
||||
titleStyle,
|
||||
active,
|
||||
updateRender
|
||||
}
|
||||
})
|
||||
this.updateConfig();
|
||||
this.tagIndex = this.active;
|
||||
this.$nextTick(() => {
|
||||
this.calcScrollPosition();
|
||||
})
|
||||
},
|
||||
updateConfig() {
|
||||
this.defaultConfig = Object.assign(this.defaultConfig, this.config);
|
||||
},
|
||||
calcScrollPosition() {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query.select('#_scroll').boundingClientRect((res) => {
|
||||
this.scorll = res;
|
||||
this.updateTabWidth();
|
||||
}).exec();
|
||||
},
|
||||
updateTabWidth(index = 0) {
|
||||
let data = this.tabList;
|
||||
|
||||
if (data.length == 0) return false;
|
||||
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
|
||||
query.select('#_tab_' + index).boundingClientRect((res) => {
|
||||
data[index]._slider = {
|
||||
width: res.width,
|
||||
left: res.left,
|
||||
scrollLeft: res.left - (data[index - 1] ? data[index - 1]._slider.width : 0),
|
||||
};
|
||||
|
||||
if (this.tagIndex == index) {
|
||||
this.tabToIndex(this.tagIndex);
|
||||
}
|
||||
|
||||
index++;
|
||||
if (data.length > index) {
|
||||
this.updateTabWidth(index);
|
||||
}
|
||||
}).exec();
|
||||
},
|
||||
|
||||
tabToIndex(index) {
|
||||
let _slider = this.tabList[index]._slider;
|
||||
let width = uni.upx2px(this.defaultConfig.underLineWidth);
|
||||
|
||||
if (!width) {
|
||||
if (this.defaultConfig.itemWidth) {
|
||||
width = uni.upx2px(this.defaultConfig.itemWidth)/3;
|
||||
} else {
|
||||
width = this.tabList[index]['title'].length * uni.upx2px(this.defaultConfig.fontSize);
|
||||
}
|
||||
width += uni.upx2px(this.defaultConfig.underLinePadding) * 2;
|
||||
}
|
||||
this.childrens.forEach((item, ind) => {
|
||||
let active = ind === index;
|
||||
if (active !== item.active || !item.inited) {
|
||||
item.updateRender(active, this);
|
||||
}
|
||||
});
|
||||
|
||||
let scorll_left = this.scorll.left || 0;
|
||||
this.slider = {
|
||||
left: _slider.left - scorll_left + (_slider.width - width) / 2,
|
||||
width: width,
|
||||
scrollLeft: _slider.scrollLeft - scorll_left,
|
||||
}
|
||||
},
|
||||
|
||||
tabClick(index) {
|
||||
this.tagIndex = index;
|
||||
this.tabToIndex(index);
|
||||
this.$emit('change', index);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
._tab-box {
|
||||
width: 100%;
|
||||
font-size: 26rpx;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
.scroll-view-h {
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
._scroll-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
._tab-item-box {
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
&._flex {
|
||||
display: flex;
|
||||
|
||||
._item {
|
||||
flex: 1;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
}
|
||||
&._clamp {
|
||||
._item {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
._item {
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
color: #333;
|
||||
|
||||
&._active {
|
||||
color: #e54d42;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
._underline {
|
||||
height: 4rpx;
|
||||
background-color: #e54d42;
|
||||
border-radius: 6rpx;
|
||||
transition: transform .5s;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,210 @@
|
||||
<template xlang="wxml" minapp="mpvue">
|
||||
<view class="tki-qrcode">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<canvas class="tki-qrcode-canvas" :canvas-id="cid" :style="{width:cpSize+'px',height:cpSize+'px'}" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<canvas :id="cid" :width="cpSize" :height="cpSize" class="tki-qrcode-canvas" />
|
||||
<!-- #endif -->
|
||||
<image v-show="show" :src="result" :style="{width:cpSize+'px',height:cpSize+'px'}" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QRCode from "./qrcode.js"
|
||||
let qrcode
|
||||
export default {
|
||||
name: "tki-qrcode",
|
||||
props: {
|
||||
cid: {
|
||||
type: String,
|
||||
default: 'tki-qrcode-canvas'
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: 'upx'
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
val: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
foreground: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
pdground: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 40
|
||||
},
|
||||
lv: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
onval: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadMake: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
usingComponents: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showLoading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loadingText: {
|
||||
type: String,
|
||||
default: '二维码生成中'
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_makeCode() {
|
||||
let that = this
|
||||
if (!this._empty(this.val)) {
|
||||
qrcode = new QRCode({
|
||||
context: that, // 上下文环境
|
||||
canvasId:that.cid, // canvas-id
|
||||
usingComponents: that.usingComponents, // 是否是自定义组件
|
||||
showLoading: that.showLoading, // 是否显示loading
|
||||
loadingText: that.loadingText, // loading文字
|
||||
text: that.val, // 生成内容
|
||||
size: that.cpSize, // 二维码大小
|
||||
background: that.background, // 背景色
|
||||
foreground: that.foreground, // 前景色
|
||||
pdground: that.pdground, // 定位角点颜色
|
||||
correctLevel: that.lv, // 容错级别
|
||||
image: that.icon, // 二维码图标
|
||||
imageSize: that.iconSize,// 二维码图标大小
|
||||
cbResult: function (res) { // 生成二维码的回调
|
||||
that._result(res)
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '二维码内容不能为空',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
},
|
||||
_clearCode() {
|
||||
this._result('')
|
||||
qrcode.clear()
|
||||
},
|
||||
_saveCode() {
|
||||
let that = this;
|
||||
if (this.result != "") {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: that.result,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: '二维码保存成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
_result(res) {
|
||||
this.result = res;
|
||||
this.$emit('result', res)
|
||||
},
|
||||
_empty(v) {
|
||||
let tp = typeof v,
|
||||
rt = false;
|
||||
if (tp == "number" && String(v) == "") {
|
||||
rt = true
|
||||
} else if (tp == "undefined") {
|
||||
rt = true
|
||||
} else if (tp == "object") {
|
||||
if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null) rt = true
|
||||
} else if (tp == "string") {
|
||||
if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]") rt = true
|
||||
} else if (tp == "function") {
|
||||
rt = false
|
||||
}
|
||||
return rt
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
size: function (n, o) {
|
||||
if (n != o && !this._empty(n)) {
|
||||
this.cSize = n
|
||||
if (!this._empty(this.val)) {
|
||||
setTimeout(() => {
|
||||
this._makeCode()
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
},
|
||||
val: function (n, o) {
|
||||
if (this.onval) {
|
||||
if (n != o && !this._empty(n)) {
|
||||
setTimeout(() => {
|
||||
this._makeCode()
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cpSize() {
|
||||
if(this.unit == "upx"){
|
||||
return uni.upx2px(this.size)
|
||||
}else{
|
||||
return this.size
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
if (this.loadMake) {
|
||||
if (!this._empty(this.val)) {
|
||||
setTimeout(() => {
|
||||
this._makeCode()
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.tki-qrcode {
|
||||
position: relative;
|
||||
}
|
||||
.tki-qrcode-canvas {
|
||||
position: fixed;
|
||||
top: -99999upx;
|
||||
left: -99999upx;
|
||||
z-index: -99999;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<view :class="'trigonometry ' + (direction === 'up' ? 'up' : '') + ' ' + (size === 'small' ? 'small' : '')" :style="'color:' + color + ';opacity: ' + opacity"></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
|
||||
components: {},
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
direction: {
|
||||
type: String
|
||||
},
|
||||
size: {
|
||||
type: String
|
||||
},
|
||||
opacity: {
|
||||
type: String,
|
||||
default: '0.8'
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.trigonometry {
|
||||
border-color: transparent transparent currentcolor currentcolor;
|
||||
border-style: solid;
|
||||
border-width: 3px;
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
opacity: .8;
|
||||
margin: -3px 10rpx 0;
|
||||
}
|
||||
|
||||
.up {
|
||||
margin-top: 1rpx;
|
||||
-webkit-transform: rotate(135deg);
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
.small {
|
||||
border-width: 2px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.small.up {
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<view class="uploader-container row wrap">
|
||||
<view class="upload-image-box" v-for="(item, index) in fileList" :key="index" :style="{width: previewSize, height: previewSize}">
|
||||
<custom-image mode="aspectFit" class="img-preview" radius="10rpx" :src="item.url" :width="previewSize" :height="previewSize" />
|
||||
<view class="close-icon row-center" @click="deleteImage($event, index)">
|
||||
<u-icon name="close" size="30" color="white" />
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="uplader-upload row-center"
|
||||
:style="{width: previewSize, height: previewSize}"
|
||||
@click="handleImage"
|
||||
v-show="(fileList.length == 0 || mutiple) && fileList.length < maxUpload"
|
||||
v-if="!useSlot"
|
||||
>
|
||||
<u-icon size="48" color="#dcdee0" name="camera" />
|
||||
<view type="image" accept="image/*" class="uploader-input" />
|
||||
</view>
|
||||
<view
|
||||
class="uplader-upload-slot row-center"
|
||||
@click="handleImage"
|
||||
v-show="(fileList.length == 0 || mutiple) && fileList.length < maxUpload"
|
||||
v-else
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "uploader",
|
||||
props: {
|
||||
fileList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 默认不允许多选图片
|
||||
mutiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 限制上传文件数量
|
||||
maxUpload: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
previewSize: {
|
||||
type: String,
|
||||
default: "160rpx"
|
||||
},
|
||||
// 是否可删除
|
||||
deletable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
useSlot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
create() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
handleImage() {
|
||||
uni.chooseImage({
|
||||
count: this.mutiple ? this.maxUpload : 1,
|
||||
success: (res) => {
|
||||
this.$emit("after-read", res.tempFiles)
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteImage(e, index) {
|
||||
this.$emit('delete', index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.uploader-container {
|
||||
.upload-image-box {
|
||||
position: relative;
|
||||
margin-right: 8rpx;
|
||||
margin-bottom: 8rpx;
|
||||
.img-preview {
|
||||
|
||||
}
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
right: -20rpx;
|
||||
top: -15rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background-color: red;
|
||||
border-radius: 50%;
|
||||
z-index: 20;
|
||||
}
|
||||
}
|
||||
.uplader-upload {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background-color: #f7f8fa;
|
||||
.uploader-input {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.uplader-upload-slot {
|
||||
position: relative;
|
||||
min-width: 160rpx;
|
||||
min-height: 160rpx;
|
||||
.uploader-input {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 www.uviewui.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,106 @@
|
||||
<p align="center">
|
||||
<img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
|
||||
</p>
|
||||
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3>
|
||||
<h3 align="center">多平台快速开发的UI框架</h3>
|
||||
|
||||
|
||||
## 说明
|
||||
|
||||
uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
|
||||
|
||||
## 特性
|
||||
|
||||
- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
|
||||
- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
|
||||
- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨
|
||||
- 众多的常用页面和布局,让您专注逻辑,事半功倍
|
||||
- 详尽的文档支持,现代化的演示效果
|
||||
- 按需引入,精简打包体积
|
||||
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# npm方式安装
|
||||
npm i uview-ui
|
||||
```
|
||||
|
||||
## 快速上手
|
||||
|
||||
1. `main.js`引入uView库
|
||||
```js
|
||||
// main.js
|
||||
import uView from 'uview-ui';
|
||||
Vue.use(uView);
|
||||
```
|
||||
|
||||
2. `App.vue`引入基础样式(注意style标签需声明scss属性支持)
|
||||
```css
|
||||
/* App.vue */
|
||||
<style lang="scss">
|
||||
@import "uview-ui/index.scss";
|
||||
</style>
|
||||
```
|
||||
|
||||
3. `uni.scss`引入全局scss变量文件
|
||||
```css
|
||||
/* uni.scss */
|
||||
@import "uview-ui/theme.scss";
|
||||
```
|
||||
|
||||
4. `pages.json`配置easycom规则(按需引入)
|
||||
|
||||
```js
|
||||
// pages.json
|
||||
{
|
||||
"easycom": {
|
||||
// npm安装的方式不需要前面的"@/",下载安装的方式需要"@/"
|
||||
// npm安装方式
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
|
||||
// 下载安装方式
|
||||
// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
|
||||
},
|
||||
// 此为本身已有的内容
|
||||
"pages": [
|
||||
// ......
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
|
||||
|
||||
## 使用方法
|
||||
配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
|
||||
|
||||
```html
|
||||
<template>
|
||||
<u-button>按钮</u-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
|
||||
|
||||
## 链接
|
||||
|
||||
- [官方文档](https://uviewui.com/)
|
||||
- [更新日志](https://uviewui.com/components/changelog.html)
|
||||
- [升级指南](https://uviewui.com/components/changelog.html)
|
||||
- [关于我们](https://uviewui.com/cooperation/about.html)
|
||||
|
||||
## 预览
|
||||
|
||||
您可以通过**微信**扫码,查看最佳的演示效果。
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
|
||||
|
||||
<!-- ## 捐赠uView的研发
|
||||
|
||||
uView文档和源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。
|
||||
|
||||
<img src="https://uviewui.com/common/wechat.png" width="220" >
|
||||
<img style="margin-left: 100px;" src="https://uviewui.com/common/alipay.png" width="220" >
|
||||
-->
|
||||
## 版权信息
|
||||
uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。
|
||||
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble"
|
||||
length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex">
|
||||
<view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]">
|
||||
{{tips.text}}
|
||||
</view>
|
||||
<block v-for="(item, index) in list" :key="index">
|
||||
<view
|
||||
@touchmove.stop.prevent
|
||||
@tap="itemClick(index)"
|
||||
:style="[itemStyle(index)]"
|
||||
class="u-action-sheet-item u-line-1"
|
||||
:class="[index < list.length - 1 ? 'u-border-bottom' : '']"
|
||||
:hover-stay-time="150"
|
||||
>
|
||||
<text>{{item.text}}</text>
|
||||
<text class="u-action-sheet-item__subtext u-line-1" v-if="item.subText">{{item.subText}}</text>
|
||||
</view>
|
||||
</block>
|
||||
<view class="u-gab" v-if="cancelBtn">
|
||||
</view>
|
||||
<view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class"
|
||||
:hover-stay-time="150" v-if="cancelBtn" @tap="close">{{cancelText}}</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* actionSheet 操作菜单
|
||||
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
|
||||
* @tutorial https://www.uviewui.com/components/actionSheet.html
|
||||
* @property {Array<Object>} list 按钮的文字数组,见官方文档示例
|
||||
* @property {Object} tips 顶部的提示文字,见官方文档示例
|
||||
* @property {String} cancel-text 取消按钮的提示文字
|
||||
* @property {Boolean} cancel-btn 是否显示底部的取消按钮(默认true)
|
||||
* @property {Number String} border-radius 弹出部分顶部左右的圆角值,单位rpx(默认0)
|
||||
* @property {Boolean} mask-close-able 点击遮罩是否可以关闭(默认true)
|
||||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
|
||||
* @property {Number String} z-index z-index值(默认1075)
|
||||
* @property {String} cancel-text 取消按钮的提示文字
|
||||
* @event {Function} click 点击ActionSheet列表项时触发
|
||||
* @event {Function} close 点击取消按钮时触发
|
||||
* @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet>
|
||||
*/
|
||||
export default {
|
||||
name: "u-action-sheet",
|
||||
props: {
|
||||
// 点击遮罩是否可以关闭actionsheet
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx
|
||||
list: {
|
||||
type: Array,
|
||||
default () {
|
||||
// 如下
|
||||
// return [{
|
||||
// text: '确定',
|
||||
// color: '',
|
||||
// fontSize: ''
|
||||
// }]
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 顶部的提示文字
|
||||
tips: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
text: '',
|
||||
color: '',
|
||||
fontSize: '26'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 底部的取消按钮
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 通过双向绑定控制组件的弹出与收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹出的顶部圆角值
|
||||
borderRadius: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 弹出的z-index值
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 取消按钮的文字提示
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 顶部提示的样式
|
||||
tipsStyle() {
|
||||
let style = {};
|
||||
if (this.tips.color) style.color = this.tips.color;
|
||||
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx';
|
||||
return style;
|
||||
},
|
||||
// 操作项目的样式
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {};
|
||||
if (this.list[index].color) style.color = this.list[index].color;
|
||||
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx';
|
||||
// 选项被禁用的样式
|
||||
if (this.list[index].disabled) style.color = '#c0c4cc';
|
||||
return style;
|
||||
}
|
||||
},
|
||||
uZIndex() {
|
||||
// 如果用户有传递z-index值,优先使用
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击取消按钮
|
||||
close() {
|
||||
// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
|
||||
// 这是一个vue发送事件的特殊用法
|
||||
this.popupClose();
|
||||
this.$emit('close');
|
||||
},
|
||||
// 弹窗关闭
|
||||
popupClose() {
|
||||
this.$emit('input', false);
|
||||
},
|
||||
// 点击某一个item
|
||||
itemClick(index) {
|
||||
// disabled的项禁止点击
|
||||
if(this.list[index].disabled) return;
|
||||
this.$emit('click', index);
|
||||
this.$emit('input', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-tips {
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
padding: 34rpx 0;
|
||||
line-height: 1;
|
||||
color: $u-tips-color;
|
||||
}
|
||||
|
||||
.u-action-sheet-item {
|
||||
@include vue-flex;;
|
||||
line-height: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32rpx;
|
||||
padding: 34rpx 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.u-action-sheet-item__subtext {
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.u-gab {
|
||||
height: 12rpx;
|
||||
background-color: rgb(234, 234, 236);
|
||||
}
|
||||
|
||||
.u-actionsheet-cancel {
|
||||
color: $u-main-color;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<view class="u-alert-tips" v-if="show" :class="[
|
||||
!show ? 'u-close-alert-tips': '',
|
||||
type ? 'u-alert-tips--bg--' + type + '-light' : '',
|
||||
type ? 'u-alert-tips--border--' + type + '-disabled' : '',
|
||||
]" :style="{
|
||||
backgroundColor: bgColor,
|
||||
borderColor: borderColor
|
||||
}">
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon v-if="showIcon" :name="uIcon" :size="description ? 40 : 32" class="u-icon" :color="uIconType" :custom-style="iconStyle"></u-icon>
|
||||
</view>
|
||||
<view class="u-alert-content" @tap.stop="click">
|
||||
<view class="u-alert-title" :style="[uTitleStyle]">
|
||||
{{title}}
|
||||
</view>
|
||||
<view v-if="description" class="u-alert-desc" :style="[descStyle]">
|
||||
{{description}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon @click="close" v-if="closeAble && !closeText" hoverClass="u-type-error-hover-color" name="close" color="#c0c4cc"
|
||||
:size="22" class="u-close-icon" :style="{
|
||||
top: description ? '18rpx' : '24rpx'
|
||||
}"></u-icon>
|
||||
</view>
|
||||
<text v-if="closeAble && closeText" class="u-close-text" :style="{
|
||||
top: description ? '18rpx' : '24rpx'
|
||||
}">{{closeText}}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* alertTips 警告提示
|
||||
* @description 警告提示,展现需要关注的信息
|
||||
* @tutorial https://uviewui.com/components/alertTips.html
|
||||
* @property {String} title 显示的标题文字
|
||||
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
|
||||
* @property {String} type 关闭按钮(默认为叉号icon图标)
|
||||
* @property {String} icon 图标名称
|
||||
* @property {Object} icon-style 图标的样式,对象形式
|
||||
* @property {Object} title-style 标题的样式,对象形式
|
||||
* @property {Object} desc-style 描述的样式,对象形式
|
||||
* @property {String} close-able 用文字替代关闭图标,close-able为true时有效
|
||||
* @property {Boolean} show-icon 是否显示左边的辅助图标
|
||||
* @property {Boolean} show 显示或隐藏组件
|
||||
* @event {Function} click 点击组件时触发
|
||||
* @event {Function} close 点击关闭按钮时触发
|
||||
*/
|
||||
export default {
|
||||
name: 'u-alert-tips',
|
||||
props: {
|
||||
// 显示文字
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 主题,success/warning/info/error
|
||||
type: {
|
||||
type: String,
|
||||
default: 'warning'
|
||||
},
|
||||
// 辅助性文字
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否可关闭
|
||||
closeAble: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 关闭按钮自定义文本
|
||||
closeText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示图标
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 文字颜色,如果定义了color值,icon会失效
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边显示的icon
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// icon的样式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 标题的样式
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 描述文字的样式
|
||||
descStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uTitleStyle() {
|
||||
let style = {};
|
||||
// 如果有描述文字的话,标题进行加粗
|
||||
style.fontWeight = this.description ? 500 : 'normal';
|
||||
// 将用户传入样式对象和style合并,传入的优先级比style高,同属性会被覆盖
|
||||
return this.$u.deepMerge(style, this.titleStyle);
|
||||
},
|
||||
uIcon() {
|
||||
// 如果有设置icon名称就使用,否则根据type主题,推定一个默认的图标
|
||||
return this.icon ? this.icon : this.$u.type2icon(this.type);
|
||||
},
|
||||
uIconType() {
|
||||
// 如果有设置图标的样式,优先使用,没有的话,则用type的样式
|
||||
return Object.keys(this.iconStyle).length ? '' : this.type;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击内容
|
||||
click() {
|
||||
this.$emit('click');
|
||||
},
|
||||
// 点击关闭按钮
|
||||
close() {
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-alert-tips {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 30rpx;
|
||||
border-radius: 8rpx;
|
||||
position: relative;
|
||||
transition: all 0.3s linear;
|
||||
border: 1px solid #fff;
|
||||
|
||||
&--bg--primary-light {
|
||||
background-color: $u-type-primary-light;
|
||||
}
|
||||
|
||||
&--bg--info-light {
|
||||
background-color: $u-type-info-light;
|
||||
}
|
||||
|
||||
&--bg--success-light {
|
||||
background-color: $u-type-success-light;
|
||||
}
|
||||
|
||||
&--bg--warning-light {
|
||||
background-color: $u-type-warning-light;
|
||||
}
|
||||
|
||||
&--bg--error-light {
|
||||
background-color: $u-type-error-light;
|
||||
}
|
||||
|
||||
&--border--primary-disabled {
|
||||
border-color: $u-type-primary-disabled;
|
||||
}
|
||||
|
||||
&--border--success-disabled {
|
||||
border-color: $u-type-success-disabled;
|
||||
}
|
||||
|
||||
&--border--error-disabled {
|
||||
border-color: $u-type-error-disabled;
|
||||
}
|
||||
|
||||
&--border--warning-disabled {
|
||||
border-color: $u-type-warning-disabled;
|
||||
}
|
||||
|
||||
&--border--info-disabled {
|
||||
border-color: $u-type-info-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
.u-close-alert-tips {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.u-icon {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.u-alert-title {
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
.u-alert-desc {
|
||||
font-size: 26rpx;
|
||||
text-align: left;
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
.u-close-icon {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
}
|
||||
|
||||
.u-close-hover {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.u-close-text {
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }">
|
||||
<canvas
|
||||
class="cropper"
|
||||
:disable-scroll="true"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }"
|
||||
canvas-id="cropper"
|
||||
id="cropper"
|
||||
></canvas>
|
||||
<canvas
|
||||
class="cropper"
|
||||
:disable-scroll="true"
|
||||
:style="{
|
||||
position: 'fixed',
|
||||
top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`,
|
||||
left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`,
|
||||
width: `${cropperOpt.width * cropperOpt.pixelRatio}px`,
|
||||
height: `${cropperOpt.height * cropperOpt.pixelRatio}`
|
||||
}"
|
||||
canvas-id="targetId"
|
||||
id="targetId"
|
||||
></canvas>
|
||||
</view>
|
||||
<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }">
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="upload" @tap="uploadTap">选择图片</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 -->
|
||||
<view class="upload" @tap="uploadTap">重新选择</view>
|
||||
<!-- #endif -->
|
||||
<view class="getCropperImage" @tap="getCropperImage(false)">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WeCropper from './weCropper.js';
|
||||
export default {
|
||||
props: {
|
||||
// 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色,
|
||||
// mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)"
|
||||
boundStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
lineWidth: 4,
|
||||
borderColor: 'rgb(245, 245, 245)',
|
||||
mask: 'rgba(0, 0, 0, 0.35)'
|
||||
};
|
||||
}
|
||||
}
|
||||
// // 裁剪框宽度,单位rpx
|
||||
// rectWidth: {
|
||||
// type: [String, Number],
|
||||
// default: 400
|
||||
// },
|
||||
// // 裁剪框高度,单位rpx
|
||||
// rectHeight: {
|
||||
// type: [String, Number],
|
||||
// default: 400
|
||||
// },
|
||||
// // 输出图片宽度,单位rpx
|
||||
// destWidth: {
|
||||
// type: [String, Number],
|
||||
// default: 400
|
||||
// },
|
||||
// // 输出图片高度,单位rpx
|
||||
// destHeight: {
|
||||
// type: [String, Number],
|
||||
// default: 400
|
||||
// },
|
||||
// // 输出的图片类型,如果发现裁剪的图片很大,可能是因为设置为了"png",改成"jpg"即可
|
||||
// fileType: {
|
||||
// type: String,
|
||||
// default: 'jpg',
|
||||
// },
|
||||
// // 生成的图片质量
|
||||
// // H5上无效,目前不考虑使用此参数
|
||||
// quality: {
|
||||
// type: [Number, String],
|
||||
// default: 1
|
||||
// }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 底部导航的高度
|
||||
bottomNavHeight: 50,
|
||||
originWidth: 200,
|
||||
width: 0,
|
||||
height: 0,
|
||||
cropperOpt: {
|
||||
id: 'cropper',
|
||||
targetId: 'targetCropper',
|
||||
pixelRatio: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
scale: 2.5,
|
||||
zoom: 8,
|
||||
cut: {
|
||||
x: (this.width - this.originWidth) / 2,
|
||||
y: (this.height - this.originWidth) / 2,
|
||||
width: this.originWidth,
|
||||
height: this.originWidth
|
||||
},
|
||||
boundStyle: {
|
||||
lineWidth: uni.upx2px(this.boundStyle.lineWidth),
|
||||
mask: this.boundStyle.mask,
|
||||
color: this.boundStyle.borderColor
|
||||
}
|
||||
},
|
||||
// 裁剪框和输出图片的尺寸,高度默认等于宽度
|
||||
// 输出图片宽度,单位px
|
||||
destWidth: 200,
|
||||
// 裁剪框宽度,单位px
|
||||
rectWidth: 200,
|
||||
// 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可
|
||||
fileType: 'jpg',
|
||||
src: '', // 选择的图片路径,用于在点击确定时,判断是否选择了图片
|
||||
};
|
||||
},
|
||||
onLoad(option) {
|
||||
let rectInfo = uni.getSystemInfoSync();
|
||||
this.width = rectInfo.windowWidth;
|
||||
this.height = rectInfo.windowHeight - this.bottomNavHeight;
|
||||
this.cropperOpt.width = this.width;
|
||||
this.cropperOpt.height = this.height;
|
||||
this.cropperOpt.pixelRatio = rectInfo.pixelRatio;
|
||||
|
||||
if (option.destWidth) this.destWidth = option.destWidth;
|
||||
if (option.rectWidth) {
|
||||
let rectWidth = Number(option.rectWidth);
|
||||
this.cropperOpt.cut = {
|
||||
x: (this.width - rectWidth) / 2,
|
||||
y: (this.height - rectWidth) / 2,
|
||||
width: rectWidth,
|
||||
height: rectWidth
|
||||
};
|
||||
}
|
||||
this.rectWidth = option.rectWidth;
|
||||
if (option.fileType) this.fileType = option.fileType;
|
||||
// 初始化
|
||||
this.cropper = new WeCropper(this.cropperOpt)
|
||||
.on('ready', ctx => {
|
||||
// wecropper is ready for work!
|
||||
})
|
||||
.on('beforeImageLoad', ctx => {
|
||||
// before picture loaded, i can do something
|
||||
})
|
||||
.on('imageLoad', ctx => {
|
||||
// picture loaded
|
||||
})
|
||||
.on('beforeDraw', (ctx, instance) => {
|
||||
// before canvas draw,i can do something
|
||||
});
|
||||
// 设置导航栏样式,以免用户在page.json中没有设置为黑色背景
|
||||
uni.setNavigationBarColor({
|
||||
frontColor: '#ffffff',
|
||||
backgroundColor: '#000000'
|
||||
});
|
||||
uni.chooseImage({
|
||||
count: 1, // 默认9
|
||||
sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
|
||||
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
|
||||
success: res => {
|
||||
this.src = res.tempFilePaths[0];
|
||||
// 获取裁剪图片资源后,给data添加src属性及其值
|
||||
this.cropper.pushOrign(this.src);
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
touchStart(e) {
|
||||
this.cropper.touchStart(e);
|
||||
},
|
||||
touchMove(e) {
|
||||
this.cropper.touchMove(e);
|
||||
},
|
||||
touchEnd(e) {
|
||||
this.cropper.touchEnd(e);
|
||||
},
|
||||
getCropperImage(isPre = false) {
|
||||
if(!this.src) return this.$u.toast('请先选择图片再裁剪');
|
||||
|
||||
let cropper_opt = {
|
||||
destHeight: Number(this.destWidth), // uni.canvasToTempFilePath要求这些参数为数值
|
||||
destWidth: Number(this.destWidth),
|
||||
fileType: this.fileType
|
||||
};
|
||||
this.cropper.getCropperImage(cropper_opt, (path, err) => {
|
||||
if (err) {
|
||||
uni.showModal({
|
||||
title: '温馨提示',
|
||||
content: err.message
|
||||
});
|
||||
} else {
|
||||
if (isPre) {
|
||||
uni.previewImage({
|
||||
current: '', // 当前显示图片的 http 链接
|
||||
urls: [path] // 需要预览的图片 http 链接列表
|
||||
});
|
||||
} else {
|
||||
uni.$emit('uAvatarCropper', path);
|
||||
this.$u.route({
|
||||
type: 'back'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
uploadTap() {
|
||||
const self = this;
|
||||
uni.chooseImage({
|
||||
count: 1, // 默认9
|
||||
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
|
||||
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
|
||||
success: (res) => {
|
||||
self.src = res.tempFilePaths[0];
|
||||
// 获取裁剪图片资源后,给data添加src属性及其值
|
||||
|
||||
self.cropper.pushOrign(this.src);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../libs/css/style.components.scss';
|
||||
|
||||
.content {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.cropper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.cropper-buttons {
|
||||
background-color: #000000;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.cropper-wrapper {
|
||||
position: relative;
|
||||
@include vue-flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.cropper-buttons {
|
||||
width: 100vw;
|
||||
@include vue-flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.cropper-buttons .upload,
|
||||
.cropper-buttons .getCropperImage {
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cropper-buttons .upload {
|
||||
text-align: left;
|
||||
padding-left: 50rpx;
|
||||
}
|
||||
|
||||
.cropper-buttons .getCropperImage {
|
||||
text-align: right;
|
||||
padding-right: 50rpx;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<view class="u-avatar" :style="[wrapStyle]" @tap="click">
|
||||
<image
|
||||
@error="loadError"
|
||||
:style="[imgStyle]"
|
||||
class="u-avatar__img"
|
||||
v-if="!uText && avatar"
|
||||
:src="avatar"
|
||||
:mode="imgMode"
|
||||
></image>
|
||||
<text class="u-line-1" v-else-if="uText" :style="{
|
||||
fontSize: '38rpx'
|
||||
}">{{uText}}</text>
|
||||
<slot v-else></slot>
|
||||
<view class="u-avatar__sex" v-if="showSex" :class="['u-avatar__sex--' + sexIcon]" :style="[uSexStyle]">
|
||||
<u-icon :name="sexIcon" size="20"></u-icon>
|
||||
</view>
|
||||
<view class="u-avatar__level" v-if="showLevel" :style="[uLevelStyle]">
|
||||
<u-icon :name="levelIcon" size="20"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let base64Avatar = "data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z";
|
||||
/**
|
||||
* avatar 头像
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://www.uviewui.com/components/avatar.html
|
||||
* @property {String} bg-color 背景颜色,一般显示文字时用(默认#ffffff)
|
||||
* @property {String} src 头像路径,如加载失败,将会显示默认头像
|
||||
* @property {String Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值,单位rpx(默认default)
|
||||
* @property {String} mode 显示类型,见上方说明(默认circle)
|
||||
* @property {String} sex-icon 性别图标,man-男,woman-女(默认man)
|
||||
* @property {String} level-icon 等级图标(默认level)
|
||||
* @property {String} sex-bg-color 性别图标背景颜色
|
||||
* @property {String} level-bg-color 等级图标背景颜色
|
||||
* @property {String} show-sex 是否显示性别图标(默认false)
|
||||
* @property {String} show-level 是否显示等级图标(默认false)
|
||||
* @property {String} img-mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值(默认aspectFill)
|
||||
* @property {String} index 用户传递的标识符值,如果是列表循环,可穿v-for的index值
|
||||
* @event {Function} click 头像被点击
|
||||
* @example <u-avatar :src="src"></u-avatar>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-avatar',
|
||||
props: {
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
// 头像路径
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 尺寸,large-大,default-中等,mini-小,如果为数值,则单位为rpx
|
||||
// 宽度等于高度
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 'default'
|
||||
},
|
||||
// 头像模型,square-带圆角方形,circle-圆形
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 文字内容
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图片的裁剪模型
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 标识符
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 右上角性别角标,man-男,woman-女
|
||||
sexIcon: {
|
||||
type: String,
|
||||
default: 'man'
|
||||
},
|
||||
// 右下角的等级图标
|
||||
levelIcon: {
|
||||
type: String,
|
||||
default: 'level'
|
||||
},
|
||||
// 右下角等级图标背景颜色
|
||||
levelBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 右上角性别图标的背景颜色
|
||||
sexBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示性别图标
|
||||
showSex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示等级图标
|
||||
showLevel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
// 头像的地址,因为如果加载错误,需要赋值为默认图片,props值无法修改,所以需要一个中间值
|
||||
avatar: this.src ? this.src : base64Avatar,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
src(n) {
|
||||
// 用户可能会在头像加载失败时,再次修改头像值,所以需要重新赋值
|
||||
if(!n) {
|
||||
// 如果传入null或者'',或者undefined,显示默认头像
|
||||
this.avatar = base64Avatar;
|
||||
this.error = true;
|
||||
} else {
|
||||
this.avatar = n;
|
||||
this.error = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapStyle() {
|
||||
let style = {};
|
||||
style.height = this.size == 'large' ? '120rpx' : this.size == 'default' ?
|
||||
'90rpx' : this.size == 'mini' ? '70rpx' : this.size + 'rpx';
|
||||
style.width = style.height;
|
||||
style.flex = `0 0 ${style.height}`;
|
||||
style.backgroundColor = this.bgColor;
|
||||
style.borderRadius = this.mode == 'circle' ? '500px' : '5px';
|
||||
if(this.text) style.padding = `0 6rpx`;
|
||||
return style;
|
||||
},
|
||||
imgStyle() {
|
||||
let style = {};
|
||||
style.borderRadius = this.mode == 'circle' ? '500px' : '5px';
|
||||
return style;
|
||||
},
|
||||
// 取字符串的第一个字符
|
||||
uText() {
|
||||
return String(this.text)[0];
|
||||
},
|
||||
// 性别图标的自定义样式
|
||||
uSexStyle() {
|
||||
let style = {};
|
||||
if(this.sexBgColor) style.backgroundColor = this.sexBgColor;
|
||||
return style;
|
||||
},
|
||||
// 等级图标的自定义样式
|
||||
uLevelStyle() {
|
||||
let style = {};
|
||||
if(this.levelBgColor) style.backgroundColor = this.levelBgColor;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 图片加载错误时,显示默认头像
|
||||
loadError() {
|
||||
this.error = true;
|
||||
this.avatar = base64Avatar;
|
||||
},
|
||||
click() {
|
||||
this.$emit('click', this.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-avatar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
color: $u-content-color;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__sex {
|
||||
position: absolute;
|
||||
width: 32rpx;
|
||||
color: #ffffff;
|
||||
height: 32rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 100rpx;
|
||||
top: 5%;
|
||||
z-index: 1;
|
||||
right: -7%;
|
||||
border: 1px #ffffff solid;
|
||||
|
||||
&--man {
|
||||
background-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--woman {
|
||||
background-color: $u-type-error;
|
||||
}
|
||||
|
||||
&--none {
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
|
||||
&__level {
|
||||
position: absolute;
|
||||
width: 32rpx;
|
||||
color: #ffffff;
|
||||
height: 32rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 100rpx;
|
||||
bottom: 5%;
|
||||
z-index: 1;
|
||||
right: -7%;
|
||||
border: 1px #ffffff solid;
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<view @tap="backToTop" class="u-back-top" :class="['u-back-top--mode--' + mode]" :style="[{
|
||||
bottom: bottom + 'rpx',
|
||||
right: right + 'rpx',
|
||||
borderRadius: mode == 'circle' ? '10000rpx' : '8rpx',
|
||||
zIndex: uZIndex,
|
||||
opacity: opacity
|
||||
}, customStyle]">
|
||||
<view class="u-back-top__content" v-if="!$slots.default && !$slots.$default">
|
||||
<u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon>
|
||||
<view class="u-back-top__content__tips">
|
||||
{{tips}}
|
||||
</view>
|
||||
</view>
|
||||
<slot v-else />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'u-back-top',
|
||||
props: {
|
||||
// 返回顶部的形状,circle-圆形,square-方形
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 自定义图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'arrow-upward'
|
||||
},
|
||||
// 提示文字
|
||||
tips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 返回顶部滚动时间
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 100
|
||||
},
|
||||
// 滚动距离
|
||||
scrollTop: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 距离顶部多少距离显示,单位rpx
|
||||
top: {
|
||||
type: [Number, String],
|
||||
default: 400
|
||||
},
|
||||
// 返回顶部按钮到底部的距离,单位rpx
|
||||
bottom: {
|
||||
type: [Number, String],
|
||||
default: 200
|
||||
},
|
||||
// 返回顶部按钮到右边的距离,单位rpx
|
||||
right: {
|
||||
type: [Number, String],
|
||||
default: 40
|
||||
},
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: '9'
|
||||
},
|
||||
// 图标的样式,对象形式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
color: '#909399',
|
||||
fontSize: '38rpx'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 整个组件的样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showBackTop(nVal, oVal) {
|
||||
// 当组件的显示与隐藏状态发生跳变时,修改组件的层级和不透明度
|
||||
// 让组件有显示和消失的动画效果,如果用v-if控制组件状态,将无设置动画效果
|
||||
if(nVal) {
|
||||
this.uZIndex = this.zIndex;
|
||||
this.opacity = 1;
|
||||
} else {
|
||||
this.uZIndex = -1;
|
||||
this.opacity = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showBackTop() {
|
||||
// 由于scrollTop为页面的滚动距离,默认为px单位,这里将用于传入的top(rpx)值
|
||||
// 转为px用于比较,如果滚动条到顶的距离大于设定的距离,就显示返回顶部的按钮
|
||||
return this.scrollTop > uni.upx2px(this.top);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 不透明度,为了让组件有一个显示和隐藏的过渡动画
|
||||
opacity: 0,
|
||||
// 组件的z-index值,隐藏时设置为-1,就会看不到
|
||||
uZIndex: -1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
backToTop() {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: this.duration
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-back-top {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
@include vue-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
background-color: #E1E1E1;
|
||||
color: $u-content-color;
|
||||
align-items: center;
|
||||
transition: opacity 0.4s;
|
||||
|
||||
&__content {
|
||||
@include vue-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__tips {
|
||||
font-size: 24rpx;
|
||||
transform: scale(0.8);
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<view v-if="show" class="u-badge" :class="[
|
||||
isDot ? 'u-badge-dot' : '',
|
||||
size == 'mini' ? 'u-badge-mini' : '',
|
||||
type ? 'u-badge--bg--' + type : ''
|
||||
]" :style="[{
|
||||
top: offset[0] + 'rpx',
|
||||
right: offset[1] + 'rpx',
|
||||
fontSize: fontSize + 'rpx',
|
||||
position: absolute ? 'absolute' : 'static',
|
||||
color: color,
|
||||
backgroundColor: bgColor
|
||||
}, boxStyle]"
|
||||
>
|
||||
{{showText}}
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* badge 角标
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://www.uviewui.com/components/badge.html
|
||||
* @property {String Number} count 展示的数字,大于 overflowCount 时显示为 ${overflowCount}+,为0且show-zero为false时隐藏
|
||||
* @property {Boolean} is-dot 不展示数字,只有一个小点(默认false)
|
||||
* @property {Boolean} absolute 组件是否绝对定位,为true时,offset参数才有效(默认true)
|
||||
* @property {String Number} overflow-count 展示封顶的数字值(默认99)
|
||||
* @property {String} type 使用预设的背景颜色(默认error)
|
||||
* @property {Boolean} show-zero 当数值为 0 时,是否展示 Badge(默认false)
|
||||
* @property {String} size Badge的尺寸,设为mini会得到小一号的Badge(默认default)
|
||||
* @property {Array} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,单位rpx。absolute为true时有效(默认[20, 20])
|
||||
* @property {String} color 字体颜色(默认#ffffff)
|
||||
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
|
||||
* @property {Boolean} is-center 组件中心点是否和父组件右上角重合,优先级比offset高,如设置,offset参数会失效(默认false)
|
||||
* @example <u-badge type="error" count="7"></u-badge>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-badge',
|
||||
props: {
|
||||
// primary,warning,success,error,info
|
||||
type: {
|
||||
type: String,
|
||||
default: 'error'
|
||||
},
|
||||
// default, mini
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
//是否是圆点
|
||||
isDot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示的数值内容
|
||||
count: {
|
||||
type: [Number, String],
|
||||
},
|
||||
// 展示封顶的数字值
|
||||
overflowCount: {
|
||||
type: Number,
|
||||
default: 99
|
||||
},
|
||||
// 当数值为 0 时,是否展示 Badge
|
||||
showZero: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 位置偏移
|
||||
offset: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [20, 20]
|
||||
}
|
||||
},
|
||||
// 是否开启绝对定位,开启了offset才会起作用
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: '24'
|
||||
},
|
||||
// 字体演示
|
||||
color: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// badge的背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否让badge组件的中心点和父组件右上角重合,配置的话,offset将会失效
|
||||
isCenter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否将badge中心与父组件右上角重合
|
||||
boxStyle() {
|
||||
let style = {};
|
||||
if(this.isCenter) {
|
||||
style.top = 0;
|
||||
style.right = 0;
|
||||
// Y轴-50%,意味着badge向上移动了badge自身高度一半,X轴50%,意味着向右移动了自身宽度一半
|
||||
style.transform = "translateY(-50%) translateX(50%)";
|
||||
} else {
|
||||
style.top = this.offset[0] + 'rpx';
|
||||
style.right = this.offset[1] + 'rpx';
|
||||
style.transform = "translateY(0) translateX(0)";
|
||||
}
|
||||
// 如果尺寸为mini,后接上scal()
|
||||
if(this.size == 'mini') {
|
||||
style.transform = style.transform + " scale(0.8)";
|
||||
}
|
||||
return style;
|
||||
},
|
||||
// isDot类型时,不显示文字
|
||||
showText() {
|
||||
if(this.isDot) return '';
|
||||
else {
|
||||
if(this.count > this.overflowCount) return `${this.overflowCount}+`;
|
||||
else return this.count;
|
||||
}
|
||||
},
|
||||
// 是否显示组件
|
||||
show() {
|
||||
// 如果count的值为0,并且showZero设置为false,不显示组件
|
||||
if(this.count == 0 && this.showZero == false) return false;
|
||||
else return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-badge {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 24rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 100rpx;
|
||||
z-index: 9;
|
||||
|
||||
&--bg--primary {
|
||||
background-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--bg--error {
|
||||
background-color: $u-type-error;
|
||||
}
|
||||
|
||||
&--bg--success {
|
||||
background-color: $u-type-success;
|
||||
}
|
||||
|
||||
&--bg--info {
|
||||
background-color: $u-type-info;
|
||||
}
|
||||
|
||||
&--bg--warning {
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
|
||||
.u-badge-dot {
|
||||
height: 16rpx;
|
||||
width: 16rpx;
|
||||
border-radius: 100rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.u-badge-mini {
|
||||
transform: scale(0.8);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
// .u-primary {
|
||||
// background: $u-type-primary;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
// .u-error {
|
||||
// background: $u-type-error;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
// .u-warning {
|
||||
// background: $u-type-warning;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
// .u-success {
|
||||
// background: $u-type-success;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
// .u-black {
|
||||
// background: #585858;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
.u-info {
|
||||
background-color: $u-type-info;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,596 @@
|
||||
<template>
|
||||
<button
|
||||
id="u-wave-btn"
|
||||
class="u-btn u-line-1 u-fix-ios-appearance"
|
||||
:class="[
|
||||
'u-size-' + size,
|
||||
plain ? 'u-btn--' + type + '--plain' : '',
|
||||
loading ? 'u-loading' : '',
|
||||
shape == 'circle' ? 'u-round-circle' : '',
|
||||
hairLine ? showHairLineBorder : 'u-btn--bold-border',
|
||||
'u-btn--' + type,
|
||||
disabled ? `u-btn--${type}--disabled` : '',
|
||||
]"
|
||||
:hover-start-time="Number(hoverStartTime)"
|
||||
:hover-stay-time="Number(hoverStayTime)"
|
||||
:disabled="disabled"
|
||||
:form-type="formType"
|
||||
:open-type="openType"
|
||||
:app-parameter="appParameter"
|
||||
:hover-stop-propagation="hoverStopPropagation"
|
||||
:send-message-title="sendMessageTitle"
|
||||
send-message-path="sendMessagePath"
|
||||
:lang="lang"
|
||||
:data-name="dataName"
|
||||
:session-from="sessionFrom"
|
||||
:send-message-img="sendMessageImg"
|
||||
:show-message-card="showMessageCard"
|
||||
@getphonenumber="getphonenumber"
|
||||
@getuserinfo="getuserinfo"
|
||||
@error="error"
|
||||
@opensetting="opensetting"
|
||||
@launchapp="launchapp"
|
||||
:style="[customStyle, {
|
||||
overflow: ripple ? 'hidden' : 'visible'
|
||||
}]"
|
||||
@tap.stop="click($event)"
|
||||
:hover-class="getHoverClass"
|
||||
:loading="loading"
|
||||
>
|
||||
<slot></slot>
|
||||
<view
|
||||
v-if="ripple"
|
||||
class="u-wave-ripple"
|
||||
:class="[waveActive ? 'u-wave-active' : '']"
|
||||
:style="{
|
||||
top: rippleTop + 'px',
|
||||
left: rippleLeft + 'px',
|
||||
width: fields.targetWidth + 'px',
|
||||
height: fields.targetWidth + 'px',
|
||||
'background-color': rippleBgColor || 'rgba(0, 0, 0, 0.15)'
|
||||
}"
|
||||
></view>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* button 按钮
|
||||
* @description Button 按钮
|
||||
* @tutorial https://www.uviewui.com/components/button.html
|
||||
* @property {String} size 按钮的大小
|
||||
* @property {Boolean} ripple 是否开启点击水波纹效果
|
||||
* @property {String} ripple-bg-color 水波纹的背景色,ripple为true时有效
|
||||
* @property {String} type 按钮的样式类型
|
||||
* @property {Boolean} plain 按钮是否镂空,背景色透明
|
||||
* @property {Boolean} disabled 是否禁用
|
||||
* @property {Boolean} hair-line 是否显示按钮的细边框(默认true)
|
||||
* @property {Boolean} shape 按钮外观形状,见文档说明
|
||||
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈)
|
||||
* @property {String} form-type 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
* @property {String} open-type 开放能力
|
||||
* @property {String} data-name 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
* @property {String} hover-class 指定按钮按下去的样式类。当 hover-class="none" 时,没有点击态效果(App-nvue 平台暂不支持)
|
||||
* @property {Number} hover-start-time 按住后多久出现点击态,单位毫秒
|
||||
* @property {Number} hover-stay-time 手指松开后点击态保留时间,单位毫秒
|
||||
* @property {Object} custom-style 对按钮的自定义样式,对象形式,见文档说明
|
||||
* @event {Function} click 按钮点击
|
||||
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效
|
||||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
|
||||
* @event {Function} error 当使用开放能力时,发生错误的回调
|
||||
* @event {Function} opensetting 在打开授权设置页并关闭后回调
|
||||
* @event {Function} launchapp 打开 APP 成功的回调
|
||||
* @example <u-button>月落</u-button>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-button',
|
||||
props: {
|
||||
// 是否细边框
|
||||
hairLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 按钮的预置样式,default,primary,error,warning,success
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
// 按钮尺寸,default,medium,mini
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
// 按钮形状,circle(两边为半圆),square(带圆角)
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
// 按钮是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁止状态
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
// https://uniapp.dcloud.io/component/button
|
||||
openType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
// 取值为submit(提交表单),reset(重置表单)
|
||||
formType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
||||
// 只微信小程序、QQ小程序有效
|
||||
appParameter: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
|
||||
hoverStopPropagation: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
|
||||
lang: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
// 会话来源,open-type="contact"时有效。只微信小程序有效
|
||||
sessionFrom: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 会话内消息卡片标题,open-type="contact"时有效
|
||||
// 默认当前标题,只微信小程序有效
|
||||
sendMessageTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
||||
// 默认当前分享路径,只微信小程序有效
|
||||
sendMessagePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 会话内消息卡片图片,open-type="contact"时有效
|
||||
// 默认当前页面截图,只微信小程序有效
|
||||
sendMessageImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
|
||||
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
||||
showMessageCard: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 手指按(触摸)按钮时按钮时的背景颜色
|
||||
hoverBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 水波纹的背景颜色
|
||||
rippleBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否开启水波纹效果
|
||||
ripple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 按下的类名
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义样式,对象形式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
dataName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 节流,一定时间内只能触发一次
|
||||
throttleTime: {
|
||||
type: [String, Number],
|
||||
default: 1000
|
||||
},
|
||||
// 按住后多久出现点击态,单位毫秒
|
||||
hoverStartTime: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
// 手指松开后点击态保留时间,单位毫秒
|
||||
hoverStayTime: {
|
||||
type: [String, Number],
|
||||
default: 150
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 当没有传bgColor变量时,按钮按下去的颜色类名
|
||||
getHoverClass() {
|
||||
// 如果开启水波纹效果,则不启用hover-class效果
|
||||
if (this.loading || this.disabled || this.ripple || this.hoverClass) return '';
|
||||
let hoverClass = '';
|
||||
hoverClass = this.plain ? 'u-' + this.type + '-plain-hover' : 'u-' + this.type + '-hover';
|
||||
return hoverClass;
|
||||
},
|
||||
// 在'primary', 'success', 'error', 'warning'类型下,不显示边框,否则会造成四角有毛刺现象
|
||||
showHairLineBorder() {
|
||||
if (['primary', 'success', 'error', 'warning'].indexOf(this.type) >= 0 && !this.plain) {
|
||||
return '';
|
||||
} else {
|
||||
return 'u-hairline-border';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rippleTop: 0, // 水波纹的起点Y坐标到按钮上边界的距离
|
||||
rippleLeft: 0, // 水波纹起点X坐标到按钮左边界的距离
|
||||
fields: {}, // 波纹按钮节点信息
|
||||
waveActive: false // 激活水波纹
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 按钮点击
|
||||
click(e) {
|
||||
// 进行节流控制,每this.throttle毫秒内,只在开始处执行
|
||||
this.$u.throttle(() => {
|
||||
// 如果按钮时disabled和loading状态,不触发水波纹效果
|
||||
if (this.loading === true || this.disabled === true) return;
|
||||
// 是否开启水波纹效果
|
||||
if (this.ripple) {
|
||||
// 每次点击时,移除上一次的类,再次添加,才能触发动画效果
|
||||
this.waveActive = false;
|
||||
this.$nextTick(function() {
|
||||
this.getWaveQuery(e);
|
||||
});
|
||||
}
|
||||
this.$emit('click', e);
|
||||
}, this.throttleTime);
|
||||
},
|
||||
// 查询按钮的节点信息
|
||||
getWaveQuery(e) {
|
||||
this.getElQuery().then(res => {
|
||||
// 查询返回的是一个数组节点
|
||||
let data = res[0];
|
||||
// 查询不到节点信息,不操作
|
||||
if (!data.width || !data.width) return;
|
||||
// 水波纹的最终形态是一个正方形(通过border-radius让其变为一个圆形),这里要保证正方形的边长等于按钮的最长边
|
||||
// 最终的方形(变换后的圆形)才能覆盖整个按钮
|
||||
data.targetWidth = data.height > data.width ? data.height : data.width;
|
||||
if (!data.targetWidth) return;
|
||||
this.fields = data;
|
||||
let touchesX = '',
|
||||
touchesY = '';
|
||||
// #ifdef MP-BAIDU
|
||||
touchesX = e.changedTouches[0].clientX;
|
||||
touchesY = e.changedTouches[0].clientY;
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
touchesX = e.detail.clientX;
|
||||
touchesY = e.detail.clientY;
|
||||
// #endif
|
||||
// #ifndef MP-BAIDU || MP-ALIPAY
|
||||
touchesX = e.touches[0].clientX;
|
||||
touchesY = e.touches[0].clientY;
|
||||
// #endif
|
||||
// 获取触摸点相对于按钮上边和左边的x和y坐标,原理是通过屏幕的触摸点(touchesY),减去按钮的上边界data.top
|
||||
// 但是由于`transform-origin`默认是center,所以这里再减去半径才是水波纹view应该的位置
|
||||
// 总的来说,就是把水波纹的矩形(变换后的圆形)的中心点,移动到我们的触摸点位置
|
||||
this.rippleTop = touchesY - data.top - data.targetWidth / 2;
|
||||
this.rippleLeft = touchesX - data.left - data.targetWidth / 2;
|
||||
this.$nextTick(() => {
|
||||
this.waveActive = true;
|
||||
});
|
||||
});
|
||||
},
|
||||
// 获取节点信息
|
||||
getElQuery() {
|
||||
return new Promise(resolve => {
|
||||
let queryInfo = '';
|
||||
// 获取元素节点信息,请查看uniapp相关文档
|
||||
// https://uniapp.dcloud.io/api/ui/nodes-info?id=nodesrefboundingclientrect
|
||||
queryInfo = uni.createSelectorQuery().in(this);
|
||||
//#ifdef MP-ALIPAY
|
||||
queryInfo = uni.createSelectorQuery();
|
||||
//#endif
|
||||
queryInfo.select('.u-btn').boundingClientRect();
|
||||
queryInfo.exec(data => {
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 下面为对接uniapp官方按钮开放能力事件回调的对接
|
||||
getphonenumber(res) {
|
||||
this.$emit('getphonenumber', res);
|
||||
},
|
||||
getuserinfo(res) {
|
||||
this.$emit('getuserinfo', res);
|
||||
},
|
||||
error(res) {
|
||||
this.$emit('error', res);
|
||||
},
|
||||
opensetting(res) {
|
||||
this.$emit('opensetting', res);
|
||||
},
|
||||
launchapp(res) {
|
||||
this.$emit('launchapp', res);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../libs/css/style.components.scss';
|
||||
.u-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.u-btn {
|
||||
position: relative;
|
||||
border: 0;
|
||||
//border-radius: 10rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
// 避免边框某些场景可能被“裁剪”,不能设置为hidden
|
||||
overflow: visible;
|
||||
line-height: 1;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 0 40rpx;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.15s;
|
||||
|
||||
&--bold-border {
|
||||
border: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
&--default {
|
||||
color: $u-content-color;
|
||||
border-color: #c0c4cc;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-primary;
|
||||
background-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-success;
|
||||
background-color: $u-type-success;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-error;
|
||||
background-color: $u-type-error;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-warning;
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
|
||||
&--default--disabled {
|
||||
color: #ffffff;
|
||||
border-color: #e4e7ed;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
&--primary--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-primary-disabled!important;
|
||||
background-color: $u-type-primary-disabled!important;
|
||||
}
|
||||
|
||||
&--success--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-success-disabled!important;
|
||||
background-color: $u-type-success-disabled!important;
|
||||
}
|
||||
|
||||
&--error--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-error-disabled!important;
|
||||
background-color: $u-type-error-disabled!important;
|
||||
}
|
||||
|
||||
&--warning--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-warning-disabled!important;
|
||||
background-color: $u-type-warning-disabled!important;
|
||||
}
|
||||
|
||||
&--primary--plain {
|
||||
color: $u-type-primary!important;
|
||||
border-color: $u-type-primary-disabled!important;
|
||||
background-color: $u-type-primary-light!important;
|
||||
}
|
||||
|
||||
&--success--plain {
|
||||
color: $u-type-success!important;
|
||||
border-color: $u-type-success-disabled!important;
|
||||
background-color: $u-type-success-light!important;
|
||||
}
|
||||
|
||||
&--error--plain {
|
||||
color: $u-type-error!important;
|
||||
border-color: $u-type-error-disabled!important;
|
||||
background-color: $u-type-error-light!important;
|
||||
}
|
||||
|
||||
&--warning--plain {
|
||||
color: $u-type-warning!important;
|
||||
border-color: $u-type-warning-disabled!important;
|
||||
background-color: $u-type-warning-light!important;
|
||||
}
|
||||
}
|
||||
|
||||
.u-hairline-border:after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
// 设置为border-box,意味着下面的scale缩小为0.5,实际上缩小的是伪元素的内容(border-box意味着内容不含border)
|
||||
box-sizing: border-box;
|
||||
// 中心点作为变形(scale())的原点
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 199.8%;
|
||||
height: 199.7%;
|
||||
-webkit-transform: scale(0.5, 0.5);
|
||||
transform: scale(0.5, 0.5);
|
||||
border: 1px solid currentColor;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.u-wave-ripple {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.u-wave-ripple.u-wave-active {
|
||||
opacity: 0;
|
||||
transform: scale(2);
|
||||
transition: opacity 1s linear, transform 0.4s linear;
|
||||
}
|
||||
|
||||
.u-round-circle {
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.u-round-circle::after {
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.u-loading::after {
|
||||
background-color: hsla(0, 0%, 100%, 0.35);
|
||||
}
|
||||
|
||||
.u-size-default {
|
||||
font-size: 30rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
}
|
||||
|
||||
.u-size-medium {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
width: auto;
|
||||
font-size: 26rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
padding: 0 80rpx;
|
||||
}
|
||||
|
||||
.u-size-mini {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
width: auto;
|
||||
font-size: 22rpx;
|
||||
padding-top: 1px;
|
||||
height: 50rpx;
|
||||
line-height: 50rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.u-primary-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-primary-dark !important;
|
||||
}
|
||||
|
||||
.u-default-plain-hover {
|
||||
color: $u-type-primary-dark !important;
|
||||
background: $u-type-primary-light !important;
|
||||
}
|
||||
|
||||
.u-success-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-success-dark !important;
|
||||
}
|
||||
|
||||
.u-warning-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-warning-dark !important;
|
||||
}
|
||||
|
||||
.u-error-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-error-dark !important;
|
||||
}
|
||||
|
||||
.u-info-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-info-dark !important;
|
||||
}
|
||||
|
||||
.u-default-hover {
|
||||
color: $u-type-primary-dark !important;
|
||||
border-color: $u-type-primary-dark !important;
|
||||
background-color: $u-type-primary-light !important;
|
||||
}
|
||||
|
||||
.u-primary-hover {
|
||||
background: $u-type-primary-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-success-hover {
|
||||
background: $u-type-success-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-info-hover {
|
||||
background: $u-type-info-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-warning-hover {
|
||||
background: $u-type-warning-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-error-hover {
|
||||
background: $u-type-error-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,639 @@
|
||||
<template>
|
||||
<u-popup closeable :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" :border-radius="borderRadius" :closeable="closeable">
|
||||
<view class="u-calendar">
|
||||
<view class="u-calendar__header">
|
||||
<view class="u-calendar__header__text" v-if="!$slots['tooltip']">
|
||||
{{toolTip}}
|
||||
</view>
|
||||
<slot v-else name="tooltip" />
|
||||
</view>
|
||||
<view class="u-calendar__action u-flex u-row-center">
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeYear" name="arrow-left-double" :color="yearArrowColor" @click="changeYearHandler(0)"></u-icon>
|
||||
</view>
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeMonth" name="arrow-left" :color="monthArrowColor" @click="changeMonthHandler(0)"></u-icon>
|
||||
</view>
|
||||
<view class="u-calendar__action__text">{{ showTitle }}</view>
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeMonth" name="arrow-right" :color="monthArrowColor" @click="changeMonthHandler(1)"></u-icon>
|
||||
</view>
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeYear" name="arrow-right-double" :color="yearArrowColor" @click="changeYearHandler(1)"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-calendar__week-day">
|
||||
<view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{item}}</view>
|
||||
</view>
|
||||
<view class="u-calendar__content">
|
||||
<!-- 前置空白部分 -->
|
||||
<block v-for="(item, index) in weekdayArr" :key="index">
|
||||
<view class="u-calendar__content__item"></view>
|
||||
</block>
|
||||
<view class="u-calendar__content__item" :class="{
|
||||
'u-hover-class':openDisAbled(year,month,index+1),
|
||||
'u-calendar__content--start-date': (mode == 'range' && startDate==`${year}-${month}-${index+1}`) || mode== 'date',
|
||||
'u-calendar__content--end-date':(mode== 'range' && endDate==`${year}-${month}-${index+1}`) || mode == 'date'
|
||||
}" :style="{backgroundColor: getColor(index,1)}" v-for="(item, index) in daysArr" :key="index"
|
||||
@tap="dateClick(index)">
|
||||
<view class="u-calendar__content__item__inner" :style="{color: getColor(index,2)}">
|
||||
<view>{{ index + 1 }}</view>
|
||||
</view>
|
||||
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && startDate==`${year}-${month}-${index+1}` && startDate!=endDate">{{startText}}</view>
|
||||
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && endDate==`${year}-${month}-${index+1}`">{{endText}}</view>
|
||||
</view>
|
||||
<view class="u-calendar__content__bg-month">{{month}}</view>
|
||||
</view>
|
||||
<view class="u-calendar__bottom">
|
||||
<view class="u-calendar__bottom__choose">
|
||||
<text>{{mode == 'date' ? activeDate : startDate}}</text>
|
||||
<text v-if="endDate">至{{endDate}}</text>
|
||||
</view>
|
||||
<view class="u-calendar__bottom__btn">
|
||||
<u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)">确定</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* calendar 日历
|
||||
* @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。
|
||||
* @tutorial http://uviewui.com/components/calendar.html
|
||||
* @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围
|
||||
* @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起
|
||||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
|
||||
* @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
|
||||
* @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
|
||||
* @property {String Number} max-year 可切换的最大年份(默认2050)
|
||||
* @property {String Number} min-year 最小可选日期(默认1950)
|
||||
* @property {String Number} min-date 可切换的最小年份(默认1950-01-01)
|
||||
* @property {String Number} max-date 最大可选日期(默认当前日期)
|
||||
* @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20)
|
||||
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
|
||||
* @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266)
|
||||
* @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399)
|
||||
* @property {String} color 日期字体的默认颜色(默认#303133)
|
||||
* @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff)
|
||||
* @property {String Number} z-index 弹出时的z-index值(默认10075)
|
||||
* @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff)
|
||||
* @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
|
||||
* @property {String} range-color 选择范围内字体颜色(默认#2979ff)
|
||||
* @property {String} start-text 起始日期底部的提示文字(默认 '开始')
|
||||
* @property {String} end-text 结束日期底部的提示文字(默认 '结束')
|
||||
* @property {String} btn-type 底部确定按钮的主题(默认 'primary')
|
||||
* @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期')
|
||||
* @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
|
||||
* @example <u-calendar v-model="show" :mode="mode"></u-calendar>
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'u-calendar',
|
||||
props: {
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许通过点击遮罩关闭Picker
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 通过双向绑定控制组件的弹出与收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹出的z-index值
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否允许切换年份
|
||||
changeYear: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否允许切换月份
|
||||
changeMonth: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// date-单个日期选择,range-开始日期+结束日期选择
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'date'
|
||||
},
|
||||
// 可切换的最大年份
|
||||
maxYear: {
|
||||
type: [Number, String],
|
||||
default: 2050
|
||||
},
|
||||
// 可切换的最小年份
|
||||
minYear: {
|
||||
type: [Number, String],
|
||||
default: 1950
|
||||
},
|
||||
// 最小可选日期(不在范围内日期禁用不可选)
|
||||
minDate: {
|
||||
type: [Number, String],
|
||||
default: '1950-01-01'
|
||||
},
|
||||
/**
|
||||
* 最大可选日期
|
||||
* 默认最大值为今天,之后的日期不可选
|
||||
* 2030-12-31
|
||||
* */
|
||||
maxDate: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 弹窗顶部左右两边的圆角值
|
||||
borderRadius: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
// 月份切换按钮箭头颜色
|
||||
monthArrowColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 年份切换按钮箭头颜色
|
||||
yearArrowColor: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 默认日期字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 选中|起始结束日期背景色
|
||||
activeBgColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 选中|起始结束日期字体颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// 范围内日期背景色
|
||||
rangeBgColor: {
|
||||
type: String,
|
||||
default: 'rgba(41,121,255,0.13)'
|
||||
},
|
||||
// 范围内日期字体颜色
|
||||
rangeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// mode=range时生效,起始日期自定义文案
|
||||
startText: {
|
||||
type: String,
|
||||
default: '开始'
|
||||
},
|
||||
// mode=range时生效,结束日期自定义文案
|
||||
endText: {
|
||||
type: String,
|
||||
default: '结束'
|
||||
},
|
||||
//按钮样式类型
|
||||
btnType: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
// 当前选中日期带选中效果
|
||||
isActiveCurrent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 切换年月是否触发事件 mode=date时生效
|
||||
isChange: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示右上角的关闭图标
|
||||
closeable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 顶部的提示文字
|
||||
toolTip: {
|
||||
type: String,
|
||||
default: '选择日期'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 星期几,值为1-7
|
||||
weekday: 1,
|
||||
weekdayArr:[],
|
||||
// 当前月有多少天
|
||||
days: 0,
|
||||
daysArr:[],
|
||||
showTitle: '',
|
||||
year: 2020,
|
||||
month: 0,
|
||||
day: 0,
|
||||
startYear: 0,
|
||||
startMonth: 0,
|
||||
startDay: 0,
|
||||
endYear: 0,
|
||||
endMonth: 0,
|
||||
endDay: 0,
|
||||
today: '',
|
||||
activeDate: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
isStart: true,
|
||||
min: null,
|
||||
max: null,
|
||||
weekDayZh: ['日', '一', '二', '三', '四', '五', '六']
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dataChange() {
|
||||
return `${this.mode}-${this.minDate}-${this.maxDate}`;
|
||||
},
|
||||
uZIndex() {
|
||||
// 如果用户有传递z-index值,优先使用
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dataChange(val) {
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
getColor(index, type) {
|
||||
let color = type == 1 ? '' : this.color;
|
||||
let day = index + 1
|
||||
let date = `${this.year}-${this.month}-${day}`
|
||||
let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
|
||||
let start = this.startDate.replace(/\-/g, '/')
|
||||
let end = this.endDate.replace(/\-/g, '/')
|
||||
if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
|
||||
color = type == 1 ? this.activeBgColor : this.activeColor;
|
||||
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
|
||||
color = type == 1 ? this.rangeBgColor : this.rangeColor;
|
||||
}
|
||||
return color;
|
||||
},
|
||||
init() {
|
||||
let now = new Date();
|
||||
this.year = now.getFullYear();
|
||||
this.month = now.getMonth() + 1;
|
||||
this.day = now.getDate();
|
||||
this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
|
||||
this.activeDate = this.today;
|
||||
this.min = this.initDate(this.minDate);
|
||||
this.max = this.initDate(this.maxDate || this.today);
|
||||
this.startDate = "";
|
||||
this.startYear = 0;
|
||||
this.startMonth = 0;
|
||||
this.startDay = 0;
|
||||
this.endYear = 0;
|
||||
this.endMonth = 0;
|
||||
this.endDay = 0;
|
||||
this.endDate = "";
|
||||
this.isStart = true;
|
||||
this.changeData();
|
||||
},
|
||||
//日期处理
|
||||
initDate(date) {
|
||||
let fdate = date.split('-');
|
||||
return {
|
||||
year: Number(fdate[0] || 1920),
|
||||
month: Number(fdate[1] || 1),
|
||||
day: Number(fdate[2] || 1)
|
||||
}
|
||||
},
|
||||
openDisAbled: function(year, month, day) {
|
||||
let bool = true;
|
||||
let date = `${year}/${month}/${day}`;
|
||||
// let today = this.today.replace(/\-/g, '/');
|
||||
let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
|
||||
let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
|
||||
let timestamp = new Date(date).getTime();
|
||||
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
|
||||
bool = false;
|
||||
}
|
||||
return bool;
|
||||
},
|
||||
generateArray: function(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
formatNum: function(num) {
|
||||
return num < 10 ? '0' + num : num + '';
|
||||
},
|
||||
//一个月有多少天
|
||||
getMonthDay(year, month) {
|
||||
let days = new Date(year, month, 0).getDate();
|
||||
return days;
|
||||
},
|
||||
getWeekday(year, month) {
|
||||
let date = new Date(`${year}/${month}/01 00:00:00`);
|
||||
return date.getDay();
|
||||
},
|
||||
checkRange(year) {
|
||||
let overstep = false;
|
||||
if (year < this.minYear || year > this.maxYear) {
|
||||
uni.showToast({
|
||||
title: "日期超出范围啦~",
|
||||
icon: 'none'
|
||||
})
|
||||
overstep = true;
|
||||
}
|
||||
return overstep;
|
||||
},
|
||||
changeMonthHandler(isAdd) {
|
||||
if (isAdd) {
|
||||
let month = this.month + 1;
|
||||
let year = month > 12 ? this.year + 1 : this.year;
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month > 12 ? 1 : month;
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
|
||||
} else {
|
||||
let month = this.month - 1;
|
||||
let year = month < 1 ? this.year - 1 : this.year;
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month < 1 ? 12 : month;
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
}
|
||||
},
|
||||
changeYearHandler(isAdd) {
|
||||
let year = isAdd ? this.year + 1 : this.year - 1;
|
||||
if (!this.checkRange(year)) {
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
},
|
||||
changeData() {
|
||||
this.days = this.getMonthDay(this.year, this.month);
|
||||
this.daysArr=this.generateArray(1,this.days)
|
||||
this.weekday = this.getWeekday(this.year, this.month);
|
||||
this.weekdayArr=this.generateArray(1,this.weekday)
|
||||
this.showTitle = `${this.year}年${this.month}月`;
|
||||
if (this.isChange && this.mode == 'date') {
|
||||
this.btnFix(true);
|
||||
}
|
||||
},
|
||||
dateClick: function(day) {
|
||||
day += 1;
|
||||
if (!this.openDisAbled(this.year, this.month, day)) {
|
||||
this.day = day;
|
||||
let date = `${this.year}-${this.month}-${day}`;
|
||||
if (this.mode == 'date') {
|
||||
this.activeDate = date;
|
||||
} else {
|
||||
let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime()
|
||||
if (this.isStart || compare) {
|
||||
this.startDate = date;
|
||||
this.startYear = this.year;
|
||||
this.startMonth = this.month;
|
||||
this.startDay = this.day;
|
||||
this.endYear = 0;
|
||||
this.endMonth = 0;
|
||||
this.endDay = 0;
|
||||
this.endDate = "";
|
||||
this.activeDate = "";
|
||||
this.isStart = false;
|
||||
} else {
|
||||
this.endDate = date;
|
||||
this.endYear = this.year;
|
||||
this.endMonth = this.month;
|
||||
this.endDay = this.day;
|
||||
this.isStart = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
close() {
|
||||
// 修改通过v-model绑定的父组件变量的值为false,从而隐藏日历弹窗
|
||||
this.$emit('input', false);
|
||||
},
|
||||
getWeekText(date) {
|
||||
date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
|
||||
let week = date.getDay();
|
||||
return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
|
||||
},
|
||||
btnFix(show) {
|
||||
if (!show) {
|
||||
this.close();
|
||||
}
|
||||
if (this.mode == 'date') {
|
||||
let arr = this.activeDate.split('-')
|
||||
let year = this.isChange ? this.year : Number(arr[0]);
|
||||
let month = this.isChange ? this.month : Number(arr[1]);
|
||||
let day = this.isChange ? this.day : Number(arr[2]);
|
||||
//当前月有多少天
|
||||
let days = this.getMonthDay(year, month);
|
||||
let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`;
|
||||
let weekText = this.getWeekText(result);
|
||||
let isToday = false;
|
||||
if (`${year}-${month}-${day}` == this.today) {
|
||||
//今天
|
||||
isToday = true;
|
||||
}
|
||||
this.$emit('change', {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
days: days,
|
||||
result: result,
|
||||
week: weekText,
|
||||
isToday: isToday,
|
||||
// switch: show //是否是切换年月操作
|
||||
});
|
||||
} else {
|
||||
if (!this.startDate || !this.endDate) return;
|
||||
let startMonth = this.formatNum(this.startMonth);
|
||||
let startDay = this.formatNum(this.startDay);
|
||||
let startDate = `${this.startYear}-${startMonth}-${startDay}`;
|
||||
let startWeek = this.getWeekText(startDate)
|
||||
|
||||
let endMonth = this.formatNum(this.endMonth);
|
||||
let endDay = this.formatNum(this.endDay);
|
||||
let endDate = `${this.endYear}-${endMonth}-${endDay}`;
|
||||
let endWeek = this.getWeekText(endDate);
|
||||
this.$emit('change', {
|
||||
startYear: this.startYear,
|
||||
startMonth: this.startMonth,
|
||||
startDay: this.startDay,
|
||||
startDate: startDate,
|
||||
startWeek: startWeek,
|
||||
endYear: this.endYear,
|
||||
endMonth: this.endMonth,
|
||||
endDay: this.endDay,
|
||||
endDate: endDate,
|
||||
endWeek: endWeek
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-calendar {
|
||||
color: $u-content-color;
|
||||
|
||||
&__header {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
background-color: #fff;
|
||||
color: $u-main-color;
|
||||
|
||||
&__text {
|
||||
margin-top: 30rpx;
|
||||
padding: 0 60rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__action {
|
||||
padding: 40rpx 0 40rpx 0;
|
||||
|
||||
&__icon {
|
||||
margin: 0 16rpx;
|
||||
}
|
||||
|
||||
&__text {
|
||||
padding: 0 16rpx;
|
||||
color: $u-main-color;
|
||||
font-size: 32rpx;
|
||||
line-height: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__week-day {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 0;
|
||||
overflow: hidden;
|
||||
|
||||
&__text {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
@include vue-flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 6px 0;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
|
||||
&--end-date {
|
||||
border-top-right-radius: 8rpx;
|
||||
border-bottom-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
&--start-date {
|
||||
border-top-left-radius: 8rpx;
|
||||
border-bottom-left-radius: 8rpx;
|
||||
}
|
||||
|
||||
&__item {
|
||||
width: 14.2857%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
&__inner {
|
||||
height: 84rpx;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
font-size: 32rpx;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
|
||||
&__desc {
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
transform: scale(0.75);
|
||||
transform-origin: center center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
bottom: 2rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__tips {
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transform: scale(0.8);
|
||||
transform-origin: center center;
|
||||
text-align: center;
|
||||
bottom: 8rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&__bg-month {
|
||||
position: absolute;
|
||||
font-size: 130px;
|
||||
line-height: 130px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #e4e7ed;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__bottom {
|
||||
width: 100%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
padding: 0 40rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
|
||||
&__choose {
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<view class="u-keyboard" @touchmove.stop.prevent="() => {}">
|
||||
<view class="u-keyboard-grids">
|
||||
<block>
|
||||
<view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i">
|
||||
<view :hover-stay-time="100" @tap="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn"
|
||||
v-for="(item, j) in group" :key="j">
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
<view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back"
|
||||
hover-class="u-hover-class">
|
||||
<u-icon :size="38" name="backspace" :bold="true"></u-icon>
|
||||
</view>
|
||||
<view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @tap="changeCarInputMode">
|
||||
<text class="zh" :class="[!abc ? 'active' : 'inactive']">中</text>
|
||||
/
|
||||
<text class="en" :class="[abc ? 'active' : 'inactive']">英</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "u-keyboard",
|
||||
props: {
|
||||
// 是否打乱键盘按键的顺序
|
||||
random: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
|
||||
abc: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
areaList() {
|
||||
let data = [
|
||||
'京',
|
||||
'沪',
|
||||
'粤',
|
||||
'津',
|
||||
'冀',
|
||||
'豫',
|
||||
'云',
|
||||
'辽',
|
||||
'黑',
|
||||
'湘',
|
||||
'皖',
|
||||
'鲁',
|
||||
'苏',
|
||||
'浙',
|
||||
'赣',
|
||||
'鄂',
|
||||
'桂',
|
||||
'甘',
|
||||
'晋',
|
||||
'陕',
|
||||
'蒙',
|
||||
'吉',
|
||||
'闽',
|
||||
'贵',
|
||||
'渝',
|
||||
'川',
|
||||
'青',
|
||||
'琼',
|
||||
'宁',
|
||||
'挂',
|
||||
'藏',
|
||||
'港',
|
||||
'澳',
|
||||
'新',
|
||||
'使',
|
||||
'学'
|
||||
];
|
||||
let tmp = [];
|
||||
// 打乱顺序
|
||||
if (this.random) data = this.$u.randomArray(data);
|
||||
// 切割成二维数组
|
||||
tmp[0] = data.slice(0, 10);
|
||||
tmp[1] = data.slice(10, 20);
|
||||
tmp[2] = data.slice(20, 30);
|
||||
tmp[3] = data.slice(30, 36);
|
||||
return tmp;
|
||||
},
|
||||
EngKeyBoardList() {
|
||||
let data = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M'
|
||||
];
|
||||
let tmp = [];
|
||||
if (this.random) data = this.$u.randomArray(data);
|
||||
tmp[0] = data.slice(0, 10);
|
||||
tmp[1] = data.slice(10, 20);
|
||||
tmp[2] = data.slice(20, 30);
|
||||
tmp[3] = data.slice(30, 36);
|
||||
return tmp;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击键盘按钮
|
||||
carInputClick(i, j) {
|
||||
let value = '';
|
||||
// 不同模式,获取不同数组的值
|
||||
if (this.abc) value = this.EngKeyBoardList[i][j];
|
||||
else value = this.areaList[i][j];
|
||||
this.$emit('change', value);
|
||||
},
|
||||
// 修改汽车牌键盘的输入模式,中文|英文
|
||||
changeCarInputMode() {
|
||||
this.abc = !this.abc;
|
||||
},
|
||||
// 点击退格键
|
||||
backspaceClick() {
|
||||
this.$emit('backspace');
|
||||
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
|
||||
this.timer = null;
|
||||
this.timer = setInterval(() => {
|
||||
this.$emit('backspace');
|
||||
}, 250);
|
||||
},
|
||||
clearTimer() {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-keyboard-grids {
|
||||
background: rgb(215, 215, 217);
|
||||
padding: 24rpx 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.u-keyboard-grids-item {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-keyboard-grids-btn {
|
||||
text-decoration: none;
|
||||
width: 62rpx;
|
||||
flex: 0 0 64rpx;
|
||||
height: 80rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
font-size: 36rpx;
|
||||
text-align: center;
|
||||
line-height: 80rpx;
|
||||
background-color: #fff;
|
||||
margin: 8rpx 5rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx #888992;
|
||||
font-weight: 500;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-carinput-hover {
|
||||
background-color: rgb(185, 188, 195) !important;
|
||||
}
|
||||
|
||||
.u-keyboard-back {
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
right: 22rpx;
|
||||
bottom: 32rpx;
|
||||
height: 80rpx;
|
||||
background-color: rgb(185, 188, 195);
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
border-radius: 8rpx;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 0rpx #888992;
|
||||
}
|
||||
|
||||
.u-keyboard-change {
|
||||
font-size: 24rpx;
|
||||
box-shadow: 0 2rpx 0rpx #888992;
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
left: 22rpx;
|
||||
line-height: 1;
|
||||
bottom: 32rpx;
|
||||
height: 80rpx;
|
||||
background-color: #ffffff;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
border-radius: 8rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-keyboard-change .inactive.zh {
|
||||
transform: scale(0.85) translateY(-10rpx);
|
||||
}
|
||||
|
||||
.u-keyboard-change .inactive.en {
|
||||
transform: scale(0.85) translateY(10rpx);
|
||||
}
|
||||
|
||||
.u-keyboard-change .active {
|
||||
color: rgb(237, 112, 64);
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.u-keyboard-change .zh {
|
||||
transform: translateY(-10rpx);
|
||||
}
|
||||
|
||||
.u-keyboard-change .en {
|
||||
transform: translateY(10rpx);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-card"
|
||||
@tap.stop="click"
|
||||
:class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': borderRadius > 0 }"
|
||||
:style="{
|
||||
borderRadius: borderRadius + 'rpx',
|
||||
margin: margin,
|
||||
boxShadow: boxShadow
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-if="showHead"
|
||||
class="u-card__head"
|
||||
:style="[{padding: padding + 'rpx'}, headStyle]"
|
||||
:class="{
|
||||
'u-border-bottom': headBorderBottom
|
||||
}"
|
||||
@tap="headClick"
|
||||
>
|
||||
<view v-if="!$slots.head" class="u-flex u-row-between">
|
||||
<view class="u-card__head--left u-flex u-line-1" v-if="title">
|
||||
<image
|
||||
:src="thumb"
|
||||
class="u-card__head--left__thumb"
|
||||
mode="aspectfull"
|
||||
v-if="thumb"
|
||||
:style="{
|
||||
height: thumbWidth + 'rpx',
|
||||
width: thumbWidth + 'rpx',
|
||||
borderRadius: thumbCircle ? '100rpx' : '6rpx'
|
||||
}"
|
||||
></image>
|
||||
<text
|
||||
class="u-card__head--left__title u-line-1"
|
||||
:style="{
|
||||
fontSize: titleSize + 'rpx',
|
||||
color: titleColor
|
||||
}"
|
||||
>
|
||||
{{ title }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="u-card__head--right u-line-1" v-if="subTitle">
|
||||
<text
|
||||
class="u-card__head__title__text"
|
||||
:style="{
|
||||
fontSize: subTitleSize + 'rpx',
|
||||
color: subTitleColor
|
||||
}"
|
||||
>
|
||||
{{ subTitle }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<slot name="head" v-else />
|
||||
</view>
|
||||
<view @tap="bodyClick" class="u-card__body" :style="[{padding: padding + 'rpx'}, bodyStyle]"><slot name="body" /></view>
|
||||
<view
|
||||
v-if="showFoot"
|
||||
class="u-card__foot"
|
||||
@tap="footClick"
|
||||
:style="[{padding: $slots.foot ? padding + 'rpx' : 0}, footStyle]"
|
||||
:class="{
|
||||
'u-border-top': footBorderTop
|
||||
}"
|
||||
>
|
||||
<slot name="foot" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* card 卡片
|
||||
* @description 卡片组件一般用于多个列表条目,且风格统一的场景
|
||||
* @tutorial https://www.uviewui.com/components/card.html
|
||||
* @property {Boolean} full 卡片与屏幕两侧是否留空隙(默认false)
|
||||
* @property {String} title 头部左边的标题
|
||||
* @property {String} title-color 标题颜色(默认#303133)
|
||||
* @property {String | Number} title-size 标题字体大小,单位rpx(默认30)
|
||||
* @property {String} sub-title 头部右边的副标题
|
||||
* @property {String} sub-title-color 副标题颜色(默认#909399)
|
||||
* @property {String | Number} sub-title-size 副标题字体大小(默认26)
|
||||
* @property {Boolean} border 是否显示边框(默认true)
|
||||
* @property {String | Number} index 用于标识点击了第几个卡片
|
||||
* @property {String} box-shadow 卡片外围阴影,字符串形式(默认none)
|
||||
* @property {String} margin 卡片与屏幕两边和上下元素的间距,需带单位,如"30rpx 20rpx"(默认30rpx)
|
||||
* @property {String | Number} border-radius 卡片整体的圆角值,单位rpx(默认16)
|
||||
* @property {Object} head-style 头部自定义样式,对象形式
|
||||
* @property {Object} body-style 中部自定义样式,对象形式
|
||||
* @property {Object} foot-style 底部自定义样式,对象形式
|
||||
* @property {Boolean} head-border-bottom 是否显示头部的下边框(默认true)
|
||||
* @property {Boolean} foot-border-top 是否显示底部的上边框(默认true)
|
||||
* @property {Boolean} show-head 是否显示头部(默认true)
|
||||
* @property {Boolean} show-head 是否显示尾部(默认true)
|
||||
* @property {String} thumb 缩略图路径,如设置将显示在标题的左边,不建议使用相对路径
|
||||
* @property {String | Number} thumb-width 缩略图的宽度,高等于宽,单位rpx(默认60)
|
||||
* @property {Boolean} thumb-circle 缩略图是否为圆形(默认false)
|
||||
* @event {Function} click 整个卡片任意位置被点击时触发
|
||||
* @event {Function} head-click 卡片头部被点击时触发
|
||||
* @event {Function} body-click 卡片主体部分被点击时触发
|
||||
* @event {Function} foot-click 卡片底部部分被点击时触发
|
||||
* @example <u-card padding="30" title="card"></u-card>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-card',
|
||||
props: {
|
||||
// 与屏幕两侧是否留空隙
|
||||
full: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题颜色
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 标题字体大小,单位rpx
|
||||
titleSize: {
|
||||
type: [Number, String],
|
||||
default: '30'
|
||||
},
|
||||
// 副标题
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 副标题颜色
|
||||
subTitleColor: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 副标题字体大小,单位rpx
|
||||
subTitleSize: {
|
||||
type: [Number, String],
|
||||
default: '26'
|
||||
},
|
||||
// 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时)
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 用于标识点击了第几个
|
||||
index: {
|
||||
type: [Number, String, Object],
|
||||
default: ''
|
||||
},
|
||||
// 用于隔开上下左右的边距,带单位的写法,如:"30rpx 30rpx","20rpx 20rpx 30rpx 30rpx"
|
||||
margin: {
|
||||
type: String,
|
||||
default: '30rpx'
|
||||
},
|
||||
// card卡片的圆角
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: '16'
|
||||
},
|
||||
// 头部自定义样式,对象形式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 主体自定义样式,对象形式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 底部自定义样式,对象形式
|
||||
footStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 头部是否下边框
|
||||
headBorderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部是否有上边框
|
||||
footBorderTop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 标题左边的缩略图
|
||||
thumb: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 缩略图宽高,单位rpx
|
||||
thumbWidth: {
|
||||
type: [String, Number],
|
||||
default: '60'
|
||||
},
|
||||
// 缩略图是否为圆形
|
||||
thumbCircle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 给head,body,foot的内边距
|
||||
padding: {
|
||||
type: [String, Number],
|
||||
default: '30'
|
||||
},
|
||||
// 是否显示头部
|
||||
showHead: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示尾部
|
||||
showFoot: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 卡片外围阴影,字符串形式
|
||||
boxShadow: {
|
||||
type: String,
|
||||
default: 'none'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click', this.index);
|
||||
},
|
||||
headClick() {
|
||||
this.$emit('head-click', this.index);
|
||||
},
|
||||
bodyClick() {
|
||||
this.$emit('body-click', this.index);
|
||||
},
|
||||
footClick() {
|
||||
this.$emit('foot-click', this.index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-full {
|
||||
// 如果是与屏幕之间不留空隙,应该设置左右边距为0
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--border:after {
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
&__head {
|
||||
&--left {
|
||||
color: $u-main-color;
|
||||
|
||||
&__thumb {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
&__title {
|
||||
max-width: 400rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&--right {
|
||||
color: $u-tips-color;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
&__foot {
|
||||
color: $u-tips-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<view class="u-cell-box">
|
||||
<view class="u-cell-title" v-if="title" :style="[titleStyle]">
|
||||
{{title}}
|
||||
</view>
|
||||
<view class="u-cell-item-box" :class="{'u-border-bottom u-border-top': border}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* cellGroup 单元格父组件Group
|
||||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-item
|
||||
* @tutorial https://www.uviewui.com/components/cell.html
|
||||
* @property {String} title 分组标题
|
||||
* @property {Boolean} border 是否显示外边框(默认true)
|
||||
* @property {Object} title-style 分组标题的的样式,对象形式,如{'font-size': '24rpx'} 或 {'fontSize': '24rpx'}
|
||||
* @example <u-cell-group title="设置喜好">
|
||||
*/
|
||||
export default {
|
||||
name: "u-cell-group",
|
||||
props: {
|
||||
// 分组标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示分组list上下边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 分组标题的样式,对象形式,注意驼峰属性写法
|
||||
// 类似 {'font-size': '24rpx'} 和 {'fontSize': '24rpx'}
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
index: 0,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-cell-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.u-cell-title {
|
||||
padding: 30rpx 32rpx 10rpx 32rpx;
|
||||
font-size: 30rpx;
|
||||
text-align: left;
|
||||
color: $u-tips-color;
|
||||
}
|
||||
|
||||
.u-cell-item-box {
|
||||
background-color: #FFFFFF;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<view
|
||||
@tap="click"
|
||||
class="u-cell"
|
||||
:class="{ 'u-border-bottom': borderBottom, 'u-border-top': borderTop, 'u-col-center': center, 'u-cell--required': required }"
|
||||
hover-stay-time="150"
|
||||
:hover-class="hoverClass"
|
||||
:style="{
|
||||
backgroundColor: bgColor
|
||||
}"
|
||||
>
|
||||
<u-icon :size="iconSize" :name="icon" v-if="icon" :custom-style="iconStyle" class="u-cell__left-icon-wrap"></u-icon>
|
||||
<view class="u-flex" v-else>
|
||||
<slot name="icon"></slot>
|
||||
</view>
|
||||
<view
|
||||
class="u-cell_title"
|
||||
:style="[
|
||||
{
|
||||
width: titleWidth ? titleWidth + 'rpx' : 'auto'
|
||||
},
|
||||
titleStyle
|
||||
]"
|
||||
>
|
||||
<block v-if="title !== ''">{{ title }}</block>
|
||||
<slot name="title" v-else></slot>
|
||||
|
||||
<view class="u-cell__label" v-if="label || $slots.label" :style="[labelStyle]">
|
||||
<block v-if="label !== ''">{{ label }}</block>
|
||||
<slot name="label" v-else></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="u-cell__value" :style="[valueStyle]">
|
||||
<block class="u-cell__value" v-if="value !== ''">{{ value }}</block>
|
||||
<slot v-else></slot>
|
||||
</view>
|
||||
<view class="u-flex u-cell_right" v-if="$slots['right-icon']">
|
||||
<slot name="right-icon"></slot>
|
||||
</view>
|
||||
<u-icon v-if="arrow" name="arrow-right" :style="[arrowStyle]" class="u-icon-wrap u-cell__right-icon-wrap"></u-icon>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* cellItem 单元格Item
|
||||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-group使用
|
||||
* @tutorial https://www.uviewui.com/components/cell.html
|
||||
* @property {String} title 左侧标题
|
||||
* @property {String} icon 左侧图标名,只支持uView内置图标,见Icon 图标
|
||||
* @property {Object} icon-style 左边图标的样式,对象形式
|
||||
* @property {String} value 右侧内容
|
||||
* @property {String} label 标题下方的描述信息
|
||||
* @property {Boolean} border-bottom 是否显示cell的下边框(默认true)
|
||||
* @property {Boolean} border-top 是否显示cell的上边框(默认false)
|
||||
* @property {Boolean} center 是否使内容垂直居中(默认false)
|
||||
* @property {String} hover-class 是否开启点击反馈,none为无效果(默认true)
|
||||
* // @property {Boolean} border-gap border-bottom为true时,Cell列表中间的条目的下边框是否与左边有一个间隔(默认true)
|
||||
* @property {Boolean} arrow 是否显示右侧箭头(默认true)
|
||||
* @property {Boolean} required 箭头方向,可选值(默认right)
|
||||
* @property {Boolean} arrow-direction 是否显示左边表示必填的星号(默认false)
|
||||
* @property {Object} title-style 标题样式,对象形式
|
||||
* @property {Object} value-style 右侧内容样式,对象形式
|
||||
* @property {Object} label-style 标题下方描述信息的样式,对象形式
|
||||
* @property {String} bg-color 背景颜色(默认transparent)
|
||||
* @property {String Number} index 用于在click事件回调中返回,标识当前是第几个Item
|
||||
* @property {String Number} title-width 标题的宽度,单位rpx
|
||||
* @example <u-cell-item icon="integral-fill" title="会员等级" value="新版本"></u-cell-item>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-cell-item',
|
||||
props: {
|
||||
// 左侧图标名称(只能uView内置图标),或者图标src
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左侧标题
|
||||
title: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 右侧内容
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 标题下方的描述信息
|
||||
label: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否显示下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示上边框
|
||||
borderTop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 多个cell中,中间的cell显示下划线时,下划线是否给一个到左边的距离
|
||||
// 1.4.0版本废除此参数,默认边框由border-top和border-bottom提供,此参数会造成干扰
|
||||
// borderGap: {
|
||||
// type: Boolean,
|
||||
// default: true
|
||||
// },
|
||||
// 是否开启点击反馈,即点击时cell背景为灰色,none为无效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'u-cell-hover'
|
||||
},
|
||||
// 是否显示右侧箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 内容是否垂直居中
|
||||
center: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示左边表示必填的星号
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题的宽度,单位rpx
|
||||
titleWidth: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 右侧箭头方向,可选值:right|up|down,默认为right
|
||||
arrowDirection: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// 控制标题的样式
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 右侧显示内容的样式
|
||||
valueStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 描述信息的样式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
// 用于识别被点击的是第几个cell
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否使用lable插槽
|
||||
useLabelSlot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 左边图标的大小,单位rpx,只对传入icon字段时有效
|
||||
iconSize: {
|
||||
type: [Number, String],
|
||||
default: 34
|
||||
},
|
||||
// 左边图标的样式,对象形式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
arrowStyle() {
|
||||
let style = {};
|
||||
if (this.arrowDirection == 'up') style.transform = 'rotate(-90deg)';
|
||||
else if (this.arrowDirection == 'down') style.transform = 'rotate(90deg)';
|
||||
else style.transform = 'rotate(0deg)';
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click', this.index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
.u-cell {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
width: 100%;
|
||||
padding: 26rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 54rpx;
|
||||
color: $u-content-color;
|
||||
background-color: #fff;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.u-cell_title {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.u-cell__left-icon-wrap {
|
||||
margin-right: 10rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.u-cell__right-icon-wrap {
|
||||
margin-left: 10rpx;
|
||||
color: #969799;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.u-cell__left-icon-wrap,
|
||||
.u-cell__right-icon-wrap {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.u-cell-border:after {
|
||||
position: absolute;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
content: ' ';
|
||||
pointer-events: none;
|
||||
border-bottom: 1px solid $u-border-color;
|
||||
/* #endif */
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.u-cell-border {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.u-cell__label {
|
||||
margin-top: 6rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 36rpx;
|
||||
color: $u-tips-color;
|
||||
/* #ifndef APP-NVUE */
|
||||
word-wrap: break-word;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.u-cell__value {
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
/* #ifndef APP-NVUE */
|
||||
vertical-align: middle;
|
||||
/* #endif */
|
||||
color: $u-tips-color;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.u-cell__title,
|
||||
.u-cell__value {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.u-cell--required {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: visible;
|
||||
/* #endif */
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-cell--required:before {
|
||||
position: absolute;
|
||||
/* #ifndef APP-NVUE */
|
||||
content: '*';
|
||||
/* #endif */
|
||||
left: 8px;
|
||||
margin-top: 4rpx;
|
||||
font-size: 14px;
|
||||
color: $u-type-error;
|
||||
}
|
||||
|
||||
.u-cell_right {
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<view class="u-checkbox-group u-clearfix">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/util/emitter.js';
|
||||
/**
|
||||
* checkboxGroup 开关选择器父组件Group
|
||||
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
|
||||
* @tutorial https://www.uviewui.com/components/checkbox.html
|
||||
* @property {String Number} max 最多能选中多少个checkbox(默认999)
|
||||
* @property {String Number} size 组件整体的大小,单位rpx(默认40)
|
||||
* @property {Boolean} disabled 是否禁用所有checkbox(默认false)
|
||||
* @property {String Number} icon-size 图标大小,单位rpx(默认20)
|
||||
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox(默认false)
|
||||
* @property {String} width 宽度,需带单位
|
||||
* @property {String} width 宽度,需带单位
|
||||
* @property {String} shape 外观形状,shape-方形,circle-圆形(默认circle)
|
||||
* @property {Boolean} wrap 是否每个checkbox都换行(默认false)
|
||||
* @property {String} active-color 选中时的颜色,应用到所有子Checkbox组件(默认#2979ff)
|
||||
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
|
||||
* @example <u-checkbox-group></u-checkbox-group>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-checkbox-group',
|
||||
mixins: [Emitter],
|
||||
props: {
|
||||
// 最多能选中多少个checkbox
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: 999
|
||||
},
|
||||
// 所有选中项的 name
|
||||
// value: {
|
||||
// default: Array,
|
||||
// default() {
|
||||
// return []
|
||||
// }
|
||||
// },
|
||||
// 是否禁用所有复选框
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 在表单内提交时的标识符
|
||||
name: {
|
||||
type: [Boolean, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁止点击提示语选中复选框
|
||||
labelDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 形状,square为方形,circle为原型
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
// 选中状态下的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 组件的整体大小
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 34
|
||||
},
|
||||
// 每个checkbox占u-checkbox-group的宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否每个checkbox都换行
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图标的大小,单位rpx
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
|
||||
this.children = [];
|
||||
},
|
||||
methods: {
|
||||
emitEvent() {
|
||||
let values = [];
|
||||
this.children.map(val => {
|
||||
if(val.value) values.push(val.name);
|
||||
})
|
||||
this.$emit('change', values);
|
||||
// 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证
|
||||
// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
|
||||
setTimeout(() => {
|
||||
// 将当前的值发送到 u-form-item 进行校验
|
||||
this.dispatch('u-form-item', 'on-form-change', values);
|
||||
}, 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-checkbox-group {
|
||||
/* #ifndef MP || APP-NVUE */
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<view class="u-checkbox" :style="[checkboxStyle]">
|
||||
<view class="u-checkbox__icon-wrap" @tap="toggle" :class="[iconClass]" :style="[iconStyle]">
|
||||
<u-icon class="u-checkbox__icon-wrap__icon" name="checkbox-mark" :size="checkboxIconSize" :color="iconColor"/>
|
||||
</view>
|
||||
<view class="u-checkbox__label" @tap="onClickLabel" :style="{
|
||||
fontSize: $u.addUnit(labelSize)
|
||||
}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* checkbox 复选框
|
||||
* @description 该组件需要搭配checkboxGroup组件使用,以便用户进行操作时,获得当前复选框组的选中情况。
|
||||
* @tutorial https://www.uviewui.com/components/checkbox.html
|
||||
* @property {String Number} icon-size 图标大小,单位rpx(默认20)
|
||||
* @property {String Number} label-size label字体大小,单位rpx(默认28)
|
||||
* @property {String Number} name checkbox组件的标示符
|
||||
* @property {String} shape 形状,见官网说明(默认circle)
|
||||
* @property {Boolean} disabled 是否禁用
|
||||
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox
|
||||
* @property {String} active-color 选中时的颜色,如设置CheckboxGroup的active-color将失效
|
||||
* @event {Function} change 某个checkbox状态发生变化时触发,回调为一个对象
|
||||
* @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
|
||||
*/
|
||||
export default {
|
||||
name: "u-checkbox",
|
||||
props: {
|
||||
// checkbox的名称
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 形状,square为方形,circle为原型
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否为选中状态
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁止点击提示语选中复选框
|
||||
labelDisabled: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
// 选中状态下的颜色,如设置此值,将会覆盖checkboxGroup的activeColor值
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标的大小,单位rpx
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// label的字体大小,rpx单位
|
||||
labelSize: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 组件的整体大小
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
parentDisabled: false,
|
||||
newParams: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
|
||||
this.parent = this.$u.$parent.call(this, 'u-checkbox-group');
|
||||
// 如果存在u-checkbox-group,将本组件的this塞进父组件的children中
|
||||
this.parent && this.parent.children.push(this);
|
||||
},
|
||||
computed: {
|
||||
// 是否禁用,如果父组件u-checkbox-group禁用的话,将会忽略子组件的配置
|
||||
isDisabled() {
|
||||
return this.disabled !== '' ? this.disabled : this.parent ? this.parent.disabled : false;
|
||||
},
|
||||
// 是否禁用label点击
|
||||
isLabelDisabled() {
|
||||
return this.labelDisabled !== '' ? this.labelDisabled : this.parent ? this.parent.labelDisabled : false;
|
||||
},
|
||||
// 组件尺寸,对应size的值,默认值为34rpx
|
||||
checkboxSize() {
|
||||
return this.size ? this.size : (this.parent ? this.parent.size : 34);
|
||||
},
|
||||
// 组件的勾选图标的尺寸,默认20
|
||||
checkboxIconSize() {
|
||||
return this.iconSize ? this.iconSize : (this.parent ? this.parent.iconSize : 20);
|
||||
},
|
||||
// 组件选中激活时的颜色
|
||||
elActiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parent ? this.parent.activeColor : 'primary');
|
||||
},
|
||||
// 组件的形状
|
||||
elShape() {
|
||||
return this.shape ? this.shape : (this.parent ? this.parent.shape : 'square');
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {};
|
||||
// 既要判断是否手动禁用,还要判断用户v-model绑定的值,如果绑定为false,那么也无法选中
|
||||
if (this.elActiveColor && this.value && !this.isDisabled) {
|
||||
style.borderColor = this.elActiveColor;
|
||||
style.backgroundColor = this.elActiveColor;
|
||||
}
|
||||
style.width = this.$u.addUnit(this.checkboxSize);
|
||||
style.height = this.$u.addUnit(this.checkboxSize);
|
||||
return style;
|
||||
},
|
||||
// checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可
|
||||
iconColor() {
|
||||
return this.value ? '#ffffff' : 'transparent';
|
||||
},
|
||||
iconClass() {
|
||||
let classes = [];
|
||||
classes.push('u-checkbox__icon-wrap--' + this.elShape);
|
||||
if (this.value == true) classes.push('u-checkbox__icon-wrap--checked');
|
||||
if (this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled');
|
||||
if (this.value && this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled--checked');
|
||||
// 支付宝小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
|
||||
return classes.join(' ');
|
||||
},
|
||||
checkboxStyle() {
|
||||
let style = {};
|
||||
if(this.parent && this.parent.width) {
|
||||
style.width = this.parent.width;
|
||||
// #ifdef MP
|
||||
// 各家小程序因为它们特殊的编译结构,使用float布局
|
||||
style.float = 'left';
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局
|
||||
style.flex = `0 0 ${this.parent.width}`;
|
||||
// #endif
|
||||
}
|
||||
if(this.parent && this.parent.wrap) {
|
||||
style.width = '100%';
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局,将宽度设置100%,即可自动换行
|
||||
style.flex = '0 0 100%';
|
||||
// #endif
|
||||
}
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClickLabel() {
|
||||
if (!this.isLabelDisabled && !this.isDisabled) {
|
||||
this.setValue();
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
if (!this.isDisabled) {
|
||||
this.setValue();
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
this.$emit('change', {
|
||||
value: !this.value,
|
||||
name: this.name
|
||||
})
|
||||
// 执行父组件u-checkbox-group的事件方法
|
||||
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
|
||||
setTimeout(() => {
|
||||
if(this.parent && this.parent.emitEvent) this.parent.emitEvent();
|
||||
}, 80);
|
||||
},
|
||||
// 设置input的值,这里通过input事件,设置通过v-model绑定的组件的值
|
||||
setValue() {
|
||||
// 判断是否超过了可选的最大数量
|
||||
let checkedNum = 0;
|
||||
if(this.parent && this.parent.children) {
|
||||
// 只要父组件的某一个子元素的value为true,就加1(已有的选中数量)
|
||||
this.parent.children.map(val => {
|
||||
if (val.value) checkedNum++;
|
||||
})
|
||||
}
|
||||
// 如果原来为选中状态,那么可以取消
|
||||
if (this.value == true) {
|
||||
this.emitEvent();
|
||||
this.$emit('input', !this.value);
|
||||
} else {
|
||||
// 如果超出最多可选项,提示
|
||||
if(this.parent && checkedNum >= this.parent.max) {
|
||||
return this.$u.toast(`最多可选${this.parent.max}项`);
|
||||
}
|
||||
// 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中
|
||||
this.emitEvent();
|
||||
this.$emit('input', !this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-checkbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
line-height: 1.8;
|
||||
|
||||
&__icon-wrap {
|
||||
color: $u-content-color;
|
||||
flex: none;
|
||||
display: -webkit-flex;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
color: transparent;
|
||||
text-align: center;
|
||||
transition-property: color, border-color, background-color;
|
||||
font-size: 20px;
|
||||
border: 1px solid #c8c9cc;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
|
||||
&__icon {
|
||||
line-height: 0;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
&--checked {
|
||||
color: #fff;
|
||||
background-color: $u-type-primary;
|
||||
border-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: #ebedf0;
|
||||
border-color: #c8c9cc;
|
||||
}
|
||||
|
||||
&--disabled--checked {
|
||||
color: #c8c9cc !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
word-wrap: break-word;
|
||||
margin-left: 10rpx;
|
||||
margin-right: 24rpx;
|
||||
color: $u-content-color;
|
||||
font-size: 30rpx;
|
||||
|
||||
&--disabled {
|
||||
color: #c8c9cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-circle-progress"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px',
|
||||
backgroundColor: bgColor
|
||||
}"
|
||||
>
|
||||
<!-- 支付宝小程序不支持canvas-id属性,必须用id属性 -->
|
||||
<canvas
|
||||
class="u-canvas-bg"
|
||||
:canvas-id="elBgId"
|
||||
:id="elBgId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<canvas
|
||||
class="u-canvas"
|
||||
:canvas-id="elId"
|
||||
:id="elId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* circleProgress 环形进度条
|
||||
* @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度条。注意:此组件的percent值只能动态增加,不能动态减少。
|
||||
* @tutorial https://www.uviewui.com/components/circleProgress.html
|
||||
* @property {String Number} percent 圆环进度百分比值,为数值类型,0-100
|
||||
* @property {String} inactive-color 圆环的底色,默认为灰色(该值无法动态变更)(默认#ececec)
|
||||
* @property {String} active-color 圆环激活部分的颜色(该值无法动态变更)(默认#19be6b)
|
||||
* @property {String Number} width 整个圆环组件的宽度,高度默认等于宽度值,单位rpx(默认200)
|
||||
* @property {String Number} border-width 圆环的边框宽度,单位rpx(默认14)
|
||||
* @property {String Number} duration 整个圆环执行一圈的时间,单位ms(默认呢1500)
|
||||
* @property {String} type 如设置,active-color值将会失效
|
||||
* @property {String} bg-color 整个组件背景颜色,默认为白色
|
||||
* @example <u-circle-progress active-color="#2979ff" :percent="80"></u-circle-progress>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-circle-progress',
|
||||
props: {
|
||||
// 圆环进度百分比值
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
// 限制值在0到100之间
|
||||
validator: val => {
|
||||
return val >= 0 && val <= 100;
|
||||
}
|
||||
},
|
||||
// 底部圆环的颜色(灰色的圆环)
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#ececec'
|
||||
},
|
||||
// 圆环激活部分的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#19be6b'
|
||||
},
|
||||
// 圆环线条的宽度,单位rpx
|
||||
borderWidth: {
|
||||
type: [Number, String],
|
||||
default: 14
|
||||
},
|
||||
// 整个圆形的宽度,单位rpx
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 200
|
||||
},
|
||||
// 整个圆环执行一圈的时间,单位ms
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 1500
|
||||
},
|
||||
// 主题类型
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 整个圆环进度区域的背景色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// #ifdef MP-WEIXIN
|
||||
elBgId: 'uCircleProgressBgId', // 微信小程序中不能使用this.$u.guid()形式动态生成id值,否则会报错
|
||||
elId: 'uCircleProgressElId',
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
elBgId: this.$u.guid(), // 非微信端的时候,需用动态的id,否则一个页面多个圆形进度条组件数据会混乱
|
||||
elId: this.$u.guid(),
|
||||
// #endif
|
||||
widthPx: uni.upx2px(this.width), // 转成px后的整个组件的背景宽度
|
||||
borderWidthPx: uni.upx2px(this.borderWidth), // 转成px后的圆环的宽度
|
||||
startAngle: -Math.PI / 2, // canvas画圆的起始角度,默认为3点钟方向,定位到12点钟方向
|
||||
progressContext: null, // 活动圆的canvas上下文
|
||||
newPercent: 0, // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用
|
||||
oldPercent: 0 // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
percent(nVal, oVal = 0) {
|
||||
if (nVal > 100) nVal = 100;
|
||||
if (nVal < 0) oVal = 0;
|
||||
// 此值其实等于this.percent,命名一个新
|
||||
this.newPercent = nVal;
|
||||
this.oldPercent = oVal;
|
||||
setTimeout(() => {
|
||||
// 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值
|
||||
// 将此值减少或者新增到新的百分比值
|
||||
this.drawCircleByProgress(oVal);
|
||||
}, 50);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 赋值,用于加载后第一个画圆使用
|
||||
this.newPercent = this.percent;
|
||||
this.oldPercent = 0;
|
||||
},
|
||||
computed: {
|
||||
// 有type主题时,优先起作用
|
||||
circleColor() {
|
||||
if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) return this.$u.color[this.type];
|
||||
else return this.activeColor;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 在h5端,必须要做一点延时才起作用,this.$nextTick()无效(HX2.4.7)
|
||||
setTimeout(() => {
|
||||
this.drawProgressBg();
|
||||
this.drawCircleByProgress(this.oldPercent);
|
||||
}, 50);
|
||||
},
|
||||
methods: {
|
||||
drawProgressBg() {
|
||||
let ctx = uni.createCanvasContext(this.elBgId, this);
|
||||
ctx.setLineWidth(this.borderWidthPx); // 设置圆环宽度
|
||||
ctx.setStrokeStyle(this.inactiveColor); // 线条颜色
|
||||
ctx.beginPath(); // 开始描绘路径
|
||||
// 设置一个原点(110,110),半径为100的圆的路径到当前路径
|
||||
let radius = this.widthPx / 2;
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 2 * Math.PI, false);
|
||||
ctx.stroke(); // 对路径进行描绘
|
||||
ctx.draw();
|
||||
},
|
||||
drawCircleByProgress(progress) {
|
||||
// 第一次操作进度环时将上下文保存到了this.data中,直接使用即可
|
||||
let ctx = this.progressContext;
|
||||
if (!ctx) {
|
||||
ctx = uni.createCanvasContext(this.elId, this);
|
||||
this.progressContext = ctx;
|
||||
}
|
||||
// 表示进度的两端为圆形
|
||||
ctx.setLineCap('round');
|
||||
// 设置线条的宽度和颜色
|
||||
ctx.setLineWidth(this.borderWidthPx);
|
||||
ctx.setStrokeStyle(this.circleColor);
|
||||
// 将总过渡时间除以100,得出每修改百分之一进度所需的时间
|
||||
let time = Math.floor(this.duration / 100);
|
||||
// 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的
|
||||
// 3点钟方向开始画图,转为更好理解的12点钟方向开始作图,这需要起始角和终止角同时加上this.startAngle值
|
||||
let endAngle = ((2 * Math.PI) / 100) * progress + this.startAngle;
|
||||
ctx.beginPath();
|
||||
// 半径为整个canvas宽度的一半
|
||||
let radius = this.widthPx / 2;
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false);
|
||||
ctx.stroke();
|
||||
ctx.draw();
|
||||
// 如果变更后新值大于旧值,意味着增大了百分比
|
||||
if (this.newPercent > this.oldPercent) {
|
||||
// 每次递增百分之一
|
||||
progress++;
|
||||
// 如果新增后的值,大于需要设置的值百分比值,停止继续增加
|
||||
if (progress > this.newPercent) return;
|
||||
} else {
|
||||
// 同理于上面
|
||||
progress--;
|
||||
if (progress < this.newPercent) return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
// 定时器,每次操作间隔为time值,为了让进度条有动画效果
|
||||
this.drawCircleByProgress(progress);
|
||||
}, time);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
.u-circle-progress {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-canvas-bg {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.u-canvas {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<view class="u-col" :class="[
|
||||
'u-col-' + span
|
||||
]" :style="{
|
||||
padding: `0 ${Number(gutter)/2 + 'rpx'}`,
|
||||
marginLeft: 100 / 12 * offset + '%',
|
||||
flex: `0 0 ${100 / 12 * span}%`,
|
||||
alignItems: uAlignItem,
|
||||
justifyContent: uJustify,
|
||||
textAlign: textAlign
|
||||
}"
|
||||
@tap="click">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* col 布局单元格
|
||||
* @description 通过基础的 12 分栏,迅速简便地创建布局(搭配<u-row>使用)
|
||||
* @tutorial https://www.uviewui.com/components/layout.html
|
||||
* @property {String Number} span 栅格占据的列数,总12等分(默认0)
|
||||
* @property {String} text-align 文字水平对齐方式(默认left)
|
||||
* @property {String Number} offset 分栏左边偏移,计算方式与span相同(默认0)
|
||||
* @example <u-col span="3"><view class="demo-layout bg-purple"></view></u-col>
|
||||
*/
|
||||
export default {
|
||||
name: "u-col",
|
||||
props: {
|
||||
// 占父容器宽度的多少等分,总分为12份
|
||||
span: {
|
||||
type: [Number, String],
|
||||
default: 12
|
||||
},
|
||||
// 指定栅格左侧的间隔数(总12栏)
|
||||
offset: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
|
||||
justify: {
|
||||
type: String,
|
||||
default: 'start'
|
||||
},
|
||||
// 垂直对齐方式,可选值为top、center、bottom
|
||||
align: {
|
||||
type: String,
|
||||
default: 'center'
|
||||
},
|
||||
// 文字对齐方式
|
||||
textAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 是否阻止事件传播
|
||||
stop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
gutter: 20, // 给col添加间距,左右边距各占一半,从父组件u-row获取
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false;
|
||||
},
|
||||
mounted() {
|
||||
// 获取父组件实例,并赋值给对应的参数
|
||||
this.parent = this.$u.$parent.call(this, 'u-row');
|
||||
if (this.parent) {
|
||||
this.gutter = this.parent.gutter;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uJustify() {
|
||||
if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify;
|
||||
else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify;
|
||||
else return this.justify;
|
||||
},
|
||||
uAlignItem() {
|
||||
if (this.align == 'top') return 'flex-start';
|
||||
if (this.align == 'bottom') return 'flex-end';
|
||||
else return this.align;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(e) {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-col {
|
||||
/* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
|
||||
float: left;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.u-col-0 {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.u-col-1 {
|
||||
width: calc(100%/12);
|
||||
}
|
||||
|
||||
.u-col-2 {
|
||||
width: calc(100%/12 * 2);
|
||||
}
|
||||
|
||||
.u-col-3 {
|
||||
width: calc(100%/12 * 3);
|
||||
}
|
||||
|
||||
.u-col-4 {
|
||||
width: calc(100%/12 * 4);
|
||||
}
|
||||
|
||||
.u-col-5 {
|
||||
width: calc(100%/12 * 5);
|
||||
}
|
||||
|
||||
.u-col-6 {
|
||||
width: calc(100%/12 * 6);
|
||||
}
|
||||
|
||||
.u-col-7 {
|
||||
width: calc(100%/12 * 7);
|
||||
}
|
||||
|
||||
.u-col-8 {
|
||||
width: calc(100%/12 * 8);
|
||||
}
|
||||
|
||||
.u-col-9 {
|
||||
width: calc(100%/12 * 9);
|
||||
}
|
||||
|
||||
.u-col-10 {
|
||||
width: calc(100%/12 * 10);
|
||||
}
|
||||
|
||||
.u-col-11 {
|
||||
width: calc(100%/12 * 11);
|
||||
}
|
||||
|
||||
.u-col-12 {
|
||||
width: calc(100%/12 * 12);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<view class="u-collapse-item" :style="[itemStyle]">
|
||||
<view :hover-stay-time="200" class="u-collapse-head" @tap.stop="headClick" :hover-class="hoverClass" :style="[headStyle]">
|
||||
<block v-if="!$slots['title-all']">
|
||||
<view v-if="!$slots['title']" class="u-collapse-title u-line-1" :style="[{ textAlign: align ? align : 'left' },
|
||||
isShow && activeStyle && !arrow ? activeStyle : '']">
|
||||
{{ title }}
|
||||
</view>
|
||||
<slot v-else name="title" />
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon v-if="arrow" :color="arrowColor" :class="{ 'u-arrow-down-icon-active': isShow }"
|
||||
class="u-arrow-down-icon" name="arrow-down"></u-icon>
|
||||
</view>
|
||||
</block>
|
||||
<slot v-else name="title-all" />
|
||||
</view>
|
||||
<view class="u-collapse-body" :style="[{
|
||||
height: isShow ? height + 'px' : '0'
|
||||
}]">
|
||||
<view class="u-collapse-content" :id="elId" :style="[bodyStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* collapseItem 手风琴Item
|
||||
* @description 通过折叠面板收纳内容区域(搭配u-collapse使用)
|
||||
* @tutorial https://www.uviewui.com/components/collapse.html
|
||||
* @property {String} title 面板标题
|
||||
* @property {String Number} index 主要用于事件的回调,标识那个Item被点击
|
||||
* @property {Boolean} disabled 面板是否可以打开或收起(默认false)
|
||||
* @property {Boolean} open 设置某个面板的初始状态是否打开(默认false)
|
||||
* @property {String Number} name 唯一标识符,如不设置,默认用当前collapse-item的索引值
|
||||
* @property {String} align 标题的对齐方式(默认left)
|
||||
* @property {Object} active-style 不显示箭头时,可以添加当前选择的collapse-item活动样式,对象形式
|
||||
* @event {Function} change 某个item被打开或者收起时触发
|
||||
* @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item>
|
||||
*/
|
||||
export default {
|
||||
name: "u-collapse-item",
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题的对齐方式
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 是否可以点击收起
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// collapse显示与否
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 唯一标识符
|
||||
name: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
//活动样式
|
||||
activeStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 标识当前为第几个
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
elId: this.$u.guid(),
|
||||
height: 0, // body内容的高度
|
||||
headStyle: {}, // 头部样式,对象形式
|
||||
bodyStyle: {}, // 主体部分样式
|
||||
itemStyle: {}, // 每个item的整体样式
|
||||
arrowColor: '', // 箭头的颜色
|
||||
hoverClass: '', // 头部按下时的效果样式类
|
||||
arrow: true, // 是否显示右侧箭头
|
||||
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
open(val) {
|
||||
this.isShow = val;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false;
|
||||
// 获取u-collapse的信息,放在u-collapse是为了方便,不用每个u-collapse-item写一遍
|
||||
this.isShow = this.open;
|
||||
},
|
||||
methods: {
|
||||
// 异步获取内容,或者动态修改了内容时,需要重新初始化
|
||||
init() {
|
||||
this.parent = this.$u.$parent.call(this, 'u-collapse');
|
||||
if(this.parent) {
|
||||
this.nameSync = this.name ? this.name : this.parent.childrens.length;
|
||||
this.parent.childrens.push(this);
|
||||
this.headStyle = this.parent.headStyle;
|
||||
this.bodyStyle = this.parent.bodyStyle;
|
||||
this.arrowColor = this.parent.arrowColor;
|
||||
this.hoverClass = this.parent.hoverClass;
|
||||
this.arrow = this.parent.arrow;
|
||||
this.itemStyle = this.parent.itemStyle;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.queryRect();
|
||||
});
|
||||
},
|
||||
// 点击collapsehead头部
|
||||
headClick() {
|
||||
if (this.disabled) return;
|
||||
if (this.parent && this.parent.accordion == true) {
|
||||
this.parent.childrens.map(val => {
|
||||
// 自身不设置为false,因为后面有this.isShow = !this.isShow;处理了
|
||||
if (this != val) {
|
||||
val.isShow = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.isShow = !this.isShow;
|
||||
// 触发本组件的事件
|
||||
this.$emit('change', {
|
||||
index: this.index,
|
||||
show: this.isShow
|
||||
})
|
||||
// 只有在打开时才发出事件
|
||||
if (this.isShow) this.parent && this.parent.onChange();
|
||||
this.$forceUpdate();
|
||||
},
|
||||
// 查询内容高度
|
||||
queryRect() {
|
||||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
|
||||
// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同
|
||||
this.$uGetRect('#' + this.elId).then(res => {
|
||||
this.height = res.height;
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-collapse-head {
|
||||
position: relative;
|
||||
@include vue-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: $u-main-color;
|
||||
font-size: 30rpx;
|
||||
line-height: 1;
|
||||
padding: 24rpx 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.u-collapse-title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-arrow-down-icon {
|
||||
transition: all 0.3s;
|
||||
margin-right: 20rpx;
|
||||
margin-left: 14rpx;
|
||||
}
|
||||
|
||||
.u-arrow-down-icon-active {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.u-collapse-body {
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.u-collapse-content {
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: $u-tips-color;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<view class="u-collapse">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* collapse 手风琴
|
||||
* @description 通过折叠面板收纳内容区域
|
||||
* @tutorial https://www.uviewui.com/components/collapse.html
|
||||
* @property {Boolean} accordion 是否手风琴模式(默认true)
|
||||
* @property {Boolean} arrow 是否显示标题右侧的箭头(默认true)
|
||||
* @property {String} arrow-color 标题右侧箭头的颜色(默认#909399)
|
||||
* @property {Object} head-style 标题自定义样式,对象形式
|
||||
* @property {Object} body-style 主体自定义样式,对象形式
|
||||
* @property {String} hover-class 样式类名,按下时有效(默认u-hover-class)
|
||||
* @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
|
||||
* @example <u-collapse></u-collapse>
|
||||
*/
|
||||
export default {
|
||||
name:"u-collapse",
|
||||
props: {
|
||||
// 是否手风琴模式
|
||||
accordion: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 头部的样式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 主体的样式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 每一个item的样式
|
||||
itemStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示右侧的箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 箭头的颜色
|
||||
arrowColor: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 标题部分按压时的样式类,"none"为无效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'u-hover-class'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 重新初始化一次内部的所有子元素的高度计算,用于异步获取数据渲染的情况
|
||||
init() {
|
||||
this.childrens.forEach((vm, index) => {
|
||||
vm.init();
|
||||
})
|
||||
},
|
||||
// collapse item被点击,由collapse item调用父组件方法
|
||||
onChange() {
|
||||
let activeItem = [];
|
||||
this.childrens.forEach((vm, index) => {
|
||||
if (vm.isShow) {
|
||||
activeItem.push(vm.nameSync);
|
||||
}
|
||||
})
|
||||
// 如果是手风琴模式,只有一个匹配结果,也即activeItem长度为1,将其转为字符串
|
||||
if (this.accordion) activeItem = activeItem.join('');
|
||||
this.$emit('change', activeItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-notice-bar"
|
||||
:style="{
|
||||
background: computeBgColor,
|
||||
padding: padding
|
||||
}"
|
||||
:class="[
|
||||
type ? `u-type-${type}-light-bg` : ''
|
||||
]"
|
||||
>
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon class="u-left-icon" v-if="volumeIcon" name="volume-fill" :size="volumeSize" :color="computeColor"></u-icon>
|
||||
</view>
|
||||
<swiper :disable-touch="disableTouch" @change="change" :autoplay="autoplay && playState == 'play'" :vertical="vertical" circular :interval="duration" class="u-swiper">
|
||||
<swiper-item v-for="(item, index) in list" :key="index" class="u-swiper-item">
|
||||
<view
|
||||
class="u-news-item u-line-1"
|
||||
:style="[textStyle]"
|
||||
@tap="click(index)"
|
||||
:class="['u-type-' + type]"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon @click="getMore" class="u-right-icon" v-if="moreIcon" name="arrow-right" :size="26" :color="computeColor"></u-icon>
|
||||
<u-icon @click="close" class="u-right-icon" v-if="closeIcon" name="close" :size="24" :color="computeColor"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 显示的内容,数组
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 显示的主题,success|error|primary|info|warning
|
||||
type: {
|
||||
type: String,
|
||||
default: 'warning'
|
||||
},
|
||||
// 是否显示左侧的音量图标
|
||||
volumeIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示右侧的右箭头图标
|
||||
moreIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示右侧的关闭图标
|
||||
closeIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 文字颜色,各图标也会使用文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 滚动方向,row-水平滚动,column-垂直滚动
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 字体大小,单位rpx
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
},
|
||||
// 滚动一个周期的时间长,单位ms
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 2000
|
||||
},
|
||||
// 音量喇叭的大小
|
||||
volumeSize: {
|
||||
type: [Number, String],
|
||||
default: 34
|
||||
},
|
||||
// 水平滚动时的滚动速度,即每秒滚动多少rpx,这有利于控制文字无论多少时,都能有一个恒定的速度
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 160
|
||||
},
|
||||
// 水平滚动时,是否采用衔接形式滚动
|
||||
isCircular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动方向,horizontal-水平滚动,vertical-垂直滚动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
// 播放状态,play-播放,paused-暂停
|
||||
playState: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 是否禁止用手滑动切换
|
||||
// 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
|
||||
disableTouch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 通知的边距
|
||||
padding: {
|
||||
type: [Number, String],
|
||||
default: '18rpx 24rpx'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算字体颜色,如果没有自定义的,就用uview主题颜色
|
||||
computeColor() {
|
||||
if (this.color) return this.color;
|
||||
// 如果是无主题,就默认使用content-color
|
||||
else if(this.type == 'none') return '#606266';
|
||||
else return this.type;
|
||||
},
|
||||
// 文字内容的样式
|
||||
textStyle() {
|
||||
let style = {};
|
||||
if (this.color) style.color = this.color;
|
||||
else if(this.type == 'none') style.color = '#606266';
|
||||
style.fontSize = this.fontSize + 'rpx';
|
||||
return style;
|
||||
},
|
||||
// 垂直或者水平滚动
|
||||
vertical() {
|
||||
if(this.mode == 'horizontal') return false;
|
||||
else return true;
|
||||
},
|
||||
// 计算背景颜色
|
||||
computeBgColor() {
|
||||
if (this.bgColor) return this.bgColor;
|
||||
else if(this.type == 'none') return 'transparent';
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// animation: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 点击通告栏
|
||||
click(index) {
|
||||
this.$emit('click', index);
|
||||
},
|
||||
// 点击关闭按钮
|
||||
close() {
|
||||
this.$emit('close');
|
||||
},
|
||||
// 点击更多箭头按钮
|
||||
getMore() {
|
||||
this.$emit('getMore');
|
||||
},
|
||||
change(e) {
|
||||
let index = e.detail.current;
|
||||
if(index == this.list.length - 1) {
|
||||
this.$emit('end');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-notice-bar {
|
||||
width: 100%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
padding: 18rpx 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-swiper {
|
||||
font-size: 26rpx;
|
||||
height: 32rpx;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.u-swiper-item {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-news-item {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-right-icon {
|
||||
margin-left: 12rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-left-icon {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<view class="u-countdown">
|
||||
<view v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))" :style="{ fontSize: fontSize + 'rpx'}">
|
||||
{{ d }}
|
||||
</view>
|
||||
<view
|
||||
:style="{fontSize: separatorSize + 'rpx', 'margin-right': '6rpx' }"
|
||||
v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))"
|
||||
>
|
||||
天
|
||||
</view>
|
||||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showHours">
|
||||
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
|
||||
{{ h }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-countdown-colon"
|
||||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
|
||||
v-if="showHours"
|
||||
>
|
||||
{{ separator == 'colon' ? ':' : '时' }}
|
||||
</view>
|
||||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showMinutes">
|
||||
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
|
||||
{{ i }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-countdown-colon"
|
||||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
|
||||
v-if="showMinutes"
|
||||
>
|
||||
{{ separator == 'colon' ? ':' : '分' }}
|
||||
</view>
|
||||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showSeconds">
|
||||
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
|
||||
{{ s }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-countdown-colon"
|
||||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
|
||||
v-if="showSeconds && separator == 'zh'"
|
||||
>
|
||||
秒
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* countDown 倒计时
|
||||
* @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
|
||||
* @tutorial https://www.uviewui.com/components/countDown.html
|
||||
* @property {String Number} timestamp 倒计时,单位为秒
|
||||
* @property {Boolean} autoplay 是否自动开始倒计时,如果为false,需手动调用开始方法。见官网说明(默认true)
|
||||
* @property {String} separator 分隔符,colon为英文冒号,zh为中文(默认colon)
|
||||
* @property {String Number} separator-size 分隔符的字体大小,单位rpx(默认30)
|
||||
* @property {String} separator-color 分隔符的颜色(默认#303133)
|
||||
* @property {String Number} font-size 倒计时字体大小,单位rpx(默认30)
|
||||
* @property {Boolean} show-border 是否显示倒计时数字的边框(默认false)
|
||||
* @property {Boolean} hide-zero-day 当"天"的部分为0时,隐藏该字段 (默认true)
|
||||
* @property {String} border-color 数字边框的颜色(默认#303133)
|
||||
* @property {String} bg-color 倒计时数字的背景颜色(默认#ffffff)
|
||||
* @property {String} color 倒计时数字的颜色(默认#303133)
|
||||
* @property {String} height 数字高度值(宽度等同此值),设置边框时看情况是否需要设置此值,单位rpx(默认auto)
|
||||
* @property {Boolean} show-days 是否显示倒计时的"天"部分(默认true)
|
||||
* @property {Boolean} show-hours 是否显示倒计时的"时"部分(默认true)
|
||||
* @property {Boolean} show-minutes 是否显示倒计时的"分"部分(默认true)
|
||||
* @property {Boolean} show-seconds 是否显示倒计时的"秒"部分(默认true)
|
||||
* @event {Function} end 倒计时结束
|
||||
* @event {Function} change 每秒触发一次,回调为当前剩余的倒计秒数
|
||||
* @example <u-count-down ref="uCountDown" :timestamp="86400" :autoplay="false"></u-count-down>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-count-down',
|
||||
props: {
|
||||
// 倒计时的时间,秒为单位
|
||||
timestamp: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 是否自动开始倒计时
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 用英文冒号(colon)或者中文(zh)当做分隔符,false的时候为中文,如:"11:22"或"11时22秒"
|
||||
separator: {
|
||||
type: String,
|
||||
default: 'colon'
|
||||
},
|
||||
// 分隔符的大小,单位rpx
|
||||
separatorSize: {
|
||||
type: [Number, String],
|
||||
default: 30
|
||||
},
|
||||
// 分隔符颜色
|
||||
separatorColor: {
|
||||
type: String,
|
||||
default: "#303133"
|
||||
},
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 字体大小,单位rpx
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 30
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
// 数字框高度,单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否显示数字框
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 是否显示秒
|
||||
showSeconds: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示分钟
|
||||
showMinutes: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示小时
|
||||
showHours: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示“天”
|
||||
showDays: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 当"天"的部分为0时,不显示
|
||||
hideZeroDay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听时间戳的变化
|
||||
timestamp(newVal, oldVal) {
|
||||
// 如果倒计时间发生变化,清除定时器,重新开始倒计时
|
||||
this.clearTimer();
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
d: '00', // 天的默认值
|
||||
h: '00', // 小时的默认值
|
||||
i: '00', // 分钟的默认值
|
||||
s: '00', // 秒的默认值
|
||||
timer: null ,// 定时器
|
||||
seconds: 0, // 记录不停倒计过程中变化的秒数
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 倒计时item的样式,item为分别的时分秒部分的数字
|
||||
itemStyle() {
|
||||
let style = {};
|
||||
if(this.height) {
|
||||
style.height = this.height + 'rpx';
|
||||
style.width = this.height + 'rpx';
|
||||
}
|
||||
if(this.showBorder) {
|
||||
style.borderStyle = 'solid';
|
||||
style.borderColor = this.borderColor;
|
||||
style.borderWidth = '1px';
|
||||
}
|
||||
if(this.bgColor) {
|
||||
style.backgroundColor = this.bgColor;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
// 倒计时数字的样式
|
||||
letterStyle() {
|
||||
let style = {};
|
||||
if(this.fontSize) style.fontSize = this.fontSize + 'rpx';
|
||||
if(this.color) style.color = this.color;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 如果自动倒计时
|
||||
this.autoplay && this.timestamp && this.start();
|
||||
},
|
||||
methods: {
|
||||
// 倒计时
|
||||
start() {
|
||||
// 避免可能出现的倒计时重叠情况
|
||||
this.clearTimer();
|
||||
if (this.timestamp <= 0) return;
|
||||
this.seconds = Number(this.timestamp);
|
||||
this.formatTime(this.seconds);
|
||||
this.timer = setInterval(() => {
|
||||
this.seconds--;
|
||||
// 发出change事件
|
||||
this.$emit('change', this.seconds);
|
||||
if (this.seconds < 0) {
|
||||
return this.end();
|
||||
}
|
||||
this.formatTime(this.seconds);
|
||||
}, 1000);
|
||||
},
|
||||
// 格式化时间
|
||||
formatTime(seconds) {
|
||||
// 小于等于0的话,结束倒计时
|
||||
seconds <= 0 && this.end();
|
||||
let [day, hour, minute, second] = [0, 0, 0, 0];
|
||||
day = Math.floor(seconds / (60 * 60 * 24));
|
||||
// 判断是否显示“天”参数,如果不显示,将天部分的值,加入到小时中
|
||||
// hour为给后面计算秒和分等用的(基于显示天的前提下计算)
|
||||
hour = Math.floor(seconds / (60 * 60)) - day * 24;
|
||||
// showHour为需要显示的小时
|
||||
let showHour = null;
|
||||
if(this.showDays) {
|
||||
showHour = hour;
|
||||
} else {
|
||||
// 如果不显示天数,将“天”部分的时间折算到小时中去
|
||||
showHour = Math.floor(seconds / (60 * 60));
|
||||
}
|
||||
minute = Math.floor(seconds / 60) - hour * 60 - day * 24 * 60;
|
||||
second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
|
||||
// 如果小于10,在前面补上一个"0"
|
||||
showHour = showHour < 10 ? '0' + showHour : showHour;
|
||||
minute = minute < 10 ? '0' + minute : minute;
|
||||
second = second < 10 ? '0' + second : second;
|
||||
day = day < 10 ? '0' + day : day;
|
||||
this.d = day;
|
||||
this.h = showHour;
|
||||
this.i = minute;
|
||||
this.s = second;
|
||||
},
|
||||
// 停止倒计时
|
||||
end() {
|
||||
this.clearTimer();
|
||||
this.$emit('end', {});
|
||||
},
|
||||
reset() {
|
||||
this.clearTimer();
|
||||
this.seconds = Number(this.timestamp);
|
||||
|
||||
this.s = this.timestamp;
|
||||
console.log(this.s)
|
||||
},
|
||||
// 清除定时器
|
||||
clearTimer() {
|
||||
if(this.timer) {
|
||||
// 清除定时器
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-countdown {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-countdown-item {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rpx;
|
||||
border-radius: 6rpx;
|
||||
white-space: nowrap;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.u-countdown-time {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.u-countdown-colon {
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
padding: 0 5rpx;
|
||||
line-height: 1;
|
||||
align-items: center;
|
||||
padding-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.u-countdown-scale {
|
||||
transform: scale(0.9);
|
||||
transform-origin: center center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-count-num"
|
||||
:style="{
|
||||
fontSize: fontSize + 'rpx',
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
color: color
|
||||
}"
|
||||
>
|
||||
{{ displayValue }}
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* countTo 数字滚动
|
||||
* @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
|
||||
* @tutorial https://www.uviewui.com/components/countTo.html
|
||||
* @property {String Number} start-val 开始值
|
||||
* @property {String Number} end-val 结束值
|
||||
* @property {String Number} duration 滚动过程所需的时间,单位ms(默认2000)
|
||||
* @property {Boolean} autoplay 是否自动开始滚动(默认true)
|
||||
* @property {String Number} decimals 要显示的小数位数,见官网说明(默认0)
|
||||
* @property {Boolean} use-easing 滚动结束时,是否缓动结尾,见官网说明(默认true)
|
||||
* @property {String} separator 千位分隔符,见官网说明
|
||||
* @property {String} color 字体颜色(默认#303133)
|
||||
* @property {String Number} font-size 字体大小,单位rpx(默认50)
|
||||
* @property {Boolean} bold 字体是否加粗(默认false)
|
||||
* @event {Function} end 数值滚动到目标值时触发
|
||||
* @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-count-to',
|
||||
props: {
|
||||
// 开始的数值,默认从0增长到某一个数
|
||||
startVal: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 要滚动的目标数值,必须
|
||||
endVal: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
required: true
|
||||
},
|
||||
// 滚动到目标数值的动画持续时间,单位为毫秒(ms)
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 2000
|
||||
},
|
||||
// 设置数值后是否自动开始滚动
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 要显示的小数位数
|
||||
decimals: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 是否在即将到达目标数值的时候,使用缓慢滚动的效果
|
||||
useEasing: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 十进制分割
|
||||
decimal: {
|
||||
type: [Number, String],
|
||||
default: '.'
|
||||
},
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 50
|
||||
},
|
||||
// 是否加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 千位分隔符,类似金额的分割(¥23,321.05中的",")
|
||||
separator: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localStartVal: this.startVal,
|
||||
displayValue: this.formatNumber(this.startVal),
|
||||
printVal: null,
|
||||
paused: false, // 是否暂停
|
||||
localDuration: Number(this.duration),
|
||||
startTime: null, // 开始的时间
|
||||
timestamp: null, // 时间戳
|
||||
remaining: null, // 停留的时间
|
||||
rAF: null,
|
||||
lastTime: 0 // 上一次的时间
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
countDown() {
|
||||
return this.startVal > this.endVal;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
startVal() {
|
||||
this.autoplay && this.start();
|
||||
},
|
||||
endVal() {
|
||||
this.autoplay && this.start();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.autoplay && this.start();
|
||||
},
|
||||
methods: {
|
||||
easingFn(t, b, c, d) {
|
||||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
|
||||
},
|
||||
requestAnimationFrame(callback) {
|
||||
const currTime = new Date().getTime();
|
||||
// 为了使setTimteout的尽可能的接近每秒60帧的效果
|
||||
const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
|
||||
const id = setTimeout(() => {
|
||||
callback(currTime + timeToCall);
|
||||
}, timeToCall);
|
||||
this.lastTime = currTime + timeToCall;
|
||||
return id;
|
||||
},
|
||||
|
||||
cancelAnimationFrame(id) {
|
||||
clearTimeout(id);
|
||||
},
|
||||
// 开始滚动数字
|
||||
start() {
|
||||
this.localStartVal = this.startVal;
|
||||
this.startTime = null;
|
||||
this.localDuration = this.duration;
|
||||
this.paused = false;
|
||||
this.rAF = this.requestAnimationFrame(this.count);
|
||||
},
|
||||
// 暂定状态,重新再开始滚动;或者滚动状态下,暂停
|
||||
reStart() {
|
||||
if (this.paused) {
|
||||
this.resume();
|
||||
this.paused = false;
|
||||
} else {
|
||||
this.stop();
|
||||
this.paused = true;
|
||||
}
|
||||
},
|
||||
// 暂停
|
||||
stop() {
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
},
|
||||
// 重新开始(暂停的情况下)
|
||||
resume() {
|
||||
this.startTime = null;
|
||||
this.localDuration = this.remaining;
|
||||
this.localStartVal = this.printVal;
|
||||
this.requestAnimationFrame(this.count);
|
||||
},
|
||||
// 重置
|
||||
reset() {
|
||||
this.startTime = null;
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
this.displayValue = this.formatNumber(this.startVal);
|
||||
},
|
||||
count(timestamp) {
|
||||
if (!this.startTime) this.startTime = timestamp;
|
||||
this.timestamp = timestamp;
|
||||
const progress = timestamp - this.startTime;
|
||||
this.remaining = this.localDuration - progress;
|
||||
if (this.useEasing) {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
|
||||
} else {
|
||||
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
|
||||
}
|
||||
} else {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
|
||||
} else {
|
||||
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
|
||||
}
|
||||
}
|
||||
if (this.countDown) {
|
||||
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
|
||||
} else {
|
||||
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
|
||||
}
|
||||
this.displayValue = this.formatNumber(this.printVal);
|
||||
if (progress < this.localDuration) {
|
||||
this.rAF = this.requestAnimationFrame(this.count);
|
||||
} else {
|
||||
this.$emit('end');
|
||||
}
|
||||
},
|
||||
// 判断是否数字
|
||||
isNumber(val) {
|
||||
return !isNaN(parseFloat(val));
|
||||
},
|
||||
formatNumber(num) {
|
||||
// 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错
|
||||
num = Number(num);
|
||||
num = num.toFixed(Number(this.decimals));
|
||||
num += '';
|
||||
const x = num.split('.');
|
||||
let x1 = x[0];
|
||||
const x2 = x.length > 1 ? this.decimal + x[1] : '';
|
||||
const rgx = /(\d+)(\d{3})/;
|
||||
if (this.separator && !this.isNumber(this.separator)) {
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, '$1' + this.separator + '$2');
|
||||
}
|
||||
}
|
||||
return x1 + x2;
|
||||
},
|
||||
destroyed() {
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-count-num {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<view class="u-divider" :style="{
|
||||
height: height == 'auto' ? 'auto' : height + 'rpx',
|
||||
backgroundColor: bgColor,
|
||||
marginBottom: marginBottom + 'rpx',
|
||||
marginTop: marginTop + 'rpx'
|
||||
}" @tap="click">
|
||||
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
|
||||
<view v-if="useSlot" class="u-divider-text" :style="{
|
||||
color: color,
|
||||
fontSize: fontSize + 'rpx'
|
||||
}"><slot /></view>
|
||||
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* divider 分割线
|
||||
* @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。
|
||||
* @tutorial https://www.uviewui.com/components/divider.html
|
||||
* @property {String Number} half-width 文字左或右边线条宽度,数值或百分比,数值时单位为rpx
|
||||
* @property {String} border-color 线条颜色,优先级高于type(默认#dcdfe6)
|
||||
* @property {String} color 文字颜色(默认#909399)
|
||||
* @property {String Number} fontSize 字体大小,单位rpx(默认26)
|
||||
* @property {String} bg-color 整个divider的背景颜色(默认呢#ffffff)
|
||||
* @property {String Number} height 整个divider的高度,单位rpx(默认40)
|
||||
* @property {String} type 将线条设置主题色(默认primary)
|
||||
* @property {Boolean} useSlot 是否使用slot传入内容,如果不传入,中间不会有空隙(默认true)
|
||||
* @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0)
|
||||
* @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0)
|
||||
* @event {Function} click divider组件被点击时触发
|
||||
* @example <u-divider color="#fa3534">长河落日圆</u-divider>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-divider',
|
||||
props: {
|
||||
// 单一边divider横线的宽度(数值),单位rpx。或者百分比
|
||||
halfWidth: {
|
||||
type: [Number, String],
|
||||
default: 150
|
||||
},
|
||||
// divider横线的颜色,如设置,
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#dcdfe6'
|
||||
},
|
||||
// 主题色,可以是primary|info|success|warning|error之一值
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
// 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 文字大小,单位rpx
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
},
|
||||
// 整个divider的背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// 整个divider的高度单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
// 上边距
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 下边距
|
||||
marginBottom: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否使用slot传入内容,如果不用slot传入内容,先的中间就不会有空隙
|
||||
useSlot: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lineStyle() {
|
||||
let style = {};
|
||||
if(String(this.halfWidth).indexOf('%') != -1) style.width = this.halfWidth;
|
||||
else style.width = this.halfWidth + 'rpx';
|
||||
// borderColor优先级高于type值
|
||||
if(this.borderColor) style.borderColor = this.borderColor;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
.u-divider {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.u-divider-line {
|
||||
border-bottom: 1px solid $u-border-color;
|
||||
transform: scale(1, 0.5);
|
||||
transform-origin: center;
|
||||
|
||||
&--bordercolor--primary {
|
||||
border-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--bordercolor--success {
|
||||
border-color: $u-type-success;
|
||||
}
|
||||
|
||||
&--bordercolor--error {
|
||||
border-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--bordercolor--info {
|
||||
border-color: $u-type-info;
|
||||
}
|
||||
|
||||
&--bordercolor--warning {
|
||||
border-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
|
||||
.u-divider-text {
|
||||
white-space: nowrap;
|
||||
padding: 0 16rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<view class="u-dropdown-item" v-if="active" @touchmove.stop.prevent="() => {}" @tap.stop.prevent="() => {}">
|
||||
<block v-if="!$slots.default && !$slots.$default">
|
||||
<scroll-view scroll-y="true" :style="{
|
||||
height: $u.addUnit(height)
|
||||
}">
|
||||
<view class="u-dropdown-item__options">
|
||||
<u-cell-group>
|
||||
<u-cell-item @click="cellClick(item.value)" :arrow="false" :title="item.label" v-for="(item, index) in options"
|
||||
:key="index" :title-style="{
|
||||
color: value == item.value ? activeColor : inactiveColor
|
||||
}">
|
||||
<u-icon v-if="value == item.value" name="checkbox-mark" :color="activeColor" size="32"></u-icon>
|
||||
</u-cell-item>
|
||||
</u-cell-group>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</block>
|
||||
<slot v-else />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* dropdown-item 下拉菜单
|
||||
* @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
|
||||
* @tutorial http://uviewui.com/components/dropdown.html
|
||||
* @property {String | Number} v-model 双向绑定选项卡选择值
|
||||
* @property {String} title 菜单项标题
|
||||
* @property {Array[Object]} options 选项数据,如果传入了默认slot,此参数无效
|
||||
* @property {Boolean} disabled 是否禁用此选项卡(默认false)
|
||||
* @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300)
|
||||
* @property {String | Number} height 弹窗下拉内容的高度(内容超出将会滚动)(默认auto)
|
||||
* @example <u-dropdown-item title="标题"></u-dropdown-item>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-dropdown-item',
|
||||
props: {
|
||||
// 当前选中项的value值
|
||||
value: {
|
||||
type: [Number, String, Array],
|
||||
default: ''
|
||||
},
|
||||
// 菜单项标题
|
||||
title: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 选项数据,如果传入了默认slot,此参数无效
|
||||
options: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否禁用此菜单项
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 下拉弹窗的高度
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false, // 当前项是否处于展开状态
|
||||
activeColor: '#2979ff', // 激活时左边文字和右边对勾图标的颜色
|
||||
inactiveColor: '#606266', // 未激活时左边文字和右边对勾图标的颜色
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 监听props是否发生了变化,有些值需要传递给父组件u-dropdown,无法双向绑定
|
||||
propsChange() {
|
||||
return `${this.title}-${this.disabled}`;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
propsChange(n) {
|
||||
// 当值变化时,通知父组件重新初始化,让父组件执行每个子组件的init()方法
|
||||
// 将所有子组件数据重新整理一遍
|
||||
if (this.parent) this.parent.init();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 父组件的实例
|
||||
this.parent = false;
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 获取父组件u-dropdown
|
||||
let parent = this.$u.$parent.call(this, 'u-dropdown');
|
||||
if (parent) {
|
||||
this.parent = parent;
|
||||
// 将子组件的激活颜色配置为父组件设置的激活和未激活时的颜色
|
||||
this.activeColor = parent.activeColor;
|
||||
this.inactiveColor = parent.inactiveColor;
|
||||
// 将本组件的this,放入到父组件的children数组中,让父组件可以操作本(子)组件的方法和属性
|
||||
// push进去前,显判断是否已经存在了本实例,因为在子组件内部数据变化时,会通过父组件重新初始化子组件
|
||||
let exist = parent.children.find(val => {
|
||||
return this === val;
|
||||
})
|
||||
if (!exist) parent.children.push(this);
|
||||
if (parent.children.length == 1) this.active = true;
|
||||
// 父组件无法监听children的变化,故将子组件的title,传入父组件的menuList数组中
|
||||
parent.menuList.push({
|
||||
title: this.title,
|
||||
disabled: this.disabled
|
||||
});
|
||||
}
|
||||
},
|
||||
// cell被点击
|
||||
cellClick(value) {
|
||||
// 修改通过v-model绑定的值
|
||||
this.$emit('input', value);
|
||||
// 通知父组件(u-dropdown)收起菜单
|
||||
this.parent.close();
|
||||
// 发出事件,抛出当前勾选项的value
|
||||
this.$emit('change', value);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<view class="u-dropdown">
|
||||
<view class="u-dropdown__menu" :style="{
|
||||
height: $u.addUnit(height)
|
||||
}" :class="{
|
||||
'u-border-bottom': borderBottom
|
||||
}">
|
||||
<view class="u-dropdown__menu__item" v-for="(item, index) in menuList" :key="index" @tap.stop="menuClick(index)">
|
||||
<view class="u-flex">
|
||||
<text class="u-dropdown__menu__item__text" :style="{
|
||||
color: item.disabled ? '#c0c4cc' : (index === current || highlightIndex == index) ? activeColor : inactiveColor,
|
||||
fontSize: $u.addUnit(titleSize)
|
||||
}">{{item.title}}</text>
|
||||
<view class="u-dropdown__menu__item__arrow" :class="{
|
||||
'u-dropdown__menu__item__arrow--rotate': index === current
|
||||
}">
|
||||
<u-icon :custom-style="{display: 'flex'}" :name="menuIcon" :size="$u.addUnit(menuIconSize)" :color="index === current || highlightIndex == index ? activeColor : '#c0c4cc'"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-dropdown__content" :style="[contentStyle, {
|
||||
transition: `opacity ${duration / 1000}s linear`,
|
||||
top: $u.addUnit(height),
|
||||
height: contentHeight + 'px'
|
||||
}]"
|
||||
@tap="maskClick" @touchmove.stop.prevent>
|
||||
<view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="u-dropdown__content__mask"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* dropdown 下拉菜单
|
||||
* @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
|
||||
* @tutorial http://uviewui.com/components/dropdown.html
|
||||
* @property {String} active-color 标题和选项卡选中的颜色(默认#2979ff)
|
||||
* @property {String} inactive-color 标题和选项卡未选中的颜色(默认#606266)
|
||||
* @property {Boolean} close-on-click-mask 点击遮罩是否关闭菜单(默认true)
|
||||
* @property {Boolean} close-on-click-self 点击当前激活项标题是否关闭菜单(默认true)
|
||||
* @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300)
|
||||
* @property {String | Number} height 标题菜单的高度,单位任意(默认80)
|
||||
* @property {String | Number} border-radius 菜单展开内容下方的圆角值,单位任意(默认0)
|
||||
* @property {Boolean} border-bottom 标题菜单是否显示下边框(默认false)
|
||||
* @property {String | Number} title-size 标题的字体大小,单位任意,数值默认为rpx单位(默认28)
|
||||
* @event {Function} open 下拉菜单被打开时触发
|
||||
* @event {Function} close 下拉菜单被关闭时触发
|
||||
* @example <u-dropdown></u-dropdown>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-dropdown',
|
||||
props: {
|
||||
// 菜单标题和选项的激活态颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 菜单标题和选项的未激活态颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 点击遮罩是否关闭菜单
|
||||
closeOnClickMask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 点击当前激活项标题是否关闭菜单
|
||||
closeOnClickSelf: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 过渡时间
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 300
|
||||
},
|
||||
// 标题菜单的高度,单位任意,数值默认为rpx单位
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 80
|
||||
},
|
||||
// 是否显示下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题的字体大小
|
||||
titleSize: {
|
||||
type: [Number, String],
|
||||
default: 28
|
||||
},
|
||||
// 下拉出来的内容部分的圆角值
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 菜单右侧的icon图标
|
||||
menuIcon: {
|
||||
type: String,
|
||||
default: 'arrow-down'
|
||||
},
|
||||
// 菜单右侧图标的大小
|
||||
menuIconSize: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDropdown: true, // 是否打开下来菜单,
|
||||
menuList: [], // 显示的菜单
|
||||
active: false, // 下拉菜单的状态
|
||||
// 当前是第几个菜单处于激活状态,小程序中此处不能写成false或者"",否则后续将current赋值为0,
|
||||
// 无能的TX没有使用===而是使用==判断,导致程序认为前后二者没有变化,从而不会触发视图更新
|
||||
current: 99999,
|
||||
// 外层内容的样式,初始时处于底层,且透明
|
||||
contentStyle: {
|
||||
zIndex: -1,
|
||||
opacity: 0
|
||||
},
|
||||
// 让某个菜单保持高亮的状态
|
||||
highlightIndex: 99999,
|
||||
contentHeight: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 下拉出来部分的样式
|
||||
popupStyle() {
|
||||
let style = {};
|
||||
// 进行Y轴位移,展开状态时,恢复原位。收齐状态时,往上位移100%,进行隐藏
|
||||
style.transform = `translateY(${this.active ? 0 : '-100%'})`
|
||||
style['transition-duration'] = this.duration / 1000 + 's';
|
||||
style.borderRadius = `0 0 ${this.$u.addUnit(this.borderRadius)} ${this.$u.addUnit(this.borderRadius)}`;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 引用所有子组件(u-dropdown-item)的this,不能在data中声明变量,否则在微信小程序会造成循环引用而报错
|
||||
this.children = [];
|
||||
},
|
||||
mounted() {
|
||||
this.getContentHeight();
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 当某个子组件内容变化时,触发父组件的init,父组件再让每一个子组件重新初始化一遍
|
||||
// 以保证数据的正确性
|
||||
this.menuList = [];
|
||||
this.children.map(child => {
|
||||
child.init();
|
||||
})
|
||||
},
|
||||
// 点击菜单
|
||||
menuClick(index) {
|
||||
// 判断是否被禁用
|
||||
if (this.menuList[index].disabled) return;
|
||||
// 如果点击时的索引和当前激活项索引相同,意味着点击了激活项,需要收起下拉菜单
|
||||
if (index === this.current && this.closeOnClickSelf) {
|
||||
this.close();
|
||||
// 等动画结束后,再移除下拉菜单中的内容,否则直接移除,也就没有下拉菜单收起的效果了
|
||||
setTimeout(() => {
|
||||
this.children[index].active = false;
|
||||
}, this.duration)
|
||||
return;
|
||||
}
|
||||
this.open(index);
|
||||
},
|
||||
// 打开下拉菜单
|
||||
open(index) {
|
||||
// 重置高亮索引,否则会造成多个菜单同时高亮
|
||||
// this.highlightIndex = 9999;
|
||||
// 展开时,设置下拉内容的样式
|
||||
this.contentStyle = {
|
||||
zIndex: 11,
|
||||
}
|
||||
// 标记展开状态以及当前展开项的索引
|
||||
this.active = true;
|
||||
this.current = index;
|
||||
// 历遍所有的子元素,将索引匹配的项标记为激活状态,因为子元素是通过v-if控制切换的
|
||||
// 之所以不是因display: none,是因为nvue没有display这个属性
|
||||
this.children.map((val, idx) => {
|
||||
val.active = index == idx ? true : false;
|
||||
})
|
||||
this.$emit('open', this.current);
|
||||
},
|
||||
// 设置下拉菜单处于收起状态
|
||||
close() {
|
||||
this.$emit('close', this.current);
|
||||
// 设置为收起状态,同时current归位,设置为空字符串
|
||||
this.active = false;
|
||||
this.current = 99999;
|
||||
// 下拉内容的样式进行调整,不透明度设置为0
|
||||
this.contentStyle = {
|
||||
zIndex: -1,
|
||||
opacity: 0
|
||||
}
|
||||
},
|
||||
// 点击遮罩
|
||||
maskClick() {
|
||||
// 如果不允许点击遮罩,直接返回
|
||||
if (!this.closeOnClickMask) return;
|
||||
this.close();
|
||||
},
|
||||
// 外部手动设置某个菜单高亮
|
||||
highlight(index = undefined) {
|
||||
this.highlightIndex = index !== undefined ? index : 99999;
|
||||
},
|
||||
// 获取下拉菜单内容的高度
|
||||
getContentHeight() {
|
||||
// 这里的原理为,因为dropdown组件是相对定位的,它的下拉出来的内容,必须给定一个高度
|
||||
// 才能让遮罩占满菜单一下,直到屏幕底部的高度
|
||||
// this.$u.sys()为uView封装的获取设备信息的方法
|
||||
let windowHeight = this.$u.sys().windowHeight;
|
||||
this.$uGetRect('.u-dropdown__menu').then(res => {
|
||||
// 这里获取的是dropdown的尺寸,在H5上,uniapp获取尺寸是有bug的(以前提出修复过,后来又出现了此bug,目前hx2.8.11版本)
|
||||
// H5端bug表现为元素尺寸的top值为导航栏底部到到元素的上边沿的距离,但是元素的bottom值确是导航栏顶部到元素底部的距离
|
||||
// 二者是互相矛盾的,本质原因是H5端导航栏非原生,uni的开发者大意造成
|
||||
// 这里取菜单栏的botton值合理的,不能用res.top,否则页面会造成滚动
|
||||
this.contentHeight = windowHeight - res.bottom;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-dropdown {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
&__menu {
|
||||
@include vue-flex;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
height: 80rpx;
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__text {
|
||||
font-size: 28rpx;
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
margin-left: 6rpx;
|
||||
transition: transform .3s;
|
||||
align-items: center;
|
||||
@include vue-flex;
|
||||
|
||||
&--rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: absolute;
|
||||
z-index: 8;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&__mask {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
background: rgba(0, 0, 0, .3);
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&__popup {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
transition: all 0.3s;
|
||||
transform: translate3D(0, -100%, 0);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<view class="u-empty" v-if="show" :style="{
|
||||
marginTop: marginTop + 'rpx'
|
||||
}">
|
||||
<u-icon
|
||||
:name="src ? src : 'empty-' + mode"
|
||||
:custom-style="iconStyle"
|
||||
:label="text ? text : icons[mode]"
|
||||
label-pos="bottom"
|
||||
:label-color="color"
|
||||
:label-size="fontSize"
|
||||
:size="iconSize"
|
||||
:color="iconColor"
|
||||
margin-top="14"
|
||||
></u-icon>
|
||||
<view class="u-slot-wrap">
|
||||
<slot name="bottom"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* empty 内容为空
|
||||
* @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
|
||||
* @tutorial https://www.uviewui.com/components/empty.html
|
||||
* @property {String} color 文字颜色(默认#c0c4cc)
|
||||
* @property {String} text 文字提示(默认“无内容”)
|
||||
* @property {String} src 自定义图标路径,如定义,mode参数会失效
|
||||
* @property {String Number} font-size 提示文字的大小,单位rpx(默认28)
|
||||
* @property {String} mode 内置的图标,见官网说明(默认data)
|
||||
* @property {String Number} img-width 图标的宽度,单位rpx(默认240)
|
||||
* @property {String} img-height 图标的高度,单位rpx(默认auto)
|
||||
* @property {String Number} margin-top 组件距离上一个元素之间的距离(默认0)
|
||||
* @property {Boolean} show 是否显示组件(默认true)
|
||||
* @event {Function} click 点击组件时触发
|
||||
* @event {Function} close 点击关闭按钮时触发
|
||||
* @example <u-empty text="所谓伊人,在水一方" mode="list"></u-empty>
|
||||
*/
|
||||
export default {
|
||||
name: "u-empty",
|
||||
props: {
|
||||
// 图标路径
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 提示文字
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#c0c4cc'
|
||||
},
|
||||
// 图标的颜色
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#c0c4cc'
|
||||
},
|
||||
// 图标的大小
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: 120
|
||||
},
|
||||
// 文字大小,单位rpx
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: 26
|
||||
},
|
||||
// 选择预置的图标类型
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'data'
|
||||
},
|
||||
// 图标宽度,单位rpx
|
||||
imgWidth: {
|
||||
type: [String, Number],
|
||||
default: 120
|
||||
},
|
||||
// 图标高度,单位rpx
|
||||
imgHeight: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否显示组件
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 组件距离上一个元素之间的距离
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
icons: {
|
||||
car: '购物车为空',
|
||||
page: '页面不存在',
|
||||
search: '没有搜索结果',
|
||||
address: '没有收货地址',
|
||||
wifi: '没有WiFi',
|
||||
order: '订单为空',
|
||||
coupon: '没有优惠券',
|
||||
favor: '暂无收藏',
|
||||
permission: '无权限',
|
||||
history: '无历史记录',
|
||||
news: '无新闻列表',
|
||||
message: '消息列表为空',
|
||||
list: '列表为空',
|
||||
data: '数据为空'
|
||||
},
|
||||
// icons: [{
|
||||
// icon: 'car',
|
||||
// text: '购物车为空'
|
||||
// },{
|
||||
// icon: 'page',
|
||||
// text: '页面不存在'
|
||||
// },{
|
||||
// icon: 'search',
|
||||
// text: '没有搜索结果'
|
||||
// },{
|
||||
// icon: 'address',
|
||||
// text: '没有收货地址'
|
||||
// },{
|
||||
// icon: 'wifi',
|
||||
// text: '没有WiFi'
|
||||
// },{
|
||||
// icon: 'order',
|
||||
// text: '订单为空'
|
||||
// },{
|
||||
// icon: 'coupon',
|
||||
// text: '没有优惠券'
|
||||
// },{
|
||||
// icon: 'favor',
|
||||
// text: '暂无收藏'
|
||||
// },{
|
||||
// icon: 'permission',
|
||||
// text: '无权限'
|
||||
// },{
|
||||
// icon: 'history',
|
||||
// text: '无历史记录'
|
||||
// },{
|
||||
// icon: 'news',
|
||||
// text: '无新闻列表'
|
||||
// },{
|
||||
// icon: 'message',
|
||||
// text: '消息列表为空'
|
||||
// },{
|
||||
// icon: 'list',
|
||||
// text: '列表为空'
|
||||
// },{
|
||||
// icon: 'data',
|
||||
// text: '数据为空'
|
||||
// }],
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-empty {
|
||||
@include vue-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.u-image {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.u-slot-wrap {
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,384 @@
|
||||
<template>
|
||||
<view class="u-field" :class="{'u-border-top': borderTop, 'u-border-bottom': borderBottom }">
|
||||
<view class="u-field-inner" :class="[type == 'textarea' ? 'u-textarea-inner' : '', 'u-label-postion-' + labelPosition]">
|
||||
<view class="u-label" :class="[required ? 'u-required' : '']" :style="{
|
||||
justifyContent: justifyContent,
|
||||
flex: labelPosition == 'left' ? `0 0 ${labelWidth}rpx` : '1'
|
||||
}">
|
||||
<view class="u-icon-wrap" v-if="icon">
|
||||
<u-icon size="32" :custom-style="iconStyle" :name="icon" :color="iconColor" class="u-icon"></u-icon>
|
||||
</view>
|
||||
<slot name="icon"></slot>
|
||||
<text class="u-label-text" :class="[this.$slots.icon || icon ? 'u-label-left-gap' : '']">{{ label }}</text>
|
||||
</view>
|
||||
<view class="fild-body">
|
||||
<view class="u-flex-1 u-flex" :style="[inputWrapStyle]">
|
||||
<textarea v-if="type == 'textarea'" class="u-flex-1 u-textarea-class" :style="[fieldStyle]" :value="value"
|
||||
:placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength"
|
||||
:focus="focus" :autoHeight="autoHeight" :fixed="fixed" @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm"
|
||||
@tap="fieldClick" />
|
||||
<input
|
||||
v-else
|
||||
:style="[fieldStyle]"
|
||||
:type="type"
|
||||
class="u-flex-1 u-field__input-wrap"
|
||||
:value="value"
|
||||
:password="password || this.type === 'password'"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled"
|
||||
:maxlength="inputMaxlength"
|
||||
:focus="focus"
|
||||
:confirmType="confirmType"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@input="onInput"
|
||||
@confirm="onConfirm"
|
||||
@tap="fieldClick"
|
||||
/>
|
||||
</view>
|
||||
<u-icon :size="clearSize" v-if="clearable && value != '' && focused" name="close-circle-fill" color="#c0c4cc" class="u-clear-icon" @click="onClear"/>
|
||||
<view class="u-button-wrap"><slot name="right" /></view>
|
||||
<u-icon v-if="rightIcon" @click="rightIconClick" :name="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" size="26" class="u-arror-right" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="errorMessage !== false && errorMessage != ''" class="u-error-message" :style="{
|
||||
paddingLeft: labelWidth + 'rpx'
|
||||
}">{{ errorMessage }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* field 输入框
|
||||
* @description 借助此组件,可以实现表单的输入, 有"text"和"textarea"类型的,此外,借助uView的picker和actionSheet组件可以快速实现上拉菜单,时间,地区选择等, 为表单解决方案的利器。
|
||||
* @tutorial https://www.uviewui.com/components/field.html
|
||||
* @property {String} type 输入框的类型(默认text)
|
||||
* @property {String} icon label左边的图标,限uView的图标名称
|
||||
* @property {Object} icon-style 左边图标的样式,对象形式
|
||||
* @property {Boolean} right-icon 输入框右边的图标名称,限uView的图标名称(默认false)
|
||||
* @property {Boolean} required 是否必填,左边您显示红色"*"号(默认false)
|
||||
* @property {String} label 输入框左边的文字提示
|
||||
* @property {Boolean} password 是否密码输入方式(用点替换文字),type为text时有效(默认false)
|
||||
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true)
|
||||
* @property {Number String} label-width label的宽度,单位rpx(默认130)
|
||||
* @property {String} label-align label的文字对齐方式(默认left)
|
||||
* @property {Object} field-style 自定义输入框的样式,对象形式
|
||||
* @property {Number | String} clear-size 清除图标的大小,单位rpx(默认30)
|
||||
* @property {String} input-align 输入框内容对齐方式(默认left)
|
||||
* @property {Boolean} border-bottom 是否显示field的下边框(默认true)
|
||||
* @property {Boolean} border-top 是否显示field的上边框(默认false)
|
||||
* @property {String} icon-color 左边通过icon配置的图标的颜色(默认#606266)
|
||||
* @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true)
|
||||
* @property {String Boolean} error-message 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
|
||||
* @property {String} placeholder 输入框的提示文字
|
||||
* @property {String} placeholder-style placeholder的样式(内联样式,字符串),如"color: #ddd"
|
||||
* @property {Boolean} focus 是否自动获得焦点(默认false)
|
||||
* @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false)
|
||||
* @property {Boolean} disabled 是否不可输入(默认false)
|
||||
* @property {Number String} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140)
|
||||
* @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done)
|
||||
* @event {Function} input 输入框内容发生变化时触发
|
||||
* @event {Function} focus 输入框获得焦点时触发
|
||||
* @event {Function} blur 输入框失去焦点时触发
|
||||
* @event {Function} confirm 点击完成按钮时触发
|
||||
* @event {Function} right-icon-click 通过right-icon生成的图标被点击时触发
|
||||
* @event {Function} click 输入框被点击或者通过right-icon生成的图标被点击时触发,这样设计是考虑到传递右边的图标,一般都为需要弹出"picker"等操作时的场景,点击倒三角图标,理应发出此事件,见上方说明
|
||||
* @example <u-field v-model="mobile" label="手机号" required :error-message="errorMessage"></u-field>
|
||||
*/
|
||||
export default {
|
||||
name:"u-field",
|
||||
props: {
|
||||
icon: String,
|
||||
rightIcon: String,
|
||||
// arrowDirection: {
|
||||
// type: String,
|
||||
// default: 'right'
|
||||
// },
|
||||
required: Boolean,
|
||||
label: String,
|
||||
password: Boolean,
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边标题的宽度单位rpx
|
||||
labelWidth: {
|
||||
type: [Number, String],
|
||||
default: 130
|
||||
},
|
||||
// 对齐方式,left|center|right
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
inputAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
errorMessage: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
placeholder: String,
|
||||
placeholderStyle: String,
|
||||
focus: Boolean,
|
||||
fixed: Boolean,
|
||||
value: [Number, String],
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxlength: {
|
||||
type: [Number, String],
|
||||
default: 140
|
||||
},
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'done'
|
||||
},
|
||||
// lable的位置,可选为 left-左边,top-上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 输入框的自定义样式
|
||||
fieldStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 清除按钮的大小
|
||||
clearSize: {
|
||||
type: [Number, String],
|
||||
default: 30
|
||||
},
|
||||
// lable左边的图标样式,对象形式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示上边框
|
||||
borderTop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否自动去除两端的空格
|
||||
trim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
focused: false,
|
||||
itemIndex: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
inputWrapStyle() {
|
||||
let style = {};
|
||||
style.textAlign = this.inputAlign;
|
||||
// 判断lable的位置,如果是left的话,让input左边两边有间隙
|
||||
if(this.labelPosition == 'left') {
|
||||
style.margin = `0 8rpx`;
|
||||
} else {
|
||||
// 如果lable是top的,input的左边就没必要有间隙了
|
||||
style.marginRight = `8rpx`;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
rightIconStyle() {
|
||||
let style = {};
|
||||
if (this.arrowDirection == 'top') style.transform = 'roate(-90deg)';
|
||||
if (this.arrowDirection == 'bottom') style.transform = 'roate(90deg)';
|
||||
else style.transform = 'roate(0deg)';
|
||||
return style;
|
||||
},
|
||||
labelStyle() {
|
||||
let style = {};
|
||||
if(this.labelAlign == 'left') style.justifyContent = 'flext-start';
|
||||
if(this.labelAlign == 'center') style.justifyContent = 'center';
|
||||
if(this.labelAlign == 'right') style.justifyContent = 'flext-end';
|
||||
return style;
|
||||
},
|
||||
// uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法
|
||||
justifyContent() {
|
||||
if(this.labelAlign == 'left') return 'flex-start';
|
||||
if(this.labelAlign == 'center') return 'center';
|
||||
if(this.labelAlign == 'right') return 'flex-end';
|
||||
},
|
||||
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
|
||||
inputMaxlength() {
|
||||
return Number(this.maxlength)
|
||||
},
|
||||
// label的位置
|
||||
fieldInnerStyle() {
|
||||
let style = {};
|
||||
if(this.labelPosition == 'left') {
|
||||
style.flexDirection = 'row';
|
||||
} else {
|
||||
style.flexDirection = 'column';
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInput(event) {
|
||||
let value = event.detail.value;
|
||||
// 判断是否去除空格
|
||||
if(this.trim) value = this.$u.trim(value);
|
||||
this.$emit('input', value);
|
||||
},
|
||||
onFocus(event) {
|
||||
this.focused = true;
|
||||
this.$emit('focus', event);
|
||||
},
|
||||
onBlur(event) {
|
||||
// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
|
||||
// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
|
||||
setTimeout(() => {
|
||||
this.focused = false;
|
||||
}, 100)
|
||||
this.$emit('blur', event);
|
||||
},
|
||||
onConfirm(e) {
|
||||
this.$emit('confirm', e.detail.value);
|
||||
},
|
||||
onClear(event) {
|
||||
this.$emit('input', '');
|
||||
},
|
||||
rightIconClick() {
|
||||
this.$emit('right-icon-click');
|
||||
this.$emit('click');
|
||||
},
|
||||
fieldClick() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-field {
|
||||
font-size: 28rpx;
|
||||
padding: 20rpx 28rpx;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
.u-field-inner {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-textarea-inner {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.u-textarea-class {
|
||||
min-height: 96rpx;
|
||||
width: auto;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.fild-body {
|
||||
@include vue-flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-arror-right {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.u-label-text {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.u-label-left-gap {
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.u-label-postion-top {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.u-label {
|
||||
width: 130rpx;
|
||||
flex: 1 1 130rpx;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-required::before {
|
||||
content: '*';
|
||||
position: absolute;
|
||||
left: -16rpx;
|
||||
font-size: 14px;
|
||||
color: $u-type-error;
|
||||
height: 9px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.u-field__input-wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
height: 48rpx;
|
||||
flex: 1;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.u-clear-icon {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-error-message {
|
||||
color: $u-type-error;
|
||||
font-size: 26rpx;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.placeholder-style {
|
||||
color: rgb(150, 151, 153);
|
||||
}
|
||||
|
||||
.u-input-class {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.u-button-wrap {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,431 @@
|
||||
<template>
|
||||
<view class="u-form-item" :class="{'u-border-bottom': elBorderBottom, 'u-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')}">
|
||||
<view class="u-form-item__body" :style="{
|
||||
flexDirection: elLabelPosition == 'left' ? 'row' : 'column'
|
||||
}">
|
||||
<!-- 微信小程序中,将一个参数设置空字符串,结果会变成字符串"true" -->
|
||||
<view class="u-form-item--left" :style="{
|
||||
width: uLabelWidth,
|
||||
flex: `0 0 ${uLabelWidth}`,
|
||||
marginBottom: elLabelPosition == 'left' ? 0 : '10rpx',
|
||||
}">
|
||||
<!-- 为了块对齐 -->
|
||||
<view class="u-form-item--left__content" v-if="required || leftIcon || label">
|
||||
<!-- nvue不支持伪元素before -->
|
||||
<text v-if="required" class="u-form-item--left__content--required">*</text>
|
||||
<view class="u-form-item--left__content__icon" v-if="leftIcon">
|
||||
<u-icon :name="leftIcon" :custom-style="leftIconStyle"></u-icon>
|
||||
</view>
|
||||
<view class="u-form-item--left__content__label" :style="[elLabelStyle, {
|
||||
'justify-content': elLabelAlign == 'left' ? 'flex-start' : elLabelAlign == 'center' ? 'center' : 'flex-end'
|
||||
}]">
|
||||
{{label}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-form-item--right u-flex">
|
||||
<view class="u-form-item--right__content">
|
||||
<view class="u-form-item--right__content__slot ">
|
||||
<slot />
|
||||
</view>
|
||||
<view class="u-form-item--right__content__icon u-flex" v-if="$slots.right || rightIcon">
|
||||
<u-icon :custom-style="rightIconStyle" v-if="rightIcon" :name="rightIcon"></u-icon>
|
||||
<slot name="right" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-form-item__message" v-if="validateState === 'error' && showError('message')" :style="{
|
||||
paddingLeft: elLabelPosition == 'left' ? $u.addUnit(elLabelWidth) : '0',
|
||||
}">{{validateMessage}}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/util/emitter.js';
|
||||
import schema from '../../libs/util/async-validator';
|
||||
// 去除警告信息
|
||||
schema.warning = function() {};
|
||||
|
||||
/**
|
||||
* form-item 表单item
|
||||
* @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
|
||||
* @tutorial http://uviewui.com/components/form.html
|
||||
* @property {String} label 左侧提示文字
|
||||
* @property {Object} prop 表单域model对象的属性名,在使用 validate、resetFields 方法的情况下,该属性是必填的
|
||||
* @property {Boolean} border-bottom 是否显示表单域的下划线边框
|
||||
* @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方
|
||||
* @property {String Number} label-width 提示文字的宽度,单位rpx(默认90)
|
||||
* @property {Object} label-style lable的样式,对象形式
|
||||
* @property {String} label-align lable的对齐方式
|
||||
* @property {String} right-icon 右侧自定义字体图标(限uView内置图标)或图片地址
|
||||
* @property {String} left-icon 左侧自定义字体图标(限uView内置图标)或图片地址
|
||||
* @property {Object} left-icon-style 左侧图标的样式,对象形式
|
||||
* @property {Object} right-icon-style 右侧图标的样式,对象形式
|
||||
* @property {Boolean} required 是否显示左边的"*"号,这里仅起展示作用,如需校验必填,请通过rules配置必填规则(默认false)
|
||||
* @example <u-form-item label="姓名"><u-input v-model="form.name" /></u-form-item>
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'u-form-item',
|
||||
mixins: [Emitter],
|
||||
inject: {
|
||||
uForm: {
|
||||
default () {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
// input的label提示语
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 绑定的值
|
||||
prop: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
// label的位置,left-左边,top-上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label的宽度,单位rpx
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// lable的样式,对象形式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// lable字体的对齐方式
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 右侧图标
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左侧图标
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左侧图标的样式
|
||||
leftIconStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 左侧图标的样式
|
||||
rightIconStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialValue: '', // 存储的默认值
|
||||
// isRequired: false, // 是否必填,由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成
|
||||
validateState: '', // 是否校验成功
|
||||
validateMessage: '', // 校验失败的提示语
|
||||
// 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色,
|
||||
errorType: ['message'],
|
||||
fieldValue: '', // 获取当前子组件input的输入的值
|
||||
// 父组件的参数,在computed计算中,无法得知this.parent发生变化,故将父组件的参数值,放到data中
|
||||
parentData: {
|
||||
borderBottom: true,
|
||||
labelWidth: 90,
|
||||
labelPosition: 'left',
|
||||
labelStyle: {},
|
||||
labelAlign: 'left',
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
validateState(val) {
|
||||
this.broadcastInputError();
|
||||
},
|
||||
// 监听u-form组件的errorType的变化
|
||||
"uForm.errorType"(val) {
|
||||
this.errorType = val;
|
||||
this.broadcastInputError();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 计算后的label宽度,由于需要多个判断,故放到computed中
|
||||
uLabelWidth() {
|
||||
// 如果用户设置label为空字符串(微信小程序空字符串最终会变成字符串的'true'),意味着要将label的位置宽度设置为auto
|
||||
return this.elLabelPosition == 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.$u.addUnit(this
|
||||
.elLabelWidth)) : '100%';
|
||||
},
|
||||
showError() {
|
||||
return type => {
|
||||
// 如果errorType数组中含有none,或者toast提示类型
|
||||
if (this.errorType.indexOf('none') >= 0) return false;
|
||||
else if (this.errorType.indexOf(type) >= 0) return true;
|
||||
else return false;
|
||||
}
|
||||
},
|
||||
// label的宽度
|
||||
elLabelWidth() {
|
||||
// label默认宽度为90,优先使用本组件的值,如果没有(如果设置为0,也算是配置了值,依然起效),则用u-form的值
|
||||
return (this.labelWidth != 0 || this.labelWidth != '') ? this.labelWidth : (this.parentData.labelWidth ? this.parentData
|
||||
.labelWidth :
|
||||
90);
|
||||
},
|
||||
// label的样式
|
||||
elLabelStyle() {
|
||||
return Object.keys(this.labelStyle).length ? this.labelStyle : (this.parentData.labelStyle ? this.parentData.labelStyle :
|
||||
{});
|
||||
},
|
||||
// label的位置,左侧或者上方
|
||||
elLabelPosition() {
|
||||
return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition :
|
||||
'left');
|
||||
},
|
||||
// label的对齐方式
|
||||
elLabelAlign() {
|
||||
return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left');
|
||||
},
|
||||
// label的下划线
|
||||
elBorderBottom() {
|
||||
// 子组件的borderBottom默认为空字符串,如果不等于空字符串,意味着子组件设置了值,优先使用子组件的值
|
||||
return this.borderBottom !== '' ? this.borderBottom : this.parentData.borderBottom ? this.parentData.borderBottom :
|
||||
true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
broadcastInputError() {
|
||||
// 子组件发出事件,第三个参数为true或者false,true代表有错误
|
||||
this.broadcast('u-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border'));
|
||||
},
|
||||
// 判断是否需要required校验
|
||||
setRules() {
|
||||
let that = this;
|
||||
// 由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成
|
||||
// 从父组件u-form拿到当前u-form-item需要验证 的规则
|
||||
// let rules = this.getRules();
|
||||
// if (rules.length) {
|
||||
// this.isRequired = rules.some(rule => {
|
||||
// // 如果有必填项,就返回,没有的话,就是undefined
|
||||
// return rule.required;
|
||||
// });
|
||||
// }
|
||||
|
||||
// blur事件
|
||||
this.$on('on-form-blur', that.onFieldBlur);
|
||||
// change事件
|
||||
this.$on('on-form-change', that.onFieldChange);
|
||||
},
|
||||
|
||||
// 从u-form的rules属性中,取出当前u-form-item的校验规则
|
||||
getRules() {
|
||||
// 父组件的所有规则
|
||||
let rules = this.parent.rules;
|
||||
rules = rules ? rules[this.prop] : [];
|
||||
// 保证返回的是一个数组形式
|
||||
return [].concat(rules || []);
|
||||
},
|
||||
|
||||
// blur事件时进行表单校验
|
||||
onFieldBlur() {
|
||||
this.validation('blur');
|
||||
},
|
||||
|
||||
// change事件进行表单校验
|
||||
onFieldChange() {
|
||||
this.validation('change');
|
||||
},
|
||||
|
||||
// 过滤出符合要求的rule规则
|
||||
getFilteredRule(triggerType = '') {
|
||||
let rules = this.getRules();
|
||||
// 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证
|
||||
if (!triggerType) return rules;
|
||||
// 历遍判断规则是否有对应的事件,比如blur,change触发等的事件
|
||||
// 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change']
|
||||
// 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性
|
||||
return rules.filter(res => res.trigger && res.trigger.indexOf(triggerType) !== -1);
|
||||
},
|
||||
|
||||
// 校验数据
|
||||
validation(trigger, callback = () => {}) {
|
||||
// 检验之间,先获取需要校验的值
|
||||
this.fieldValue = this.parent.model[this.prop];
|
||||
// blur和change是否有当前方式的校验规则
|
||||
let rules = this.getFilteredRule(trigger);
|
||||
// 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件u-form会因为
|
||||
// 对count变量的统计错误而无法进入上一层的回调
|
||||
if (!rules || rules.length === 0) {
|
||||
return callback('');
|
||||
}
|
||||
// 设置当前的装填,标识为校验中
|
||||
this.validateState = 'validating';
|
||||
// 调用async-validator的方法
|
||||
let validator = new schema({
|
||||
[this.prop]: rules
|
||||
});
|
||||
validator.validate({
|
||||
[this.prop]: this.fieldValue
|
||||
}, {
|
||||
firstFields: true
|
||||
}, (errors, fields) => {
|
||||
// 记录状态和报错信息
|
||||
this.validateState = !errors ? 'success' : 'error';
|
||||
this.validateMessage = errors ? errors[0].message : '';
|
||||
// 调用回调方法
|
||||
callback(this.validateMessage);
|
||||
});
|
||||
},
|
||||
|
||||
// 清空当前的u-form-item
|
||||
resetField() {
|
||||
this.parent.model[this.prop] = this.initialValue;
|
||||
// 设置为`success`状态,只是为了清空错误标记
|
||||
this.validateState = 'success';
|
||||
}
|
||||
},
|
||||
|
||||
// 组件创建完成时,将当前实例保存到u-form中
|
||||
mounted() {
|
||||
// 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
|
||||
this.parent = this.$u.$parent.call(this, 'u-form');
|
||||
if (this.parent) {
|
||||
// 历遍parentData中的属性,将parent中的同名属性赋值给parentData
|
||||
Object.keys(this.parentData).map(key => {
|
||||
this.parentData[key] = this.parent[key];
|
||||
});
|
||||
// 如果没有传入prop,或者uForm为空(如果u-form-input单独使用,就不会有uForm注入),就不进行校验
|
||||
if (this.prop) {
|
||||
// 将本实例添加到父组件中
|
||||
this.parent.fields.push(this);
|
||||
this.errorType = this.parent.errorType;
|
||||
// 设置初始值
|
||||
this.initialValue = this.fieldValue;
|
||||
// 添加表单校验,这里必须要写在$nextTick中,因为u-form的rules是通过ref手动传入的
|
||||
// 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给u-form,导致规则为空
|
||||
this.$nextTick(() => {
|
||||
this.setRules();
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 组件销毁前,将实例从u-form的缓存中移除
|
||||
beforeDestroy() {
|
||||
// 如果当前没有prop的话表示当前不要进行删除(因为没有注入)
|
||||
if (this.parent && this.prop) {
|
||||
this.parent.fields.map((item, index) => {
|
||||
if (item === this) this.parent.fields.splice(index, 1);
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-form-item {
|
||||
@include vue-flex;
|
||||
// align-items: flex-start;
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
box-sizing: border-box;
|
||||
line-height: $u-form-item-height;
|
||||
flex-direction: column;
|
||||
|
||||
&__border-bottom--error:after {
|
||||
border-color: $u-type-error;
|
||||
}
|
||||
|
||||
&__body {
|
||||
@include vue-flex;
|
||||
}
|
||||
|
||||
&--left {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
padding-right: 10rpx;
|
||||
flex: 1;
|
||||
|
||||
&__icon {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
&--required {
|
||||
position: absolute;
|
||||
left: -16rpx;
|
||||
vertical-align: middle;
|
||||
color: $u-type-error;
|
||||
padding-top: 6rpx;
|
||||
}
|
||||
|
||||
&__label {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--right {
|
||||
flex: 1;
|
||||
|
||||
&__content {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
&__slot {
|
||||
flex: 1;
|
||||
/* #ifndef MP */
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-left: 10rpx;
|
||||
color: $u-light-color;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__message {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: $u-type-error;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<view class="u-form"><slot /></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* form 表单
|
||||
* @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
|
||||
* @tutorial http://uviewui.com/components/form.html
|
||||
* @property {Object} model 表单数据对象
|
||||
* @property {Boolean} border-bottom 是否显示表单域的下划线边框
|
||||
* @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方
|
||||
* @property {String Number} label-width 提示文字的宽度,单位rpx(默认90)
|
||||
* @property {Object} label-style lable的样式,对象形式
|
||||
* @property {String} label-align lable的对齐方式
|
||||
* @property {Object} rules 通过ref设置,见官网说明
|
||||
* @property {Array} error-type 错误的提示方式,数组形式,见上方说明(默认['message'])
|
||||
* @example <u-form :model="form" ref="uForm"></u-form>
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'u-form',
|
||||
props: {
|
||||
// 当前form的需要验证字段的集合
|
||||
model: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 验证规则
|
||||
// rules: {
|
||||
// type: [Object, Function, Array],
|
||||
// default() {
|
||||
// return {};
|
||||
// }
|
||||
// },
|
||||
// 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色,
|
||||
// border-bottom-下边框呈现红色,none-无提示
|
||||
errorType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['message', 'toast']
|
||||
}
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// label的位置,left-左边,top-上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label的宽度,单位rpx
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
default: 90
|
||||
},
|
||||
// lable字体的对齐方式
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// lable的样式,对象形式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
uForm: this
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 存储当前form下的所有u-form-item的实例
|
||||
// 不能定义在data中,否则微信小程序会造成循环引用而报错
|
||||
this.fields = [];
|
||||
},
|
||||
methods: {
|
||||
setRules(rules) {
|
||||
this.rules = rules;
|
||||
},
|
||||
// 清空所有u-form-item组件的内容,本质上是调用了u-form-item组件中的resetField()方法
|
||||
resetFields() {
|
||||
this.fields.map(field => {
|
||||
field.resetField();
|
||||
});
|
||||
},
|
||||
// 校验全部数据
|
||||
validate(callback) {
|
||||
return new Promise(resolve => {
|
||||
// 对所有的u-form-item进行校验
|
||||
let valid = true; // 默认通过
|
||||
let count = 0; // 用于标记是否检查完毕
|
||||
let errorArr = []; // 存放错误信息
|
||||
this.fields.map(field => {
|
||||
// 调用每一个u-form-item实例的validation的校验方法
|
||||
field.validation('', error => {
|
||||
// 如果任意一个u-form-item校验不通过,就意味着整个表单不通过
|
||||
if (error) {
|
||||
valid = false;
|
||||
errorArr.push(error);
|
||||
}
|
||||
// 当历遍了所有的u-form-item时,调用promise的then方法
|
||||
if (++count === this.fields.length) {
|
||||
resolve(valid); // 进入promise的then方法
|
||||
// 判断是否设置了toast的提示方式,只提示最前面的表单域的第一个错误信息
|
||||
if(this.errorType.indexOf('none') === -1 && this.errorType.indexOf('toast') >= 0 && errorArr.length) {
|
||||
this.$u.toast(errorArr[0]);
|
||||
}
|
||||
// 调用回调方法
|
||||
if (typeof callback == 'function') callback(valid);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<u-modal v-model="show" :show-cancel-button="true" confirm-text="升级" title="发现新版本" @cancel="cancel" @confirm="confirm">
|
||||
<view class="u-update-content">
|
||||
<rich-text :nodes="content"></rich-text>
|
||||
</view>
|
||||
</u-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
content: `
|
||||
1. 修复badge组件的size参数无效问题<br>
|
||||
2. 新增Modal模态框组件<br>
|
||||
3. 新增压窗屏组件,可以在APP上以弹窗的形式遮盖导航栏和底部tabbar<br>
|
||||
4. 修复键盘组件在微信小程序上遮罩无效的问题
|
||||
`,
|
||||
}
|
||||
},
|
||||
onReady() {
|
||||
this.show = true;
|
||||
},
|
||||
methods: {
|
||||
cancel() {
|
||||
this.closeModal();
|
||||
},
|
||||
confirm() {
|
||||
this.closeModal();
|
||||
},
|
||||
closeModal() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-full-content {
|
||||
background-color: #00C777;
|
||||
}
|
||||
|
||||
.u-update-content {
|
||||
font-size: 26rpx;
|
||||
color: $u-content-color;
|
||||
line-height: 1.7;
|
||||
padding: 30rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<view class="u-gap" :style="[gapStyle]"></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* gap 间隔槽
|
||||
* @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量
|
||||
* @tutorial https://www.uviewui.com/components/gap.html
|
||||
* @property {String} bg-color 背景颜色(默认#f3f4f6)
|
||||
* @property {String Number} height 分割槽高度,单位rpx(默认30)
|
||||
* @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0)
|
||||
* @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0)
|
||||
* @example <u-gap height="80" bg-color="#bbb"></u-gap>
|
||||
*/
|
||||
export default {
|
||||
name: "u-gap",
|
||||
props: {
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent ' // 背景透明
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 30
|
||||
},
|
||||
// 与上一个组件的距离
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 与下一个组件的距离
|
||||
marginBottom: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
gapStyle() {
|
||||
return {
|
||||
backgroundColor: this.bgColor,
|
||||
height: this.height + 'rpx',
|
||||
marginTop: this.marginTop + 'rpx',
|
||||
marginBottom: this.marginBottom + 'rpx'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<view class="u-grid-item" :hover-class="parentData.hoverClass"
|
||||
:hover-stay-time="200" @tap="click" :style="{
|
||||
background: bgColor,
|
||||
width: width,
|
||||
}">
|
||||
<view class="u-grid-item-box" :style="[customStyle]" :class="[parentData.border ? 'u-border-right u-border-bottom' : '']">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* gridItem 提示
|
||||
* @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配u-grid使用
|
||||
* @tutorial https://www.uviewui.com/components/grid.html
|
||||
* @property {String} bg-color 宫格的背景颜色(默认#ffffff)
|
||||
* @property {String Number} index 点击宫格时,返回的值
|
||||
* @property {Object} custom-style 自定义样式,对象形式
|
||||
* @event {Function} click 点击宫格触发
|
||||
* @example <u-grid-item></u-grid-item>
|
||||
*/
|
||||
export default {
|
||||
name: "u-grid-item",
|
||||
props: {
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// 点击时返回的index
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 自定义样式,对象形式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
padding: '30rpx 0'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
parentData: {
|
||||
hoverClass: '', // 按下去的时候,是否显示背景灰色
|
||||
col: 3, // 父组件划分的宫格数
|
||||
border: true, // 是否显示边框,根据父组件决定
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 父组件的实例
|
||||
this.updateParentData();
|
||||
// this.parent在updateParentData()中定义
|
||||
this.parent.children.push(this);
|
||||
},
|
||||
computed: {
|
||||
// 每个grid-item的宽度
|
||||
width() {
|
||||
return 100 / Number(this.parentData.col) + '%';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取父组件的参数
|
||||
updateParentData() {
|
||||
// 此方法写在mixin中
|
||||
this.getParentData('u-grid');
|
||||
},
|
||||
click() {
|
||||
this.$emit('click', this.index);
|
||||
this.parent && this.parent.click(this.index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-grid-item {
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
|
||||
/* #ifdef MP */
|
||||
position: relative;
|
||||
float: left;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.u-grid-item-hover {
|
||||
background: #f7f7f7 !important;
|
||||
}
|
||||
|
||||
.u-grid-marker-box {
|
||||
position: absolute;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.u-grid-marker-wrap {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.u-grid-item-box {
|
||||
padding: 30rpx 0;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<view class="u-grid" :class="{'u-border-top u-border-left': border}" :style="[gridStyle]"><slot /></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* grid 宫格布局
|
||||
* @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。
|
||||
* @tutorial https://www.uviewui.com/components/grid.html
|
||||
* @property {String Number} col 宫格的列数(默认3)
|
||||
* @property {Boolean} border 是否显示宫格的边框(默认true)
|
||||
* @property {Boolean} hover-class 点击宫格的时候,是否显示按下的灰色背景(默认false)
|
||||
* @event {Function} click 点击宫格触发
|
||||
* @example <u-grid :col="3" @click="click"></u-grid>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-grid',
|
||||
props: {
|
||||
// 分成几列
|
||||
col: {
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
},
|
||||
// 是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 宫格按压时的样式类,"none"为无效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'u-hover-class'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
index: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData() {
|
||||
if(this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(u-radio)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
|
||||
typeof(child.updateParentData) == 'function' && child.updateParentData();
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
|
||||
this.children = [];
|
||||
},
|
||||
computed: {
|
||||
// 计算父组件的值是否发生变化
|
||||
parentData() {
|
||||
return [this.hoverClass, this.col, this.size, this.border];
|
||||
},
|
||||
// 宫格对齐方式
|
||||
gridStyle() {
|
||||
let style = {};
|
||||
switch(this.align) {
|
||||
case 'left':
|
||||
style.justifyContent = 'flex-start';
|
||||
break;
|
||||
case 'center':
|
||||
style.justifyContent = 'center';
|
||||
break;
|
||||
case 'right':
|
||||
style.justifyContent = 'flex-end';
|
||||
break;
|
||||
default: style.justifyContent = 'flex-start';
|
||||
};
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(index) {
|
||||
this.$emit('click', index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-grid {
|
||||
width: 100%;
|
||||
/* #ifdef MP */
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
/* #endif */
|
||||
|
||||
/* #ifndef MP */
|
||||
@include vue-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<view :style="[customStyle]" class="u-icon" @tap="click" :class="['u-icon--' + labelPos]">
|
||||
<image class="u-icon__img" v-if="isImg" :src="name" :mode="imgMode" :style="[imgStyle]"></image>
|
||||
<text v-else class="u-icon__icon" :class="customClass" :style="[iconStyle]" :hover-class="hoverClass"
|
||||
@touchstart="touchstart">
|
||||
<text v-if="showDecimalIcon" :style="[decimalIconStyle]" :class="decimalIconClass" :hover-class="hoverClass"
|
||||
class="u-icon__decimal">
|
||||
</text>
|
||||
</text>
|
||||
<!-- 这里进行空字符串判断,如果仅仅是v-if="label",可能会出现传递0的时候,结果也无法显示 -->
|
||||
<text v-if="label !== ''" class="u-icon__label" :style="{
|
||||
color: labelColor,
|
||||
fontSize: $u.addUnit(labelSize),
|
||||
marginLeft: labelPos == 'right' ? $u.addUnit(marginLeft) : 0,
|
||||
marginTop: labelPos == 'bottom' ? $u.addUnit(marginTop) : 0,
|
||||
marginRight: labelPos == 'left' ? $u.addUnit(marginRight) : 0,
|
||||
marginBottom: labelPos == 'top' ? $u.addUnit(marginBottom) : 0,
|
||||
}">{{ label }}
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* icon 图标
|
||||
* @description 基于字体的图标集,包含了大多数常见场景的图标。
|
||||
* @tutorial https://www.uviewui.com/components/icon.html
|
||||
* @property {String} name 图标名称,见示例图标集
|
||||
* @property {String} color 图标颜色(默认inherit)
|
||||
* @property {String | Number} size 图标字体大小,单位rpx(默认32)
|
||||
* @property {String | Number} label-size label字体大小,单位rpx(默认28)
|
||||
* @property {String} label 图标右侧的label文字(默认28)
|
||||
* @property {String} label-pos label文字相对于图标的位置,只能right或bottom(默认right)
|
||||
* @property {String} label-color label字体颜色(默认#606266)
|
||||
* @property {Object} custom-style icon的样式,对象形式
|
||||
* @property {String} custom-prefix 自定义字体图标库时,需要写上此值
|
||||
* @property {String | Number} margin-left label在右侧时与图标的距离,单位rpx(默认6)
|
||||
* @property {String | Number} margin-top label在下方时与图标的距离,单位rpx(默认6)
|
||||
* @property {String | Number} margin-bottom label在上方时与图标的距离,单位rpx(默认6)
|
||||
* @property {String | Number} margin-right label在左侧时与图标的距离,单位rpx(默认6)
|
||||
* @property {String} label-pos label相对于图标的位置,只能right或bottom(默认right)
|
||||
* @property {String} index 一个用于区分多个图标的值,点击图标时通过click事件传出
|
||||
* @property {String} hover-class 图标按下去的样式类,用法同uni的view组件的hover-class参数,详情见官网
|
||||
* @property {String} width 显示图片小图标时的宽度
|
||||
* @property {String} height 显示图片小图标时的高度
|
||||
* @property {String} top 图标在垂直方向上的定位
|
||||
* @property {String} top 图标在垂直方向上的定位
|
||||
* @property {String} top 图标在垂直方向上的定位
|
||||
* @property {Boolean} show-decimal-icon 是否为DecimalIcon
|
||||
* @property {String} inactive-color 背景颜色,可接受主题色,仅Decimal时有效
|
||||
* @property {String | Number} percent 显示的百分比,仅Decimal时有效
|
||||
* @event {Function} click 点击图标时触发
|
||||
* @example <u-icon name="photo" color="#2979ff" size="28"></u-icon>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-icon',
|
||||
props: {
|
||||
// 图标类名
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标颜色,可接受主题色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 字体大小,单位rpx
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 'inherit'
|
||||
},
|
||||
// 是否显示粗体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 触摸图标时的类名
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义扩展前缀,方便用户扩展自己的图标库
|
||||
customPrefix: {
|
||||
type: String,
|
||||
default: 'uicon'
|
||||
},
|
||||
// 图标右边或者下面的文字
|
||||
label: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// label的位置,只能右边或者下边
|
||||
labelPos: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// label的大小
|
||||
labelSize: {
|
||||
type: [String, Number],
|
||||
default: '28'
|
||||
},
|
||||
// label的颜色
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// label与图标的距离(横向排列)
|
||||
marginLeft: {
|
||||
type: [String, Number],
|
||||
default: '6'
|
||||
},
|
||||
// label与图标的距离(竖向排列)
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: '6'
|
||||
},
|
||||
// label与图标的距离(竖向排列)
|
||||
marginRight: {
|
||||
type: [String, Number],
|
||||
default: '6'
|
||||
},
|
||||
// label与图标的距离(竖向排列)
|
||||
marginBottom: {
|
||||
type: [String, Number],
|
||||
default: '6'
|
||||
},
|
||||
// 图片的mode
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'widthFix'
|
||||
},
|
||||
// 自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 用于显示图片小图标时,图片的宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 用于显示图片小图标时,图片的高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 用于解决某些情况下,让图标垂直居中的用途
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否为DecimalIcon
|
||||
showDecimalIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 背景颜色,可接受主题色,仅Decimal时有效
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#ececec'
|
||||
},
|
||||
// 显示的百分比,仅Decimal时有效
|
||||
percent: {
|
||||
type: [Number, String],
|
||||
default: '50'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
customClass() {
|
||||
let classes = []
|
||||
classes.push(this.customPrefix + '-' + this.name)
|
||||
// uView的自定义图标类名为u-iconfont
|
||||
if (this.customPrefix == 'uicon') {
|
||||
classes.push('u-iconfont')
|
||||
} else {
|
||||
classes.push(this.customPrefix)
|
||||
}
|
||||
// 主题色,通过类配置
|
||||
if (this.showDecimalIcon && this.inactiveColor && this.$u.config.type.includes(this.inactiveColor)) {
|
||||
classes.push('u-icon__icon--' + this.inactiveColor)
|
||||
} else if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
|
||||
// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
|
||||
// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
|
||||
//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
|
||||
classes = classes.join(' ')
|
||||
//#endif
|
||||
return classes
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
style = {
|
||||
fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size),
|
||||
fontWeight: this.bold ? 'bold' : 'normal',
|
||||
// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
|
||||
top: this.$u.addUnit(this.top)
|
||||
}
|
||||
// 非主题色值时,才当作颜色值
|
||||
if (this.showDecimalIcon && this.inactiveColor && !this.$u.config.type.includes(this.inactiveColor)) {
|
||||
style.color = this.inactiveColor
|
||||
} else if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color
|
||||
|
||||
return style
|
||||
},
|
||||
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
|
||||
isImg() {
|
||||
return this.name.indexOf('/') !== -1
|
||||
},
|
||||
imgStyle() {
|
||||
let style = {}
|
||||
// 如果设置width和height属性,则优先使用,否则使用size属性
|
||||
style.width = this.width ? this.$u.addUnit(this.width) : this.$u.addUnit(this.size)
|
||||
style.height = this.height ? this.$u.addUnit(this.height) : this.$u.addUnit(this.size)
|
||||
return style
|
||||
},
|
||||
decimalIconStyle() {
|
||||
let style = {}
|
||||
style = {
|
||||
fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size),
|
||||
fontWeight: this.bold ? 'bold' : 'normal',
|
||||
// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
|
||||
top: this.$u.addUnit(this.top),
|
||||
width: this.percent + '%'
|
||||
}
|
||||
// 非主题色值时,才当作颜色值
|
||||
if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color
|
||||
return style
|
||||
},
|
||||
decimalIconClass() {
|
||||
let classes = []
|
||||
classes.push(this.customPrefix + '-' + this.name)
|
||||
// uView的自定义图标类名为u-iconfont
|
||||
if (this.customPrefix == 'uicon') {
|
||||
classes.push('u-iconfont')
|
||||
} else {
|
||||
classes.push(this.customPrefix)
|
||||
}
|
||||
// 主题色,通过类配置
|
||||
if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
|
||||
else classes.push('u-icon__icon--primary')
|
||||
// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
|
||||
// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
|
||||
//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
|
||||
classes = classes.join(' ')
|
||||
//#endif
|
||||
return classes
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click', this.index)
|
||||
},
|
||||
touchstart() {
|
||||
this.$emit('touchstart', this.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
@import '../../iconfont.css';
|
||||
|
||||
.u-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&--left {
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--top {
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
|
||||
&--primary {
|
||||
color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: $u-type-success;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: $u-type-error;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: $u-type-warning;
|
||||
}
|
||||
|
||||
&--info {
|
||||
color: $u-type-info;
|
||||
}
|
||||
}
|
||||
|
||||
&__decimal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__img {
|
||||
height: auto;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
&__label {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user