| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- <template>
- <div class="evaluation-page">
- <div class="evaluation-header">
- <div class="header-left">
- <span class="header-title">评价订单</span>
- <span class="header-info">订单号:{{ orderInfo.orderNo }}</span>
- <span class="header-info">{{ orderInfo.orderTime }}</span>
- </div>
- <div class="header-right">
- <el-button v-if="evaluateForm.evaluationType !== 3" type="primary" @click="handleSubmitEvaluate">提交评价</el-button>
- <el-button type="info" @click="handleBack">返回</el-button>
- </div>
- </div>
- <div class="evaluate-content">
- <!-- 物流服务评价 -->
- <div class="logistics-evaluation">
- <div class="section-title">物流服务评价</div>
- <div class="logistics-ratings">
- <div class="rating-item">
- <span class="rating-label">快递包装</span>
- <el-rate v-model="evaluateForm.expressPacking" :colors="rateColors" :disabled="evaluateForm.evaluationType === 3" />
- </div>
- <div class="rating-item">
- <span class="rating-label">送货速度</span>
- <el-rate v-model="evaluateForm.deliverySpeed" :colors="rateColors" :disabled="evaluateForm.evaluationType === 3" />
- </div>
- <div class="rating-item">
- <span class="rating-label">配送员评价</span>
- <el-rate v-model="evaluateForm.deliveryManRating" :colors="rateColors" :disabled="evaluateForm.evaluationType === 3" />
- </div>
- </div>
- </div>
- <!-- 商品评价列表 -->
- <div v-for="(item, index) in evaluateForm.productEvaluations" :key="index" class="product-evaluation-item">
- <div class="product-left">
- <div class="product-image">
- <el-image :src="item.image" fit="contain">
- <template #error>
- <div class="image-placeholder">
- <el-icon :size="30" color="#ccc"><Picture /></el-icon>
- </div>
- </template>
- </el-image>
- </div>
- <div class="product-name">{{ item.name }}</div>
- <div class="product-code">{{ item.productNo }}</div>
- </div>
- <div class="product-right">
- <div class="evaluation-hint" v-if="evaluateForm.evaluationType !== 3">
- <el-icon color="#e6a23c"><WarningFilled /></el-icon>
- <span>请至少填写一件商品的评价</span>
- </div>
- <div class="rating-row">
- <span class="field-label">商品评价</span>
- <el-rate v-model="item.rating" :colors="rateColors" :disabled="evaluateForm.evaluationType === 3" />
- </div>
- <div class="content-row">
- <span class="field-label">评价晒单</span>
- <el-input v-model="item.content" type="textarea" :rows="4" placeholder="请输入评价内容" maxlength="200" :disabled="evaluateForm.evaluationType === 3" />
- </div>
- <div class="upload-row" v-if="evaluateForm.evaluationType !== 3">
- <el-upload
- class="upload-box"
- :action="action"
- :show-file-list="false"
- :on-success="(res) => handleProductUploadSuccess(res, index)"
- :before-upload="beforeAvatarUpload"
- accept="image/*"
- multiple
- list-type="picture-card"
- >
- <div class="upload-placeholder">
- <el-icon :size="24"><Plus /></el-icon>
- </div>
- </el-upload>
- <div v-if="item.images && item.images.length > 0" class="image-list">
- <div v-for="(img, imgIndex) in item.images" :key="imgIndex" class="image-item">
- <el-image :src="img" fit="cover" style="width: 100px; height: 100px; border-radius: 6px;" />
- <el-icon class="delete-icon" @click="handleDeleteImage(index, imgIndex)"><Close /></el-icon>
- </div>
- </div>
- </div>
- <div class="upload-row" v-else>
- <div v-if="item.images && item.images.length > 0" class="image-list">
- <div v-for="(img, imgIndex) in item.images" :key="imgIndex" class="image-item">
- <el-image :src="img" fit="cover" style="width: 100px; height: 100px; border-radius: 6px;" />
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, reactive, onMounted } from 'vue';
- import { useRouter, useRoute } from 'vue-router';
- import { Picture, Plus, WarningFilled, Close } from '@element-plus/icons-vue';
- import { ElMessage } from 'element-plus';
- import type { UploadProps } from 'element-plus';
- import { getOrderProducts, addOrderEvaluation, getOrderEvaluation, getOrderEvaluationHeader } from '@/api/pc/enterprise/order';
- const action = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
- const router = useRouter();
- const route = useRoute();
- const rateColors = ['#f7ba2a', '#f7ba2a', '#f7ba2a'];
- const orderInfo = reactive({
- orderId: '',
- orderNo: '',
- orderTime: ''
- });
- const evaluateForm = reactive({
- expressPacking: 5,
- deliverySpeed: 5,
- deliveryManRating: 5,
- evaluationType: 1 as number,
- productEvaluations: [] as any[]
- });
- // 加载订单商品
- const loadOrderProducts = async (orderId: string) => {
- try {
- const res = await getOrderProducts([Number(orderId)]);
- if (res.code === 200 && res.rows) {
- return res.rows.map((product: any) => ({
- orderItemId: product.id,
- productId: product.productId,
- name: product.productName || '',
- productName: product.productName || '',
- productNo: product.productNo || '',
- image: product.productImage || '',
- productImg: product.productImage || '',
- rating: 5,
- content: '',
- images: []
- }));
- }
- return [];
- } catch (error) {
- console.error('加载订单商品失败:', error);
- return [];
- }
- };
- // 加载已有评价数据(追评/查看)
- const loadExistingEvaluation = async (orderId: string) => {
- try {
- const res = await getOrderEvaluationHeader(orderId);
- if (res.code === 200 && res.data) {
- // 回显物流评分
- evaluateForm.expressPacking = res.data.logisticsPackScore || 5;
- evaluateForm.deliverySpeed = res.data.deliverSpeedScore || 5;
- evaluateForm.deliveryManRating = res.data.courierServiceScore || 5;
- // 回显商品评价内容
- const itemList = res.data.orderEvaluationItemList || [];
- if (itemList.length > 0 && evaluateForm.productEvaluations.length > 0) {
- // 按 orderItemId 或 productId 匹配回显
- itemList.forEach((evalItem: any) => {
- const productIndex = evaluateForm.productEvaluations.findIndex(
- (p: any) => p.orderItemId === evalItem.orderItemId || p.productId === evalItem.productId
- );
- if (productIndex !== -1) {
- evaluateForm.productEvaluations[productIndex].rating = evalItem.productScore || 5;
- evaluateForm.productEvaluations[productIndex].content = evalItem.content || '';
- const imgStr = evalItem.images || '';
- evaluateForm.productEvaluations[productIndex].images = imgStr ? imgStr.split(',').filter((url: string) => url) : [];
- }
- });
- }
- }
- } catch (error) {
- console.error('加载评价数据失败:', error);
- }
- };
- //上传成功(按商品索引)
- const handleProductUploadSuccess = (res: any, index: number) => {
- if (res.code == 200) {
- if (!evaluateForm.productEvaluations[index].images) {
- evaluateForm.productEvaluations[index].images = [];
- }
- evaluateForm.productEvaluations[index].images.push(res.data.url);
- } else {
- ElMessage({ message: res.msg, type: 'warning' });
- }
- };
- // 删除图片
- const handleDeleteImage = (productIndex: number, imageIndex: number) => {
- evaluateForm.productEvaluations[productIndex].images.splice(imageIndex, 1);
- };
- const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
- const isImage = rawFile.type.startsWith('image/');
- if (!isImage) {
- ElMessage.error('只能上传图片文件!');
- return false;
- }
- if (rawFile.size / 1024 / 1024 > 2) {
- ElMessage.error('图片大小不能超过2MB!');
- return false;
- }
- return true;
- };
- const handleSubmitEvaluate = async () => {
- const hasEvaluation = evaluateForm.productEvaluations.some((item: any) => item.content);
- if (!hasEvaluation) {
- ElMessage.warning('请至少填写一件商品的评价');
- return;
- }
- try {
- const submitData = {
- orderId: orderInfo.orderId,
- orderNo: orderInfo.orderNo,
- logisticsPackScore: evaluateForm.expressPacking,
- deliverSpeedScore: evaluateForm.deliverySpeed,
- courierServiceScore: evaluateForm.deliveryManRating,
- evaluationType: evaluateForm.evaluationType,
- orderEvaluationItemList: evaluateForm.productEvaluations
- .filter((item: any) => item.content)
- .map((item: any) => ({
- orderItemId: item.orderItemId,
- productId: item.productId,
- productName: item.productName || item.name,
- productImg: item.productImg || item.image,
- productScore: item.rating,
- content: item.content,
- images: item.images && item.images.length > 0 ? item.images.join(',') : ''
- }))
- };
- const res = await addOrderEvaluation(submitData);
- if (res.code === 200) {
- ElMessage.success('评价提交成功');
- router.back();
- } else {
- ElMessage.error(res.msg || '评价提交失败');
- }
- } catch (error) {
- console.error('评价提交失败:', error);
- ElMessage.error('评价提交失败');
- }
- };
- const handleBack = () => {
- router.back();
- };
- onMounted(async () => {
- const orderId = String(route.query.orderId || '');
- const type = Number(route.query.type) || 1;
- const orderNo = (route.query.orderNo as string) || '';
- const orderTime = (route.query.orderTime as string) || '';
- orderInfo.orderId = orderId;
- orderInfo.orderNo = orderNo;
- orderInfo.orderTime = orderTime;
- evaluateForm.evaluationType = type;
- if (orderId) {
- // 优先从 history state 获取已加载的商品数据
- const stateProducts = history.state?.products;
- let products: any[] = [];
- if (stateProducts) {
- try {
- const parsed = JSON.parse(stateProducts);
- products = parsed.map((p: any) => ({
- orderItemId: p.id,
- productId: p.productId,
- name: p.productName || p.name,
- productName: p.productName || p.name,
- productNo: p.spec2,
- image: p.productImg || p.image,
- productImg: p.productImg || p.image,
- rating: 5,
- content: '',
- images: []
- }));
- } catch (e) {
- products = await loadOrderProducts(orderId);
- }
- } else {
- products = await loadOrderProducts(orderId);
- }
- evaluateForm.productEvaluations = products;
- // 追评或查看评价时,加载已有评价
- if (type === 2 || type === 3) {
- await loadExistingEvaluation(orderId);
- }
- }
- });
- </script>
- <style scoped lang="scss">
- .evaluation-page {
- padding: 20px;
- background: #fff;
- min-height: 100%;
- width: 100%;
- }
- .evaluation-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- .header-left {
- display: flex;
- align-items: center;
- gap: 12px;
- .header-title {
- font-size: 16px;
- font-weight: bold;
- color: #333;
- }
- .header-info {
- font-size: 14px;
- color: #666;
- }
- }
- .header-right {
- display: flex;
- gap: 8px;
- }
- }
- .evaluate-content {
- .logistics-evaluation {
- border: 1px solid #eee;
- border-radius: 4px;
- padding: 20px;
- margin-bottom: 20px;
- .section-title {
- font-size: 14px;
- font-weight: bold;
- color: #e6a23c;
- margin-bottom: 15px;
- }
- .logistics-ratings {
- display: flex;
- gap: 40px;
- .rating-item {
- display: flex;
- align-items: center;
- gap: 10px;
- .rating-label {
- font-size: 13px;
- color: #666;
- white-space: nowrap;
- }
- }
- }
- }
- .product-evaluation-item {
- display: flex;
- border: 1px solid #eee;
- border-radius: 4px;
- padding: 20px;
- margin-bottom: 15px;
- gap: 30px;
- .product-left {
- width: 180px;
- flex-shrink: 0;
- .product-image {
- width: 100px;
- height: 100px;
- background: #f5f5f5;
- border-radius: 4px;
- overflow: hidden;
- margin-bottom: 10px;
- .el-image {
- width: 100%;
- height: 100%;
- }
- .image-placeholder {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- }
- .product-name {
- font-size: 13px;
- color: #333;
- margin-bottom: 6px;
- line-height: 1.4;
- }
- .product-code {
- font-size: 12px;
- color: #e6a23c;
- }
- }
- .product-right {
- flex: 1;
- .evaluation-hint {
- display: flex;
- align-items: center;
- gap: 5px;
- font-size: 13px;
- color: #e6a23c;
- margin-bottom: 15px;
- }
- .rating-row {
- display: flex;
- align-items: center;
- gap: 10px;
- margin-bottom: 15px;
- }
- .content-row {
- display: flex;
- gap: 10px;
- margin-bottom: 15px;
- }
- .field-label {
- font-size: 13px;
- color: #666;
- white-space: nowrap;
- line-height: 32px;
- }
- .upload-row {
- padding-left: 65px;
- }
- }
- }
- }
- .upload-box {
- :deep(.el-upload) {
- width: 100px;
- height: 100px;
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- cursor: pointer;
- overflow: hidden;
- &:hover {
- border-color: #e60012;
- }
- }
- .upload-placeholder {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- color: #999;
- font-size: 12px;
- gap: 5px;
- }
- .upload-preview {
- width: 100%;
- height: 100%;
- .el-image {
- width: 100%;
- height: 100%;
- }
- }
- }
- .image-list {
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- margin-top: 10px;
- .image-item {
- position: relative;
- width: 100px;
- height: 100px;
- .delete-icon {
- position: absolute;
- top: 2px;
- right: 2px;
- width: 20px;
- height: 20px;
- background: rgba(0, 0, 0, 0.5);
- color: #fff;
- border-radius: 50%;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 14px;
- &:hover {
- background: rgba(0, 0, 0, 0.7);
- }
- }
- }
- }
- </style>
|