代码提交
This commit is contained in:
66
components/Empty/index.vue
Normal file
66
components/Empty/index.vue
Normal file
@ -0,0 +1,66 @@
|
||||
<!--
|
||||
@name: 空状态组件
|
||||
@author: kahu4
|
||||
@date: 2023-10-30 16:47
|
||||
@description:index
|
||||
@update: 2023-10-30 16:47
|
||||
-->
|
||||
<script setup>
|
||||
import { toRefs } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
iconSrc: {},
|
||||
padding: {
|
||||
type: String,
|
||||
default: () => '260rpx 0 0 0'
|
||||
}
|
||||
})
|
||||
|
||||
const {iconSrc, padding} = toRefs(props)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="empty-container"
|
||||
:style="{padding}"
|
||||
>
|
||||
<view class="icon-box">
|
||||
<slot name="icon">
|
||||
<image :src="iconSrc" />
|
||||
</slot>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<slot>空空如也~</slot>
|
||||
</view>
|
||||
<view class="bottom-row">
|
||||
<slot name="bottom"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.empty-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding-top: 260rpx;
|
||||
|
||||
.icon-box {
|
||||
image {
|
||||
width: 170rpx;
|
||||
height: 170rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.info-row {
|
||||
padding: 20rpx 0 30rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
</style>
|
368
components/Header/index.vue
Normal file
368
components/Header/index.vue
Normal file
@ -0,0 +1,368 @@
|
||||
<!--
|
||||
@name: index
|
||||
@author: kahu4
|
||||
@date: 2023-11-09 10:56
|
||||
@description:index
|
||||
@update: 2023-11-09 10:56
|
||||
-->
|
||||
<script setup>
|
||||
import { computed, defineProps, ref, toRefs, unref, watch } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
import { createAnimation } from "@/utils/utils";
|
||||
|
||||
const HEADER_HEIGHT = 40 // header高度
|
||||
|
||||
const {goBack} = useRouter()
|
||||
/**
|
||||
* @property {String} systemBarAreaBg 系统导航条区域背景颜色
|
||||
* @property {String} headerAreaBg header区域背景颜色
|
||||
* @property {String} headerAreaTextColor header区域字体
|
||||
* @property {Boolean} showReturn 是否展示返回按钮
|
||||
* @property {String} returnColor 返回按钮的颜色
|
||||
* @property {Number} returnSize 返回按钮的大小
|
||||
* @property {Boolean||String} textShadow 字体阴影
|
||||
* @property {Boolean} bgChangeByScroll 是否随着页面滚动增加header背景(包括system-bar-area)
|
||||
* @property {String} bgChangeColor 滚动时候变换的颜色
|
||||
* @property {Number} scrollTop 当前页面高度
|
||||
* @property {Boolean} showRight 是否需要右边
|
||||
* @property {number} leftWidth 左侧(返回)宽度(设置了此属性title将不再居中)
|
||||
*
|
||||
*/
|
||||
const props = defineProps({
|
||||
systemBarAreaBg: {
|
||||
type: String,
|
||||
default: () => '#FFFFFF00' // 透明 #FFFFFF00
|
||||
},
|
||||
headerAreaBg: {
|
||||
type: String,
|
||||
default: () => '#FFFFFF00' // 透明 #FFFFFF00
|
||||
},
|
||||
headerAreaTextColor: {
|
||||
type: String,
|
||||
default: () => '#000000'
|
||||
},
|
||||
showReturn: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
returnColor: {
|
||||
type: String,
|
||||
default: () => '#000'
|
||||
},
|
||||
returnSize: {
|
||||
type: Number,
|
||||
default: () => 22
|
||||
},
|
||||
textShadow: {
|
||||
type: [Boolean, String],
|
||||
default: () => false
|
||||
},
|
||||
bgChangeByScroll: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
bgChangeColor: {
|
||||
type: String,
|
||||
default: () => '#fff'
|
||||
},
|
||||
scrollTop: {
|
||||
type: Number,
|
||||
default: () => 0
|
||||
},
|
||||
propUp: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
showRight: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
leftWidth: {
|
||||
type: Number,
|
||||
default: () => 0
|
||||
}
|
||||
})
|
||||
|
||||
const {
|
||||
systemBarAreaBg,
|
||||
headerAreaBg,
|
||||
headerAreaTextColor,
|
||||
showReturn,
|
||||
returnColor,
|
||||
returnSize,
|
||||
textShadow,
|
||||
bgChangeByScroll,
|
||||
bgChangeColor,
|
||||
scrollTop,
|
||||
propUp,
|
||||
showRight,
|
||||
leftWidth
|
||||
} = toRefs(props)
|
||||
|
||||
const emits = defineEmits(['getSystemInfo', 'animation'])
|
||||
|
||||
|
||||
// 高度信息 单位px
|
||||
const heightInfo = ref({
|
||||
safeAreaInsets: {bottom: 0, top: 0, left: 0, right: 0}, // 安全区信息
|
||||
statusBarHeight: 0, // 状态栏高度
|
||||
screenWidth: 0, // 屏幕内部宽度
|
||||
screenHeight: 0, // 屏幕内部高度
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取系统信息,设置导航条
|
||||
*/
|
||||
function getSystemInfo() {
|
||||
const res = uni.getSystemInfoSync();
|
||||
const heightObj = unref(heightInfo)
|
||||
heightObj.safeAreaInsets = res.safeAreaInsets
|
||||
heightObj.statusBarHeight = res.statusBarHeight
|
||||
heightObj.screenWidth = res.screenWidth || res.windowWidth
|
||||
heightObj.screenHeight = res.screenHeight || res.windowHeight
|
||||
}
|
||||
|
||||
// 胶囊信息 单位px
|
||||
const menuInfo = ref({
|
||||
bottom: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: 0
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取小程序右上角信息
|
||||
*/
|
||||
function getMenuInfo() {
|
||||
const menuButtonBoundingClientRect = uni.getMenuButtonBoundingClientRect();
|
||||
if (menuButtonBoundingClientRect) {
|
||||
menuInfo.value = {...menuButtonBoundingClientRect}
|
||||
}
|
||||
}
|
||||
|
||||
// scss全局变量
|
||||
const scssVarStyle = computed(() => {
|
||||
return {
|
||||
'--header-height': `${ HEADER_HEIGHT * 2 }rpx`
|
||||
}
|
||||
})
|
||||
|
||||
// 系统导航条区域样式
|
||||
const systemBarAreaStyle = computed(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: `${ unref(heightInfo).safeAreaInsets.top * 2 }rpx`,
|
||||
background: unref(systemBarAreaBg)
|
||||
}
|
||||
})
|
||||
|
||||
// header区域样式
|
||||
const headerAreaStyle = computed(() => {
|
||||
// 计算margin top
|
||||
// margin-top (导航条高度 - 胶囊高度) / 2 永远确保胶囊在header中央
|
||||
const marginTop = unref(menuInfo).height > 0 ? `-${ ((HEADER_HEIGHT - (unref(menuInfo).height)) / 2) * 2 }rpx` : 0
|
||||
return {
|
||||
width: '100%',
|
||||
background: unref(headerAreaBg),
|
||||
color: unref(headerAreaTextColor),
|
||||
marginTop
|
||||
}
|
||||
})
|
||||
|
||||
// 文本样式
|
||||
const textShadowStyle = computed(() => {
|
||||
return {
|
||||
textShadow: unref(textShadow) ? unref(textShadow) : 'none',
|
||||
}
|
||||
})
|
||||
|
||||
const titleStyle = computed(() => {
|
||||
let width = unref(leftWidth) <= 0 ? '' : `calc( 100vw - var(--side-distance) - var(--side-distance) - ${ leftWidth.value }rpx )`
|
||||
// #ifdef MP-WEIXIN
|
||||
width = unref(leftWidth) <= 0 ? '' : `calc( 100vw - var(--side-distance) - var(--side-distance) - ${ leftWidth.value }rpx - ${ menuInfo.value.width }px )`
|
||||
// #endif
|
||||
return {
|
||||
width,
|
||||
left: unref(leftWidth) <= 0 ? '50%' : `calc( var(--side-distance) + ${ leftWidth.value }rpx )`,
|
||||
textShadow: unref(textShadow) ? unref(textShadow) : 'none',
|
||||
transform: unref(leftWidth) <= 0 ? 'translateX(-50%) translateY(-50%)' : 'translateX(0) translateY(-50%)'
|
||||
}
|
||||
})
|
||||
|
||||
// 滚动后背景样式
|
||||
const scrollMaskStyle = computed(() => {
|
||||
return {
|
||||
background: unref(bgChangeColor),
|
||||
opacity: unref(scrollTransparency)
|
||||
}
|
||||
})
|
||||
|
||||
// 总高度
|
||||
const containerHeight = computed(() => {
|
||||
return (unref(heightInfo).safeAreaInsets.top + HEADER_HEIGHT) * 2
|
||||
})
|
||||
|
||||
let animation
|
||||
const scrollTransparency = ref(0) // 滚动透明度
|
||||
function doCreateAnimation() {
|
||||
const scrollEnd = heightInfo.value.safeAreaInsets.bottom + HEADER_HEIGHT + 100
|
||||
animation = createAnimation(0, scrollEnd, 0, 1)
|
||||
}
|
||||
|
||||
watch(scrollTop, () => {
|
||||
if (!bgChangeByScroll.value) return
|
||||
if (!animation) doCreateAnimation()
|
||||
scrollTransparency.value = animation(unref(scrollTop));
|
||||
emits('animation', scrollTransparency.value)
|
||||
})
|
||||
|
||||
|
||||
defineExpose({containerHeight, heightInfo})
|
||||
|
||||
onLoad(() => {
|
||||
getSystemInfo()
|
||||
doCreateAnimation()
|
||||
// #ifdef MP-WEIXIN
|
||||
getMenuInfo()
|
||||
// #endif
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="container"
|
||||
:style="scssVarStyle"
|
||||
>
|
||||
<view class="header-container">
|
||||
<!-- 头部系统导航条区域 -->
|
||||
<view
|
||||
class="system-bar-area"
|
||||
:style="systemBarAreaStyle"
|
||||
>
|
||||
|
||||
</view>
|
||||
<!-- header -->
|
||||
<view
|
||||
class="header-row"
|
||||
:style="headerAreaStyle"
|
||||
>
|
||||
<view
|
||||
class="left"
|
||||
:style="textShadowStyle"
|
||||
v-if="showReturn"
|
||||
>
|
||||
<slot name="left">
|
||||
<uv-icon
|
||||
name="arrow-left"
|
||||
:color="returnColor"
|
||||
:size="returnSize"
|
||||
@click="goBack"
|
||||
/>
|
||||
</slot>
|
||||
</view>
|
||||
<view
|
||||
class="title"
|
||||
:style="titleStyle"
|
||||
>
|
||||
<slot>
|
||||
</slot>
|
||||
</view>
|
||||
<view
|
||||
class="right"
|
||||
v-if="showRight"
|
||||
>
|
||||
<slot name="right">
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 背景 mask -->
|
||||
<view
|
||||
class="bg-mask"
|
||||
:style="scrollMaskStyle"
|
||||
></view>
|
||||
|
||||
</view>
|
||||
<!-- 撑起 -->
|
||||
<view
|
||||
class="prop-up"
|
||||
:style="{height:`${containerHeight}rpx`}"
|
||||
v-if="propUp"
|
||||
></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
|
||||
.container {
|
||||
--header-height: 0rpx;
|
||||
--side-distance: 30rpx;
|
||||
$side-distance: var(--side-distance); // 侧边距
|
||||
$header-height: var(--header-height); // header高度
|
||||
.header-container {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
|
||||
.system-bar-area {
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: $header-height;
|
||||
position: relative;
|
||||
|
||||
.left, .right, .title {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
height: 100%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.left {
|
||||
left: $side-distance;
|
||||
}
|
||||
|
||||
.right {
|
||||
right: $side-distance;
|
||||
}
|
||||
|
||||
.title {
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-mask {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
box-shadow: 0 0 15rpx rgba(162, 162, 162, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.prop-up {
|
||||
width: 100%;
|
||||
height: $header-height;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
74
components/ListLoadLoading/index.vue
Normal file
74
components/ListLoadLoading/index.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<!--
|
||||
@name: index
|
||||
@author: kahu4
|
||||
@date: 2023-11-06 18:08
|
||||
@description:index
|
||||
@update: 2023-11-06 18:08
|
||||
-->
|
||||
<script setup>
|
||||
import { toRefs } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
default: () => '加载中...'
|
||||
}
|
||||
})
|
||||
const {text} = toRefs(props)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="load-over flex flex-ai__center flex-jc__center">
|
||||
<view class="line"></view>
|
||||
<view class="text-box flex flex-ai__center flex-jc__center">
|
||||
<view
|
||||
:class="{text:true}"
|
||||
:style="{animationDelay: `${index*0.2}s`}"
|
||||
v-for="(item,index) in text"
|
||||
:key="index"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.load-over {
|
||||
font-size: 28rpx;
|
||||
position: relative;
|
||||
color: $tips-color;
|
||||
|
||||
.line {
|
||||
width: 130rpx;
|
||||
height: 2rpx;
|
||||
border-radius: 2rpx;
|
||||
background: $tips-color;
|
||||
}
|
||||
|
||||
.text-box {
|
||||
@include usePadding(30, 20);
|
||||
|
||||
.text {
|
||||
@include usePadding(5, 0);
|
||||
animation: jump 3s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes jump {
|
||||
0%, 60% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
20% {
|
||||
transform: translateY(-15rpx);
|
||||
}
|
||||
40% {
|
||||
transform: translateY(15rpx);
|
||||
}
|
||||
}
|
||||
</style>
|
44
components/ListLoadOver/index.vue
Normal file
44
components/ListLoadOver/index.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<!--
|
||||
@name: index
|
||||
@author: kahu4
|
||||
@date: 2023-11-06 18:08
|
||||
@description:index
|
||||
@update: 2023-11-06 18:08
|
||||
-->
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="load-over flex flex-ai__center flex-jc__center">
|
||||
<view class="line"></view>
|
||||
<view class="text">
|
||||
<slot>
|
||||
到底了
|
||||
</slot>
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.load-over {
|
||||
font-size: 28rpx;
|
||||
position: relative;
|
||||
color: $tips-color;
|
||||
|
||||
.line {
|
||||
width: 130rpx;
|
||||
height: 2rpx;
|
||||
border-radius: 2rpx;
|
||||
background: $tips-color;
|
||||
}
|
||||
|
||||
.text {
|
||||
@include usePadding(30, 20)
|
||||
}
|
||||
}
|
||||
</style>
|
140
components/Modal/index.vue
Normal file
140
components/Modal/index.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<!--
|
||||
@name: index
|
||||
@author: kahu4
|
||||
@date: 2023-10-31 15:14
|
||||
@description:index
|
||||
@update: 2023-10-31 15:14
|
||||
-->
|
||||
<script setup>
|
||||
import Popup from "@/components/Popup/index.vue";
|
||||
import { ref, toRefs } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
// 内容
|
||||
content: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
// 确认文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: () => '确认'
|
||||
},
|
||||
// 取消文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: () => '取消'
|
||||
},
|
||||
// 是否展示取消
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
// 是否展示提交
|
||||
showConfirm: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
}
|
||||
})
|
||||
|
||||
const {content, confirmText, cancelText, showCancel, showConfirm} = toRefs(props)
|
||||
|
||||
const emits = defineEmits(['confirm', 'cancel'])
|
||||
|
||||
const popupRef = ref()
|
||||
|
||||
function show() {
|
||||
popupRef.value.show()
|
||||
}
|
||||
|
||||
function close() {
|
||||
// something before close todo
|
||||
popupRef.value.close()
|
||||
}
|
||||
|
||||
function clickBtn(type = 'confirm') {
|
||||
emits(type)
|
||||
close()
|
||||
}
|
||||
|
||||
defineExpose({show, close})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Popup
|
||||
ref="popupRef"
|
||||
:showCloseable="false"
|
||||
mode="center"
|
||||
>
|
||||
<view class="modal-inner">
|
||||
<view class="content">
|
||||
<slot>
|
||||
{{ content }}
|
||||
</slot>
|
||||
</view>
|
||||
<view
|
||||
class="btn-group"
|
||||
v-if="showCancel||showConfirm"
|
||||
>
|
||||
<view
|
||||
class="btn cancel"
|
||||
v-if="showCancel"
|
||||
@click="clickBtn('cancel')"
|
||||
>
|
||||
{{ cancelText }}
|
||||
</view>
|
||||
<view
|
||||
class="btn"
|
||||
v-if="showConfirm"
|
||||
@click="clickBtn('confirm')"
|
||||
>
|
||||
{{ confirmText }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</Popup>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.modal-inner {
|
||||
width: 70vw;
|
||||
padding: 50rpx 40rpx 20rpx 50rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
gap: 30rpx;
|
||||
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
flex: 1 0 40%;
|
||||
text-align: center;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border: 1rpx solid #ee6d46;
|
||||
background: #ee6d46;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
background: #fff;
|
||||
color: #ee6d46;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
28
components/PayPopup/index.data.js
Normal file
28
components/PayPopup/index.data.js
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @name: index.data
|
||||
* @author: kahu4
|
||||
* @date: 2023-11-08 11:08
|
||||
* @description:index.data
|
||||
* @update: 2023-11-08 11:08
|
||||
* */
|
||||
import wechatIcon from "@/static/icon/pay/weixin.png";
|
||||
import { PayType } from "@/utils/paymentUtils";
|
||||
import aliIcon from "@/static/icon/pay/zhifubao.png";
|
||||
|
||||
export const payRows = [
|
||||
{
|
||||
label: '微信支付',
|
||||
eLabel: 'Wechat Pay',
|
||||
icon: wechatIcon,
|
||||
type: PayType["0"],
|
||||
disabled: false
|
||||
|
||||
},
|
||||
{
|
||||
label: '支付宝支付',
|
||||
eLabel: 'ALi Pay',
|
||||
icon: aliIcon,
|
||||
type: PayType["2"],
|
||||
disabled: true
|
||||
}
|
||||
]
|
165
components/PayPopup/index.vue
Normal file
165
components/PayPopup/index.vue
Normal file
@ -0,0 +1,165 @@
|
||||
<!--
|
||||
@name: 付款弹窗
|
||||
@author: kahu
|
||||
@date: 2023-11-08 10:53
|
||||
@description:index
|
||||
@update: 2023-11-08 10:53
|
||||
-->
|
||||
<script setup>
|
||||
import Popup from '@/components/Popup/index.vue';
|
||||
import { ref, unref } from "vue";
|
||||
import { payRows } from "./index.data";
|
||||
import UvRadioGroup from "@/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue";
|
||||
import UvRadio from "@/uni_modules/uv-radio/components/uv-radio/uv-radio.vue";
|
||||
import { doPayment, PayType } from "@/utils/paymentUtils";
|
||||
import { orderInfo as orderInfoRequest } from "@/api/order";
|
||||
import { useInterface } from "@/hooks/useInterface";
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
|
||||
const {toast} = useInterface()
|
||||
const {push} = useRouter()
|
||||
const emits = defineEmits(['confirm', 'close']);
|
||||
|
||||
const popupRef = ref()
|
||||
|
||||
async function show(orderId) {
|
||||
await doGetOrderDetail(orderId)
|
||||
unref(popupRef).show()
|
||||
}
|
||||
|
||||
function close() {
|
||||
emits('close')
|
||||
}
|
||||
|
||||
defineExpose({show})
|
||||
|
||||
const payType = ref(PayType["0"]) // 支付方式
|
||||
|
||||
const orderInfo = ref({}) // 订单信息
|
||||
|
||||
async function doGetOrderDetail(orderId) {
|
||||
orderInfo.value = await orderInfoRequest({key: orderId});
|
||||
}
|
||||
|
||||
const subLoading = ref(false)
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
subLoading.value = true
|
||||
await doPayment({type: payType.value, payInfo: orderInfo.value})
|
||||
subLoading.value = false
|
||||
emits('confirm')
|
||||
close()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
push({url: '/pages/payStatus/index?type=2'})
|
||||
toast({title: '支付失败了'})
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Popup
|
||||
ref="popupRef"
|
||||
title="支付"
|
||||
@close="close"
|
||||
>
|
||||
<view class="pay-container">
|
||||
<uv-radio-group
|
||||
placement="column"
|
||||
iconPlacement="right"
|
||||
v-model="payType"
|
||||
class="pay-box__inner flex flex-ai__center flex-jc__center flex-wrap"
|
||||
shape="circle"
|
||||
activeColor="#ec6e47"
|
||||
>
|
||||
<template
|
||||
v-for="pay in payRows"
|
||||
:key="pay.type"
|
||||
>
|
||||
<uv-radio
|
||||
:name="pay.type"
|
||||
:disabled="pay.disabled"
|
||||
>
|
||||
<view class="pay-row flex flex-ai__center">
|
||||
<image
|
||||
class="icon"
|
||||
:src="pay.icon"
|
||||
/>
|
||||
<view class="info">
|
||||
<view class="label">
|
||||
{{ pay.label }}
|
||||
</view>
|
||||
<view>
|
||||
{{ pay.eLabel }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uv-radio>
|
||||
</template>
|
||||
</uv-radio-group>
|
||||
<view
|
||||
class="animation-button sub-button"
|
||||
:class="{active:subLoading}"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
立即支付
|
||||
</view>
|
||||
</view>
|
||||
</Popup>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.pay-container {
|
||||
@include usePadding(35, 0);
|
||||
width: 100%;
|
||||
|
||||
.pay-row {
|
||||
|
||||
@include usePadding(0, 20);
|
||||
font-size: 20rpx;
|
||||
color: $tips-color;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.info {
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.uv-radio) {
|
||||
width: 100%;
|
||||
transition: all .3s;
|
||||
|
||||
&:active {
|
||||
scale: 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-button {
|
||||
$button-height: 80rpx;
|
||||
margin: 20rpx auto;
|
||||
height: $button-height;
|
||||
line-height: $button-height;
|
||||
border-radius: $button-height;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
169
components/Popup/index.vue
Normal file
169
components/Popup/index.vue
Normal file
@ -0,0 +1,169 @@
|
||||
<!--
|
||||
@name: index
|
||||
@author: kahu4
|
||||
@date: 2023-10-31 15:06
|
||||
@description:index
|
||||
@update: 2023-10-31 15:06
|
||||
-->
|
||||
<script setup>
|
||||
import { nextTick, ref, toRefs } from "vue";
|
||||
import UniPopup from "@/components/uniComponents/UPopup/uni-popup/uni-popup.vue";
|
||||
|
||||
/** some javascript code in here */
|
||||
const emit = defineEmits(['open', 'close'])
|
||||
/**
|
||||
* @property {String} title 标题
|
||||
* @property {String} mode 模式
|
||||
* @value top 顶部弹出
|
||||
* @value center 中间弹出
|
||||
* @value bottom 底部弹出
|
||||
* @value left 左侧弹出
|
||||
* @value right 右侧弹出
|
||||
* @value message 消息提示
|
||||
* @value dialog 对话框
|
||||
* @value share 底部分享示例
|
||||
* @property {Boolean} showCloseable 是否展示关闭按钮
|
||||
* @property {Boolean} isMaskClick 是否点击masker关闭弹窗
|
||||
* @event {Function} open 打开弹窗
|
||||
* @event {Function} close 关闭回调
|
||||
*/
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: () => 'bottom'
|
||||
},
|
||||
showCloseable: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
isMaskClick: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
})
|
||||
|
||||
const {title, mode, showCloseable, isMaskClick} = toRefs(props)
|
||||
|
||||
const popup = ref()
|
||||
const isShow = ref(false)
|
||||
/**
|
||||
* 打开
|
||||
*/
|
||||
const show = () => {
|
||||
nextTick(() => {
|
||||
isShow.value = true
|
||||
popup.value.open()
|
||||
emit('open')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭回调
|
||||
*/
|
||||
const close = () => {
|
||||
isShow.value = false
|
||||
popup.value.close()
|
||||
}
|
||||
|
||||
const handlePopupChange = (e) => {
|
||||
if (!e.show) emit('close')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
close
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UniPopup
|
||||
ref="popup"
|
||||
:type="mode"
|
||||
:is-mask-click="isMaskClick"
|
||||
background-color="#fff"
|
||||
@change="handlePopupChange"
|
||||
class="y-popup"
|
||||
>
|
||||
<view class="popup_inner">
|
||||
<view
|
||||
class="head"
|
||||
v-if="title||showCloseable"
|
||||
>
|
||||
<view></view>
|
||||
<view>{{ title }}</view>
|
||||
<slot name="rightOption">
|
||||
<view
|
||||
@click="close"
|
||||
v-if="showCloseable"
|
||||
>
|
||||
<uv-icon
|
||||
name="close"
|
||||
color="#000"
|
||||
size="16"
|
||||
/>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<!-- 如果是h5 增加tabbar高度 -->
|
||||
<!-- <view class="h5-tabbar-height"></view>-->
|
||||
</UniPopup>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.popup_inner {
|
||||
padding: 20rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
.head {
|
||||
padding: 20rpx 0;
|
||||
font-weight: bolder;
|
||||
font-size: 36rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.content {
|
||||
.tips {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 22rpx;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.option_list_box {
|
||||
.option_item {
|
||||
margin: 15rpx 0;
|
||||
|
||||
.icon {
|
||||
margin-right: 30rpx !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.button_content {
|
||||
margin-top: 50rpx;
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background: #ee6d46;
|
||||
font-size: 40rpx;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 80rpx;
|
||||
border-radius: 80rpx;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
@ -7,18 +7,20 @@
|
||||
{{ title }}
|
||||
</view>
|
||||
<view class="activity-header-subtitle">
|
||||
{{ subtitle }}
|
||||
<slot name="subTitle">
|
||||
{{ subtitle }}
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="activity-header-more"
|
||||
@tap="handleMoreClick"
|
||||
class="activity-header-more"
|
||||
@tap="handleMoreClick"
|
||||
>
|
||||
<view class="activity-header-more-info">{{ more }}</view>
|
||||
<img
|
||||
class="image"
|
||||
src="@/static/images/next.png"
|
||||
alt=""
|
||||
class="image"
|
||||
src="@/static/images/next.png"
|
||||
alt=""
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
@ -31,6 +33,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps(["title", "subtitle", 'more'])
|
||||
|
||||
const title = ref(props.title)
|
||||
@ -46,7 +49,7 @@ const handleMoreClick = () => {
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.activity {
|
||||
|
||||
&-header {
|
||||
@ -94,7 +97,8 @@ const handleMoreClick = () => {
|
||||
}
|
||||
}
|
||||
|
||||
&-body {}
|
||||
&-body {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -11,5 +11,5 @@ const props = defineProps(['size'])
|
||||
const size = ref(props.size)
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
@ -33,7 +33,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.buy-progress {
|
||||
&-info {
|
||||
flex: 1;
|
||||
|
@ -10,12 +10,11 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
const props = defineProps(['class', 'width'])
|
||||
console.log("gxs --> % props:\n", props)
|
||||
const className = ref(props.class)
|
||||
const width = ref(props.width)
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.card {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
|
@ -133,7 +133,6 @@ const emit = defineEmits(['callback'])
|
||||
|
||||
const items = ref(props.items || [])
|
||||
// const defaultValue = ref(props.defaultValue)
|
||||
// console.log("--> % defaultValue:\n", defaultValue)
|
||||
|
||||
const value = ref(props.value || '请选择')
|
||||
const province = ref(props.items)
|
||||
@ -151,7 +150,6 @@ watch(() => props.items, (next) => {
|
||||
})
|
||||
|
||||
watch(() => props.defaultValue, (next) => {
|
||||
console.log("--> % defaultValue % next:\n", next)
|
||||
value.value = `${next.province.name} ${next.city.name} ${next.district.name}`
|
||||
setDefaultValue(items.value, next)
|
||||
})
|
||||
@ -179,7 +177,6 @@ const setDefaultValue = (items, value) => {
|
||||
}
|
||||
province.value = items
|
||||
items.map(prov => {
|
||||
console.log("--> % setDefaultValue % prov:\n", prov)
|
||||
if (prov.name == value.province.name) {
|
||||
city.value = prov.id
|
||||
provinceActive.value = {
|
||||
@ -205,7 +202,6 @@ const setDefaultValue = (items, value) => {
|
||||
})
|
||||
}
|
||||
})
|
||||
console.log(provinceActive.value, cityActive.value, districtActive.value)
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
@ -248,23 +244,8 @@ const selectCity = (selectItem, index) => {
|
||||
}
|
||||
|
||||
const selectDistrict = (selectItem, index) => {
|
||||
console.log("--> % selectDistrict % selectItem:\n", selectItem)
|
||||
districtActive.value = selectItem
|
||||
value.value = `${provinceActive.value?.name} ${cityActive.value?.name} ${districtActive.value?.name}`
|
||||
console.log({
|
||||
province: {
|
||||
id: provinceActive.value?.id,
|
||||
name: provinceActive.value?.name,
|
||||
},
|
||||
city: {
|
||||
id: cityActive.value?.id,
|
||||
name: cityActive.value?.name,
|
||||
},
|
||||
district: {
|
||||
id: districtActive.value?.id,
|
||||
name: districtActive.value?.name,
|
||||
},
|
||||
})
|
||||
emit('callback', {
|
||||
province: {
|
||||
id: provinceActive.value?.id,
|
||||
@ -283,7 +264,7 @@ const selectDistrict = (selectItem, index) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.cityselect {
|
||||
width: 100%;
|
||||
height: 75%;
|
||||
|
@ -10,7 +10,7 @@ const props = defineProps(['min'])
|
||||
const min = ref(props?.min != undefined)
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 0 34rpx;
|
||||
|
||||
|
106
components/detail-reply/detail-reply.vue
Normal file
106
components/detail-reply/detail-reply.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<!-- <uv-scroll-list>-->
|
||||
<view class="reply">
|
||||
<view class="reply-cont">
|
||||
<view class="reply-user">
|
||||
<view class="reply-user-pic">
|
||||
<uv-image
|
||||
:src="data.avatar"
|
||||
width="50rpx"
|
||||
height="50rpx"
|
||||
></uv-image>
|
||||
</view>
|
||||
<view class="reply-user-name">
|
||||
{{ data.nickname }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="reply-text">
|
||||
{{ data.comment }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="reply-pic flex flex-ai__center"
|
||||
v-if="data.pics && data.pics.length>0"
|
||||
>
|
||||
<template
|
||||
v-for="(pic,index) in data.pics.slice(0,1)"
|
||||
:key="index"
|
||||
>
|
||||
<image
|
||||
class="image"
|
||||
:src="pic"
|
||||
@click="doPreviewImage(index, data.pics)"
|
||||
/>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<!-- </uv-scroll-list>-->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { toRefs } from 'vue';
|
||||
import { useImage } from "@/hooks/useImage";
|
||||
|
||||
const props = defineProps(['data'])
|
||||
|
||||
const {data} = toRefs(props);
|
||||
|
||||
const {preview} = useImage()
|
||||
|
||||
function doPreviewImage(current, urls) {
|
||||
preview({
|
||||
current,
|
||||
urls,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.reply {
|
||||
display: flex;
|
||||
border: 1rpx solid #E6E6E6;
|
||||
border-radius: 15rpx;
|
||||
overflow: hidden;
|
||||
width: 565rpx;
|
||||
margin-top: 30rpx;
|
||||
|
||||
&-cont {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
|
||||
.reply-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-pic {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-text {
|
||||
font-size: 24rpx;
|
||||
line-height: 32rpx;
|
||||
color: #333333;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&-pic {
|
||||
width: 187rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-pic {
|
||||
|
||||
.image {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -16,11 +16,10 @@ import { ref } from 'vue'
|
||||
const props = defineProps(['data'])
|
||||
|
||||
const goodsData = props.data
|
||||
console.log("--> % goodsData:\n", goodsData)
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.goods {
|
||||
width: 100%;
|
||||
padding: 30rpx 0;
|
||||
|
@ -1,144 +1,251 @@
|
||||
<template>
|
||||
<uv-popup
|
||||
ref="popupRef"
|
||||
mode="bottom"
|
||||
:style="{ height: '50%' }"
|
||||
round="round"
|
||||
<Popup
|
||||
ref="popupRef"
|
||||
:showCloseable="false"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<view
|
||||
class="goodAttrSelect"
|
||||
v-if="storeInfo"
|
||||
class="goodAttrSelect"
|
||||
>
|
||||
<view class="goodAttrSelect-goods">
|
||||
<goods
|
||||
list
|
||||
min
|
||||
:storeName="storeInfo.storeName"
|
||||
:price="storeInfo.price"
|
||||
surplus="200"
|
||||
:data="storeInfo"
|
||||
/>
|
||||
<view
|
||||
class="goodAttrSelect-goods"
|
||||
v-if="curAttr"
|
||||
>
|
||||
<uv-image
|
||||
class="attr-image"
|
||||
:src="curAttr.image || detailData.storeInfo.image"
|
||||
width="150rpx"
|
||||
height="150rpx"
|
||||
></uv-image>
|
||||
<view class="attr-info">
|
||||
<view class="name">
|
||||
{{ storeInfo.storeName }}
|
||||
</view>
|
||||
<view class="attr-info-bottom">
|
||||
<view class="price">¥{{ curAttr.price }}</view>
|
||||
<view class="stock">库存:{{ curAttr.stock }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<div class="line"></div>
|
||||
<view class="goodAttrSelect-attr row">
|
||||
<view
|
||||
class="goodAttrSelect-attr row"
|
||||
v-if="curAttr"
|
||||
>
|
||||
<view class="goodAttrSelect-attr-title">
|
||||
数量
|
||||
</view>
|
||||
<view class="goodAttrSelect-attr-content">
|
||||
<uv-number-box
|
||||
v-model="storeNum"
|
||||
min="1"
|
||||
/>
|
||||
<!-- cart number -->
|
||||
<view
|
||||
class="cart-num flex flex-ai__center flex-jc__sb"
|
||||
@click.stop=""
|
||||
>
|
||||
<view
|
||||
class="button"
|
||||
@click="handleCartNumberChange(curAttr,'minus')"
|
||||
>
|
||||
<uv-icon
|
||||
name="minus"
|
||||
color="#333"
|
||||
size="12"
|
||||
></uv-icon>
|
||||
</view>
|
||||
<view class="input">
|
||||
<input
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
v-model="storeNum"
|
||||
@blur="(e)=>handleCartNumberInputChange(e,curAttr)"
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="button"
|
||||
@click="handleCartNumberChange(curAttr,'plus')"
|
||||
>
|
||||
<uv-icon
|
||||
name="plus"
|
||||
color="#333"
|
||||
size="12"
|
||||
></uv-icon>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <uv-number-box
|
||||
v-model="storeNum"
|
||||
min="1"
|
||||
:max="curAttr.stock"
|
||||
></uv-number-box>-->
|
||||
</view>
|
||||
</view>
|
||||
<div class="line"></div>
|
||||
|
||||
<view
|
||||
class="goodAttrSelect-attr"
|
||||
v-for="(item, index) in productAttr"
|
||||
:key="index"
|
||||
class="goodAttrSelect-attr"
|
||||
v-for="(item, index) in productAttr"
|
||||
:key="index"
|
||||
>
|
||||
<view class="goodAttrSelect-attr-title">
|
||||
{{ item.attrName }}
|
||||
</view>
|
||||
<view class="goodAttrSelect-attr-content">
|
||||
<space>
|
||||
<space wrap="warp">
|
||||
<view
|
||||
:class="{ attr: true, check: select[item.attrName] == attr.attr }"
|
||||
v-for="(attr, attrIndex) in item.attrValue"
|
||||
:key="attrIndex"
|
||||
@tap="handleSelectAttr(item.attrName, attr)"
|
||||
>{{ attr.attr }}</view>
|
||||
:class="{ attr: true, check: selectedAttr[index] === attr }"
|
||||
v-for="(attr, attrIndex) in item.attrValueArr"
|
||||
:key="attrIndex"
|
||||
@tap="handleSelectAttr(index, attr)"
|
||||
>{{ attr }}
|
||||
</view>
|
||||
</space>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goodAttrSelect-action">
|
||||
<uv-button
|
||||
round
|
||||
block
|
||||
type="primary"
|
||||
@tap="handleSubmit"
|
||||
round
|
||||
block
|
||||
type="primary"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
确定
|
||||
</uv-button>
|
||||
</view>
|
||||
</view>
|
||||
</uv-popup>
|
||||
</Popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { getProductDetail, getProductAddCollect, getProductDelCollect } from '@/api/product'
|
||||
import { ref, unref } from 'vue';
|
||||
import { getProductDetail } from '@/api/product'
|
||||
import { useInterface } from "@/hooks/useInterface";
|
||||
import Popup from '@/components/Popup/index.vue';
|
||||
|
||||
const props = defineProps(["id"])
|
||||
|
||||
const emit = defineEmits(['onSelect', 'submit'])
|
||||
const emit = defineEmits(['onSelect', 'submit', 'close'])
|
||||
|
||||
const popupRef = ref()
|
||||
const select = ref({})
|
||||
const selectedAttr = ref([])
|
||||
const visible = ref(false)
|
||||
const detailData = ref(null)
|
||||
const storeInfo = ref(null)
|
||||
const productAttr = ref(null)
|
||||
const productValue = ref(null)
|
||||
const storeNum = ref(1)
|
||||
const curAttr = ref(null)
|
||||
const defaultSelectAttrStr = ref(undefined)
|
||||
|
||||
const selectAttrPanel = ref(false)
|
||||
|
||||
const handleGetDetail = async (id) => {
|
||||
|
||||
const detail = await getProductDetail(id)
|
||||
if (detail) {
|
||||
detailData.value = detail
|
||||
storeInfo.value = detail.storeInfo
|
||||
productAttr.value = detail.productAttr
|
||||
productValue.value = detail.productValue
|
||||
let attr = {}
|
||||
detail.productAttr.forEach(item => {
|
||||
attr[item.attrName] = ''
|
||||
})
|
||||
select.value = attr
|
||||
if (!defaultSelectAttrStr.value) {
|
||||
// 设置默认选中
|
||||
let attr = []
|
||||
detail.productAttr.forEach((item, i) => {
|
||||
attr[i] = item.attrValueArr[0]
|
||||
})
|
||||
selectedAttr.value = attr
|
||||
let selectedAttrStr = selectedAttr.value.join(',')
|
||||
curAttr.value = productValue.value[selectedAttrStr]
|
||||
} else {
|
||||
selectedAttr.value = unref(defaultSelectAttrStr).split(',')
|
||||
curAttr.value = productValue.value[defaultSelectAttrStr.value]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleSelectAttr = (attr, value) => {
|
||||
select.value[attr] = value.attr
|
||||
const handleSelectAttr = (index, attr) => {
|
||||
selectedAttr.value[index] = attr
|
||||
let selectedAttrStr = selectedAttr.value.join(',')
|
||||
curAttr.value = productValue.value[selectedAttrStr]
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
// let value = []
|
||||
// productAttr.value.map(item => {
|
||||
// value.push(selectedAttr.value[item.attrName] || '')
|
||||
// })
|
||||
|
||||
|
||||
let value = []
|
||||
productAttr.value.map(item => {
|
||||
value.push(select.value[item.attrName] || '')
|
||||
})
|
||||
|
||||
if (value.includes('')) {
|
||||
uni.showToast({
|
||||
title: '请选择规格',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
emit('select', {
|
||||
store: productValue.value[value.toString()],
|
||||
// if (value.includes('')) {
|
||||
// uni.showToast({
|
||||
// title: '请选择规格',
|
||||
// icon: 'none',
|
||||
// duration: 2000,
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
let selectedAttrStr = selectedAttr.value.join(',')
|
||||
emit('submit', {
|
||||
store: productValue.value[selectedAttrStr],
|
||||
num: storeNum.value
|
||||
})
|
||||
emit('select', {
|
||||
store: productValue.value[selectedAttrStr],
|
||||
num: storeNum.value
|
||||
})
|
||||
emit('onSelect', {
|
||||
store: productValue.value[selectedAttrStr],
|
||||
num: storeNum.value
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
popupRef.value.open()
|
||||
const {toast} = useInterface()
|
||||
|
||||
/**
|
||||
* 用户手动输入改变数量
|
||||
* @param e
|
||||
* @param item
|
||||
* @returns {*}
|
||||
*/
|
||||
function handleCartNumberInputChange(e, item) {
|
||||
const value = Number(e.detail.value)
|
||||
if (value <= 0) {
|
||||
storeNum.value = 1
|
||||
toast({title: '至少选一件哦~'})
|
||||
}
|
||||
if (value > item.stock) {
|
||||
storeNum.value = item.stock
|
||||
toast({title: '超出库存啦~'})
|
||||
}
|
||||
storeNum.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户点击购物车+-改变数量
|
||||
* @param item
|
||||
* @param type
|
||||
* @returns {*}
|
||||
*/
|
||||
function handleCartNumberChange(item, type = 'plus') {
|
||||
if (type === 'plus') {
|
||||
// +
|
||||
if (storeNum.value + 1 > item.stock) return toast({title: '超出库存啦~'})
|
||||
storeNum.value += 1
|
||||
} else {
|
||||
// -
|
||||
if (storeNum.value - 1 <= 0) return toast({title: '至少选一件哦~'})
|
||||
storeNum.value -= 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const open = (cartNum = 1, selectAttrStr = undefined) => {
|
||||
defaultSelectAttrStr.value = selectAttrStr
|
||||
storeNum.value = cartNum
|
||||
handleGetDetail(props.id)
|
||||
popupRef.value.show()
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
popupRef.value.close()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
|
||||
@ -148,12 +255,47 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.goodAttrSelect {
|
||||
height: 100%;
|
||||
|
||||
&-goods {
|
||||
padding: 20rpx 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
display: flex;
|
||||
|
||||
.attr-image {
|
||||
|
||||
}
|
||||
|
||||
.attr-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin-left: 30rpx;
|
||||
.name {
|
||||
font-size: 28rpx;
|
||||
line-height: 40rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
&-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.price {
|
||||
font-size: 30rpx;
|
||||
line-height: 42rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.stock {
|
||||
font-size: 24rpx;
|
||||
line-height: 42rpx;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
@ -178,7 +320,8 @@ defineExpose({
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
&-content {}
|
||||
&-content {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,13 +334,49 @@ defineExpose({
|
||||
height: 68rpx;
|
||||
border: 1rpx solid #333333;
|
||||
padding: 0 20rpx;
|
||||
line-height: 68rpx;
|
||||
|
||||
font-size: 28rpx;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&.check {
|
||||
background: #333333;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.cart-num {
|
||||
font-size: 24rpx;
|
||||
|
||||
.input {
|
||||
width: 120rpx;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 32rpx;
|
||||
width: 34rpx;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 5rpx;
|
||||
border: 2rpx solid #cccccc;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all .3s;
|
||||
|
||||
&:active {
|
||||
scale: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
96
components/good-coupon-select/good-coupon-select.vue
Normal file
96
components/good-coupon-select/good-coupon-select.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<Popup
|
||||
ref="popupRef"
|
||||
:showCloseable="false"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<view class="coupon-box">
|
||||
<template v-if="!showEmpty">
|
||||
<template v-for="item in couponList">
|
||||
<CouponItem
|
||||
:coupons="item"
|
||||
:type="'get'"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<Empty
|
||||
:iconSrc="emptyIcon"
|
||||
v-else
|
||||
>
|
||||
暂时没有可领取的优惠券~
|
||||
</Empty>
|
||||
</view>
|
||||
</Popup>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, unref } from 'vue';
|
||||
import { useInterface } from "@/hooks/useInterface";
|
||||
import Empty from "@/components/Empty/index.vue"
|
||||
import { getProductCoupon } from "@/api/coupon";
|
||||
import CouponItem from "@/pages/discountCoupon/components/CouponItem.vue";
|
||||
import Popup from '@/components/Popup/index.vue';
|
||||
import emptyIcon from "@/static/icon/empty/优惠券.png";
|
||||
|
||||
const props = defineProps(["id"])
|
||||
|
||||
const emit = defineEmits(['submitCoupon', 'close'])
|
||||
const couponList = ref([])
|
||||
const popupRef = ref(false)
|
||||
const currentCoupon = ref({})
|
||||
const visible = ref(false)
|
||||
const showEmpty = ref(false)
|
||||
|
||||
const selectCouponPanel = ref(false)
|
||||
|
||||
const handleGetDetail = async (id) => {
|
||||
const list = await getProductCoupon(id)
|
||||
if (list) {
|
||||
couponList.value = list
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
emit('submitCoupon', {
|
||||
couponId: currentCoupon.value.id
|
||||
})
|
||||
}
|
||||
|
||||
const {toast} = useInterface()
|
||||
|
||||
/**
|
||||
* 用户手动输入改变数量
|
||||
* @param e
|
||||
* @param item
|
||||
* @returns {*}
|
||||
*/
|
||||
const selectCurrentCoupon = (item) => {
|
||||
currentCoupon.value = item
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
handleGetDetail(props.id)
|
||||
popupRef.value.show()
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
popupRef.value.close()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.coupon-box {
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
@ -1,31 +1,31 @@
|
||||
<template>
|
||||
<view
|
||||
:class="{ goods: true, 'goods-card': card, 'goods-list': list, 'goods-min': min, 'goods-fill': fill, 'goods-round': round }"
|
||||
:class="{ goods: true, 'goods-card': card, 'goods-list': list, 'goods-min': min, 'goods-fill': fill, 'goods-round': round }"
|
||||
@tap="toDetail"
|
||||
>
|
||||
<view
|
||||
class="goods-header"
|
||||
@tap="toDetail"
|
||||
class="goods-header"
|
||||
>
|
||||
<view class="goods-thumb">
|
||||
<image
|
||||
:src="data.image"
|
||||
class="goods-thumb-img"
|
||||
style="object-fit: cover"
|
||||
:src="data.image"
|
||||
class="goods-thumb-img"
|
||||
style="object-fit: cover"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view :class="['goods-content']">
|
||||
<view
|
||||
class="goods-storeName"
|
||||
@tap="toDetail"
|
||||
>{{ data.storeName }}</view>
|
||||
class="goods-storeName"
|
||||
>{{ data.storeName }}
|
||||
</view>
|
||||
<view
|
||||
class="goods-list-model"
|
||||
v-if="selectModel"
|
||||
class="goods-list-model"
|
||||
v-if="selectModel"
|
||||
>
|
||||
<div
|
||||
class="goods-list-model-border"
|
||||
@tap.stop="handleOpenSelect"
|
||||
class="goods-list-model-border"
|
||||
@tap.stop="handleOpenSelect"
|
||||
>
|
||||
<view class="goods-list-model-label">{{ data.attrInfo.sku }}</view>
|
||||
<view class="goods-list-model-action icon">
|
||||
@ -34,8 +34,8 @@
|
||||
</div>
|
||||
</view>
|
||||
<view
|
||||
class="goods-list-model-info"
|
||||
v-if="model"
|
||||
class="goods-list-model-info"
|
||||
v-if="model"
|
||||
>
|
||||
<view class="goods-list-model-label">{{ data.attrInfo.sku }}</view>
|
||||
|
||||
@ -47,45 +47,54 @@
|
||||
<view class="goods-info">
|
||||
<view class="goods-info-left">
|
||||
<view
|
||||
class="goods-desc"
|
||||
v-if="groupNum"
|
||||
>{{ data.groupNum }}人团</view>
|
||||
class="goods-desc"
|
||||
v-if="groupNum"
|
||||
>{{ data.groupNum }}人团
|
||||
</view>
|
||||
<view
|
||||
class="goods-price-row"
|
||||
v-if="primary"
|
||||
class="goods-price-row"
|
||||
v-if="primary"
|
||||
>
|
||||
<view class="goods-price goods-price-primary">
|
||||
¥{{ data.price }}
|
||||
¥{{price || data.price }}
|
||||
</view>
|
||||
<view
|
||||
class="goods-price goods-price-original"
|
||||
v-if="original"
|
||||
class="goods-price goods-price-original"
|
||||
v-if="original"
|
||||
>
|
||||
¥{{ data.original }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="goods-price goods-price-default"
|
||||
v-if="!primary"
|
||||
>¥{{ data.price }}</view>
|
||||
class="goods-price goods-price-default"
|
||||
v-if="!primary"
|
||||
>
|
||||
<slot
|
||||
name="price"
|
||||
:row="data"
|
||||
>
|
||||
¥{{price || data.price }}
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-info-action">
|
||||
<view class="goods-info-action-btn">
|
||||
<slot name="action"></slot>
|
||||
</view>
|
||||
<view
|
||||
class="goods-info-action-desc"
|
||||
v-if="stock"
|
||||
v-if="stock"
|
||||
class="goods-info-action-desc"
|
||||
>
|
||||
仅剩{{ data.stock }}件
|
||||
<text v-if="data.stock">仅剩{{ data.stock }}件</text>
|
||||
<text v-else>库存不足</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="buy-num"
|
||||
v-if="purchase"
|
||||
class="buy-num"
|
||||
v-if="purchase"
|
||||
>
|
||||
<view class="buy-num-info-desc">
|
||||
{{ data.purchase }}
|
||||
x{{ purchase }}
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@ -95,21 +104,21 @@
|
||||
<view class="buy-progress">
|
||||
<view class="buy-progress-info">
|
||||
<view
|
||||
class="buy-progress-info-desc"
|
||||
v-if="quantity"
|
||||
class="buy-progress-info-desc"
|
||||
v-if="quantity"
|
||||
>
|
||||
限量{{ data.quantity }}件
|
||||
</view>
|
||||
<uv-line-progress
|
||||
:percentage="50"
|
||||
:showText="false"
|
||||
:percentage="50"
|
||||
:showText="false"
|
||||
/>
|
||||
</view>
|
||||
<view class="buy-progress-action">
|
||||
<uv-button
|
||||
round
|
||||
block
|
||||
type="primary"
|
||||
round
|
||||
block
|
||||
type="primary"
|
||||
>
|
||||
立即抢购
|
||||
</uv-button>
|
||||
@ -118,17 +127,18 @@
|
||||
</view>
|
||||
</view>
|
||||
<good-attr-select
|
||||
ref="selectAttrPanel"
|
||||
:id="data.id"
|
||||
@select="handleSelectAttr"
|
||||
ref="selectAttrPanel"
|
||||
:id="data.id"
|
||||
@select="handleSelectAttr"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { navigateTo } from '@/utils/router';
|
||||
import { ref } from 'vue'
|
||||
const props = defineProps(['data', 'min', 'groupNum', 'original', 'stock', 'primary', 'card', 'list', 'buyProgress', 'quantity', 'selectModel', 'model', 'purchase', 'link', 'fill', 'round'])
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
|
||||
const props = defineProps(['data', 'min', 'groupNum', 'original', 'stock', 'primary', 'card', 'list', 'buyProgress', 'quantity', 'selectModel', 'model', 'purchase', 'link', 'fill', 'round','price'])
|
||||
|
||||
// 团购人数
|
||||
const groupNum = ref(props.groupNum)
|
||||
@ -150,23 +160,25 @@ const selectModel = ref(props.selectModel !== undefined)
|
||||
const model = ref(props.model !== undefined)
|
||||
// 购买数量
|
||||
const purchase = ref(props.purchase)
|
||||
// 购买价格
|
||||
const price = ref(props.price)
|
||||
|
||||
|
||||
// 原价
|
||||
const original = ref(props.original)
|
||||
|
||||
// 小尺寸
|
||||
const min = ref(props.min)
|
||||
const selectAttrPanel = ref(null)
|
||||
const link = ref(props.link !== undefined)
|
||||
const fill = ref(props.fill !== undefined)
|
||||
const round = ref(props.round !== undefined)
|
||||
|
||||
const {push} = useRouter()
|
||||
const toDetail = () => {
|
||||
if (!link.value) { return }
|
||||
navigateTo({
|
||||
url: '/pages/goodsDetail/goodsDetail',
|
||||
query: {
|
||||
id: props.data.id
|
||||
}
|
||||
})
|
||||
if (!link.value) {
|
||||
return
|
||||
}
|
||||
push({url: '/pages/goodsDetail/goodsDetail'}, {data: {id: props.data.id}})
|
||||
}
|
||||
|
||||
const handleOpenSelect = () => {
|
||||
@ -179,7 +191,7 @@ const handleSelectAttr = () => {
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.goods {
|
||||
position: relative;
|
||||
padding: 30rpx 0;
|
||||
@ -188,7 +200,6 @@ const handleSelectAttr = () => {
|
||||
&-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.goods {
|
||||
&-content {
|
||||
padding: 0 20rpx;
|
||||
@ -205,7 +216,8 @@ const handleSelectAttr = () => {
|
||||
width: 100%;
|
||||
|
||||
&-img {
|
||||
width: 100%;
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@ -213,43 +225,48 @@ const handleSelectAttr = () => {
|
||||
}
|
||||
}
|
||||
|
||||
&-header {}
|
||||
&-header {
|
||||
}
|
||||
|
||||
&-thumb {
|
||||
background: #FAFAFA;
|
||||
}
|
||||
|
||||
&-content {}
|
||||
&-content {
|
||||
}
|
||||
|
||||
&-storeName {
|
||||
line-height: 40rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
|
||||
|
||||
&-price {
|
||||
&-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.goods-price {}
|
||||
.goods-price {
|
||||
}
|
||||
}
|
||||
|
||||
&-primary {
|
||||
line-height: 42rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #EE6D46;
|
||||
}
|
||||
|
||||
&-default {
|
||||
line-height: 40rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
line-height: 42rpx;
|
||||
font-size: 30rpx;
|
||||
color: #EE6D46;
|
||||
}
|
||||
|
||||
&-original {
|
||||
@ -274,10 +291,12 @@ const handleSelectAttr = () => {
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
|
||||
&-left {}
|
||||
&-left {
|
||||
}
|
||||
|
||||
&-action {
|
||||
&-btn {}
|
||||
&-btn {
|
||||
}
|
||||
|
||||
&-desc {
|
||||
line-height: 28rpx;
|
||||
@ -290,21 +309,22 @@ const handleSelectAttr = () => {
|
||||
}
|
||||
|
||||
&-image {
|
||||
&-img {}
|
||||
&-img {
|
||||
}
|
||||
}
|
||||
|
||||
&-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 14rpx;
|
||||
padding: 40rpx 34rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
|
||||
.goods {
|
||||
&-thumb {
|
||||
margin-bottom: 0;
|
||||
width: 220rpx;
|
||||
height: 220rpx;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
|
||||
&-img {
|
||||
width: 100%;
|
||||
@ -314,17 +334,16 @@ const handleSelectAttr = () => {
|
||||
}
|
||||
|
||||
&-content {
|
||||
padding-right: 40rpx;
|
||||
margin-left: 30rpx;
|
||||
margin-left: 40rpx;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
&-model {
|
||||
display: flex;
|
||||
|
||||
@ -341,7 +360,8 @@ const handleSelectAttr = () => {
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
&-info {}
|
||||
&-info {
|
||||
}
|
||||
|
||||
&-label {
|
||||
line-height: 38rpx;
|
||||
@ -466,7 +486,6 @@ const handleSelectAttr = () => {
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// &-desc {
|
||||
// color: #999999;
|
||||
// font-size: 24rpx;
|
||||
@ -556,7 +575,6 @@ const handleSelectAttr = () => {
|
||||
}
|
||||
|
||||
.buy-num {
|
||||
|
||||
&-info-desc {
|
||||
color: #999999;
|
||||
font-size: 24rpx;
|
||||
|
169
components/goodsComponents/Goods.vue
Normal file
169
components/goodsComponents/Goods.vue
Normal file
@ -0,0 +1,169 @@
|
||||
<!--
|
||||
@name: 上哦
|
||||
@author: kahu4
|
||||
@date: 2023-11-02 18:30
|
||||
@description:Goods
|
||||
@update: 2023-11-02 18:30
|
||||
-->
|
||||
<script setup>
|
||||
import { toRefs } from "vue";
|
||||
import { GOODS_ITEM_TYPE } from "@/components/goodsComponents/utils/index.type";
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
|
||||
const props = defineProps({
|
||||
goods: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
imgWidth: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
/** 图片比例 */
|
||||
ratio: {
|
||||
type: String,
|
||||
default: () => "1/1"
|
||||
},
|
||||
infoPadding: {
|
||||
type: String,
|
||||
default: () => "0 0"
|
||||
},
|
||||
/** title是否换行 */
|
||||
titleNowrap: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
/** title 文字大小 rpx */
|
||||
titleSize: {
|
||||
type: Number,
|
||||
default: () => 28
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: () => GOODS_ITEM_TYPE.NEW
|
||||
},
|
||||
row: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
goods,
|
||||
imgWidth,
|
||||
ratio,
|
||||
infoPadding,
|
||||
titleNowrap,
|
||||
titleSize,
|
||||
row,
|
||||
} = toRefs(props)
|
||||
|
||||
const {push} = useRouter()
|
||||
|
||||
function toDetail() {
|
||||
push({url: '/pages/goodsDetail/goodsDetail'}, {data: {id: goods.value.id}})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
:class="{'goods':true,'row':row}"
|
||||
@click="toDetail"
|
||||
>
|
||||
<!-- goods image -->
|
||||
<view
|
||||
class="goods-image"
|
||||
:style="{
|
||||
'width':imgWidth,
|
||||
'aspect-ratio':ratio
|
||||
}"
|
||||
>
|
||||
<image
|
||||
:src="goods.image"
|
||||
class="image"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<!-- good info -->
|
||||
<view
|
||||
class="goods-info"
|
||||
:style="{
|
||||
'padding':infoPadding,
|
||||
'width':row?`calc( 100% - ${imgWidth} )`:'100%',
|
||||
'height':row?`${imgWidth}`:'auto'
|
||||
}"
|
||||
>
|
||||
<view
|
||||
:class="{
|
||||
'title-row':true,
|
||||
'nowrap': titleNowrap
|
||||
}"
|
||||
:style="{
|
||||
'font-size':`${titleSize}rpx`
|
||||
}"
|
||||
>
|
||||
{{ goods.storeName }}
|
||||
</view>
|
||||
<slot
|
||||
name="options"
|
||||
:goods="goods"
|
||||
></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.goods {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&-image {
|
||||
flex-shrink: 0;
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-info {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.title-row {
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
//padding: 14rpx 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.nowrap {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.goods-image {
|
||||
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
143
components/goodsComponents/components/Options.vue
Normal file
143
components/goodsComponents/components/Options.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<!--
|
||||
@name: Options
|
||||
@author: kahu4
|
||||
@date: 2023-11-03 15:30
|
||||
@description:Options
|
||||
@update: 2023-11-03 15:30
|
||||
-->
|
||||
<script setup>
|
||||
import { toRefs } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
btnText: {
|
||||
type: String,
|
||||
default: '立即抢购'
|
||||
},
|
||||
oldPrice: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tips: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
schedule: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
goods: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const {row, showBtn, oldPrice, btnText} = toRefs(props)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="options flex flex-jc__sb flex-ai__center"
|
||||
:class="{row,'flex-ai__center':row,'flex-ai__end':!row}"
|
||||
>
|
||||
<view class="price-box ">
|
||||
<view class="price-row flex flex-jc__sb flex-ai__end">
|
||||
<view class="price">
|
||||
¥{{ goods.price }}
|
||||
</view>
|
||||
<view
|
||||
class="old-price"
|
||||
v-if="oldPrice"
|
||||
>
|
||||
¥{{ goods.otPrice }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="tips-row"
|
||||
v-if="tips"
|
||||
>
|
||||
限量100件
|
||||
</view>
|
||||
<view
|
||||
class="process-row"
|
||||
v-if="schedule"
|
||||
>
|
||||
<view class="schedule"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="btn-box"
|
||||
:class="{'no-btn':!showBtn}"
|
||||
>
|
||||
<slot
|
||||
:goods="goods"
|
||||
name="right-tip"
|
||||
>
|
||||
{{ showBtn ? btnText : `仅剩${ goods.stock }${ goods.unit || '' }` }}
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.options {
|
||||
width: 100%;
|
||||
|
||||
.price-box {
|
||||
|
||||
.price-row {
|
||||
font-size: 28rpx;
|
||||
color: $primary-color;
|
||||
|
||||
.old-price {
|
||||
color: $tips-color;
|
||||
font-size: 20rpx;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.tips-row {
|
||||
color: $tips-color;
|
||||
font-size: 24rpx;
|
||||
margin: 6rpx 0;
|
||||
}
|
||||
|
||||
.process-row {
|
||||
width: 100%;
|
||||
height: 15rpx;
|
||||
background: #f5f5f5;
|
||||
position: relative;
|
||||
|
||||
.schedule {
|
||||
background: $primary-color;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-box {
|
||||
@include usePadding(15, 10);
|
||||
flex-shrink: 0;
|
||||
background: $primary-color;
|
||||
color: $white-color;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.no-btn {
|
||||
padding: 0 0;
|
||||
background: none;
|
||||
color: $tips-color;
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
15
components/goodsComponents/utils/index.type.js
Normal file
15
components/goodsComponents/utils/index.type.js
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @name: index.type
|
||||
* @author: kahu4
|
||||
* @date: 2023-11-02 18:51
|
||||
* @description:index.type
|
||||
* @update: 2023-11-02 18:51
|
||||
* */
|
||||
export const GOODS_ITEM_TYPE = {
|
||||
NEW: 'NEW', // 新品首发
|
||||
GROUP: 'GROUP', // 拼团
|
||||
SECKILL: 'SECKILL', // 秒杀
|
||||
BARGAIN: 'BARGAIN', // 砍价
|
||||
SHOPPING_CART: 'CART', // 购物车数据
|
||||
CONFIRM_ORDER: 'ORDER', // 确认订单数据
|
||||
}
|
101
components/home/HomeGoodItem.vue
Normal file
101
components/home/HomeGoodItem.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<!--
|
||||
@name: HomeGoodItem
|
||||
@author: kahu4
|
||||
@date: 2023-10-27 14:47
|
||||
@description:HomeGoodItem
|
||||
@update: 2023-10-27 14:47
|
||||
-->
|
||||
<script setup>
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
const {push} = useRouter()
|
||||
const props = defineProps({
|
||||
good: {
|
||||
type: Object
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
infoPadding: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
const toDetail = () => {
|
||||
push({url: '/pages/goodsDetail/goodsDetail'},{data:{id: props.good.id}})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
class="good-item"
|
||||
:style="{background:props.background}"
|
||||
@tap="toDetail"
|
||||
>
|
||||
<view class="image">
|
||||
<image
|
||||
:src="props.good.image"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="info-box"
|
||||
:style="{padding:`0 ${props.infoPadding}rpx`}"
|
||||
>
|
||||
<view class="title-row">
|
||||
{{ props.good.storeName }}
|
||||
</view>
|
||||
<view class="price-row">
|
||||
<slot
|
||||
name="bottom"
|
||||
:good="good"
|
||||
></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view class="blank"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.good-item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-radius: 15rpx;
|
||||
overflow: hidden;
|
||||
.image {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.info-box {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.title-row {
|
||||
margin: 14rpx 0;
|
||||
|
||||
max-width: 25vw;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.blank {
|
||||
width: 100%;
|
||||
height: 22rpx;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@ -7,10 +7,9 @@
|
||||
<script setup>
|
||||
import { ref, defineProps, reactive } from 'vue';
|
||||
const props = defineProps(['size'])
|
||||
console.log("gxs --> % props:\n", props)
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.layout {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view>
|
||||
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -9,12 +9,12 @@
|
||||
name:"logo",
|
||||
data() {
|
||||
return {
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
@ -1,206 +1,119 @@
|
||||
<template>
|
||||
<view>
|
||||
<view
|
||||
:class="['order', className]"
|
||||
v-if="data"
|
||||
:class="['order', className]"
|
||||
v-if="data"
|
||||
>
|
||||
|
||||
<view
|
||||
class="order-header"
|
||||
@tap="toOrderInfo"
|
||||
class="order-header"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
<view class="order-logo">
|
||||
<!-- <image :src="data." alt=""> -->
|
||||
<view class="color-y">Y</view>
|
||||
<view>SHOP商城</view>
|
||||
</view>
|
||||
<view class="order-status status-1">
|
||||
{{ data._status._msg }}
|
||||
<view
|
||||
class="order-status status-2"
|
||||
v-if="data._status"
|
||||
>
|
||||
{{ data._status._title }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="order-goods"
|
||||
@tap="toOrderInfo"
|
||||
class="order-goods"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
<goods
|
||||
list
|
||||
interval
|
||||
desc="3"
|
||||
showAction
|
||||
model
|
||||
purchase="x3"
|
||||
:data="item.productInfo"
|
||||
v-for="(item, index) in data.cartInfo"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="order-info"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
<text>总价:¥{{ payPrice }}</text>
|
||||
<text>优惠:¥{{ deductionPrice }}</text>
|
||||
<text>运费:-¥{{ freightPrice }}</text>
|
||||
<text>总计:¥{{ totalPrice }}</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="order-actions"
|
||||
v-if="data._status._type == 0"
|
||||
>
|
||||
<!-- 未支付 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
<view class="order-actions-btns">
|
||||
<view
|
||||
v-for="(item, index) in data.cartInfo"
|
||||
class="order-evaluate"
|
||||
:class="{evaluateBtn: data._status._type == 3}"
|
||||
>
|
||||
<view
|
||||
class="order-actions-delete"
|
||||
@tap="handleCancel"
|
||||
>
|
||||
取消订单
|
||||
</view>
|
||||
<view
|
||||
class="order-actions-primary"
|
||||
@tap="handlePay"
|
||||
>
|
||||
立即付款
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<view
|
||||
class="order-actions"
|
||||
v-if="data._status._type == 1"
|
||||
>
|
||||
<!-- 待发货 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
|
||||
<view class="order-actions-btns">
|
||||
<view
|
||||
class="order-actions-delete"
|
||||
@tap="toSelectRefundGood"
|
||||
>
|
||||
申请退款
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="order-actions"
|
||||
v-if="data._status._type == 2"
|
||||
>
|
||||
<!-- 待收货 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
<view class="order-actions-btns">
|
||||
|
||||
<view
|
||||
class="order-actions-delete"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
查看物流
|
||||
</view>
|
||||
<view
|
||||
class="order-actions-primary"
|
||||
@tap="handleOrderTake"
|
||||
>
|
||||
确认收货
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="order-actions"
|
||||
v-if="data._status._type == 3"
|
||||
>
|
||||
<!-- 待评价 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
<view class="order-actions-btns">
|
||||
<view
|
||||
class="order-actions-delete"
|
||||
@tap="handleDelete"
|
||||
>
|
||||
删除订单
|
||||
</view>
|
||||
<view
|
||||
class="order-actions-primary"
|
||||
@tap="toOrderInfo"
|
||||
v-if="data._status._type == 3 && item.isReply === 0"
|
||||
class="order-actions-primary order-evaluate-btn"
|
||||
@tap.stop="toEvaluate(item)"
|
||||
>
|
||||
去评价
|
||||
</view>
|
||||
<goods
|
||||
list
|
||||
interval
|
||||
desc="3"
|
||||
showAction
|
||||
model
|
||||
:purchase="item.cartNum"
|
||||
:data="item.productInfo"
|
||||
>
|
||||
<template #price>
|
||||
¥{{ item.truePrice }}
|
||||
</template>
|
||||
</goods>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="order-info"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
<view class="text">总价:¥{{ data.cost }}</view>
|
||||
<view class="text">优惠:¥{{ couponPrice }}</view>
|
||||
<view class="text">运费:¥{{ totalPostage }}</view>
|
||||
<view class="text flex flex-ai__center">总计:
|
||||
<view class="total-price">¥{{ totalPrice }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="order-actions"
|
||||
v-if="data._status._type == 4"
|
||||
>
|
||||
<!-- 已完成 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
<view class="order-actions-btns">
|
||||
<view
|
||||
class="order-actions-delete"
|
||||
@tap="handleDelete"
|
||||
>
|
||||
删除订单
|
||||
</view>
|
||||
<view
|
||||
class="order-actions-primary"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
查看订单
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="data._status">
|
||||
<view
|
||||
class="order-actions"
|
||||
|
||||
<view
|
||||
class="order-actions"
|
||||
v-if="data._status._type == 5"
|
||||
>
|
||||
<!-- 退款中 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
<view class="order-actions-btns">
|
||||
<view
|
||||
class="order-actions-primary"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
查看订单
|
||||
>
|
||||
<!-- 未支付 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="order-actions"
|
||||
v-if="data._status._type == 6"
|
||||
>
|
||||
<!-- 已退款 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
<view class="order-actions-btns">
|
||||
<view
|
||||
class="order-actions-primary"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
查看订单
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="order-actions"
|
||||
v-if="data._status._type == 7"
|
||||
>
|
||||
<!-- 退款 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
<view class="order-actions-btns">
|
||||
<view
|
||||
class="order-actions-primary"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
查看订单
|
||||
<view class="order-actions-btns">
|
||||
<view
|
||||
v-if="data._status._type == 0"
|
||||
class="order-actions-default"
|
||||
@tap="handleCancel"
|
||||
>
|
||||
取消订单
|
||||
</view>
|
||||
<view
|
||||
v-if="data._status._type == 0"
|
||||
class="order-actions-primary"
|
||||
@tap="handlePay"
|
||||
>
|
||||
立即付款
|
||||
</view>
|
||||
<view
|
||||
v-if="['1','2','3','4'].includes(data._status._type)"
|
||||
class="order-actions-default"
|
||||
@tap="toSelectRefundGood"
|
||||
>
|
||||
申请退款
|
||||
</view>
|
||||
<view
|
||||
class="order-actions-default"
|
||||
v-if="data._status._type == 2"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
查看物流
|
||||
</view>
|
||||
<view
|
||||
v-if="data._status._type == 2"
|
||||
class="order-actions-primary"
|
||||
@tap="handleOrderTake"
|
||||
>
|
||||
确认收货
|
||||
</view>
|
||||
<view
|
||||
v-if="['4'].includes(data._status._type)"
|
||||
class="order-actions-default"
|
||||
@tap="handleDelete"
|
||||
>
|
||||
删除订单
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -211,24 +124,24 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { orderCancel, orderInfo, orderDelete, orderTake } from '@/api/order'
|
||||
import { orderCancel, orderDelete, orderTake } from '@/api/order'
|
||||
|
||||
import { navigateTo, back } from '@/utils/router'
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const {push} = useRouter()
|
||||
|
||||
const emit = defineEmits(['refresh', 'pay'])
|
||||
|
||||
const props = defineProps(['class', 'data'])
|
||||
|
||||
const data = ref(props.data)
|
||||
|
||||
// 运费金额
|
||||
const freightPrice = ref(props.data.freightPrice)
|
||||
const totalPostage = ref(props.data.totalPostage)
|
||||
// 实际支付金额
|
||||
const payPrice = ref(props.data.payPrice)
|
||||
// 优惠券金额
|
||||
const couponPrice = ref(props.data.couponPrice)
|
||||
// 抵扣金额
|
||||
const deductionPrice = ref(props.data.deductionPrice)
|
||||
// 订单总价
|
||||
const totalPrice = ref(props.data.totalPrice)
|
||||
|
||||
@ -247,7 +160,6 @@ const handleCancel = async () => {
|
||||
duration: 2000
|
||||
});
|
||||
} else if (res.cancel) {
|
||||
console.log('用户点击取消');
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -255,44 +167,40 @@ const handleCancel = async () => {
|
||||
|
||||
|
||||
const toSelectRefundGood = () => {
|
||||
navigateTo({
|
||||
url: '/pages/selectRefundGood/selectRefundGood',
|
||||
query: {
|
||||
push({url: '/pages/selectRefundGood/selectRefundGood'}, {
|
||||
data: {
|
||||
orderId: data.value.orderId,
|
||||
id: data.value.id,
|
||||
status: data.value.status
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handlePay = () => {
|
||||
|
||||
navigateTo({
|
||||
url: '/pages/selectPlay/selectPlay',
|
||||
query: {
|
||||
key: data.value.unique,
|
||||
orderId: data.value.orderId,
|
||||
}
|
||||
})
|
||||
emit('pay', data.value.orderId)
|
||||
/* push({url: '/pages/selectPlay/selectPlay'}, {
|
||||
data: {
|
||||
key: data.value.unique,
|
||||
orderId: data.value.orderId,
|
||||
}
|
||||
})*/
|
||||
}
|
||||
|
||||
|
||||
const toOrderInfo = () => {
|
||||
navigateTo({
|
||||
url: '/pages/orderInfo/orderInfo',
|
||||
query: {
|
||||
push({url: '/pages/orderInfo/orderInfo'}, {
|
||||
data: {
|
||||
key: data.value.unique,
|
||||
orderId: data.value.orderId,
|
||||
// id: data.value.id,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const toEvaluate = () => {
|
||||
navigateTo({
|
||||
url: '/pages/evaluate/evaluate',
|
||||
query: {
|
||||
unique: data.value.unique,
|
||||
const toEvaluate = (item) => {
|
||||
push({url: '/pages/evaluate/evaluate'}, {
|
||||
data: {
|
||||
unique: item.unique,
|
||||
orderId: data.value.orderId,
|
||||
}
|
||||
})
|
||||
@ -306,7 +214,7 @@ const handleDelete = async () => {
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
await orderDelete({
|
||||
id: data.value.orderId
|
||||
uni: data.value.orderId
|
||||
})
|
||||
data.value = null
|
||||
uni.showToast({
|
||||
@ -314,29 +222,28 @@ const handleDelete = async () => {
|
||||
duration: 2000
|
||||
});
|
||||
} else if (res.cancel) {
|
||||
console.log('用户点击取消');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleOrderTake = async () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确认收货',
|
||||
success: async (res) => {
|
||||
console.log("gxs --> % success: % res:\n", res)
|
||||
if (res.confirm) {
|
||||
let option = {
|
||||
uni: data.value.orderId,
|
||||
}
|
||||
const res = await orderTake(option)
|
||||
|
||||
await orderTake(option)
|
||||
emit('refresh')
|
||||
uni.showToast({
|
||||
title: '已收货',
|
||||
duration: 2000
|
||||
});
|
||||
} else if (res.cancel) {
|
||||
// console.log('用户点击取消');
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -346,5 +253,6 @@ const handleOrderTake = async () => {
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
200
components/refund-order/refund-order.vue
Normal file
200
components/refund-order/refund-order.vue
Normal file
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<view>
|
||||
<view
|
||||
:class="['order', className]"
|
||||
v-if="data"
|
||||
>
|
||||
|
||||
<view
|
||||
class="order-header"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
<view class="order-logo">
|
||||
<view class="color-y">Y</view>
|
||||
<view>SHOP商城</view>
|
||||
</view>
|
||||
<view class="order-status status-2">
|
||||
{{ refundOrderStatus[data.state] }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="order-goods"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
<goods
|
||||
list
|
||||
interval
|
||||
desc="3"
|
||||
showAction
|
||||
model
|
||||
:purchase="item.cartNum"
|
||||
:data="item.productInfo"
|
||||
:price="item.truePrice"
|
||||
v-for="(item, index) in data.cartInfo"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="refund-order-info"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
<text
|
||||
class="text"
|
||||
:class="data.state === 3 && 'color-y'"
|
||||
>{{ data.state === 3 ? '成功退款' : '待平台处理' }}
|
||||
</text>
|
||||
<text class="text">{{ data.state === 3 ? '成功' : '' }}退款¥{{data.refundAmount}}</text>
|
||||
</view>
|
||||
|
||||
<view class="order-actions">
|
||||
<!-- 已完成 -->
|
||||
<view class="order-actions-left">
|
||||
</view>
|
||||
<view class="order-actions-btns">
|
||||
<view
|
||||
class="order-actions-default"
|
||||
@tap="handleDelete"
|
||||
v-if="[3,4].includes(data.state)"
|
||||
>
|
||||
删除记录
|
||||
</view>
|
||||
<view
|
||||
class="order-actions-default"
|
||||
@tap="handleRevoke"
|
||||
v-if="[0,1].includes(data.state)"
|
||||
>
|
||||
撤销申请
|
||||
</view>
|
||||
<view
|
||||
class="order-actions-default"
|
||||
@tap="toRefund"
|
||||
v-if="data.state === 5"
|
||||
>
|
||||
再次申请
|
||||
</view>
|
||||
<view
|
||||
class="order-actions-default"
|
||||
@tap="toAddLogistics"
|
||||
v-if="data.state === 1"
|
||||
>
|
||||
填写物流
|
||||
</view>
|
||||
<view
|
||||
class="order-actions-primary"
|
||||
@tap="toOrderInfo"
|
||||
>
|
||||
查看详情
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { afterSalesOrderDelete, afterSalesOrderRevoke } from '@/api/order'
|
||||
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
import { refundOrderStatus } from '@/config'
|
||||
|
||||
const {push} = useRouter()
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const props = defineProps(['class', 'data'])
|
||||
|
||||
const data = ref(props.data)
|
||||
|
||||
// 运费金额
|
||||
const freightPrice = ref(props.data.freightPrice)
|
||||
// 实际支付金额
|
||||
const payPrice = ref(props.data.payPrice)
|
||||
// 优惠券金额
|
||||
const couponPrice = ref(props.data.couponPrice)
|
||||
// 订单总价
|
||||
const totalPrice = ref(props.data.totalPrice)
|
||||
|
||||
// 删除记录
|
||||
const handleDelete = async () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确认删除记录',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
await afterSalesOrderDelete({
|
||||
id: data.value.id,
|
||||
orderCode: data.value.orderCode
|
||||
})
|
||||
data.value = null
|
||||
uni.showToast({
|
||||
title: '已删除',
|
||||
duration: 2000
|
||||
});
|
||||
} else if (res.cancel) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 撤销申请
|
||||
const handleRevoke = async () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确认撤销申请吗',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
await afterSalesOrderRevoke({
|
||||
id: data.value.id,
|
||||
key: data.value.orderCode
|
||||
})
|
||||
data.value = null
|
||||
uni.showToast({
|
||||
title: '已撤销',
|
||||
duration: 2000
|
||||
});
|
||||
} else if (res.cancel) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const toOrderInfo = () => {
|
||||
push({url: '/pages/refundInfo/refundInfo'}, {
|
||||
data: {
|
||||
id: data.value.id,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 填写物流
|
||||
const toAddLogistics = () => {
|
||||
push({url: '/pages/addLogistics/addLogistics'}, {
|
||||
data: {
|
||||
id: data.value.id,
|
||||
orderCode: data.value.orderCode
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 再次申请
|
||||
const toRefund = () => {
|
||||
console.log(data.value)
|
||||
push({url: '/pages/refund/refund'}, {
|
||||
data: {
|
||||
refundType: data.value.serviceType,
|
||||
goods: data.value.cartInfo.map(v=>{
|
||||
return v.productAttrUnique
|
||||
}).toString(),
|
||||
id: data.value.orderId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
@ -1,72 +1,116 @@
|
||||
<template>
|
||||
<view :class="{
|
||||
<view
|
||||
:class="{
|
||||
reply: true,
|
||||
noPic: !data?.pics
|
||||
}">
|
||||
}"
|
||||
>
|
||||
<view class="reply-content">
|
||||
<view class="reply-user">
|
||||
<view class="reply-user-pic">
|
||||
<image
|
||||
class="img"
|
||||
src="http://yshop.l1.ttut.cc/admin-api/infra/file/4/get/7599202df273d25f1ce3aeba21165a544849248fda23aad090098d7be0c063c9.jpeg"
|
||||
/>
|
||||
<!-- <image :src="data.avatar" /> -->
|
||||
</view>
|
||||
<view class="reply-user-name">
|
||||
<view class="name">
|
||||
<!-- {{ data.nickname }} -->
|
||||
我的名字
|
||||
<view class="reply-warp">
|
||||
<view class="reply-user">
|
||||
<view class="reply-user-pic">
|
||||
<image
|
||||
class="img"
|
||||
:src="data.avatar"
|
||||
@click="doPreviewImage(0,[data.avatar])"
|
||||
/>
|
||||
</view>
|
||||
<view class="productScore">
|
||||
<uv-rate
|
||||
count="5"
|
||||
:value="data.productScore"
|
||||
readonly
|
||||
size="12"
|
||||
gutter="1"
|
||||
active-color="#ee6d46"
|
||||
inactive-color="#999999"
|
||||
></uv-rate>
|
||||
<view class="reply-user-name">
|
||||
<view class="name">
|
||||
{{ data.nickname }}
|
||||
</view>
|
||||
<view class="productScore">
|
||||
<uv-rate
|
||||
count="5"
|
||||
:value="data.productScore"
|
||||
readonly
|
||||
size="26rpx"
|
||||
gutter="1"
|
||||
active-color="#ee6d46"
|
||||
inactive-color="#999999"
|
||||
></uv-rate>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="reply-time">{{ data.createTime }}</view>
|
||||
</view>
|
||||
<view class="reply-text">
|
||||
{{ data.comment }}
|
||||
</view>
|
||||
<view
|
||||
class="reply-pic flex flex-ai__center"
|
||||
v-if="data.pics && data.pics.length>0"
|
||||
>
|
||||
<template
|
||||
v-for="(pic,index) in data.pics"
|
||||
:key="index"
|
||||
>
|
||||
<image
|
||||
class="image"
|
||||
:src="pic"
|
||||
@click="doPreviewImage(index, data.pics)"
|
||||
/>
|
||||
</template>
|
||||
</view>
|
||||
<view class="reply-sku">规格:{{data.sku}}</view>
|
||||
</view>
|
||||
<view
|
||||
class="reply-pic"
|
||||
v-if="data?.pics"
|
||||
>
|
||||
<uv-album
|
||||
:urls="data?.pics"
|
||||
space="8rpx"
|
||||
></uv-album>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { toRefs } from 'vue';
|
||||
import { useImage } from "@/hooks/useImage";
|
||||
|
||||
const props = defineProps(['data'])
|
||||
|
||||
const data = ref(props.data)
|
||||
const {data} = toRefs(props);
|
||||
|
||||
const {preview} = useImage()
|
||||
|
||||
function doPreviewImage(current, urls) {
|
||||
preview({
|
||||
current,
|
||||
urls,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.reply {
|
||||
margin-top: 20rpx;
|
||||
border-top: 1rpx solid #E6E6E6;
|
||||
padding-bottom: 10rpx;
|
||||
&-content {
|
||||
padding: 32rpx 34rpx;
|
||||
border-right: 0;
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
|
||||
.reply-warp {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.reply-time {
|
||||
font-size: 24rpx;
|
||||
line-height: 1em;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.noPic {
|
||||
|
||||
.reply-content {}
|
||||
.reply-content {
|
||||
}
|
||||
}
|
||||
|
||||
&-pic {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
background: #FFFFFF;
|
||||
border-radius: 15rpx;
|
||||
//width: 180rpx;
|
||||
//height: 180rpx;
|
||||
//background: #FFFFFF;
|
||||
//border-radius: 15rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
&-user {
|
||||
@ -75,14 +119,12 @@ const data = ref(props.data)
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&-pic {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 10rpx;
|
||||
margin-right: 20rpx;
|
||||
|
||||
.img {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
|
||||
@ -90,18 +132,33 @@ const data = ref(props.data)
|
||||
}
|
||||
|
||||
&-name {
|
||||
margin-left: 6rpx;
|
||||
line-height: 33rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 40rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
border-right: 0;
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
&-text {
|
||||
margin-top: 30rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 40rpx;
|
||||
color: #333333;
|
||||
}
|
||||
&-sku{
|
||||
font-size: 24rpx;
|
||||
line-height: 33rpx;
|
||||
color: #999999;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-pic {
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.image {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,220 +1,219 @@
|
||||
<template>
|
||||
<view
|
||||
:class="classObject"
|
||||
:style="[getStyle]"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from "vue"
|
||||
const props = defineProps([
|
||||
'direction',
|
||||
'align',
|
||||
'wrap',
|
||||
'justify',
|
||||
'size',
|
||||
'height',
|
||||
'padding',
|
||||
'flex',
|
||||
'border'
|
||||
])
|
||||
|
||||
console.log("--> % props:\n", props)
|
||||
|
||||
|
||||
const classObject = ref({})
|
||||
|
||||
const handleClassObject = (props) => {
|
||||
console.log("--> % handleClassObject % props:\n", props.value)
|
||||
let className = 'yshop-space'
|
||||
let direction = props.direction
|
||||
let align = props.align
|
||||
let wrap = props.wrap
|
||||
let justify = props.justify
|
||||
let flex = props.flex
|
||||
let border = props.border
|
||||
if (border) {
|
||||
className += ' yshop-space-border'
|
||||
}
|
||||
if (direction) {
|
||||
className += ` yshop-space-${direction}`
|
||||
}
|
||||
if (wrap) {
|
||||
className += ` yshop-space-${wrap}`
|
||||
}
|
||||
if (align) {
|
||||
className += ` yshop-space-align-${align}`
|
||||
}
|
||||
if (justify) {
|
||||
className += ` yshop-space-justify-${justify}`
|
||||
}
|
||||
console.log("--> % handleClassObject % className:\n", className)
|
||||
|
||||
classObject.value = className
|
||||
}
|
||||
|
||||
const getStyle = ({
|
||||
size = 6,
|
||||
wrap,
|
||||
height,
|
||||
padding,
|
||||
flex
|
||||
}) => {
|
||||
let innerStyle = {}
|
||||
// let size = size.value
|
||||
// let wrap = wrap.value
|
||||
// let height = height.value
|
||||
// let padding = padding.value
|
||||
|
||||
if (height) {
|
||||
innerStyle.height = `${height}rpx`
|
||||
}
|
||||
if (typeof size === 'number') {
|
||||
innerStyle.gap = size + 'px'
|
||||
}
|
||||
|
||||
|
||||
if (wrap) {
|
||||
innerStyle.flexWrap = 'wrap'
|
||||
}
|
||||
|
||||
if (typeof size === 'string') {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
innerStyle.gap = '8rpx'
|
||||
break
|
||||
case 'middle':
|
||||
innerStyle.gap = '16rpx'
|
||||
break
|
||||
case 'large':
|
||||
innerStyle.gap = '24rpx'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (typeof padding === 'string') {
|
||||
innerStyle.padding = `${padding}rpx`
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(padding) === '[object Array]') {
|
||||
if (typeof padding === 'object') {
|
||||
if (padding.length == 1) {
|
||||
innerStyle.padding = `${padding[0]}rpx`
|
||||
}
|
||||
if (padding.length == 2) {
|
||||
innerStyle.padding = `${padding[0]}rpx ${padding[1]}rpx`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(size) === '[object Array]') {
|
||||
if (typeof size === 'object') {
|
||||
if (size.length == 1) {
|
||||
innerStyle.gap = `${size[0]}rpx`
|
||||
}
|
||||
if (size.length == 2) {
|
||||
innerStyle.gap = `${size[0]}rpx ${size[1]}rpx`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (flex) {
|
||||
innerStyle.flex = flex
|
||||
}
|
||||
|
||||
return innerStyle
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleClassObject(props)
|
||||
getStyle(props)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
space {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.yshop-space {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
|
||||
&-vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-align-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&-align-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&-justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&-justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
&-align-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&-align-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
&-item:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-border {
|
||||
border-bottom: 1rpx solid #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
.yshop-space {
|
||||
&>view {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
&>text {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
&>image {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.u-tag-wrapper {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.u-tag {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// #endif
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
:class="classObject"
|
||||
:style="[getStyle]"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue"
|
||||
|
||||
const props = defineProps([
|
||||
'direction',
|
||||
'align',
|
||||
'wrap',
|
||||
'justify',
|
||||
'size',
|
||||
'height',
|
||||
'padding',
|
||||
'flex',
|
||||
'border'
|
||||
])
|
||||
|
||||
const classObject = ref({})
|
||||
|
||||
const handleClassObject = (props) => {
|
||||
let className = 'yshop-space'
|
||||
let direction = props.direction
|
||||
let align = props.align
|
||||
let wrap = props.wrap
|
||||
let justify = props.justify
|
||||
let flex = props.flex
|
||||
let border = props.border
|
||||
if (border) {
|
||||
className += ' yshop-space-border'
|
||||
}
|
||||
if (direction) {
|
||||
className += ` yshop-space-${ direction }`
|
||||
}
|
||||
if (wrap) {
|
||||
className += ` yshop-space-${ wrap }`
|
||||
}
|
||||
if (align) {
|
||||
className += ` yshop-space-align-${ align }`
|
||||
}
|
||||
if (justify) {
|
||||
className += ` yshop-space-justify-${ justify }`
|
||||
}
|
||||
|
||||
classObject.value = className
|
||||
}
|
||||
|
||||
const getStyle = ({
|
||||
size = 6,
|
||||
wrap,
|
||||
height,
|
||||
padding,
|
||||
flex
|
||||
}) => {
|
||||
let innerStyle = {}
|
||||
// let size = size.value
|
||||
// let wrap = wrap.value
|
||||
// let height = height.value
|
||||
// let padding = padding.value
|
||||
|
||||
if (height) {
|
||||
innerStyle.height = `${ height }rpx`
|
||||
}
|
||||
if (typeof size === 'number') {
|
||||
innerStyle.gap = size + 'px'
|
||||
}
|
||||
|
||||
|
||||
if (wrap) {
|
||||
innerStyle.flexWrap = 'wrap'
|
||||
}
|
||||
|
||||
if (typeof size === 'string') {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
innerStyle.gap = '8rpx'
|
||||
break
|
||||
case 'middle':
|
||||
innerStyle.gap = '16rpx'
|
||||
break
|
||||
case 'large':
|
||||
innerStyle.gap = '24rpx'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (typeof padding === 'string') {
|
||||
innerStyle.padding = `${ padding }rpx`
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(padding) === '[object Array]') {
|
||||
if (typeof padding === 'object') {
|
||||
if (padding.length == 1) {
|
||||
innerStyle.padding = `${ padding[0] }rpx`
|
||||
}
|
||||
if (padding.length == 2) {
|
||||
innerStyle.padding = `${ padding[0] }rpx ${ padding[1] }rpx`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(size) === '[object Array]') {
|
||||
if (typeof size === 'object') {
|
||||
if (size.length == 1) {
|
||||
innerStyle.gap = `${ size[0] }rpx`
|
||||
}
|
||||
if (size.length == 2) {
|
||||
innerStyle.gap = `${ size[0] }rpx ${ size[1] }rpx`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (flex) {
|
||||
innerStyle.flex = flex
|
||||
}
|
||||
|
||||
return innerStyle
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleClassObject(props)
|
||||
getStyle(props)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
space {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.yshop-space {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
|
||||
&-warp {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&-vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-align-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&-align-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&-justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&-justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
&-align-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&-align-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
&-item:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-border {
|
||||
border-bottom: 1rpx solid #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
.yshop-space {
|
||||
& > view {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
& > text {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
& > image {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.u-tag-wrapper {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.u-tag {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// #endif
|
||||
</style>
|
||||
|
45
components/uniComponents/UPopup/uni-popup-dialog/keypress.js
Normal file
45
components/uniComponents/UPopup/uni-popup-dialog/keypress.js
Normal file
@ -0,0 +1,45 @@
|
||||
// #ifdef H5
|
||||
export default {
|
||||
name: 'Keypress',
|
||||
props: {
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
const keyNames = {
|
||||
esc: ['Esc', 'Escape'],
|
||||
tab: 'Tab',
|
||||
enter: 'Enter',
|
||||
space: [' ', 'Spacebar'],
|
||||
up: ['Up', 'ArrowUp'],
|
||||
left: ['Left', 'ArrowLeft'],
|
||||
right: ['Right', 'ArrowRight'],
|
||||
down: ['Down', 'ArrowDown'],
|
||||
delete: ['Backspace', 'Delete', 'Del']
|
||||
}
|
||||
const listener = ($event) => {
|
||||
if (this.disable) {
|
||||
return
|
||||
}
|
||||
const keyName = Object.keys(keyNames).find(key => {
|
||||
const keyName = $event.key
|
||||
const value = keyNames[key]
|
||||
return value === keyName || (Array.isArray(value) && value.includes(keyName))
|
||||
})
|
||||
if (keyName) {
|
||||
// 避免和其他按键事件冲突
|
||||
setTimeout(() => {
|
||||
this.$emit(keyName, {})
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
document.addEventListener('keyup', listener)
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
document.removeEventListener('keyup', listener)
|
||||
})
|
||||
},
|
||||
render: () => {}
|
||||
}
|
||||
// #endif
|
@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<view class="uni-popup-dialog">
|
||||
<view class="uni-dialog-title">
|
||||
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
|
||||
</view>
|
||||
<view v-if="mode === 'base'" class="uni-dialog-content">
|
||||
<slot>
|
||||
<text class="uni-dialog-content-text">{{content}}</text>
|
||||
</slot>
|
||||
</view>
|
||||
<view v-else class="uni-dialog-content">
|
||||
<slot>
|
||||
<input class="uni-dialog-input" v-model="val" :type="inputType" :placeholder="placeholderText" :focus="focus" >
|
||||
</slot>
|
||||
</view>
|
||||
<view class="uni-dialog-button-group">
|
||||
<view class="uni-dialog-button" @click="closeDialog">
|
||||
<text class="uni-dialog-button-text">{{closeText}}</text>
|
||||
</view>
|
||||
<view class="uni-dialog-button uni-border-left" @click="onOk">
|
||||
<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popup from '../uni-popup/popup.js'
|
||||
import {
|
||||
initVueI18n
|
||||
} from '@dcloudio/uni-i18n'
|
||||
import messages from '../uni-popup/i18n/index.js'
|
||||
const { t } = initVueI18n(messages)
|
||||
/**
|
||||
* PopUp 弹出层-对话框样式
|
||||
* @description 弹出层-对话框样式
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||
* @property {String} value input 模式下的默认值
|
||||
* @property {String} placeholder input 模式下输入提示
|
||||
* @property {String} type = [success|warning|info|error] 主题样式
|
||||
* @value success 成功
|
||||
* @value warning 提示
|
||||
* @value info 消息
|
||||
* @value error 错误
|
||||
* @property {String} mode = [base|input] 模式、
|
||||
* @value base 基础对话框
|
||||
* @value input 可输入对话框
|
||||
* @property {String} content 对话框内容
|
||||
* @property {Boolean} beforeClose 是否拦截取消事件
|
||||
* @event {Function} confirm 点击确认按钮触发
|
||||
* @event {Function} close 点击取消按钮触发
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "uniPopupDialog",
|
||||
mixins: [popup],
|
||||
emits:['confirm','close'],
|
||||
props: {
|
||||
inputType:{
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'error'
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'base'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
beforeClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
cancelText:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
confirmText:{
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogType: 'error',
|
||||
focus: false,
|
||||
val: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
okText() {
|
||||
return this.confirmText || t("uni-popup.ok")
|
||||
},
|
||||
closeText() {
|
||||
return this.cancelText || t("uni-popup.cancel")
|
||||
},
|
||||
placeholderText() {
|
||||
return this.placeholder || t("uni-popup.placeholder")
|
||||
},
|
||||
titleText() {
|
||||
return this.title || t("uni-popup.title")
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
type(val) {
|
||||
this.dialogType = val
|
||||
},
|
||||
mode(val) {
|
||||
if (val === 'input') {
|
||||
this.dialogType = 'info'
|
||||
}
|
||||
},
|
||||
value(val) {
|
||||
this.val = val
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 对话框遮罩不可点击
|
||||
this.popup.disableMask()
|
||||
// this.popup.closeMask()
|
||||
if (this.mode === 'input') {
|
||||
this.dialogType = 'info'
|
||||
this.val = this.value
|
||||
} else {
|
||||
this.dialogType = this.type
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.focus = true
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 点击确认按钮
|
||||
*/
|
||||
onOk() {
|
||||
if (this.mode === 'input'){
|
||||
this.$emit('confirm', this.val)
|
||||
}else{
|
||||
this.$emit('confirm')
|
||||
}
|
||||
if(this.beforeClose) return
|
||||
this.popup.close()
|
||||
},
|
||||
/**
|
||||
* 点击取消按钮
|
||||
*/
|
||||
closeDialog() {
|
||||
this.$emit('close')
|
||||
if(this.beforeClose) return
|
||||
this.popup.close()
|
||||
},
|
||||
close(){
|
||||
this.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" >
|
||||
.uni-popup-dialog {
|
||||
width: 300px;
|
||||
border-radius: 11px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-dialog-title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
.uni-dialog-title-text {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.uni-dialog-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.uni-dialog-content-text {
|
||||
font-size: 14px;
|
||||
color: #6C6C6C;
|
||||
}
|
||||
|
||||
.uni-dialog-button-group {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
border-top-color: #f5f5f5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.uni-dialog-button {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.uni-border-left {
|
||||
border-left-color: #f0f0f0;
|
||||
border-left-style: solid;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.uni-dialog-button-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-button-color {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.uni-dialog-input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
border: 1px #eee solid;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
border-radius: 5px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.uni-popup__success {
|
||||
color: #4cd964;
|
||||
}
|
||||
|
||||
.uni-popup__warn {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
.uni-popup__error {
|
||||
color: #dd524d;
|
||||
}
|
||||
|
||||
.uni-popup__info {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<view class="uni-popup-message">
|
||||
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
|
||||
<slot>
|
||||
<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popup from '../uni-popup/popup.js'
|
||||
/**
|
||||
* PopUp 弹出层-消息提示
|
||||
* @description 弹出层-消息提示
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||
* @property {String} type = [success|warning|info|error] 主题样式
|
||||
* @value success 成功
|
||||
* @value warning 提示
|
||||
* @value info 消息
|
||||
* @value error 错误
|
||||
* @property {String} message 消息提示文字
|
||||
* @property {String} duration 显示时间,设置为 0 则不会自动关闭
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'uniPopupMessage',
|
||||
mixins:[popup],
|
||||
props: {
|
||||
/**
|
||||
* 主题 success/warning/info/error 默认 success
|
||||
*/
|
||||
type: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
/**
|
||||
* 消息文字
|
||||
*/
|
||||
message: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 显示时间,设置为 0 则不会自动关闭
|
||||
*/
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 3000
|
||||
},
|
||||
maskShow:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
created() {
|
||||
this.popup.maskShow = this.maskShow
|
||||
this.popup.messageChild = this
|
||||
},
|
||||
methods: {
|
||||
timerClose(){
|
||||
if(this.duration === 0) return
|
||||
clearTimeout(this.timer)
|
||||
this.timer = setTimeout(()=>{
|
||||
this.popup.close()
|
||||
},this.duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" >
|
||||
.uni-popup-message {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-popup-message__box {
|
||||
background-color: #e1f3d8;
|
||||
padding: 10px 15px;
|
||||
border-color: #eee;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
.fixforpc-width {
|
||||
margin-top: 20px;
|
||||
border-radius: 4px;
|
||||
flex: none;
|
||||
min-width: 380px;
|
||||
/* #ifndef APP-NVUE */
|
||||
max-width: 50%;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
max-width: 500px;
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
|
||||
.uni-popup-message-text {
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.uni-popup__success {
|
||||
background-color: #e1f3d8;
|
||||
}
|
||||
|
||||
.uni-popup__success-text {
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.uni-popup__warn {
|
||||
background-color: #faecd8;
|
||||
}
|
||||
|
||||
.uni-popup__warn-text {
|
||||
color: #E6A23C;
|
||||
}
|
||||
|
||||
.uni-popup__error {
|
||||
background-color: #fde2e2;
|
||||
}
|
||||
|
||||
.uni-popup__error-text {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.uni-popup__info {
|
||||
background-color: #F2F6FC;
|
||||
}
|
||||
|
||||
.uni-popup__info-text {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<view class="uni-popup-share">
|
||||
<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
|
||||
<view class="uni-share-content">
|
||||
<view class="uni-share-content-box">
|
||||
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
|
||||
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
|
||||
<text class="uni-share-text">{{item.text}}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-share-button-box">
|
||||
<button class="uni-share-button" @click="close">{{cancelText}}</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popup from '../uni-popup/popup.js'
|
||||
import {
|
||||
initVueI18n
|
||||
} from '@dcloudio/uni-i18n'
|
||||
import messages from '../uni-popup/i18n/index.js'
|
||||
const { t } = initVueI18n(messages)
|
||||
export default {
|
||||
name: 'UniPopupShare',
|
||||
mixins:[popup],
|
||||
emits:['select'],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
beforeClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bottomData: [{
|
||||
text: '微信',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
|
||||
name: 'wx'
|
||||
},
|
||||
{
|
||||
text: '支付宝',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
|
||||
name: 'wx'
|
||||
},
|
||||
{
|
||||
text: 'QQ',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
|
||||
name: 'qq'
|
||||
},
|
||||
{
|
||||
text: '新浪',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
|
||||
name: 'sina'
|
||||
},
|
||||
// {
|
||||
// text: '百度',
|
||||
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
|
||||
// name: 'copy'
|
||||
// },
|
||||
// {
|
||||
// text: '其他',
|
||||
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
|
||||
// name: 'more'
|
||||
// }
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
computed: {
|
||||
cancelText() {
|
||||
return t("uni-popup.cancel")
|
||||
},
|
||||
shareTitleText() {
|
||||
return this.title || t("uni-popup.shareTitle")
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 选择内容
|
||||
*/
|
||||
select(item, index) {
|
||||
this.$emit('select', {
|
||||
item,
|
||||
index
|
||||
})
|
||||
this.close()
|
||||
|
||||
},
|
||||
/**
|
||||
* 关闭窗口
|
||||
*/
|
||||
close() {
|
||||
if(this.beforeClose) return
|
||||
this.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" >
|
||||
.uni-popup-share {
|
||||
background-color: #fff;
|
||||
border-top-left-radius: 11px;
|
||||
border-top-right-radius: 11px;
|
||||
}
|
||||
.uni-share-title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
}
|
||||
.uni-share-title-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.uni-share-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.uni-share-content-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.uni-share-content-item {
|
||||
width: 90px;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-share-content-item:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.uni-share-image {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.uni-share-text {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #3B4144;
|
||||
}
|
||||
|
||||
.uni-share-button-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.uni-share-button {
|
||||
flex: 1;
|
||||
border-radius: 50px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.uni-share-button::after {
|
||||
border-radius: 50px;
|
||||
}
|
||||
</style>
|
7
components/uniComponents/UPopup/uni-popup/i18n/en.json
Normal file
7
components/uniComponents/UPopup/uni-popup/i18n/en.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"uni-popup.cancel": "cancel",
|
||||
"uni-popup.ok": "ok",
|
||||
"uni-popup.placeholder": "pleace enter",
|
||||
"uni-popup.title": "Hint",
|
||||
"uni-popup.shareTitle": "Share to"
|
||||
}
|
8
components/uniComponents/UPopup/uni-popup/i18n/index.js
Normal file
8
components/uniComponents/UPopup/uni-popup/i18n/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
import en from './en.json'
|
||||
import zhHans from './zh-Hans.json'
|
||||
import zhHant from './zh-Hant.json'
|
||||
export default {
|
||||
en,
|
||||
'zh-Hans': zhHans,
|
||||
'zh-Hant': zhHant
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"uni-popup.cancel": "取消",
|
||||
"uni-popup.ok": "确定",
|
||||
"uni-popup.placeholder": "请输入",
|
||||
"uni-popup.title": "提示",
|
||||
"uni-popup.shareTitle": "分享到"
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"uni-popup.cancel": "取消",
|
||||
"uni-popup.ok": "確定",
|
||||
"uni-popup.placeholder": "請輸入",
|
||||
"uni-popup.title": "提示",
|
||||
"uni-popup.shareTitle": "分享到"
|
||||
}
|
45
components/uniComponents/UPopup/uni-popup/keypress.js
Normal file
45
components/uniComponents/UPopup/uni-popup/keypress.js
Normal file
@ -0,0 +1,45 @@
|
||||
// #ifdef H5
|
||||
export default {
|
||||
name: 'Keypress',
|
||||
props: {
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
const keyNames = {
|
||||
esc: ['Esc', 'Escape'],
|
||||
tab: 'Tab',
|
||||
enter: 'Enter',
|
||||
space: [' ', 'Spacebar'],
|
||||
up: ['Up', 'ArrowUp'],
|
||||
left: ['Left', 'ArrowLeft'],
|
||||
right: ['Right', 'ArrowRight'],
|
||||
down: ['Down', 'ArrowDown'],
|
||||
delete: ['Backspace', 'Delete', 'Del']
|
||||
}
|
||||
const listener = ($event) => {
|
||||
if (this.disable) {
|
||||
return
|
||||
}
|
||||
const keyName = Object.keys(keyNames).find(key => {
|
||||
const keyName = $event.key
|
||||
const value = keyNames[key]
|
||||
return value === keyName || (Array.isArray(value) && value.includes(keyName))
|
||||
})
|
||||
if (keyName) {
|
||||
// 避免和其他按键事件冲突
|
||||
setTimeout(() => {
|
||||
this.$emit(keyName, {})
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
document.addEventListener('keyup', listener)
|
||||
// this.$once('hook:beforeDestroy', () => {
|
||||
// document.removeEventListener('keyup', listener)
|
||||
// })
|
||||
},
|
||||
render: () => {}
|
||||
}
|
||||
// #endif
|
26
components/uniComponents/UPopup/uni-popup/popup.js
Normal file
26
components/uniComponents/UPopup/uni-popup/popup.js
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.popup = this.getParent()
|
||||
},
|
||||
methods:{
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getParent(name = 'uniPopup') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
}
|
||||
}
|
482
components/uniComponents/UPopup/uni-popup/uni-popup.vue
Normal file
482
components/uniComponents/UPopup/uni-popup/uni-popup.vue
Normal file
@ -0,0 +1,482 @@
|
||||
<template>
|
||||
<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']">
|
||||
<view @touchstart="touchstart">
|
||||
<UTransition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass"
|
||||
:duration="duration" :show="showTrans" @click="onTap" />
|
||||
<UTransition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration"
|
||||
:show="showTrans" @click="onTap">
|
||||
<view class="uni-popup__wrapper" :style="{ backgroundColor: bg }" :class="[popupstyle]" @click="clear">
|
||||
<slot />
|
||||
</view>
|
||||
</UTransition>
|
||||
</view>
|
||||
<!-- #ifdef H5 -->
|
||||
<keypress v-if="maskShow" @esc="onTap" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef H5
|
||||
import keypress from './keypress.js'
|
||||
// #endif
|
||||
import UTransition from "../uni-transition/uni-transition.vue";
|
||||
|
||||
/**
|
||||
* PopUp 弹出层
|
||||
* @description 弹出层组件,为了解决遮罩弹层的问题
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||
* @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
|
||||
* @value top 顶部弹出
|
||||
* @value center 中间弹出
|
||||
* @value bottom 底部弹出
|
||||
* @value left 左侧弹出
|
||||
* @value right 右侧弹出
|
||||
* @value message 消息提示
|
||||
* @value dialog 对话框
|
||||
* @value share 底部分享示例
|
||||
* @property {Boolean} animation = [true|false] 是否开启动画
|
||||
* @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
|
||||
* @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
|
||||
* @property {String} backgroundColor 主窗口背景色
|
||||
* @property {String} maskBackgroundColor 蒙版颜色
|
||||
* @property {Boolean} safeArea 是否适配底部安全区
|
||||
* @event {Function} change 打开关闭弹窗触发,e={show: false}
|
||||
* @event {Function} maskClick 点击遮罩触发
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'uniPopup',
|
||||
components: {
|
||||
UTransition,
|
||||
// #ifdef H5
|
||||
keypress
|
||||
// #endif
|
||||
},
|
||||
emits: ['change', 'maskClick'],
|
||||
props: {
|
||||
// 开启动画
|
||||
animation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
|
||||
// message: 消息提示 ; dialog : 对话框
|
||||
type: {
|
||||
type: String,
|
||||
default: 'center'
|
||||
},
|
||||
// maskClick
|
||||
isMaskClick: {
|
||||
type: Boolean,
|
||||
default: null
|
||||
},
|
||||
// TODO 2 个版本后废弃属性 ,使用 isMaskClick
|
||||
maskClick: {
|
||||
type: Boolean,
|
||||
default: null
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'none'
|
||||
},
|
||||
safeArea: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maskBackgroundColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.4)'
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
/**
|
||||
* 监听type类型
|
||||
*/
|
||||
type: {
|
||||
handler: function(type) {
|
||||
if (!this.config[type]) return
|
||||
this[this.config[type]](true)
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
isDesktop: {
|
||||
handler: function(newVal) {
|
||||
if (!this.config[newVal]) return
|
||||
this[this.config[this.type]](true)
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
/**
|
||||
* 监听遮罩是否可点击
|
||||
* @param {Object} val
|
||||
*/
|
||||
maskClick: {
|
||||
handler: function(val) {
|
||||
this.mkclick = val
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
isMaskClick: {
|
||||
handler: function(val) {
|
||||
this.mkclick = val
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
// H5 下禁止底部滚动
|
||||
showPopup(show) {
|
||||
// #ifdef H5
|
||||
// fix by mehaotian 处理 h5 滚动穿透的问题
|
||||
document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
duration: 300,
|
||||
ani: [],
|
||||
showPopup: false,
|
||||
showTrans: false,
|
||||
popupWidth: 0,
|
||||
popupHeight: 0,
|
||||
config: {
|
||||
top: 'top',
|
||||
bottom: 'bottom',
|
||||
center: 'center',
|
||||
left: 'left',
|
||||
right: 'right',
|
||||
message: 'top',
|
||||
dialog: 'center',
|
||||
share: 'bottom'
|
||||
},
|
||||
maskClass: {
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.4)'
|
||||
},
|
||||
transClass: {
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
maskShow: true,
|
||||
mkclick: true,
|
||||
popupstyle: this.isDesktop ? 'fixforpc-top' : 'top'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isDesktop() {
|
||||
return this.popupWidth >= 500 && this.popupHeight >= 500
|
||||
},
|
||||
bg() {
|
||||
if (this.backgroundColor === '' || this.backgroundColor === 'none') {
|
||||
return 'transparent'
|
||||
}
|
||||
return this.backgroundColor
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const fixSize = () => {
|
||||
const {
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
windowTop,
|
||||
safeArea,
|
||||
screenHeight,
|
||||
safeAreaInsets
|
||||
} = uni.getSystemInfoSync()
|
||||
this.popupWidth = windowWidth
|
||||
this.popupHeight = windowHeight + (windowTop || 0)
|
||||
// TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
|
||||
if (safeArea && this.safeArea) {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.safeAreaInsets = screenHeight - safeArea.bottom
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
this.safeAreaInsets = safeAreaInsets.bottom
|
||||
// #endif
|
||||
} else {
|
||||
this.safeAreaInsets = 0
|
||||
}
|
||||
}
|
||||
fixSize()
|
||||
// #ifdef H5
|
||||
// window.addEventListener('resize', fixSize)
|
||||
// this.$once('hook:beforeDestroy', () => {
|
||||
// window.removeEventListener('resize', fixSize)
|
||||
// })
|
||||
// #endif
|
||||
},
|
||||
// #ifndef VUE3
|
||||
// TODO vue2
|
||||
destroyed() {
|
||||
this.setH5Visible()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
// TODO vue3
|
||||
unmounted() {
|
||||
this.setH5Visible()
|
||||
},
|
||||
// #endif
|
||||
created() {
|
||||
// this.mkclick = this.isMaskClick || this.maskClick
|
||||
if (this.isMaskClick === null && this.maskClick === null) {
|
||||
this.mkclick = true
|
||||
} else {
|
||||
this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick
|
||||
}
|
||||
if (this.animation) {
|
||||
this.duration = 300
|
||||
} else {
|
||||
this.duration = 0
|
||||
}
|
||||
// TODO 处理 message 组件生命周期异常的问题
|
||||
this.messageChild = null
|
||||
// TODO 解决头条冒泡的问题
|
||||
this.clearPropagation = false
|
||||
this.maskClass.backgroundColor = this.maskBackgroundColor
|
||||
},
|
||||
methods: {
|
||||
setH5Visible() {
|
||||
// #ifdef H5
|
||||
// fix by mehaotian 处理 h5 滚动穿透的问题
|
||||
document.getElementsByTagName('body')[0].style.overflow = 'visible'
|
||||
// #endif
|
||||
},
|
||||
/**
|
||||
* 公用方法,不显示遮罩层
|
||||
*/
|
||||
closeMask() {
|
||||
this.maskShow = false
|
||||
},
|
||||
/**
|
||||
* 公用方法,遮罩层禁止点击
|
||||
*/
|
||||
disableMask() {
|
||||
this.mkclick = false
|
||||
},
|
||||
// TODO nvue 取消冒泡
|
||||
clear(e) {
|
||||
// #ifndef APP-NVUE
|
||||
e.stopPropagation()
|
||||
// #endif
|
||||
this.clearPropagation = true
|
||||
},
|
||||
|
||||
open(direction) {
|
||||
// fix by mehaotian 处理快速打开关闭的情况
|
||||
if (this.showPopup) {
|
||||
return
|
||||
}
|
||||
let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
|
||||
if (!(direction && innerType.indexOf(direction) !== -1)) {
|
||||
direction = this.type
|
||||
}
|
||||
if (!this.config[direction]) {
|
||||
console.error('缺少类型:', direction)
|
||||
return
|
||||
}
|
||||
this[this.config[direction]]()
|
||||
this.$emit('change', {
|
||||
show: true,
|
||||
type: direction
|
||||
})
|
||||
},
|
||||
close(type) {
|
||||
this.showTrans = false
|
||||
this.$emit('change', {
|
||||
show: false,
|
||||
type: this.type
|
||||
})
|
||||
clearTimeout(this.timer)
|
||||
// // 自定义关闭事件
|
||||
// this.customOpen && this.customClose()
|
||||
this.timer = setTimeout(() => {
|
||||
this.showPopup = false
|
||||
}, 300)
|
||||
},
|
||||
// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
|
||||
touchstart() {
|
||||
this.clearPropagation = false
|
||||
},
|
||||
|
||||
onTap() {
|
||||
if (this.clearPropagation) {
|
||||
// fix by mehaotian 兼容 nvue
|
||||
this.clearPropagation = false
|
||||
return
|
||||
}
|
||||
this.$emit('maskClick')
|
||||
if (!this.mkclick) return
|
||||
this.close()
|
||||
},
|
||||
/**
|
||||
* 顶部弹出样式处理
|
||||
*/
|
||||
top(type) {
|
||||
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
|
||||
this.ani = ['slide-top']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: this.bg,
|
||||
borderRadius:'15px 15px 0 0'
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
this.$nextTick(() => {
|
||||
if (this.messageChild && this.type === 'message') {
|
||||
this.messageChild.timerClose()
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 底部弹出样式处理
|
||||
*/
|
||||
bottom(type) {
|
||||
this.popupstyle = 'bottom'
|
||||
this.ani = ['slide-bottom']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
paddingBottom: 0 + 'px',
|
||||
backgroundColor: this.bg,
|
||||
borderRadius:'15px 15px 0 0'
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
},
|
||||
/**
|
||||
* 中间弹出样式处理
|
||||
*/
|
||||
center(type) {
|
||||
this.popupstyle = 'center'
|
||||
this.ani = ['zoom-out', 'fade']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
/* #ifndef APP-NVUE */
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
/* #endif */
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius:'15px 15px 0 0'
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
},
|
||||
left(type) {
|
||||
this.popupstyle = 'left'
|
||||
this.ani = ['slide-left']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
backgroundColor: this.bg,
|
||||
borderRadius:'15px 15px 0 0',
|
||||
/* #ifndef APP-NVUE */
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
/* #endif */
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
},
|
||||
right(type) {
|
||||
this.popupstyle = 'right'
|
||||
this.ani = ['slide-right']
|
||||
this.transClass = {
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
backgroundColor: this.bg,
|
||||
borderRadius:'15px 15px 0 0',
|
||||
/* #ifndef APP-NVUE */
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
/* #endif */
|
||||
}
|
||||
// TODO 兼容 type 属性 ,后续会废弃
|
||||
if (type) return
|
||||
this.showPopup = true
|
||||
this.showTrans = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.uni-popup {
|
||||
position: fixed;
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 99;
|
||||
|
||||
/* #endif */
|
||||
&.top,
|
||||
&.left,
|
||||
&.right {
|
||||
/* #ifdef H5 */
|
||||
top: var(--window-top);
|
||||
/* #endif */
|
||||
/* #ifndef H5 */
|
||||
top: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-popup__wrapper {
|
||||
border-radius: 15px;
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
|
||||
/* iphonex 等安全区设置,底部安全区适配 */
|
||||
/* #ifndef APP-NVUE */
|
||||
// padding-bottom: constant(safe-area-inset-bottom);
|
||||
// padding-bottom: env(safe-area-inset-bottom);
|
||||
/* #endif */
|
||||
&.left,
|
||||
&.right {
|
||||
/* #ifdef H5 */
|
||||
padding-top: var(--window-top);
|
||||
/* #endif */
|
||||
/* #ifndef H5 */
|
||||
padding-top: 0;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fixforpc-z-index {
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 999;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.fixforpc-top {
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,131 @@
|
||||
// const defaultOption = {
|
||||
// duration: 300,
|
||||
// timingFunction: 'linear',
|
||||
// delay: 0,
|
||||
// transformOrigin: '50% 50% 0'
|
||||
// }
|
||||
// #ifdef APP-NVUE
|
||||
const nvueAnimation = uni.requireNativePlugin('animation')
|
||||
// #endif
|
||||
class MPAnimation {
|
||||
constructor(options, _this) {
|
||||
this.options = options
|
||||
// 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
|
||||
this.animation = uni.createAnimation({
|
||||
...options
|
||||
})
|
||||
this.currentStepAnimates = {}
|
||||
this.next = 0
|
||||
this.$ = _this
|
||||
|
||||
}
|
||||
|
||||
_nvuePushAnimates(type, args) {
|
||||
let aniObj = this.currentStepAnimates[this.next]
|
||||
let styles = {}
|
||||
if (!aniObj) {
|
||||
styles = {
|
||||
styles: {},
|
||||
config: {}
|
||||
}
|
||||
} else {
|
||||
styles = aniObj
|
||||
}
|
||||
if (animateTypes1.includes(type)) {
|
||||
if (!styles.styles.transform) {
|
||||
styles.styles.transform = ''
|
||||
}
|
||||
let unit = ''
|
||||
if(type === 'rotate'){
|
||||
unit = 'deg'
|
||||
}
|
||||
styles.styles.transform += `${type}(${args+unit}) `
|
||||
} else {
|
||||
styles.styles[type] = `${args}`
|
||||
}
|
||||
this.currentStepAnimates[this.next] = styles
|
||||
}
|
||||
_animateRun(styles = {}, config = {}) {
|
||||
let ref = this.$.$refs['ani'].ref
|
||||
if (!ref) return
|
||||
return new Promise((resolve, reject) => {
|
||||
nvueAnimation.transition(ref, {
|
||||
styles,
|
||||
...config
|
||||
}, res => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_nvueNextAnimate(animates, step = 0, fn) {
|
||||
let obj = animates[step]
|
||||
if (obj) {
|
||||
let {
|
||||
styles,
|
||||
config
|
||||
} = obj
|
||||
this._animateRun(styles, config).then(() => {
|
||||
step += 1
|
||||
this._nvueNextAnimate(animates, step, fn)
|
||||
})
|
||||
} else {
|
||||
this.currentStepAnimates = {}
|
||||
typeof fn === 'function' && fn()
|
||||
this.isEnd = true
|
||||
}
|
||||
}
|
||||
|
||||
step(config = {}) {
|
||||
// #ifndef APP-NVUE
|
||||
this.animation.step(config)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
|
||||
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
|
||||
this.next++
|
||||
// #endif
|
||||
return this
|
||||
}
|
||||
|
||||
run(fn) {
|
||||
// #ifndef APP-NVUE
|
||||
this.$.animationData = this.animation.export()
|
||||
this.$.timer = setTimeout(() => {
|
||||
typeof fn === 'function' && fn()
|
||||
}, this.$.durationTime)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.isEnd = false
|
||||
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
|
||||
if(!ref) return
|
||||
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
|
||||
this.next = 0
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
|
||||
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
|
||||
'translateZ'
|
||||
]
|
||||
const animateTypes2 = ['opacity', 'backgroundColor']
|
||||
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
|
||||
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
|
||||
MPAnimation.prototype[type] = function(...args) {
|
||||
// #ifndef APP-NVUE
|
||||
this.animation[type](...args)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this._nvuePushAnimates(type, args)
|
||||
// #endif
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
export function createAnimation(option, _this) {
|
||||
if(!_this) return
|
||||
clearTimeout(_this.timer)
|
||||
return new MPAnimation(option, _this)
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createAnimation } from './createAnimation'
|
||||
|
||||
/**
|
||||
* Transition 过渡动画
|
||||
* @description 简单过渡动画组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=985
|
||||
* @property {Boolean} show = [false|true] 控制组件显示或隐藏
|
||||
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
|
||||
* @value fade 渐隐渐出过渡
|
||||
* @value slide-top 由上至下过渡
|
||||
* @value slide-right 由右至左过渡
|
||||
* @value slide-bottom 由下至上过渡
|
||||
* @value slide-left 由左至右过渡
|
||||
* @value zoom-in 由小到大过渡
|
||||
* @value zoom-out 由大到小过渡
|
||||
* @property {Number} duration 过渡动画持续时间
|
||||
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
|
||||
*/
|
||||
export default {
|
||||
name: 'uniTransition',
|
||||
emits:['click','change'],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
modeClass: {
|
||||
type: [Array, String],
|
||||
default() {
|
||||
return 'fade'
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
customClass:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
onceRender:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
transform: '',
|
||||
opacity: 1,
|
||||
animationData: {},
|
||||
durationTime: 300,
|
||||
config: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.open()
|
||||
} else {
|
||||
// 避免上来就执行 close,导致动画错乱
|
||||
if (this.isShow) {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 生成样式数据
|
||||
stylesObject() {
|
||||
let styles = {
|
||||
...this.styles,
|
||||
'transition-duration': this.duration / 1000 + 's'
|
||||
}
|
||||
let transform = ''
|
||||
for (let i in styles) {
|
||||
let line = this.toLine(i)
|
||||
transform += line + ':' + styles[i] + ';'
|
||||
}
|
||||
return transform
|
||||
},
|
||||
// 初始化动画条件
|
||||
transformStyles() {
|
||||
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 动画默认配置
|
||||
this.config = {
|
||||
duration: this.duration,
|
||||
timingFunction: 'ease',
|
||||
transformOrigin: '50% 50%',
|
||||
delay: 0
|
||||
}
|
||||
this.durationTime = this.duration
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* ref 触发 初始化动画
|
||||
*/
|
||||
init(obj = {}) {
|
||||
if (obj.duration) {
|
||||
this.durationTime = obj.duration
|
||||
}
|
||||
this.animation = createAnimation(Object.assign(this.config, obj),this)
|
||||
},
|
||||
/**
|
||||
* 点击组件触发回调
|
||||
*/
|
||||
onClick() {
|
||||
this.$emit('click', {
|
||||
detail: this.isShow
|
||||
})
|
||||
},
|
||||
/**
|
||||
* ref 触发 动画分组
|
||||
* @param {Object} obj
|
||||
*/
|
||||
step(obj, config = {}) {
|
||||
if (!this.animation) return
|
||||
for (let i in obj) {
|
||||
try {
|
||||
if(typeof obj[i] === 'object'){
|
||||
this.animation[i](...obj[i])
|
||||
}else{
|
||||
this.animation[i](obj[i])
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`方法 ${i} 不存在`)
|
||||
}
|
||||
}
|
||||
this.animation.step(config)
|
||||
return this
|
||||
},
|
||||
/**
|
||||
* ref 触发 执行动画
|
||||
*/
|
||||
run(fn) {
|
||||
if (!this.animation) return
|
||||
this.animation.run(fn)
|
||||
},
|
||||
// 开始过度动画
|
||||
open() {
|
||||
clearTimeout(this.timer)
|
||||
this.transform = ''
|
||||
this.isShow = true
|
||||
let { opacity, transform } = this.styleInit(false)
|
||||
if (typeof opacity !== 'undefined') {
|
||||
this.opacity = opacity
|
||||
}
|
||||
this.transform = transform
|
||||
// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
|
||||
this.$nextTick(() => {
|
||||
// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
|
||||
this.timer = setTimeout(() => {
|
||||
this.animation = createAnimation(this.config, this)
|
||||
this.tranfromInit(false).step()
|
||||
this.animation.run()
|
||||
this.$emit('change', {
|
||||
detail: this.isShow
|
||||
})
|
||||
}, 20)
|
||||
})
|
||||
},
|
||||
// 关闭过度动画
|
||||
close(type) {
|
||||
if (!this.animation) return
|
||||
this.tranfromInit(true)
|
||||
.step()
|
||||
.run(() => {
|
||||
this.isShow = false
|
||||
this.animationData = null
|
||||
this.animation = null
|
||||
let { opacity, transform } = this.styleInit(false)
|
||||
this.opacity = opacity || 1
|
||||
this.transform = transform
|
||||
this.$emit('change', {
|
||||
detail: this.isShow
|
||||
})
|
||||
})
|
||||
},
|
||||
// 处理动画开始前的默认样式
|
||||
styleInit(type) {
|
||||
let styles = {
|
||||
transform: ''
|
||||
}
|
||||
let buildStyle = (type, mode) => {
|
||||
if (mode === 'fade') {
|
||||
styles.opacity = this.animationType(type)[mode]
|
||||
} else {
|
||||
styles.transform += this.animationType(type)[mode] + ' '
|
||||
}
|
||||
}
|
||||
if (typeof this.modeClass === 'string') {
|
||||
buildStyle(type, this.modeClass)
|
||||
} else {
|
||||
this.modeClass.forEach(mode => {
|
||||
buildStyle(type, mode)
|
||||
})
|
||||
}
|
||||
return styles
|
||||
},
|
||||
// 处理内置组合动画
|
||||
tranfromInit(type) {
|
||||
let buildTranfrom = (type, mode) => {
|
||||
let aniNum = null
|
||||
if (mode === 'fade') {
|
||||
aniNum = type ? 0 : 1
|
||||
} else {
|
||||
aniNum = type ? '-100%' : '0'
|
||||
if (mode === 'zoom-in') {
|
||||
aniNum = type ? 0.8 : 1
|
||||
}
|
||||
if (mode === 'zoom-out') {
|
||||
aniNum = type ? 1.2 : 1
|
||||
}
|
||||
if (mode === 'slide-right') {
|
||||
aniNum = type ? '100%' : '0'
|
||||
}
|
||||
if (mode === 'slide-bottom') {
|
||||
aniNum = type ? '100%' : '0'
|
||||
}
|
||||
}
|
||||
this.animation[this.animationMode()[mode]](aniNum)
|
||||
}
|
||||
if (typeof this.modeClass === 'string') {
|
||||
buildTranfrom(type, this.modeClass)
|
||||
} else {
|
||||
this.modeClass.forEach(mode => {
|
||||
buildTranfrom(type, mode)
|
||||
})
|
||||
}
|
||||
|
||||
return this.animation
|
||||
},
|
||||
animationType(type) {
|
||||
return {
|
||||
fade: type ? 1 : 0,
|
||||
'slide-top': `translateY(${type ? '0' : '-100%'})`,
|
||||
'slide-right': `translateX(${type ? '0' : '100%'})`,
|
||||
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
|
||||
'slide-left': `translateX(${type ? '0' : '-100%'})`,
|
||||
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
|
||||
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
|
||||
}
|
||||
},
|
||||
// 内置动画类型与实际动画对应字典
|
||||
animationMode() {
|
||||
return {
|
||||
fade: 'opacity',
|
||||
'slide-top': 'translateY',
|
||||
'slide-right': 'translateX',
|
||||
'slide-bottom': 'translateY',
|
||||
'slide-left': 'translateX',
|
||||
'zoom-in': 'scale',
|
||||
'zoom-out': 'scale'
|
||||
}
|
||||
},
|
||||
// 驼峰转中横线
|
||||
toLine(name) {
|
||||
return name.replace(/([A-Z])/g, '-$1').toLowerCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -14,7 +14,6 @@ import { VUE_APP_UPLOAD_URL } from '@/config';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps(['modelValue'])
|
||||
console.log("--> % modelValue:\n", props.modelValue)
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const list = ref(props.modelValue)
|
||||
@ -38,7 +37,6 @@ const afterRead = async (event) => {
|
||||
})
|
||||
for (let i = 0; i < lists.length; i++) {
|
||||
const result = await uploadFilePromise(lists[i].url)
|
||||
console.log("gxs --> % afterRead % result:\n", result)
|
||||
let item = list.value[fileListLen]
|
||||
list.value.splice(fileListLen, 1, Object.assign(item, {
|
||||
status: 'success',
|
||||
@ -60,9 +58,8 @@ const uploadFilePromise = (url) => {
|
||||
user: 'test'
|
||||
},
|
||||
success: (res) => {
|
||||
console.log("gxs --> % returnnewPromise % res:\n", res)
|
||||
setTimeout(() => {
|
||||
resolve(res.data.data)
|
||||
resolve(JSON.parse(res.data).data)
|
||||
}, 10)
|
||||
}
|
||||
});
|
||||
@ -71,7 +68,7 @@ const uploadFilePromise = (url) => {
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.activity {
|
||||
|
||||
&-header {
|
||||
|
@ -31,6 +31,14 @@ const buttonText = computed(() => {
|
||||
});
|
||||
|
||||
const startCountdown = () => {
|
||||
if(!props.mobile){
|
||||
uni.showToast({
|
||||
title: '用户名不能为空',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
})
|
||||
return false
|
||||
}
|
||||
if (!countingDown.value) {
|
||||
countingDown.value = true;
|
||||
handleSendSmsCode()
|
||||
@ -42,7 +50,6 @@ const handleSendSmsCode = () => {
|
||||
uni.showLoading({
|
||||
title: '发送验证码中'
|
||||
});
|
||||
console.log("gxs --> % handleSendSmsCode % props.mobile:\n", props.mobile)
|
||||
sendSmsCode({
|
||||
"mobile": props.mobile,
|
||||
"scene": props.scene
|
||||
@ -55,7 +62,6 @@ const handleSendSmsCode = () => {
|
||||
});
|
||||
timer.value = setInterval(() => {
|
||||
countdownSeconds.value--;
|
||||
console.log("gxs --> % timer % countdownSeconds.value:\n", countdownSeconds.value)
|
||||
if (countdownSeconds.value <= 0) {
|
||||
clearInterval(timer.value);
|
||||
countdownSeconds.value = 60; // 倒计时结束后重置为初始值
|
||||
@ -64,7 +70,6 @@ const handleSendSmsCode = () => {
|
||||
}, 1000);
|
||||
}).catch(error => {
|
||||
countingDown.value = false;
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@ -73,7 +78,7 @@ onUnmounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="scss">
|
||||
.goods {
|
||||
position: relative;
|
||||
padding: 30rpx 0;
|
||||
|
Reference in New Issue
Block a user