feat:新增反馈按钮和举报按钮的弹窗组件
This commit is contained in:
parent
a2d36cb7a0
commit
32e329b1dc
@ -210,6 +210,14 @@
|
|||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</LikeUsersModal>
|
</LikeUsersModal>
|
||||||
|
|
||||||
|
<!-- 举报弹窗(也由 ShareReportButtons 触发,asset-detail 内显式声明以便直接控制) -->
|
||||||
|
<ReportModal
|
||||||
|
:visible="showReportModal"
|
||||||
|
:assetId="assetIdParam"
|
||||||
|
@close="showReportModal = false"
|
||||||
|
@submit="handleReportSubmit"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -221,6 +229,7 @@ import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
|
|||||||
import LikeUsersModal from '@/pages/components/LikeUsersModal.vue';
|
import LikeUsersModal from '@/pages/components/LikeUsersModal.vue';
|
||||||
import ShareReportButtons from '@/pages/components/ShareReportButtons.vue';
|
import ShareReportButtons from '@/pages/components/ShareReportButtons.vue';
|
||||||
import ShareModal from '@/pages/components/ShareModal.vue';
|
import ShareModal from '@/pages/components/ShareModal.vue';
|
||||||
|
import ReportModal from '@/pages/components/ReportModal.vue';
|
||||||
import LenticularCard from '@/components/lenticular/LenticularCard.vue';
|
import LenticularCard from '@/components/lenticular/LenticularCard.vue';
|
||||||
import { useLenticularCraftTiltPreview } from '@/composables/useLenticularCraftTiltPreview.js';
|
import { useLenticularCraftTiltPreview } from '@/composables/useLenticularCraftTiltPreview.js';
|
||||||
import {
|
import {
|
||||||
@ -429,6 +438,13 @@ const likeUsersActiveTab = ref(0);
|
|||||||
const showShareModal = ref(false);
|
const showShareModal = ref(false);
|
||||||
const shareCoverUrl = ref('');
|
const shareCoverUrl = ref('');
|
||||||
const shareQrcodeUrl = ref('');
|
const shareQrcodeUrl = ref('');
|
||||||
|
// 举报弹窗(asset-detail 内显式持有,与 ShareReportButtons 内的 modal 互不影响,可由本页面其他位置触发)
|
||||||
|
const showReportModal = ref(false);
|
||||||
|
|
||||||
|
// 举报提交回调(埋点/统计/事件总线用)
|
||||||
|
const handleReportSubmit = (payload) => {
|
||||||
|
console.log('[asset-detail] report submitted', payload);
|
||||||
|
};
|
||||||
|
|
||||||
// 全部点赞用户数据(从API加载)
|
// 全部点赞用户数据(从API加载)
|
||||||
const allLikedUsers = ref([]);
|
const allLikedUsers = ref([]);
|
||||||
|
|||||||
615
frontend/pages/components/FeedbackModal.vue
Normal file
615
frontend/pages/components/FeedbackModal.vue
Normal file
@ -0,0 +1,615 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
v-if="visible"
|
||||||
|
class="modal-wrapper"
|
||||||
|
@touchmove.stop.prevent
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<transition name="fade">
|
||||||
|
<view
|
||||||
|
v-if="visible"
|
||||||
|
class="modal-mask"
|
||||||
|
@touchstart.stop="handleMaskTouchStart"
|
||||||
|
@click.stop="handleMaskClick"
|
||||||
|
></view>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<transition name="slide-up">
|
||||||
|
<view v-if="visible" class="modal-container" @click.stop>
|
||||||
|
<!-- 背景图片(紫色渐变 + 装饰) -->
|
||||||
|
<image
|
||||||
|
class="modal-background"
|
||||||
|
src="/static/assetDetail/Vector.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<image
|
||||||
|
class="title-background"
|
||||||
|
src="/static/assetDetail/topfans-fk.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
|
||||||
|
<!-- 蒙层(增强对比) -->
|
||||||
|
<view class="modal-overlay"></view>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<view class="modal-content" @touchstart.stop @touchend.stop @click.stop>
|
||||||
|
<!-- 标题:意见反馈 -->
|
||||||
|
<view class="title-row">
|
||||||
|
<!-- <text class="modal-title">意见反馈</text> -->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 4 个反馈类型 - 单行 -->
|
||||||
|
<view class="options-grid">
|
||||||
|
<view
|
||||||
|
v-for="opt in feedbackOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
class="option-item"
|
||||||
|
:class="{ 'option-item--active': selectedType === opt.value }"
|
||||||
|
@tap="handleSelectOption(opt)"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
class="option-text"
|
||||||
|
:class="{ 'option-text--active': selectedType === opt.value }"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 上传图片 -->
|
||||||
|
<view class="upload-area" @tap="handleChooseImage">
|
||||||
|
<view v-if="!imagePath" class="upload-placeholder">
|
||||||
|
<view class="upload-icon-circle">
|
||||||
|
<text class="upload-plus">+</text>
|
||||||
|
</view>
|
||||||
|
<text class="upload-label">上传图片</text>
|
||||||
|
</view>
|
||||||
|
<view v-else class="upload-preview">
|
||||||
|
<image
|
||||||
|
class="upload-image"
|
||||||
|
:src="imagePath"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<view class="upload-remove" @tap.stop="handleRemoveImage">
|
||||||
|
<text class="upload-remove-text">×</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 文字输入 -->
|
||||||
|
<view class="input-area">
|
||||||
|
<textarea
|
||||||
|
class="input-field"
|
||||||
|
v-model="content"
|
||||||
|
placeholder="请输入具体内容"
|
||||||
|
placeholder-class="input-placeholder"
|
||||||
|
:maxlength="200"
|
||||||
|
auto-height
|
||||||
|
></textarea>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 按钮 -->
|
||||||
|
<view class="button-row">
|
||||||
|
<view class="action-button back-button" @tap="handleCloseClick">
|
||||||
|
<text class="action-text">返回</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="action-button confirm-button"
|
||||||
|
:class="{ 'action-button--disabled': !canSubmit }"
|
||||||
|
@tap="handleConfirm"
|
||||||
|
>
|
||||||
|
<text class="action-text">{{
|
||||||
|
submitting ? "提交中…" : "确定"
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</transition>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { submitFeedbackApi, uploadLocalFileToOss } from "@/utils/api.js";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["close", "submit"]);
|
||||||
|
|
||||||
|
// 反馈类型选项(与 Figma 设计一致)
|
||||||
|
const feedbackOptions = [
|
||||||
|
{ value: "functionality", label: "功能异常" },
|
||||||
|
{ value: "ui", label: "界面问题" },
|
||||||
|
{ value: "suggestion", label: "产品建议" },
|
||||||
|
{ value: "other", label: "其他" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
const selectedType = ref("");
|
||||||
|
const content = ref("");
|
||||||
|
const imagePath = ref("");
|
||||||
|
const imageUrl = ref("");
|
||||||
|
const submitting = ref(false);
|
||||||
|
|
||||||
|
// 是否可提交:必须选择反馈类型
|
||||||
|
const canSubmit = computed(() => {
|
||||||
|
return !!selectedType.value && !submitting.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 关闭相关:与 ReportModal 保持一致的触摸处理,防止误触穿透
|
||||||
|
let maskTouchStartTime = 0;
|
||||||
|
const handleMaskTouchStart = (e) => {
|
||||||
|
maskTouchStartTime = Date.now();
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
const handleMaskClick = () => {
|
||||||
|
emit("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseClick = (e) => {
|
||||||
|
if (e && e.preventDefault) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
handleResetAndClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetAndClose = () => {
|
||||||
|
// 关闭前重置表单
|
||||||
|
selectedType.value = "";
|
||||||
|
content.value = "";
|
||||||
|
imagePath.value = "";
|
||||||
|
imageUrl.value = "";
|
||||||
|
submitting.value = false;
|
||||||
|
emit("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择反馈类型
|
||||||
|
const handleSelectOption = (opt) => {
|
||||||
|
selectedType.value = opt.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择图片
|
||||||
|
const handleChooseImage = () => {
|
||||||
|
uni.chooseImage({
|
||||||
|
count: 1,
|
||||||
|
sizeType: ["compressed"],
|
||||||
|
sourceType: ["album", "camera"],
|
||||||
|
success: (res) => {
|
||||||
|
const tempFile = res.tempFilePaths?.[0];
|
||||||
|
if (tempFile) {
|
||||||
|
imagePath.value = tempFile;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.warn("[FeedbackModal] chooseImage fail", err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除图片
|
||||||
|
const handleRemoveImage = () => {
|
||||||
|
imagePath.value = "";
|
||||||
|
imageUrl.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认提交
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
if (!canSubmit.value) {
|
||||||
|
if (!selectedType.value) {
|
||||||
|
uni.showToast({ title: "请选择反馈类型", icon: "none" });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
// 如果选择了本地图片,先上传到 OSS
|
||||||
|
let uploadedUrl = "";
|
||||||
|
if (imagePath.value && !imageUrl.value) {
|
||||||
|
try {
|
||||||
|
const upRes = await uploadLocalFileToOss(imagePath.value, {
|
||||||
|
type: "feedback",
|
||||||
|
});
|
||||||
|
uploadedUrl = upRes?.imageUrl || "";
|
||||||
|
imageUrl.value = uploadedUrl;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[FeedbackModal] upload image fail", e);
|
||||||
|
uni.showToast({ title: "图片上传失败", icon: "none" });
|
||||||
|
submitting.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用反馈 API
|
||||||
|
const res = await submitFeedbackApi({
|
||||||
|
type: selectedType.value,
|
||||||
|
content: content.value || "",
|
||||||
|
image_url: uploadedUrl || imageUrl.value || "",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res && res.code === 0) {
|
||||||
|
uni.showToast({ title: "反馈已提交", icon: "success" });
|
||||||
|
emit("submit", { type: selectedType.value });
|
||||||
|
handleResetAndClose();
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: (res && res.message) || "提交失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[FeedbackModal] submit fail", e);
|
||||||
|
uni.showToast({ title: e?.message || "提交失败", icon: "none" });
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
position: relative;
|
||||||
|
width: 680rpx;
|
||||||
|
max-height: 85vh;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.title-background {
|
||||||
|
position: absolute;
|
||||||
|
top: -224rpx;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 110%;
|
||||||
|
z-index: 2;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(75, 30, 110, 0.35) 0%,
|
||||||
|
rgba(131, 75, 158, 0.55) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容区域 */
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 248rpx 36rpx 36rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题 */
|
||||||
|
.title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4 个反馈类型 - 单行 */
|
||||||
|
.options-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16rpx 14rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
/* 单行 4 个:扣除 3 个 gap 后均分 */
|
||||||
|
flex: 0 0 calc((100% - 42rpx) / 4);
|
||||||
|
height: 64rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: url("/static/assetDetail/text-bj.png") center no-repeat;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 4px 4px 0 rgba(182, 48, 48, 0.25), 0 2px 0px 0 rgba(212, 39, 39, 0.31);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item--active {
|
||||||
|
background: linear-gradient(
|
||||||
|
165deg,
|
||||||
|
#f0e4b1 0%,
|
||||||
|
#f08399 50%,
|
||||||
|
#b94e73 90%,
|
||||||
|
#834b9e 100%
|
||||||
|
);
|
||||||
|
box-shadow:
|
||||||
|
0 4rpx 12rpx rgba(255, 143, 158, 0.4),
|
||||||
|
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.45);
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text--active {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上传图片区域 */
|
||||||
|
.upload-area {
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
background: linear-gradient(
|
||||||
|
168.9deg,
|
||||||
|
rgba(154, 146, 255, 0.48) 4.58%,
|
||||||
|
rgba(255, 202, 229, 0.48) 43.91%,
|
||||||
|
rgba(255, 250, 253, 0.475) 76.13%,
|
||||||
|
rgba(211, 209, 255, 0.48) 91.61%
|
||||||
|
);
|
||||||
|
box-shadow: 2rpx 4rpx 1.8rpx rgba(214, 41, 41, 0.12);
|
||||||
|
backdrop-filter: blur(0.6px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:active {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-placeholder {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-icon-circle {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-plus {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-top: -4rpx;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
opacity: 0.85;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-preview {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-remove {
|
||||||
|
position: absolute;
|
||||||
|
top: 6rpx;
|
||||||
|
right: 6rpx;
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-remove-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 1;
|
||||||
|
margin-top: -4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文字输入 */
|
||||||
|
.input-area {
|
||||||
|
height: 100rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
background: linear-gradient(
|
||||||
|
168.9deg,
|
||||||
|
rgba(154, 146, 255, 0.48) 4.58%,
|
||||||
|
rgba(255, 202, 229, 0.48) 43.91%,
|
||||||
|
rgba(255, 250, 253, 0.475) 76.13%,
|
||||||
|
rgba(211, 209, 255, 0.48) 91.61%
|
||||||
|
);
|
||||||
|
box-shadow: 2rpx 4rpx 1.8rpx rgba(214, 41, 41, 0.12);
|
||||||
|
backdrop-filter: blur(0.6px);
|
||||||
|
padding: 16rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-placeholder {
|
||||||
|
color: rgba(255, 249, 231, 0.6) !important;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮 */
|
||||||
|
.button-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
padding: 0 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
width: 192rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 36rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 1rpx 2rpx 5.1rpx rgba(225, 33, 33, 0.32);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button--disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
opacity: 0.82;
|
||||||
|
background: linear-gradient(
|
||||||
|
116deg,
|
||||||
|
rgba(246, 233, 180, 0.2) -19.66%,
|
||||||
|
rgba(240, 131, 153, 0.2) 70.92%,
|
||||||
|
rgba(213, 107, 109, 0.17) 138.79%,
|
||||||
|
rgba(105, 209, 230, 0.14) 198.61%
|
||||||
|
);
|
||||||
|
box-shadow: 0 1px 4px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-button {
|
||||||
|
opacity: 0.82;
|
||||||
|
background: linear-gradient(
|
||||||
|
116deg,
|
||||||
|
#f6e9b4 -19.66%,
|
||||||
|
#f08399 70.92%,
|
||||||
|
rgba(213, 107, 109, 0.84) 138.79%,
|
||||||
|
rgba(105, 209, 230, 0.69) 198.61%
|
||||||
|
);
|
||||||
|
box-shadow: 1px 2px 5.1px 0 rgba(225, 33, 33, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(191, 61, 61, 0.84);
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画 */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-active,
|
||||||
|
.slide-up-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-from,
|
||||||
|
.slide-up-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(80rpx);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
678
frontend/pages/components/ReportModal.vue
Normal file
678
frontend/pages/components/ReportModal.vue
Normal file
@ -0,0 +1,678 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
v-if="visible"
|
||||||
|
class="modal-wrapper"
|
||||||
|
@touchmove.stop.prevent
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<transition name="fade">
|
||||||
|
<view
|
||||||
|
v-if="visible"
|
||||||
|
class="modal-mask"
|
||||||
|
@touchstart.stop="handleMaskTouchStart"
|
||||||
|
@click.stop="handleMaskClick"
|
||||||
|
></view>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<transition name="slide-up">
|
||||||
|
<view v-if="visible" class="modal-container" @click.stop>
|
||||||
|
<!-- 背景图片(紫色渐变 + 装饰) -->
|
||||||
|
<image
|
||||||
|
class="modal-background"
|
||||||
|
src="/static/assetDetail/Vector.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<image
|
||||||
|
class="title-background"
|
||||||
|
src="/static/assetDetail/topfans-jb.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
|
||||||
|
<!-- 蒙层(增强对比) -->
|
||||||
|
<view class="modal-overlay"></view>
|
||||||
|
|
||||||
|
<!-- 关闭按钮 -->
|
||||||
|
<!-- <view class="close-button" @touchstart.stop="handleCloseTouchStart" @touchend.stop="handleCloseTouchEnd"
|
||||||
|
@click="handleCloseClick">
|
||||||
|
<image class="back-icon" src="/static/starbookcontent/tuichu.png" mode="aspectFit"></image>
|
||||||
|
</view> -->
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<view class="modal-content" @touchstart.stop @touchend.stop @click.stop>
|
||||||
|
<!-- 标题 -->
|
||||||
|
<view class="title-row">
|
||||||
|
<!-- <text class="modal-title">举报</text> -->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 举报类型选项 3x3 网格 -->
|
||||||
|
<view class="options-grid">
|
||||||
|
<view
|
||||||
|
v-for="(opt, idx) in reportOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
class="option-item"
|
||||||
|
:class="{ 'option-item--active': selectedReason === opt.value }"
|
||||||
|
@tap="handleSelectOption(opt)"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
class="option-text"
|
||||||
|
:class="{ 'option-text--active': selectedReason === opt.value }"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 上传图片 -->
|
||||||
|
<view class="upload-area" @tap="handleChooseImage">
|
||||||
|
<view v-if="!imagePath" class="upload-placeholder">
|
||||||
|
<view class="upload-icon-circle">
|
||||||
|
<text class="upload-plus">+</text>
|
||||||
|
</view>
|
||||||
|
<text class="upload-label">上传图片</text>
|
||||||
|
</view>
|
||||||
|
<view v-else class="upload-preview">
|
||||||
|
<image
|
||||||
|
class="upload-image"
|
||||||
|
:src="imagePath"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<view class="upload-remove" @tap.stop="handleRemoveImage">
|
||||||
|
<text class="upload-remove-text">×</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 文字输入 -->
|
||||||
|
<view class="input-area">
|
||||||
|
<textarea
|
||||||
|
class="input-field"
|
||||||
|
v-model="content"
|
||||||
|
placeholder="请输入具体内容"
|
||||||
|
placeholder-class="input-placeholder"
|
||||||
|
:maxlength="200"
|
||||||
|
auto-height
|
||||||
|
></textarea>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 按钮 -->
|
||||||
|
<view class="button-row">
|
||||||
|
<view class="action-button back-button" @tap="handleCloseClick">
|
||||||
|
<text class="action-text">返回</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="action-button confirm-button"
|
||||||
|
:class="{ 'action-button--disabled': !canSubmit }"
|
||||||
|
@tap="handleConfirm"
|
||||||
|
>
|
||||||
|
<text class="action-text">{{
|
||||||
|
submitting ? "提交中…" : "确定"
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</transition>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { submitReportApi, uploadLocalFileToOss } from "@/utils/api.js";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
assetId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["close", "submit"]);
|
||||||
|
|
||||||
|
// 举报类型选项(与 Figma 设计一致)
|
||||||
|
const reportOptions = [
|
||||||
|
{ value: "malicious_distortion", label: "恶意丑化" },
|
||||||
|
{ value: "baiting", label: "拉踩引战" },
|
||||||
|
{ value: "plagiarism", label: "抄袭盗图" },
|
||||||
|
{ value: "slander", label: "抹黑造谣" },
|
||||||
|
{ value: "bad_guidance", label: "不良导向" },
|
||||||
|
{ value: "legal_red_line", label: "法规红线" },
|
||||||
|
{ value: "illegal_ads", label: "违规广告" },
|
||||||
|
{ value: "personal_dislike", label: "个人反感" },
|
||||||
|
{ value: "other", label: "其他" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
const selectedReason = ref("");
|
||||||
|
const content = ref("");
|
||||||
|
const imagePath = ref("");
|
||||||
|
const imageUrl = ref("");
|
||||||
|
const submitting = ref(false);
|
||||||
|
|
||||||
|
// 是否可提交:必须选择举报类型
|
||||||
|
const canSubmit = computed(() => {
|
||||||
|
return !!selectedReason.value && !submitting.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 关闭相关:与 LikeUsersModal 保持一致的触摸处理,防止误触穿透
|
||||||
|
let maskTouchStartTime = 0;
|
||||||
|
const handleMaskTouchStart = (e) => {
|
||||||
|
maskTouchStartTime = Date.now();
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
const handleMaskClick = () => {
|
||||||
|
emit("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
let closeTouchStartTime = 0;
|
||||||
|
let closeTouchLocked = false;
|
||||||
|
const handleCloseTouchStart = (e) => {
|
||||||
|
closeTouchStartTime = Date.now();
|
||||||
|
closeTouchLocked = false;
|
||||||
|
};
|
||||||
|
const handleCloseTouchEnd = (e) => {
|
||||||
|
if (closeTouchLocked) return;
|
||||||
|
closeTouchLocked = true;
|
||||||
|
const touchDuration = Date.now() - closeTouchStartTime;
|
||||||
|
if (touchDuration < 300) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleResetAndClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleCloseClick = (e) => {
|
||||||
|
if (e && e.preventDefault) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
handleResetAndClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetAndClose = () => {
|
||||||
|
// 关闭前重置表单
|
||||||
|
selectedReason.value = "";
|
||||||
|
content.value = "";
|
||||||
|
imagePath.value = "";
|
||||||
|
imageUrl.value = "";
|
||||||
|
submitting.value = false;
|
||||||
|
emit("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择举报类型
|
||||||
|
const handleSelectOption = (opt) => {
|
||||||
|
selectedReason.value = opt.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择图片
|
||||||
|
const handleChooseImage = () => {
|
||||||
|
uni.chooseImage({
|
||||||
|
count: 1,
|
||||||
|
sizeType: ["compressed"],
|
||||||
|
sourceType: ["album", "camera"],
|
||||||
|
success: (res) => {
|
||||||
|
const tempFile = res.tempFilePaths?.[0];
|
||||||
|
if (tempFile) {
|
||||||
|
imagePath.value = tempFile;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.warn("[ReportModal] chooseImage fail", err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除图片
|
||||||
|
const handleRemoveImage = () => {
|
||||||
|
imagePath.value = "";
|
||||||
|
imageUrl.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认提交
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
if (!canSubmit.value) {
|
||||||
|
if (!selectedReason.value) {
|
||||||
|
uni.showToast({ title: "请选择举报类型", icon: "none" });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
// 如果选择了本地图片,先上传到 OSS
|
||||||
|
let uploadedUrl = "";
|
||||||
|
if (imagePath.value && !imageUrl.value) {
|
||||||
|
try {
|
||||||
|
const upRes = await uploadLocalFileToOss(imagePath.value, {
|
||||||
|
type: "asset",
|
||||||
|
});
|
||||||
|
uploadedUrl = upRes?.imageUrl || "";
|
||||||
|
imageUrl.value = uploadedUrl;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ReportModal] upload image fail", e);
|
||||||
|
uni.showToast({ title: "图片上传失败", icon: "none" });
|
||||||
|
submitting.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用举报 API
|
||||||
|
const res = await submitReportApi({
|
||||||
|
asset_id: props.assetId,
|
||||||
|
reason: selectedReason.value,
|
||||||
|
content: content.value || "",
|
||||||
|
image_url: uploadedUrl || imageUrl.value || "",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res && res.code === 0) {
|
||||||
|
uni.showToast({ title: "举报成功", icon: "success" });
|
||||||
|
emit("submit", { reason: selectedReason.value });
|
||||||
|
handleResetAndClose();
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: (res && res.message) || "举报失败",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ReportModal] submit fail", e);
|
||||||
|
uni.showToast({ title: e?.message || "举报失败", icon: "none" });
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
position: relative;
|
||||||
|
width: 680rpx;
|
||||||
|
max-height: 85vh;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.title-background {
|
||||||
|
position: absolute;
|
||||||
|
top: -224rpx;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 80%;
|
||||||
|
z-index: 0;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(75, 30, 110, 0.35) 0%,
|
||||||
|
rgba(131, 75, 158, 0.55) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 关闭按钮 */
|
||||||
|
.close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 16rpx;
|
||||||
|
right: 16rpx;
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-icon {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容区域 */
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 248rpx 36rpx 36rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题 */
|
||||||
|
.title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9 个举报类型 - 3 列网格 */
|
||||||
|
.options-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16rpx 14rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
flex: 0 0 calc((100% - 28rpx) / 3);
|
||||||
|
height: 64rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
/* background: linear-gradient(
|
||||||
|
168.9deg,
|
||||||
|
rgba(154, 146, 255, 0.48) 4.58%,
|
||||||
|
rgba(255, 202, 229, 0.48) 43.91%,
|
||||||
|
rgba(255, 250, 253, 0.475) 76.13%,
|
||||||
|
rgba(211, 209, 255, 0.48) 91.61%
|
||||||
|
);
|
||||||
|
box-shadow:
|
||||||
|
0 4rpx 4rpx rgba(182, 48, 48, 0.25),
|
||||||
|
0 2rpx 3rpx rgba(212, 39, 39, 0.31);
|
||||||
|
backdrop-filter: blur(0.6px); */
|
||||||
|
background: url("/static/assetDetail/text-bj.png") center no-repeat;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 4px 0 rgba(182, 48, 48, 0.25),
|
||||||
|
0 2px 0px 0 rgba(212, 39, 39, 0.31);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item--active {
|
||||||
|
background: linear-gradient(
|
||||||
|
165deg,
|
||||||
|
#f0e4b1 0%,
|
||||||
|
#f08399 50%,
|
||||||
|
#b94e73 90%,
|
||||||
|
#834b9e 100%
|
||||||
|
);
|
||||||
|
box-shadow:
|
||||||
|
0 4rpx 12rpx rgba(255, 143, 158, 0.4),
|
||||||
|
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.45);
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text--active {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上传图片区域 */
|
||||||
|
.upload-area {
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
background: linear-gradient(
|
||||||
|
168.9deg,
|
||||||
|
rgba(154, 146, 255, 0.48) 4.58%,
|
||||||
|
rgba(255, 202, 229, 0.48) 43.91%,
|
||||||
|
rgba(255, 250, 253, 0.475) 76.13%,
|
||||||
|
rgba(211, 209, 255, 0.48) 91.61%
|
||||||
|
);
|
||||||
|
box-shadow: 2rpx 4rpx 1.8rpx rgba(214, 41, 41, 0.12);
|
||||||
|
backdrop-filter: blur(0.6px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:active {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-placeholder {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-icon-circle {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-plus {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-top: -4rpx;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
opacity: 0.85;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-preview {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-remove {
|
||||||
|
position: absolute;
|
||||||
|
top: 6rpx;
|
||||||
|
right: 6rpx;
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-remove-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 1;
|
||||||
|
margin-top: -4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文字输入 */
|
||||||
|
.input-area {
|
||||||
|
height: 100rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
background: linear-gradient(
|
||||||
|
168.9deg,
|
||||||
|
rgba(154, 146, 255, 0.48) 4.58%,
|
||||||
|
rgba(255, 202, 229, 0.48) 43.91%,
|
||||||
|
rgba(255, 250, 253, 0.475) 76.13%,
|
||||||
|
rgba(211, 209, 255, 0.48) 91.61%
|
||||||
|
);
|
||||||
|
box-shadow: 2rpx 4rpx 1.8rpx rgba(214, 41, 41, 0.12);
|
||||||
|
backdrop-filter: blur(0.6px);
|
||||||
|
padding: 16rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(0, 0, 0, 0.84);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-placeholder {
|
||||||
|
color: rgba(255, 249, 231, 0.6) !important;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮 */
|
||||||
|
.button-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 24rpx;
|
||||||
|
padding: 0 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
/* flex: 1; */
|
||||||
|
width: 192rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 36rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 1rpx 2rpx 5.1rpx rgba(225, 33, 33, 0.32);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button--disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
opacity: 0.82;
|
||||||
|
background: linear-gradient(
|
||||||
|
116deg,
|
||||||
|
rgba(246, 233, 180, 0.2) -19.66%,
|
||||||
|
rgba(240, 131, 153, 0.2) 70.92%,
|
||||||
|
rgba(213, 107, 109, 0.17) 138.79%,
|
||||||
|
rgba(105, 209, 230, 0.14) 198.61%
|
||||||
|
);
|
||||||
|
box-shadow: 0 1px 4px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-button {
|
||||||
|
opacity: 0.82;
|
||||||
|
background: linear-gradient(
|
||||||
|
116deg,
|
||||||
|
#f6e9b4 -19.66%,
|
||||||
|
#f08399 70.92%,
|
||||||
|
rgba(213, 107, 109, 0.84) 138.79%,
|
||||||
|
rgba(105, 209, 230, 0.69) 198.61%
|
||||||
|
);
|
||||||
|
box-shadow: 1px 2px 5.1px 0 rgba(225, 33, 33, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff9e7;
|
||||||
|
font-family: "yt", sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: -1rpx 1rpx 4rpx rgba(191, 61, 61, 0.84);
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画 */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-active,
|
||||||
|
.slide-up-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-from,
|
||||||
|
.slide-up-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(80rpx);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -20,11 +20,20 @@
|
|||||||
:qrcodeUrl="shareQrcodeUrl"
|
:qrcodeUrl="shareQrcodeUrl"
|
||||||
@close="showShareModal = false"
|
@close="showShareModal = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 举报弹窗 -->
|
||||||
|
<ReportModal
|
||||||
|
:visible="showReportModal"
|
||||||
|
:assetId="assetId"
|
||||||
|
@close="showReportModal = false"
|
||||||
|
@submit="handleReportSubmit"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import ShareModal from './ShareModal.vue';
|
import ShareModal from './ShareModal.vue';
|
||||||
|
import ReportModal from './ReportModal.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 资产拥有者的昵称
|
// 资产拥有者的昵称
|
||||||
@ -68,6 +77,9 @@ const showShareModal = ref(false);
|
|||||||
const shareCoverUrl = ref('');
|
const shareCoverUrl = ref('');
|
||||||
const shareQrcodeUrl = ref('');
|
const shareQrcodeUrl = ref('');
|
||||||
|
|
||||||
|
// 举报弹窗状态
|
||||||
|
const showReportModal = ref(false);
|
||||||
|
|
||||||
// 分享
|
// 分享
|
||||||
const handleShare = () => {
|
const handleShare = () => {
|
||||||
shareCoverUrl.value = props.coverUrl;
|
shareCoverUrl.value = props.coverUrl;
|
||||||
@ -76,18 +88,18 @@ const handleShare = () => {
|
|||||||
showShareModal.value = true;
|
showShareModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 举报
|
// 举报 - 打开举报弹窗
|
||||||
const handleReport = () => {
|
const handleReport = () => {
|
||||||
uni.showModal({
|
if (!props.assetId) {
|
||||||
title: '举报',
|
uni.showToast({ title: '藏品信息缺失', icon: 'none' });
|
||||||
content: '确定要举报该藏品吗?',
|
return;
|
||||||
success: (res) => {
|
}
|
||||||
if (res.confirm) {
|
showReportModal.value = true;
|
||||||
// TODO: 调用举报API
|
};
|
||||||
uni.showToast({ title: '举报成功', icon: 'success' });
|
|
||||||
}
|
// 举报提交完成回调(埋点/埋日志可用)
|
||||||
}
|
const handleReportSubmit = (payload) => {
|
||||||
});
|
console.log('[ShareReportButtons] report submitted', payload);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
BIN
frontend/static/assetDetail/Vector.png
Normal file
BIN
frontend/static/assetDetail/Vector.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 195 KiB |
BIN
frontend/static/assetDetail/text-bj.png
Normal file
BIN
frontend/static/assetDetail/text-bj.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
BIN
frontend/static/assetDetail/topfans-fk.png
Normal file
BIN
frontend/static/assetDetail/topfans-fk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
BIN
frontend/static/assetDetail/topfans-jb.png
Normal file
BIN
frontend/static/assetDetail/topfans-jb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
BIN
frontend/static/background/profilebj.png
Normal file
BIN
frontend/static/background/profilebj.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 305 KiB |
@ -1018,3 +1018,30 @@ export const dashboardApi = {
|
|||||||
getUpgradeProgress: (starId) => dashboardRequest('/upgrade-progress', { star_id: starId }),
|
getUpgradeProgress: (starId) => dashboardRequest('/upgrade-progress', { star_id: starId }),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 举报 ====================
|
||||||
|
// TODO: 等待后端接入资产举报接口 (建议路径: POST /api/v1/assets/:assetId/reports)
|
||||||
|
// 或通用举报接口 (建议路径: POST /api/v1/reports),参数:
|
||||||
|
// { target_type: 'asset', target_id, reason, content, image_url }
|
||||||
|
export function submitReportApi(data) {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/assets/reports',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 意见反馈 ====================
|
||||||
|
// 鉴权:由 gateway AuthMiddleware 强制 JWT
|
||||||
|
// 路径:POST /api/v1/feedback
|
||||||
|
// 参数:{ type, content, image_url }
|
||||||
|
// - type: 'functionality' | 'ui' | 'suggestion' | 'other'
|
||||||
|
// - content: 用户填写的具体描述(可空)
|
||||||
|
// - image_url: 截图 URL(可空,需先走 OSS 上传)
|
||||||
|
export function submitFeedbackApi(data) {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/feedback',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user