Files
product-uniapp/pages/publish/edit.vue
T
2026-02-09 17:51:32 +08:00

753 lines
18 KiB
Vue
Raw 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="publish-page">
<uni-nav-bar leftText=" " leftIcon="back" :fixed="true" :border="false" @clickLeft="leftClick" color="#000000" backgroundColor="#fff" :statusBar="true" title="编辑发布"></uni-nav-bar>
<view class="form-card">
<view class="select-input gradient-bg position" @click="onSelectCategory()">
<view class="category-content">
<text class="category-text">{{form.category || '请输入/选择型号'}}</text>
</view>
<u-icon name="arrow-down" color="#3BC8BE" size="20"></u-icon>
<!-- 型号选择弹层 -->
<view v-if="showSelect1" class="select-popup">
<!-- 点击空白处关闭下拉 -->
<view class="popup-mask" @click="showSelect1=false"></view>
<view class="popup-panel" @click.stop>
<view class="popup-header">
<input class="popup-search" v-model="searchKey1" placeholder="搜索型号" />
<text class="popup-close" @click="showSelect1=false">取消</text>
</view>
<scroll-view scroll-y class="popup-list">
<view
v-for="item in filteredCategories"
:key="item.id"
class="popup-item"
@click.stop="selectCategory(item)"
>
{{ item.model }} ({{item.product_name}})
</view>
<view v-if="!filteredCategories.length" class="popup-empty">暂无数据</view>
</scroll-view>
</view>
</view>
</view>
<view class="conts mag-top">
<view class="">
<textarea v-model="form.content" class="content-textarea" placeholder="请输入发布内容"
:maxlength="maxWords" @input="handleContentInput" @focus="checkLogin()" />
</view>
<view class="media-upload-wrapper">
<uni-file-picker
ref="mediaPicker"
v-model="fileList"
fileMediatype="image"
limit="9"
:disabled="!isLogin"
@select="selectMedia"
@delete="delfn"
:imageStyles="imageStyles"
>
<view class="upload-icon-wrapper">
<image src="/static/image/photo.png" mode="" class="addimg"></image>
</view>
</uni-file-picker>
</view>
<view class="help-text">
<u-icon name="info-circle" size="18" color="#A6ABB7"></u-icon>
<text>请上传产品实物的正、侧、背多角度照片(禁止效果图),图片不多于9张</text>
</view>
</view>
<view class="mag-top">
<picker mode="selector" :range="conditionOptions" range-key="name" :disabled="!isLogin" @tap="checkLogin()" @change="onCategoryChange">
<view class="select-input gradient-bg">
<view class="category-content">
<text class="category-text">{{form.age || '请选择样机新旧程度'}}</text>
</view>
<u-icon name="arrow-down" color="#3BC8BE" size="20"></u-icon>
</view>
</picker>
</view>
<view class="mag-top prices">
<input
class="uni-input"
type="digit"
:value="form.price"
placeholder="请输入折扣价格"
@input="onPriceInput"
@blur="onPriceBlur"
/>
<text>元</text>
</view>
<view class="mag-top">
<view style="margin-bottom: 10rpx;"><span style="color: red;">*</span> 是否包邮:</view>
<uni-data-checkbox v-model="form.is_free" :localdata="freeshipping" />
</view>
<view class="detils" v-if="skudetils!=''">
<view class="product-card">
<view class="title">产品名称:{{skudetils.product_name}}</view>
<view class="price-row">
<text class="spec-label">零售价:</text>
<text class="spec-value">{{ skudetils.price }}</text>
</view>
<view class="spec-row">
<text class="spec-label">品牌:</text>
<text class="spec-value">{{ skudetils.brand }}</text>
</view>
<view class="spec-row" v-for="(item, idx) in skudetils.spec_json" :key="idx">
<text class="spec-label">{{item.attr_name}}:</text>
<text class="spec-value">{{ item.attr_value }}</text>
</view>
</view>
<rich-text :nodes="productcontents"></rich-text>
</view>
</view>
<view class="foot">
<view class="agreement">
<button class="primary-btn gradient-btn" :disabled="userinfo.is_release!=1" @click="submitForm">保存修改</button>
</view>
</view>
<u-popup :show="showlogin" :closeOnClickOverlay="false" :round="20" mode="center">
<view class="popupcontent">
<image class="log1" src="/static/image/log1.png"></image>
<view class="title">您还未登录!登录后方可编辑内容!</view>
<view class="yhxy">
<checkbox-group name="" style="width: 28px;display: inline-block;">
<label @tap="selfChangde()">
<checkbox :checked="selfChecked" activeBackgroundColor="#3F435B"
style="transform:scale(0.7);border-radius: 50%" />
</label>
</checkbox-group>
{{$t('home.argin')}}<text class="xy" @click="gouser">{{$t('home.UserAgreement')}}</text>{{$t('home.and')}}<text
class="xy" @click="goprivacy">{{$t('home.Privacy')}}</text>
</view>
</view>
<view class="cent">
<button class="btn1" @click="showlogin = !showlogin">{{$t('cancel')}}</button>
<button v-if="selfChecked" class="btn" open-type="getPhoneNumber" @click="showlogin = !showlogin"
@getphonenumber="getPhoneNumber">{{$t('login')}}</button>
<button v-else class="btn" @click="loginbtn">{{$t('login')}}</button>
</view>
</u-popup>
</view>
</template>
<script>
import { request } from '@/api/apiT.js'
export default {
name: 'EditPublishPage',
data(){
return {
postId:'',
showSelect1:false,
searchKey1:'',
productcontents:'',
userinfo:'',
skudetils:'',
// 单选数据源
freeshipping: [ {
text: '全国包邮',
value: 1
}, {
text: '省内包邮',
value: 2
}, {
text: '邮费电联',
value: 3
}],
categoryOptions:[],
conditionOptions:[
{name:'全新(未开箱)',value:'10'},
{name:'95%新(开箱无瑕)',value:'9'},
{name:'90%新(外观轻损<2处)',value:'8'},
{name:'85%新(外观轻损<3处)',value:'7'},
{name:'75%新(外观轻损<5处)',value:'6'},
{name:'70%新(外观轻损<6处)',value:'5'},
{name:'60%新(外观轻损<7处)',value:'4'},
{name:'不足60%新(外观损伤>7处)',value:'3'}
],
form:{
age:'',
category:'',
sku_id:'',
content:'',
media:[],
price:'',
is_free:''
},
selfChecked: false,
showlogin:false,
maxWords:2000,
maxMedia:9,
imageStyles:{},
fileList:[]
}
},
computed:{
isLogin() {
return !!uni.getStorageSync('is_login')
},
filteredCategories(){
if(!this.searchKey1) return this.categoryOptions
return this.categoryOptions.filter(i => (i.model || '').toLowerCase().includes(this.searchKey1.toLowerCase()))
}
},
onLoad(options) {
this.postId = options.id || ''
this.getinfo()
if(this.postId){
this.loadPostDetail()
}
},
methods:{
leftClick(){
uni.navigateBack()
},
getinfo(){
request({
url:'common/user/info',
method:'POST',
}).then(res => {
this.userinfo=res.data.data.user || {}
uni.setStorageSync('is_release', this.userinfo.is_release);
})
},
loadPostDetail(){
request({
url:'common/post/show',
method:'GET',
data:{
id:this.postId
}
}).then(res => {
if(res.data.code === 200){
const data = res.data.data || {}
this.form.content = data.content || ''
this.form.price = data.price != null ? String(data.price) : ''
this.form.is_free = data.is_free != null ? data.is_free : ''
this.form.age = data.age || ''
if(data.sku){
this.skudetils = data.sku
this.skudetils.spec_json = JSON.parse(data.sku.spec_json || '[]')
this.form.category = data.sku.model || ''
this.form.sku_id = data.sku.id || ''
if(data.sku.desc){
this.productcontents = this.processRichText(data.sku.desc || '')
}
}
if(Array.isArray(data.media)){
this.form.media = data.media.map(m => m.url || '').filter(Boolean)
this.fileList = data.media.map(m => ({ url: m.url_pull || m.img_src || m.url || '' })).filter(item => item.url)
}
}
})
},
onSelectCategory() {
if (!uni.getStorageSync('is_login')) {
this.showlogin = true
return
}
this.getcategory()
this.showSelect1 = true
},
checkLogin(cb) {
if (!this.isLogin) {
this.showlogin = true
return
}
if (typeof cb === 'function') {
cb()
}
},
// 处理富文本HTML,让图片自适应
processRichText(html) {
if (!html) return ''
// 使用正则表达式给所有 img 标签添加样式
return html.replace(/<img([^>]*)>/gi, (match, attrs) => {
// 检查是否已有 style 属性
if (attrs.includes('style=')) {
// 如果已有 style,添加 max-width
return match.replace(/style=["']([^"']*)["']/i, (styleMatch, styleContent) => {
// 移除已有的 max-width 和 width
let newStyle = styleContent.replace(/max-width\s*:\s*[^;]+;?/gi, '').replace(/width\s*:\s*[^;]+;?/gi, '')
newStyle = newStyle.trim()
if (newStyle && !newStyle.endsWith(';')) {
newStyle += ';'
}
newStyle += 'max-width: 100%; height: auto;'
return `style="${newStyle}"`
})
} else {
// 如果没有 style,添加 style 属性
return `<img${attrs} style="max-width: 100%; height: auto;">`
}
})
},
selectCategory(item){
console.log(item)
this.skudetils = item
this.form.category = item.model || ''
this.form.sku_id = item.id || ''
// 处理富文本,让图片自适应
if(item.desc !=''&& item.desc !=null){
this.productcontents = this.processRichText(item.desc || '')
}
this.showSelect1 = false
},
goprivacy(){
uni.navigateTo({
url: '/pages/login/agreement',// 替换为你要跳转的页面路径
});
},
gouser(){
uni.navigateTo({
url: '/pages/login/agreementuser',// 替换为你要跳转的页面路径
});
},
selfChangde(e) {
this.selfChecked = !this.selfChecked;
},
loginbtn(){
if(!this.selfChecked){
uni.showToast({
title:this.$t('home.arginimg'),
icon: 'none'
});
return false
}
},
getPhoneNumber(e) {
let that = this;
request({
url:'miniapp/phone',
method:'POST',
data:{
phone_code: e.detail.code,
}
}).then(res => {
if (res.data.code == -1) {
uni.showToast({
title: res.data.msg,
icon: 'none'
});
} else if (res.data.code == 200) {
uni.showToast({
title: '登录成功',
icon: 'none'
});
//返回数据接收成功
uni.setStorageSync('is_login', true);
}
})
},
//商品下拉
getcategory(){
request({
url:'common/sku/preset',
method:'GET'
}).then(res => {
if (res.data.code == 200) {
this.categoryOptions = res.data.data|| [];
}
})
},
onCategoryChange(e){
const index = e.detail.value; // picker 返回的 index
const selectCity = this.conditionOptions[index]; // 直接取整条数据
this.form.age = selectCity.name
},
handleContentInput(e){
if(e.detail.value.length>this.maxWords){
this.form.content = e.detail.value.slice(0,this.maxWords)
}
},
sanitize(v) {
v = String(v || '');
v = v.replace(/[^\d.]/g, ''); // 只留数字和点
const i = v.indexOf('.');
if (i !== -1) {
v = v.slice(0, i + 1) + v.slice(i + 1).replace(/\./g, '');
}
return v;
},
onPriceInput(e) {
const v = this.sanitize(e.detail.value);
// 关键:先更新数据,再 nextTick 强制触发视图回写
if (v !== this.form.price) {
this.form.price = v;
this.$nextTick(() => {
// 再写一次,逼迫输入框用受控值覆盖(对合成态更稳)
this.form.price = v;
});
}
},
onPriceBlur(e) {
// 兜底:失焦时彻底清洗,保证最终不会残留中文
const v = this.sanitize(e.detail.value);
if (v !== this.form.price) this.form.price = v;
},
uploadfn(imgUrl){
return new Promise((resolve, reject) => {
uni.uploadFile({
url: 'https://bsh.info.yw.hnzhwlkj.cn/upload/image?scene=postlist',
filePath: imgUrl,
name: 'image',
header:{'content-type': 'application/x-www-form-urlencoded','Fdesk-Auth-Token': 'Bearer ' + uni.getStorageSync('token'),'Fdesk-Language': uni.getStorageSync('i18locale')?uni.getStorageSync('i18locale'):'zh','Fdesk-Login-Type': 'wechat','Fdesk-System-Code':'wechat'},
success: (uploadFileRes) => {
const res = JSON.parse(uploadFileRes.data || '{}')
if(res && res.data && res.data.href){
resolve(res.data.href)
}else{
reject('上传失败')
}
},
fail: (err) => reject(err)
});
})
},
selectMedia(e){
const allowCount = this.maxMedia - this.form.media.length
if(allowCount <= 0){
uni.showToast({
title:`最多只能上传${this.maxMedia}张图片`,
icon:'none'
})
return
}
const tempFilePaths = e.tempFilePaths || (e.tempFiles || []).map(item => item.path || item.url)
const paths = tempFilePaths.slice(0, allowCount)
if(paths.length === 0){
return
}
Promise.all(paths.map(p => this.uploadfn(p))).then(urls => {
urls.forEach(url => {
this.form.media.push(url)
})
}).catch(() => {
uni.showToast({
title:'图片上传失败,请重试',
icon:'none'
})
})
},
delfn(e){
this.form.media.splice(e.index, 1)
},
submitForm(){
// 必填校验
if(!this.form.category || !this.form.sku_id){
uni.showToast({
title: '请选择型号',
icon: 'none'
});
return
}
if(!this.form.content || !this.form.content.trim()){
uni.showToast({
title: '请输入文字内容',
icon: 'none'
});
return
}
if(!this.form.age){
uni.showToast({
title: '请选择样机新旧程度',
icon: 'none'
});
return
}
if(!this.form.price || !this.form.price.trim()){
uni.showToast({
title: '请输入折扣价格',
icon: 'none'
});
return
}
if(this.form.is_free===''){
uni.showToast({
title: '请选择包邮方式',
icon: 'none'
});
return
}
if(!this.form.media || this.form.media.length === 0){
uni.showToast({
title: '请至少上传一张图片',
icon: 'none'
});
return
}
if(!uni.getStorageSync('is_login')){
this.showlogin = true
return
}
const payload = {
age:this.form.age,
sku_id:this.form.sku_id,
content:this.form.content,
is_free:this.form.is_free,
price:this.form.price,
media:this.form.media,
id:this.postId
}
request({
url:'common/post/update',
method:'POST',
data:payload
}).then(res => {
uni.showToast({
title: res.data.message || '保存成功',
icon: 'none'
});
if(res.data.code ==200){
setTimeout(() => {
uni.navigateBack()
}, 1000);
}
})
}
}
}
</script>
<style lang="scss">
.prices{
display: flex;
align-items: center;
gap: 10px;
}
.detils{
margin-top: 40rpx;
overflow: hidden;
word-wrap: break-word;
word-break: break-all;
}
// 确保富文本中的图片自适应
.rich-wrap{
width: 100%;
overflow: hidden;
}
.cent{
display: flex;
align-items: center;
justify-content: space-around;
padding: 0px 10px 20px;
.btn1{
background-color: #fff;
border: 1px solid #206ff4;
color: #206ff4;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 40px;
line-height: 40px;
}
.btn{
background-color: #206ff4;
color: white;
border-radius: 50px;
width: 46%;
margin: 10px 0px 20px;
height: 40px;
line-height: 40px;
}
}
.form-card{
background: #fff;
padding: 30rpx;
margin-bottom: 400rpx;
box-shadow: 0 12rpx 30rpx rgba(0,0,0,0.04);
}
.select-input{
height: 88rpx;
border-radius: 12rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.gradient-bg{
background: #F4F4F4;
color: #999999;
}
.conts{
background: #F4F4F4;
color: #999999;
padding: 20rpx;
border-radius: 10rpx;
}
.uni-input{
background: #F4F4F4;
padding: 20rpx 25rpx;
border-radius: 10rpx;
}
.category-content{
display: flex;
align-items: center;
flex: 1;
}
.category-text{
font-size: 28rpx;
color: #999999;
font-weight: 500;
}
.mag-top{
margin-top: 15px;
}
.content-textarea{
width: 100%;
border-radius: 16rpx;
max-height: 160rpx;
padding: 24rpx 0px;
font-size: 28rpx;
color: #2F323A;
box-sizing: border-box;
}
.media-upload-wrapper{
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.upload-icon-wrapper{
display: flex;
flex-direction: column;
align-items: center;
}
.addimg{
width: 215rpx;
height: 215rpx;
}
.help-text{
font-size: 24rpx;
color: #A6ABB7;
margin-top: 40rpx;
display: flex;
align-items: center;
gap: 8rpx;
}
.foot{
background: #fff;
position: fixed;
bottom: 0;
width: 100%;
z-index: 11;
}
.agreement{
margin: 20px 12px;
}
.primary-btn{
color: #fff;
border-radius: 20rpx;
height: 88rpx;
line-height: 88rpx;
font-size: 32rpx;
border: none;
}
.gradient-btn{
background: #3BC8BE;
margin-top: 20rpx;
}
.position{
position: relative;
}
.select-popup{
position: absolute;
left: 0;
right: 0;
top: 100%; /* 紧贴选择框下方 */
z-index: 1000;
}
.popup-panel{
position: absolute;
left: 0;
right: 0;
top: 8rpx; /* 与选择框留一点间距 */
background: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
border-bottom-left-radius: 20rpx;
border-bottom-right-radius: 20rpx;
max-height: 40vh; /* 控制整体可视高度,下拉列表形式 */
display: flex;
flex-direction: column;
padding: 20rpx;
box-sizing: border-box;
z-index: 1001;
}
.popup-header{
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.popup-search{
flex: 1;
height: 72rpx;
border-radius: 10rpx;
background: #f4f4f4;
padding: 0 20rpx;
font-size: 28rpx;
color: #333;
margin-right: 12rpx;
box-sizing: border-box;
}
.popup-mask{
position: fixed;
inset: 0;
background: transparent;
}
.popup-close{
font-size: 28rpx;
color: #3BC8BE;
}
.popup-list{
flex: 1;
max-height: calc(40vh - 140rpx); /* 预留头部高度,保证可滚动 */
}
.popup-item{
padding: 20rpx 12rpx;
border-bottom: 1px solid #f0f0f0;
font-size: 28rpx;
color: #333;
}
.popup-item:last-child{
border-bottom: none;
}
.popup-empty{
text-align: center;
color: #999;
font-size: 26rpx;
padding: 30rpx 0;
}
.popupcontent{
background-size: 100% 100%;
padding: 26px 15px;
text-align: center;
background-repeat: no-repeat;
.title{
color: #333333;
font-size: 15px;
margin-bottom: 15px;
}
}
.log1{
width: 180px;
height: 180px;
}
.yhxy{
color: #999999;
font-size: 12px;
margin-bottom: 5px;
.xy{
color: #297EFF;
}
}
</style>