chuangte_bike_newxcx/page_shanghu/guanli/tuiguang_detail.vue
2026-01-28 18:00:39 +08:00

819 lines
18 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<u-navbar :title="isEdit ? '编辑推广' : '新增推广'" :border-bottom="false" :background="bgc" back-icon-color="#111827" title-color='#111827'
title-size='34' title-bold height='44'>
</u-navbar>
<!-- 表单区域 -->
<view class="form-container">
<!-- 用户手机号 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">用户手机号</text>
<text class="label-hint"></text>
</view>
<view class="form-input">
<input type="text" v-model="formData.userPhone" placeholder="请输入用户手机号" maxlength="11" />
</view>
</view>
<!-- 分成比例 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">分成比例</text>
<text class="label-hint">0-100%</text>
</view>
<view class="form-input with-suffix">
<input type="number" :value="pointPercent" placeholder="请输入分成比例" @input="onPointInput" />
<text class="suffix">%</text>
</view>
</view>
<!-- 状态 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">状态</text>
</view>
<view class="form-select" @click="showStatusPicker = true">
<text :class="[formData.status ? '' : 'placeholder']">{{ statusText || '请选择状态' }}</text>
<u-icon name="arrow-right" size="28" color="#999"></u-icon>
</view>
</view>
<!-- 关联卡券 -->
<view class="form-item">
<view class="form-label">
<text class="label-text">关联卡券</text>
<text class="label-hint">可多选</text>
</view>
<view class="form-select" @click="openCouponPicker">
<text :class="[selectedCoupons.length === 0 ? 'placeholder' : '']">
{{ selectedCoupons.length > 0 ? '已选择' + selectedCoupons.length + '张卡券' : '请选择卡券' }}
</text>
<u-icon name="arrow-right" size="28" color="#999"></u-icon>
</view>
</view>
<!-- 已选卡券展示 -->
<view class="selected-coupons" v-if="selectedCoupons.length > 0">
<view class="coupon-tag" v-for="(coupon, index) in selectedCoupons" :key="coupon.id" @click="removeCoupon(index)">
<text>{{ coupon.name }}</text>
<u-icon name="close" size="24" color="#999"></u-icon>
</view>
</view>
<!-- 备注 -->
<view class="form-item textarea-item">
<view class="form-label">
<text class="label-text">备注</text>
<text class="label-hint">选填</text>
</view>
<view class="form-textarea">
<textarea v-model="formData.remark" placeholder="请输入备注信息" maxlength="200" />
<text class="word-count">{{ remarkLength }}/200</text>
</view>
</view>
</view>
<!-- 底部占位 -->
<view class="footer-placeholder"></view>
<!-- 底部按钮 -->
<view class="footer-container">
<view class="btn-group" v-if="isEdit">
<view class="delete-btn" @click="handleDelete">
<text>删除</text>
</view>
<view class="save-btn" @click="handleSave">
<text>保存修改</text>
</view>
</view>
<view class="add-btn-wrap" v-else @click="handleSave">
<view class="btn-shadow"></view>
<view class="btn-content">
<text>确认新增</text>
</view>
</view>
</view>
<!-- 状态选择器 -->
<u-select v-model="showStatusPicker" :list="statusList" @confirm="onStatusConfirm"></u-select>
<!-- 卡券选择弹窗 -->
<view class="coupon-popup" v-if="showCouponPicker">
<view class="popup-mask" @click="closeCouponPicker"></view>
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">选择卡券</text>
<text class="popup-close" @click="closeCouponPicker">×</text>
</view>
<!-- 搜索框 -->
<view class="search-box">
<u-icon name="search" size="28" color="#999"></u-icon>
<input type="text" v-model="couponSearchKey" placeholder="搜索卡券名称" @input="filterCoupons" />
</view>
<!-- 卡券列表 -->
<scroll-view scroll-y class="coupon-list">
<view class="coupon-item"
v-for="coupon in filteredCouponList"
:key="coupon.id"
@click="toggleCoupon(coupon)"
:class="[coupon.isSelected ? 'selected' : '']">
<view class="coupon-info">
<text class="coupon-name">{{ coupon.name }}</text>
<text class="coupon-desc">有效期:{{ coupon.validDays }}天 | ¥{{ coupon.price }}</text>
</view>
<view class="check-box" :class="[coupon.isSelected ? 'checked' : '']">
<u-icon v-if="coupon.isSelected" name="checkmark" size="24" color="#fff"></u-icon>
</view>
</view>
<u-empty v-if="filteredCouponList.length === 0" mode="search" text="暂无卡券" margin-top="60"></u-empty>
</scroll-view>
<!-- 确认按钮 -->
<view class="popup-footer">
<view class="confirm-btn" @click="confirmCouponSelect">
<text>确认选择 ({{ tempSelectedCount }})</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
bgc: {
backgroundColor: "#F7F8FA",
},
isEdit: false,
id: '',
areaId: '',
formData: {
areaId: '',
userPhone: '',
point: null,
status: '1',
couponIds: [],
remark: ''
},
pointPercent: '', // 用于显示的百分比值
showStatusPicker: false,
statusList: [
{ value: '1', label: '启用' },
{ value: '2', label: '禁用' }
],
showCouponPicker: false,
couponList: [], // 所有卡券列表
filteredCouponList: [], // 过滤后的卡券列表
selectedCoupons: [], // 已选择的卡券
tempSelectedIds: [], // 临时选择的卡券ID弹窗中
couponSearchKey: ''
}
},
computed: {
// 状态文本
statusText() {
if (this.formData.status === '1') return '启用'
if (this.formData.status === '2') return '禁用'
return ''
},
// 备注长度
remarkLength() {
return (this.formData.remark || '').length
},
// 临时选择数量
tempSelectedCount() {
return this.tempSelectedIds.length
}
},
onLoad(option) {
this.areaId = option.areaId || ''
this.formData.areaId = this.areaId
if (option.id) {
this.id = option.id
this.isEdit = true
this.getDetail()
}
this.getCouponList()
},
methods: {
// 分成比例输入(只允许整数 0-100
onPointInput(e) {
let v = (e && e.detail && e.detail.value) ? String(e.detail.value) : ''
// 只保留数字
v = v.replace(/\\D/g, '')
if (v === '') {
this.pointPercent = ''
return
}
let num = parseInt(v, 10)
if (isNaN(num)) {
this.pointPercent = ''
return
}
if (num > 100) num = 100
if (num < 0) num = 0
this.pointPercent = String(num)
},
// 获取详情
getDetail() {
this.$u.get('/bst/areaPromotion/' + this.id).then(res => {
if (res.code === 200) {
const data = res.data || {}
this.formData = {
areaId: data.areaId || this.areaId,
userPhone: data.userPhone || '',
point: data.point,
status: data.status || '1',
couponIds: data.couponIds || [],
remark: data.remark || ''
}
// 转换分成比例为百分比显示
if (data.point !== null && data.point !== undefined) {
this.pointPercent = String(Math.round(data.point * 100))
}
// 设置已选卡券
if (data.couponIds && data.couponIds.length > 0) {
this.setSelectedCoupons(data.couponIds)
}
} else {
uni.showToast({
title: res.msg || '获取详情失败',
icon: 'none'
})
}
})
},
// 获取卡券列表
getCouponList() {
this.$u.get('/bst/vip/list', {
areaId: this.areaId,
pageNum: 1,
pageSize: 999
}).then(res => {
if (res.code === 200) {
const rows = res.rows || []
// 给每个卡券添加isSelected属性
this.couponList = rows.map(item => {
return {
...item,
isSelected: false
}
})
this.filteredCouponList = JSON.parse(JSON.stringify(this.couponList))
// 如果已有选中的卡券,更新选中状态
if (this.formData.couponIds && this.formData.couponIds.length > 0) {
this.setSelectedCoupons(this.formData.couponIds)
}
}
})
},
// 根据ID设置已选卡券
setSelectedCoupons(ids) {
this.selectedCoupons = this.couponList.filter(item => ids.indexOf(item.id) > -1)
},
// 打开卡券选择器
openCouponPicker() {
// 初始化临时选择ID
this.tempSelectedIds = this.selectedCoupons.map(item => item.id)
this.couponSearchKey = ''
// 更新列表的选中状态
this.filteredCouponList = this.couponList.map(item => {
return {
...item,
isSelected: this.tempSelectedIds.indexOf(item.id) > -1
}
})
this.showCouponPicker = true
},
// 关闭卡券选择器
closeCouponPicker() {
this.showCouponPicker = false
},
// 过滤卡券
filterCoupons() {
let list = []
if (!this.couponSearchKey) {
list = JSON.parse(JSON.stringify(this.couponList))
} else {
const key = this.couponSearchKey.toLowerCase()
list = this.couponList.filter(item => item.name.toLowerCase().indexOf(key) > -1)
}
// 更新选中状态
this.filteredCouponList = list.map(item => {
return {
...item,
isSelected: this.tempSelectedIds.indexOf(item.id) > -1
}
})
},
// 切换卡券选择
toggleCoupon(coupon) {
const index = this.tempSelectedIds.indexOf(coupon.id)
if (index > -1) {
this.tempSelectedIds.splice(index, 1)
} else {
this.tempSelectedIds.push(coupon.id)
}
// 更新列表选中状态
this.filteredCouponList = this.filteredCouponList.map(item => {
return {
...item,
isSelected: this.tempSelectedIds.indexOf(item.id) > -1
}
})
},
// 确认卡券选择
confirmCouponSelect() {
this.selectedCoupons = this.couponList.filter(item => this.tempSelectedIds.indexOf(item.id) > -1)
this.formData.couponIds = this.tempSelectedIds.slice()
this.showCouponPicker = false
},
// 移除已选卡券
removeCoupon(index) {
this.selectedCoupons.splice(index, 1)
this.formData.couponIds = this.selectedCoupons.map(item => item.id)
},
// 状态选择确认
onStatusConfirm(e) {
this.formData.status = e[0].value
},
// 保存
handleSave() {
// 转换分成比例
if (this.pointPercent) {
const percent = parseInt(this.pointPercent, 10)
if (isNaN(percent) || percent < 0 || percent > 100) {
uni.showToast({
title: '分成比例需在0-100之间',
icon: 'none'
})
return
}
this.formData.point = percent / 100
} else {
this.formData.point = null
}
// 验证手机号格式
if (this.formData.userPhone && !/^1[3-9]\d{9}$/.test(this.formData.userPhone)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
})
return
}
const submitData = JSON.parse(JSON.stringify(this.formData))
if (this.isEdit) {
submitData.id = this.id
// 修改
this.$u.put('/bst/areaPromotion', submitData).then(res => {
if (res.code === 200) {
uni.showToast({
title: '修改成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: res.msg || '修改失败',
icon: 'none'
})
}
})
} else {
// 新增
this.$u.post('/bst/areaPromotion', submitData).then(res => {
if (res.code === 200) {
uni.showToast({
title: '新增成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: res.msg || '新增失败',
icon: 'none'
})
}
})
}
},
// 删除
handleDelete() {
uni.showModal({
title: '提示',
content: '确定要删除该推广吗?',
success: (res) => {
if (res.confirm) {
this.$u.delete('/bst/areaPromotion/' + this.id).then(res => {
if (res.code === 200) {
uni.showToast({
title: '删除成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: res.msg || '删除失败',
icon: 'none'
})
}
})
}
}
})
}
}
}
</script>
<style lang="scss">
page {
background-color: #F7F8FA;
min-height: 100vh;
}
.page {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.form-container {
padding: 24rpx 32rpx;
}
.form-item {
background: #FFFFFF;
border-radius: 20rpx;
padding: 32rpx;
margin-bottom: 24rpx;
.form-label {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.label-text {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.label-hint {
font-size: 24rpx;
color: #999;
margin-left: 12rpx;
}
}
.form-input {
background: #F7F8FA;
border-radius: 12rpx;
padding: 24rpx;
input {
width: 100%;
font-size: 28rpx;
color: #333;
}
}
.with-suffix {
display: flex;
align-items: center;
input {
flex: 1;
}
.suffix {
font-size: 28rpx;
color: #666;
margin-left: 12rpx;
}
}
.form-select {
background: #F7F8FA;
border-radius: 12rpx;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
text {
font-size: 28rpx;
color: #333;
}
.placeholder {
color: #999;
}
}
}
.textarea-item {
.form-textarea {
background: #F7F8FA;
border-radius: 12rpx;
padding: 24rpx;
position: relative;
textarea {
width: 100%;
height: 200rpx;
font-size: 28rpx;
color: #333;
}
.word-count {
position: absolute;
right: 24rpx;
bottom: 16rpx;
font-size: 24rpx;
color: #999;
}
}
}
.selected-coupons {
background: #FFFFFF;
border-radius: 20rpx;
padding: 24rpx 32rpx;
margin-bottom: 24rpx;
display: flex;
flex-wrap: wrap;
.coupon-tag {
display: flex;
align-items: baseline;
background: linear-gradient(135deg, #E8F4FD 0%, #D4ECFD 100%);
padding: 12rpx 20rpx;
border-radius: 30rpx;
margin-right: 16rpx;
margin-bottom: 16rpx;
text {
font-size: 26rpx;
color: #4297F3;
margin-right: 12rpx;
}
}
}
.footer-placeholder {
height: 200rpx;
}
.footer-container {
position: fixed;
bottom: 60rpx;
left: 0;
right: 0;
display: flex;
justify-content: center;
z-index: 100;
padding: 0 32rpx;
.btn-group {
display: flex;
width: 100%;
.delete-btn {
flex: 1;
height: 100rpx;
background: #FFFFFF;
border: 2rpx solid #FF4D4F;
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
text {
font-size: 32rpx;
font-weight: 600;
color: #FF4D4F;
}
}
.save-btn {
flex: 2;
height: 100rpx;
background: linear-gradient(135deg, #4297F3 0%, #2B76E5 100%);
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 10rpx 20rpx rgba(66, 151, 243, 0.3);
text {
font-size: 32rpx;
font-weight: 600;
color: #FFFFFF;
}
}
}
.add-btn-wrap {
position: relative;
width: 100%;
height: 100rpx;
.btn-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #4297F3 0%, #2B76E5 100%);
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
color: #FFFFFF;
font-size: 32rpx;
font-weight: bold;
z-index: 2;
box-shadow: 0 10rpx 20rpx rgba(66, 151, 243, 0.3);
}
.btn-shadow {
position: absolute;
top: 20rpx;
left: 10%;
width: 80%;
height: 100%;
background: #4297F3;
filter: blur(20rpx);
opacity: 0.4;
border-radius: 100rpx;
z-index: 1;
}
}
}
// 卡券选择弹窗
.coupon-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
.popup-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.popup-content {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: #FFFFFF;
border-radius: 40rpx 40rpx 0 0;
max-height: 70vh;
display: flex;
flex-direction: column;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1rpx solid #F0F0F0;
.popup-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.popup-close {
font-size: 48rpx;
color: #999;
line-height: 1;
}
}
.search-box {
display: flex;
align-items: center;
margin: 24rpx 32rpx;
padding: 20rpx 24rpx;
background: #F7F8FA;
border-radius: 40rpx;
input {
flex: 1;
margin-left: 16rpx;
font-size: 28rpx;
color: #333;
}
}
.coupon-list {
flex: 1;
width: 720rpx;
margin: auto;
max-height: 44vh;
.coupon-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 24rpx;
background: #F9FAFB;
border-radius: 16rpx;
margin-bottom: 16rpx;
.coupon-info {
flex: 1;
.coupon-name {
font-size: 30rpx;
font-weight: 500;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.coupon-desc {
font-size: 24rpx;
color: #999;
}
}
.check-box {
width: 44rpx;
height: 44rpx;
border: 2rpx solid #D9D9D9;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.checked {
background: #4297F3;
border-color: #4297F3;
}
}
.selected {
background: linear-gradient(135deg, #E8F4FD 0%, #D4ECFD 100%);
border: 2rpx solid #4297F3;
}
}
.popup-footer {
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #F0F0F0;
.confirm-btn {
height: 96rpx;
background: linear-gradient(135deg, #4297F3 0%, #2B76E5 100%);
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 32rpx;
font-weight: 600;
color: #FFFFFF;
}
}
}
}
}
</style>