This commit is contained in:
2026-03-06 13:41:22 +08:00
commit f39c6a705f
394 changed files with 159599 additions and 0 deletions

View File

@ -0,0 +1,417 @@
<template>
<view class="remark-page">
<scroll-view class="remark-scroll" scroll-y>
<!-- 文字评价 -->
<view class="section text-review-section">
<view class="section-title">文字评价</view>
<view class="textarea-wrap">
<textarea class="remark-textarea" :value="remark" :maxlength="remarkMaxLen" placeholder="请输入评价"
placeholder-class="textarea-placeholder" :auto-height="true" @input="onRemarkChange" />
<view class="char-count">{{ remark.length }}/{{ remarkMaxLen }}</view>
</view>
</view>
<!-- 上传图片 -->
<view class="section upload-section">
<view class="section-title">上传图片 <text class="tips">(最多可上传3张)</text></view>
<view class="image-upload-grid">
<view v-for="(item, index) in imgList" :key="index" class="image-item">
<image class="uploaded-image" :src="item.fullUrl || item.url" mode="aspectFill" @click="previewImage(item.fullUrl || item.url, imgList.map(i => i.fullUrl || i.url))" />
<view class="delete-btn" @click="deleteImage(index)">
<text class="delete-icon">×</text>
</view>
</view>
<view v-if="imgList.length < 3" class="image-item upload-placeholder" @click="chooseImage">
<text class="add-icon">+</text>
</view>
</view>
</view>
<!-- 星级评分 -->
<view class="section rating-section">
<view class="rating-row">
<text class="rating-label">描述相符</text>
<view class="star-row">
<image v-for="v in 5" :key="v" class="star-icon"
:src="starDesc >= v ? require('@/static/images/star.png') : require('@/static/images/star_dark.png')"
@click="starDesc = v" />
</view>
</view>
<view class="rating-row" style="margin-top: 40rpx;">
<text class="rating-label">服务态度</text>
<view class="star-row">
<image v-for="v in 5" :key="v" class="star-icon"
:src="starService >= v ? require('@/static/images/star.png') : require('@/static/images/star_dark.png')"
@click="starService = v" />
</view>
</view>
</view>
<!-- 洗护对比图有数据时展示 -->
<!-- <view v-if="showWashImg" class="section wash-compare-section" @click="showModal = true">
<view class="wash-compare-row">
<text class="wash-compare-label">洗护对比图</text>
<image class="arrow-icon" src="@/static/images/arrow_right_black.png" mode="aspectFit" />
</view>
</view> -->
<view class="bottom-placeholder" />
</scroll-view>
<view class="submit-footer">
<view class="submit-btn" @click="submit">提交</view>
</view>
<compare-modal v-if="showModal" :before-imgs="washBeforeImgs" :after-imgs="washAfterImgs" :select-imgs="selectImgs"
:is-can-remark="true" @close="showModal = false" @add="selectImgsChange" />
</view>
</template>
<script>
import { createRemark } from "@/api/shop";
import CompareModal from "@/components/CompareModal.vue";
import { ORDER_TYPE_GOODS } from "@/constants/app.business";
import { uploadImageToOSS_PUT } from "@/utils/oss";
export default {
components: { CompareModal },
data() {
return {
orderId: "",
remark: "",
remarkMaxLen: 300,
starDesc: 5,
starService: 5,
imgList: [],
washBeforeImgs: [],
washAfterImgs: [],
selectImgs: [],
showModal: false,
orderType: ORDER_TYPE_GOODS,
};
},
computed: {
showWashImg() {
return [...this.washBeforeImgs, ...this.washAfterImgs].length > 0;
},
},
onLoad(options) {
console.log(options, '--')
this.orderId = options.orderId || "";
if (options.orderType) this.orderType = options.orderType;
const eventChannel = this.getOpenerEventChannel();
if (eventChannel && typeof eventChannel.on === "function") {
eventChannel.on("remarkInfo", (data) => {
this.washAfterImgs = data.afterImages || [];
this.washBeforeImgs = data.beforeImages || [];
this.selectImgs = data.remarkImages || [];
});
}
},
beforeDestroy() {
const ec = this.getOpenerEventChannel();
if (ec && typeof ec.emit === "function") ec.emit("refreshData");
},
methods: {
selectImgsChange(imgs) {
this.selectImgs = imgs;
this.showModal = false;
},
onRemarkChange(e) {
const val = (e && e.detail && e.detail.value) || (e && e.mp && e.mp.detail && e.mp.detail.value) || "";
this.remark = String(val).slice(0, this.remarkMaxLen);
},
chooseImage() {
const remaining = 3 - this.imgList.length;
if (remaining <= 0) {
uni.showToast({ title: "最多只能上传3张图片", icon: "none" });
return;
}
uni.chooseImage({
count: remaining,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePaths = res.tempFilePaths || [];
if (tempFilePaths.length === 0) return;
this.uploadImages(tempFilePaths);
},
fail: (err) => {
console.error('选择图片失败', err);
}
});
},
async uploadImages(filePaths) {
uni.showLoading({ title: "图片上传中...", icon: "none", mask: true });
try {
const uploadTasks = filePaths.map(filePath => this.uploadImageToOSS(filePath));
await Promise.all(uploadTasks);
uni.hideLoading();
uni.showToast({ title: "上传成功", icon: "success" });
} catch (error) {
uni.hideLoading();
uni.showToast({ title: error?.message || "上传失败", icon: "none" });
}
},
async uploadImageToOSS(filePath) {
try {
const { url, objectKey } = await uploadImageToOSS_PUT(filePath);
this.imgList.push({
url: objectKey,
fullUrl: url,
});
this.$forceUpdate();
} catch (error) {
console.error('图片上传失败:', error);
throw error;
}
},
deleteImage(index) {
this.imgList.splice(index, 1);
this.$forceUpdate();
},
previewImage(current, urls) {
uni.previewImage({
current: current,
urls: urls || [current]
});
},
submit() {
if (!this.remark || !this.remark.trim()) {
uni.showToast({ title: "请输入评价内容", icon: "none" });
return;
}
const pics = [...this.selectImgs, ...this.imgList].map((v) => (v.url || v.objectKey || v.filename || v.fullUrl)).filter(Boolean);
if (!pics.length) {
uni.showToast({ title: "请上传图片", icon: "none" });
return;
}
uni.showLoading({ title: "提交中...", icon: "none" });
createRemark({
order_id: +this.orderId,
type: this.orderType,
imgs: pics,
content: this.remark.trim(),
description: this.starDesc || 0,
attitude: this.starService || 0,
})
.then(() => {
uni.hideLoading();
uni.$emit("createRemarkSuccess");
uni.navigateBack();
})
.catch((err) => {
uni.hideLoading();
uni.showToast({ title: err?.message || err || "提交失败", icon: "none" });
});
},
},
};
</script>
<style lang="scss" scoped>
.remark-page {
min-height: 100vh;
background: #f5f5f5;
box-sizing: border-box;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.remark-scroll {
height: 100vh;
box-sizing: border-box;
}
.section {
margin: 20rpx;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
box-sizing: border-box;
padding: 20rpx;
}
.section-title {
font-size: 24rpx;
font-weight: 500;
color: #272427;
margin-bottom: 20rpx;
.tips {
font-size: 20rpx;
color: #ACACAC;
}
}
/* 文字评价 */
.text-review-section {
.textarea-wrap {
position: relative;
padding: 20rpx;
background: #f5f5f5;
border-radius: 16rpx;
min-height: 240rpx;
}
.remark-textarea {
width: 100%;
min-height: 200rpx;
font-size: 28rpx;
color: #272427;
line-height: 1.5;
box-sizing: border-box;
}
.textarea-placeholder {
color: #acacac;
font-size: 28rpx;
}
.char-count {
position: absolute;
right: 32rpx;
bottom: 24rpx;
font-size: 24rpx;
color: #9b939a;
}
}
/* 上传图片 */
.upload-section {
.image-upload-grid {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
}
.image-item {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 16rpx;
overflow: hidden;
flex-shrink: 0;
}
.uploaded-image {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
.delete-btn {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 40rpx;
height: 40rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.delete-icon {
color: #fff;
font-size: 32rpx;
font-weight: bold;
line-height: 1;
}
.upload-placeholder {
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
border: 1rpx dashed #d0d0d0;
}
.add-icon {
font-size: 64rpx;
color: #999;
font-weight: 300;
line-height: 1;
}
}
/* 星级 */
.rating-section {
margin-top: 24rpx;
padding: 20rpx;
.rating-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.rating-label {
font-size: 24rpx;
color: #3D3D3D;
}
.star-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.star-icon {
width: 40rpx;
height: 40rpx;
}
}
/* 洗护对比图 */
.wash-compare-section {
.wash-compare-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
background: #f5f5f5;
margin: 0 24rpx 24rpx;
border-radius: 12rpx;
}
.wash-compare-label {
font-size: 28rpx;
color: #272427;
}
.arrow-icon {
width: 24rpx;
height: 24rpx;
}
}
.bottom-placeholder {
height: 180rpx;
}
.submit-footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
background: #fff;
box-sizing: border-box;
}
.submit-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 32rpx;
font-weight: 500;
color: #fff;
background: #fe019b;
border-radius: 44rpx;
}
</style>