Files
rating/pages/release/index.vue
2026-05-20 15:58:04 +08:00

757 lines
17 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="page-container">
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="nav-side left" @tap="goBack">
<text class="back-icon"></text>
</view>
<text class="nav-title">发起评分</text>
<view class="nav-side right" @tap="handlePreview">
<text class="preview-text">预览</text>
</view>
</view>
</view>
<view :style="{ height: statusBarHeight + 44 + 'px' }"></view>
<view class="content-wrap">
<view class="topic-editor card-block">
<textarea
v-model="formData.topic"
class="topic-input"
maxlength="40"
placeholder="输入一个有争议的话题..."
placeholder-class="topic-placeholder"
auto-height
/>
<textarea
v-model="formData.description"
class="desc-input"
maxlength="120"
placeholder="简单介绍一下这个评分话题..."
placeholder-class="desc-placeholder"
auto-height
/>
</view>
<view class="cover-card" :class="{ 'has-cover': formData.cover }" @tap="chooseCover">
<image
v-if="formData.cover"
:src="formData.cover"
class="cover-image"
mode="aspectFill"
/>
<view v-else class="cover-placeholder">
<view class="cover-icon"></view>
<text class="cover-text">上传封面图</text>
</view>
</view>
<view class="category-group">
<view
v-for="category in categories"
:key="category"
class="category-chip"
:class="{ active: formData.category === category }"
@tap="formData.category = category"
>
{{ category }}
</view>
</view>
<view class="section-header">
<text class="section-title">评分对象</text>
<text class="section-count">{{ participants.length }}/20</text>
</view>
<view class="participant-list">
<view
v-for="(item, index) in participants"
:key="item.id"
class="participant-card"
>
<view class="participant-main">
<view class="participant-avatar-wrap" @tap="chooseParticipantAvatar(index)">
<image
v-if="item.avatar"
:src="item.avatar"
class="participant-avatar"
mode="aspectFill"
/>
<view v-else class="participant-avatar empty">
<view class="camera-small"></view>
</view>
</view>
<view class="participant-info">
<input
v-model="item.name"
class="participant-name"
maxlength="12"
placeholder="输入名字"
placeholder-class="participant-placeholder"
/>
<input
v-model="item.desc"
class="participant-desc"
maxlength="24"
placeholder="一句话简介"
placeholder-class="participant-placeholder"
/>
</view>
</view>
<view class="participant-actions">
<text class="drag-icon"></text>
<text class="delete-icon" @tap="removeParticipant(item.id)">🗑</text>
</view>
</view>
<view class="add-participant" @tap="addParticipant">
<text class="add-plus">+</text>
<text class="add-text">添加评分对象</text>
</view>
</view>
<view class="settings-card card-block">
<view class="setting-row">
<view class="setting-info">
<text class="setting-title">允许用户锐评</text>
<text class="setting-desc">开启后用户可以对评分对象发表评论</text>
</view>
<switch
:checked="formData.allowComment"
color="#4d44f1"
@change="formData.allowComment = $event.detail.value"
/>
</view>
<view class="setting-row">
<view class="setting-info">
<text class="setting-title">允许开启PK</text>
<text class="setting-desc">启用后系统会自动生成对象间的对比PK</text>
</view>
<switch
:checked="formData.allowPk"
color="#4d44f1"
@change="formData.allowPk = $event.detail.value"
/>
</view>
<view class="setting-row last">
<view class="setting-info">
<text class="setting-title">匿名评分</text>
<text class="setting-desc">用户可以隐藏个人身份进行打分</text>
</view>
<switch
:checked="formData.anonymous"
color="#4d44f1"
@change="formData.anonymous = $event.detail.value"
/>
</view>
</view>
</view>
<view class="footer-bar">
<button class="submit-btn" @tap="handleSubmit">发布全民评分</button>
</view>
</view>
</template>
<script setup>
import { reactive, ref } from "vue";
import { getStatusBarHeight } from "@/utils/system";
import { releaseTopic } from "@/api/release";
import { smartNavigateBack } from "@/utils/page";
const statusBarHeight = ref(getStatusBarHeight() || 0);
const categories = ["历史", "数码", "城市", "景点", "动漫", "影视"];
const formData = reactive({
topic: "",
description: "",
cover: "",
category: "历史",
tags: ["历史", "皇帝"],
allowComment: true,
allowPk: true,
anonymous: false,
});
const tagInput = ref("");
const participants = ref([
{
id: 1,
name: "秦始皇",
desc: "一句话简介",
avatar: "",
},
{
id: 2,
name: "汉武帝",
desc: "开疆拓土,大汉盛世",
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=hanwudi",
},
]);
const goBack = () => {
smartNavigateBack();
};
const handlePreview = () => {
uni.showToast({
title: "预览功能待接入",
icon: "none",
});
};
const chooseCover = () => {
uni.chooseImage({
count: 1,
sizeType: ["original", "compressed"],
sourceType: ["album"],
success: (res) => {
const path = res.tempFilePaths[0];
// #ifdef MP-ALIPAY
// 支付宝小程序不支持 cropImage直接使用原图
onAvatarSelect(path);
// #endif
// #ifndef MP-ALIPAY
// 调用系统裁剪,强制 1:1
uni.cropImage({
src: path,
aspectRatio: "1:1",
success: (cropRes) => {
onAvatarSelect(cropRes.tempFilePath);
},
fail: (err) => {
console.warn("Crop failed or cancelled", err);
onAvatarSelect(path);
},
});
// #endif
},
fail: (err) => {
console.warn("Crop failed or cancelled", err);
},
});
};
const chooseParticipantAvatar = (index) => {
uni.chooseImage({
count: 1,
success: (res) => {
participants.value[index].avatar = res.tempFilePaths[0];
},
});
};
const handleAddTag = () => {
const value = tagInput.value.trim();
if (!value) return;
if (formData.tags.includes(value)) {
uni.showToast({
title: "标签已存在",
icon: "none",
});
return;
}
formData.tags.push(value);
tagInput.value = "";
};
const removeTag = (index) => {
formData.tags.splice(index, 1);
};
const addParticipant = () => {
if (participants.value.length >= 20) {
uni.showToast({
title: "最多添加20个对象",
icon: "none",
});
return;
}
participants.value.push({
id: Date.now(),
name: "",
desc: "",
avatar: "",
});
};
const removeParticipant = (id) => {
if (participants.value.length <= 1) {
uni.showToast({
title: "至少保留1个对象",
icon: "none",
});
return;
}
participants.value = participants.value.filter((item) => item.id !== id);
};
const handleSubmit = async () => {
if (!formData.topic.trim()) {
uni.showToast({ title: '请输入话题', icon: 'none' });
return;
}
if (!formData.cover) {
uni.showToast({ title: '请上传封面图', icon: 'none' });
return;
}
// 检查是否有空的对象名称
const hasEmptyName = participants.value.some(p => !p.name.trim());
if (hasEmptyName) {
uni.showToast({ title: '请完善评分对象名称', icon: 'none' });
return;
}
uni.showLoading({ title: '发布中...' });
try {
const payload = {
...formData,
participants: participants.value,
};
await releaseTopic(payload);
uni.hideLoading();
uni.showToast({
title: "发布成功",
icon: "success",
});
smartNavigateBack({ delay: 1500 });
} catch (error) {
uni.hideLoading();
uni.showToast({
title: "发布失败,请重试",
icon: "none",
});
console.error("发布失败:", error);
}
};
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: #f8f9fc; /* 整体背景调亮 */
padding-bottom: 180rpx;
box-sizing: border-box;
}
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: rgba(244, 245, 251, 0.96);
backdrop-filter: blur(18rpx);
}
.nav-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
}
.nav-side {
width: 120rpx;
display: flex;
align-items: center;
}
.nav-side.right {
justify-content: flex-end;
}
.back-icon {
width: 36rpx;
height: 36rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23222' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='15 18 9 12 15 6'%3E%3C/polyline%3E%3C/svg%3E");
background-size: cover;
}
.nav-title {
font-size: 34rpx;
font-weight: 700;
color: #171717;
}
.preview-text {
font-size: 28rpx;
color: #5f63ff;
}
.content-wrap {
padding: 24rpx;
}
/* 统一的卡片风格 */
.card-block {
background: #ffffff;
border-radius: 36rpx; /* 增大圆角,与设计图更统一 */
box-shadow: 0 4rpx 20rpx rgba(72, 82, 130, 0.03); /* 柔和的阴影 */
margin-bottom: 32rpx; /* 增加块级间距 */
border: 2rpx solid #eef0f7; /* 统一的极淡边框 */
}
.topic-editor {
padding: 32rpx; /* 增加内边距 */
margin-bottom: 24rpx;
}
.topic-input,
.desc-input {
width: 100%;
min-height: 52rpx;
color: #232323;
}
.topic-input {
font-size: 44rpx; /* 稍微减小标题字号,显得更精致 */
font-weight: 800; /* 加粗 */
margin-bottom: 16rpx; /* 增加行距 */
}
.desc-input {
font-size: 28rpx;
color: #666;
line-height: 1.5;
}
.topic-placeholder {
font-size: 44rpx;
font-weight: 800;
color: #c1c2d3;
}
.desc-placeholder {
font-size: 28rpx;
color: #b8b9c9;
}
.cover-card {
height: 340rpx;
border-radius: 36rpx;
background: #e9ecf5;
overflow: hidden;
margin-bottom: 28rpx;
border: 2rpx dashed #d1d5db;
}
.cover-card.has-cover {
border: none; /* 有图片时去掉虚线框 */
}
.cover-image {
width: 100%;
height: 100%;
}
.cover-placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #77728b;
}
.cover-icon {
width: 80rpx; /* 图标稍微放大 */
height: 80rpx;
margin-bottom: 24rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2377728b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'%3E%3C/circle%3E%3Cpolyline points='21 15 16 10 5 21'%3E%3C/polyline%3E%3C/svg%3E"); /* 替换为更像图片的占位图标 */
background-size: cover;
}
.cover-text {
font-size: 30rpx;
}
.category-group {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 24rpx;
}
.category-chip {
min-width: 88rpx;
padding: 14rpx 32rpx; /* 增加内边距 */
border-radius: 999rpx;
background: #e9ecf5; /* 背景调浅一些,更接近设计图 */
color: #5c6176; /* 字体颜色调浅 */
font-size: 28rpx; /* 字体调大 */
font-weight: 500;
text-align: center;
transition: all 0.2s ease;
}
.category-chip.active {
background: linear-gradient(135deg, #6c4af2, #883cf3); /* 更接近设计图的紫色渐变 */
color: #fff;
font-weight: 700;
box-shadow: 0 8rpx 20rpx rgba(108, 74, 242, 0.3); /* 调整阴影颜色 */
}
.tag-box {
padding: 24rpx 32rpx; /* 增加内边距 */
margin-bottom: 32rpx;
}
.selected-tags {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12rpx;
}
.tag-chip {
padding: 8rpx 20rpx;
background-color: #f0f4ff; /* 浅蓝色背景 */
border-radius: 8rpx;
color: #4e46e5;
font-size: 26rpx;
display: flex;
align-items: center;
}
.tag-chip text {
margin-right: 8rpx;
}
.tag-chip::after {
content: "×";
font-size: 28rpx;
color: #8c89e8;
margin-left: 4rpx;
}
.tag-input {
flex: 1;
min-width: 180rpx;
height: 52rpx;
font-size: 26rpx;
color: #555;
}
.tag-input-placeholder {
color: #b9b7c7;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx; /* 增加底部间距 */
padding: 0 8rpx; /* 标题与内容对齐 */
}
.section-title {
font-size: 34rpx; /* 调整大小 */
font-weight: 800; /* 加粗 */
color: #171717;
}
.section-count {
min-width: 78rpx;
height: 46rpx;
line-height: 46rpx;
text-align: center;
border-radius: 14rpx;
background: #e6e9f7;
color: #5c6176;
font-size: 26rpx;
}
.participant-list {
margin-bottom: 32rpx;
}
.participant-card {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
border-radius: 36rpx; /* 更圆润的边角 */
padding: 32rpx 28rpx; /* 增加内边距 */
margin-bottom: 24rpx;
border: 2rpx solid #eef0f7; /* 增加细微的描边,更接近设计图质感 */
box-shadow: 0 4rpx 20rpx rgba(72, 82, 130, 0.03); /* 阴影调弱 */
}
.participant-main {
display: flex;
align-items: center;
flex: 1;
}
.participant-avatar-wrap {
margin-right: 20rpx;
}
.participant-avatar {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
background: #e8ebf7;
}
.participant-avatar.empty {
display: flex;
align-items: center;
justify-content: center;
border: 2rpx dashed #b5bad0; /* 加深虚线颜色 */
background: #f8f9fc; /* 空状态背景色 */
}
.camera-small {
width: 38rpx;
height: 38rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a90a6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z'%3E%3C/path%3E%3Ccircle cx='12' cy='13' r='4'%3E%3C/circle%3E%3C/svg%3E");
background-size: cover;
}
.participant-info {
flex: 1;
}
.participant-name,
.participant-desc {
width: 100%;
}
.participant-name {
height: 42rpx;
font-size: 30rpx;
font-weight: 700;
color: #202020;
margin-bottom: 10rpx;
}
.participant-desc {
height: 38rpx;
font-size: 24rpx;
color: #676c7f;
}
.participant-placeholder {
color: #b6b9c7;
}
.participant-actions {
width: 56rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16rpx;
margin-left: 16rpx;
}
.drag-icon {
font-size: 34rpx;
color: #b2b4c5;
line-height: 1;
}
.delete-icon {
font-size: 32rpx; /* 图标放大 */
color: #ff6b6b; /* 明确的删除红色 */
line-height: 1;
padding: 8rpx; /* 增加点击区域 */
}
.add-participant {
height: 120rpx; /* 增加高度 */
border: 2rpx dashed #c0c4d6; /* 边框颜色更明显 */
border-radius: 36rpx; /* 圆角一致 */
display: flex;
align-items: center;
justify-content: center;
color: #888d9e; /* 字体颜色微调 */
gap: 16rpx; /* 间距变大 */
background: #f8f9fc; /* 添加底色 */
}
.add-plus {
font-size: 38rpx;
line-height: 1;
}
.add-text {
font-size: 28rpx;
}
.settings-card {
padding: 16rpx 32rpx; /* 增加内边距 */
}
.setting-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx 0; /* 增加行间距 */
border-bottom: 2rpx solid #f0f1f7;
}
.setting-row.last {
border-bottom: none;
}
.setting-info {
flex: 1;
padding-right: 20rpx;
}
.setting-title {
display: block;
font-size: 30rpx; /* 字体稍微调小 */
font-weight: 700; /* 加粗 */
color: #181818;
margin-bottom: 12rpx; /* 增加与描述的间距 */
}
.setting-desc {
display: block;
font-size: 24rpx; /* 描述字体稍微调大 */
color: #8c92a4; /* 颜色调浅 */
line-height: 1.5;
}
.footer-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 24rpx 24rpx calc(24rpx + env(safe-area-inset-bottom));
background: linear-gradient(180deg, rgba(244, 245, 251, 0) 0%, rgba(244, 245, 251, 0.98) 34%);
}
.submit-btn {
height: 100rpx;
line-height: 100rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #5c43f5, #7a2cf3); /* 更接近设计图的深紫色 */
color: #fff;
font-size: 34rpx;
font-weight: 700;
box-shadow: 0 16rpx 32rpx rgba(100, 60, 240, 0.3); /* 加强底部按钮阴影 */
}
.submit-btn::after {
border: none;
}
</style>