@ -0,0 +1,341 @@ | |||
<template> | |||
<view class="page__view"> | |||
<navbar title="申请售后" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#F3F2F7" /> | |||
<view class="main"> | |||
<view v-if="form.type != 2"> | |||
<view class="product" v-for="item in applyServiceProduct" :key="item.id"> | |||
<productCard :data="item" ></productCard> | |||
</view> | |||
</view> | |||
<uv-form | |||
ref="form" | |||
:model="form" | |||
:rules="rules" | |||
errorType="toast" | |||
> | |||
<view class="card info"> | |||
<view class="card-header">申请信息</view> | |||
<!-- 其他 --> | |||
<template v-if="form.type == 2"> | |||
<view class="form-item"> | |||
<uv-form-item prop="remark" :customStyle="formItemStyle"> | |||
<view class="form-item-label">备注</view> | |||
<view class="form-item-content"> | |||
<formTextarea v-model="form.remark" height="436rpx"></formTextarea> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</template> | |||
<!-- 退货退款, 换货 --> | |||
<template v-else> | |||
<view class="form-item"> | |||
<uv-form-item prop="type" :customStyle="formItemStyle"> | |||
<view class="form-item-label light">申请类型</view> | |||
<view class="form-item-content input type"> | |||
{{ typeDesc }} | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="reason" :customStyle="formItemStyle"> | |||
<view class="form-item-label">申请原因</view> | |||
<view class="form-item-content input"> | |||
<formInput v-model="form.reason" placeholder="请输入内容"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="count" :customStyle="formItemStyle"> | |||
<view class="flex row"> | |||
<view class="form-item-label">退货数量</view> | |||
<view class="form-item-content count"> | |||
<uv-number-box | |||
v-model="form.count" | |||
bgColor="transparent" | |||
:iconStyle="{ | |||
background: '#FFFFFF', | |||
fontSize: '20rpx', | |||
lineHeight: 1, | |||
padding: '9px', | |||
borderRadius: '50%', | |||
}" | |||
></uv-number-box> | |||
</view> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="amount" :customStyle="formItemStyle"> | |||
<view class="form-item-label">申请金额(元)</view> | |||
<view class="form-item-content input"> | |||
<formInput v-model="form.amount" placeholder="请输入内容" type="number"></formInput> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</template> | |||
<view class="form-item"> | |||
<uv-form-item prop="images" :customStyle="formItemStyle"> | |||
<view class="form-item-label">上传图片/视频(选填)</view> | |||
<view class="form-item-content upload"> | |||
<formUpload v-model="form.images"></formUpload> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</view> | |||
<view class="card phone"> | |||
<view class="form-item"> | |||
<uv-form-item prop="phone" :customStyle="formItemStyle"> | |||
<view class="flex row"> | |||
<view class="form-item-label">联系电话</view> | |||
<view class="form-item-content"> | |||
<formInput v-model="form.phone" inputAlign="right"></formInput> | |||
</view> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</view> | |||
</uv-form> | |||
</view> | |||
<view class="bottom"> | |||
<button class="btn" @click="onSubmit">提交申请</button> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import { mapState } from 'vuex' | |||
import productCard from './productCard.vue' | |||
import formInput from '@/pages_order/components/formInput.vue' | |||
import formTextarea from '@/pages_order/components/formTextarea.vue' | |||
import formUpload from '@/pages_order/components/formUpload.vue' | |||
export default { | |||
components: { | |||
productCard, | |||
formInput, | |||
formTextarea, | |||
formUpload, | |||
}, | |||
data() { | |||
return { | |||
id: null, | |||
typeOptions: [ | |||
{ | |||
id: '001', | |||
label: '退货退款', | |||
value: 0, | |||
}, | |||
{ | |||
id: '002', | |||
label: '换货', | |||
value: 1, | |||
}, | |||
{ | |||
id: '003', | |||
label: '其他', | |||
value: 2, | |||
}, | |||
], | |||
form: { | |||
type: null, | |||
reason: null, | |||
count: 1, | |||
amount: null, | |||
images: [], | |||
phone: null, | |||
remark: null, | |||
}, | |||
rules: { | |||
'reason': { | |||
type: 'string', | |||
required: true, | |||
message: '请输入申请原因', | |||
}, | |||
'count': { | |||
type: 'number', | |||
required: true, | |||
message: '请输入退货数量', | |||
}, | |||
'amount': { | |||
type: 'number', | |||
required: true, | |||
message: '请输入申请金额(元)', | |||
}, | |||
'phone': { | |||
type: 'string', | |||
required: true, | |||
message: '联系电话', | |||
}, | |||
'remark': { | |||
type: 'string', | |||
required: true, | |||
message: '请输入备注', | |||
}, | |||
}, | |||
formItemStyle: { padding: 0 }, | |||
} | |||
}, | |||
computed: { | |||
...mapState(['configList', 'userInfo', 'applyServiceProduct']), | |||
typeDesc() { | |||
const type = this.form.type | |||
return this.typeOptions.find(item => item.value === type)?.label | |||
}, | |||
}, | |||
onLoad(arg) { | |||
const { id, type } = arg | |||
this.id = id | |||
this.form.type = this.typeOptions.find(item => item.value == type)?.value | |||
}, | |||
methods: { | |||
async onSubmit() { | |||
try { | |||
const res = await this.$refs.form.validate() | |||
console.log('onSubmit res', res) | |||
// todo | |||
setTimeout(() => { | |||
this.$utils.navigateBack() | |||
}, 800) | |||
} catch (err) { | |||
console.log('onSubmit err', err) | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.page__view { | |||
width: 100vw; | |||
min-height: 100vh; | |||
background-color: $uni-bg-color; | |||
position: relative; | |||
/deep/ .nav-bar__view { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
} | |||
} | |||
.main { | |||
padding: calc(var(--status-bar-height) + 144rpx) 32rpx 242rpx 32rpx; | |||
} | |||
.product { | |||
& + & { | |||
margin-top: 32rpx; | |||
} | |||
} | |||
.card { | |||
margin-top: 40rpx; | |||
padding: 32rpx; | |||
background: #FAFAFF; | |||
border: 2rpx solid #FFFFFF; | |||
border-radius: 32rpx; | |||
&-header { | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1.4; | |||
color: #252545; | |||
margin-bottom: 32rpx; | |||
} | |||
&.phone { | |||
padding-top: 28rpx; | |||
padding-bottom: 28rpx; | |||
} | |||
} | |||
.form { | |||
&-item { | |||
border-bottom: 2rpx solid #EEEEEE; | |||
&:last-child { | |||
border: none; | |||
} | |||
& + & { | |||
margin-top: 32rpx; | |||
} | |||
&-label { | |||
padding: 8rpx 0; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 26rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
&.light { | |||
color: #8B8B8B; | |||
} | |||
} | |||
&-content { | |||
&.input { | |||
padding: 6rpx 0; | |||
} | |||
&.upload, | |||
&.count { | |||
padding: 8rpx 0; | |||
} | |||
} | |||
.type { | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 32rpx; | |||
line-height: 1.4; | |||
color: #393939; | |||
} | |||
} | |||
} | |||
.row { | |||
justify-content: space-between; | |||
} | |||
.bottom { | |||
position: fixed; | |||
left: 0; | |||
bottom: 0; | |||
z-index: 2; | |||
width: 100vw; | |||
height: 200rpx; | |||
padding: 24rpx 40rpx; | |||
background: #FFFFFF; | |||
box-sizing: border-box; | |||
.btn { | |||
width: 100%; | |||
padding: 16rpx 0; | |||
box-sizing: border-box; | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1; | |||
color: #FFFFFF; | |||
background-image: linear-gradient(to right, #4B348F, #845CFA); | |||
border-radius: 41rpx; | |||
} | |||
} | |||
</style> |
@ -0,0 +1,230 @@ | |||
<template> | |||
<view class="flex card"> | |||
<view v-if="mode == 'edit'"> | |||
<uv-checkbox-group | |||
v-model="checkboxValue" | |||
shape="circle" | |||
@change="onCheckChange" | |||
> | |||
<uv-checkbox | |||
size="36rpx" | |||
icon-size="36rpx" | |||
activeColor="#7451DE" | |||
:name="1" | |||
></uv-checkbox> | |||
</uv-checkbox-group> | |||
</view> | |||
<view class="flex right"> | |||
<view class="img-box"> | |||
<image class="img" :src="data.url" mode="aspectFit"></image> | |||
</view> | |||
<view class="info"> | |||
<view class="title">{{ data.name }}</view> | |||
<view class="flex price-box"> | |||
<view class="flex price">¥<text class="highlight">{{ data.price.toFixed(2) }}</text></view> | |||
</view> | |||
<view class="flex tool"> | |||
<view class="flex count"> | |||
规格:<text class="highlight">{{ data.countDesc || data.count }}</text> | |||
</view> | |||
<view class="flex status">{{ statusDesc }}</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view v-if="data.customized" class="sup customized"> | |||
定制组合 | |||
</view> | |||
<view v-else-if="data.free" class="sup free"> | |||
自定组合 | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
const STATUS_AND_DESC_MAPPING = { | |||
0: '待支付', | |||
1: '待发货', | |||
2: '待收货', | |||
3: '待评价', | |||
4: '已完成', | |||
5: '售后', | |||
} | |||
export default { | |||
props: { | |||
data: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
mode: { | |||
type: String, | |||
default: 'read' // read | edit | |||
}, | |||
}, | |||
data() { | |||
return { | |||
checkboxValue : [], | |||
} | |||
}, | |||
computed: { | |||
checked: { | |||
set(val) { | |||
this.checkboxValue = val ? [1] : [] | |||
if (this.data.selected == val) { | |||
return | |||
} | |||
this.$emit('select', val) | |||
}, | |||
get() { | |||
return this.checkboxValue[0] == 1 ? true : false | |||
} | |||
}, | |||
statusDesc() { | |||
return STATUS_AND_DESC_MAPPING[this.data.status] | |||
}, | |||
}, | |||
watch: { | |||
data: { | |||
handler(val) { | |||
this.checked = val.selected | |||
}, | |||
immediate: true, | |||
deep: true, | |||
} | |||
}, | |||
methods: { | |||
onCheckChange(arr) { | |||
this.checked = arr[0] == 1 ? true : false | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.card { | |||
position: relative; | |||
height: 240rpx; | |||
padding: 0 32rpx; | |||
background-image: linear-gradient(#FAFAFF, #F3F3F3); | |||
border: 2rpx solid #FFFFFF; | |||
border-radius: 32rpx; | |||
box-sizing: border-box; | |||
column-gap: 24rpx; | |||
overflow: hidden; | |||
/deep/ .uv-checkbox__label-wra { | |||
padding: 0; | |||
} | |||
} | |||
.right { | |||
flex: 1; | |||
column-gap: 24rpx; | |||
} | |||
.img { | |||
&-box { | |||
width: 144rpx; | |||
height: 144rpx; | |||
border-radius: 16rpx; | |||
overflow: hidden; | |||
} | |||
width: 100%; | |||
height: 100%; | |||
} | |||
.info { | |||
flex: 1; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
line-height: 1.4; | |||
font-size: 26rpx; | |||
color: #8B8B8B; | |||
.title { | |||
font-weight: 600; | |||
font-size: 28rpx; | |||
color: #000000; | |||
} | |||
.desc { | |||
margin-top: 8rpx; | |||
line-height: 1.5; | |||
} | |||
.price { | |||
&-box { | |||
margin-top: 8rpx; | |||
justify-content: flex-start; | |||
column-gap: 20rpx; | |||
} | |||
font-weight: 600; | |||
font-size: 24rpx; | |||
color: #7451DE; | |||
.highlight { | |||
margin: 0 8rpx; | |||
font-size: 32rpx; | |||
} | |||
&-origin { | |||
font-size: 28rpx; | |||
line-height: 1; | |||
text-decoration: line-through; | |||
} | |||
} | |||
.tool { | |||
margin-top: 8rpx; | |||
justify-content: space-between; | |||
.count { | |||
.highlight { | |||
margin: 0 8rpx; | |||
color: #252545; | |||
} | |||
} | |||
.btn { | |||
padding: 8rpx 40rpx; | |||
font-family: PingFang SC; | |||
font-weight: 600; | |||
font-size: 28rpx; | |||
line-height: 1.5; | |||
color: #FFFFFF; | |||
background: #7451DE; | |||
border-radius: 30rpx; | |||
} | |||
} | |||
} | |||
.sup { | |||
position: absolute; | |||
top: 28rpx; | |||
right: -60rpx; | |||
padding: 5rpx 52rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 28rpx; | |||
line-height: 1.5; | |||
white-space: nowrap; | |||
transform: rotate(45deg); | |||
&.customized { | |||
color: #FFFFFF; | |||
background: #252545; | |||
} | |||
&.free { | |||
color: #252545; | |||
background: #D7D7FF; | |||
} | |||
} | |||
</style> |
@ -0,0 +1,269 @@ | |||
<template> | |||
<view> | |||
<uv-popup ref="popup" mode="bottom" bgColor="none" > | |||
<view class="popup__view"> | |||
<view class="flex header"> | |||
<view class="title">选择售后类型</view> | |||
<button class="btn" @click="close">关闭</button> | |||
</view> | |||
<view class="form"> | |||
<uv-form | |||
ref="form" | |||
:model="form" | |||
:rules="rules" | |||
errorType="toast" | |||
> | |||
<view class="form-item"> | |||
<uv-form-item prop="type" :customStyle="formItemStyle"> | |||
<view class="form-item-label">请选择售后类型</view> | |||
<view class="form-item-content flex tags"> | |||
<view | |||
v-for="item in typeOptions" | |||
:key="item.id" | |||
:class="['flex', 'tag', item.value === form.type ? 'is-active' : '']" | |||
@click="onSelectType(item.value)" | |||
> | |||
{{ item.label }} | |||
</view> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
<view class="form-item"> | |||
<uv-form-item prop="productList" :customStyle="formItemStyle"> | |||
<view class="form-item-label">选择需要售后的商品</view> | |||
<view class="form-item-content products"> | |||
<view class="product" v-for="(item, index) in productList" :key="item.id"> | |||
<productCard | |||
mode="edit" | |||
:data="item" | |||
@select="onSelectProduct(index, $event)" | |||
></productCard> | |||
</view> | |||
</view> | |||
</uv-form-item> | |||
</view> | |||
</uv-form> | |||
</view> | |||
<view class="footer"> | |||
<button class="flex btn" @click="onNext">下一步</button> | |||
</view> | |||
</view> | |||
</uv-popup> | |||
</view> | |||
</template> | |||
<script> | |||
import productCard from './productCard.vue' | |||
export default { | |||
components: { | |||
productCard, | |||
}, | |||
data() { | |||
return { | |||
id: null, | |||
productList: [], | |||
typeOptions: [ | |||
{ | |||
id: '001', | |||
label: '退货退款', | |||
value: 0, | |||
}, | |||
{ | |||
id: '002', | |||
label: '换货', | |||
value: 1, | |||
}, | |||
{ | |||
id: '003', | |||
label: '其他', | |||
value: 2, | |||
}, | |||
], | |||
form: { | |||
type: null, | |||
productList: [], | |||
}, | |||
rules: { | |||
'type': { | |||
type: 'number', | |||
required: true, | |||
message: '请选择售后类型', | |||
}, | |||
'productList': { | |||
type: 'array', | |||
required: true, | |||
message: '请选择售后商品', | |||
}, | |||
}, | |||
formItemStyle: { padding: 0 }, | |||
} | |||
}, | |||
methods: { | |||
open(data) { | |||
const { | |||
id, | |||
productList, | |||
} = data | |||
this.id = id | |||
this.productList = productList.map(item => ({ ...item, selected: false })) | |||
this.$refs.popup.open() | |||
}, | |||
close() { | |||
this.$refs.popup.close() | |||
}, | |||
onSelectType(type) { | |||
this.form.type = type | |||
}, | |||
onSelectProduct(index, selected) { | |||
console.log('onSelectProduct', index, selected) | |||
this.productList[index].selected = selected | |||
this.form.productList = this.productList.filter(item => item.selected) | |||
}, | |||
async onNext() { | |||
try { | |||
const res = await this.$refs.form.validate() | |||
console.log('onNext res', res) | |||
const { | |||
type, | |||
productList, | |||
} = this.form | |||
// todo: submit | |||
this.$store.commit('setApplyServiceProduct', productList) | |||
this.$utils.navigateTo(`/pages_order/service/index?id=${this.id}&type=${type}`) | |||
this.close() | |||
} catch (err) { | |||
console.log('onNext err', err) | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
.popup__view { | |||
width: 100vw; | |||
display: flex; | |||
flex-direction: column; | |||
box-sizing: border-box; | |||
background: #FFFFFF; | |||
border-top-left-radius: 32rpx; | |||
border-top-right-radius: 32rpx; | |||
} | |||
.header { | |||
position: relative; | |||
width: 100%; | |||
padding: 24rpx 0; | |||
box-sizing: border-box; | |||
border-bottom: 2rpx solid #EEEEEE; | |||
.title { | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 34rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
} | |||
.btn { | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 32rpx; | |||
line-height: 1.4; | |||
color: #8B8B8B; | |||
position: absolute; | |||
top: 26rpx; | |||
left: 40rpx; | |||
} | |||
} | |||
.form { | |||
&-item { | |||
& + & { | |||
border-top: 2rpx solid #EEEEEE; | |||
} | |||
&-label { | |||
padding: 24rpx 40rpx; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 28rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
} | |||
} | |||
} | |||
.tags { | |||
padding: 12rpx 44rpx 44rpx 44rpx; | |||
column-gap: 24rpx; | |||
.tag { | |||
flex: 1; | |||
padding: 18rpx 0; | |||
font-family: PingFang SC; | |||
font-weight: 400; | |||
font-size: 28rpx; | |||
line-height: 1.4; | |||
color: #181818; | |||
background: #F9F9F9; | |||
border: 2rpx solid #F9F9F9; | |||
border-radius: 16rpx; | |||
box-sizing: border-box; | |||
&.is-active { | |||
color: #7451DE; | |||
background: #F2EEFF; | |||
border-color: #7451DE; | |||
} | |||
} | |||
} | |||
.products { | |||
padding: 0 32rpx; | |||
} | |||
.product { | |||
& + & { | |||
margin-top: 32rpx; | |||
} | |||
} | |||
.footer { | |||
width: 100%; | |||
// todo:check | |||
// height: 214rpx; | |||
padding: 32rpx 40rpx; | |||
box-sizing: border-box; | |||
border-top: 2rpx solid #F1F1F1; | |||
.btn { | |||
width: 100%; | |||
padding: 16rpx 0; | |||
font-family: PingFang SC; | |||
font-weight: 500; | |||
font-size: 36rpx; | |||
line-height: 1.4; | |||
color: #FFFFFF; | |||
background-image: linear-gradient(to right, #4B348F, #845CFA); | |||
border-radius: 41rpx; | |||
} | |||
} | |||
</style> |